某游戏so加固思路分析

反调试

frida hook strstr发现会对一些敏感的字符串进行判断,将其中涉及到反调试的进行过滤直接返回null

xposed
substrate
XposedBridge.jar
/data/local/tmp
TracerPid:
State:
android_server
gdb
lldb
test-keys
dkplugin
com.qgwapp.shadowside
libtopa1024.so
com.svm.proteinbox
libpbclient.so

因为libil2cpp.so加壳了,要想脱壳需要在so之前进行附加调试,但是发现使用am start -D -n无法暂停app。

  1. 这里测试在android 10上可以先利用frida 以pause模式启动后再用ida附加进行调试,这样即可用frida hook过掉反调试,又可以在libil2cpp.so加载之前进行附加。

  2. 但是使用高版本的Frida 16.1.4在android 13上以pause模式启动app后frida会闪退,这里采用另一个思路,hook linker64call_constructors,当判断到libil2cpp.so被加载的时候用frida patch libil2cpp.so.init_array中的第一个函数开头为0x14000000,这是一条死循环指令,这时候用ida附加后将patch的指令修正后即可正常调试。(对于call_constructors函数如何得到so路径,可以通过反编译linker64查看)

var linker_module = Module.getBaseAddress("linker64");
// hook call_constructors
Interceptor.attach(linker_module.add(0x4EDE0), {
    onEnter:function(args){
        var readpath
        var flag = args[0].add(416).readU8()
        if(flag & 1){
            readpath = args[0].add(432).readPointer()
        }
        else{
            readpath = args[0].add(417)
        }
    
        if(readpath.readCString().indexOf("libil2cpp.so") != -1){
            // patch .init_array 的第一个函数头部
            Memory.protect(Module.findBaseAddress("libil2cpp.so").add(0x56BB808), 4, 'rwx')
            Module.findBaseAddress("libil2cpp.so").add(0x56BB808).writeU32(0x14000000);
            console.log("write ok")
        }
    },onLeave:function(){}
})

在调试的过程中发现程序有的时候会fork一个子进程,貌似是用来做反调试的,子进程fork后直接kill掉即可,否则ida无法正常调试。

加固so工作流程分析

libil2cpp.so使用了自定义linker加固so,ida中查看导出表发现符号全部都被加密了。

壳要想还原原程序需要获取到最早的执行时机,一般都是截获.init_array中的函数。readelf查看so的.init_array发现有三个函数

ida查看这三个函数发现地址不对,函数跳转过去全都是乱码,.init_array是由linker64执行的,所以函数不可能是加密的。

因为.init_array中的函数也是需要做重定位的,去重定位表中看一下对应的重定位项,.init_array中的数据对应的重定位类型为R_AARCH64_RELATIVE,一般是位于RELA(.rela.dyn)节区中。RELA的前三个重定位对应的就是.init_array中的三个函数的重定位数据,所以实际重定位后.init_array中的三个函数地址应该为0x56BB808,0x56BC018,0x56BC710

这里ida未能解析重定位表完成重定位是因为section干扰了ida的分析,直接将elf头的中section数据清空也可以使ida进行正常的重定位。

过掉反调试后ida调试这三个函数

func1

func1 主要是初始化一个函数表,后面func2func3会使用这些初始化的函数

func2

func2 会先调用自实现的prelinker_image函数,解析壳自己的dynamic动态链接节区获取到dynstr等数据的位置,接着就会调用解密函数去解密dynstr中的字符串数据

func3

func3会加密代码段和数据段,然后还原到内存中原程序位置。之后再次解析壳so的.dynamic,目的是得到壳中解密后的.dynstr.dynsym等动态链接信息。调用prelinker_image获取原代码的dynamic相关信息之后调用linker_image对原程序进行重定位。

linker_image会获取到原程序的.rel.dyn和.rel.plt节区中所有的重定位项进行重定位。

最后循环调用原程序的.init_array中的所有函数。

壳加固思路分析

原程序具有两个PT_LOAD段(代码段和数据段),加固的时候将代码段和数据段中的数据加密,同时将第二个PT_LOAD段(数据段)在文件中清空,以.bss段的形式存在。所以加固后的so第一个PT_LOAD段的内存大小是远大于文件大小的,多出来的这部分在内存中就是数据段。壳还会增加两个PT_LOAD段保存壳的代码和数据

将解密后原程序代码段和数据段dump后查看原程序的.plt表在原程序的代码段中,而got表在原程序的数据段中,也就是二者的相对偏移并没有变化,.plt表中的代码不需要动态修正即可正常工作

因为壳的代码段在内存中的偏移为0x563f000,而原程序的.got表在这偏移之前的原程序代码段中。壳的重定位表中只包含了0x563f000偏移地址之后需要重定位的数据,所以壳的重定位表只包含了壳自身的重定位项。

而原程序的重定位表是壳在func3函数中对原程序进行重定位之前保存在malloc申请的堆内存中。并且重定位表在完成重定位操作后就没有用了,可以直接清除防止dump

壳获取原程序的dynamic信息中只包含了DT_NEEDEDinit_array数据,并不包含原程序的dynstrdynsym信息,这是因为壳和原程序共用同一份dynstrdynsym。壳和原程序需要共用一份符号表和字符串表的原因是:linker保存了壳程序的program header table信息到soinfo中,后续其他so与此so进行交互的时候需要通过soinfo链找到此so的program header table信息。例如如果有其他so试图通过dlsym获取此加固so的导出函数时,linker就需要得到对应加固so的soinfo,接着获取program header table中的符号表symtab,找到对应的导出函数地址并返回。(这也解决了之前自实现linker加固so的时候需要修复壳so的soinfo为原始程序的soinfo的问题)

dump解密数据并patch壳so

func2的时候用ida脚本将解密后的.dynstr数据dump下来

func3在对原程序进行linker_image重定位之前将代码段和数据段dump下来,调用了linker_image之后dump还需要对重定位的数据进行修复。这里目的是为了dump sdk,所以直接将解密的数据patch到对应的文件偏移处,其中解密的数据段为了方便直接插入到文件中原程序代码段和壳代码段中间即可,然后修改第一个PT_LOAD段的大小为原始程序代码段和数据段之和,后面的段对应的文件偏移需要加上插入的原程序代码段的大小。

dump global-metadata.dat

查看apk的unity资源文件发现使用的引擎版本是2018.4.24f1

自编译一个相同版本的apk,找到加载global-metadata.dat文件的地方,同时定位加固so的相似位置进行dump,dump后修复global-metadata.dat文件的头

寻找g_CodeRegistration和g_MetadataRegistration并dump sdk

对于2018版本的unity引擎,通过.init_array的函数找到RegisterRuntimeInitializeAndCleanup函数,并通过参数得到s_Il2CppCodegenRegistration函数,继续通过参数得到两个表的地址

壳so在func3函数最后会调用原程序init_array中的所有函数,将这些函数列表dump下来

分析原程序init_array中的所有函数,最后定位到s_Il2CppCodegenRegistration函数

通过s_Il2CppCodegenRegistration函数参数得到g_CodeRegistration = 0x2F6B408,g_MetadataRegistration = 0x2F6B488

Il2CppDumper填入g_CodeRegistrationg_MetadataRegistration成功dump sdk

查看get_position_Injected函数地址

对比ida中解密的代码发现是正确的

posted @ 2023-11-18 01:45  怎么可以吃突突  阅读(519)  评论(5编辑  收藏  举报