2015-02-04 10:15:45 qq396225997 阅读数 765
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12992 人正在学习 去看看 宋晓波

有需求是在完整的apk中截取Android Unity3D并暴露Java的接口。这次文章就是这方面实现的一个小例子。

首先反编译apk,把asserts/bin/manager 文件下的UnityEngine 中的getkey 方法加入调用Android代码的调用:

AndroidJNI.AttachCurrentThread();
    AndroidJNI.PushLocalFrame(0);
    try
    {
        Debug.Log("kyx_keyevent run");
        using (AndroidJavaClass class2 = new AndroidJavaClass("com.kuaiyouxi.keyevent.KYXUnityKeyEvent"))
        {
            object[] args = new object[] { key };
            key = (KeyCode) class2.CallStatic<object>("kyxKeyEvent", args);
        }
    }
    catch (Exception)
    {
        Debug.LogError("kyx_keyevent run");
    }
    finally
    {
        AndroidJNI.PopLocalFrame(IntPtr.Zero);
    }
return GetKeyString(name);

Debug.Log("kyx_keyevent "+(int)key);

Reflector 

package com.kuaiyouxi.keyevent;

import android.util.Log;

public class KYXUnityKeyEvent {
	public static int kyxKeyEvent(int obj){
		Log.e("kyx_keyevent", "run in kyx_keyevent"+obj);
		if(obj == 97 || obj == 100 || obj == 96 || obj == 23){
			obj = 0;
		}
		return obj;
	}
	
	public static String kyxKeyEventString(String obj){
		Log.e("kyx_keyevent", "run in kyx_keyevent"+obj);
		return obj;
	}
	
	public static int kyxKeyDownEvent(int obj){
		Log.e("kyx_keyDownevent", "run in kyx_keyDownevent"+obj);
		if(obj == 97 || obj == 100 || obj == 96 || obj == 23){
			obj = 0;
		}
		return obj;
	}
	
	public static String kyxKeyDownEventString(String obj){
		Log.e("kyx_keyDownevent", "run in kyx_keyDownevent"+obj);
		return obj;
	}
	
	public static int kyxKeyUpEvent(int obj){
		Log.e("kyx_keyUpevent", "run in kyx_keyUpevent"+obj);
		if(obj == 97 || obj == 100 || obj == 96 || obj == 23){
			obj = 0;
		}
		return obj;
	}
	
	public static String kyxKeyUpEventString(String obj){
		Log.e("kyx_keyUpevent", "run in kyx_keyUpevent"+obj);
		return obj;
	}
	
	
	
}

把这个程序编译打包成apk。然后反编译,把上诉代码的smali放进unity3d apk中,打包签名。就可以跑起来了。是不是很简单~

2015-02-03 10:47:27 qq396225997 阅读数 531
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12992 人正在学习 去看看 宋晓波

目的:截取Android Unity3d游戏交互事件,例如游戏中的keycode事件,并做自定义修改。

工具:apktool , .net Reflector 以及插件Reflexil

前言:

Android游戏的项目中,遇到Unity3D游戏的情况特别多。研究一下,废话不多说,植入主题:

操作:

1.先拿到一个Unity3D 的游戏,用apktool 反编译,去到asserts/bin/Data/Manager 目录下,里面就是存放Unity3d 的dll 文件。

2.把UnityEngine 拷贝到一个新的文件夹里。(因为在原来的文件夹编译会提示重复引用的异常,拷出来是最笨的方法了)用Reflector 


这几个方法就是Unity3d C# 写的获取事件的方法了。

3.直接用Reflexil 工具,对对应的方方法进行修改。


4.编译,放回asserts 文件夹下。编译打包签名apk。直接就可以运行了

2019-09-05 13:39:05 qq_43380549 阅读数 539
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12992 人正在学习 去看看 宋晓波

作者:dawu@知道创宇404实验室
时间:2019/02/25
英文版本:https://paper.seebug.org/943/
如果你想第一时间了解漏洞资讯,可以关注我们的知道创宇Paper:https://paper.seebug.org/829/

0x00 前言

这是一篇游戏引发的简单技术文。

起因是个人很喜欢玩 google play 上的一些数字类型(角色攻击是线性增长,怪物指数变强,到后期越打不过,通过重生增强属性变强)的小游戏。但是这种游戏仍旧存在一定缺陷,前期资源不多,玩的太慢、玩的时间长了,就感觉没意思,就不想玩了,所以在玩到游戏中期的时候,往往都会去网上搜索XXX破解版/内购版,快速进入后期然后放弃这款游戏。

这样的做法其实是很不安全的,因为无法判断XXX破解版/内购版在破解/内购之后还做了什么。所以我最后的解决办法是,逆向这些apk,修改游戏逻辑。让我在玩的时候,可以快速度过缓慢的前期。

逆向了几个玩过的游戏,发现这类游戏使用Unity3D开发的居多。因此本文将介绍简单Unity3D类安卓游戏的逆向修改思路。

0x01 准备工具

