2022 强网逆向部分题解

前言

整个比赛打下来就只有坐牢、坐牢还是坐牢…萌新逆向选手拿到的都是一堆ELF然后进行着一堆奇怪的函数…完全看不懂…或许只有GameMaster算是我能看懂的题目吧…

GAME MASTER

感觉整场比赛下来的逆向签到题目,本质上是一个C#编写的一个21点游戏,因为看不懂C#只能连蒙带猜的看,毕竟和C还是长得有些相似,还算好理解…

我们将其拖入到PE中进行查看

1.png

可以很明显的发现是一个C#文件,使用dnSpy(32位)进行打开,直接可以找到关键的主函数

2.png

可以很明显的发现程序加载了附件中的game message,我们往下翻可以发现下面这些特殊操作:

6.png

程序中似乎可以输入作弊码,同时在goldFunc中我们可以看到每一种作弊码对应的效果,有加钱的有减赌注的…同时我们也发现了几个有点不一样的作弊码,他们对我们之前加载的game message进行了操作,如下图:

3.png

4.png

5.png

大致可以看出来程序将game message进行了异或以及AES ECB的解密,对此我们尝试输入对应的作弊码,发现game message没有什么变化…不知道是不是因为程序没有将写完后的数据关闭,而导致数据仍然停留在内存中,我也看不懂C#,也不是很清楚…

尝试输入作弊码的师傅可能需要注意一下,第一个大的前缀作弊码输入完后需要再输入一位字符(可以随意输入),之后再进行输入第二段作弊码

对此我们采取手动进行解密game message的信息,写出如下Python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from operator import imod
from Crypto.Cipher import AES

data = open("./gamemessage","rb").read()
data = list(data)
for i in range(len(data)):
data[i] ^= 34

key = [66,114,97,105,110,115,116,111,114,109,105,110,103,33,33,33]
aes = AES.new(bytes(key),AES.MODE_ECB)
de_data = aes.decrypt(bytes(data))

with open("decode","wb") as f:
f.write(de_data)

f.close()

之后我们可以将我们的game message拖入到010中进行查看,可以发现整个文件是一个PE文件加上了一堆奇怪的东西

7.png

之后我们可以恢复得到一个exe,将其拖入PE,同样发现其是一个C#文件,我们同样直接拖入到dnSpy进行分析查看

8.png

我们可以发现其中的主要关键为其中的Check1整个函数,在其中我们缺少对应的xyz,如果我们采用手逆的话可能会有些麻烦,我们可以直接利用Z3对其进行约束求解,将解密得到的xyz直接应用到下面,按程序逻辑进行执行即可得到对应题目的flag,写出脚本如下:

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
from sympy import im
from z3 import *
from Crypto.Util.number import *
s = Solver()
enc = [101, 5, 80, 213, 163, 26, 59, 38, 19, 6, 173, 189, 198, 166, 140, 183, 42, 247, 223,
24, 106, 20, 145, 37, 24, 7, 22, 191, 110, 179, 227, 5, 62, 9, 13, 17, 65, 22, 37, 5]
x = BitVec('x', 64)
y = BitVec('y', 64)
z = BitVec('z', 64)
key = [0]*40
num = -1

for i in range(320):
x = (((x >> 29 ^ x >> 28 ^ x >> 25 ^ x >> 23) & 1) | x << 1)
y = (((y >> 30 ^ y >> 27) & 1) | y << 1)
z = (((z >> 31 ^ z >> 30 ^ z >> 29 ^ z >> 28 ^ z >> 26 ^ z >> 24) & 1) | z << 1)

if i % 8 == 0:
num += 1
key[num] = ((key[num] << 1) | ((z >> 32 & 1 & (x >> 30 & 1))
^ (((z >> 32 & 1) ^ 1) & (y >> 31 & 1)))) & 0xff

for i in range(len(enc)):
s.add(key[i] == enc[i])

if s.check() == sat:
print(s.model())
else:
print("error")

# y = 868387187
# x = 156324965
# z = 3131229747


L = [156324965, 868387187, 3131229747]
key = [0]*12
for i in range(3):
for j in range(4):
key[i * 4 + j] = (L[i] >> j * 8 & 255)

array5 = [60,100,36,86,51,251,167,108,116,245,207,223,40,103,34,62,22,251,227]
for i in range(len(array5)):
array5[i] ^= key[i % len(key)]
print(chr(array5[i]),end='')

之后就可以直接得到flag啦 ~

EasyRE

又是一个说自己easy的题目,CTF里面的easy以及baby的题目都不能相信,基本上就是不easy卑鄙了,当然或许有些题是真的easy以及baby难度

拿到程序我们将其拖入IDA中,可以看到程序去除了对应的符号表,显得每一个函数十分的难看,这里我们借助一下Finger插件进行恢复一下符号,之后我们可以快速的找到程序的主函数

1.png

可以看到程序创建了一个子进程re3,我们将写完的程序进行提出来,我们将其拖入进IDA可以发现有大量红色的未反编译出的函数,我们手动修复的同时可以发现在一处出现了我们父进程的那两个数字0xCAFEB0550xCAFE1055,同时中间的大部分数据无法还原,因此猜测可能是一个SMC

3.png

我们此时回到父进程进行观察那个循环中if语句对应的操作

2.png

可以看到函数中存在有sys_ptrace以及两个关键的if判断语句对我们的产生的异常信号进行处理,对于sys_ptrace的一些知识点可以参考

https://bbs.pediy.com/thread-265924.htm#msg_header_h2_1

其中关键的部分在于对于ptrace的一些参数:

