Wizard Time

魔法时间

拖入IDA可以发现是被混淆了的,并且在TLS回调函数中也有反调试。

函数名称被混淆,先看字符串(上面的图是后面截的,符号已经恢复部分)

可以发现这个是mingw-w64 编译的,我们照葫芦画瓢,编译一个出来,然后bindiff恢复符号。

虽然没改变多少,但是心理慰藉还是有的。放入x64dbg动调看哪里等待输入

可以发现就是这片代码,v5是 5* 65的char数组

接下来就是校验输入,v4从上下文可以知道是输入字符串的长度,也是一个数组。校验函数的返回值为130才能判断为正确

check_function的逻辑比较简单,将输入的字符串挨个字符处理,每个字符转换为与’a’的相对差,然后在定位到Dest二维数组中,每出现一次就加上2^k。最后Dest数组要和flagMat数组进行以一比对,相同才正确。这个tarMat数组没有仔细研究,根据后面的显示效果来看,应该是跟最后显示flag有关。

接下来要做的就是求解出输入的5个字符串,想求解方法我想了好一段时间,后面想到 1<<k可以当作位运算理解,把这个二维数组当成矩阵位图,就可以求解。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//flagMat太长,就不贴出来了
int main() {
char buff[5][64] = { NULL };
for (int i = 0; i != 5; ++i) {
for (char j = 0; j != 'z'-'a'+1; ++j) {
auto map = flagMat[i * 26 + j];
for (int k = 0; k != 64; ++k) {
if (map & (1ll << k)) {
buff[i][k] = j + 'a';
}
}
}
printf("%d: %s\n", i, buff[i]);
}
}

输入后就获得了这个:

不过这个确实考查做题人能否在短时间内恢复眼睛疲劳并且分辨出相似的字符。

通过不断猜测,我前前后后试了这些flag

3174273 55 177317A34 8 264 8 1 37314D33

317427355177317A3482648137314D33

317427358F77317A3482648F37314D33

317427388F77317A3482648F37314D33

317427355F77317A3452645F37314D33

317427388F77317A3482648F37314D33

后面我突然想到,flag可能不是这种随机值,而是base或者hex。

