本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(1)

unity游戏C#脚本Dump

发布于2021-04-17 18:37     阅读(1973)     评论(0)     点赞(5)     收藏(2)


unity游戏C#脚本Dump

unity游戏加载流程

libmain.so这个是Unity启动的时候最开始加载的,先加载了libmain.so,才能继续加载其他相关的so

libmain.so会加载两个so,一个是libmono.so,另一个是libunity.so

libunity.so则是unity底层写的cpp

libmono.so 相当于负责加载解析dll文件运行,运行时主要工作框架如下

mini/main.c: main()
    mono_main_with_options()
        mono_main()    --mini/driver.c
            mini_init()    --mini/mini.c
            mono_assembly_open()    --metadata/assembly.c
            --mono_assembly_load_from_full() ------对文件名进行判断 是否已 file://开头
            ----mono_assembly_is_in_gac --判断是否在gac当中
            ----mono_assembly_open_from_bundle()
            ------mono_image_open_from_data_with_name()  --image.c
            --------do_mono_image_load()
            main_thread_handler() // assembly(也就是bytecode)的编译执行

首先判断文件名是否为file://开头,之后依据文件名称判断是否在gac之中,最后打开bundle,然后在 mono 虚拟机上加载并且执行

其中较为重要的函数为mono_image_open_from_data_with_name,其中的第一个参数,包含了加载文件的二进制码。

一般获取Unity游戏源码,需要Hook mono_image_open_from_data_with_name获取第一个参数即可dump出加载的二进制码

实例分析

unity3D游戏中Assembly-CSharp.dll 中存放着游戏的核心代码,看了一下发现被加密

由于加密无法分析C#代码,需要解密或者dump出源码。

寻找解密c#函数

libmono.so追踪

Assembly-CSharp.dll是由 libmono.so 运行时读取然后在 mono 虚拟机上执行,关键函数为mono_image_open_from_data_with_name

因此IDA反编译libmono发现so被加密,用gg修改器dump出libmono.so的内存,比较原先机器码,发现为xor 0xd9加密

阅读源码发现字符串data-%p

依据关键字搜索dump出的内存,仔细查看反编译出的函数代码,并没有找到加密的地方。阅读源码向上溯源

mono_assembly_load_from_full->mono_assembly_open_from_bundle

在mono_assembly_open_from_bundle之中发现了奇怪的一处,0x194bf8应该是真正的mono_image_open_from_data_with_name

此处本应该指向mono_image_open_from_data_with_name,但是194bf8处的地址明显被Hook

此处为inline_hook的shellcode

调用一个导出函数g_tprt_pfn_array+0x68

可以大概得知此处是调用Assembly-csharp解密的地方。

借此发现Hook点还有很多都是和g_tprt_pfn_array导出函数有关,就不细看了

libtprt逆向

libmain.so这个是Unity启动的时候最开始加载的,先加载了libmain.so,才能继续加载其他相关的so

libmain.so会加载两个so,一个是libmono.so,另一个是libunity.so

看了一下libmain.so中并没有对libmono的.so进行解密就直接加载了,有点奇怪,因为libmono.so是被加密的。

去HOOK dlopen看了一下在libmain之前加载的so,发现第一个加载的就是libtprt.so

看到libtprt.so 网上查了一下,发现其是用来保护的

libtprt.so所有字符串都被加密了,函数偏移为0x8638,写个Frida来调用Hook该函数

该脚本用来Hook解密字符串函数,得到参数,返回结果,寄存器LR的值,方便定位关键点

这里需要在启动前注入,游戏启动时,frida无法注入。

因为是第一个加载的.so,所以需要先Hook dlopen函数,来获取Hook时机。

frida -U -f com.tencent.tmgp.cf --no-pause -l 2.js执行如下关键点

Java.perform(function(){
	var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
	console.log(android_dlopen_ext);
	if(android_dlopen_ext != null){
		Interceptor.attach(android_dlopen_ext,{
        onEnter: function(args){
            var soName = args[0].readCString();
            if(soName.indexOf("libtprt.so") != -1){
                this.hook = true;
            }
        },
        onLeave: function(retval){
            if(this.hook) {
                dlopentodo();
            };
        }
    });
	}
	
	function dlopentodo(){
		var soAddr = Module.findBaseAddress("libtprt.so");
		var ptrTencentEncrypt_addr = soAddr.add(0x8639);
		var file = new File("/sdcard/encrypt.txt","a+");
		file.write("soaddr is :"+soAddr.toString()+"\n");
		Interceptor.attach(ptrTencentEncrypt_addr,{
        onEnter: function(args){
			 file.write("r0 is "+(this.context.r0).toString()+" ");
			 file.write("LR is "+(this.context.lr-soAddr).toString(16)+" ");
        },
        onLeave: function(retval){
          file.write(retval.readCString()+"\n");
        }
    });
	}   
})

