[unity]Real-time Dump

发布于 2020-04-06  160 次阅读


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(),如此一来就非常好找,在unitylibil2cpp文件夹下搜索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 获取基本的类名、方法名、偏移,就要先摸清楚 MethodInfoIl2CppClass 这两个结构体:

//..\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++;
        }
    }
}

在一系列结构体赋值操作后,每个methodnamemethodPointer都得以匹配。我们只需要在上述代码的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等一系列操作,这篇文章里暂时就不再展开说了

0x03 参考

1.还原使用IL2CPP编译的unity游戏的symbol(二)

2.[Honkai 3rd]v3.5符号还原