最后的flag就是flag{1t'5_w1z4Rd_71M3}

赛前做了长城杯、强网杯的题目,flag都是数字+小写字母的组合,导致我一开始没有想到这个是一个hex字符串。

Microsoft VS Code

微软大战代码

main函数里的运算非常复杂,但是采用了系统提供的加密库。

程序有反调试,不过x64dbg带ScyllaHide插件可以直接过,对照IDA的源代码,找到加密类型和密钥即可。

在x64dbg中可以确定所有的参数:

这个是AES 256 CBC 加密,我们把密钥、密文和iv向量dump出来

密码:

iv

密文程序里面现成的

可以写出脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = bytes([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x47, 0x11, 0x7B, 0x13, 0x7A, 0x15, 0x72, 0x17, 0x77, 0x19, 0x6D, 0x1B, 0x6F, 0x1D, 0x3E, 0x1F
])

iv_data = [
0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,0
]
iv = bytes(iv_data)

data = bytes([
0xDE, 0x58, 0x46, 0x70, 0xEB, 0xC7, 0x47, 0x06, 0x62, 0xEA, 0xF6, 0x49, 0x03, 0xB8, 0x8E, 0x35,
0xF1, 0xF8, 0x8F, 0x4E, 0x18, 0xD0, 0x8B, 0xD2, 0x5C, 0xF3, 0x53, 0x2F, 0x9F, 0x36, 0xE4, 0x99,
0xBC, 0xE0, 0x15, 0xCF, 0x95, 0x54, 0x36, 0xD3, 0x8F, 0x9F, 0x06, 0x8F, 0xCF, 0x8B, 0x3F, 0x0B
])

#aes = AES.new(key, AES.MODE_EBC)
aes = AES.new(key, AES.MODE_CBC, iv)
da = aes.decrypt(data)
print(f"falg:{da.decode('utf-8', errors='ignore')}")

flag{M1cr0SOf7_V5_C0dE,d0_Y0U_Kn0W??}

不过我提交的时候发现上面的flag过不了,后面有人提交之后我再提交的,遗憾二血。

当时以为flag不对,因为…

这个就和题目描述的不一样了,正确的flag不唯一

MaybeAndroid

安卓题目

apk放入jdax进行分析,ktolin编写的。放入虚拟机可知,我们需要输入一个注册码,这个注册码可能是我们要求的flag。我们搜索字符串可以发现相关的代码,下面有个isVip

我们继续追踪,很明显,这里是AES CBC PKCS5Padding的加密方式,IV向量,密钥,加密后的数据全部可以获得

求出注册码

但是这个程序还有一个页面,用来执行python程序,并且flag_check代码需要vip执行,我们阅读这个文件的源码,发现是非常奇怪的代码,还有emoji,可以联想到对open和符号运算符函数进行了重载。我们可以MT管理器定位到flag_check.py,同级目录下还有一个文件,这个文件内部就是对上述的函数进行了重载。

看到箭头的两处已经能大概猜出是rc4加密了。rc4是对称加密,输入密文输出就是明文。写出RC4加密就行。这里需要注意还要异或一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def rc4_keystream(key: bytes, n: int):
Sbox = list(range(256))
j = 0
for i in range(256):
#TBox
j = (j + Sbox[i] + key[i % len(key)]) % 256
Sbox[i], Sbox[j] = Sbox[j], Sbox[i]
# PRGA
i = j = 0
out = []
for _ in range(n):
i = (i + 1) % 256
j = (j + Sbox[i]) % 256
Sbox[i], Sbox[j] = Sbox[j], Sbox[i]
out.append(Sbox[(Sbox[i] + Sbox[j]) % 256])
return bytes(out)

key = b"y0u_@re_vip_Us3r"
enc_data = bytes.fromhex("738d9ea5a7c5824836d63c872324e36936c1dd7026b2df418268066a936256a7")
n = 32
ks = rc4_keystream(key, n)
plaintext = bytes([ks[i] ^ enc_data[i]^0x55 for i in range(n)])
s = plaintext.decode("utf-8")
print("flag{" + s + "}")

flag{5f19b83de29bd46e9e02f7f88bfb4ea2}

Find my time

寻找正确时间

[使用AI]

这是一道Qt 5.15 GUI 题目,我理清楚逻辑和处理IDA抽风花了很长时间

根据程序的名称和提示,还有运行时显示的东西,大体上就能判断出是要找出一个正确的时间,然后程序就会以这个时间作为密钥进行解密,解密后就会显示在窗口里。

程序放入IDA时,我留意到了非常长的.rdata段,但是程序似乎不会在运行时解压什么东西。经过我x64dbg的追踪,并没有出现释放文件和脱壳。

程序的主要逻辑隐藏的比较深

这个函数可以确定是Qt项目中的main函数。为了方便分析Qt的各种特性,可以写一个Qt程序进行对照分析。

这里可以看出



信号槽的连接还是有Connect符号,可以直接定位到槽函数和信号函数

找到了槽函数和QTimer

有Pixmap,说明程序在哪里加载了位图,或者我们的flag就是贴在图片里面的。但是先把重心返回到槽函数中

槽函数也许有混淆或者被优化了,IDA的伪代码有很多的错误,槽函数的参数数量最多只有this指针。这里我们可以发现获取了系统时间,并且时间格式是FILETIME,并且还做了一些神秘的转换。把转换的代码贴入浏览器:

看来是把FILETIME时间戳转换为了UNIX的时间戳,二者的起始时间不一样,精度也不同。

接下来是sub_1400017C0函数,第一眼就可以看见对FILETIME结构体赋值,很明显,IDA推导的结构体类型并不正确,原来的结构体大小要更大。

下面一个循环可以直接看出是计算年数,n1970``366365说明的很明显了

后面经过我的动调,可以确定这个是将FILETIME进行时间分离的,动调可以看出分离了

YYYY MM DD HH MM SS

还有一个月天数数组:

用在了这里

动调可以把大多数东西看清楚

下面还能看到一大段判断函数的调用

接下来我开始深入研究sub_7FF6FB6B1540函数,调试返回的结果看不出什么东西,但是能够发现这个很关键的东西:

程序包含了gmp库。为了降低逆向难度,我这里使用了网上别人编译的6.3.0的版本进行了bindiff合并符号

但是效果非常有限,上面的函数并没有加载出什么符号。但是毕竟出现了**prime,**就猜测这个函数与质数有关。

后面为了快速分析sub_7FF6FB6B17C0(时间处理函数)干了什么和恢复可读性,我将伪代码喂给了AI并让其生成了一个结构体

可读性增加了很多。经过我的动调测试,这个确实是测试输入的整数是否为质数is_prime_1。接下来我们继续动调,观察执行的流程。由于IDA显示的伪代码并不正确,太过于有误导性,因此我们看汇编,观察传入函数is_prime_1的参数值rcx。经过了漫长的调试,我分析出了这些:

第一个判断的是year是不是质数

后面判断天

以此类推,动调追踪就可以。

不过月份的判断我踩了坑,判断逻辑:

当初一眼扫过去以为月份是必须是good_month里面的,里面只包含了2 3 5 7,在下面格式化后不能有0的条件相互冲突了,后面再分析时发现11月也可以,也只能是11月。

下一个步到达这里:

这个就是把时间格式化为YYYYmmDDHHMMSS,并且校验字符串里面不能包含0

后面跟着流程图走,还有校验

这个字符串必须是质数,下面还有

这个是判断unix_time+YYYYMMDDHHMMSS 是要为质数,不过我在动调的时候就发现,还有一个寄存器里面的字符串是YYYYMMDDHHMMSS+unix_time的,这个在后面进行判断了

经过这些质数检验后,最终来到了这里

这个块就是时间正确的分支。给时间结构体第一位设置了1,这个我们可以从解密函数里看到被使用了

由此推断,这个分支就是正确的分支

总结一下,这个函数要求输入的时间是:

  1. year 是质数
  2. 月份是质数
  3. day 是质数且不为一
  4. hour min second 都是质数,且不为一
  5. Unix时间戳是质数
  6. str(YYYYMMDDHHMMSS)是质数而且不包含零
  7. str(unix_timestamp) + str(YYYYMMDDHHMMSS) 与 str(YYYYMMDDHHMMSS)str+(unix_timestamp)也是质数

约束条件很复杂,我尝试Python穷举,但是时间太过于漫长,于是喂给了AI,AI生成了两个时间段:

调试的时候把这两个时间戳写进内存就可以了

flag{4Kr0s5_Th0u54nDs_0f_yE4Rs_u_f1nd_m3_1nTh3_sE4_oF_7imE}

图片是一半一半出现的,解密算法就没有去深入研究(不过都是质数了,那大概是RSA了吧)

此外,我也分析了解密函数,因为分析时间判断函数我曾一度陷入死胡同,我就想能否通过破解解密函数来获取flag,但是并未深入,这个函数的比较复杂。我尝试过在x64dbg里面GetSystemTimeAsFileTime修改的时间写死,不过屏幕显示出来的字节码还是变化的,这个就超乎我的预料的,解密函数是比较复杂的,我猜测可能是不符和条件的就会随机生成字符

我也尝试过直接把结构体头部第一个字节直接改成1,但是也没有什么效果

于是我就放弃了这条路,再次深入时间处理函数

同时,我发现有另外两函数与槽函数长得一模一样,经过的我的动调验证,这个两个函数并没有被调用。

这个有可能是QT元对象系统生成的东西

还有,分析质数函数时,这个函数的参数看起来非常之多,一开始我也没搞清楚这个函数到底用了哪些参数,根据调用约定只有一个参数,IDA显示的伪代码非常之混乱。

后面喂给了AI生成了比较详细的代码

这个函数其实只有一个参数,IDA抽风搞出那么多,不过我不太理解的是为什么要先把数字转换为字符串,再转为mpz_t,再来判断质数,转换字符串这个步骤我感觉可以去掉(混淆另说)