开始学习Android逆向
<!--more-->
APK文件结构
随便使用 7-zip 打开一个 apk 文件
列出一些关键的文件和目录的用途
| |
|---|
| |
| armeabi-v7a基本通用所有android设备,arm64-v8a只适用于64位的android设备,x86常见用于android模拟器,其目录下的.so文件是c或c++编译的动态链接库文件 |
| |
| res目录存放资源文件,包括图片,字符串等等,APK的脸蛋由他的layout文件设计 |
| APK的应用清单信息,它描述了应用的名字,版本,权限,引用的库文件等等信息 |
| classes.dex是java源码编译后生成的java字节码文件,APK运行的主要逻辑 |
| resources.arsc是编译后的二进制资源文件,它是一个映射表,映射着资源和id,通过R文件中的id就可以找到对应的资源 |
双开
最简单的思路就是给 apk 换个包名,换个包名之后系统识别会是两个不同的 apk,就能实现共存。
APK逆向
什么是JVM、Dalvik、ART
JVM是JAVA虚拟机,运行JAVA字节码程序
Dalvik是Google专门为Android设计的一个虚拟机,Dalvik有专属的文件执行格式dex(Dalvik executable)
Art(Android Runtime)相当于Dalvik的升级版,本质与Dalvik无异
smali及其语法
smali是Dalvik的寄存器语言,smali代码是dex反编译而来的。
关键字
数据类型对应
常用指令
| |
|---|
| |
| |
| |
| |
| 全称equal(a=b),比较寄存器ab内容,相同则跳 |
| 全称not equal(a!=b),ab内容不相同则跳 |
| 全称equal zero(a=0),z即是0的标记,a等于0则跳 |
| 全称not equal zero(a!=0),a不等于0则跳 |
| 全称greater equal(a>=b),a大于或等于则跳 |
| 全称little equal(a<=b),a小于或等于则跳 |
| |
| 分支跳转,一般会有多个分支线,并根据指令跳转到适当位置 |
| |
工具
工具清单
- Android Studio:开发安卓应用(包括模块),毕竟会开发才能会逆向
目前就这么多,后面遇到再补...
签名校验
这个跟传统密码学的校验还不太一样,这个校验主要校验的可以认为是公钥的 hash。
传统的签名是:我将apk的重要文件进行hash之后,将hash用私钥加密得到一串签名,同时会放置一串公钥信息,验证签名的过程就是把文件 hash 和签名做公钥加密之后比较是否一致,这只是验证签名的完整性,而且通常来说不可能放在代码层校验,因为代码本身是签名要包括的内容。
而真正的签名校验其实是在上述完整性保证的前提下,去验证公钥信息是否一致,因为只有拥有对应的私钥才能够签名,就意味着该 APK 肯定是由真正的开发者发布的。
Hook框架模块编写准备
以Xposed框架为例,在开发 app 时候需要做以下几步以便于 LSPOSED 等 Xposed 框架识别和加载。
- 打开
build.gradle 将该 jar 的依赖设置为 compileOnly - 在 assets 目录新建 xposed_init 文件,将入口设置为 hook 的主框架
- 修改 AndroidManifest.xml 文件
OK,一步步来实现这一步骤,第一步将 jar包 拖入项目中
第二步,打开 build.gradle 或者 build.gradle.kts 找到对应依赖,导入声明方式设置为 compileOnly。
第三步,在 assets 目录新建 xposed_init 文件,定义入口点。
这里我指向了 Hook 类,事实上你叫什么都无所谓。
第四步,修改 AndroidManifest.xml 文件,将标签的 application 标签的内容设置为以下形式:
复制代码 隐藏代码
<!-- 是否是xposed模块,xposed根据这个来判断是否是模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模块描述,显示在xposed模块列表那里第二行 -->
<meta-data
android:name="xposeddescription"
android:value="这是一个Xposed模块" />
<!-- 最低xposed版本号(lib文件名可知) -->
<meta-data
android:name="xposedminversion"
android:value="89" />
这里插一句这种 XML 格式的一些说明
标签是 XML 的主要内容,以 HTML 为例,当设置一个 a 标签时 <a href='www.example.com'>test</a>,其中
testhref 是 a 标签的属性名,www.example.com 就是这个 a 标签 href 属性对应的属性值。
标签不一定是成双成对出现的,可以是“孤儿标签”,例如 HTML 的 <br/>,它仅表示换行的意思,但是有些成双成对的标签它没有内容也可以成为孤儿标签,例如上面的例子可以变成 <a href='www.example.com'/>,这在语法并没有问题,只是这个链接没有可以点击的对象而已。
查看配置文件可以发现,这个 application 默认是一个“孤儿标签”,想给它加内容就需要把它改写成成双成对的形式,以便于向该标签添加内容。
好,做完这几步,这个 app 已经是一个合格的 Xposed 框架了。
hook方法的技巧
先总结一下 hook 的方法:
findAndHookMethod:根据方法名和参数列表 hook 指定参数,适用于参数简单的方法。hookAllMethods:仅根据方法名进行 hook,由于 JAVA 存在方法重载,因此可能指定名字会 hook 到原本不想 hook 的方法,仅适用于一些复杂参数的方法。findAndHookConstructorhookAllConstructorssetStaticObjectField:设置静态成员值(除基本类型之外的静态变量都可以用这个方法)callStaticMethodcallMethod
这里 hook 的 demo 选择了正己大神对应课程的教程包。
hook普通方法
先写一个 hello world 模块。
用 jadx 打开要 hook 的包,选择 hook a方法,直接复制代码片段,该导的包就从 xposed 里面导就行。
这里写出如下代码
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo", loadPackageParam.classLoader, "a", String.class, newXC_MethodHook() {
@Override
protectedvoidbeforeHookedMethod(XC_MethodHook.MethodHookParam param)throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log(param.args[0].toString());
Stringnewstr="xia0ji2333";
param.args[0] = newstr;
}
@Override
protectedvoidafterHookedMethod(MethodHookParam param)throws Throwable {
super.afterHookedMethod(param);
Log.e("zj2595","返回值修改之前:"+param.getResult().toString()+"\n");
param.setResult("被我修改了233");
}
});
}
}
其中 beforeHookedMethod 用于在调用之前修改函数,通常用于监控参数等目的,在这里也可以选择直接拦截掉,afterHookedMethod 用于在调用之后修改函数,通常用于修改返回值,这里我分别实现修改参数和修改返回值的功能。
加载 xposed 模块,安装之后在 lsposed 里面选择
最后启动 LOG,用 Android Studio 抓日志,hook 成功。
hook复杂方法
以 com.zj.wuaipojie.Demo 包中的 complexParameterFunc 方法为例。
这个方法甚至有一个 HashMap 泛型,对于这种方法,如果不关心它的复杂参数,完全可以使用 hookAllMethods 来 hook。
代码如下:
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
Classa= loadPackageParam.classLoader.loadClass("com.zj.wuaipojie.Demo");
XposedBridge.hookAllMethods(a, "complexParameterFunc", newXC_MethodHook() {
@Override
protectedvoidbeforeHookedMethod(MethodHookParam param)throws Throwable {
super.beforeHookedMethod(param);
Stringa1= param.args[0].toString();
Stringa2= param.args[1].toString();
Log.e("zj2595",a1);
Log.e("zj2595",a2);
param.args[0] = "hooked_"+a1;
}
});
}
}
最终结果也是 hook 上了:
hook替换方法
有些方法可能有副作用,例如 a 方法调用了 exit 函数,而我 hook 之后似乎没有办法阻止这个事情发生,这个时候就需要进行替换。
假设这里输出的 这是替换函数 就是一个副作用函数,现在我想阻止它输出。
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
Classa= loadPackageParam.classLoader.loadClass("com.zj.wuaipojie.Demo");
XposedBridge.hookAllMethods(a,"repleaceFunc",newXC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam)throws Throwable {
return"";
}
});
}
}
这里需要使用 XC_MethodReplacement 并实现 replaceHookedMethod 接口,直接给一个 return "" 即可。
可以看到原本应该输出的 这是替换函数 没有了。
hook变量的技巧
在说这个之前,区分一下两种类型的变量,一种是静态的,它只有一份,一种是非静态的,它随具体实例。
比如人这个类,数量就是对于整个人类而言的,它只有一个,所以通常来说它的定义是静态的变量。
再比如身高对人类而言,每个人都有一个确定的身高,所以通常来说它只能定义为非静态变量。
Hook静态变量
使用 setStaticObjectField 方法即可。
以图中的静态方法为例
直接调用方法修改即可
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
finalClassclazz= XposedHelpers.findClass("com.zj.wuaipojie.Demo", loadPackageParam.classLoader);
XposedHelpers.setStaticObjectField(clazz, "staticField", "Hooked Static Field Value by xia0ji233");
}
}
结果也是成功修改:
Hook非静态变量
直接看代码和结果吧,主要使用 hookAllConstructors 方法去做
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
finalClassclazz= XposedHelpers.findClass("com.zj.wuaipojie.Demo", loadPackageParam.classLoader);
XposedBridge.hookAllConstructors(clazz, newXC_MethodHook() {
@Override
protectedvoidafterHookedMethod(MethodHookParam param)throws Throwable {
super.afterHookedMethod(param);
//param.thisObject获取当前所属的对象
Objectob= param.thisObject;
XposedHelpers.setIntField(ob,"privateInt",114514);
}
});
}
}
这里 hook 该类所有的构造函数,并在构造函数结束之后(实现 afterHookedMethod 方法)对成员的属性进行修改,成功修改实例的成员值。
Hook构造函数
这里有参构造和无参构造一起 hook,用的是同一种方法,参数需要自己加才能 hook 到指定构造方法。
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
XposedHelpers.findAndHookConstructor("com.zj.wuaipojie.Demo", loadPackageParam.classLoader, newXC_MethodHook() {
@Override
protectedvoidbeforeHookedMethod(MethodHookParam param)throws Throwable {
super.beforeHookedMethod(param);
Log.e("zj2595","Demo Constructor Hooked");
}
});
XposedHelpers.findAndHookConstructor("com.zj.wuaipojie.Demo", loadPackageParam.classLoader, String.class, newXC_MethodHook() {
@Override
protectedvoidbeforeHookedMethod(MethodHookParam param)throws Throwable {
super.beforeHookedMethod(param);
Log.e("zj2595","Demo(String) Constructor Hooked, param: " + param.args[0]);
}
});
}
}
因为无参构造调用了有参构造,所以无参的构造 hook 先输出,再输出有参构造,如果是调用结束后的 hook 输出顺序则会相反。
其它类型的hook
主动调用
当需要主动调用某方法时,需要使用方法 callMethod,不过这里只能调用非静态方法,因此必须拿到实例才能调用,实例可以自己新建也可以用某些方法拿到。
如果是静态类,那就传类和静态方法名即可。
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
Classclazz= XposedHelpers.findClass("com.zj.wuaipojie.Demo",loadPackageParam.classLoader);
XposedHelpers.callMethod(clazz.newInstance(),"refl");
}
}
这里是非静态方法的主动调用,如果要调用静态方法,则换成
复制代码 隐藏代码
Classclazz= XposedHelpers.findClass("com.zj.wuaipojie.Demo",loadPackageParam.classLoader);
XposedHelpers.callMethod(clazz,"staticFunc");
这里展示一下调用非静态方法的结果:
反射调用
先介绍一下反射吧(实则我也不懂)
讲反射之前先讲一讲正射,正射是加载类最常见的一种方式。
复制代码 隐藏代码
Appleapple=newApple();
apple.setPrice(4);
通过导入包 apple 并直接调用构造方法构造类的方法称为正射,正射需要在编译阶段就确定类。
反射则是通过运行时动态加载类。
复制代码 隐藏代码
Classclz= Class.forName("com.xiaoji.Apple");
Methodmethod= clz.getMethod("setPrice", int.class);
Constructorconstructor= clz.getConstructor();
Objectobject= constructor.newInstance();
method.invoke(object, 4);
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
反射调用
复制代码 隐藏代码
package com.xiaoji.xposeddemo;
import android.util.Log;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
publicclassHookimplementsIXposedHookLoadPackage {
@Override
publicvoidhandleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)throws Throwable {
if(!loadPackageParam.packageName.equals("com.zj.wuaipojie")){
return ;
}
Classclazz= Class.forName("com.zj.wuaipojie.Demo",false,loadPackageParam.classLoader);
Methodrefl= clazz.getDeclaredMethod("refl");
refl.setAccessible(true);
refl.invoke(clazz.newInstance());
}
}
成功通过反射调用方法,调用私有方法时,需要使用 setAccessible 把权限设置一下。
Hook内部类
基本与 hook 普通方法一致,使用 findAndHookMethod 方法,在传类名的时候需要使用类似 com.zj.wuaipojie.Demo$InnerClass 的写法,用 $ 区分内部类。
本文内容来自网络,如有侵权请联系删除