目的
-
使用函数名拿到U3D非导出函数指针
起因
-
相对u3d游戏进行hook基本上离不开 Il2CppDumper 的使用,有时候想要找个函数什么的就很麻烦,第一步就得使用上面这工具来找到函数地址
-
第二就是每次都要用 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,可以去头文件查看
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 以及当前位置的指针位置由此写出
var s_ImagesTable = soAddr.add(0x2DF9134).readPointer()
var s_ImagesCount = soAddr.add(0x2DF9130).readPointer().toInt32()
function list_Images(){
console.warn("--------------------------------------------------------")
var tmp = s_ImagesCount + 1
console.error(" List ",tmp," Images")
console.warn("----------------------------")
for(var t=0;t<s_ImagesCount;t++){
console.log("\n\x1b[36m [*]",s_ImagesTable.add(sizeof_Il2CppImage*t),"\t",
s_ImagesTable.add(sizeof_Il2CppImage*t).add(p_size).readPointer().readCString()+"\x1b[0m")
// console.log("hash_value:\t")
// console.log("\t"+hexdump(s_ImagesTable.add(52*t).add(p_size*2).readPointer(),{length:16,header:false,ansi:false}))
}
console.warn("---------------------------------------------------------")
}
运行一下就可以拿到一下内容
这些玩意其实就是我们用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:这里做的事情其实一开始也很懵,完全就是和做数学一样,把未知量用已知函数去代替,带入到我们能解决即可 [......].png)
由此可以写出以下代码进行主动调用
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("---------------------------------------------------------")
}
结果展示
看到此处的话目的算是勉强达到了,但是还有待优化把,比如一开始的
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中取第一个地址 0xcf80ec00
前两个地址对应的是name和nameNoExt
可以看到第三个指针指向的地址的第一个区域由指回来了,就是咋们的Il2CppImage*
上面这张图不是重点,重点在上上张图第二三个圈起来的部分,也就是
TypeDefinitionIndex typeStart;
uint32_t typeCount;
这两位,分别记录了偏移位置和方法个数,偏移位置的开始在(还是去参考IDA拿到地址,同样也是bss段的一个指针)
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
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>基本就稳了,就是这意思没猜错
拓展
能拓展的东西就挺多的,这就涉及最初起点了
-
让 Il2CppDumperTool 脱离 Il2CppDumper 以及python脚本更易用
搞一些通用的API Hook,比如
- UnityEngine.GameObject.SetActive(Boolean)
- UnityEngine.Object.GetName(UnityEngine:Object):String
- UnityEngine.Application.get_identifier():String
- UnityEngine.PlayerPrefs.GetInt(String,Int32):Int32
......
https://github.com/axhlzy/Il2CppHookScripts/tree/master/Il2cppHook