概述

使用HOOK方案主要是在分析的时候会经常用到,虽然二次打包重新修改代码也可以做到,但是一方面效率低,另一方面如果APP有校验的逻辑就需要进一步绕过,总体还是比较费时费力。所以,通过动态HOOK的方式可以不用直接修改APP文件,也比较方便。下面就分别介绍下比较成熟的几个HOOK框架及其应用:XPOSED,frida,substrate。

XPOSED

本节介绍的是XPOSED框架的使用,XPOSED的安装器替换安卓系统的app_process文件,从而实现对系统的接管,通过回调模块的方式来达到不用修改APK就能改变其表现行为的目的。该框架比较成熟,对应的模块也非常多,常用的还有模拟地理位置,伪装手机设备信息等,脑洞是非常之大,基本上能想到的都能做到。由于篇幅限制这里介绍一个简短而实用的案例,其他模块可以参考XPOSED官网的模块列表。
场景:大妈的烦恼!
周末自驾出去玩,大妈在一旁抱怨:他们单位里要搞个活动需要员工来组织,大家都不想参加,不然搞得都没有时间跳广场舞了。最后他们想出了一个办法:投骰子,谁输了谁上。他们群里大家都在投的火热,就差大妈了,大妈激动的不敢投,只好在这抱怨。。。我让大妈先憋着急,我有大宝剑可以借她一用。
某信投骰子的原理:
package com.tencent.mm.sdk.platformtools
public static int tx(int paramInt){
return new Random(System.currentTimeMillis()).nextInt(paramInt + 0 + 1) + 0;
}
这个函数比较有意思,如果是猜拳游戏就随机化0~2之间的数字,分别表示剪刀、石头、布;如果是投骰子游戏则随机化一个0~5之间的数字,也就是骰子的点数。因此只要HOOK这段代码,返回想要的点数即可,这里使用XPOSED框架,主要代码:
findAndHookMethod("com.tencent.mm.sdk.platformtools.be", lpparam.classLoader, "tx", int.class, new XC_MethodHook() {@Overrideprotected void afterHookedMethod(MethodHookParam param) {int gameType = ((Integer) param.args[0]).intValue();switch (gameType) {case 5:
//投骰子游戏,强制返回最大点数,基数为0,所以返回5param.setResult(5);default:}}
});
给大妈选择了最大的点数,大妈顺利逃过一劫,从此又可以愉快地跳广场舞了。
官网:http://repo.xposed.info/
模块:http://repo.xposed.info/module-overview
某信的APK安装包并没有进行加壳保护,虽然做过代码混淆处理,但是还是能够反编译,而且即使混效果上面的代码逻辑也能看得懂。

frida

本节介绍的是frida框架,frida这个HOOK框架主要使用Python和javascript脚本编写,所以兼容性和移植性都很好,可以适用于多种平台,最重要的是不用每次都重启手机。官网:http://www.frida.re/,里面介绍了安装步骤,参考Android下的例子可以很快上手:http://www.frida.re/docs/examples/android/。
场景:厉害了我的蛇!
最近比较魔性的一款手游“贪吃蛇大作战”,就连很少玩游戏的朋友也开始玩起来了。自己也体验玩了几局,发现有些“玩家”操作都异常的快速和灵敏,总是被他们撞死。心想这些玩家不会是用了辅助工具了吧,当看到那些原地打转速度非常快而且轨迹非常之圆,心中就更加确定真人肯定无法达到这种操作(事后分析才知道是机器人玩家)。于是打算分析下,刚好最近同事推荐了frida这个HOOK框架,正好可以拿来练练手。
游戏初始化函数initEntity:
package com.wepie.snake.module.game;private void initEntity()
{GameConfig.factor = 1.0F;SnakeSurfaceView.access$102(SnakeSurfaceView.this, SnakeFactory.creatSnakeSelf(LoginHelper.getLoginUser().nickname, 5, null));SnakeSurfaceView.access$1102(SnakeSurfaceView.this, new AiManager(SnakeSurfaceView.this.snakeInfo));SnakeSurfaceView.this.allSnakes.clear();SnakeSurfaceView.this.allSnakes.add(SnakeSurfaceView.this.snakeInfo);SnakeSurfaceView.this.allSnakes.addAll(SnakeSurfaceView.this.aiManager.snakeAis);initUtils();
}
游戏初始化函数会调用函数creatSnakeSelf来创建玩家的蛇:
public static SnakeInfo creatSnakeSelf(String paramString, int paramInt, MultiNode paramMultiNode)
{int i = SkinConfig.getInUserSkinId();SkinInfo localSkinInfo = SkinManager.getInstance().getSkin(i);paramString = new SnakeInfo(paramString, localSkinInfo, paramMultiNode);Log.i("999", "----->creatSnakeSelf skin_id=" + localSkinInfo.skin_id);paramString.bodyInfo.initBodyPoints(getBodyPoints(paramString, 0.0F, 0.0F, paramInt));return paramString;
}
该函数创建蛇的颜色和身体,颜色反正随机的就好无所谓,主要是蛇的身体,继续向下分析可以分析出paramInt就是身体的初始长度,再回头看下initEntity对creatSnakeSelf的调用可以看出:游戏初始时蛇的长度是5。现在就好办了,我们HOOK一下creatSnakeSelf函数的调用,让初始的长度非常大就好了。
creatSnakeSelf函数的第三个参数是MultiNode类型,先记下完整的类名:
com.wepie.snake.module.game.main.MultiNode
HOOK后运行游戏,蛇的身体在开始游戏时就非常长啦。
后面再实现无敌模式,通过游戏结束函数(onGameOver)逆向分析,寻找一个比较巧妙又比较方便HOOK的函数,最终找到了doSnakeDie这个函数:
private void doSnakeDie(final SnakeInfo snakeInfo, final SnakeInfo snakeInfo2) {if (snakeInfo.snakeStatus.superFrameCount <= 0 && (snakeInfo2 == null || snakeInfo2.snakeStatus.superFrameCount <= 0)) {this.extraFactory.addWrecks(snakeInfo);if (!snakeInfo.isSnakeAi) {((SnakeSurfaceView.GameStatusCallback)this.gameStatusCallback).onGameOver();}else if (snakeInfo2 != null) {snakeInfo2.addKillNum();if (!snakeInfo2.isSnakeAi) {((SnakeSurfaceView.GameStatusCallback)this.gameStatusCallback).onKillAi();}}snakeInfo.doDie();return;}
}
这个函数的意义是:第一个参数蛇去碰第二个参数蛇,如果第一个参数是我们自己的蛇,那么就意味着撞死游戏结束;如果第一个参数是机器人蛇,那么死了就死了,我们还可以继续。
因此HOOK的方法就是:拦截该函数的调用,判断第一个参数是否是我们自己的蛇,如果是则让函数直接返回。
所以,还需要在creatSnakeSelf调用时保存下创建的自己的蛇对象。当然通过函数的逻辑可以看出机器人蛇与玩家的蛇不同之处就是变量isSnakeAi,如果不想保存creatSnakeSelf创建的蛇对象,直接通过isSnakeAi来判断也是可以的,后面一并给出相应地做法。
记下所要用到的类与函数:
类: com.wepie.snake.module.game.snake.CollisionUtil
函数: doSnakeDie
蛇类型: com.wepie.snake.module.game.snake.SnakeInfo
参考frida的Android示例代码,编写脚本:
#coding:utf-8import frida, sysdef on_message(message, data):if message['type'] == 'send':print("[*] {0}".format(message['payload']))else:print(message)jscode = """
Java.perform(function () {var curSnake = null;// Function to hook is defined herevar SnakeFactory = Java.use('com.wepie.snake.module.game.snake.SnakeFactory');// Whenever creatSnakeSelf is calledSnakeFactory.creatSnakeSelf.implementation = function (name, len, multiNode) {// Show a message to know that the function got calledsend('creatSnakeSelf');len = 50;// Call the original creatSnakeSelfcurSnake = this.creatSnakeSelf(name, len, multiNode);// Log to the console that it's doneconsole.log('Done: init snake len: ' + JSON.stringify(len) + " name: " + name+ " my snake: " + curSnake.toString() + ' isSnakeAi: ' + curSnake.isSnakeAi.value);return curSnake;};var CollisionUtil = Java.use('com.wepie.snake.module.game.snake.CollisionUtil');CollisionUtil.doSnakeDie.implementation = function (snake1, snake2) {// Show a message to know that the function got calledif (snake1.equals(curSnake) || snake1.isSnakeAi.value == false){console.log('snake1 isSnakeAi: ' + snake1.isSnakeAi.value + " snake1: " + snake1.toString());// Log to the console that it's doneconsole.log('Done: my snake will not die!');return;}else{// Call the original doSnakeDiereturn this.doSnakeDie(snake1, snake2);}};});
"""process = frida.get_usb_device().attach('com.wepie.snake')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
主要功能逻辑是上面的JS代码,外面的Python脚本基本都这个模式,在蛇初始化时候creatSnakeSelf保存下自己的蛇对象,修改CollisionUtil的碰撞逻辑,当参数1的蛇是自己的蛇对象时候就返回掉(或者参数1的蛇isSnakeAi值为false直接返回也可以),就能保证不死了。
效果图如下,随便撞什么蛇或者墙都不死了:
通过上面的分析也可以看出,该游戏没有加壳保护也没有进行代码混淆,所以很容易就被秒破了。

substrate

上面介绍的两个HOOK场景皆是在java代码层做的HOOK,而且通常都会有一些限制。例如,XPOSED框架的限制是必须要求连带安装XPOSED installer,对应的模块功能才能起效果,而frida则需要配合PC终端操作。而这里介绍的substrate方式,主要是在JNI层做HOOK,而且实现出的应用可以单独使用。
场景:叉叉助手的加速器!
之前分析游戏保护的时候接触过叉叉助手(先了解对方怎么做,才好想出对应的保护方法),例如下面的某三国游戏,叉叉助手提供了加速器功能。可以在游戏界面上注入并加载一个插件,插件可以自由设置辅助配置,很是强大。
后来就从这个叉叉助手模拟实现了一个可以任意注入插件的方法:
通过逆向分析叉叉助手来看它的实现原理:使用inject工具把so注入到zygote中,并利用libsubstrate.so的功能来实现对关键函数的HOOK。首先对使用MSJavaHookMethod对handleBindApplication进行HOOK来拦截APP的启动,在拦截函数中判断启动的APP是否是要拦截的APP,如果不是则放行,如果是则通过MSJavaHookClassLoad来HOOK目标APP中Activity的类加载,并在拦截函数中再HOOK Activity的onCreate函数,在onCreate的拦截函数中动态加载插件APK,并调用插件的init函数。部分代码如下:
static void OnCallback_ClassOnCreate(JNIEnv *jni, jobject activity, jobject bundle)
{string strName;strName = getObjectClassName(jni, activity);LOGD("[%s] begin: %s::OnCreate", __FUNCTION__, strName.c_str());(*old_onCreate)(jni, activity, bundle);LOGD("[%s] dynamic load plug apk: %s", __FUNCTION__, g_cfg.strPlugApkPath.c_str());……//调用插件的静态函数init,原型:public static void init(Activity activity, String soPath)jmethodID plugUIinit = jni->GetStaticMethodID(plugClass, "init", "(Landroid/app/Activity;Ljava/lang/String;)V");if ( plugUIinit==NULL ) {LOGE("[%s] not found \"init\" in plug activity: public static void init(Activity activity, String soPath)", __FUNCTION__);}else{LOGD("[%s] invoke %s::init", __FUNCTION__, g_cfg.strPlugActivity.c_str());jstring soPath = jni->NewStringUTF(g_cfg.strPlugSoPath.c_str());jni->CallStaticVoidMethod(plugClass, plugUIinit, activity, soPath);}LOGD("[%s] end", __FUNCTION__);
}

但是C++写起来开发效率比较低,出错了也不容易排查。上面的JNI代码用java代码写起来就非常轻松:

String dexOutputDir = "/data/data/" + targetPackageName + "/cache";
DexClassLoader dexClassLoader = new DexClassLoader(plugApkPath, dexOutputDir, null, ClassLoader.getSystemClassLoader());
java.lang.Class<?> plugClass = dexClassLoader.loadClass(plugInitClass);
Method mInit = plugClass.getDeclaredMethod(Constant.METHOD_NAME_plug_init, Activity.class, String.class);
mInit.invoke(null, param.thisObject, plugSoPath);

但是并不是意味着就不用写JNI代码了,有时候是必须在SO底层做HOOK的。这个应用场景主要在破解逆向分析、脱壳上比较有用,前期的JNI代码写的很是痛苦,编译一次手机需要重启,而且一旦出错并不是那么容易排查,但是框架搭建好后,以后再需要只要HOOK对应的NDK层函数即可。

官网:http://www.cydiasubstrate.com/

总结

1、XPOSED
优点:
1)、代码编写方便,开发速度较快。
2)、有许多现成的模块可以用,而且很多模块也是开源的,方便学习研究。
缺点:
1)、每次编写代码需要重启手机生效。
2)、不支持native的HOOK。
3)、独立性较差,需要依赖XPOSED installer,不易单独分发。
2、substrate
优点:
1)、比较擅长在native层的HOOK。
2)、独立性较好,实现的功能可以封装在单独APP里分发给用户使用,因此也是较大型外挂辅助工具的首选。
缺点:
1)、每次编写代码需要重启手机生效。
2)、开发效率较低,成本较高。
3、frida
优点:
1)、无须重启手机和目标APP,这个可以节省很多时间,如果APP测试的点需要很复杂地搭建好环境,一旦重新启动就意味着很麻烦地再重新搭建环境,例如账号登录,进入特定关卡等。
2)、JS脚本编写,灵活方便,再也不用担心多参数个数和类型问题了。
3)、可以直接使用或修改对象的成员变量,非常方便。
4)、配合PC终端命令行使用,脚本编写出错也不会导致APP崩溃,只需修改后重新来过即可,有时会有问题,这个时候需要重启下APP或手机即可。
缺点:
1)、JS脚本套在python脚本里面,编写JS脚本时候不是很方便,容易出错,好在即使出错也不会导致APP崩溃掉,修改后重新来过即可。
2)、该工具配合PC终端使用,更适合专业者,不利于分发给用户使用。
综上的案例也可以看出游戏保护刻不容缓,叉叉助手能做到这么成熟的地步主要是因为现在的手游保护力度较弱。
推荐网易云加密对安卓APP进行加壳加固保护:http://dun.163.com/product/app-protect

Android主流HOOK框架介绍与应用--游戏破解游戏外挂的必杀技相关推荐

  1. 2018年android常用的框架介绍

    转载地址:http://blog.csdn.net/RuingMan/article/details/73546718 http://www.cnblogs.com/jincheng-yangchao ...

  2. 如何写一个Android inline hook框架

    Android_Inline_Hook https://github.com/GToad/Android_Inline_Hook_ARM64 有32和64的实现,但是是分离的,要用的话还要自己把两份代 ...

  3. 主流webgis框架介绍与对比

    概述 想写本文,主要是源于前两天有个老师找到我说让我录一个大概半个小时的视频,跟大家分享一下各webgis框架之间的区别以及在应用的过程中应该如何选择.其实之前也有学员问过类似的问题,当时只是针对他们 ...

  4. 【Unity游戏破解】外挂原理分析

    文章目录 认识unity 打包目录结构 游戏逆向流程 Unity游戏攻击面 可被攻击原因 mono的打包 建议方案 锁血 飞天 无限金币 攻击力翻倍 以上统称内存挂 透视 自瞄 压枪 瞬移 内购破解 ...

  5. Android GUI系统框架介绍

    这个又是内部技术分享时准备的PPT,Android GUI框架是一个非常庞大的系统,也是Android最重要的系统之一,其决定了一个Android界面究竟如何显示出来,显示效果/效率怎样,也一直是An ...

  6. Android显示系统设计框架介绍

    1. Linux内核提供了统一的framebuffer显示驱动,设备节点/dev/graphics/fb*或者/dev/fb*,以fb0表示第一个显示屏,当前实现中只用到了一个显示屏. 2. Andr ...

  7. android主流技术框架,android开发现在流行什么IDE和开发框架?

    慕仙森 idea, AS (android studio), adt, 其中 AS 是google 非常推荐的.看官网就知道了. 框架的话: xutils , andbase , volley等等,还 ...

  8. Android系统Audio框架介绍(一)

    原址 音频基础知识 声音有哪些重要属性呢? 响度(Loudness) 响度就是人类可以感知到的各种声音的大小,也就是音量.响度与声波的振幅有直接关系. 音调(Pitch) 音调与声音的频率有关系,当声 ...

  9. Android系统Audio框架介绍

    音频基础知识 声音有哪些重要属性呢? 响度(Loudness) 响度就是人类可以感知到的各种声音的大小,也就是音量.响度与声波的振幅有直接关系. 音调(Pitch) 音调与声音的频率有关系,当声音的频 ...

最新文章

  1. Java并发编程的艺术(二)——重排序
  2. JavaScript学习记录总结(四)——js函数的特殊性
  3. Help:立体图绘制以及根据X,Y,Z三坐标值,在图上描点
  4. Hadoop下如何执行脚本
  5. html 替换反斜杠,在URL直接替换反斜杠反斜杠
  6. C++ std::vector 自定义排序
  7. java标点符号用什么意思_标点符号的使用我说他说XX说后面在什么情况下加逗号、冒号、冒号双引号、双引号或者逗号双引号等的区分问题请详细说明谢谢...
  8. 编译错误:invalid types ‘int[int]‘ for array subscrip-markdown编辑器
  9. Photoshop技术学习有感
  10. js页面跳转 URL含中文造成乱码
  11. 基于滴滴云搭建安全稳定的 Memcached 服务器
  12. 新网站如何提升排名?网站排名提升的优化技巧分享
  13. iOS 组件中设置文件支持MRC
  14. codeup之沙漏图形
  15. Windows 10下视频播放器泛黄,颜色太暖、太亮
  16. 拆t420s屏轴,T430S T420S 拆机详解LED改高分IPS 1920*1080,拆机注意事项,机友福利
  17. 圆形标定板_一种圆阵列标定板特征点提取方法与流程
  18. #即时通讯#实现消息已读回执功能的思路与实现
  19. Kepware的完美替代者
  20. yum 安装mysql 5.7

热门文章

  1. getopt()函数
  2. java 读取zip文件_JAVA实现zip文件内容读取及解压
  3. [0x7FFE1E17E050] ANOMALY: meaningless REX prefix used cmd窗口activate报错
  4. MATLAB App Designer入门实战(一)
  5. 【实时语音转文本】PC端实时语音转文本(麦克风外音系统内部音源)
  6. 铁通计算机网络,【计算机网络技术】常见宽带错误代码及处理办法(使用移动宽带【铁通】、部分电信宽带故障、联通宽带故...
  7. 建网站如何选择空间?
  8. windows防火墙是干什么的_请教个人防火墙是做什么用的,
  9. 使用Matlab工具箱(procamcalib)进行投影仪标定---超详细过程
  10. iview表格中,鼠标滑过单元格展示提示信息