Featured image of post GameSafety2021 Android Final

GameSafety2021 Android Final

根据其他大佬的Wp和指导一步步复现2021年的决赛题,记录其中遇到的一些问题

wp参考链接如下:

  • Question1:尝试使用Zygisk-il2cppdumperdump,但是程序会卡死,dump不出文件
  • Question2:用elf-dump-fixdump下来的il2cpp.so缺少符号,未知原因,但是可以搜字符串global-metadata.dat定位到加载函数
  • Question3:追踪加载global-metadata.dat函数的调用链,发现被hook到sec2021的函数中了,追踪到最后发现是个看不懂的地方(?)
  • Question4:实现无敌之后未成功绕过app的crc校验,导致重打包的apk无法运行

问题解决情况

  • Question1
  • Question2
  • Question3
  • Question4

待办事项

  • 分析libsec2021.so的解密函数
  • 分析il2cpp.so的代码段校验并绕过
  • dump解密后的il2cpp.so
  • 编译一份flappybird游戏
  • bindiff恢复符号
  • 实现无敌功能
  • 封包成破解版apk

解题流程

  • 解包APK,lib目录下有libil2cpp.so\assets\bin\Data\Managed\Metadata路径下有global-metadata.dat,可以知道这是il2cpp引擎的游戏
  • libil2cpp.soglobal-metadata.dat都是被加密的,打开libsec2021.so发现IDA无法正确识别ELF文件,发现是文件头的e_phentsize有问题,把23改成32即可正确识别,libil2cpp.so也是同样的处理方式
  • elf-dump-fix把内存中的libil2cpp.so dump下来并修复了
  • 写了一个脚本,成功把global-metadata.dat dump下来了
 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
function WriteMemToFile(addr, size, file_path) {  
    Java.perform(function() {  
        let prefix = '/data/data/com.personal.flappybird/files/';  
        let mkdir =  
            Module.findExportByName('libc.so', 'mkdir');  
        let chmod =  
            Module.findExportByName('libc.so', 'chmod');  
        let fopen =  
            Module.findExportByName('libc.so', 'fopen');  
        let fwrite =  
            Module.findExportByName('libc.so', 'fwrite');  
        let fclose =  
            Module.findExportByName('libc.so', 'fclose');  
  
        let call_mkdir = new NativeFunction(mkdir, 'int', ['pointer', 'int']);  
        let call_chmod = new NativeFunction(chmod, 'int', ['pointer', 'int']);  
        let call_fopen =  
            new NativeFunction(fopen, 'pointer', ['pointer', 'pointer']);  
        let call_fwrite =  
            new NativeFunction(fwrite, 'int', ['pointer', 'int', 'int', 'pointer']);  
        let call_fclose = new NativeFunction(fclose, 'int', ['pointer']);  
  
        call_mkdir(Memory.allocUtf8String(prefix), 0x1FF);  
        call_chmod(Memory.allocUtf8String(prefix), 0x1FF);  
        let fp = call_fopen(  
            Memory.allocUtf8String(prefix + file_path),  
            Memory.allocUtf8String('wb'));  
        if (call_fwrite(addr, 1, size, fp)) {  
            console.log('[+] Write file success, file path: ' + prefix + file_path);  
        } else {  
            console.log('[x] Write file failed');  
        }        call_fclose(fp);  
    });
}  
  
function hook_dlopen(addr, soName, callback) {  
    Interceptor.attach(addr, {  
        onEnter: function(args){  
            var name = args[0].readCString();  
            //console.log('openlib:', name);  
            if (name.indexOf(soName) !== -1) this.hook = true;  
        },        
        onLeave: function(retval){  
            if (this.hook) callback();  
        }    
    });
}  
  
var dlopen = Module.findExportByName(null, "dlopen");  
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");  
hook_dlopen(dlopen, "libil2cpp.so", hook_il2cpp);  
hook_dlopen(android_dlopen_ext, "libil2cpp.so", hook_il2cpp);  
  
  
function hook_il2cpp(){  
    var il2cpp = Process.findModuleByName('libil2cpp.so');  
    console.log('il2cpp:', il2cpp.base);  
  
    Interceptor.attach(il2cpp.base.add(0x5B9238), {  
        onEnter: function(args){  
            console.log("sub_0x5B9238 called!!!");  
            console.log("args[0] = ", args[0].readCString());  
        },        
        onLeave: function(retval){  
            console.log("sub_0x5B9238 return!!!");  
            console.log("retval = ", retval);  
            var metadataSize = retval.add(0x108).readInt() + retval.add(0x10C).readInt();  
            console.log("offset = ", retval.add(0x108).readInt());  
            console.log("size = ", retval.add(0x10C).readInt());  
            console.log("MetadataSize = ", metadataSize);  
            var file_path = "global-metadata.dat";  
            //console.log("[+]Metadata is dumping!!");  
            WriteMemToFile(retval, metadataSize, file_path);  
            //console.log("[+]Metadata dump success!!");  
        }  
    });
}  
//frida -H 127.0.0.1:12345 -f com.personal.flappybird -l hook.js

  • dump下来的文件放到il2cppdumper中仍然无用,在github上找到flappybird的源码(FlappyBirdStyleGame),打算自己编译一份,结果踩坑了。。。原因是版本太新,于是用下了个版本低一点unity

废了很大力才编译出来(选了好几个unity版本都不符合预期。。。)

  • 用il2cppdumper分析自己编译的libil2cpp.so,成功恢复符号并通过字符串的搜索找到对应的和实现无敌的函数PlayerController__OnCollisionEnter2D
  • 在该函数内部找到判定角色死亡的关键逻辑,但是现在目的是要在题目的so中找到对应游戏逻辑所在的位置,于是在UnityEngine_Component__CompareTag函数中发现有字符串信息
  • 在dump下来的so中通过bindiff恢复了部分符号之后,也是成功定位到题目的so的关键逻辑
  • 只需要把0x540E60处指令的BNE修改成B即可实现无敌
  • 经过Frida的验证确实可以实现无敌
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function patch(){  
    var module = Process.findModuleByName("libil2cpp.so");  
    var il2cppAddr = module.base;  
    console.log("il2cppAddr:", il2cppAddr);  
    var PatchPosition = 0x540E60;  
    var PatchCode = [0xE2, 0x00, 0x00, 0xEA];  
    console.log("[+]Patching...");  
    Memory.protect(il2cppAddr.add(PatchPosition), 4, 'rwx');  
    Memory.writeByteArray(il2cppAddr.add(PatchPosition), PatchCode);  
    console.log("[+]Patched success!!");  
}

Question2:dump下来的il2cpp.so缺少符号(已解决)

请看下面两个对比图,符号是有一些恢复的,但是关键代码符号没有恢复

  • 在别人wp中看到的
  • 我dump下来的libil2cpp.so在IDA中看到的
  • 下面是我dump il2cpp的过程(不知道是不是dump的时候地址范围选错了)

我目前的猜测wp中看到的是大佬编译了一份il2cpp.so然后恢复的符号,但是不确定是不是,打算自己编译一份看看的,但是不会,待办(确实是)

Question3:加载global-metadata.dat函数的调用链

如图,不明白这个加载函数为什么调用到这里了

Question4:实现无敌之后未成功绕过app的crc校验,导致重打包的apk无法运行

因为没有分析libil2cpp.so的解密函数和其他的一些代码段校验函数,所以无法实现破解版apk可以直接运行😭

使用 Hugo 构建
主题 StackJimmy 设计