|
利用 http://www.groad.net/bbs/read.php?tid-2876.html 中的 C 程序,在 gdb 中反汇编出 return_input 的地址:
(gdb) disas main
Dump of assembler code for function main:
0x080483f2 <main+0>: push %ebp
0x080483f3 <main+1>: mov %esp,%ebp
0x080483f5 <main+3>: call 0x80483d4 <return_input>
0x080483fa <main+8>: mov $0x0,%eax
0x080483ff <main+13>: pop %ebp
0x08048400 <main+14>: ret
End of assembler dump.
利用栈溢出漏洞将返回到 main() 函数中的地址 0x080483fa 替换为 return_input() 函数的入口地址,也就是说希望 return_input() 函数执行 2 次:
[root@centos shellcode]# printf "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDD\xf5\x83\x04\x08" | ./stack
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDD▒
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDD▒
上面,0x080483f5 是 return_input() 函数的入口地址。由于 x86 体系结构采用小端存储格式,所以我们的地址要倒着写。
成功执行了两次。这说明,通过缓冲区的溢出,我们覆盖了程序的返回地址,控制了 EIP 。如果上面的程序是一个注册码验证的函数,我们同样可以通过修改返回地址已达到绕过注册码的目的。
修改上面的 return_input 函数,在它里面添加一段简单的“注册码”验证功能,测试代码:
#include <stdio.h> | #include <stdlib.h> | | int return_input (void) | { | printf ("Please input your serial code: | "); | char array[30]; | int ok = 0; | gets (array); | | if (array[0] == 'b' && array[1] == 'e' && array[2] == 'y' && array[3] == 'e' && array[4] == 's') | ok = 1; | | return (ok); | | } | | int register_ok (void) | { | printf ("register ok! | "); | exit (EXIT_SUCCESS); | } | | int register_invalid(void) | { | printf ("register failed! | "); | exit (EXIT_FAILURE); | } | int main() | { | | if (return_input()) | register_ok(); | else | register_invalid(); | | | return (0); | | } |
|
编译运行测试:
[root@centos shellcode]# gcc -mpreferred-stack-boundary=2 -g stack2.c -o stack2
/tmp/ccO3INgh.o: In function `return_input':
/root/shellcode/stack2.c:9: warning: the `gets' function is dangerous and should not be used.
[root@centos shellcode]# ./stack2
Please input your serial code:
dfadsfa
register failed!
[root@centos shellcode]# ./stack2
Please input your serial code:
beyes
register ok!
正确的“注册码”是 beyes 。
下面同样通过缓冲区溢出绕过这个注册码。
在 gdb 中查看这个程序的反汇编:
(gdb) disas main
Dump of assembler code for function main:
0x08048498 <main+0>: push %ebp
0x08048499 <main+1>: mov %esp,%ebp
0x0804849b <main+3>: call 0x8048404 <return_input>
0x080484a0 <main+8>: test %eax,%eax
0x080484a2 <main+10>: je 0x80484ab <main+19>
0x080484a4 <main+12>: call 0x804845c <register_ok>
0x080484a9 <main+17>: jmp 0x80484b0 <main+24>
0x080484ab <main+19>: call 0x804847a <register_invalid>
0x080484b0 <main+24>: mov $0x0,%eax
0x080484b5 <main+29>: pop %ebp
0x080484b6 <main+30>: ret
End of assembler dump.
上面,地址 0x080484a4 处调用了 register_ok 函数表明注册成功。这里,我们可以通过缓冲区溢出,修改 return_input() 的返回地址,使之直接跳转到此,从而达到成功注册的目的。如下面构造的输入: | [root@centos shellcode]# printf "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDD\xa4\x84\x04\x08" | ./stack2 | Please input your serial code: | register ok! |
|
[/table] | [table=100%,#f9f7ed]这里需要再提及一点,如果主程序中不是调用注册成功函数(register_ok),而是直接打印出成功或失败信息。那么这样修改缓冲区,虽然会看到注册成功的信息(register ok!),但是也还会看到段错误。原因是,当我们输入超长字符串时,也覆盖了 EBP,这个 EBP 在返回时是在 main() 中仍会用来动态的寻找 main 栈中的数据 。如在 printf() 函数执行时,里面的字符串参数地址就是通过 EBP 来寻得。 |
|
|