本人虽然是个手残,却非常喜欢尝试各种音游,即使被虐到爆炸也停不下来。
最近看上一款某渊的音游,它的判定线移动打拍的玩法挺不错的,于是乎又手贱买了,然后被虐到体无完肤。
本着至少要给自己爽一下的原则,就准备对它下毒手了。
首先拆一下压缩包,发现是unity引擎,而且还是Assembly-CSharp.dll的方式,感觉也许会轻松不少(并不是
拖出来,上dnSpy,理所当然地出错。
拖进UltraEditor,发现可以明显地看出PE文件结构,就是有点怪而已。所以这应该不是那种简单地把整个dll文件套个加密算法,然后加载的时候解密那种方法。
大概是加密了关键部分,然后在解析的时候再解密吧。
试一下能不能Dump,ida附加,结果游戏直接闪退,估计是有反调试之类的,咱一个逆向萌新,对安卓方面的反调和反反调完全不懂,瞬间懵逼。
于是咱打算换一个歪门邪道,用GG修改器的Dump功能,这个游戏似乎没有针对GG修改器做什么。
Dump下整个游戏进程,然后在里面搜索DLL,把文件提取出来,但是发现,其他什么UnityEngine什么的都有,唯独缺了Assembly-CSharp.dll。
抱着不祥的预感在内存中搜索6D 7A 90 00 03 00 00 00 04,发现dll文件本身在内存中没有半点变化……
估计是魔改mono文件,然后用自己的一套来解析的吧……
拖libmono.so到ida里面,结果发现libmono.so也被搞了……
看一下Hex,被大量无意义字节填充了,估计是在载入so的时候再将原函数填回去……
还真是到处搞事情啊,不过so方面不比dll,到了内存里面你总得把这些函数给我还回来吧。
还是上GG(真好用),把libmono.so给Dump出来,记得选范围时要完整,宁可范围搞大一点,也别少选
再拖进IDA,现在就没问题了
通过之前的试探,我们大概可以猜测一下dll被加载的过程,首先是原文件读入内存,再通过魔改后的mono使用自己的一套方法来解析文件。
看其他dll都没有事,所以在这个魔改后的mono中应该分开了两种不同的加载方式。
mono加载dll的时候,首先是进
mono_image_open_from_data_with_name这个函数(这个应该就不用细说了),然后走到
do_mono_image_load对PE文件进行分析。
static MonoImage *
do_mono_image_load (MonoImage *image, MonoImageOpenStatus *status,
gboolean care_about_cli, gboolean care_about_pecoff)
{
MonoCLIImageInfo *iinfo;
MonoDotNetHeader *header;
mono_profiler_module_event (image, MONO_PROFILE_START_LOAD);
/* if MONO_SECURITY_MODE_CORE_CLR is set then determine if this image is platform code */
image->core_clr_platform_code = mono_security_core_clr_determine_platform_image (image);
mono_image_init (image);
iinfo = image->image_info;
header = &iinfo->cli_header;
if (status)
*status = MONO_IMAGE_IMAGE_INVALID;
if (care_about_pecoff == FALSE)
goto done;
if (!mono_verifier_verify_pe_data (image, NULL))
goto invalid_image;
if (!mono_image_load_pe_data (image))
goto invalid_image;
if (care_about_cli == FALSE) {
goto done;
}
if (!mono_verifier_verify_cli_data (image, NULL))
goto invalid_image;
if (!mono_image_load_cli_data (image))
goto invalid_image;
if (!mono_verifier_verify_table_data (image, NULL))
goto invalid_image;
#ifndef USE_COREE
/* if the last bit is not set, then the image is mixed mode with native code */
if (!(iinfo->cli_cli_header.ch_flags & 1))
goto invalid_image;
#endif
mono_image_load_names (image);
load_modules (image);
done:
mono_profiler_module_loaded (image, MONO_PROFILE_OK);
if (status)
*status = MONO_IMAGE_OK;
return image;
invalid_image:
mono_profiler_module_loaded (image, MONO_PROFILE_FAILED);
mono_image_close (image);
return NULL;
}
首先是mono_image_load_pe_data函数对PE文件进行解析,这里有一个检验MZ头的步骤,而这个dll的头部是mz,所以这里应该有问题
static MonoImage *
do_mono_image_load (MonoImage *image, MonoImageOpenStatus *status,
gboolean care_about_cli, gboolean care_about_pecoff)
{
MonoCLIImageInfo *iinfo;
MonoDotNetHeader *header;
mono_profiler_module_event (image, MONO_PROFILE_START_LOAD);
/* if MONO_SECURITY_MODE_CORE_CLR is set then determine if this image is platform code */
image->core_clr_platform_code = mono_security_core_clr_determine_platform_image (image);
mono_image_init (image);
iinfo = image->image_info;
header = &iinfo->cli_header;
if (status)
*status = MONO_IMAGE_IMAGE_INVALID;
if (care_about_pecoff == FALSE)
goto done;
if (!mono_verifier_verify_pe_data (image, NULL))
goto invalid_image;
if (!mono_image_load_pe_data (image))
goto invalid_image;
if (care_about_cli == FALSE) {
goto done;
}
if (!mono_verifier_verify_cli_data (image, NULL))
goto invalid_image;
if (!mono_image_load_cli_data (image))
goto invalid_image;
if (!mono_verifier_verify_table_data (image, NULL))
goto invalid_image;
#ifndef USE_COREE
/* if the last bit is not set, then the image is mixed mode with native code */
if (!(iinfo->cli_cli_header.ch_flags & 1))
goto invalid_image;
#endif
mono_image_load_names (image);
load_modules (image);
done:
mono_profiler_module_loaded (image, MONO_PROFILE_OK);
if (status)
*status = MONO_IMAGE_OK;
return image;
invalid_image:
mono_profiler_module_loaded (image, MONO_PROFILE_FAILED);
mono_image_close (image);
return NULL;
}
首先是mono_image_load_pe_data函数对PE文件进行解析,这里有一个检验MZ头的步骤,而这个dll的头部是mz,所以这里应该有问题
不仅仅认正常的MZ,也认修改后的mz,没错了。
顺着加载过程摸下去,我们还可以找到魔改后同时认可PE和pe的部分
修改这里两处标识后之后,继续尝试读取dll文件,仍旧失败,根据失败原因继续走,发现时dll文件的段头被加密了,在mono中对应部分找到解密方法
段头的每一项都被单独处理,加密过程中密钥会随着改变的一个流加密,有伪代码的情况下还原解密方法没什么难度。
继续读取,发现PE结构解析部分没有问题,但还是出错了,继续往后跟,发现是.Net部分读取时出了问题
在#~读取的时候,其中的各个项都出现读取失败的情况,但是奇怪的是,最开始的几个项读取正常。在load_tables函数中发现,读取MaskValid时做了处理,将MaskValid和MaskSorted异或后再保存
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)