免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。
前言
本文为B站国资社畜大佬《你想有多PWN》视频教程的学习实践笔记,感兴趣的可以点击阅读原文去看视频教程。
缓冲区溢出
GDB
-
启动程序,指定任何由程序解释的参数,并运行程序指定的环境变量。 -
让已停止的程序在开发者设定的条件下继续运行。 -
可以在自由设定的地点停止程序的执行。 -
检查在程序停止时什么导致了停止。 -
更改程序,可以测试其他执行路径,而不是重新启动并重新输入数据。
缓冲区溢出例子
给出一段教程中存在缓冲区溢出问题的代码:
char sh[]="/bin/sh";
int init_func(){
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
return 0;
}
int func(char *cmd){
system(cmd);
return 0;
}
int main(){
char a[8] = {};
char b[8] = {};
//char a[1] = {'b'};
puts("input:");
gets(a);
printf(a);
if(b[0]=='a'){
func(sh);
}
return 0;
}
可以使用gcc来编译:
gcc -o vuln -g question_1.c
开启调试之旅
-
输入以下命令进入gdb调试:
gdb ./vuln
2. 换成intel语法:
set disassembly-flavor intel
3. 输入以下指令你个找到main函数入口点:
disassemble main
4. 我们重点讲讲跟漏洞利用有关的汇编指令:
0x00000000000012af <+58>: lea rax,[rbp-0x18]
0x00000000000012b3 <+62>: mov rdi,rax
0x00000000000012b6 <+65>: mov eax,0x0
0x00000000000012bb <+70>: call 0x10e0 <gets@plt>
这几行代码是程序调用 gets(a)
。因为 gets()
函数不检查输入长度,所以当输入超出变量 a
的大小时,会引起栈溢出。在这种情况下,溢出数据可以覆盖栈上的其他变量,甚至是返回地址。
-
首先将rbp寄存器的值减去0x18然后保存到rax寄存器中,这里指本地数组a的开始地址;
-
将rax寄存器的值复制到rdi寄存器中,
rdi
在x86_64系统V ABI调用约定中用于传递函数的第一个参数; -
将eax寄存器的值清零,这条指令因为gets函数没有返回值因此不是必须的;
-
然后调用位于gets函数;
再看gets函数:
0x00000000000012d1 <+92>: movzx eax,BYTE PTR [rbp-0x10]
0x00000000000012d5 <+96>: cmp al,0x61
0x00000000000012d7 <+98>: jne 0x12e8 <main+115>
这几行代码是检查变量 b[0]
是否等于 'a'
(ASCII码为 0x61)。如果是,则执行条件分支内的 func(sh)
。通过栈溢出,可以控制 b[0]
的值,满足条件调用 system("/bin/sh")
获取shell。
-
它将字节(8位)位于
rbp-0x10
(即数组b
的第一个元素)的内容移动到eax
寄存器的低字节(al
),其他字节填充为零; -
比较
al
(eax
的低8位)和字符'a'
的ASCII值(0x61)。这是逐字节比较操作,只考虑al
中的内容; -
jne
(跳转如果不相等)指令会检查前一个cmp
指令的结果。如果al
中的值不等于0x61,那么执行跳转到main+115
处的代码。如果比较结果是al
等于0x61,程序则不跳转,继续顺序执行后面的指令。
4. 理解了漏洞利用的方法,那么我们开始打断点,在gdb中,要通过break指令来打上断点,我们可以在地址main+58处打上断点,准备观察a数组和b数组的值:
b *main+58
5. 然后输入run启动程序,此时gdb就会main入口处:
6. 然后输入continue或者c就可以达到我们下的断点:
7. 现在,我们可以通过x指令来查看此时a数组和b数组的值:
x/gx $rbp-0x18
x/gx $rbp-0x10
8. 此时均为零,为了更方便我们一次输出20字节的值
x/20bx $rbp-0x18
9. 此时0x7fffffffd718这一行就是a数组的值,0x7fffffffd720这一行就是b数组的值,然后我们下断点,经过gets函数再看看:
10. 可以看到此时a数组的前三位已经变成了0x36,转换为ascii也就是6,对应了我们输入的666,那么,我们只需要输入超过8个长度,而且第9个长度必须是a,就能覆盖b数组的第一个元素,进入shell,重点就是main+98这个地方会不会跳转,如果不会跳转说明会进入shell:
11. 我们在main+98处打上断点,然后试一试:
12. 现在到了断点处,我们之间一步一步走看会不会跳转:
n
13. 可以看到,没有跳转,并且即将进入shell,我们看看内存:
14. b数组的第一个元素是a,符合预期,接着我们就可以continue看看是不是最终get shell了:
15. 大功告成!
原文始发于微信公众号(赛博安全狗):【零基础学习PWN】GDB调试缓冲区溢出程序
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论