逆向最简单的Unity3D类安卓游戏建议使用安装好 JAVA 环境的Windows系统(涉及到dll文件的修改,所以Windows平台更加适合)。

1.1 安卓 APK 逆向三件套

一般 APK 逆向,常使用到 apktooldex2jarjd-gui。在逆向 Unity3D 安卓游戏时,仅仅只需要使用到 apktool

  • Apktool: 用于解压/重新打包安卓APK。
  • dex2jar: 将解压出来的dex文件变成jar,方便使用jd-gui查看
  • jd-gui: 查看dex文件逻辑

1.2 dll文件逆向三件套

因为一般的 Unity3D 安卓游戏的主逻辑都在 asserts/bin/data/Managed/Assembly-CSarp.dll 中,所以我们还需要 dll文件逆向/重新打包 的工具。

  • ILSpy: 用于查看dll程序逻辑
  • ILDASM: 用于反编译dll文件,生成il文件(存放了dll反编译后的指令)和res文件(反编译后的资源文件),可以安装WindowsSDK或者从网上下载。
  • ilasm: .net4.0自带了,位置在C:\Windows\Microsofr.NET\Framework\v4.0.30319\ilasm.exe

1.3 生成重新打包的自签名证书

修改完 apk 之后,需要对 apk 进行签名。该命令用于生成签名的证书。

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 0validity 10000
# 记住设置的密码,最后自签名应用的时候需要输入密码

0x02 开发一个简单的 Unity3D 游戏

用Unity3D开发了一个简单小游戏作为本文的样例,逻辑十分简单:

  1. 英雄每过一关战斗力都会增加100.
  2. 怪物的战斗力为 Math.pow(2,当前关数)
  3. 当英雄战斗力小于怪物的战斗力时,英雄无法闯关。英雄可以考虑修炼或者重生提高战斗力。
  4. 英雄每次修炼战斗力都会增加1000.
  5. 英雄选择重生后,关卡数清零,需要重新闯关,但英雄初始战斗力会增加 2000 * 重生前闯关数。
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

具体代码可以参考 Github

0x03 游戏逆向步骤

1.使用 apktool 解压游戏安装包

java -jar apktool.jar d game.apk

2.提取出 game/assets/bin/data/Managed/Assembly-CSarp.dll ,使用 ILSpy 打开即可看到 dll 里面的逻辑。

注: Unity3D开发的安卓游戏,其核心代码都在这个 dll 文件中,所以逆向/修改这个 dll 文件就可以了。这也是 Unity3D 和 其它安卓逆向不同的地方。

在这里插入图片描述

在没有混淆的情况下,反编译出的函数内容和原内容十分相似:

在这里插入图片描述

ILSpy 反编译的 Click1 内容

在这里插入图片描述
Click1 的原始代码

3.找到关键函数、关键逻辑后,就可以尝试反编译 dll 文件并修改。使用 ILDASMdll 文件反编译成 il 文件。使用 ILDASM 打开 dll 文件后, File -> dump 就可以导出反编译结果了。

在这里插入图片描述

4.根据步骤2,就很容易理解逻辑了,然后根据速查表,就可以知道在步骤3导出的il文件中修改哪里了。例如步骤2中 Click1 就是游戏中 点击闯关 按钮绑定的逻辑。闯关的关键判断就在: info.hero_power + info.temp_power + info.add_power >= info.monster_power。所以打开步骤3中生成的 .il 文件,结合 .NET IL 指令速查表修改这部分对应的关键逻辑即可。

在这里插入图片描述

修改为 info.hero_power + info.temp_power + info.add_power != info.monster_power 就可以通过此处的逻辑判断。

在这里插入图片描述

5.修改关键逻辑后,通过重新编译 dll 文件、apk 文件、签名修改后的 apk 就可以在手机上安装运行了。

重新编译dll文件命令如下:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe game.il /output=Assembly-CSarp.dll /dll

将重新编译的dll放回 game/assets/bin/data/Managed/ 目录下,使用apktool重新打包apk:

java -jar apktool.jar b game
cp game/dist/game.apk ./

自签名应用:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore game.apk alias_name

6.修改成功,开局修炼一次后,就可以无限闯关。顺利到达第30关。

在这里插入图片描述

0x04 杂谈和总结

  1. Unity3D有一个较为明显的特征: 开局会显示游戏LOGO。这个可以作为判断一个游戏是不是Unity3D开发的小参考。
  2. 文中的demo到了31关,就会发生整型溢出,怪物战斗力变为负数。原因是怪物战斗力的值为int型。在以前玩过的某个后期极度不平衡的游戏中,我的确遇到过整型溢出的问题。造成花钱升级还能增余额的情况。
  3. 在修改游戏之前把游戏语言调整为英文有助于在逆向的时候理解各个函数的意义(对于没有混淆的应用)。
  4. 游戏修改之后,很容易丧失原本的乐趣,变成纯粹的数字游戏。谨慎修改!

0x05 参考链接

  1. Apktool
  2. ILSpy
  3. .NET IL 指令速查表
  4. Unity3d类安卓游戏逆向分析初探

