吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 18984|回复: 29
收起左侧

[Android 原创] U3D游戏天天爱捕鱼 DUMP DLL并修复以验证某文章PASS 某保护

  [复制链接]
meiercn 发表于 2019-5-1 18:56
一直在看你们发的贴子 却从没发过贴,主要是自己代码写的烂.逻辑又混乱.
去年看过一个文章叫:"浅谈某NetHTProtect" 最近碰到个游戏 正好验证那个文章.
本来是3月份就处理过了.今天有时间整理了一下.
-------------------------------------------------------


WINRAR直接打开APK: 发现某个保护存在:
1.jpg
直接WINHEX打开DLL明显是被加密了的:
2.jpg

IDA打开 libmono.so,发现没有相关的导出.猜测应该是加载的时候会动态修改SO.
3.jpg

在游戏跑起来以后DUMP出SO.发现猜测是正确的.
于是写一个SO注入.使用线程循环查找SO 的mono_image_open_from_data_with_name 如果存在则是SO解密后 已经加载.再执行对应的HOOK:
5.jpg
但是遗憾这个游戏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的定义是:
[C] 纯文本查看 复制代码
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解密了)
6.jpg

判断方法就是 先获取被加载DLL的Image 取得其表信息.再进一步.取得方法总数.
7.jpg
(之所以要在mono_runtime_invoke中调用自己的处理函数是因为经验告诉我们,不要在线程中去调用游戏的函数.容易各种问题.增加难度.而且这个函数.MONO会频繁调用.省心省事,注入自己的DLL也方便)
如果方法总数不为0 就开始还原方法内容(期间啃mono源码相当烦.直接上源码)
直接 int crows=0 循环 一个个的还原:
[C] 纯文本查看 复制代码
uint32_t cols[MONO_METHOD_SIZE];
mono_metadata_decode_row(table_info, crows, cols, MONO_METHOD_SIZE);
uint32_t rva = cols[MONO_METHOD_RVA];
 
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[dt + savepy + i] = rt->code[i];
						}
						else
						{
							loginfo("!!!! error !!! 保存偏移不可能是0 算错了?。");
							while (true) { sleep(1); }
							
						}



最后将acfile数组 保存出来...
代码没贴的很完整是因为...从上面部分代码 已经能看到...我其实写的很烂.....
一个代码写的很烂的人是怎么脑补出来这些内容的呢,例如上面方法头的格式......
答案就是:照抄MONO的源码! 只要在MONO源码中搜索上面的函数. 一层层返回看.就能MONO是怎么处理方法内容的了.

最后DNSPY打开 保存出来的DLL:
17.png

GIF.gif

很明显..存在LUA.不过既然DLL已经有了..就没必要在SO中导出LUA了.... 直接写C#多爽

打开VS.新建一个 DLL.选择 .net 3.5
依赖里面添加UNITYENDINE.DLL和游戏的DLL.
然后就和他们写U3D游戏一样.写一个 MonoBehaviour 类.在ONGUI里面出几个按钮..直接调用 Assembly-CSharp.dll 中的类导出LUA就好了.
(下面代码不包括导出LUA..因为这个游戏LUA很常规不特别)
993.jpg

然后利用上面HOOK的mono_runtime_invoke  函数.判断一下  Assembly-CSharp.dll  是否已经解密并加载,再注入我们自己写的DLL:
[C] 纯文本查看 复制代码
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...

最后:
196.png
1999.jpg

下面那几排黄字是 showdbginfo函数调用. EventSystem.current 取得当前鼠标下的对象... 改U3D必备阿...通过当前指向的gameobjet 一层一层往上找.就获得了一个路径
每返回一层.获取下gamoebject 上面的 类..就能大概知道你当前指向的gameobject 属于  Assembly-CSharp.dll  中的那个类...
再在DNSPY里面去搜索这个类.......
em................
验证完成后..这破游戏 我就扔一边了..因为并不好玩...


7.jpg

免费评分

参与人数 11威望 +3 吾爱币 +21 热心值 +11 收起 理由
青山依旧 + 1 + 1 牛皮 大佬
qtfreet00 + 3 + 12 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Juch + 1 + 1 用心讨论,共获提升!
mdx + 1 + 1 我很赞同!
云幻灭 + 1 + 1 用心讨论,共获提升!
独行风云 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
nj001 + 1 + 1 谢谢@Thanks!
笙若 + 1 + 1 谢谢@Thanks!
多幸运遇见baby + 1 + 1 用心讨论,共获提升!
qaz003 + 1 用心讨论,共获提升!
zhanghui1982 + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| meiercn 发表于 2019-5-1 19:04
本帖最后由 meiercn 于 2019-5-1 19:06 编辑

补一个返回结构的图.代码中我是用的 FAKEMonoMethodHeader
区别在:
X33.jpg
其他代码中要用的 头文件.结构 变量 都可以在MONO源码中找到.


(从我那些备注应该能看出来..我其实19年前并不会C...阿.好尴尬)
7877726 发表于 2020-1-29 00:19
meiercn 发表于 2019-5-1 19:04
补一个返回结构的图.代码中我是用的 FAKEMonoMethodHeader
区别在:

大神,这个修改后能达到什么效果?玩这破游戏花了几百块钱了,现在啥也剩下~~~~~,这个游戏能改吗???简单的也行,就显示这个BOSS能不能打死就行,或者显示这个BOSS的几率~~~~
沫忆 发表于 2019-5-1 19:00
aopdnf 发表于 2019-5-1 21:18
易盾,我就是调用mono_metadata_parse_mh_full这个解密来着,中间那个container直接填0
FraMeQ 发表于 2019-5-1 21:27
本帖最后由 FraMeQ 于 2019-5-1 21:46 编辑

楼主很厉害
头像被屏蔽
V_情思指尖绕 发表于 2019-5-1 22:05
提示: 作者被禁止或删除 内容自动屏蔽
chenjingyes 发表于 2019-5-2 00:07
感谢楼主分享    学习了
feob 发表于 2019-5-2 06:54
楼主厉害了,多谢
艾莉希雅 发表于 2019-5-2 10:15
此贴精髓在那句因为并不好玩
感谢楼主排雷
涛之雨 发表于 2019-5-2 13:20
谢谢楼主教程。。。我可以照精华画虎了。。。
此贴必精华(不是的话当我没说。。。)
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 16:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表