首页
论坛
课程
招聘
[原创]内存解析Il2cpp函数地址
2020-12-29 22:07 8226

[原创]内存解析Il2cpp函数地址

2020-12-29 22:07
8226

目的

使用函数名拿到U3D非导出函数地址

起因

  1. 相对u3d游戏进行hook基本上离不开 Il2CppDumper 的使用,有时候想要找个函数什么的就很麻烦,第一步就得使用上面这工具来找到函数地址
  2. 第二就是每次都要用 Il2CppDumper 来找函数地址不能方便的实现 frida 脚本的自动化,既然都用到了 frida 当然是越方便越爽

经过

  • 之前搞了一个通过 Il2CppDumper 拿到的 script.js 加上一点python 筛选,来实现对函数的批量断点,方便实现对点击事件或者是其他关键函数(Like:Show,Click,Button,Rewarded)多函数的批量断点,方便我们分析代码调用
  • 这还是不够优雅,要让他自动去拿到这些地址,我们就得读一点点,在源码层面去hook拿到一些我们需要的关键点

分析

网上也有很多类似的相关文章
比如 [unity]Real-time Dump一套强劲的 Il2cpp Hook 框架IL2CPP的原理
都可以去参照 去细品

 

这里我讲讲我用到的东西
分析的入口点从加载 global-metadata.dat 入手
这是一个非常标志性的入口,不管是在源码还是在IDA中都很容易的定位
找到入口位置
IDA中从字符串窗口搜索该关键词也可以轻松的定位到一下代码

 

源码中的入口
IDA中F5也是随意的改改名字,改改结构体就差不多一个样了

 

记下未初始化的段的两个地址
两个关键地址
在源码中让后看,可以看下面就开始对此处分配出来的内存填数据了,后面我们用的时候也是无需但是未初始化的问题

 

这里的s_ImagesTable是一个指向一个 Il2CppImage 列表的开头,
sizeof(Il2CppImage) = 52,可以去头文件查看

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
typedef struct Il2CppImage
{
    const char* name;
    const char *nameNoExt;
    Il2CppAssembly* assembly;
 
    TypeDefinitionIndex typeStart;
    uint32_t typeCount;
 
    TypeDefinitionIndex exportedTypeStart;
    uint32_t exportedTypeCount;
 
    CustomAttributeIndex customAttributeStart;
    uint32_t customAttributeCount;
 
    MethodIndex entryPointIndex;
 
#ifdef __cplusplus
    mutable
#endif
    Il2CppNameToTypeDefinitionIndexHashTable * nameToClassHashTable;
 
    const Il2CppCodeGenModule* codeGenModule;
 
    uint32_t token;
    uint8_t dynamic;
} Il2CppImage;

这一步我们关注的只有 const char *nameNoExt 以及当前位置的指针位置由此写出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var s_ImagesTable = soAddr.add(0x2DF9134).readPointer()
var s_ImagesCount = soAddr.add(0x2DF9130).readPointer().toInt32()
function list_Images(keywords){
    LOG("-------------------------------------------------------------------------------------",LogColor.COLOR_33)
        for(var t=0;t<s_ImagesCount;t++){
            var tt = s_ImagesTable.add(sizeof_Il2CppImage*t)
            var typeCount = tt.add(p_size*4).readPointer().toInt32()
            var name = tt.add(p_size).readPointer().readCString()
            if (keywords != undefined){
                if (name.indexOf(keywords)!=-1){
                    LOG("[*]"+tt+"\t"+name,LogColor.COLOR_36)
                }else{--tmp}
            }else{
                LOG("[*]"+tt+"\t"+name,LogColor.COLOR_36)
            }
            current_off = current_off.add(typeCount*p_size)
        }
        LOG("----------------------------",LogColor.COLOR_33)
        LOG("  List "+(keywords==undefined?tmp:--tmp)+" Images",LogColor.RED)
        LOG("-------------------------------------------------------------------------------------",LogColor.COLOR_33)
}

运行一下就可以拿到一下内容
列出Images
这些玩意其实就是我们用Il2CppDumper拿到的哪些dll


然后下面就可以进入正题
 

使用命名空间,类名,方法名,参数个数拿到我们需要的函数 内存地址以及IDA中静态分析的地址

 

下面介绍两个主角函数(这里引入的两个函数都是libil2cpp.so的默认导出函数)
位置:D:\Program Files\Unity\Editor\Data\il2cpp\libil2cpp\il2cpp-api.cpp

  • il2cpp_class_get_method_from_name
  • il2cpp_class_from_name
    这里为了能截图在一张中移动了函数位置 ↓
    (ps:这里做的事情其实,就是和做数学一样,把未知量用已知函数去代替,带入到我们能解决即可 )
    函数原型

