0x00 前言
某游戏3.5版本更新后,已经无法再使用原版Il2cppDumper得到函数地址。遂尝试做动态Dump
0x01 ver3.5都做了什么?
1. Il2CppGlobalMetadataHeader
发生了变化
搜索字符串global-metadata.dat
,查找引用:
//int sub_64F680C()
sub_64F5A74(&v11);
v9 = "Metadata";
v10 = 8;
v13 = v11;
v14 = *(_DWORD *)(v11 - 12);
sub_64B0AE8(&v12, &v13, &v9);
sub_64FCCC4(&v11);
v6 = "global-metadata.dat";
回溯,找到上层引用 void sub_6498CAC()
,即MetadataCache::Initialize()
:
//ver3.5 MetadataCache::Initialize()
dword_78C7314 = sub_64F680C();
dword_78C7318 = dword_78C7314;
v26 = dword_78C7314 + *(_DWORD *)(dword_78C7314 + 120);
if ( *(_DWORD *)(dword_78C7314 + 124) >= 0x44u )
{
v0 = dword_78C7314 + *(_DWORD *)(dword_78C7314 + 120);
v1 = 0;
do
{
sub_64E467C(v0);
v0 += 68;
++v1;
}
while ( v1 < *(_DWORD *)(dword_78C7318 + 124) / 0x44u );
}
dword_78C731C = sub_64F67E4(*(_DWORD *)(dword_78C730C + 24), 4);
dword_78C7320 = sub_64F67E4(*(_DWORD *)(dword_78C7318 + 84) / 0x68u, 4);
dword_78C7324 = sub_64F67E4(*(_DWORD *)(dword_78C7318 + 300) >> 6, 4);
dword_78C7328 = sub_64F67E4(*(_DWORD *)(dword_78C730C + 32), 4);
dword_78C732C = *(_DWORD *)(dword_78C7318 + 116) >> 5;
ver3.4如下:
//ver3.4 Orig_MetadataCache::Initialize()
dword_7225D84 = (int)sub_5F65F94("global-metadata.dat");
dword_7225D88 = dword_7225D84;
v27 = dword_7225D84 + *(_DWORD *)(dword_7225D84 + 184);
if ( *(_DWORD *)(dword_7225D84 + 188) >= 0x44u )
{
v0 = dword_7225D84 + *(_DWORD *)(dword_7225D84 + 184);
v1 = 0;
do
{
sub_5F5711C(v0);
v0 += 68;
++v1;
}
while ( v1 < *(_DWORD *)(dword_7225D88 + 188) / 0x44u );
}
dword_7225D8C = sub_5F69400(*(_DWORD *)(dword_7225D7C + 24), 4);
dword_7225D90 = sub_5F69400(*(_DWORD *)(dword_7225D88 + 164) / 0x68u, 4);
dword_7225D94 = sub_5F69400(*(_DWORD *)(dword_7225D88 + 52) / 0x38u, 4);
dword_7225D98 = sub_5F69400(*(_DWORD *)(dword_7225D7C + 32), 4);
dword_7225D9C = *(_DWORD *)(dword_7225D88 + 180) >> 5;
2.新增导出函数il2cpp_init_mhy
//EXPORT il2cpp_init_mhy
int (__fastcall *__fastcall il2cpp_init_mhy(int (__fastcall *result)(_DWORD, _DWORD), int a2, int a3))(_DWORD, _DWORD)
{
dword_78C7640 = result;
dword_78C7644 = (int (__fastcall *)(_DWORD, _DWORD))a2;
dword_78C7648 = (int (__fastcall *)(_DWORD, _DWORD, _DWORD))a3;
return result;
}
我们重点分析一下a3
,因为其他两个看着头晕
a3
被调用于sub_649B7F8+4C
,来看看 sub_649B7F8
的伪代码:
//int sub_649B7F8
int __fastcall sub_649B7F8(int a1)
{
int v1; // r4
int result; // r0
int v3; // r0
int v4; // [sp+4h] [bp-Ch]
v1 = a1;
result = 0;
if ( v1 != -1 )
{
result = *(_DWORD *)(dword_78C7334 + 4 * v1);
if ( !result )
{
v4 = 0;
v3 = dword_78C7648(dword_78C7314, v1, &v4);
result = il2cpp_string_new_len_0(v3, v4);
*(_DWORD *)(dword_78C7334 + 4 * v1) = result;
}
}
return result;
}
一眼看过去似乎很难看出来这对应的是Il2cpp
的哪个函数。注意到il2cpp_string_new_len_0()
,实际上就是il2cpp::vm::String::NewLen()
,如此一来就非常好找,在unity
的libil2cpp
文件夹下搜索String::NewLen
即可:
Search "String::NewLen" (5 hits in 5 files) D:\Program Files\Unity20170418\Editor\Data\il2cpp\libil2cpp\icalls\mscorlib\System.Runtime.InteropServices\Marshal.cpp (1 hit) Line 191: return String::NewLen(value, len); D:\Program Files\Unity20170418\Editor\Data\il2cpp\libil2cpp\il2cpp-api.cpp (1 hit) Line 994: return String::NewLen(str, length); D:\Program Files\Unity20170418\Editor\Data\il2cpp\libil2cpp\il2cpp-vm-support.h (1 hit) Line 49: #define IL2CPP_VM_STRING_NEW_LEN(value, length) il2cpp::vm::String::NewLen(value, length) D:\Program Files\Unity20170418\Editor\Data\il2cpp\libil2cpp\vm\MetadataCache.cpp (1 hit) Line 1247: s_StringLiteralTable[index] = String::NewLen((const char)s_GlobalMetadata + s_GlobalMetadataHeader->stringLiteralDataOffset + stringLiteral->dataIndex, stringLiteral->length); D:\Program Files\Unity20170418\Editor\Data\il2cpp\libil2cpp\vm\String.cpp (1 hit) Line 72: Il2CppString String::NewLen(const char* str, uint32_t length)
重点关注MetadataCache.cpp
的1247行,这就是我们要找的。对应函数为MetadataCache::GetStringLiteralFromIndex()
。查看上层引用MetadataCache::InitializeMethodMetadata()
,可以发现 GetStringLiteralFromIndex()
用于从global-metadata.dat
中读取字符串并赋值到metadataUsages
中。实际上在这个地方字符串已经完成解密了,如果做dump就可以得到明文字符串:
//void MetadataCache::InitializeMethodMetadata(uint32_t index)
switch (usage)
{
case kIl2CppMetadataUsageTypeInfo:
*s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetTypeInfoFromTypeIndex(decodedIndex);
break;
case kIl2CppMetadataUsageIl2CppType:
*s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = const_cast<Il2CppType*>(GetIl2CppTypeFromIndex(decodedIndex));
break;
case kIl2CppMetadataUsageMethodDef:
case kIl2CppMetadataUsageMethodRef:
*s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = const_cast<MethodInfo*>(GetMethodInfoFromIndex(encodedSourceIndex));
break;
case kIl2CppMetadataUsageFieldInfo:
*s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetFieldInfoFromIndex(decodedIndex);
break;
case kIl2CppMetadataUsageStringLiteral:
*s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetStringLiteralFromIndex(decodedIndex);
break;
default:
NOT_IMPLEMENTED(MetadataCache::InitializeMethodMetadata);
break;
}
0x02 ver3.5的Real-time Dump
一系列分析不再赘述,参考:还原使用IL2CPP编译的unity游戏的symbol(二)
1.如何快速定位到 SetupMethodsLocked
在源代码中查找 SetupMethodsLocked
的上层引用:
//SetupMethods
void Class::SetupMethods(Il2CppClass *klass)
{
if (klass->method_count || klass->rank)
{
FastAutoLock lock(&g_MetadataLock);
SetupMethodsLocked(klass, lock);
}
}
我们继续往上找:
//GetMethods
const MethodInfo* Class::GetMethods(Il2CppClass *klass, void* *iter)
{
if (!iter)
return NULL;
if (!*iter)
{
Class::SetupMethods(klass);
if (klass->method_count == 0)
return NULL;
*iter = &klass->methods[0];
return klass->methods[0];
}
const MethodInfo** methodAddress = (const MethodInfo**)*iter;
methodAddress++;
if (methodAddress < &klass->methods[klass->method_count])
{
*iter = methodAddress;
return *methodAddress;
}
return NULL;
}
实际上这里的Class::GetMethods()
对应的就是ida中的il2cpp_class_get_methods_0()
:
//il2cpp_class_get_methods_0
int __fastcall il2cpp_class_get_methods_0(int a1, unsigned int *a2)
{
unsigned int *v2; // r5
int v3; // r6
int v4; // r4
unsigned int v5; // r0
v2 = a2;
v3 = a1;
v4 = 0;
if ( a2 )
{
if ( *a2 )
{
v5 = *a2 + 4;
if ( v5 < *(_DWORD *)(v3 + 64) + 4 * (unsigned int)*(unsigned __int16 *)(v3 + 156) )
{
*a2 = v5;
return *(_DWORD *)v5;
}
}
else
{
SetupMethods(a1);
if ( *(_WORD *)(v3 + 156) )
{
*v2 = *(_DWORD *)(v3 + 64);
v5 = *(_DWORD *)(v3 + 64);
return *(_DWORD *)v5;
}
}
}
return v4;
}
因此通过 il2cpp_class_get_methods_0
-->SetupMethods
--> SetupMethodsLocked
的调用链即可快速定位
2.分析 SetupMethodsLocked
我们要通过 SetupMethodsLocked
获取基本的类名、方法名、偏移,就要先摸清楚 MethodInfo
和 Il2CppClass
这两个结构体:
//..\Editor\Data\il2cpp\libil2cpp\il2cpp-class-internals.h
struct MethodInfo
{
Il2CppMethodPointer methodPointer;
InvokerMethod invoker_method;
const char* name;
Il2CppClass *declaring_type;
const Il2CppType *return_type;
const ParameterInfo* parameters;
...
};
//..\Editor\Data\il2cpp\libil2cpp\il2cpp-class-internals.h
struct Il2CppClass
{
// The following fields are always valid for a Il2CppClass structure
const Il2CppImage* image;
void* gc_desc;
const char* name;
const char* namespaze;
const Il2CppType* byval_arg;
const Il2CppType* this_arg;
Il2CppClass* element_class;
Il2CppClass* castClass;
Il2CppClass* declaringType;
Il2CppClass* parent;
Il2CppGenericClass *generic_class;
const Il2CppTypeDefinition* typeDefinition; // non-NULL for Il2CppClass's constructed from type defintions
const Il2CppInteropData* interopData;
// End always valid fields
...
};
对着抄两个c艹的结构体:
//MethodInfo
typedef struct MethodInfo
{
int32_t methodPointer;
int32_t invoker_method;
const char* name;
Il2CppClass *klass;
const void *return_type;
const void* parameters;
...
} MethodInfo;
//Il2CppClass
typedef struct Il2CppClass
{
const void* image;
void* gc_desc;
const char* name;
const char* namespaze;
int32_t byval_arg;
int32_t this_arg;
Il2CppClass* element_class;
Il2CppClass* castClass;
Il2CppClass* declaringType;
Il2CppClass* parent;
void *generic_class;
const void* typeDefinition;
const void* interopData;
...
} Il2CppClass;
其中 MethodInfo
中的 methodPointer
对应的是实际运行时的函数地址。要获取可执行文件中函数的真实地址,需要减掉libil2cpp.so
的基地址;name
即方法名;klass -> name
即类名
接下来让我们看看SetupMethodsLocked()
都做了些什么:
//..\Editor\Data\il2cpp\libil2cpp\vm\Class.cpp
void SetupMethodsLocked(Il2CppClass *klass, const FastAutoLock& lock)
{
if ((!klass->method_count && !klass->rank) || klass->methods)
return;
if (klass->generic_class)
{
InitLocked(GenericClass::GetTypeDefinition(klass->generic_class), lock);
GenericClass::SetupMethods(klass);
}
else if (klass->rank)
{
InitLocked(klass->element_class, lock);
SetupVTable(klass, lock);
}
else
{
if (klass->method_count == 0)
{
klass->methods = NULL;
return;
}
klass->methods = (const MethodInfo**)IL2CPP_CALLOC(klass->method_count, sizeof(MethodInfo*));
MethodInfo* methods = (MethodInfo*)IL2CPP_CALLOC(klass->method_count, sizeof(MethodInfo));
MethodInfo* newMethod = methods;
MethodIndex start = klass->typeDefinition->methodStart;
IL2CPP_ASSERT(start != kFieldIndexInvalid);
MethodIndex end = start + klass->method_count;
for (MethodIndex index = start; index < end; ++index)
{
const Il2CppMethodDefinition* methodDefinition = MetadataCache::GetMethodDefinitionFromIndex(index);
newMethod->name = MetadataCache::GetStringFromIndex(methodDefinition->nameIndex);
newMethod->methodPointer = MetadataCache::GetMethodPointerFromIndex(methodDefinition->methodIndex);
newMethod->invoker_method = MetadataCache::GetMethodInvokerFromIndex(methodDefinition->invokerIndex);
newMethod->declaring_type = klass;
newMethod->return_type = MetadataCache::GetIl2CppTypeFromIndex(methodDefinition->returnType);
ParameterInfo* parameters = (ParameterInfo*)IL2CPP_CALLOC(methodDefinition->parameterCount, sizeof(ParameterInfo));
ParameterInfo* newParameter = parameters;
for (uint16_t paramIndex = 0; paramIndex < methodDefinition->parameterCount; ++paramIndex)
{
const Il2CppParameterDefinition* parameterDefinition = MetadataCache::GetParameterDefinitionFromIndex(methodDefinition->parameterStart + paramIndex);
newParameter->name = MetadataCache::GetStringFromIndex(parameterDefinition->nameIndex);
newParameter->position = paramIndex;
newParameter->token = parameterDefinition->token;
newParameter->customAttributeIndex = parameterDefinition->customAttributeIndex;
newParameter->parameter_type = MetadataCache::GetIl2CppTypeFromIndex(parameterDefinition->typeIndex);
newParameter++;
}
newMethod->parameters = parameters;
newMethod->customAttributeIndex = methodDefinition->customAttributeIndex;
newMethod->flags = methodDefinition->flags;
newMethod->iflags = methodDefinition->iflags;
newMethod->slot = methodDefinition->slot;
newMethod->parameters_count = static_cast<const uint8_t>(methodDefinition->parameterCount);
newMethod->is_inflated = false;
newMethod->token = methodDefinition->token;
newMethod->methodDefinition = methodDefinition;
newMethod->genericContainer = MetadataCache::GetGenericContainerFromIndex(methodDefinition->genericContainerIndex);
if (newMethod->genericContainer)
newMethod->is_generic = true;
klass->methods[index - start] = newMethod;
newMethod++;
}
}
}
在一系列结构体赋值操作后,每个method
的name
与methodPointer
都得以匹配。我们只需要在上述代码的71行这个位置拿到已经赋值完成的newMethod
结构体即可,在ida中对应的是r5
寄存器。关键代码位置为:

