最近学习到《深入理解计算机系统》这边书的3.12节的缓冲区溢出,于是写了个简单的测试代码演示了一下通过缓冲区溢出是如何神不知鬼不觉的运行一段代码的。
先上代码运行后再分析:
#include <stdio.h> void hit() { unsigned char buff[ 100 ] = { 0,0,0,0, //返回地址 ‘B‘,‘O‘,‘M‘,‘B‘,‘\0‘, 0x83,0xc4,0x80, //add $0xffffff80,%esp 0x8b,0x44,0x24,0x0c, //mov 0x0c(%esp),%eax 0xff,0xf0, //push %eax 0x55, //push %ebp 0x89,0xe5, //mov %esp,%ebp 0x83,0xec,0x18, //sub $0x18,%esp 0xc7,0x04,0x24,0,0,0,0, //movl $0x402080,(%esp) 0xe8,0,0,0,0, //call 401278 <_puts> 0xc9, //leave 0xc3, //ret }; *(unsigned int*)&buff[27] = (unsigned int)&buff[4]; *(unsigned int*)&buff[32] = (unsigned int)puts - (unsigned int)&buff[36]; *(unsigned int*)&buff[0] = *(unsigned int*)&buff[sizeof(buff)+12]; /* 缓冲区溢出:buff[sizeof(buff)+12]的数据被修改 * 然而编译的结果可以看到buff[sizeof(buff)+12]这个地址刚好是 * hit函数返回到main时所保存的main函数的下一条指令地址 * main函数的栈帧被破坏,结果导致错误指令的执行 */ *(unsigned int*)&buff[sizeof(buff)+12] = (unsigned int)&buff[9]; } void main() { /* * 如果栈帧不被破坏,则hit函数无任何意义 * 栈被破坏,神不知鬼不觉的运行了一个函数 puts("BOMB"); */ hit(); printf("\n\n%08x\n",puts); }
然后我在cygwin中编译运行这段代码:
gcc 版本 4.8.2 (GCC) hp@hp-PC ~/c $ gcc y.c hp@hp-PC ~/c $ ./a.exe BOMB 004012e8
从代码上看,hit函数什么也没做就是往buff内填充数据,然后是输出的puts函数的地址,但是运行后却发现多了一个BOMB的输出。
再来看看hit函数的汇编代码。
hp@hp-PC ~/c $ gcc -c y.c && objdump -d y.o y.o: 文件格式 pe-i386 Disassembly of section .text: 00000000 <_hit>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 57 push %edi 4: 53 push %ebx 5: 83 ec 70 sub $0x70,%esp 8: 8d 5d 94 lea -0x6c(%ebp),%ebx b: b8 00 00 00 00 mov $0x0,%eax 10: ba 19 00 00 00 mov $0x19,%edx 15: 89 df mov %ebx,%edi 17: 89 d1 mov %edx,%ecx 19: f3 ab rep stos %eax,%es:(%edi) 1b: c6 45 98 42 movb $0x42,-0x68(%ebp) 1f: c6 45 99 4f movb $0x4f,-0x67(%ebp) 23: c6 45 9a 4d movb $0x4d,-0x66(%ebp) 27: c6 45 9b 42 movb $0x42,-0x65(%ebp) 2b: c6 45 9d 83 movb $0x83,-0x63(%ebp) 2f: c6 45 9e c4 movb $0xc4,-0x62(%ebp) 33: c6 45 9f 80 movb $0x80,-0x61(%ebp) 37: c6 45 a0 8b movb $0x8b,-0x60(%ebp) 3b: c6 45 a1 44 movb $0x44,-0x5f(%ebp) 3f: c6 45 a2 24 movb $0x24,-0x5e(%ebp) 43: c6 45 a3 0c movb $0xc,-0x5d(%ebp) 47: c6 45 a4 ff movb $0xff,-0x5c(%ebp) 4b: c6 45 a5 f0 movb $0xf0,-0x5b(%ebp) 4f: c6 45 a6 55 movb $0x55,-0x5a(%ebp) 53: c6 45 a7 89 movb $0x89,-0x59(%ebp) 57: c6 45 a8 e5 movb $0xe5,-0x58(%ebp) 5b: c6 45 a9 83 movb $0x83,-0x57(%ebp) 5f: c6 45 aa ec movb $0xec,-0x56(%ebp) 63: c6 45 ab 18 movb $0x18,-0x55(%ebp) 67: c6 45 ac c7 movb $0xc7,-0x54(%ebp) 6b: c6 45 ad 04 movb $0x4,-0x53(%ebp) 6f: c6 45 ae 24 movb $0x24,-0x52(%ebp) 73: c6 45 b3 e8 movb $0xe8,-0x4d(%ebp) 77: c6 45 b8 c9 movb $0xc9,-0x48(%ebp) 7b: c6 45 b9 c3 movb $0xc3,-0x47(%ebp) 7f: 8d 45 94 lea -0x6c(%ebp),%eax 82: 83 c0 1b add $0x1b,%eax 85: 8d 55 94 lea -0x6c(%ebp),%edx 88: 83 c2 04 add $0x4,%edx 8b: 89 10 mov %edx,(%eax) 8d: 8d 45 94 lea -0x6c(%ebp),%eax 90: 83 c0 20 add $0x20,%eax 93: ba 00 00 00 00 mov $0x0,%edx 98: 8d 4d 94 lea -0x6c(%ebp),%ecx 9b: 83 c1 24 add $0x24,%ecx 9e: 29 ca sub %ecx,%edx a0: 89 10 mov %edx,(%eax) a2: 8d 45 94 lea -0x6c(%ebp),%eax a5: 8d 55 94 lea -0x6c(%ebp),%edx a8: 83 c2 70 add $0x70,%edx ab: 8b 12 mov (%edx),%edx ad: 89 10 mov %edx,(%eax) af: 8d 45 94 lea -0x6c(%ebp),%eax b2: 83 c0 70 add $0x70,%eax b5: 8d 55 94 lea -0x6c(%ebp),%edx b8: 83 c2 09 add $0x9,%edx bb: 89 10 mov %edx,(%eax) bd: 83 c4 70 add $0x70,%esp c0: 5b pop %ebx c1: 5f pop %edi c2: 5d pop %ebp c3: c3 ret 000000c4 <_main>: c4: 55 push %ebp c5: 89 e5 mov %esp,%ebp c7: 83 e4 f0 and $0xfffffff0,%esp ca: 83 ec 10 sub $0x10,%esp cd: e8 00 00 00 00 call d2 <_main+0xe> d2: e8 29 ff ff ff call 0 <_hit> d7: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) de: 00 df: c7 04 24 00 00 00 00 movl $0x0,(%esp) e6: e8 00 00 00 00 call eb <_main+0x27> eb: c9 leave ec: c3 ret ed: 90 nop ee: 90 nop ef: 90 nop hp@hp-PC ~/c $
hit函数什么也没做,但是buff的缓冲区溢出了,在sizeof(buff) + 12的位置上写入了一个地址。然而这个位置却恰好是,main函数中hit();调用后面的一条指令地址,在汇编语句上就是 d7: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 这条指令的地址。
代码进入hit函数后的栈帧如下:
| | | 返回地址 | | %ebp | <- %ebp | %edi | | %ebx | | | ... | | 0xc3, ret | c9 leave | e8 0 0 0 0 call <_puts> | c7 04 24 0 0 0 0 movl $0,(%esp) | 83 ec 18 sub $0x18,%esp | 89 e5 mov %esp,%ebp | 55 push %ebp | ff f0 push %eax | 8b 44 24 0c mov 0x0c(%esp),%eax | 83 c4 80 add $0xffffff80,%esp | B O M B \0 | 返回地址 | <- buff 将原始的返回地址存储在buff中 | | | <- %esp
main函数的call 0<_hit> 指令调用后,就进入_hit函数,首先将 %ebp %edi %ebx 顺序压入堆栈,然后将 %esp指针进去0x70,在栈上为buff数组分配空间。buff数组中填入了危险的数据,这些数据是病毒指令,注意栈图上列出来的指令是倒置的。
当hit函数的ret指令开始运行时,栈帧如下:
| | | ... | | 病毒地址 | | %ebp | | %edi | | %ebx | | | ... | | 0xc3, ret | c9 leave | e8 0 0 0 0 call 401278 <_puts> | c7 04 24 0 0 0 0 movl $0,(%esp) <- %eip | 83 ec 18 sub $0x18,%esp | 89 e5 mov %esp,%ebp | 55 push %ebp | ff f0 push %eax | 8b 44 24 0c mov 0x0c(%esp),%eax | 83 c4 80 add $0xffffff80,%esp | B O M B \0 | 返回地址 | 将原始的返回地址存储在buff中 | |
将原始的返回地址存储在buff中将原始的返回地址存储在buff中
虽然hit函数的栈帧被释放了,为buff分配的栈空间已经收回去了,%esp的指针已经指向了返回地址处,但栈上存储的病毒数据并未被清空。接着ret指令执行完成后,就要跳转到第一个病毒代码运行,病毒代码是在栈上运行的。病毒地址就指向了 buff+9的地址处,这就是一条 add $0xffffff80,%esp 指令。
ret运行完,并执行完病毒的6条指令后的栈空间如下:
| | | ... | | 病毒地址 | | %ebp | | %edi | | %ebx | | | ... | | 0xc3, ret | c9 leave | e8 0 0 0 0 call 401278 <_puts> | c7 04 24 0 0 0 0 movl $0,(%esp) <- %eip | 83 ec 18 sub $0x18,%esp | 89 e5 mov %esp,%ebp | 55 push %ebp | ff f0 push %eax | 8b 44 24 0c mov 0x0c(%esp),%eax | 83 c4 80 add $0xffffff80,%esp | B O M B \0 | 返回地址 | 将原始的返回地址存储在buff中 | | 返回地址 | 把返回地址再搬回到栈上 | %ebp | <- %ebp | | | .. | | | <- %esp
在栈上的病毒指令调用了puts(BOMB)函数后,就又返回到原先的地址开始运行,神不知鬼不觉的运行了一个函数。
?