28、Android逆向基础:APK结构、dex2jar与jd-gui、Smali语法基础、Xposed框架
说到Android逆向,我刚开始接触的时候也是一头雾水。那时候公司接了个竞品分析的需求,对方App加了壳,我折腾了整整三天才找到突破口。今天我就把这套基础工具链和思路,掰开了讲给你听。
28.1 APK文件结构:你拆开看看
APK说白了就是一个压缩包,你把后缀改成.zip,解压就能看到里面的东西。但这里头每个文件都有它的使命。
| 文件/目录 | 作用 |
|---|---|
| AndroidManifest.xml | App的身份证,权限、组件、版本号全在这 |
| classes.dex | 核心代码,Dalvik字节码文件 |
| resources.arsc | 编译后的资源索引表 |
| res/ | 原始资源文件(布局、图片等) |
| lib/ | Native层.so库,armeabi、arm64-v8a等 |
| META-INF/ | 签名信息,篡改后安装会报错 |
我个人习惯,拿到一个APK先看AndroidManifest.xml。为什么?因为这里暴露了App的入口Activity、用到的权限、还有有没有开启调试模式。我曾经遇到过一个App,它的核心逻辑藏在了一个不导出的Service里,但Manifest里漏了exported="false"——嗯,这就是突破口。
28.2 dex2jar + jd-gui:把字节码变回Java
classes.dex是Dalvik虚拟机跑的东西,人眼直接看很费劲。我们需要把它转成JAR,再用jd-gui打开,就能看到接近源码的Java代码了。
操作流程其实就三步:
- 把APK解压,拿到classes.dex
- 执行
d2j-dex2jar.sh classes.dex,生成classes-dex2jar.jar - 用jd-gui打开这个JAR文件
你想想看,原本一堆乱码似的字节码,瞬间变成了可读的Java代码。虽然变量名可能被混淆成a、b、c,但逻辑结构是完整的。
28.3 Smali语法基础:Dalvik的汇编语言
有时候jd-gui反编译出来的代码不完整,或者你想修改逻辑再回编译,那就得直接跟Smali打交道了。Smali是Dalvik字节码的人类可读形式,后缀是.smali。
我挑几个最常用的语法说:
| Smali指令 | 含义 |
|---|---|
| .class public Lcom/example/Test; | 声明一个public类 |
| .method public onClick()V | 定义一个返回void的public方法 |
| const/4 v0, 0x1 | 把整数1存入v0寄存器 |
| invoke-virtual {v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V | 调用虚方法,比如System.out.println |
| if-eqz v0, :cond_0 | 如果v0等于0,跳转到cond_0标签 |
| return-void | 返回空 |
说白了,Smali就是寄存器式的汇编。Android的Dalvik虚拟机有16个通用寄存器(v0-v15),参数寄存器(p0-pn)。p0是this,p1开始是方法参数。
举个例子,你想修改一个App的VIP判断逻辑。原Smali可能是这样:
.method public isVip()Z
.registers 2
const/4 v0, 0x0
return v0
.end method
把const/4 v0, 0x0改成const/4 v0, 0x1,就变成永远返回true了。改完用apktool回编译,重新签名安装就行。
28.4 Xposed框架:运行时Hook的艺术
Xposed跟上面那些静态分析不一样,它是动态的。你不需要改APK文件,直接在手机上装个框架,写个模块就能在运行时拦截、修改任何方法的调用。
我当年第一次用Xposed的时候,感觉就像开了上帝视角。你想想看,一个App的加密函数,你不需要知道它怎么实现的,直接在返回值上Hook一下,把结果打印出来就行了。
一个典型的Xposed模块长这样:
public class MainHook implements IXposedHookLoadPackage {
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
if (!lpparam.packageName.equals("com.target.app"))
return;
findAndHookMethod("com.target.app.LoginActivity",
lpparam.classLoader,
"checkPassword",
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// 参数是param.args[0]
XposedBridge.log("密码是: " + param.args[0]);
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
// 返回值是param.getResult()
param.setResult(true); // 直接返回验证通过
}
});
}
}
这里要注意几个点:
- 包名必须写对,不然Hook不生效
- 方法签名要精确,参数类型不能错
- beforeHookedMethod在方法执行前触发,afterHookedMethod在之后触发
28.5 知识体系总览
下面这张图,我把这节的核心逻辑串起来了。你顺着箭头看,就能明白逆向分析的一条完整路径。
你看,从APK结构入手,你能找到入口和权限信息。然后通过dex2jar和jd-gui做静态分析,把Java代码还原出来。如果遇到混淆或者想动态验证,就上Smali改字节码,或者用Xposed在运行时Hook。这三板斧下来,大部分App的逻辑都能摸清楚。