Kyr1os' Blog

May the wind guide your road

0%

构造Shellcode (on Linux)


shellcode就是指攻击者要执行的代码,执行shellcode可以使攻击者获取某种权限。一般来说,只要启动了bin/sh,攻击者就能完全控制计算机。因此shellcode就是指一段很短小的,用于启动bin/sh的机器代码。 在开始了解shellcode之前,最好先弄清楚Linux用户权限 本文实例环境是ubuntu16.04 64 bit.

0x10 综述

先上一份示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//sample.c
#include <unistd.h>
#include <stdio.h>

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
8
kyrios@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
16
kyrios@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
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
···
#define __NR_fork 57
#define __NR_vfork 58
#define __NR_execve 59
#define __NR_exit 60
#define __NR_wait4 61
#define __NR_kill 62
可以看到很多我们比较熟悉的函数其实都是通过系统调用来实现的。 当我们要调用这些函数的时候,我们只需要把对应的编号装进EAX,再引发系统中断(int 0x80/syscall)就可以,也就是说,不考虑传参的话,我们调用write()在x64下就可以写成
1
2
mov    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
30
gdb-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.
从0x4009fa开始是execve的传参,并在0x400a05处调用execve函数。 我们能够知道execve这里的三个参数

  • 参数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
'''
)
这里提一下记得在用asm函数是注意一下contextType,它存储了cpu类型以及操作系统,如果contextType不对的话你的asm函数可能会报错。

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
再搭配asm函数,你就能得到你想要的。