Request Description
PTRACE_TRACEME 进程被其父进程跟踪,其父进程应该希望跟踪子进程。该值仅被tracee使用,其余的request值仅被tracer使用
PTRACE_PEEKTEXT, PTRACE_PEEKDATA 从tracee的addr指定的内存地址中读取一个字节作为ptrace()调用的结果
PTRACE_PEEKUSER 从tracee的USER区域中便宜为addr处读取一个字节,该值保存了进程的寄存器和其他信息
PTRACE_POKETEXT, PTRACE_POKEDATA 向tracee的addr内存地址处复制一个字节数据
PTRACE_POKEUSER 向tracee的USER区域中偏移为addr地址处复制一个字节数据
PTRACE_GETREGS 复制tracee的通用寄存器到tracer的data处
PTRACE_GETFPREGS 复制tracee的浮点寄存器到tracer的data处
PTRACE_GETREGSET 读取tracee的寄存器
PTRACE_SETREGS 设置tracee的通用寄存器
PTRACE_SETFPREGS 设置tracee的浮点寄存器
PTRACE_CONT 重新运行stopped状态的tracee进程
PTRACE_SYSCALL 重新运行stopped状态的tracee进程,但是使tracee在系统调用的下一个entry或从系统调用退出或在执行一条指令后stop
PTRACE_SINGLESTEP 设置单步执行标志
PTRACE_ATTACH 跟踪指定pid的进程
PTRACE_DETACH 结束跟踪

通过这个我们可以还原ptrace函数的操作,我们进入到sub_401BB1函数中,观察可以确定其是一个SMC的解密操作

对此我们可以通过调试时加入断点,来进行提取对应的异或数据值

4.png

5.png

对此我们可以得到对应的异或数据

1
[0x23271ceac9cf88e1, 0x4d9403a34275494c, 0xf1ac2fea63c94ea9, 0xf32554baaf233dcc, 0xad5ea15de7bcf568, 0xabdfc454b2ec9fd0, 0xa5ee2b4680957b2b, 0xaf42f81128b7fb38, 0xca34bde4268cae3, 0x4ee274bc39f2d547, 0x53458e3ea10ab93b, 0x2e5fb32efac34cff, 0x99f8f6faa7a64aec, 0xef38004300eda44d, 0xee67c44e2bcd18fc, 0x9b1c209768ecb41e, 0xfae74344fcba3cdb, 0x62654e739151118d, 0xbfa53d12825ac60, 0x5fda7e9212d8d034, 0xe8e15b2ffd058214, 0x6258db99ec82ff1f, 0xc1f8d40001b68bf6, 0x6211d421f8ab1d50, 0xd25bc129ebbbd366, 0xaea9e2a30d3fcd24, 0x12e2013bc48da1de, 0x1db06bde7ca30286, 0x226499b91812859b, 0xb2b0d80d0f244ce4, 0xfba26ec5f66ad4a5, 0xef4975489b39baa5, 0x75da0adeb0d03511, 0xcbb9c9ef1c68088d, 0xb707f2ec82b077b8, 0x4989b97aadc513bb, 0x74c613b6d47fcde, 0x1d6396837a7ad9d8, 0x7f1a74782535fe54]

之后我们就可以编写一个IDAPython脚本将我们之前得到的re3手动SMC解密回去

1
2
3
4
5
6
7
import ida_bytes
xorkey = [0x23271ceac9cf88e1, 0x4d9403a34275494c, 0xf1ac2fea63c94ea9, 0xf32554baaf233dcc, 0xad5ea15de7bcf568, 0xabdfc454b2ec9fd0, 0xa5ee2b4680957b2b, 0xaf42f81128b7fb38, 0xca34bde4268cae3, 0x4ee274bc39f2d547, 0x53458e3ea10ab93b, 0x2e5fb32efac34cff, 0x99f8f6faa7a64aec, 0xef38004300eda44d, 0xee67c44e2bcd18fc, 0x9b1c209768ecb41e, 0xfae74344fcba3cdb, 0x62654e739151118d, 0xbfa53d12825ac60, 0x5fda7e9212d8d034, 0xe8e15b2ffd058214, 0x6258db99ec82ff1f, 0xc1f8d40001b68bf6, 0x6211d421f8ab1d50, 0xd25bc129ebbbd366, 0xaea9e2a30d3fcd24, 0x12e2013bc48da1de, 0x1db06bde7ca30286, 0x226499b91812859b, 0xb2b0d80d0f244ce4, 0xfba26ec5f66ad4a5, 0xef4975489b39baa5, 0x75da0adeb0d03511, 0xcbb9c9ef1c68088d, 0xb707f2ec82b077b8, 0x4989b97aadc513bb, 0x74c613b6d47fcde, 0x1d6396837a7ad9d8, 0x7f1a74782535fe54]
addr = 0x220F

for i in range(39):
data = xorkey[i] ^ ida_bytes.get_qword(addr+16*i)
ida_bytes.patch_qword(addr+16*i, data)

解密完成后将发送int 3指令等传给父进程的数进行patch处理,随后构建函数,通过一定分析我们可以发现这个函数将我们的输入进行保存,随后进行check,经过一些资料的查找发现这个是一个数织游戏,其将我们的数据进行统计1的个数放在第一位,随后进行check我们的输入

对于求解这个我们需要注意的是,这个静态下直接提取得到的行列数据是有一定问题的,这行与列是在init被相应修改了,我们可以尝试直接调试拿取数据

6.png

我们将数据提取,可以直接到在线网站进行求解

https://handsomeone.github.io/Nonogram/#advanced-usages

7.png


2022 强网逆向部分题解
https://equinox-shame.github.io/2022/08/06/2022 强网杯逆向复现(部分)/
作者
梓曰
发布于
2022年8月6日
许可协议