From:https://zhuanlan.zhihu.com/p/95915254
知乎:Android 逆向分析学习路线?:https://www.zhihu.com/question/25626303
入门篇 --- 学习Android安全和逆向开发的路线总结:https://www.52pojie.cn/thread-1065039-1-1.html
JDK / SDK / NDK
eclpise集成开发环境 / Android Studio
AndroidKiller / jeb / jadx / GDA / Androidk逆向助手
IDA / GDB
apkhelper / getsign / APK上上签
模拟器(雷电3.59)
MT管理器 / RE文件管理器
工具安装注意事项:
1. jdk 安装路径中不能有中文
2. ndk 的配置路径中不能有中文和空格,可以把它放在根目录
3. 安装包文件不全,运行会出错
4. 没有配置环境变量可以按住shift+右键=>”在此处打开命令窗口”选项
5. ndk安装正确的提示
D:\android-ndk-r10e>ndk-build
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
D:\android-ndk-r10e\build/core/build-local.mk:143: *** Android NDK: Aborting. Stop.
APK 是 Android Package 的缩写,即 Android安装包。APK是类似 Symbian Sis 或 Sisx 的文件格式。通过将 APK 文件直接传到Android模拟器 或 Android手机 中执行即可安装。
assets 不经过 aapt 编译的资源文件
lib .so文件
META-INF 文件摘要,摘要加密和签名证书文件目录
CERT.RSA 公钥和加密算法描述
CERT.SF 加密文件,它是使用私钥对摘要明文加密后得到的密文信息,
只有使用私钥配对的公钥才能解密该文件
MANIFEST.MF 程序清单文件,它包含包中所有文件的摘要明文
res 资源文件目录,二进制格式
drawable 图片
layout 布局
menu 菜单
resources.arsc 经过 aapt 编译过的资源文件
classes.dex 可执行文件
AndroidManifest.xml 配置文件
打包资源(res/assets/AndroidManifest.xml/Android基础类库)文件,
生成 R.java和resources.ap_文件
处理AIDL文件,生成对应的.java文件
编译Java文件,生成对应的.class文件
把.class文件转化成Davik VM支持的.dex文件(.java=>.class=>.dex)
打包生成未签名的.apk文件
对未签名.apk文件进行签名
对签名后的.apk文件进行对齐处理
1.安装方式
系统程序安装
通过Android市场安装
ADB安装(adb devices:显示当前连接的设备;adb install 安装包路径)
手机自带安装
2.安装过程
复制APK安装包到/data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保
存到/data/dalvik-cache目录,并/data/data目录下创建对应的应用数据目录。
3.安装后文件所在目录
/system/app 系统自带的应用程序,获得adb root权限才能删除
/data/app 用户程序安装的目录,安装时把apk文件复制到此目录
/data/data 存放应用程序的数据
/data/dalvik-cache 将apk中的dex文件安装到dalvik-cache目录下
4.卸载过程
删除安装过程中在上述三个目录下创建的文件及目录。
1.java虚拟机
java字节码
基于栈架构
2.dalvik虚拟机(jit机制)
【dalvik加载执行的odex文件: .dex => dexopt => .odex】
Android 5.0以下
dalvik字节码
dalvik可执行文件体积更小
基于寄存器架构
3.art虚拟机(aot机制)
【art加载执行的是oat文件: .dex => dex2oat => .oat】
Android 5.0版本及以上
1.了解dalvik寄存器
dalvik中的寄存器都是32位
2.寄存器之v命名法与p命名法
参数寄存器 P0-Pn
局部变量寄存器 V0-Vn
3.dex文件反汇编工具
smali.jar\ddx.jar
4.类型
smali ==> Java
V void
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L java类
[ 数组
5.字段
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
6.方法
Lpackage/name/ObjectName;->MethodName (III) Z
基础字节码-名称后缀/字节码后缀 目的寄存器 源寄存器
名称后缀是 wide,表示数据宽度为64位
字节码后缀是 from16,表示源寄存器为16位
空操作指令 nop
数据操作指令
move(-wide/-object/-result/-result-object/-result-wide/-exception)
move(-wide/-object)/from16
move(-object)/16
返回指令(重点)
return-void(表示方法的放回值为空)
return(表示方法的反回值为32位非对象类型的值)
return-wide(表示方法的反回值为64位非对象类型的值)
return-object(表示方法的反回值为对象类型的值)
数据定义指令(重点)
const(-wide/-string/-class)
const/4
const(-wide)/16
const(-wide)/32
const(-wide)/high16
实例操作指令
check-cast(类型转换)
instance-of(检查)
new-instance(新建)
数组操作指令
array-length(获取数组长度)
new-array(新建数组)
filled-new-array(定义数组并初始化)
filled-new-array/range(定义数组并初始化/指定取值范围)
filled-array-data(数组元素取值与赋值)
异常指令(throw)
跳转指令(重点)
goto 紧跟一个标签直接跳转到标签所在位置
packed-switch(有规律)、sparse-switch(无规律)
if-eq(等于)/if-ne(不等于)
if-lt(小于)/if-le(小于等于)
if-gt(大于)/if-ge(大于等于)
比较指令(cmp)
大于(1)/等于(0)/小于(-1)=>cmpg、cmp
大于(-1)/等于(0)/小于(1)=>cmpl
字段操作指令
普通字段=>iget(读)/iput(写)
静态字段=>sget(读)/sput(写)
方法调用指令(重点)
invoke-virtual(调用实例的虚方法) Java当中的普通方法
invoke-super(调用实例的父类/基类方法)
invoke-direct(调用实例的直接方法) 调用java当中的构造方法
invoke-static(调用实例的静态方法)
invoke-interface(调用实例的接口方法)
数据转换指令
neg-数据类型=>求补
not-数据类型=>求反
数据类型1-to-数据类型2=>将数据类型1转换为数据类型2
数据运算指令
add/sub/mul/div/rem 加/减/乘/除/模
and/or/xor 与/或/非
sh1/shr/ushr 有符号左移/有符号右移/无符号右移
先查壳,再反编译看验证
.apk文件 ==> 反编译apk(dex/配置文件/资源文件(apk反编译败))
==> 修改关键文件实现自己的目的 ==> 重新打包签名(无法重新打包) ==> apk安装后无法运行。
apktool .dex=>.smali
dex2jar .dex=>.jar
Apktool工具实际上只反编译以下三种类型文件:.xml文件、.dex文件、.arsc文件
去除广告和弹窗,撇开不存在于smali的这种情况,
容易的就是可以在XML中寻到Activity,
难的就是寻不到,发生这种情况时,就要分析代码,程序逻辑,抓住关键信息,界面和函数。
注意:当字符串等关键信息搜不到时,可以从三个方向考虑:
1. 字符串在so层;
2. 字符串被加密了
3. 结合了服务器,服务器返回,本地显示。
无论是普通类、抽象类、接口类或者内部类,在反编译出的代码中,它们都以单独的 smali 文件来存放。每个 smali 文件都由若干条语句组成, 所有的语句都遵循着一套语法规范。
1.描述类的信息
.class < 访问权限> [ 修饰关键字] < 类名>
.super < 父类名>
.source <源文件名>
2. 静态字段
# static fields
.field < 访问权限> static [ 修饰关键字] < 字段名>:< 字段类型>
3.实例字段
# instance fields
.field < 访问权限> [ 修饰关键字] < 字段名>:< 字段类型>
4.直接方法
# direct methods
.method <访问权限> [ 修饰关键字] < 方法原型>
<.locals> “.locals ”指定了使用的局部变量的个数
[.param] “.param”指定了方法的参数
[.prologue] “.prologue ”指定了代码的开始处
[.line] “.line ”指定了该处指令在源代码中的行号
<代码体>
.end method
5. 虚方法的声明与直接方法相同,只是起始处的注释为“virtual methods”。
6.接口
# interfaces
.implements < 接口名>“.implements ”是接口关键字,后面的接口
名是 DexClassDef 结构中 interfacesOff 字段指定的内容。
7.注解
# annotations
.annotation [ 注解属性] < 注解类名>
[ 注解字段 = 值]
.end annotation
注解的作用范围可以是类、方法或字段。如果注解的作用范围是类,
“.annotation ”指令会直接定义在 smali 文件中,
如果是方法或字段,“.annotation ”指令则会包含在方法或字段定义中。
1.分析流程
搜索特征字符串
搜索关键api
通过方法名来判断方法的功能
2.快速定位关键代码
反编译 APK 程序,AndroidManifest.xml => 包名/系统版本/组件
程序的主 activity(程序入口界面)
每个 Android 程序有且只有一个 主Activity
分析程序的执行流程
需重点关注的 application
application 执行时间
授权验证
3.定位关键代码的技巧
信息反馈法 ( 资源id/字符串 )
特征函数法 ( api函数 )
顺序查看法 ( 分析程序执行流程 / 病毒分析 )
代码注入法 ( 动态调式 / 插入log / 查看logcat/分析加解密 )
栈跟踪法 ( 动态调式 / 函数调用流程)
Method Profiling ( 方法剖析 => 动态调式 / 热点分析/ 函数调用流程 )
jdwp ( 调试有线协议 ) => jdb
ddms ( dalvik 调式监视器服务 )
通过该命令(adb shell getprop ro.debuggable)获取当前设备 ro.debuggable的值,当值为1时开启系统调试开关。
android.debuggable="true"
logcat(查看调式信息) / method profiling(跟踪程序的执行流程)
去除可能会产生费用的危险权限
android.permission.SEND_SMS
android.permission.CALL_PHONE
支付接口
电信支付接口
logcat字符串定位(Egame支付成功/Egame支付Cancel/orderid)
=> 搜索字符串,向上分析,回溯分析/函数名替换
联通支付接口
logcat字符串定位(Unicom支付cancel/Unicom支付成功)
=>在下面类修改处理,可以用goto或switch方法
移动支付接口
logcat字符串定位(购买道具:[] 成功/购买道具:[] 失败)
=>在下面类修改
特征码
中国移动 46000、46002、46007、46020
中国联通 46001、46006、46010
中国电信 46003、46005、46011
内购关键词
和游戏搜索方法名
onResult,onchinabilling,resulton,Paycenter,Callback
联通游戏搜索方法名
OnPayResult,PyaResulton,Activity,result,callback
电信爱游戏搜索方法名
paySuccess成功,payCancel取消,payFailed失败
移动mm搜索方法名
onBillingFinish,Billing,CallBack,onresult无用,
对关键onsuccess()函数的筛选与定位
支付宝和银行卡方法名
handle,message
支付宝搜索字符
搜索字符串“9000”
360支付
onfinishedon,Activityresult
4399
notifyDeliverGoods
内购技巧
从静态代码分析程序逻辑,找到修改点
程序逻辑分析=>最重要的又是流程分析/流程图=>核心(层次)
关键性信息查找=>静态(整体分析与局部分析)/动态
1. 如何编译原生程序
Application.mk(ARM硬件指令集/工程编译脚本/stl支持等)
Android.mk(编译选项/头文件/源文件及依赖库等)
local_path(call my-dir)
include $(clear_vars)
local_arm_mode:= arm指令模式
local_module:=模块名称
local_src_files:=源文件
build_executable(可执行文件)
build_shared_library(动态链接库)
build_static_library(静态链接库))
2. 原生程序的启动流程
原生程序的入口函数
动态库的加载/程序参数argc和argv的初始化
静态链接/动态链接(动态链接程序/动态链接库)
静态链接(crtbedin_static.o/crend_android.o)
动态链接(crtbegin_dynamic.o/crtend_android.o/加载器 (system/bin/linker))
静态链接程序在启动时不需要额外的加载其他的动态库(init/adbd/linker)
静态链接与动态链接程序的入口函数相同,动态链接程序在执行入口函数前需要通
过linker进行额外的初始化
main函数究竟何时被执行
静态链接(libc_init_static)/动态链接程序(libc_init_dynamic)
3. 原生 C++ 程序逆向分析
C++类的逆向
C++中的类可以理解为C语言中的结构体,每一个成员变量就是一个结构字段,
每一个成员函数的代码都被优化到了类的外部,它们不占据存储空间
Android NDK对C++特性的支持(app_stl)
system
gabi++(rtti)=>gabi++_static/gabi_shared
stlport(rtti/stl)=>stlport_static/stlport_shared
gnustl(c++异常/rtti/stl)=>gnustl_static/gnustl_shared
4. Android NDK JNI API逆向分析
Android NDK提供了那些函数
Linux C/C++
Android NDK<=>JNI接口<=>java
JNINativeInterfasce(jni本地接口)
JNIInvokeInterface(jni调用接口)
移植/保护核心代码
如何静态分析Android NDK程序
file=>load file=>parse c header file=>jni.h=>structures
ARM 处理器架构概述
ARM 处理器家族
Android 支持的处理器架构:ARM / x86 / MIPS
原生程序逆向初步
代码混淆技术
ARM指令集
堆栈指针寄存器sp / 堆栈寻址指令 ( stmfd压栈 / ldmfd出栈 )
存储器访问指令 ( str写入数据到存储器中 / ldr从存储器中读取数据到寄存器 )
数据处理指令(mov) / 带链接跳转指令(bl)
原生程序的生成过程 ( 交叉编译 / 跨平台编译 )
预处理(预处理指令) 编译 汇编 链接
必须了解的 ARM 知识
汇编:芯片,高级语言的逆向,中间语言
分析和修改汇编指令:赋值、跳转、算术运算、移位运算、堆栈操作、内存读写指令、函数调用约定
Thumb(16位)、Thumb2(32位)、ARM(32位)
用户模式(usr):
不分组寄存器(R0-R7)
分组寄存器(R8-R14)
传递参数与返回值(R0-R3)
保存栈顶地址(R13/SP)
保存函数的返回地址(R14/LR)
程序计数器R15(PC)
状态寄存器CPSR
ARM 处理器:ARM 状态 ( 执行32位对齐指令的ARM指令 ),Thumb 状态 ( 执行16位对齐的Thumb指令 )
完整的 ARM 汇编程序
处理器架构定义
段定义
注释与标号
@ => 单行注释
/**/ => 多行注释
汇编器指令
R0 - R3 这4个寄存器用来传递函数调用的第1到第4个参数,超出的参数通过堆栈来传递
R0 寄存器 同时用来存放函数调用的 返回值
被调用的函数在返回前无需恢复这些寄存器的内容
指令格式
{
{s}:是否影响cpsr寄存器的值
{.w}:指令宽度说明符.w表示是32位
{.n}:指令宽度说明符.n表示16位
,
{,
eq:相等/z=1
ne:不相等/标志z=0
hi:无符号数大于/c=1,z=0
cs/hs:无符号数大于或等于/c=1
cc/lo:无符号数小于/c=0
ls:无符号数小于或等于/c=0,z=1
gt:有符号数大于/z=0,n=v
ge:有符号数大于或等于/n=v
lt:有符号数小于/n!=v
le:有符号数小于或等于/z=1,n!=v
mi:负数/n=1
pl:整数或0/n=0
vs:溢出/v=1
vc:没有溢出
跳转指令
B 无条件跳转
BL 带链接的无条件跳转
BX 带状态切换的无条件跳转
BLX 带链接和状态切换的无条件跳转
寄存器访问指令
ldr(<-) / ldrd
str(->) / strd
ldm(->)
stm(<-)
push(入栈) / pop(出栈)
swp
数据处理指令
数据传送指令(mov) / 数据非传送指令(mvn)
算术运算指令
add/adc
sub/rsb/sbc/rsc
mul/mls/mla umull/umlal smull/smlal smlad/smlsd
sdiv/udiv
asr(算术右移指令)
逻辑运算指令
and/orr/eor 逻辑与/逻辑或/逻辑异或
bic(位清楚指令)
lsl/lsr 逻辑左移/逻辑右移
ror/rrx 循环右移/带扩展的循环右移
比较指令
cmp / cmn / tst / teq
其他指令
nop ( 空操作指令 )
swi ( 软中断指令 )
mrs ( 读状态寄存器指令 )
msr ( 写状态寄存器指令 )
B / BL 指令
00001BD0 BEQ loc_1c04
31-28:cond=>0000
27-25:101
25-24:L=>B:0 /BL:1
23-0:offset:目标地址与该指令的相对偏移
offset:(1c04-(1bd0+8))/4=1011
0000 101 0 000000000000000000001011
0A 00 00 0B
00001BD0 0B 00 00 0A
LDR / STR 指令
00001BC0 LDR R2,[R12]
00001BC0 00 20 9C E5
E5 9C 20 00
1110 01 011001 1100 0010 000000000000
cond 1110
指令标识 01
IPUBWL 011001
Rn:R12 1100
Rd:R2 0010
offset:0 000000000000
MOV 指令
MOV R1, #0x56000000
56 14 A0 E3
E3 A0 14 56
1110 00 1 1101 0 0000 0001 0100 01010110
Thumb 指令
CPSR 寄存器
T位:1-thumb,0-arm
交互式反汇编器,是典型的递归下降反汇编器。
导航条
蓝色 :表示常规的指令函数
黑色 :节与节之间的间隙
银白色 :数据内容
粉色 :表示外部导入符号
暗黄色: 表示 ida 未识别的内容
IDA主界面
IDA View三种反汇编视图:文本视图、图表视图、路径视图
Hex View 十六进制窗口
Struceures 结构体窗口
Enums 枚举窗口
Imports 导入函数窗口
Exports 导出函数窗口
Strings 字符串窗口
IDA常用功能及快捷键:
空格键: 切换文本视图与图表视图
ESC : 返回上一个操作地址
G : 搜索地址和符号
N : 对符号进行重命名
冒号键: 常规注释
分号键: 可重复注释
Alt+M : 添加标签
Ctrl+M: 查看标签
Ctrl+S: 查看段的信息
代码 和 数据 的 切换:
C => 解析成 代码
D => 解析成 数据
A => 解析成 ascii 字符串
U => 解析成 未定义的内容
p => 解析成 一个函数
X : 查看交叉应用
F5 : 查看伪代码
Alt+T : 搜索文本
Alt+B : 搜索十六进制
导入 jni.h 分析 jni 库函数
拷贝 伪C代码 到 反汇编 窗口:右键 => copy to assembly # 把伪 c代码复制到反汇编窗口的汇编代码。
IDA 可以修改 so 的 hex 来修改 so,edit => edit-patchrogram
在这里建议使用 winhex 来实现。
IDA 调式界面
IDA 调式常用功能
注意:修改内存 和 修改代码 的时机选择不同,
so 内寻找
基址 + 偏移
dvmloadnativecode 函数
dlsym(handle,"jni_onload")
.init 和 .init_array 下断技巧
linker 大法,先勾选三项,F9运行,再执行jdb,当目标so被加载时通过基址+偏移下段
Java层混淆、资源混淆、SO层混淆(ollvm)
签名验证、文件验证、整包验证
本地验证、服务器验证、综合验证
本地验证
java层验证
so层验证
网络验证
java层验证
so层验证
通信/实时控制
服务器
判断验证
去签名验证
PackageManager
getAppSignature
签名hash值
signatures
java层Debug类的isDebugged方法
native层的isDebugged方法
检测 status文件的TracePid
检测开放端口
inotify文件监控/proc/pid/maps等文件打开事件与检测其中内存运行的文件gettimeofday时间循环检测
fork父子循环检测
异常触发、非法指令、引入与异常信号陷阱
检测代码暂停时间
修改java层isDebugged方法返回
利用py脚本HOOK native层Debug方法
HOOK open、fopen函数动态下断修改TracePid的值或修改为过反调试的内核
利用其他端口调式
修改内核改变文件打开事件响应与修改打开/proc/pid/maps后内存运行文件结果
HOOK gettimeofday函数让其不能循环
修改内核fork方法与多IDA挂起kill19等方法调式
修改异常指令信号与识别非法指令使用异常HOOK
HOOK 常用获取时间的系统函数
修改检测时间,让其只检测一次
古典密码学(以字符为基本加密单元)
移位密码/错位密码
替代密码/置换密码
现代密码学(以信息块为基本加密单元)
哈希算法
非对称加密与解密
椭圆曲线
数字签名
数据分组与校验
散列函数:用于数据完整性校验
MD 消息摘要算法
SHA 安全散列算法
MAC 消息认证算法
CRC 循环冗余校验算法
对称加密算法:加密密钥 == 解密密钥
流密码:指加密时每次加密一位或一个字节的明文。
同步流密码
同步性 无错误传递性 主动攻击性
音频/视频数据提供版权保护
自同步流密码
自同步性 错误传递有限性
主动攻击性 明文统计扩散性
RC4/SEAL
分组密码(http://www.seacha.com/tools/):指加密时将明文分成固定长度的组,用同一密钥和算法对每一块加密,输出也是固定长度的密文。
常用于网络加密
安全性=>扩散/混乱原则
实现性=>软件/硬件
ECB 电子密码本模式
CBC 密文链接模式
CFB 密文反馈模式
OFB 输出反馈模式
CTR 计数器模式
DES
DESede/3DES/Triple DES
AES
TEA
DEA
PBE
非对称加密算法 :加密密钥 != 解密密钥
公钥==>对外公开,私钥==>对外保密。
私钥加密==>公钥解密,公钥加密==>私钥解密
DH 密钥交换算法
基于因子分解
RSA 数据加密/解密 数字签名
基于离散对数
ElGameal 数据加密/解密 数字签名
DSS数据签名标准=>DSA 数字签名
ECC 椭圆曲线加密算法
使用对称加密算法对数据进行加密与解密,使用非对称加密算法对对称加密算法所使用的密钥进行加密与解密。
数字签名
私钥==>签名
公钥==>验证
单向认证
双向认证
RSA
DSA
ECC+DSA==>ECDSA 椭圆曲线数字签名算法
认证/鉴别服务
数据完整性服务
抗否认性服务
数字证书
消息摘要算法
对称加密算法
非对称加密算法
数字签名
鉴别/认证
数据保密性
数据完整性
抗否认性
证书管理
KeyTool
构建自签名证书
构建CA签发证书
OpenSSL
根证书
服务器证书
客户端证书
密钥库==>管理私钥
数字证书==>管理公钥
加密/解密
签名/验证
X.509
服务器数字证书
协议与安全协议 ( 服务器协议关键,就是先抓包,再解密 )
概念:协议就是服务器与客户端交互信息的一种规则
客户端和服务器连接:实质都是连接服务器的IP地址和开放端口
POST:客户端提交数据给服务器
GET:客户端获取服务器数据
http,ftp,smtp: 应用层
tcp,udp: 传输层
IP:网络层
帧相关协议:数据链路层
HTTP/HTTP2
HTTPS协议 => https是基于sll/tls的http协议
ssl协议 => 安全套接字层
tls协议 => 传输层安全
socket => tcp/udp=>http/ftp
http / https
fiddler/charels
socket/tcp
抓网卡
wireshark / sniffer
防止代理
小米wifi + wpe
1. 如网游副本,有的就是没有包;
2. fiddler 只能抓 HTTP/HTTPS,如果是 socket 抓不到的;
3. 在动态调试时,没有走到发包的地方。
tcpdump + Wireshark:
adb shell tcpdump -p -vv -s 0 -w /sdcard/capture.pcap 开始抓包
ctrl+c 结束抓包
adb pull /sdcard/capture.pcap 导出文件
1. dex文件中的数据结构
u1/uint8_t => 表示1字节的无符号数
u2/uint16_t => 表示2字节的无符号数
u4/uint32_t => 表示4字节的无符号数
u8/unit64_t => 表示8字节的无符号数
sleb128 => 有符号leb128,可变长度为1-5字节
uleb128 => 无符号符号leb128,可变长度为1-5字节
uleb128p1 => 无符号leb128值加1,可变长度为1-5字节
2.dex文件整体结构
struct DexFile {
DexHeader
DexStringId
DexTypeId
DexProtoId //对 DexType 进一步说明
DexFieldId
DexMethodId
DexClassDef
DexData
DexLink
}
以索引为线索
3. DEX 的内存映射
与静态类似,只是变为 xxxItem 结构
ClassObject 结构由六个部分组成:
PDvmDex: // DEX文件字段
super: // 超类
sfields: // 对应DexClassData结构中的staticFields静态字段
iFields: // 对应DexClassData结构中的instanceFields实例字段
directMethods: // 对应DexClassData结构中的directMethods直接方法字段
virtualMethods: // 对应DexClassData结构中的virtualMethods虚方法字段
DexClassDef : class_def_item
DexClassData: class_data_item
DexFiled(staticFields): sfileds
DexFiled(instanceFields): ifileds
DexMethod(directMethods):directMethods
DexMethod(virtualMethods):virtualMethods
DexCode: code_item
linux elf 文件、windows pe文件
Android 操作系统内核采用 Linux 内核框架实现 Android ELF文件
ELF文件整体结构:
ELF Header ---> ELF文件头的位置是固定的
Segment Header Table ---> ELF程序头描述的是段的相关信息
.init
.text
.rodata
.data
.symtab 符号表
.line
.strtab 字符串表
Section Header Table ---> ELF节头表描述的是节区的信息
动态用段,静态用节
-a
-h
-l
-S
-e
-s
arsc 文件:
arsc 文件解析
xml 文件:
xml 文件解析及工具
xml 加密
xml 和 arsc 简单十六进制加密:
以 arsc 为例:在0x00000028h的文件偏移地址之前进行修改,如改头 部第一个HEX操作数为00。搜索字符,'1c' 搜索3下。把1c前面的 01改00,就可以了。看020行,头部字节流,把尾部的00改为和头 部一样。
思考:ARSC、xml文件很难去做单纯的动态加载,如DEX,这也是文件根本性质决定的,还有一个原因,就是安装后这个APK必须要打开。我们最多能做到的,在动态上(自然这就比外部混淆、十六进制加密要强些),就是用壳的apk,然后结合APK动态加载,让真实APK的资源文件是正确的,可以考虑动态代码生成(如动态生成真实APK的配置清单xml,而在asset文件夹下的APK压缩包中并没有配置清单xml),或者解密替换(这就分两种,你是保护xml\arsc文件,还是那些png等外部资源,当然原理一样,文件的加密解密)等。事实是,DEX和APK的动态加载往往必须要考虑资源怎么办的问题,解决方法往往是资源保护的一个反面,将动态加载需要的资源文件中数据先写在壳的资源文件中。毕竟,在代码与资源保护直接抉择,肯定是保护代码优先。
http://androidxref.com/
1. DEX文件优化与验证:
run_dexopt:
static const char* Dex_OPT_BIN = "/system/bin/dexopt"
// 读取和抽出dex,加上odex文件头,设置优化选项,可以看作DEX文件优化的主控函数
\dexopt\Optmain.cpp:extractAndProcessZip()
// 写入odex文件,可以说优化与验证工作的完成就是写入odex文件
\vm\analysis\DexPrepare.cpp:dvmContinueOptimization()
2.DEX文件解析:
\vm\RawDexFile.cpp:dvmRawDexFileOpen() // DEX文件解析的主控函数
\libdex\OptInvocation.cpp:dexOptGenerateCacheFileName() // 生成该dex对应odex文件
\vm\DvmDex.cpp:dvmDexFileOpenFromFd() // 完成对DEX文件映射,设置为只读文件,并进一步优化
\libdex\DexFile.cpp:dexFileParse() // 解析DEX
在实践中,我们发现并不是所有的dexfileopenpartial都能断下来,但 _Z27dexOptGenerateCacheFileNamePKcS0、dvmdexfileopenfromfd、dexfileparse 这些函数一定能断下来,而 dexfileopenpartial 常常在有壳时,也就是真正 dex 出现时才可以断下来,由此可知,其实 dalvik 很喜欢走的路就是用 odex,而不是 dex。dex 文件只有在 odex 文件不好用时才去用,这与 oat 文件一致。
3. DEX类加载
\vm\native\dalvik_system_DexFile.cpp:
Dalvik_dalvik_system_DexFile_defineClassNative // 类加载的主控函数
\vm\oo\Class.cpp:dvmDefineClass() // 确认类加载描述符进行类加载
\vm\oo\Class.cpp:findClaaNoInit() // 完成实际类加载工作
\vm\oo\Class.cpp:loadClassFromDex() // 返回值是一个ClassObject结构体
往后便是 FindClass、GetStaticMthodID、CallStaticMthod、dvmInterpretStd
begin.S:__linker_init
解析装载:
elf_reader.Load函数包括了下列函数:
ReadElfHeader() &&
VerifyElfHeader() &&
ReadProgramHeader() &&
ReserveAddressSpace() &&
LoadSegments() &&
FindPhdr();
所以, so的装载是一种解析式装载,这与dex有一定区别,dex是先加载进行优化验证生成odex,再去解析odex文件,
而so更像边解析边装载,在装载过程中主要解析是load段。
分配 soinfo:
soinfo_alloc函数,生成了一个soinfo结构
链接:
soinfo_link_image函数,也就是soinfo结构链接镜像,从而得到一个以后用的镜像文件
so中用于动态链接的结构就是.dynamic节,即动态节区
a.定位动态节
b.解析动态节
c.加载依赖so
d.重定位:
主要函数为soinfo_relocate,此函数会被soinfo_link_image函
数调用解析重定位项:.rel(.plt安卓并不用),重定位表
导入符号信息:.dynsym, 符号表修正需要重定位的地址
1. dump dex,脱壳。
2. 内存动态替换 dex,dex 自修改,自调用底层函数解析dex。
3. dex 自解析重构,完成 dex 重组脱壳。
1.在.init和.intarray下断,若.initarray节存在dex解密,dump dex,也就是dex脱壳。
2.dump so再修复,也就是so脱壳,一般选择在jni_onload。
3.因为在.init和.intarray运行之后,如果有jni_onload,就调用,变相断jni_onload。
4.自定义linker。
5.HOOK dlopen等函数,判断是否加载了so,如果加载就解密,同理在整个so的装载、链接过程中可以对so进行变形后的修复,so解密脱壳最迟要在jni_onload,当然混淆等基于内部保护的不在考虑之列。
DEX 文件结构变形
DEX 文件结构拼接隐藏
DEX 文件整体加密
ELF文件结构变形
so文件结构拼接隐藏
so文件反汇编注入
so文件整体加密、函数加密、区段加密、加壳
代码修改(广告植入、替换广告id)
资源修改(界面替换广告、链接替换)
破解(应用收费、内购)
篡改数据(无限金币、钻石)
加入恶意代码(木马、隐私、交易)
动态注入(数据拦截、窃取、修改)
本地数据修改、数据库文件修改
协议修改(服务器欺骗、向服务器发送假数据)
需要防止逆向分析(防逆向)--防止核心代码被反编译
防止二次打包(防篡改)--校验完整性、签名、防止盗版
防止调式和注入(防调试)--防止动态调式,注入获取关键数据
防止应用数据窃取(防窃取)--加密敏感数据
防止协议直接被盗刷--加密协议通信
360/娜迦/梆梆
爱加密/阿里
百度/腾讯/网秦/通付盾
类加载技术
针对apk中的classes.dex文件进行处理,放入特定的文件中,通过native代码来对其运行时解密
使用厂商(娜迦/爱加密/梆梆)
对原dex文件整体压缩加密,保存在壳代理的dex文件尾部,加载到内存中解密运行
使用厂商(360)
方法替换技术
将classes.dex文件中的方法代码进行提取,抽取方法,在运行时对其进行动态解密还原
使用厂商(娜迦/梆梆)
娜迦:libchaosvmp.so, libddog.so libfdog.so
梆梆:libsecexe.so, libsecmain.so, libSecShell.so
梆梆企业版:libDexHelper.so, libDexHelper-x86.so
爱加密:libexec.so, libexecmain.so, ijiami.dat
360:libprotectClass.so, libjiagu.so; libjiagu.so, libjiagu_art.so; libjiagu.so, libjiagu_x86.so
百度:libbaiduprotect.so
阿里聚安全:aliprotect.dat, libsgmain.so, libsgsecuritybody.so
腾讯:libtup.so, libexec.so, libshell.so; mix.dex; lib/armeabi/mix.dex, lib/armeabi/mixz.dex
腾讯御安全:libtosprotection.armeabi.so, libtosprotection.armeabi-v7a.so, libtosprotection.x86.so
通付盾:libegis.so, libNSaferOnly.so
网秦:libnqshield.so
网易易盾:libnesec.so
APKProtect:libAPKProtect.so
几维安全:libkwscmm.so, libkwscr.so, libkwslinker.so
顶像科技:libx3g.so
修改系统源码自动脱壳
通过hook方式对关键函数进行脱壳
开源工具如zjdroid,dexhunter进行脱壳
利用IDA或者GDB动态调式进行脱壳
分析 virualapp
去框架 HOOK
制作 vm 策略
实现 vm 置换
置换后销毁
其他
dex vmp (制作native oncreate)
典型非主流加固
脱壳机
虚拟机技术
proguard 等混淆、其他弱加密
DEX字符串加密
静态DEX文件整体加密解密
资源加密(xml与arsc文件加密及十六进制加密)
对抗反编译(添加垃圾类)
反调试
自定义DexClassLoader
DEX动态加载(分为利用jni和自定义jni即自定义底层函数)
DEX代码抽取到外部(类抽取加密按需解密和动态方法修改替换)
SO加密
Dex代码动态解密
SO代码膨胀混淆
jni反射抽取方法成本地原生代码即半vmp(分为jni反射解密DEX方 法和完全翻译为C代码)
初级vm即dex opcode vm指令虚拟使用jni指令还原(分为自定义后 端编译器和jni反射置换表)
高级vm即解释框架dex opcode不存在还原使用解释引擎的vm文件
内存Dump法
内存中寻找dex.035或者dey.036
/proc/xxx/maps中查找后,手动Dump
文件监视法
Dex优化生成odex
监视文件变化(inotifywait-for-Android)
监视DexOpt输出(notifywait-for-Android)
Hook法
Hook dvmDexFileOpenPartial
定制系统
修改安卓源码并刷机
内存重组法
Dex ZjDroid
对付一切内存中完整的dex,包括壳与动态加载的jar
SO elfrebuild
构造soinfo,然后对其进行重建
Hook法
针对无代码抽取且Hook dvmDexFileOpenPartial失败
Hook dexFileParse
针对无代码抽取且Hook dexFileParse失败
Hook memcmp
定制系统
修改安卓源码并刷机-针对无抽取代码
DexHunter
绕过三进程反调试-修改系统源码
断点mmap调试,针对Hook dexFileParse无效
dexopt优化时,dvmContinueOptimization()->mmap()
静态脱壳机
分析SO壳逻辑并还原加密算法
自定义linker脱SO壳
dex2oat
ART模式下,dex2oat生成oat时,内存中的DEX是完整的
定制系统
Hook Dalvik_dalvik_system_DexFile_defineClassNative
枚举所有DexClassDef,对所有的class,调用dvmDefineClass进行 强制加载
脱壳原理讲解
so + vmp
阶段发展(换个角度,要从解密后dex文件的状态来看)
静态:dex文件解密后以静态文件存储(如某一个文件夹下的odex文件)
动态:dex文件解密后也在内存中
整体加密:真实、完整的dex出现在内存中
抽取加密:真实、完整的dex从不出现在内存中
对于整体加密和抽取加密,从实现来看,整体加密就是把dex整体隐藏在so中,而抽取加密就是先从so里解密出一个待修复的dex文件,真正的dex从这个文件里抽取部分。
1.dex内存中不连续(如修改DexHeader结构)
2.dex在内存中执行是以结构体存在的,如果修复时只修复结构体,根本没有修复dex/odex文件本身(如只修复DexMethod结构体)
3.dex文件的一些方法不是事先解密,而是用到再解密,用完就删掉(如百度加固的onCreate方法)
4.dex文件在加载或运行时,被HOOK,解密后,使之一些结构体或一些结构体的一些字段人为改为错误,或删除(如阿里加固对于annotionoff注解字段的处理)
5.dex文件本身就是错的,结构体的字段值错误,或方法是干扰无效的,在加载或运行时,被HOOK,解密修复成正确的,解密后再删掉或继续用错误部分填充。
规律:无论加固还是脱壳,都往底层发展。
1.dex优化与验证过程(实例:在dexFileParse函数HOOK)
2.虚拟机动态加载打开dex过程
3.dex类加载的过程
1.打开dex文件时
存在dex优化验证时的打开和虚拟机本地native打开两种,分别对应着一代和二代脱壳。
2.加载类时:defineclassnative函数
虽然类加载过程的函数很多,但我们一般脱壳只在defineclassnative函数。
这里列举出类的整个加载过程的函数
defineclassnative函数分析与类加载概念
defineclass函数分析
findnoinit函数分析(包括了dvmLookupClass)
loadClassFromDEX函数分析
loadClassFromDEX0函数分析与解析概念
dvmLinkClass函数分析
loadMethodFromDEX函数分析
3.类的初始化时:dvmIsClassInitialized和dvmInitClass这两个函数
4.查找类FindClass
5.获得方法ID并调用方法 GetStaticMethodID,CallStaticVoidMethod
(其实在GetStaticMethodID执行了类的初始化,可以尝试从这里)
方法指令执行(一般不考虑)
DexHunter介绍
DexHunter使用方法与注意
DexHunter源码详细分析
/dalvik/vm/native/dalvik_system_DexFile.cpp
https://github.com/zyq8709/DexHunter
https://github.com/kesuki/DexHunter
如果你想脱壳一个APP,在运行APP之前,你需要把“dexname”这个文件推入到手机的“/data/”文件夹下。
在"dexname"的第一行是特征字符串(参照"slide.pptx").
第二行是目标APP所在的数据路径(例如,/data/data/com.example.seventyfour.tencenttest/)。
dexname文本必须是linux/Unix的风格形式,如果在windows下输入,则需要用winhex修改换行符的十六进制。
你可以使用"logcat"命令获得log日志来判断壳是否已经脱下。
一旦完成,生成的"whole.dex"文件就是想要的结果。
特征字符串:
360 /data/data/XXX/.jiagu/classes.dex
Ali /data/data/XXX/files/libmobisecy1.zip
Baidu /data/data/XXX/.1/classes.jar
Bangcle /data/data/XXX/.cache/classes.jar
Tencent /data/app/XXX-1.apk (/data/app/XXX-2.apk)
ijiami /data/data/XXX/cache/.
注意,他本身的系统 img 是4.4.3,如果想做到版本通用,只需要单单替换system.img就行。一定不能把他原版的img直接文件夹替换,那样模拟器会直接死翘翘。
相同点:
1.本质相同,也就是最根本的自解析重构思想和方法一样,都是主要采 用readsignleb128等自解析函数,进行自解析重构;
2.由于两种工具基本原理一样,所以需要升级,改进,和失效、缺陷的 地方,也是类似的;
3.都是类加载时的通用脱壳机,关键原理函数都是defineClassNative 函数。
不同点:
1.直接dump的方法不同,DexHunter用的是strcpy函数,而IDA脱 壳脚本用的是getword函数;
2.相比来言,IDA脱壳脚本重构的更为彻底一点,它把每个结构都进行 重构,把字段全部先初始化为0,然后利用直接dump或自解析重新获 取的办法填充回去;
3.IDA脱壳脚本得到的是一个ODEX,而DexHunter是一个DEX文 件,IDA脱壳脚本还重构了DEXHEADER头结构,修复了magic等字 段,这一点也比DexHunter较好。
4.使用的方式不同,DexHunter使用的方式是HOOK,而且对类进行了 主动一次性加载和初始化,而IDA脱壳脚本是一个脚本,其当初设计的 想法应该是当动态调试时,得到了正确的clazz结构体后,去使用。
5.总的来说,IDA脱壳脚本比DexHunter显得先进一点,但依然不是更 进一步彻底的重构,如annotionoff字段就是直接dump,但这个地方 往往会偏移出错,如阿里加固。
DexHunter 升级:
zj 是一个比较特殊的通用脱壳工具,是第一个利用系统组件漏洞来进行脱壳的脱壳机,也就是发广播,当zj接受到正确广播后,会根据pid,把广播发给pid,另pid进程返回信息。zj之所以特殊,它无法按照我说的划分为第几代脱壳,乃至现在依然可以风骚使用。这对于一个2014年就诞生的脱壳机而言是不可思议的。zj的核心是mcookie文件,而这个文件却介于解密后文件和解密后内存之间的中间状态。如果我们深入系统源码,就会发现cookie会被直接用于类结构体的生成,如果按照此定义,它应属于第2代-第3代脱壳机。但奇妙之处在于,只要存在解密,无论放不放在文件,也无论是解密的是dex还是jar、odex,理所应当都会有一个cookie出现。因此,除非真正的vmp,即完全通过编译器so壳去解释一个完全虚拟的文件或内存中的指令,否则zj都会有效果。zj很强大,但缺点也很明显,太容易被防。如果壳做好了系统组件保护策略,不允许第三方广播,或者对xpossed框架和类似zj插件进行保护,那么zj就无效。
zj 不适合模拟器,适合真机,我们的调式机就非常理想,不同机器,运行效率差别很大。
zj另外还可以:
内存 dump,由于我们有dd,和更好的工具,一般不用 zj 去 dump内存:
adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"dump_mem","start":1234567,"length":123}'
运行自编写lua脱壳插件,找到解密函数,用lua调用java:
由于这需要我们编写lua代码,还要分析解密函数,如果做到了这样,我们有更好的办法去处理,一般也不用运行lua代码
adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"invoke","filepath":"****"}'
做如下修改即可解除一定的防zj保护:
a.在设备上创建特定目录(如/data/local)并 chmod 为777
b.复制zjdroid.apk到该目录,并修改文件名为zjdroid.jar
c.修改/data/data/de.robv.android.xposed.installer/conf/modules.list,模块代码文件修改为zjdroid.jar,然后重启设备即可。
dd if=/dev/zero of=sun.txt bs=1 count=1
if 代表输入文件。如果不指定if,默认就会从stdin中读取输入,/dev/zero 是一个字符设备,会不断返回0值字节(\0)。
of 代表输出文件。如果不指定of,默认就会将stdout作为默认输出。
bs 代表字节为单位的块大小。
count 代表被复制的块数。
dex是看0x20处找大小,odex是看0x32处就有。
dd if=/proc/2175/mem of=/data/local/tmp/dump.dex skip=3077606936 bs=1 count=43576
adb push d:\gdbserver /data/local/tmp/gdbserver
adb shell
su
chmod 777 /data/local/tmp/gdbserver
ps | grep com.zyx.wifi
ls /proc/1964/task
/data/local/tmp/gdbserver :31928 --attach 2968
adb forward tcp:31928 tcp:31928
启动本地 gdb
target remote :31928
gcore,一直等待完成,时间比较久
b 下断点
c 继续运行到断点
s 等价于step into
n 等价于step over
bt 查看堆栈
info registers 查看寄存器内容
info b 查看断点信息
delete break [n] 删除编号为n的断点
disas 0xFFFF,+20 显示这个地址后面 20 行内的汇编指令
kill 终止进程
梆梆是把原dex文件加密放到了secData0.jar,所以直接拿到dex文件,修复配置文件的程序入口点就可以重打包完美运行。
open、mmap
dexfileopenpartial
dvmRawdexFileOpen
爱加密在fopen,fget原因
dexfileopenpartial
openDexFileNative
defineClassNative
1.识别360加固的代数
2.第一代360脱壳:xposed插件脱壳
3.第二代360脱壳:so手动dump并修复、mmap手动脱壳、open+memcmp手动脱壳
4.第三代360脱壳:drizzledumper脱壳、dex2oat脱壳
一个结构,
fix (){
Method
offset
}
结合dex文件结构,opcode,偏移地址
00-FF 0-255
确定00 vm的opcode
00-A8 168
01-A9
02-AA
03-AB
1.加密流程:
采用动态加载assets下的baiduprotect.jar。然后采用重写onCreate,用onCreate001代替,onCreate内容为修复onCreate001代码、执行onCreate001代码、清楚onCreate001代码。修复代码不能连续运行两次。
3.采用Hook DexParse来获取Dex相关数据,然后遍历ClassDef将所有onCreate001类直接解码。
4.Dump出修复好的Dex。
5.然而Dump的Dex还要修复(可以根据ClassDef自动修改)
Lcom/baidu/protect/A;->d(Ljava/lang/String;)V->解密方法
Lcom/baidu/protect/A;->e(Ljava/lang/String;)V->加密方法
Lcom/qsq/qianshengqian/XXXXX;->
onCreate001(Landroid/os/Bundle;)V->加解密传入参数
xdex脱壳机与骗出jni_onload的oncreate抽取指令
vm一维置换表和二维置换表
libart.so
OpenMemory
R1是dex文件的地址,R2是dex文件的大小