作者论坛账号:二娃
游戏是steam上一款单机音游,难度有点高,在被虐了千百次后,我决定对这个游戏下手。
探秘
大家都知道,unity游戏的主要逻辑都在Assembly-CSharp.dll,只要用dnspy之类的工具就能够轻易的反编译出源码。于是我兴冲冲的掏出了我的dnspy,将Assembly-CSharp.dll拖了进去,然而一片空白的dnspy告诉我事情没这么简单。
使用010 editor打开文件,发现并不是标准的PE格式,DOS头的标志MZ被修改为了ML。
那就老规矩,开启游戏,对mono.dll的mono_image_open_from_data_with_name下断点观察,结果发现游戏并没有在这部分解密PE文件。抱着不想的预感,使用CE搜索了一下这个游戏的前几个字节,结果不出意料。
可以看到游戏并没有直接解密文件,外面长啥样内存里还是啥样。看来游戏应该是对mono的代码进行了修改,用自己的规则来加载文件。那没有办法,只能老老实实的跟着代码走一遍。
解密
将mono.dll拖进IDA,一般加载dll都会走到mono_image_open_from_data_with_name,所以我们直接定位到这里,然后从github下了一份mono的源码作为对照。顺着流程走下去会走到do_mono_image_load函数,这个函数就是用来解析加载dll文件的。
PEHeader部分
首先看到pe_image_load_pe_data,这个函数是用来解析PE Header部分的,通过对比可以看出这个函数与源码不一样,是被修改过的,ida F5代码如下
_BOOL8 __fastcall pe_image_load_pe_data(__int64 image){
char *header; // rdi
__int64 v2_image; // rbx
signed int section_table_offset; // eax
_BOOL8 result; // rax
char data[128]; // [rsp+20h] [rbp-88h]
header = *(char **)(image + 0x50);
v2_image = image;
result = 0;
if ( *(_DWORD *)(image + 24) >= 0x80u ) // raw_data_len
{
memmove(data, *(const void **)(image + 16), 0x80ui64);// raw_data
if ( data[0] == 'M' && data[1] == 'L' )
{
section_table_offset = do_load_header(v2_image, header, *(_DWORD *)&data[0x3C] - 0x4D4C);// NtHeader offset - 0x4D4C
if ( section_table_offset >= 0 )
{
if ( (unsigned int)load_section_tables(v2_image, (__int64)header, section_table_offset) )
result = 1;
}
}
}
return result;
首先可以看到被修改过的mono在识别DOS头标志的时候用的不是MZ而是ML,与修改过的dll文件一致,然后在读取0x3c位置也就是NtHeader偏移值的时候减去了0x4D4C。do_load_header主要是记录一下IMAGE_NT_HEADERS结构,与源码没什么太大的差异,唯一的区别就是在识别NtHeader标志的时候用的不是PE而是ML,与修改过的dll一致。
signed __int64 __fastcall do_load_header(__int64 image, char *header, int e_lfanew){
__int64 v3; // rdi
char *v4_header; // rbx
__int64 v5; // rsi
unsigned int section_table_offset; // edi
int v8; // er11
int v9; // eax
char Dst; // [rsp+20h] [rbp-D8