2022全国大学生信息安全竞赛决赛 Re —— analgo

前言

本来想练习一下Go的有关题目的,不知不觉就找到了这个…被硬生生暴打,坐牢几天后想到为什么不可以直接使用爆破反正也就42flag,最坏的情况下也就是98的42次方(不会真的有人打开输入关闭去这样爆破吧…),简单的找了点参考,发现和今年还是去年的一个hgame的题目有点相似,hgame的那个题目叫hardasm,看官方的wp看的头大…但是校外的一个师傅给出了subprocess进行爆破的一种方法,相当于用python模拟我们输入并获取结果…网上没找到太多的关于这个的资料,于是只好对着校外师傅的脚本简单改改

简单分析

刚拿到题目,光是看名字似乎就知道是分析Go了,建议使用IDA 7.6及以上的IDA进行反编译,GO的主函数与我们常接触到的C/C++不太一样,有一些奇妙的特性,像是主函数是main_mian,传入字符串时是一个结构体,结构体里面是一个指针和一个int类型的字符串长度,也因为这些奇妙的特性导致GO逆向向来不太好进行

输入字符串

1.png

找到main_main,就可以推断出来我们需要输入的长度

加密部分

2.png

可以看到程序使用一个qmemcpy将对应的opcode赋值给了v27,同时下面启动main_anal函数,不难猜测出其就是我们的主要加密部分,进入该函数可以看到多个if块,一会if这个if那个同时还有v10这些控制if块是否执行,整个虚拟机运行部分十分混杂,特别难看(也有可能是我太菜了)

3.png

对此我们结合之前所提到的subprocess想是否我们可以直接对每一位进行爆破?因为程序下面有对应的比较语句,我们是否能像hgame的程序一样将对应的程序部分patch,将我们加密后的数据输出,然后与正确的进行比较,当输出正确一个时会对应上一个加密后的数据,通过这个方式进行有目的性的爆破处理

爆破测试

我们可以多次进行尝试输入,因为flag头可以确定是flag{},长度要求为42,多次的调试进行测试下来我们可以发现一个规律,程序将我们的输入进行两两加密

4.png

在发现上述规律后我们便要开始尝试进行patch对应的输出

修改程序

5.png

我们观察到输出是用lea将对应的偏移地址进行保存到rdx,我们需要做的便是修改对应的off_XXX处内的值,起初打算是将这个换成一个寄存器,因为我们可以通过调试发现加密后的输入保存在某一个寄存器上,修改了半天Keypatch崩了,带着IDA一起走了…要么就是Keypatch总是自己将我们改的一个地址修改到另一个地址,好奇怪?

最后我们将目标锁定在了比较的过程中,因为比较中肯定会同时出现flag加密后的数据和我们输入加密后的数据,在这个地方我们也可以尝试将对应的输入加密保存到一个固定的位置

在之前的if块中,我们可以发现比较的函数是runtime_memequal()我们进入函数进行查找对应的比较部分在哪里,最后可以锁定在一个if块上

6.png

切换到对应的汇编

7.png

我们将其进行修改后为:

8.png

需要注意的是Wrong!字符串是保存在.rdata段上的,需要注意一下其是否可写,将对应的段的权限进行修改

10.png

修改完毕后我们进行简单的测试一下看看能不能完成输入

11.png

可以发现可以将我们的输入加密后进行输出后我们可以开始写对应的爆破脚本了

编写脚本

此处借用hgame比赛时外校师傅使用的脚本进行魔改

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
import subprocess

real_flag="flag{" #绝对正确的前5个字符
cur_index=len(real_flag) #当前爆破的位置
k = 0
s = "0123465789abcdef-" # 限制的输入范围
s = list(s)
for i in range(len(s)):
s[i] = ord(s[i])
"""
v19 = 0x9F061721E8B3DD38LL; # 加密后的flag
v20 = 0x120D94C88B56A209LL;
v21 = 0x8C0C2D3877E67EF3LL;
v22 = 0x2507BAF4CF113DELL;
v23 = 0x1B66F9F5A0238BCALL;
"""
ans1 = [0x38,0xdd,0xb3,0xe8,0x21,0x17,0x06,0x9f]
ans2 = [0x09,0xa2,0x56,0x8b,0xc8,0x94,0x0d,0x12]
ans3 = [0xf3,0x7e,0xe6,0x77,0x38,0x2d,0x0c,0x8c]
ans4 = [0xde,0x13,0xf1,0x4c,0xaf,0x7b,0x50,0x02]
ans5 = [0xca,0x8b,0x23,0xa0,0xf5,0xf9,0x66,0x1b]

while cur_index < 42:
for i in s:#当前爆破的位置上的字符
real_flag_arr = [0] * 42

for j in range(len(real_flag)):#正确的先复制一下
real_flag_arr[j]=ord(real_flag[j])

real_flag_arr[len(real_flag_arr)-1]=ord("}")#最后一个字符"}"固定

for j in range(len(real_flag_arr)-2,cur_index,-1):#除了当前爆破的位置,其他位置上都设置为32
real_flag_arr[j]=48

real_flag_arr[cur_index]=i #设置当前爆破的位置上的字符
real_flag_arr_s="".join(chr(k) for k in real_flag_arr) #输入到程序中的字符串
p = subprocess.Popen(["C:\\Users\\Equinox\\Desktop\\analgo.exe"],stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(real_flag_arr_s.encode())
p.stdin.close()
out = p.stdout.read()
out = list(out)

if out[k] == ans[k]:#判断程序打印出的0xFF的个数是否增加,增加则说明当前爆破的位置上的字符设置的是正确的
real_flag += chr(i)
cur_index += 1
k += 1
print(real_flag)
break

因为前面提到的Wrong!处的字符串似乎只能存储8个字节,因此我们需要每次爆破出来后将源程序继续进行patch,将对应的rsi+8,同时改变脚本中的ans最后即可爆破出flag

flag

1
flag{568a3cdd-77e1-4c42-9fee-127e27a5744e}

2022全国大学生信息安全竞赛决赛 Re —— analgo
https://equinox-shame.github.io/2022/08/11/2022全国大学生信息安全竞赛决赛 Re — analgo/
作者
梓曰
发布于
2022年8月11日
许可协议