U3D游戏天天爱捕鱼 DUMP DLL并修复以验证某文章PASS 某保护
一直在看你们发的贴子 却从没发过贴,主要是自己代码写的烂.逻辑又混乱.去年看过一个文章叫:"浅谈某NetHTProtect" 最近碰到个游戏 正好验证那个文章.
本来是3月份就处理过了.今天有时间整理了一下.
-------------------------------------------------------
WINRAR直接打开APK: 发现某个保护存在:
直接WINHEX打开DLL明显是被加密了的:
IDA打开 libmono.so,发现没有相关的导出.猜测应该是加载的时候会动态修改SO.
在游戏跑起来以后DUMP出SO.发现猜测是正确的.
于是写一个SO注入.使用线程循环查找SO 的mono_image_open_from_data_with_name 如果存在则是SO解密后 已经加载.再执行对应的HOOK:
但是遗憾这个游戏HOOK mono_image_open_from_data_with_name要崩,且DUMP不出DLL.
换成:mono_assembly_load_from_full 将DLL保存出来. 当然如文章所说.能看到方法名.但是DNSPY并不能解析.因为方法内容是空的.
原文章的说法是 mono_metadata_parse_mh_full 在处理方法的内容但是有个参数不好构造.
查看MONO源码发现mono_metadata_parse_mh_full的定义是:
MonoMethodHeader *
mono_metadata_parse_mh_full (MonoImage *m, MonoGenericContainer *container, const char *ptr, MonoError *error)
那个不好构造的参数就是MonoGenericContainer.
原文章应该是通过动态分析找到了 保护的DECODE函数.然而我并没有条件用IDA动态调试(因为用的模拟器)
我就必须得自己构造第2个参数(当然后面就忘记了)
先定义一个全局变量:void* ddt 用来保存游戏加载的DLL
再利用上面Hook的 mono_assembly_load_from_full 函数.
通过 文件名判断是不是我们要修复的DLL,如果是
则保存 image 到ddt 并动态读取已dump出的DLL到数组:acfile.设置标记: bNeedFix=1;
然后再Hook mono_runtime_invoke 在这里面通过标记判断是否需要修复.
如果 bNeedFix>0 则开始判断当前的DLL是否已被游戏处理完成(也就是游戏是不是把dll解密了)
判断方法就是 先获取被加载DLL的Image 取得其表信息.再进一步.取得方法总数.
(之所以要在mono_runtime_invoke中调用自己的处理函数是因为经验告诉我们,不要在线程中去调用游戏的函数.容易各种问题.增加难度.而且这个函数.MONO会频繁调用.省心省事,注入自己的DLL也方便)
如果方法总数不为0 就开始还原方法内容(期间啃mono源码相当烦.直接上源码)
直接 int crows=0 循环 一个个的还原:
uint32_t cols;
mono_metadata_decode_row(table_info, crows, cols, MONO_METHOD_SIZE);
uint32_t rva = cols;
MonoMethod *method = mono_get_method_full(ddt, MONO_TOKEN_METHOD_DEF | (crows + 1), NULL, NULL);
void *container = mono_method_get_generic_container(method);
if (!container)
{
MonoClass* mc = (MonoClass*)method->klass;
if (mc)
{
container = mc->generic_container;
loginfo("------>不能获取container 使用类的container");
}
else
{
loginfo("ERROR !!! 不能取得 container!!!"); while (true) { sleep(1); }
}
}
void* loc = mono_image_rva_map(ddt, rva, 0, 1);
unsigned char flags = *(const unsigned char *)loc;
unsigned char nformat = flags & METHOD_HEADER_FORMAT_MASK;
int savepy = 0;
int imgx = (int)loc;
unsigned short codesize = 0;
const unsigned char *ncode;
if (nformat == METHOD_HEADER_TINY_FORMAT)
{
ncode = (unsigned char*)(imgx + 1);
savepy = 1;
codesize = flags >> 2;
}
else if (nformat == METHOD_HEADER_FAT_FORMAT)
{
unsigned short stacksize = *(unsigned short *)(imgx + 2);
codesize = *(unsigned short *)(imgx + 4);
ncode = (unsigned char*)(imgx + 12);
savepy = 12;
}
else {
loginfo("!!! ERROR !!! FORMAT 错误!%d/%d", flags, nformat);
while (true) { sleep(1); }
}
void* error;
FAKEMonoMethodHeader* rt = mono_metadata_parse_mh_full(ddt, container, (const char *)loc, error);//叫FAKE 是因为 size和DATA 与MONO不一样。游戏改了位置的
//dumpCode(rt->code, rt->code_size, "ret");
int dt = rva - 0x1c00;//本来是RVA-text段的虚地址+ PointertoRawData(物理地址) 但是这里。虚地址是2000 物理是400 最后就是1c00
//int dt = rva - 0x1E00;//ac first 是200物理地址 所以是1E00
if (savepy > 0)
{
loginfo("------>write:%x 0保存偏移:%x",dt, savepy);
Fix_writeCount++;
for (int i = 0; i < rt->code_size; i++)
acfile = rt->code;
}
else
{
loginfo("!!!! error !!! 保存偏移不可能是0 算错了?。");
while (true) { sleep(1); }
}
最后将acfile数组 保存出来...
代码没贴的很完整是因为...从上面部分代码 已经能看到...我其实写的很烂.....
一个代码写的很烂的人是怎么脑补出来这些内容的呢,例如上面方法头的格式......
答案就是:照抄MONO的源码! 只要在MONO源码中搜索上面的函数. 一层层返回看.就能MONO是怎么处理方法内容的了.
最后DNSPY打开 保存出来的DLL:
很明显..存在LUA.不过既然DLL已经有了..就没必要在SO中导出LUA了.... 直接写C#多爽
打开VS.新建一个 DLL.选择 .net 3.5
依赖里面添加UNITYENDINE.DLL和游戏的DLL.
然后就和他们写U3D游戏一样.写一个 MonoBehaviour 类.在ONGUI里面出几个按钮..直接调用 Assembly-CSharp.dll 中的类导出LUA就好了.
(下面代码不包括导出LUA..因为这个游戏LUA很常规不特别)
然后利用上面HOOK的mono_runtime_invoke函数.判断一下Assembly-CSharp.dll是否已经解密并加载,再注入我们自己写的DLL:
void doInject()
{
char* fn = "/data/local/yu.dll";
if (access(fn, 0) > -1)
{
bInject = true;//只调用 一次
FILE* ff = fopen(fn, "rb");
fseek(ff, 0, SEEK_END);
int size = ftell(ff);
char* buff = (char*)malloc(size);
rewind(ff);
fread(buff, 1, size, ff);
fclose(ff);
loginfo("DLL存在,开始注入");
mono_thread_attach(mono_get_root_domain());
void* domain = mono_domain_get();
if (!domain) { loginfo(" domain 获取失败,不能注入."); return; }
void* assembly = mono_domain_assembly_open(domain, fn);
if (!assembly) { loginfo(" assembly 打开失败,不能注入."); return; }
void* image = mono_assembly_get_image(assembly);
if (!assembly) { loginfo(" 获取Image失败,不能注入."); return; }
void* mc = mono_class_from_name(image, "yu", "Loader");
if (!mc) { loginfo(" 获取类名失败,不能注入."); return; }
MonoMethod* func = (MonoMethod*)mono_class_get_method_from_name(mc, "init", 0);
if (!func) { loginfo(" 获取函数失败,不能注入."); return; }
void* obj = mono_runtime_invoke(func, NULL, NULL, NULL);
loginfo("注入成功返回的对象:%x", obj);
}
else
{
loginfo("DLL 不存在????????????????");
bInject = true;
}
}
PS:上面代码的mono_runtime_invoke 是mono的名字.我HOOK后跳转是叫 new_mono_runtime_invoke.....是在 new_mono_runtime_invoke函数中 调用 的 doinject...
最后:
下面那几排黄字是 showdbginfo函数调用. EventSystem.current 取得当前鼠标下的对象... 改U3D必备阿...通过当前指向的gameobjet 一层一层往上找.就获得了一个路径
每返回一层.获取下gamoebject 上面的 类..就能大概知道你当前指向的gameobject 属于Assembly-CSharp.dll中的那个类...
再在DNSPY里面去搜索这个类.......
em................
验证完成后..这破游戏 我就扔一边了..因为并不好玩...
本帖最后由 meiercn 于 2019-5-1 19:06 编辑
补一个返回结构的图.代码中我是用的 FAKEMonoMethodHeader
区别在:
其他代码中要用的 头文件.结构 变量 都可以在MONO源码中找到.
(从我那些备注应该能看出来..我其实19年前并不会C...阿.好尴尬{:1_889:}) meiercn 发表于 2019-5-1 19:04
补一个返回结构的图.代码中我是用的 FAKEMonoMethodHeader
区别在:
大神,这个修改后能达到什么效果?玩这破游戏花了几百块钱了,现在啥也剩下~~~~~,这个游戏能改吗???简单的也行,就显示这个BOSS能不能打死就行,或者显示这个BOSS的几率~~~~ 最后一句秀了 易盾,我就是调用mono_metadata_parse_mh_full这个解密来着,中间那个container直接填0 本帖最后由 FraMeQ 于 2019-5-1 21:46 编辑
楼主很厉害 感谢楼主分享:lol学习了 楼主厉害了,多谢 此贴精髓在那句因为并不好玩
感谢楼主排雷 谢谢楼主教程。。。我可以照精华画虎了。。。
此贴必精华(不是的话当我没说。。。)