由此可以写出以下代码进行主动调用

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
function findAddrByName(namespaze){
    for(var t=0;t<s_ImagesCount;t++){
        var t_addr = s_ImagesTable.add(52*t)
        var t_name = s_ImagesTable.add(52*t).add(p_size).readPointer().readCString()
        if (t_name == namespaze) return ptr(t_addr)
    }
    return ptr(0x0)
}
 
function showAddr(namespaze,className,functionName,argsCount){
    Interceptor.detachAll()
    Il2CppImage = findAddrByName(namespaze)
    if (Il2CppImage == 0) {
        console.warn("Il2CppImage addr not found!")
        return
    }
    console.warn("---------------------------------------------------------")
    console.error(namespaze+"."+className+"."+functionName)
    console.warn("----------------------------")
    console.log("Il2CppImage\t---->\t "+Il2CppImage)
 
    //Il2CppClass* il2cpp_class_from_name(const Il2CppImage* image, const char* namespaze, const char *name)
    var il2cpp_class_from_name = new NativeFunction(
        Module.findExportByName(soName,"il2cpp_class_from_name"),
        'pointer',['pointer','pointer','pointer'])
 
    //const MethodInfo* il2cpp_class_get_method_from_name(Il2CppClass *klass, const char* name, int argsCount)
    var il2cpp_class_get_method_from_name = new NativeFunction(
        Module.findExportByName(soName,"il2cpp_class_get_method_from_name"),
        'pointer',['pointer','pointer','int'])
 
    var namespaze_t = Memory.allocUtf8String(namespaze)
    var className_t = Memory.allocUtf8String(className)
    var functionName_t = Memory.allocUtf8String(functionName)
 
    var Il2CppClass = il2cpp_class_from_name(Il2CppImage,namespaze_t,className_t)
    console.log("Il2CppClass\t---->\t",Il2CppClass)
    var MethodInfo = il2cpp_class_get_method_from_name(Il2CppClass,functionName_t,argsCount)
    console.log("MethodInfo\t---->\t",MethodInfo)
    console.log("\x1b[36mmethodPointer\t---->\t "+MethodInfo.readPointer() +"\t ===> \t"+MethodInfo.readPointer().sub(soAddr)+"\x1b[0m")
    console.warn("---------------------------------------------------------")
}

结果展示

实现通过名称拿到函数地址

 

静态dumper出来的结果


 

看到此处的话目的算是勉强达到了,但是还有待优化把,比如一开始的

1
2
var s_ImagesTable = soAddr.add(0x2DF9134).readPointer()
var s_ImagesCount = soAddr.add(0x2DF9130).readPointer().toInt32()

这两个位置需要我们手动IDA去找


新的问题

从上述结果中确实看到了我们可以成功的调用函数拿到函数的返回值,但是仅限于我们已知Il2CppImage*的时候有效,也就是仅仅在我们list_Images中列出的地址对应的子函数可以这么操作(使用il2cpp_class_from_name()获取对应的Il2CppClass),所以我们为了通用得考虑新的思路

 

两个关键结构体:Il2CppImage 和 Il2CppAssembly
两个关键结构体

 

从list_images中取地址

 

查看一下image的内存情况

 

前两个地址对应的是name和nameNoExt
可以看到第三个指针指向的地址的第一个区域由指回来了,就是咋们的Il2CppImage*

 

结合上图

 

上面这张图不是重点,重点在上上张图第二三个圈起来的部分,也就是
TypeDefinitionIndex typeStart;
uint32_t typeCount;
这两位,分别记录了偏移位置和方法个数,偏移位置的开始在(还是去参考IDA拿到地址,同样也是bss段的一个指针)

 

s_MethodInfoDefinitionTable

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
typedef struct MethodInfo
{
    Il2CppMethodPointer methodPointer;
    InvokerMethod invoker_method;
    const char* name;
    Il2CppClass *klass;
    const Il2CppType *return_type;
    const ParameterInfo* parameters;
    union
    {
        const Il2CppRGCTXData* rgctx_data; /* is_inflated is true and is_generic is false, i.e. a generic instance method */
        const Il2CppMethodDefinition* methodDefinition;
    };
    union
    {
        const Il2CppGenericMethod* genericMethod; /* is_inflated is true */
        const Il2CppGenericContainer* genericContainer; /* is_inflated is false and is_generic is true */
    };
    uint32_t token;
    uint16_t flags;
    uint16_t iflags;
    uint16_t slot;
    uint8_t parameters_count;
    uint8_t is_generic : 1; /* true if method is a generic method definition */
    uint8_t is_inflated : 1; /* true if declaring_type is a generic instance or if method is a generic instance*/
    uint8_t wrapper_type : 1; /* always zero (MONO_WRAPPER_NONE) needed for the debugger */
    uint8_t is_marshaled_from_native : 1; /* a fake MethodInfo wrapping a native function pointer */
} MethodInfo;

