tgstc-2021-Android-复赛

分析

寻找游戏逻辑代码

​ 上手发现是个unity游戏,首先找Assembly-Csharp.dll,没有,是个il2cpp打包了的游戏。那就找il2cpp.dll和global-metadata。找到了,但是都被加密了。

​ 那么现在要做的有il2cpp.dll的解密和global-metadata的解密。

​ il2cpp.dll解密估计在libsec2021里,当然也可以直接dump,global-metadata可以动调断在il2cpp里跟或者直接GG搜内存dump。

去除反调试

​ 考虑到之后还要修改,去除反调试应该是难以避免的。

​ 打开就看到和初赛一样一样的字符串加解密算法,但是全部内联到了程序里面。

​ 之前的方法不太好处理,也懒得分析它的算法,暂时先不管。

​ 打开调试器启动,准备找下它的反调试的核心函数,发现自杀的代码基本长一个样。

​ 通过异或产生一个不合法地址然后跳过去。可以用IDApython批量下断。

1
2
3
4
5
6
7
8
9
def find_maybe_bad(start, end):
inst = start
while inst < end:
if GetDisasm(inst).startswith("EOR"):
if GetDisasm(next_head(inst)).startswith("BLX"):
bpaddr = next_head(inst)
add_bpt(bpaddr)
ida_dbg.disable_bpt(bpaddr)
inst = next_head(inst)

​ 可以断下来接近60个地方。。。看了下反调会影响函数流程,也不敢轻易乱改,只能让它跑起来再把撞上的断点一个个手动修了,肉眼看到的有检测ida和frida的文件和端口,手动过一下这些反调可以让你在程序里少patch几个地方。。。

​ 最后只需要手动改不到10个地方就可以绕过反调了。

​ 其实更应该去找上层一点反调试代码,比如启动反调试线程的地方,但是程序本身大量混淆非常难看,找起来很麻烦。

​ 混淆类似ollvm的那种,感觉要去的话angr也挺好去的,符号执行到块结尾看BLX到哪里去,分支代码特征也很明显,都是ADDNE, MOVEQ这类的指令(口胡。

解密libunity和libil2cpp

​ 有了上面的工作理论上已经可以调试了,但是实际上跑起来会死在libunity里面,而且跟过去也发现全是乱的,估计是libsec2021中用了校验码之类的东西去解密libunity.so,因为我们的patch导致解密失败,解出来的东西自然全是屎。

​ 手动跟下libunity的init_proc,发现最终解密函数也是在libsec2021中。

​ 而且发现这个地方很可疑。

​ 其中用到的值被放在堆里,其指针放在

​ 怀疑是key,直接GG挂上去看正常运行时的值,手动填进去试试,发现跑起来了。

​ 解密程序其中取出了2个dword,但只使用了它们的异或值,所以这样patch程序也没问题:

​ 好了,到这一步已经可以畅通无阻调试了。

恢复global-metadata

​ 直接从内存中dump出来il2cpp.dll。global-metadata在il2cpp.so中下断后dump出来,断在下图第一个函数:

​ 丢到il2cppDumper,报错了,也算意料之中。

​ 找原因东找找西找找,最后对着源码看了下后面

​ 偏移对不上,错位了。然后对着源码一个区段一个区段看,最后发现是把开头的3个string相关的区段挪到了中间去,所以其他区段全错位了,而且字符串区段还被加密了,加密就是个异或。

​ 写个python脚本手动修了下:

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
from pwn import *

opart = []
part = [0]
head = b''

def dec(buf):
ret = b''
for i in range(len(buf)):
if buf[i] == 0:
ret += b'\x00'
continue
v = (i&0x7f) ^ (buf[i] - 0x7f) ^ 0x5c
ret += bytes([v])
return ret

with open("gmdata", "rb") as f:
raw = f.read()
head = raw[:8]
for i in range(33):
offset = u32(raw[i*8+8: i*8+12])
count = u32(raw[i*8+12: i*8+16])
part.append(raw[offset: offset+count])

opart.append(part[23])
opart.append(part[24])
opart.append(dec(part[25]))
opart = opart + part[1:23] + part[26:]

nowoff = 0x110
for i in opart:
print(type(i))
head += p32(nowoff)
head += p32(len(i))
nowoff += len(i)

for i in opart:
head += i

with open("fixMeta", "wb") as f:
f.write(head)

​ 修复出来还是报错,手动跟il2cppDumper的源码调试,发现它有个区段认错了,怀疑是版本号有问题,因为这个区段在版本27中是没有的,手动把版本号改成24,成功dump出Assembly-Csharp.dll文件。

修改无敌版

​ 剩下的工作就很简单了,用ida脚本恢复出来符号信息,找到关键函数。

​ 判断是否障碍物

image-20210418121337792

​ 因为没有分析加解密具体算法,所以这里使用自修改的patch,修改libsec2021中的解密代码,让它解密结束之后将将对应位置改掉。