int __fastcall SetupMethodsLocked(int a1, int a2)
第95行
hook addr
3.使用HookZz Hook SetupMethodsLocked
为了在上述两行标红的代码处获取r5
寄存器的值,我们可以使用HookZz
中的:
//hookzz.h
ZzHookAddress(zpointer target_start_ptr, zpointer target_end_ptr, PRECALL pre_call_ptr,HALFCALL half_call_ptr);
部分代码如下:
//hack.cpp
int arg = 0;
int base = 0;
void SetupMethodsLocked_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
if (arg == 0){
base = get_module_base("libil2cpp.so");
}
MethodInfo *reg_r5 = (MethodInfo *)(rs->general.regs.r5);
arg++;
const char *namespaze = reg_r5->klass->name;
const char *name = reg_r5->name;
int32_t pointer = reg_r5->methodPointer - base;
char *buffer = (char *)malloc(strlen(namespaze) + strlen(name) + 14);
sprintf(buffer, "%s%s%s%s%08x", namespaze, "$$", name, " 0x", pointer);
FILE *fp;
fp = fopen("/sdcard/dump.txt", "a+");
fprintf(fp, "%s\n", buffer);
fclose(fp);
//LOGI("name%d ! %s$$%s, addr : 0x%08x", arg, reg_r5->klass->name, reg_r5->name, reg_r5->methodPointer - base);
return;
}
void SetupMethodsLocked_half_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
}
void hook_SetupMethodsLocked(long base_addr) {
long addr = base_addr + your_addr;
ZzHookAddress((void *)(addr + 0x200), (void *)(addr + 0x204), SetupMethodsLocked_pre_call, SetupMethodsLocked_half_call);
}
使用上述hook的前提是要确保在libil2cpp.so
加载的第一时间就能hook上,否则打印出来的方法就会有部分缺失。后续又涉及到hook dlopen
等一系列操作,这篇文章里暂时就不再展开说了
Comments | 6 条评论
#该评论为私密评论#
做到最后一步 发现自己不会用hookzz… 能分享一份现成的dat文件吗? 我主要是想要弄出dummyDll 52上的那个不能弄出来 用utiny反编译出伪脚本
@emm
没事了 我弄出来了
其它都搞定了,居然不会抄写2个结构体,大神能否提供一下那2个结构体学习一下呀? iOS用,莫非和Android不一样?
@哒哒
咦 文章里不是写出来了嘛_(:з」∠)_
基于frida的real time unity hook
https://github.com/axhlzy/Il2CppDumperTool/blob/master/U3DHook/Ufun.js