根据上述SeeHexA(0xcf80ec00)中可知偏移0x0,方法个数0x5c2(即1474个方法),接下来就可以写出以下demo

1
2
3
4
5
6
7
8
9
10
11
12
function a1(){
    var pointerSize = Process.pointerSize
    var a = soAddr.add(0xCC6474).readPointer()
        for (var t = 0;t<1475;t++){
            var tt = a.add(t*pointerSize).readPointer()
            console.warn("---------")
            console.log("srcPointer \t--->\t"+a.add(t*pointerSize))
            console.log("Il2CppImage \t--->\t"+tt.add(pointerSize*0))
            console.log("name \t\t--->\t"+tt.add(pointerSize*2).readPointer().readCString())
            console.log("namespaze \t--->\t"+tt.add(pointerSize*3).readPointer().readCString())
        }
}

效果如下
运行结果

 

上述故意方法数多加一,看到最后空命名空间和<Module>基本就稳了,就是这意思没猜错


拓展

能拓展的东西就挺多的,这就涉及最初起点了

  1. Il2CppDumperTool 脱离 Il2CppDumper 以及python脚本更易用

  2. 搞一些通用的API Hook,比如

    • UnityEngine.GameObject.SetActive(Boolean)
    • UnityEngine.Object.GetName(UnityEngine:Object):String
    • UnityEngine.Application.get_identifier():String
    • UnityEngine.PlayerPrefs.GetInt(String,Int32):Int32
      ......

 

以上展示只是一个简单的分析,其实就上述这样东西还是非常不好用,上述的addr并不能找到所有的方法及其地址,后续就想着让他更加智能一点,就尝试手动去解析一下结构体,然后就得到了一下效果
找关键位置

 

首先是针对我们需要的三个参数的自动解析,主要涉及到ldr寻址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function FuckLDR(addr,isRealAddr){
    var ins = Instruction.parse(addr)
    if (ins.mnemonic != "ldr"){
        LOG("Not LDR Instruction")
        return ptr(0)
    }
    var off = ins.operands[1].value.disp
    // console.log(JSON.stringify(ins.operands))
    var pc_1 = addr.add(p_size*2)
    var pc_2 = addr.add(p_size*3)
    var codeEnd = pc_1.add(off).readPointer()
    var final = codeEnd.add(pc_2)
    return isRealAddr?final:final.sub(soAddr)
}

findins

 

这里为了实现找到我们需要的参数,主要是通过导出函数il2cpp_init的地址往下找找到bl进去然后再找到ldr,并解析到ldr加载的值,如果出现了“mscorlib.dll”,说明我们找到的bl是正确的,然后我们同理找下层的“global-metadata.dat”即可定位到函数 bool il2cpp::vm::MetadataCache::Initialize(),找到上述位置后,我们稍微看一下就可以发现特征 IL2CPP_CALLOC 函数,老规矩通过这个bl指令去解析到函数地址,记录所有调用的该函数的当前地址放进数组,接下来就是 通过数组访问到 [1] [4] [5] 位置就是我们需要的三个初始参数,从当前位置往上解析它,第一条 mov r0,#xx 的立即数就是我们要找的,好了介绍到这里,上效果:

 

找参数

 

这种找法效果还行,能解决大部分问题,也不能解决全部问题,总有些奇葩adr之类的东西,这些就自己手动IDA查看了

 

然后就是挨着列出
列出namespace和class

 

列出method

 

也可以解析的更加详细(其实还可以继续深入解析出方法类型返回值类型的,暂时没有继续深入)
详细method

 

用新的方法来解析找函数就不存在解析不到函数的问题了(随意解析这个so里面的任意函数)

 

解析method


实际用处

