IL2CPP API 使用
测试环境
- Unity2019.4.38
- il2cpp版本24.5
c# to il to cpp
在Unity4.6.1 p5以后版本中,在PlayerSettings—>Other Settings—>Scripting Backend有mono和il2cpp两个选项,它们是Unity脚本后处理(Scripting Backend)的两种方式。
经过il2cpp编译后的包体,其代码存在于运行库文件,il2cpp还需要一个非常重要的文件:global-metadata.dat文件
在默认状态下,运行库文件在Windows系统上名字为GameAssembly.dll
在Android上为libil2cpp.so
这两种文件本身也是攻防要地
运行库文件本身带有导出函数,这些内容为il2cpp的API函数,可以使用IDA打开库文件,切换窗口至Exports查看导出函数,如果没有就是做过加密
下面展示一个普通的unity脚本,绑定在Camera上打包
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class il2cpptest : MonoBehaviour
{
// Start is called before the first frame update
void Start(){}
// Update is called once per frame
void Update(){}
public int il2cppadd(int i) {
return i + 1;
}
private int il2cppsub(int i) {
return i - 1;
}
}
下面将通过il2cppAPI对脚本内的方法进行访问,同时你也可以修改内部属性将函数指针修改到其他位置来起到替换函数的目的
无论如何你都需要提前了解一些信息!
global-metadata.dat中保留了从class到cpp编译dll的信息,你可以使用010Editor打开dat文件直接观察可解读的信息
上面的cs脚本中int il2cppadd(int)
这个函数完全可以直接在dat文件里面找到
UnityEngine.dll.Assembly-CSharp....Assembly-CSharp.dll.il2cpptest.il2cppadd.il2cppsub
这是一段ASCII码翻译的字符串,其中的“.”为原文的字节00
,其中的Assembly-CSharp.dll
在mono打包方式中将以C#编译dll,这个dll包含了所有用户写下的代码,你可以通过这种方式猜测类结构,上文中的il2cpptest就是类名
注入
你需要将dll注入目标程序
这里不细说
直接载入库(dll 或者 so)无法实现复刻真实程序内的情况
需要在运行中调用这些API函数
il2cpp_domain_assembly_open
此函数看名字应该是用来打开内部的Assembly,但是实际上看调用已经打开的则直接返回Il2CppAssembly*指针
这个函数不需要第一个参数工作,所以可以直接传入NULL,const char* name就是模块的名字:Assembly-CSharp
本文所有的代码中可以只关心导出函数的内容,为了方便查看就把调用列出来了,结构体在最后面
const Il2CppAssembly* il2cpp_domain_assembly_open(Il2CppDomain* domain, const char* name)//导出函数
{
return Assembly::Load(name);
}
const Il2CppAssembly* Assembly::Load(const char* name)
{
const size_t len = strlen(name);
utils::VmStringUtils::CaseInsensitiveComparer comparer;
for (AssemblyVector::const_iterator assembly = s_Assemblies.begin(); assembly != s_Assemblies.end(); ++assembly)
{
if (comparer(name, (*assembly)->aname.name))
return *assembly;
}
if (!ends_with(name, ".dll") && !ends_with(name, ".exe"))
{
char* tmp = new char[len + 5];
memset(tmp, 0, len + 5);
memcpy(tmp, name, len);
memcpy(tmp + len, ".dll", 4);
const Il2CppAssembly* result = Load(tmp);
if (!result)
{
memcpy(tmp + len, ".exe", 4);
result = Load(tmp);
}
delete[] tmp;
return result;
}
else
{
for (AssemblyVector::const_iterator assembly = s_Assemblies.begin(); assembly != s_Assemblies.end(); ++assembly)
{
if (comparer(name, (*assembly)->image->name))
return *assembly;
}
return NULL;
}
}
il2cpp_assembly_get_image
从Il2CppAssembly转换到Il2CppImage,结构体的信息可以查阅源码给出,源码位于unity引擎的libil2cpp文件夹下
const Il2CppImage* il2cpp_assembly_get_image(const Il2CppAssembly* assembly)//导出函数
{
return Assembly::GetImage(assembly);
}
其实通过Il2CppAssembly结构体可以直接取出Il2CppImage结构体
il2cpp_class_from_name
*参数列表:Il2CppImage 指针 命名空间 类名字**
Il2CppClass* il2cpp_class_from_name(const Il2CppImage* image, const char* namespaze, const char* name)//导出函数
{
return Class::FromName(image, namespaze, name);
}
Il2CppClass* Class::FromName(const Il2CppImage* image, const char* namespaze, const char *name)
{
return Image::ClassFromName(image, namespaze, name);
}
Il2CppClass* Image::ClassFromName(const Il2CppImage* image, const char* namespaze, const char *name)
{
if (!image->nameToClassHashTable)
{
os::FastAutoLock lock(&s_ClassFromNameMutex);
if (!image->nameToClassHashTable)
{
image->nameToClassHashTable = new Il2CppNameToTypeDefinitionIndexHashTable();
for (uint32_t index = 0; index < image->typeCount; index++)
{
TypeDefinitionIndex typeIndex = image->typeStart + index;
AddTypeToNametoClassHashTable(image, typeIndex);
}
for (uint32_t index = 0; index < image->exportedTypeCount; index++)
{
TypeDefinitionIndex typeIndex = MetadataCache::GetExportedTypeFromIndex(image->exportedTypeStart + index);
if (typeIndex != kTypeIndexInvalid)
AddTypeToNametoClassHashTable(image, typeIndex);
}
}
}
Il2CppNameToTypeDefinitionIndexHashTable::const_iterator iter = image->nameToClassHashTable->find(std::make_pair(namespaze, name));
if (iter != image->nameToClassHashTable->end())
return MetadataCache::GetTypeInfoFromTypeDefinitionIndex(iter->second);
return NULL;
}
il2cpp_class_get_method_from_name
*参数列表:Il2CppClass 指针 方法名字 方法参数数量**
const MethodInfo* il2cpp_class_get_method_from_name(Il2CppClass *klass, const char* name, int argsCount)//导出函数
{
return Class::GetMethodFromName(klass, name, argsCount);
}
const MethodInfo* Class::GetMethodFromName(Il2CppClass *klass, const char* name, int argsCount)
{
return GetMethodFromNameFlags(klass, name, argsCount, 0);
}
const MethodInfo* Class::GetMethodFromNameFlags(Il2CppClass *klass, const char* name, int argsCount, int32_t flags)
{
Class::Init(klass);
while (klass != NULL)
{
void* iter = NULL;
while (const MethodInfo* method = Class::GetMethods(klass, &iter))
{
if (method->name[0] == name[0] &&
!strcmp(name, method->name) &&
(argsCount == IgnoreNumberOfArguments || method->parameters_count == argsCount) &&
((method->flags & flags) == flags))
{
return method;
}
}
klass = klass->parent;
}
return NULL;
}
il2cpp的函数生成后会多一个参数,排在第一位,在il2cpp_class_get_method_from_name
中需要以正常参数查询,但是真正调用的时候必须由MethodInfo结构体得到函数地址IDA分析查阅函数真正的参数数量!
IDA中的int il2cppadd(int i)
__int64 __fastcall sub_1804EA170(__int64 a1, int a2)
{
return (unsigned int)(a2 + 1);
}
~我实际写的代码,测试成功后我就写下了本文~
//这块代码不属于il2cpp源码,是我的注入dll内的代码,前面的指针你可以用void *正常传递我就不发出来了
int (*func)(int, int);//对照IDA声明了函数指针
func = (int(*)(int, int)) il2cppMethod->methodPointer;//右面是MethodInfo结构体下的函数地址,强制转换给func调用
if (func(123,2) != 3)MessageBox(NULL, L"il2cppadd false", L"false", 0);//我测试了能否成功调用il2cpptest类下的il2cppadd
总结
API函数内容多种多样,可以实现很多神奇功能,但是和il2cppdumper一样地,一旦开发者直接混淆就没法从这个层面进攻
不过通过其他方法拿到信息再通过API函数修改还是比较方便的,Github上有很多大佬已经给出了解决方案,如Zygisk
更重要的是目前这些导出函数的加密还比较少,目前看来就算将来进行了修改,也能定位一些关键函数得到很多信息
下面是结构体信息:
Structs
你可以直接接着il2cpp源代码构造,也可以手动构造下面的类,实际输出发现00
字节占了很多
typedef unsigned int uint32_t;//我的环境是64位
typedef int int32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
typedef struct Il2CppAssembly
{
Il2CppImage* image;
uint32_t token;
int32_t referencedAssemblyStart;
int32_t referencedAssemblyCount;
Il2CppAssemblyName aname;
} Il2CppAssembly;
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;
typedef struct Il2CppClass
{
// The following fields are always valid for a Il2CppClass structure
const Il2CppImage* image;
void* gc_desc;
const char* name;//类名
const char* namespaze;//命名空间
Il2CppType byval_arg;
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;
Il2CppClass* klass; // hack to pretend we are a MonoVTable. Points to ourself
// End always valid fields
// The following fields need initialized before access. This can be done per field or as an aggregate via a call to Class::Init
FieldInfo* fields; // Initialized in SetupFields
const EventInfo* events; // Initialized in SetupEvents
const PropertyInfo* properties; // Initialized in SetupProperties
const MethodInfo** methods; // Initialized in SetupMethods
Il2CppClass** nestedTypes; // Initialized in SetupNestedTypes
Il2CppClass** implementedInterfaces; // Initialized in SetupInterfaces
Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; // Initialized in Init
void* static_fields; // Initialized in Init
const Il2CppRGCTXData* rgctx_data; // Initialized in Init
// used for fast parent checks
Il2CppClass** typeHierarchy; // Initialized in SetupTypeHierachy
// End initialization required fields
void *unity_user_data;
uint32_t initializationExceptionGCHandle;
uint32_t cctor_started;
uint32_t cctor_finished;
ALIGN_TYPE(8) size_t cctor_thread;
// Remaining fields are always valid except where noted
GenericContainerIndex genericContainerIndex;
uint32_t instance_size;
uint32_t actualSize;
uint32_t element_size;
int32_t native_size;
uint32_t static_fields_size;
uint32_t thread_static_fields_size;
int32_t thread_static_fields_offset;
uint32_t flags;
uint32_t token;
uint16_t method_count; // lazily calculated for arrays, i.e. when rank > 0
uint16_t property_count;
uint16_t field_count;
uint16_t event_count;
uint16_t nested_type_count;
uint16_t vtable_count; // lazily calculated for arrays, i.e. when rank > 0
uint16_t interfaces_count;
uint16_t interface_offsets_count; // lazily calculated for arrays, i.e. when rank > 0
uint8_t typeHierarchyDepth; // Initialized in SetupTypeHierachy
uint8_t genericRecursionDepth;
uint8_t rank;
uint8_t minimumAlignment; // Alignment of this type
uint8_t naturalAligment; // Alignment of this type without accounting for packing
uint8_t packingSize;
// this is critical for performance of Class::InitFromCodegen. Equals to initialized && !has_initialization_error at all times.
// Use Class::UpdateInitializedAndNoError to update
uint8_t initialized_and_no_error : 1;
uint8_t valuetype : 1;
uint8_t initialized : 1;
uint8_t enumtype : 1;
uint8_t is_generic : 1;
uint8_t has_references : 1;
uint8_t init_pending : 1;
uint8_t size_inited : 1;
uint8_t has_finalize : 1;
uint8_t has_cctor : 1;
uint8_t is_blittable : 1;
uint8_t is_import_or_windows_runtime : 1;
uint8_t is_vtable_initialized : 1;
uint8_t has_initialization_error : 1;
VirtualInvokeData vtable[IL2CPP_ZERO_LEN_ARRAY];
} Il2CppClass;
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;
};
/* note, when is_generic == true and is_inflated == true the method represents an uninflated generic method on an inflated type. */
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;