首发于公号:剑客古月的安全屋
目录
- 前言
- smali基础知识
- ssl https校验及双向认证
- 壳
- 脱壳方法与实操
0x01 前言
本期内容主要围绕app的壳进行展开,由于笔者的理解或者语言表达原因,可能内容会有些晦涩难懂,还请诸位看官见谅。
0x02 smali
1.smali基础介绍
什么是smali?-> Android虚拟机的反汇编语言
Android代码一般是用java编写,执行java程序就需要用到java虚拟机,但由于平台适应性和性能考虑,并没有用标准的JVM,使用了专门的Android虚拟机(5.0以下为Dalvik,5.0以上为ART)。
而在Android虚拟机上,可执行文件不是class文件,而是编译打包好后的dex文件,dex文件反编译就是smali代码。这也是为什么说smali是android的反汇编语言。
2.smali语法
在逆向时看懂smali可以提升不少效率
2.1 数据类型
Java 和 Smali 的类型对应关系和jni类似,如下所示: 基本数据类型
Smali数据类型 | Java数据类型 |
---|---|
V | void |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long |
F | float |
D | double |
对象类型
Smali数据类型 | Java数据类型 |
---|---|
Lpackage/name/ObjectName; | ObjectName |
[I | int[] |
2.2 语法关键字
2.2 语法关键词
关键词 | 说明 |
---|---|
.class | 定义java类名 |
.super | 定义父类名 |
.source | 定义Java源文件名 |
.filed | 定义字段 |
.method | 定义方法开始 |
.end method | 定义方法结束 |
.annotation | 定义注解开始 |
.end annotation | 定义注解结束 |
.implements | 定义接口指令 |
.local | 指定了方法内局部变量的个数 |
.registers | 指定方法内使用寄存器的总数 |
.prologue | 表示方法中代码的开始处 |
.line | 表示java源文件中指定行 |
.paramter | 指定了方法的参数 |
.param | 和.paramter含义一致,但是表达格式不同 |
2.3 smali格式
一般来说,打开smali文件,头三行的格式如下
.class <访问权限> [关键修饰字] <类名>; .super <父类名>; .source <源文件名>
类的成员变量,类似于private,public
.filed <访问权限> [关键修饰字] <字段名>:<字段类型>
函数的声明格式
.method <访问权限> [关键修饰字] <方法原型> <.locals> <.registers> // 函数中非参数的变量的多少 [.param] // 方法参数 [.line] <代码> .end method
2.4 smali指令
Smali 指令有常量操作指令,方法调用指令,移位指令,分支判断指令。
常量操作指令主要是const相关指令,格式如下:
const-<类型> 寄存器, 操作数 const-string v1, "test" // 定义字符串“test”并存到 v1 寄存器中 const/16 v1, 0x1e // 定义了16位的数据常量"0x1e",并存到v1寄存器中
方法调用,以invoke开头的指令
invoke-kind {vA,vB,vC},mehtod@DDDD // mehtod@DDDD 表 示 函 数 的 引用;vA,vB,vC则表示函数的参数 // 其定义顺序与调用函数的参数一 一对应;kind则代表被调用的类型 // kind 主要有static、virtual、 super(被调用的函数是静态函数、正常的函数和父类函数等) invoke-super {p0, p1}, Landroidx/Appcompat/App/AppCompatActivity;->onCreate (Landroid/os/Bundle;)V // 调用AppCompatActivity的onCreate方法,p0是this,p1是bundle类型参数
移位指令
move v1,p1 // 将 p1 赋值给 v1
分支判断
if-[test] v1,v2, [condition] // 如果v1, v2 满足test,则跳转到condition 执行 if-ge p1, v0, :cond_0 // 如果 p1 >= v0, 则跳转到 cond_0
2.5 代码实例解读
代码解读1
const/4 v0, 0x1 //把值0x1存到v0本地寄存器 iput-boolean v0,p0,Lcom/aaa;->IsRegisterd:Z //把v0中的值赋给com.aaa.IsRegistered,p0代表this,相当于this.Isregistered=true 这段Smali代码表示的是一个方法参数的定义。让我们逐个解析它: v0:这是一个寄存器标识符,用于表示方法内部的一个临时变量。 p0:这是一个参数标识符,用于表示方法的第一个参数。 Lcom/aaa;:这是一个类引用,指向名为aaa的包名为com的类。 ->IsRegisterd:这是一个方法引用,指向名为IsRegisterd的方法。 :Z:这是方法的返回类型,Z表示布尔类型。 因此,这段Smali代码表示的是一个名为IsRegisterd的布尔类型方法,它是类com.aaa的成员方法,且具有一个布尔类型的参数。 sget-object v0,Lcom/aaa;->ID:Ljava/lang/String; 获取ID这个String类型的成员变量并放到v0这个寄存器中 iget-object v0,p0,Lcom/aaa;->view:Lcom/aaa/view; iget-object比sget-object多一个参数p0,这个参数代表变量所在类的实例。这里p0就是this
代码解读2
const/4 v3, 0x0 sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;
相当于 this->timer=null;
代码解读3
.local v0, args:Landroid/os/Message; const/4 v1, 0x12 iput v1,v0,Landroid/os/Message;->what:I
将args实例化为messgae,相当于 args.what = this->v1
代码解读4
.method private ifRegistered()Z .locals 2 // 本地寄存器的个数 .prologue const/4 v0, 0x1 //v0赋值为1 if-eqz v0, :cond_0 //判断v0是否等于0,等于0则跳到cond_0执行 const/4 v1, 0x1 //符合条件分支 :goto_0 //标签 return v1 //返回v1的值 :cond_0 //标签 const/4 v1, 0x0 //cond_0分支 goto :goto_0 //跳到goto_0执行 .end method
代码解读5
const-string v0, "NDKLIB" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
这段Smali代码表示的是加载一个名为"NDKLIB"的本地库。让我们逐个解析它:
const-string v0, "NDKLIB"
:这行代码将字符串常量"NDKLIB"存储到寄存器v0中。const-string
指令用于将字符串常量存储到寄存器中。invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
:这行代码调用了java.lang.System
类的静态方法loadLibrary
。invoke-static
指令用于调用静态方法。方法的参数是寄存器v0中的字符串常量。Ljava/lang/String;
表示方法的参数类型为java.lang.String
,V
表示方法的返回类型为void
。
综合起来,这段Smali代码的作用是调用System.loadLibrary("NDKLIB")
方法,用于加载名为"NDKLIB"的本地库。
代码解读6
const-string v0, "Eric" invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String; move-result-object v2
这段Smali代码表示的是调用Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
方法,并将结果存储在寄存器v2中。让我们逐个解析它:
const-string v0, "Eric"
:这行代码将字符串常量"Eric"存储到寄存器v0中。const-string
指令用于将字符串常量存储到寄存器中。invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
:这行代码调用了Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
方法。invoke-static
指令用于调用静态方法。方法的参数是寄存器v0中的字符串常量。Ljava/lang/String;
表示方法的参数类型为java.lang.String
,Ljava/lang/String;
表示方法的返回类型为java.lang.String
。move-result-object v2
:这行代码将方法调用的结果对象存储到寄存器v2中。move-result-object
指令用于将方法调用的返回值存储到寄存器中。
综合起来,这段Smali代码的作用是调用Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
方法,参数为字符串常量"Eric",并将返回的字符串结果存储在寄存器v2中。
0x03 ssl https校验
上节课稍微提到了证书校验这个问题。这里进行一下补充
一般来说,现在的app大多数都配置了证书校验,主要情况分为两种
单向校验-> 客户端存在校验服务端证书,服务端不存在校验 双向校验-> 客户端存在证书校验,服务端也存在证书校验,双向认证。
前者结果即简单抓不了包,但取消代理后可抓到包
后者结果即报错404,httptimeout等错误
当然前后者造成的原因还有app存在代理检测
借助vpn或者流量转发可进行绕过排除
但现在大多数app都采用双向校验,需要反编译app后安装证书,这个我们后期再讲
0x04 壳
1.壳的识别
我们首先需要知道壳是什么东西?什么是加固?
说简单点,就是在没有防护措施的app套一层壳,使黑客无法直接进行反编译。 说复杂点,就是对dex文件进行加密合成到壳文件中,在壳中有自定义的loader可以进行解密,可将apk的指令解密到对应的内存中
那如何识别壳呢?
如果加了壳,在直接进行反编译后的文件中会有一些特征,比如说最常见的就是看lib下是否存在特定so文件
识别工具像常见的pkid
像我这里随手查了一款apk
2.壳的历史
第一代壳就是最简单的dex加密
第二代壳则是第一代壳的升级版,加密对象变成了apk内的代码文件dex,并且把dex代码加密到了native层,即so文件
第三代和第四代大多都是企业版付费使用,使用了动态解密和so混淆+保护技术,难度技术较高,基本无法短时间破解,此处不再赘述,第四代更是使用了自己的虚拟机指令与解释器。
3.脱壳方法
此处仅介绍几款常用的方法
1.内存dump
目前市面上最常见的是内存dump
内存加载壳dex,壳dex加载原dex,这个时候只需要把内存中的原dex dump出来即可
当然该方法只适用于第一二代壳,三四代由于动态运行的原因从而导致内存中不太可能存在完整的dex文件
2.动态调试法
该方法依旧内核依旧是内存dump法,需要通过调试器找到dex文件在内存中完全解密的时间,然后将其dump出来
3.hook法
本质上来说跟前两种差不多,依旧适用于前两代壳
4.dex2oat法
ART 模式下,dex2oat 生成 oat 时,内存中的 dex 是完整的,此时可以用修改后的 dex2oat 文件替换原系统的 dex2oat 文件。
0x05 脱壳工具及实操
这里推荐两款,并在后文给大家做案例演示
1.blackdex
BlackDex是一个运行在Android手机上的脱壳工具,支持android5.0~12比较宽泛,无需依赖任何环境任何手机都可以使用,包括模拟器。只需几秒,即可对已安装包括未安装的APK进行脱壳。安装上该工具既使用,有点很明显,就是不用root,也不用什么Xposed、面具、frida,更不用刷机,直接就能脱壳。缺点就是针对一些运行中加载的类和方法可能就脱不到了,因为这种脱壳方法不会去运行程序。
个人不建议使用
2.ReFlectMaster
一体化神器,网上吹的很神,我并没有仔细看过原理,可以作为脱壳的第一选择,脱不了就用其他的脱
首先需要准备一个最好7.0以下版本的安卓机,我这里选择的是模拟器,配上root+xposed框架,我这里还用到了mt管理器
随后下载该工具移入模拟器下载
重启一下
模块启用
随后进入模块,启动后根据acitivity进行取出dex然后反编译分析
3.frida-dexdump
采用的frida框架
pip install frida-dexdump -i https://pypi.tuna.tsinghua.edu.cn/simple
这里还是建议使用模拟器,安卓版本较低,不容易出问题
进入python环境的安装目录xxxPython37Scripts,定位到 frida-dexdump 命令的目录
同目录起个cmd
pip install frida==12.8.0 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install frida-tools==5.3.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
frida-dexdump -U -f packagename
关于脱壳命令:
- 指定App的应用名称:frida-dexdump -U -n 保利票务
- 指定App的应用进程ID:frida-dexdump -U -p 3302
- 指定App的应用包名:frida-dexdump -U -f com.iCitySuzhou.suzhou001
此处给几个错误信息解决方法
1.Failed to enumerate applications: unable to handle 64-bit processes due to build configuration->fridaserver版本安装不对,请参考我前文进行配置 2.adb server version (36) doesn‘t match this client (41);killing... -> 模拟器adb环境与本机不同,进行修改即可
这里把当前目录下所有dex文件拉入jadx即可
但这里有14个文件
不妨可以合并成一个dex文件进行分析,毕竟这么多拉进来肯定嘎嘎报错
import os, sys
if __name__ == "__main__":
if len(sys.argv) < 2 :
print("start error!start need 2 arguments!")
sys.exit()
print(sys.argv[1])
path = os.path.split(__file__)[0] + '\'
print(path)
files= os.listdir(path)
for file in files:
if file.find(".dex") > 0:
sh = sys.argv[1] + ' -j 1 -r -d ' + path + " " + path + file
print(sh)
os.system(sh)
执行命令如下
python XXXXXXXDex2Java.py D:course_studyjadx-1.4.4bin
这样做肯定也有坏处,就是源码不够全
后续将继续精进app技术,敬请关注
文章参考
https://juejin.cn/post/7216968724938195001 https://www.jianshu.com/p/9931a1e77066 https://www.isisy.com/1420.html
原文始发于微信公众号(剑客古月的安全屋):app攻防-壳&smali&脱壳实操
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论