定位到几处解密出字符串Assembly-CSharp.dll关键点,发现部分为验证文件头MZ和进行CRC校验.

发现一个可疑函数函数偏移为0xe900,Hook参数前后的值,即可发现,此处为Assembly_csharp解密

同时0xe900上层调用0x3381正好是导出函数 g_tprt_pfn_array+0x68的值

其也是mono_image_open_from_data_with_name的Hook函数

仔细分析,发现其前0x1000个字符进行了AES解密算法 大小为0x1000

IV为 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11

key为36 64 34 33 35 66 32 36 08 09 0a 0b 0c 0d 0e 0f

AES部分验证如下

后0x1000个字节采用了xor 0x87

解密脚本如下

from Crypto.Cipher import AES

key = '\x36\x64\x34\x33\x35\x66\x32\x36\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
iv = '\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11'
aes = AES.new(key, AES.MODE_CBC,iv)
dec = open('Assembly-CSharp.dll', 'rb')
b = dec.read()
data=""
for i in range(0,0x1000,0x10):
    data+=aes.decrypt(b[i:i+16])
for i in range(0x1000,len(b)-0x1000,1):
    data+=chr(ord(b[i])^0x87)
with open("dec.dll",'wb') as fp:
    fp.write(data)

C# dump

尝试用dump

Hook c#函数mono_image_open_from_data_with_name,依据第一个参数来获取加载的C# dll文件,第二个参数获取C#

dll文件的大小。第八个参数以此来获取加载的dll名称。脚本如下

Java.perform(function(){
	var android_dlopen_ext = Module.findExportByName('libc.so', "dlopen");
	console.log(android_dlopen_ext);
	if(android_dlopen_ext != null){
		Interceptor.attach(android_dlopen_ext,{
        onEnter: function(args){
            var soName = args[0].readCString();
            if(soName.indexOf("libmono.so") != -1){
                this.hook = true;
            }
        },
        onLeave: function(retval){
            if(this.hook) {
                dlopentodo();
            };
        }
    });
	}
	
	function dlopentodo(){
		var soAddr = Module.findBaseAddress("libmono.so");
		var ptrTencentEncrypt_addr = soAddr.add(0x194bfc);
		Interceptor.attach(ptrTencentEncrypt_addr,{
        onEnter: function(args){
			
			var dllname=args[7].readCString();
			if(dllname.indexOf("Assembly-CSharp.dll")!=-1){
			console.log("begin write");
			var file = new File(("/sdcard/ass_dde"),"a+");
			var len=args[1]>>>0;
			var buffer=Memory.readByteArray(args[0], len);
		   file.write(buffer);
			}
			
        },
        onLeave: function(retval){
			
        }
    });
	}   
})

依据第八个参数来判断所需要替换的dll,之后读取本地文件,分配至内存,将原本dll内容替换为破解版的dll内容。这样即可实现CF外挂

核心代码如下

Interceptor.attach(func_addr,{
		onEnter: function(args){
			console.log("size is ",args[1]);
			var dllname=args[7].readCString();
			if(dllname.indexOf("Assembly-CSharp.dll")!=-1){
				var openPtr = Module.getExportByName('libc.so', 'open');
				var open_addr = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
				var dll=[0x2F,0x64,0x61,0x74,0x61,0x2F,0x6C,0x6F,0x63,0x61,0x6C,0x2F,0x74,0x6D,0x70,0x2F,0x32,0x2E,0x64,0x6C,0x6C,0x00];
				var dll_addr = Memory.alloc(dll.length);
				var mode_addr = Memory.alloc(0x10);
				Memory.writeByteArray(dll_addr,dll);
				console.log(dll_addr.readCString());
				var fd = open_addr(dll_addr,4);
				if(fd>0){
					var new_dll=Memory.alloc(24257536);///24257536
					var readPtr = Module.getExportByName('libc.so', 'read');
					var read_addr = new NativeFunction(readPtr, 'int', ['int','pointer','int']);
					var result=read_addr(fd,new_dll,24257536);
				}
                  Memory.copy(args[0],new_dll,24257536); //替换C#脚本
			}
		},
		onLeave: function(retval){
		}
	});

C#代码修改

修改人物跳跃高度

修改人物移动速度

将修改后的代码放在/data/local/tmp/上,frida脚本会读取替换mono_image_open_from_data_with_name函数的第一个参数

原文链接:https://blog.csdn.net/qq_39268483/article/details/115795885



所属网站分类: 技术文章 > 博客

作者:phpNumOne

链接:http://www.phpheidong.com/blog/article/44531/7be28cc2ee17fe2a0344/

来源:php黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

5 0
收藏该文
已收藏

评论内容:(最多支持255个字符)