在x86_64上的C中溢出缓冲区来调用函数

时间:2014-12-01 11:05:38

标签: c security buffer-overflow

您好我有这样的代码

#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]'运行我的程序。但它失败了分段错误。我做错了什么?谢谢你的帮助。

PS

我想通过覆盖outsecret

中的返回代码来调用函数checksecret

3 个答案:

答案 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寄存器中(假设您有x86x86-64 CPU)。可能位于堆栈的数组bufcurrent 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排。你会在那里看到你的密钥。