随便举个例:断点 public extern void SetActive(GameObject obj,bool value);
1.有了gameObject就可以拿到transform,以及他的名字,再遍历拿到父级名字,整个调用链你都清晰了

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
function HookSetActive(){
 
    var f_getName        = new NativeFunction(find_method("UnityEngine.CoreModule","Object","GetName",1,true),'pointer',['pointer'])
    var f_getLayer       = new NativeFunction(find_method("UnityEngine.CoreModule","GameObject","get_layer",0,true),'int',['pointer'])
    var f_getTransform   = new NativeFunction(find_method("UnityEngine.CoreModule","GameObject","get_transform",0,true),'pointer',['pointer'])
    var f_getParent      = new NativeFunction(find_method("UnityEngine.CoreModule","Transform","GetParent",0,true),'pointer',['pointer'])
 
    Interceptor.attach(find_method("UnityEngine.CoreModule","GameObject","SetActive",1,true),{
        onEnter:function(args){
            if (args[1].toInt32() == 1 || args[1].toInt32() == 0) {
                LOG("\n--------------------------------------",LogColor.YELLOW)
                LOG("public extern void SetActive( "+(args[1].toInt32()==0?"false":"true")+" );",LogColor.COLOR_36)
                LOG("gameObj\t\t--->\t"+args[0],LogColor.COLOR_36)
                LOG("getName\t\t--->\t"+f_getName(args[0]).add(Process.pointerSize*3).readUtf16String(),LogColor.COLOR_36)
                LOG("getLayer\t--->\t"+f_getLayer(args[0]),LogColor.COLOR_36)              
                var m_transform = f_getTransform(args[0])
                LOG("getTransform\t--->\t"+m_transform,LogColor.COLOR_36)
                var layerNames = ""
                try{
                    for (var i=0;i<10;i++){
                        var getName = f_getName(m_transform)
                        var spl =  layerNames == "" ? "" : " <--- "
                        layerNames = layerNames + spl + getName.add(Process.pointerSize*3).readUtf16String()
                        m_transform = f_getParent(m_transform)
                        if (m_transform == 0) break
                    }
                }catch(e){
                    LOG(e,LogColor.RED)
                }
                LOG("hierarchy\t--->\t"+layerNames,LogColor.COLOR_36)
            }
        },
        onLeave:function(retval){
 
        }
    })
}


2.有了gameObject用以上的函数API可以随意的移动,显示隐藏 。。。。
......


补充:

https://github.com/axhlzy/Il2CppHookScripts/tree/master/Il2cppHook



【公告】 [2022大礼包]《看雪论坛精华22期》发布!收录近1000余篇精华优秀文章!

最后于 2021-5-31 17:22 被唱过阡陌编辑 ,原因: github地址修改
收藏
点赞5
打赏
分享
最新回复 (9)
雪    币: 350
活跃值: 活跃值 (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mllaopang 活跃值 2020-12-30 08:17
2
0
学习                            学习
雪    币: 2623
活跃值: 活跃值 (1768)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2020-12-30 08:50
3
0
等 加精
雪    币: 510
活跃值: 活跃值 (143)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
云天逵 活跃值 2020-12-30 10:38
4
0
np,np
雪    币: 5823
活跃值: 活跃值 (397)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
NightGuard 活跃值 1 2020-12-30 18:39
5
0
 来膜拜下大牛,以前都是要用il2cppdumper获得偏移,每次app更新就得更新偏移,这下可以自动支持新版了
雪    币: 199
活跃值: 活跃值 (83)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋日新阳 活跃值 2021-1-1 21:05
6
0
np,学习了,好地方
雪    币: 222
活跃值: 活跃值 (340)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
暴强 活跃值 2021-1-4 14:31
7
0
好东西,刚想自己实现个,你就放出来了。真的好兴奋啊。
雪    币: 748
活跃值: 活跃值 (491)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Himeko 活跃值 2021-1-10 19:07
8
1
NightGuard 来膜拜下大牛,以前都是要用il2cppdumper获得偏移,每次app更新就得更新偏移,这下可以自动支持新版了
https://github.com/HimekoEx/cxk 了解一下?
雪    币: 21
活跃值: 活跃值 (43)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
方块ACE 活跃值 2021-1-13 03:00
9
1
Il2CppDumper的作者有写过用il2cpp自带的api+反射遍历的方法
https://github.com/Perfare/Riru-Il2CppDumper/blob/master/module/src/main/cpp/il2cpp.cpp#L316
雪    币: 1439
活跃值: 活跃值 (3205)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
唱过阡陌 活跃值 1 2021-1-13 08:57
10
0
 

..

最后于 2022-1-26 18:55 被唱过阡陌编辑 ,原因:
游客
登录 | 注册 方可回帖
返回