使用frida获取unity il2cpp符号信息 本次主角是米哈游的《原神》,起因还是在网上看到了一篇关于崩3的符号信息还原的文章,就想看看原神上有没有变化。
0x1 背景 本次分析选取的是android平台的,游戏采用unity开发,使用il2cpp模式编译。
使用 IL2CPP 模式编译,游戏中使用的字符串都被保存在了一个global-metadata.dat的资源文件里,只有在动态运行时才会将这些字符串读入内存。一般使用Il2CppDumper就可以读取global-metadata.dat文件中的信息,帮助反编译。
有破解就有保护,可能会遇到无法dump或者global-metadata.dat文件结构被修改的情况,无法使用Il2CppDumper,这个时候就需要自己动手了。
0x2 IL2CPP 加载过程 github上随便找一个il2cpp的源码,搜索global-metadata.dat,发现只有函数MetadataCache::Initialize()处使用。
1 2 3 4 5 6 7 void MetadataCache::Initialize () { s_GlobalMetadata = vm::MetadataLoader::LoadMetadataFile ("global-metadata.dat" ); s_GlobalMetadataHeader = (const Il2CppGlobalMetadataHeader*)s_GlobalMetadata; ... }
查看LoadMetadataFile代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void * MetadataLoader::LoadMetadataFile (const char * fileName) { std::string resourcesDirectory = utils::PathUtils::Combine (utils::Runtime::GetDataDir (), utils::StringView <char >("Metadata" )); std::string resourceFilePath = utils::PathUtils::Combine (resourcesDirectory, utils::StringView <char >(fileName, strlen (fileName))); int error = 0 ; FileHandle* handle = File::Open (resourceFilePath, kFileModeOpen, kFileAccessRead, kFileShareRead, kFileOptionsNone, &error); if (error != 0 ) return NULL ; void * fileBuffer = utils::MemoryMappedFile::Map (handle); File::Close (handle, &error); if (error != 0 ) { utils::MemoryMappedFile::Unmap (fileBuffer); fileBuffer = NULL ; return NULL ; } return fileBuffer; }
很明显,就是Initialize时调用LoadMetadataFile将global-metadata.dat映射到内存中。
还需要稍微了解一下global-metadata.dat文件结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct Il2CppGlobalMetadataHeader { int32_t sanity; int32_t version; int32_t stringLiteralOffset; int32_t stringLiteralCount; int32_t stringLiteralDataOffset; int32_t stringLiteralDataCount; int32_t stringOffset; int32_t stringCount; int32_t eventsOffset; int32_t eventsCount; int32_t propertiesOffset; int32_t propertiesCount; int32_t methodsOffset; int32_t methodsCount; ... }
0x3 某手游加载过程 反编译原神的il2cpp.so,loadmetadataFile()函数,对比原始loadmetadataFile,可以清楚的看到将文件映射进内存后进行了一次解密操作。(在MetadataCache::Initialize中)
再查看MetadataCache::Initialize(),根据红框,对比原始代码,可以发现global-metadata.dat文件结构已经被修改,即使成功dump文件也不能使用Il2CppDumper。
0x4 hook SetupMethodsLocked 为什么选择在SetupMethodsLocked hook,有其他大佬已经讲得很仔细了,可以参考还原使用IL2CPP编译的unity游戏的symbol
阅读源码,通过il2cpp_class_get_methods->Class::GetMethods->Class::SetupMethods跟踪到SetupMethodsLocked:
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 const MethodInfo* il2cpp_class_get_methods (Il2CppClass *klass, void * *iter) { return Class::GetMethods (klass, iter); } const MethodInfo* Class::GetMethods (Il2CppClass *klass, void * *iter) { ... if (!*iter) { Class::SetupMethods (klass); if (klass->method_count == 0 ) return NULL ; *iter = &klass->methods[0 ]; return klass->methods[0 ]; } ... } void Class::SetupMethods (Il2CppClass *klass) { if (klass->method_count || klass->rank) { FastAutoLock lock (&g_MetadataLock) ; SetupMethodsLocked (klass, lock); } }
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 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++; } } }
MethodInfo、Il2CppClass的结构也需要了解一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct Il2CppClass { const Il2CppImage* image; void * gc_desc; const char * name; const char * namespaze; ... } struct MethodInfo { Il2CppMethodPointer methodPointer; InvokerMethod invoker_method; const char * name; Il2CppClass *declaring_type; const Il2CppType *return_type; const ParameterInfo* parameters; ... };
反编译获取hook地址:
编写frida js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var module = Process .findModuleByName ("libil2cpp.so" );var p_size = 8 ;Interceptor .attach (ptr (module .base ).add (0x72F09EC ).add (0x204 ),{ onEnter :function (args ){ var newMethod = this .context .x20 var pointer = newMethod.readPointer (); var name = newMethod.add (p_size * 2 ).readPointer ().readCString (); var klass = newMethod.add (p_size * 3 ).readPointer (); var klass_name = klass.add (p_size * 2 ).readPointer ().readCString (); var klass_paze = klass.add (p_size * 3 ).readPointer ().readCString (); send (klass_paze+"." +klass_name+":" +name+" -> " +pointer.sub (module .base )); } });
成功hook,但是你会发现,跟本没什么卵用。因为它被混淆了。。。。
参考
[unity]Real-time Dump
[Honkai 3rd]v3.5符号还原