本文由 Seebug Paper 发布,如需转载请注明来源。

欢迎关注我和专栏,我将定期搬运技术文章~

也欢迎访问我们:知道创宇云安全 :https://www.yunaq.com/?from=CSDN91905

在这里插入图片描述

如果你想与我成为朋友,欢迎加微信kcsc818~

2017-10-31 20:36:00 OnafioO 阅读数 187
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12992 人正在学习 去看看 宋晓波

基于unity3d游戏的android版本逆向初探

https://bbs.pediy.com/thread-212532.htm

【文章标题】: 基于unity3d游戏的android版本逆向初探

【文章作者】: dreaman
【作者邮箱】: [email]dreaman_163@163.com[/email]
【作者主页】: https://github.com/dreamanlan
【软件名称】: 匿了
【软件大小】: 好几百MB
【下载地址】: 自己搜索下载
【加壳方式】: 梆梆加密
【保护方式】: 梆梆
【编写语言】: unity3d
【使用工具】: 见后面总结
【操作平台】: android
【软件介绍】: 一款MMO游戏
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  0、背景
  最近某游戏老厂出了一款MMO手游,听说画面很好,是基于unity3d的,很想看一下都是怎样的效果,但可惜
  测试时间太短而且还要激活码,等我装好apk,进去时发现我既没有激活码,而且测试也结束了。。不过登录
  的界面看起来确实很好,所以,只好逆向一下看看。
  10年前我们在微软的dotnet平台下研究过一些安全相关的东东,10年后由于mono项目与unity3d的流行,dotnet
  技术竟然在移动平台流行起来,凭着当年断断续续的记忆与一些文档,我就这么搞了一次游戏逆向,呵呵。
  
  在移动平台,dotnet dll是没有签名的,这在一定程度上让逆向与修改更容易了。
  
  本文简要介绍一下本次逆向的过程,与游戏相关的内容就略去了。
  
  1、apk解包、重打包与签名
  使用“Android逆向助手”或ApkStudio都可以,我们使用ApkStudio解包,Android逆向助手重新打包并签名。
  
  2、梆梆加密解密
  解包后的一级目录如下:
    AndroidManifest.xml
    apktool.yml
    assets/
    build/
    lib/
    original/
    res/
    smali/
  首先用Reflector或IlSpy打开assets/bin/Data/Managed目录下的Assembly-CSharp.dll这个标准的unity3d游戏模块,
  打开失败,文件加密了。。
  打开lib/armeabi-v7a目录,可以看到依赖的so文件如下:
    libAkSoundEngine.so
    libBlueDoveMediaRender.so
    libCrasheyeNDK.so
    libDexHelper.so
    libKGAudio.so
    libmain.so
    libmono.so
    libmsc.so
    libslua.so
    libunity.so
    libuwa.so
    libweibosdkcore.so
  
  从文件名看了看,多数是功能性模块,只有libDexHelper.so比较可疑,上网搜了一下知道是梆梆的东东。网上没找到
  梆梆用于unity3d游戏的加密原理与方案等信息,只能自己看看了。
  
  这时候得用上ida pro了,我用的是6.6版本,首先看一下libmono.so,这个是mono的运行时库,对dotnet的加载及运行
  支持都在这里,直接看一下mono_image_open_from_data_with_name函数,看了一下,没有明显被修改的痕迹,又找了
  几个相关的加载相关函数,都没发现修改痕迹。。
  
  怀疑是不是根本没有修改libmono.so,unity3d的版本信息在其资源文件里比较容易查到,随便打开assets/bin/Data下
  的某个assets后缀的文件(记得用十六进制编辑器),可以在文件开头看到5.3.3p2,居然用的是一个补丁版本而不是f1
  这种正式版本,去unity3d网站下个对应版本的编辑器+android发布包,安装后找到
  Unity5.3.3p2\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Libs\armeabi-v7a\libmono.so
  用Beyond Compare等支持二进制比较的工具与游戏里的libmono.so对比一下,完全相同。看来梆梆是不会静态修改
  libmono.so的了,想想也是,像unity3d这种版本频繁更新的,梆梆要是每个版本都静态改一下,还是挺被动的。不过
  话说某厂的加密就是静态修改的libmono.so(其实是自己修改源码了重新编译的)。
  
  搞不清libDexHelper.so是怎么工作的,我不是专业搞逆向的,用ida pro打开看了看,没发现线索就放弃了
  (在ida pro里没找到Assembly-CSharp.dll这样的字符串,不过用十六进制编辑器在libDexHelper.so文件尾是看到有
  这个字符串的,有兴趣的同学可以研究下它的加密原理)。
  
  我换了个思路来得到解密后的dll,就是从libmono.so动手,前面已经发现梆梆没有对这个文件进行静态处理,所以我们
  想怎么处理都比较容易,看mono的源码知道在mono_image_open_from_data_with_name函数里dll文件的内容会以完整的内
  存映像出现。
  
  所以至少有几种办法得到解密后的dll:
  1)、断点后dump内存,我用的是电脑上的虚拟机,断不了。。
  2)、修改mono源码,加入dump代码再替换游戏的libmono.so,这个理论上是可行的,但为了这么点事有点费劲了
  3)、直接修改libmono.so,手动打补丁,看了下mono源码后,发现mono_image_open_from_data_with_name函数的开头有
  一段判空检查,正常情况没什么用,看了下ida pro里这段代码占用的字节数,足够打补丁的了:)
  
  我们还需要找一个能放我们补丁代码的地方,这需要一个不怎么被使用的函数,连蒙代猜的,我选择了
  mono_load_remote_field,这个函数的空间足够写很多代码了。
  
  我们要在函数开头加的代码如下:
    
    if(data_len>6000000){
      FILE* fp = fopen("/data/local/tmp/test.dll","wb");
      if(fp){
        fwrite(data,1,data_len,fp);
        fclose(fp);
      }
    }
  
  开始的字节数判断是为了只dump想解密的dll,因为这个dll个头很大,用大小就可以判断了,这样还比较省字节数:)
  我们要手动打补丁,流程大概如下:
  1)、先翻译成字节码,这里我使用ADS 1.2来编译代码片断,字节码与反汇编如下:
      $a
      .text
          0x00000000:    e92d4070    p@-.    STMFD    r13!,{r4-r6,r14}
          0x00000004:    e1a06000    .`..    MOV      r6,r0
          0x00000008:    e59f003c    <...    LDR      r0,0x4c
          0x0000000c:    e1a05001    .P..    MOV      r5,r1
          0x00000010:    e1510000    ..Q.    CMP      r1,r0
          0x00000014:    98bd8070    p...    LDMLSFD  r13!,{r4-r6,pc}
          0x00000018:    e28f0034    4...    ADD      r0,pc,#0x34 ; #0x54
          0x0000001c:    e28f102c    ,...    ADD      r1,pc,#0x2c ; #0x50
          0x00000020:    ebfffffe    ....    BL       fopen
          0x00000024:    e1b04000    .@..    MOVS     r4,r0
          0x00000028:    08bd8070    p...    LDMEQFD  r13!,{r4-r6,pc}
          0x0000002c:    e1a03004    .0..    MOV      r3,r4
          0x00000030:    e1a02005    . ..    MOV      r2,r5
          0x00000034:    e3a01001    ....    MOV      r1,#1
          0x00000038:    e1a00006    ....    MOV      r0,r6
          0x0000003c:    ebfffffe    ....    BL       fwrite
          0x00000040:    e1a00004    ....    MOV      r0,r4
          0x00000044:    e8bd4070    p@..    LDMFD    r13!,{r4-r6,r14}
          0x00000048:    eafffffe    ....    B        fclose
      $d
          0x0000004c:    005b8d80    ..[.    DCD    6000000
          0x00000050:    00006277    wb..    DCD    25207
          0x00000054:    7461642f    /dat    DCD    1952539695
          0x00000058:    6f6c2f61    a/lo    DCD    1869360993
          0x0000005c:    2f6c6163    cal/    DCD    795631971
          0x00000060:    2f706d74    tmp/    DCD    795897204
          0x00000064:    74736574    test    DCD    1953719668
          0x00000068:    6c6c642e    .dll    DCD    1819042862
          0x0000006c:    00000000    ....    DCD    0
  
  还挺好的,恰好把字符串放在代码后面了,特别适合在代码里打补丁。
  
  2)、然后再拷到目标函数里
  这里我用的Hex workshop,先在ida pro里找到函数对应的起始位置,然后把之前说的那段没什么用的代码nop掉,加一个
到mono_load_remote_field的调用,再把前面的字节码粘贴到函数mono_load_remote_field开头就可以了。
  
  3)、再对系统函数手动重定位一下。。
  因为我们用到了3个c语言库函数fopen/fwrite/fclose,为啥用这3个函数呢,因为一般的程序都应该引入了这个库,这样
  就只需要修改一下偏移就好了(实际上ADS编译出来的指令里这几个调用也是预留给后面重定位的),在ida pro里找一下
  这三个函数的导入代码(就是三个过程)的地址,然后计算一下三个调用处到目标的偏移(这里有一点没搞明白,必须用
  “目标地址 - 调用指令地址 - 8”才对,查ARM手册也没见有这需求,谁要是清楚麻烦告诉我),修改指令的后3个字节
  为偏移即可。
  
  修改后的mono_image_open_from_data_with_name
  .text:00190A94
  .text:00190A94 ; =============== S U B R O U T I N E =======================================
  .text:00190A94
  .text:00190A94 ; Attributes: bp-based frame
  .text:00190A94
  .text:00190A94                 EXPORT mono_image_open_from_data_with_name
  .text:00190A94 mono_image_open_from_data_with_name     ; CODE XREF: sub_133E34+170p
  .text:00190A94                                         ; mono_image_open_from_data_full+3Cp
  .text:00190A94
  .text:00190A94 var_24          = -0x24
  .text:00190A94 var_20          = -0x20
  .text:00190A94 n               = -0x1C
  .text:00190A94 src             = -0x18
  .text:00190A94 var_10          = -0x10
  .text:00190A94 var_C           = -0xC
  .text:00190A94 dest            = -8
  .text:00190A94 arg_0           =  4
  .text:00190A94 arg_4           =  8
  .text:00190A94
  .text:00190A94                 STMFD           SP!, {R11,LR}
  .text:00190A98                 ADD             R11, SP, #4
  .text:00190A9C                 SUB             SP, SP, #0x20
  .text:00190AA0                 STR             R0, [R11,#src]
  .text:00190AA4                 STR             R1, [R11,#n]
  .text:00190AA8                 STR             R2, [R11,#var_20]
  .text:00190AAC                 STR             R3, [R11,#var_24]
  .text:00190AB0                 LDR             R2, [SP,#0x24+src]
  .text:00190AB4                 BL              mono_load_remote_field
  .text:00190AB8                 CMP             R3, #0
  .text:00190ABC                 CMP             R3, #0
  .text:00190AC0                 CMP             R3, #0
  .text:00190AC4                 CMP             R3, #0
  .text:00190AC8                 CMP             R3, #0
  .text:00190ACC                 CMP             R3, #0
  .text:00190AD0                 CMP             R3, #0
  .text:00190AD4                 CMP             R3, #0
  .text:00190AD8                 CMP             R3, #0
  .text:00190ADC                 CMP             R3, #0
  .text:00190AE0                 CMP             R3, #0
  .text:00190AE4                 CMP             R3, #0
  ;后面是原来的代码了
  .text:00190AE8                 LDR             R3, [R11,#src]
  ...
  
  修改后的mono_load_remote_field
  .text:001F9A4C
  .text:001F9A4C ; =============== S U B R O U T I N E =======================================
  .text:001F9A4C
  .text:001F9A4C
  .text:001F9A4C                 EXPORT mono_load_remote_field
  .text:001F9A4C mono_load_remote_field                  ; CODE XREF: mono_image_open_from_data_with_name+20p
  .text:001F9A4C                 STMFD           SP!, {R4-R6,LR}
  .text:001F9A50                 MOV             R6, R0
  .text:001F9A54                 LDR             R0, =0x5B8D80
  .text:001F9A58                 MOV             R5, R1
  .text:001F9A5C                 CMP             R1, R0
  .text:001F9A60                 LDMLSFD         SP!, {R4-R6,PC}
  .text:001F9A64                 ADR             R0, aDataLocalTmpTe ; "/data/local/tmp/test.dll"
  .text:001F9A68                 ADR             R1, dword_1F9A9C ; modes
  .text:001F9A6C                 BL              fopen
  .text:001F9A70                 MOVS            R4, R0
  .text:001F9A74                 LDMEQFD         SP!, {R4-R6,PC}
  .text:001F9A78                 MOV             R3, R4  ; s
  .text:001F9A7C                 MOV             R2, R5  ; n
  .text:001F9A80                 MOV             R1, #1  ; size
  .text:001F9A84                 MOV             R0, R6  ; ptr
  .text:001F9A88                 BL              fwrite
  .text:001F9A8C                 MOV             R0, R4  ; stream
  .text:001F9A90                 LDMFD           SP!, {R4-R6,LR}
  .text:001F9A94                 B               fclose
  .text:001F9A94 ; End of function mono_load_remote_field
  .text:001F9A94 ; ---------------------------------------------------------------------------
  .text:001F9A98 dword_1F9A98    DCD 0x5B8D80            ; DATA XREF: mono_load_remote_field+8r
  .text:001F9A9C dword_1F9A9C    DCD 0x6277              ; DATA XREF: mono_load_remote_field+1Co
  .text:001F9AA0 aDataLocalTmpTe DCB "/data/local/tmp/test.dll",0
  .text:001F9AA0                                         ; DATA XREF: mono_load_remote_field+18o
  .text:001F9AB9                 DCB 0, 0, 0
  .text:001F9ABC ; ---------------------------------------------------------------------------
  ;后面是原来的代码了
  .text:001F9ABC                 MOV             R2, R3
  .text:001F9AC0                 LDR             R3, =(aObject_c - 0x1F9ACC)
  .text:001F9AC4                 ADD             R3, PC, R3 ; "object.c"
  .text:001F9AC8                 BL              sub_29D7F8
  ...
  
  现在重新打包、签名后应该已经可以得到解密的dll了。只是还不能进入游戏。。
  
  3、去除对梆梆so的依赖
  这个就是按网上说的做就可以了(对manifest的处理有点忘了具体的修改点了。。)
  1)、修改解出的包里AndroidManifest.xml,把里面对梆梆的Activity去掉。
  2)、修改解压出的smali文件,主要在smali\com\secneo\apkwrapper目录下,把加载DexHelper的代码注掉。
  
  现在再重新打包、签名后在虚拟机里安装apk后运行,可以进游戏了(因为之前已经把dll解密了,这次要把libmono.so换
  成原版unity3d的)。
  
  4、编写自己的调试模块
  呵呵,终于可以从ARM的汇编回到人类世界了。
  现在游戏的dll已经解密,并且重新打包后也可以运行了。所以我们可以试着在逻辑上打补丁了,好吧,其实我是为了研究
  一下它有没有什么新技巧。
  编程序的事情就不说了,大概就是基于DebugConsole.cs (http://wiki.unity3d.com/index.php?title=DebugConsole),
  然后添加一些我们需要的命令,比如动态加载一个Assembly,再比如利用reflection API调用函数,嗯,这在android上确
  实是可行的,对dotnet来说,这基本不是事(所以我就不写细节,要注意的就是读dll一定要先用文件API读出byte[],然
  后再Assembly.Load。另外一个是dll所在的目录必须是有权限读的,比如sdcard上的目录就比较好)
  
  5、合并调试模块到目标游戏
  我们基于DebugConsole.cs修改编译了一个自己的dll,下一步就是把这个dll合并到目标Assembly-CSharp.dll里并且修改
  Assembly-CSharp.dll里的运行时会走到的代码来调用我们的代码了。
  合并dll有微软开发好的非常牛的工具(微软研究院经常会做一些很奇怪的事情,比如Detours项目,再比如这个IlMerge工具)
  这个工具除了会合并dotnet dll文件外,其实它还是一个dotnet PE文件处理的源码库(其实找不到源码,不过用IlSpy或
  Reflector看基本上也没障碍),另外,它还可以用来重新整理我们修改过的dotnet可执行文件。
  
  6、修改目标游戏代码调用调试模块
  这一步我采取了自制工具的方式,要手动处理的话,用CFF Explore也是可以的,这里就不详说了。
  简单说一下我们实际做的事:
  1)、游戏的启动类Game,在Update里调用了TestInput
  private void Update()
  {
      try
      {
          ...
          this.TestInput();
      }
      catch (Exception exception)
      {
          Log.Exception(exception);
      }
  }
  
  private void TestInput()
  {
  }
  
  TestInput是一个空函数,这个函数不会访问任何Game实例的变量,和静态函数效果一样,我们将它的方法体替换成我们提
  供的一个函数(就是前面用IlMerge合并到Assembly-CSharp.dll里的代码),这样我们静态注入的代码就有了执行的机会:
  private void TestInput()
  {
      DebugConsoleHelper.Init(base.gameObject);
      DebugConsoleHelper.Tick();
  }
  
  修改的原理是将Game类的TestInput方法元数据里的RVA改为GamePatch即我们合并进的类的TestInput方法的RVA。
  修改后,Game.TestInput与GamePatch.TestInput方法其实是共享了同一块指令,这在dotnet PE文件里是没问题的。
  这一步用CFF Explore手动修改也比较容易,因为只是替换一下元数据。
  我因为学习的需要要多次修改dll,所以用了自己的工具执行一段脚本来自动处理(见后)。
  
  2)、游戏的PlayerController类的Update函数里,我们需要修改一下来实现移动(主要用于没有连接服务器的情况下浏览场景)
  private void Update()
  {
      if (activeController == this)
      {
          this.ProcessJoystick();
          this.m_moveElapseTime += Time.deltaTime;
          if (this.m_moveElapseTime > m_moveInterval)
          {
              DebugConsoleHelper.Move(this.m_player, this.m_moveElapseTime);
              this.m_moveElapseTime = 0f;
          }
      }
      this.ProcessSkillCD();
  }
  
  这里需要修改字节码来实现我们的代码,本质上与传统可执行文件的修改是一样,先NOP掉一段代码,然后写入我们的代码
  .method private hidebysig instance void Update() cil managed
  {
      .maxstack 8
      L_0000: call class PlayerController PlayerController::get_activeController()
      L_0005: ldarg.0 
      L_0006: call bool [UnityEngine]UnityEngine.Object::op_Equality(class [UnityEngine]UnityEngine.Object, class [UnityEngine]UnityEngine.Object)
      L_000b: brfalse L_0076
      L_0010: ldarg.0 
      L_0011: call instance void PlayerController::ProcessJoystick()
      L_0016: ldarg.0 
      L_0017: dup 
      L_0018: ldfld float32 PlayerController::m_moveElapseTime
      L_001d: call float32 [UnityEngine]UnityEngine.Time::get_deltaTime()
      L_0022: add 
      L_0023: stfld float32 PlayerController::m_moveElapseTime
      L_0028: ldarg.0 
      L_0029: ldfld float32 PlayerController::m_moveElapseTime
      L_002e: ldsfld float32 PlayerController::m_moveInterval
      L_0033: ble.un L_0076
  ;下面是我们修改的代码
      L_0038: ldarg.0 
      L_0039: ldfld class Player PlayerController::m_player
      L_003e: ldarg.0 
      L_003f: ldfld float32 PlayerController::m_moveElapseTime
      L_0044: call void DebugConsoleHelper::Move(class Player, float32)
      L_0049: nop 
      L_004a: nop 
      L_004b: nop 
      L_004c: nop 
      L_004d: nop 
      L_004e: nop 
      L_004f: nop 
      L_0050: nop 
      L_0051: nop 
      L_0052: nop 
      L_0053: nop 
      L_0054: nop 
      L_0055: nop 
      L_0056: nop 
      L_0057: nop 
      L_0058: nop 
      L_0059: nop 
      L_005a: nop 
      L_005b: nop 
      L_005c: nop 
      L_005d: nop 
      L_005e: nop 
      L_005f: nop 
      L_0060: nop 
      L_0061: nop 
      L_0062: nop 
      L_0063: nop 
      L_0064: nop 
      L_0065: nop 
      L_0066: nop 
      L_0067: nop 
      L_0068: nop 
      L_0069: nop 
      L_006a: nop 
  ;修改结束
      L_006b: ldarg.0 
      L_006c: ldc.r4 0
      L_0071: stfld float32 PlayerController::m_moveElapseTime
      L_0076: ldarg.0 
      L_0077: call instance void PlayerController::ProcessSkillCD()
      L_007c: ret 
  }
  
  这一步我也是用我的自制工具自动处理的,用CFF Explore与Hex workshop手动修改也可以,但如果需要多次修改的话就
  有点烦了。
  
  7、自制工具
  前面说到的方法体替换与方法代码修改我自己做了一个小工具
  (是在10年前的DeObfuscator上修改而成:http://bbs.pediy.com/showthread.php?threadid=34127)
  
  对于方法体替换,由于多个类会共享方法体,所以这样的方法不能访问类的实例变量,也就是一般要用在静态方法上。
  方法体替换在工具里是直接支持的,添加好目标文件后,输入被替换类与替换类,点“方法实现替换”即可。
  
  这个小工具主要是通过脚本来自动处理上面的修改,脚本是基于我的另一个开源项目DSL实现的。
  本文涉及的修改使用的脚本如下:
  proc(main)
  {
          $files = getfilelist();
          begin("开始脚本处理");
          looplist($files){
                  $file=$$;
                  beginfile($file,"开始对"+$file+"进行修改。。。");
                  beginreplace($file);
                  replace($file,"Game","GamePatch");
                  endreplace($file);
                  beginmodify($file);
                  writeloadarg($file,"PlayerController","Update",0x38,0);
                  writeloadfield($file,"PlayerController","Update",0x39,"PlayerController","m_player");
                  writeloadarg($file,"PlayerController","Update",0x3e,0);
                  writeloadfield($file,"PlayerController","Update",0x3f,"PlayerController","m_moveElapseTime");
                  writecall($file,"PlayerController","Update",0x44,"DebugConsoleHelper","Move");
                  writenops($file,"PlayerController","Update",0x49,0x22);
                  endmodify($file);
                  endfile($file);
          };
          end("结束脚本处理");
  };
  
  这个小工具我已经开源了,欢迎使用:https://github.com/dreamanlan/DotnetPatch
  考虑到伟大的墙,我在看雪也放一个目前的版本。  
  
--------------------------------------------------------------------------------
【经验总结】
  所用工具列表:
  1、ApkStudio
  2、Android逆向助手
  3、Reflector 8
  4、ida pro 6.6
  5、hex workshop
  6、cff explore
  7、IlMerge
  8、ADS 2.1
  9、自制工具DotnetPatch
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                  2016年09月01日 20:23:13 上传的附件:
posted @ 2017-10-31 20:36 00000000O 阅读(...) 评论(...) 编辑 收藏
2019-02-26 18:32:22 weixin_44058342 阅读数 628
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12992 人正在学习 去看看 宋晓波

作者:dawu@知道创宇404实验室
时间:2019/02/25

0x00 前言

这是一篇游戏引发的简单技术文。

起因是个人很喜欢玩 google play 上的一些数字类型(角色攻击是线性增长,怪物指数变强,到后期越打不过,通过重生增强属性变强)的小游戏。但是这种游戏仍旧存在一定缺陷,前期资源不多,玩的太慢、玩的时间长了,就感觉没意思,就不想玩了,所以在玩到游戏中期的时候,往往都会去网上搜索XXX破解版/内购版,快速进入后期然后放弃这款游戏。

这样的做法其实是很不安全的,因为无法判断XXX破解版/内购版在破解/内购之后还做了什么。所以我最后的解决办法是,逆向这些apk,修改游戏逻辑。让我在玩的时候,可以快速度过缓慢的前期。

逆向了几个玩过的游戏,发现这类游戏使用Unity3D开发的居多。因此本文将介绍简单Unity3D类安卓游戏的逆向修改思路。

0x01 准备工具

逆向最简单的Unity3D类安卓游戏建议使用安装好 JAVA 环境的Windows系统(涉及到dll文件的修改,所以Windows平台更加适合)。

1.1 安卓 APK 逆向三件套

一般 APK 逆向,常使用到 apktool、dex2jar、jd-gui。在逆向 Unity3D 安卓游戏时,仅仅只需要使用到 apktool

Apktool: 用于解压/重新打包安卓APK。
dex2jar: 将解压出来的dex文件变成jar,方便使用jd-gui查看
jd-gui: 查看dex文件逻辑

1.2 dll文件逆向三件套

因为一般的 Unity3D 安卓游戏的主逻辑都在 asserts/bin/data/Managed/Assembly-CSarp.dll 中,所以我们还需要 dll文件逆向/重新打包 的工具。

ILSpy: 用于查看dll程序逻辑

ILDASM: 用于反编译dll文件,生成il文件(存放了dll反编译后的指令)和res文件(反编译后的资源文件),可以安装Windows SDK或者从网上下载。

ilasm: .net4.0自带了,位置在 C:\Windows\Microsofr.NET\Framework\v4.0.30319\ilasm.exe

1.3 生成重新打包的自签名证书

修改完 apk 之后,需要对 apk 进行签名。该命令用于生成签名的证书。
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 0validity 10000

记住设置的密码,最后自签名应用的时候需要输入密码

0x02 开发一个简单的 Unity3D 游戏

用Unity3D开发了一个简单小游戏作为本文的样例,逻辑十分简单:

  1. 英雄每过一关战斗力都会增加100.
  2. 怪物的战斗力为 Math.pow(2,当前关数)
  3. 当英雄战斗力小于怪物的战斗力时,英雄无法闯关。英雄可以考虑修炼或者重生提高战斗力。
  4. 英雄每次修炼战斗力都会增加1000.
  5. 英雄选择重生后,关卡数清零,需要重新闯关,但英雄初始战斗力会增加 2000 * 重生前闯关数。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
    具体代码可以参考 Github

0x03 游戏逆向步骤

  1. 使用 apktool 解压游戏安装包
    java -jar apktool.jar d game.apk
  2. 提取出 game/assets/bin/data/Managed/Assembly-CSarp.dll ,使用 ILSpy 打开即可看到 dll 里面的逻辑。
    注: Unity3D开发的安卓游戏,其核心代码都在这个 dll 文件中,所以逆向/修改这个 dll 文件就可以了。这也是 Unity3D 和 其它安卓逆向不同的地方。
    在这里插入图片描述
    在没有混淆的情况下,反编译出的函数内容和原内容十分相似:
    在这里插入图片描述在这里插入图片描述
  3. 找到关键函数、关键逻辑后,就可以尝试反编译 dll 文件并修改。使用 ILDASM 将 dll 文件反编译成 il 文件。使用 ILDASM 打开 dll 文件后, File -> dump 就可以导出反编译结果了。
    在这里插入图片描述
  4. 根据步骤2,就很容易理解逻辑了,然后根据速查表,就可以知道在步骤3导出的il文件中修改哪里了。例如步骤2中 Click1 就是游戏中 点击闯关 按钮绑定的逻辑。闯关的关键判断就在: info.hero_power + info.temp_power + info.add_power >= info.monster_power。所以打开步骤3中生成的 .il 文件,结合 .NET IL 指令速查表修改这部分对应的关键逻辑即可。
    在这里插入图片描述
    修改为 info.hero_power + info.temp_power + info.add_power != info.monster_power 就可以通过此处的逻辑判断。
    在这里插入图片描述
  5. 修改为 info.hero_power + info.temp_power + info.add_power != info.monster_power 就可以通过此处的逻辑判断。 修改为 info.hero_power + info.temp_power + info.add_power != info.monster_power 就可以通过此处的逻辑判断。
    在这里插入图片描述
  6. 修改关键逻辑后,通过重新编译 dll 文件、apk 文件、签名修改后的 apk 就可以在手机上安装运行了。

重新编译dll文件命令如下:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe game.il /output=Assembly-CSarp.dll /dll

将重新编译的dll放回 game/assets/bin/data/Managed/ 目录下,使用apktool重新打包apk:
java -jar apktool.jar b game
cp game/dist/game.apk ./

自签名应用:
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore game.apk alias_name

  1. 修改成功,开局修炼一次后,就可以无限闯关。顺利到达第30关。
    在这里插入图片描述

0x04 杂谈和总结

1.Unity3D有一个较为明显的特征: 开局会显示游戏LOGO。这个可以作为判断一个游戏是不是Unity3D开发的小参考。

2.文中的demo到了31关,就会发生整型溢出,怪物战斗力变为负数。原因是怪物战斗力的值为int型。在以前玩过的某个后期极度不平衡的游戏中,我的确遇到过整型溢出的问题。造成花钱升级还能增余额的情况。

3.在修改游戏之前把游戏语言调整为英文有助于在逆向的时候理解各个函数的意义(对于没有混淆的应用)。

4.游戏修改之后,很容易丧失原本的乐趣,变成纯粹的数字游戏。谨慎修改!

0x05 参考链接

1.Apktool
2.ILSpy
3..NET IL 指令速查表
4.Unity3d类安卓游戏逆向分析初探

没有更多推荐了,返回首页