您好我有这样的代码
#include <stdio.h>
#define SECRET "1234567890AZXCVBNFRT"
int checksecret(){
char buf[32];
gets(buf);
if(strcmp(SECRET,buf)==0) return 1;
else return 0;
}
void outsecret(){
printf("%s\n",SECRET);
}
int main(int argc, char** argv){
if (checksecret()){
outsecret();
};
}
离开秘密
(gdb) disassemble outsecret
Dump of assembler code for function outsecret:
0x00000000004005f4 <+0>: push %rbp
0x00000000004005f5 <+1>: mov %rsp,%rbp
0x00000000004005f8 <+4>: mov $0x4006b4,%edi
0x00000000004005fd <+9>: callq 0x400480 <puts@plt>
0x0000000000400602 <+14>: pop %rbp
0x0000000000400603 <+15>: retq
我假设我不知道SECRET
,所以我尝试用这样的字符串python -c 'print "A" * 32 + "\x40\x05\xf4"[::-1]'
运行我的程序。但它失败了分段错误。我做错了什么?谢谢你的帮助。
我想通过覆盖outsecret
checksecret
答案 0 :(得分:3)
你必须记住所有字符串都有一个额外的字符来终止字符串,所以如果输入32个字符,那么gets
会将 33 字符写入缓冲区。超出数组限制的写入会导致undefined behavior,这通常会导致崩溃。
gets
函数没有边界检查,使用起来非常危险。它已经被弃用了很长时间,并且在最新的C11标准中它甚至被删除了。
答案 1 :(得分:2)
$ python -c 'print "A" * 32 + "\x40\x05\xf4"[::1]'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@
$ perl -le 'print length("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@")'
33
您的输入字符串太长,缓冲区大小不超过32个字符('\0'
终止空字符需要额外的一个)。您是缓冲区或阵列溢出的受害者(有时也称为数组溢出)。
请注意,出于安全考虑,在C99中不推荐使用gets()
,最后在C11标准中使用has been dropped。
我想通过覆盖返回代码来调用函数outsecret checksecret
请注意,您即将离开C标准的相对安全区域。这意味着行为与编译器,编译器的版本,优化设置,ABI等相关(可能包括月亮的当前阶段)。
从x86 calling conventions开始,整数返回值直接存储在%eax
寄存器中(假设您有x86
或x86-64
CPU)。可能位于堆栈的数组buf
由current stack frame内的%rbp
偏移处理。让我们咨询gdb
反汇编命令:
$ gcc -O0 test.c
$ gdb -q a.out
(gdb) b checksecret
(gdb) r
Breakpoint 1, 0x0000000000400631 in checksecret ()
(gdb) disas
Dump of assembler code for function checksecret:
0x000000000040062d <+0>: push %rbp
0x000000000040062e <+1>: mov %rsp,%rbp
=> 0x0000000000400631 <+4>: sub $0x30,%rsp
0x0000000000400635 <+8>: mov %fs:0x28,%rax
0x000000000040063e <+17>: mov %rax,-0x8(%rbp)
0x0000000000400642 <+21>: xor %eax,%eax
0x0000000000400644 <+23>: lea -0x30(%rbp),%rax
0x0000000000400648 <+27>: mov %rax,%rdi
0x000000000040064b <+30>: callq 0x400530 <gets@plt>
0x0000000000400650 <+35>: lea -0x30(%rbp),%rax
0x0000000000400654 <+39>: mov %rax,%rsi
0x0000000000400657 <+42>: mov $0x400744,%edi
0x000000000040065c <+47>: callq 0x400510 <strcmp@plt>
0x0000000000400661 <+52>: test %eax,%eax
0x0000000000400663 <+54>: jne 0x40066c <checksecret+63>
0x0000000000400665 <+56>: mov $0x1,%eax
0x000000000040066a <+61>: jmp 0x400671 <checksecret+68>
0x000000000040066c <+63>: mov $0x0,%eax
0x0000000000400671 <+68>: mov -0x8(%rbp),%rdx
0x0000000000400675 <+72>: xor %fs:0x28,%rdx
0x000000000040067e <+81>: je 0x400685 <checksecret+88>
0x0000000000400680 <+83>: callq 0x4004f0 <__stack_chk_fail@plt>
0x0000000000400685 <+88>: leaveq
0x0000000000400686 <+89>: retq
无法直接从C代码覆盖%eax
,但您可以做的是覆盖代码段的选择性片段。在您的情况下,您想要的是替换:
0x000000000040066c <+63>: mov $0x0,%eax
与
0x000000000040066c <+63>: mov $0x1,%eax
gdb
本身很容易实现:
(gdb) x/2bx 0x40066c
0x40066c <checksecret+63>: 0xb8 0x00
set {unsigned char}0x40066d = 1
现在让我们确认一下:
(gdb) x/i 0x40066c
0x40066c <checksecret+63>: mov $0x1,%eax
从那时起,即使checksecret()
不匹配,1
也会返回SECRET
。然而,buf
本身并不容易这样做,因为你需要知道(以某种方式猜测?)正确偏移特定代码段指令。
答案 2 :(得分:0)
以上答案非常清楚并且正确地利用缓冲区溢出漏洞。但是在没有利用漏洞的情况下,有一种不同的方法可以做同样的事情。
mince@rootlab tmp $ gcc test.c -o test
mince@rootlab tmp $ strings test
/lib64/ld-linux-x86-64.so.2
libc.so.6
gets
puts
__stack_chk_fail
strcmp
__libc_start_main
__gmon_start__
GLIBC_2.4
GLIBC_2.2.5
UH-X
UH-X
[]A\A]A^A_
1234567890AZXCVBNFRT
;*3$
请看最后2排。你会在那里看到你的密钥。