今天一朋友和我说某游戏的Assembly-CSharp.dll加密了,对于mono runtime的Unity游戏,Assembly-CSharp.dll是默认打包的代码库,拿到Assembly-CSharp.dll等同于拿到源码,作弊者可以把该文件拖到.net工程中充当SDK对游戏进行二次开发:
那么我不得不来看看,首先将游戏目录下的Assembly-CSharp.dll拖入dnspy看看:
果然加密了,dnspy无法还原出来了,吓得我赶紧又拖入010Editor看看文件的二进制数据:
连PE文件开头的4D5A(MZ头)都没了,看来是对整个文件进行了加密。
Mono加载il代码库是调用mono.dll的导出函数mono_image_open_from_data_with_name完成的,如果某游戏对Assembly-CSharp.dll进行加密,那么肯定会在该导出函数或者里面的调用进行解密,否则该模块将无法正确加载,那么就来看看。用x64dbg打开游戏进程,设置入口断点,找到mono.dll的导出函数mono_image_open_from_data_with_name:
双击进入内存中查看:
好家伙,开头直接inline hook,一个大大的jmp明摆着告诉你快来分析我,那么这里跳转过去的代码肯定就是对比是否是需要解密的文件,然后进行解密的地方了。跟过去看看,跟了一层之后,发现代码直接被vm了:
那对我这种没有能力还原vm的人来说,就凉凉了了呀。那有没有可能,他里面没有做这么多操作,对源加密数据的地址解密完后,直接传入后面的过程呢?抱着这个希望,对mono_image_open_from_data_with_name下断,我把mono源码中这个函数的原型贴在这里:
第一个参数是模块数据地址,第二个是长度,最后一个参数是模块名,对应寄存器和堆栈中的数据分别是rcx、rdx、rsp+0x30。那么下断点,不断观察[rsp+0x30]中的数据,出现字符串”Assembly-CSharp.dll”的时候就停:
就是现在,观察rcx和rdx寄存器里的数据:
Rcx是Assembly-CSharp.dll的二进制数据,rdx是该模块的大小,在内存中看一下rcx保存的地址:
不错,就是我们在010Editor中查看的文件数据,那么我就在外面等它,等它跑完这个被魔改过的mono_image_open_from_data_with_name函数,单步步过…
哈哈,熟悉的4D5A出现了,那么这个肯定就是解密后的Assembly-CSharp.dll的数据了,而且经过上面的hook,知道了此文件大小是0x91010,十进制就是593936,也就是580kb左右。由于mono这个函数此时还没有将磁盘上的二进制文件加载进内存映像里进行管理,所以这部分的模块数据就是未被加密的Assembly-CSharp.dll PE文件数据,只需要将其每个字节原封不动地读取保存成PE文件即可。
那么写个简单的内存读取+文件保存的代码,填好对应的进程ID,数据地址和数据大小:
运行:
用010Editor查看:
再次拖到dnspy中:
没想到这么简单被我拿到了源码~