From:https://zhuanlan.zhihu.com/p/95915254
知乎:Android 逆向分析学习路线?:https://www.zhihu.com/question/25626303
入门篇 --- 学习Android安全和逆向开发的路线总结:https://www.52pojie.cn/thread-1065039-1-1.html
路线 1
0. 安卓逆向基础(建议1周)
- 1. 学习安卓逆向第一步必须先把环境搭建好,这是你学习安卓逆向的开始,环境搭建好后表示正式迈入安卓逆向。在环境安装的工程中会遇到很多细节上的问题。
- 2. 第二步就是要了解我们要分析的是什么文件,很多0基础的同学都不知道安卓逆向分析的什么文件。我们要分析的是应用程序或者安装包(就是.apk文件),了解 apk 是怎么生成的,以及如何安装到我们的手机里面,apk是怎么运行的,也是我们探讨的内容。
- 3. 第三步如何逆向分析 .apk 文件,掌握 apk 反编译 以及 回编译,完成这个操作使用的工具是 apktool。
一. Java 层逆向(建议3周)
- 1. 掌握 Java 语法基础,达到能看懂 Java 代码。
- 2. 了解 smali 语法,能看懂 smali 代码。
- 3. 掌握 逆向分析 apk 中常用的方法和技巧。
二. Native 层逆向(建议4周)
- 1. 了解 安卓操作系统 和 四大组件。
- 2. 了解 NDK 开发流程,自己编写案例练习。
- 3. 掌握常用 ARM 汇编指令,达到能看懂 ARM 汇编指令。
- 4. 掌握 ida工具的使用,熟练使用 ida 进行各种操作
三. APK 保护策略(建议1周)
- 1. 了解 Java 代码混淆、资源混淆
- 2. 掌握签名验证、文件校验、模拟器检测
- 3. 本地验证、网络验证
- 4. 案例练习
四. 反调试 与 反-反调试(建议1周)
- 1. 掌握常用 反调试方法 及 过反调试 技巧,比如关键文件检测、调试端口检测、进程名称检测、防附加、轮训检测 TracerPid 值、时间检测、信号检测等反调试。
- 2. 掌握 IDA 过反调试 思路
- 3. 案例练习。
五. HOOK 框架(建议2周)
- 1. 掌握 HOOK 插件开发。
- 2. 掌握 Xposed、Substrate、Fridad 等框架。
- 3. 案例练习。
六. 常见加密算法(建议2周)
- 1. 掌握编码算法、消息摘要算法、对称加密算法(Java加密与解密的艺术)。
- 2. 掌握非对称加密算法、数字签名算法。
七. 协议加解密分析(建议4周)
- 1. 了解客户端与服务器如何进行交互的(OSI模型、TCP/IP模型)。
- 2. 掌握常用的抓包工具及环境配置,HTTP协议与HTTPS安全协议,数字证书、SSL证书检测(计算机网络与通信、信息安全工程师)。
- 3. 案例练习。
八. 文件结构(建议2周)
- 1. 掌握 DEX、ELF、XML、ARSC 等文件结构。
- 2. 自编写文件解析工具。
九. 系统源码分析(建议2周)
- 1. 了解安卓操作系统启动流程、Zygote 启动流程。
- 2. 掌握 Dalvik虚拟机、ART虚拟机、SO 加载流程。
十. 加固与脱壳(建议4周)
- 1. 了解 dex 文件整体加密、Dex 代码抽取加密。
- 2. 了解 so 文件整体加密、函数加密、区段加密、加壳、混淆。
- 3. 分析通用脱壳机的实现原理及应用场景。
- 4. 了解主流加固特点及对应的脱壳技巧。
十一. 按键 + 内存(建议2周)
- 1. 环境搭建,搜索内存数据,对内存数据进行读写操作。
- 2. 市面上的模拟器辅助,有一部分就是通过搜索内存数据来找特征码,因为游戏中有的数据是不会发生变化的,我们选择这部分不变的数据作为特征码。通过特征码来搜索内存数据,找到特征码的地址,再通过特征码的地址+距离(偏移)来实现定位。
- 3. 案例练习。
十二. 篡改内存数据 + 注入技术 + HOOK技术(建议4周)
- 1. 掌握模块基地址获取、非注入式篡改数据、注入式篡改内存数据。
- 2. 掌握注入技术原理( Ptrace注入、Zygote注入、静态感染ELF文件注入)。
- 3. 掌握 HOOK 技术的实质,就是对函数进行重写( Inline HOOK、异常HOOK、导入表HOOK)。
- 4. 掌握 C++ 游戏逆向分析技巧。
- 5. 案例练习。
十三. Lua 游戏(建议4周)
- 1. 掌握 Lua 游戏逆向分析流程。
第一步:查看 lib 文件夹的 so 文件就可以确定该游戏是不是 Lua 游戏,
第二步:如果是 Lua 游戏就在 assets 文件夹下查找 lua 脚本。 - 2. Lua 游戏功能实现都在 Lua 脚本,重点分析 Lua 脚本(Lua明文、LuaC、Luajit)。
- 3. Lua 文件加密与加密,内存 dump Lua 脚本,HOOK 插件开发。
- 4. Cocos2dx-Lua 引擎源码分析
- 5. 案例练习
十四. Unity 3D 游戏(建议4周)
- 1. 掌握 Unity 3D 游戏逆向分析流程。
第一步:查看 lib文件下的 so 文件就可以确定该游戏是不是 Unity 3D 游戏,
第二步:如果是 Unity 3D 游戏,就在 assets 文件下查找相应的文件。 - 2. Unity 3D 有两种框架(MONO框架、IL2CPP框架)。
MONO框架对应的游戏逻辑实现在 dll 文件,
IL2CPP框架对应的游戏逻辑实现在 libil2cpp.so 文件。 - 3. Unity 3D 引擎源码分析。
- 4. DLL 文件处理,DLL混淆,DLL隐藏,DLL加密。
- 5. 内存dump dll文件,HOOK插件开发,注入+HOOK。
- 6. 案例练习
十五. 游戏协议(建议2周)
- 1. WPE环境搭建,拦截发送包和接收包,多截包对比分析封包数据。
- 2. 分析喊话功能,找出加密规律,各种游戏功能封包拦截分析。
- 3. 案例练习。
十六. 学习方法
- 1. 看一遍教程后自己实战操作,养成做笔记的习惯。
- 2. 注重基础,一定要把 Java层 和 Native层 搞懂。
- 3. 学习中不要纠结细节,要学会抓大放小。
- 4. 学完后自己画脑图,回顾学习的过程中那些不理解在花时间去看,此过程就是查漏补缺。
安卓逆向 大纲
Android 逆向
常用工具:
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-buildAndroid 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 文件:
APK 是 Android Package 的缩写,即 Android安装包。APK是类似 Symbian Sis 或 Sisx 的文件格式。通过将 APK 文件直接传到Android模拟器 或 Android手机 中执行即可安装。
APK 文件目录:
assets 不经过 aapt 编译的资源文件
lib .so文件
META-INF 文件摘要,摘要加密和签名证书文件目录CERT.RSA 公钥和加密算法描述CERT.SF 加密文件,它是使用私钥对摘要明文加密后得到的密文信息,只有使用私钥配对的公钥才能解密该文件MANIFEST.MF 程序清单文件,它包含包中所有文件的摘要明文
res 资源文件目录,二进制格式drawable 图片layout 布局menu 菜单
resources.arsc 经过 aapt 编译过的资源文件
classes.dex 可执行文件
AndroidManifest.xml 配置文件
APK 打包流程:
打包资源(res/assets/AndroidManifest.xml/Android基础类库)文件,生成 R.java和resources.ap_文件处理AIDL文件,生成对应的.java文件编译Java文件,生成对应的.class文件把.class文件转化成Davik VM支持的.dex文件(.java=>.class=>.dex)打包生成未签名的.apk文件对未签名.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版本及以上
Dalvik 字节码:
1.了解dalvik寄存器dalvik中的寄存器都是32位2.寄存器之v命名法与p命名法参数寄存器 P0-Pn 局部变量寄存器 V0-Vn3.dex文件反汇编工具smali.jar\ddx.jar4.类型smali ==> JavaV voidZ booleanB byteC charS shortI intJ longF floatD doubleL java类[ 数组5.字段Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;6.方法Lpackage/name/ObjectName;->MethodName (III) Z
Dalvik 指令:
基础字节码-名称后缀/字节码后缀 目的寄存器 源寄存器名称后缀是 wide,表示数据宽度为64位字节码后缀是 from16,表示源寄存器为16位空操作指令 nop数据操作指令move(-wide/-object/-result/-result-object/-result-wide/-exception)move(-wide/-object)/from16move(-object)/16返回指令(重点)return-void(表示方法的放回值为空)return(表示方法的反回值为32位非对象类型的值)return-wide(表示方法的反回值为64位非对象类型的值)return-object(表示方法的反回值为对象类型的值)数据定义指令(重点)const(-wide/-string/-class)const/4const(-wide)/16const(-wide)/32const(-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文件 ==> 反编译apk(dex/配置文件/资源文件(apk反编译败))
==> 修改关键文件实现自己的目的 ==> 重新打包签名(无法重新打包) ==> apk安装后无法运行。apktool .dex=>.smali dex2jar .dex=>.jarApktool工具实际上只反编译以下三种类型文件:.xml文件、.dex文件、.arsc文件去除广告和弹窗,撇开不存在于smali的这种情况,容易的就是可以在XML中寻到Activity,难的就是寻不到,发生这种情况时,就要分析代码,程序逻辑,抓住关键信息,界面和函数。注意:当字符串等关键信息搜不到时,可以从三个方向考虑:1. 字符串在so层;2. 字符串被加密了3. 结合了服务器,服务器返回,本地显示。
smali 文件:
无论是普通类、抽象类、接口类或者内部类,在反编译出的代码中,它们都以单独的 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 method5. 虚方法的声明与直接方法相同,只是起始处的注释为“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分析程序的执行流程需重点关注的 applicationapplication 执行时间授权验证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_SMSandroid.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,Activityresult4399notifyDeliverGoods 内购技巧从静态代码分析程序逻辑,找到修改点程序逻辑分析=>最重要的又是流程分析/流程图=>核心(层次)关键性信息查找=>静态(整体分析与局部分析)/动态
Android NDK:
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)systemgabi++(rtti)=>gabi++_static/gabi_sharedstlport(rtti/stl)=>stlport_static/stlport_sharedgnustl(c++异常/rtti/stl)=>gnustl_static/gnustl_shared4. Android NDK JNI API逆向分析Android NDK提供了那些函数Linux C/C++Android NDK<=>JNI接口<=>javaJNINativeInterfasce(jni本地接口)JNIInvokeInterface(jni调用接口)移植/保护核心代码如何静态分析Android NDK程序file=>load file=>parse c header file=>jni.h=>structures
Android 与 ARM 处理器:
ARM 处理器架构概述
ARM 处理器家族
Android 支持的处理器架构:ARM / x86 / MIPS
原生程序 与 ARM汇编语言:
原生程序逆向初步
代码混淆技术
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汇编语言程序结构:
完整的 ARM 汇编程序
处理器架构定义
段定义
注释与标号
@ => 单行注释
/**/ => 多行注释
汇编器指令
子程序 与 参数传递
R0 - R3 这4个寄存器用来传递函数调用的第1到第4个参数,超出的参数通过堆栈来传递
R0 寄存器 同时用来存放函数调用的 返回值
被调用的函数在返回前无需恢复这些寄存器的内容
ARM 指令集:
- 立即寻址 (mov r0,#0x123)
- 寄存器寻址 (mov r0,r1)
- 寄存器移位寻址 (mov r0,r1,lsl #0x2)
LSL ( 逻辑左移 )
LSR ( 逻辑右移 )
ASR ( 算术右移 )
ROR ( 循环右移 )
RRX ( 带扩展的循环右移 ) - 寄存器间接寻址 (ldr r0,[r1])
- 基址寻址 (ldr r0,[r1,#-4])
- 多寄存器寻址 (ldmia r0,{r1,r2,r3,r4})
- 堆栈寻址 (stmfd sp!,{r1-r7,lr}(入栈保护) / ldmfd sp,{r1-r7,lr}(出栈恢复))
ldmfa / stmfa
ldmea / stmea
ldmfd / stmfd
ldmed / stmed - 块拷贝寻址
ldmia / stmia
ldmda / stmda
ldmib / stmib
ldmdb / stmdb - 相对寻址:相对寻址以程序计算器 pc 的当前值作为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数有效地址ARM与Thum指令集
指令格式
<opcode>:指令助记符
{<cond>}:执行条件
{s}:是否影响cpsr寄存器的值
{.w}:指令宽度说明符.w表示是32位
{.n}:指令宽度说明符.n表示16位
<rd>:目的寄存器
,<rn>:第一个操作数寄存器
{,<perand2>}:第二个操作数:立即数、寄存器、寄存器移位
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 ( 写状态寄存器指令 )
ARM 指令机器码:
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 Pro 工具介绍:
交互式反汇编器,是典型的递归下降反汇编器。
导航条
蓝色 :表示常规的指令函数
黑色 :节与节之间的间隙
银白色 :数据内容
粉色 :表示外部导入符号
暗黄色: 表示 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 : 搜索十六进制
IDA 静态分析:
导入 jni.h 分析 jni 库函数
拷贝 伪C代码 到 反汇编 窗口:右键 => copy to assembly # 把伪 c代码复制到反汇编窗口的汇编代码。
IDA 可以修改 so 的 hex 来修改 so,edit => edit-patchrogram
在这里建议使用 winhex 来实现。
IDA 动态调试:
IDA 调式界面
- 1. 反汇编窗口
C 将当前地址处的 数据 解析成 代码
P 将当前地址处的 数据 解析成 函数 - 2. 十六进制窗口。编辑内存数据和代码
- 3. 寄存器窗口。修改寄存器的值
- 4. 模块窗口。模块路径和地址
- 5. 线程窗口
- 6. 栈窗口
- 7. 输出信息窗口
IDA 调式常用功能
- 1.断点和运行。
设置断点 F2
设置断点不可用 Disable breakpoint
编辑断点 Edit breakpoint
删除断点 Delete breakpoint
继续运行 F9
查看当前所有断点 Ctrl+Alt+B - 2.单步调式。
单步步入 F7
单步步过 F8
运行到函数的返回地址 Ctrl+F7
运行到光标处 F4 - 3. IDC 脚本
static main(void)
{
auto fp, dexAddress, end, size;
dexAddress = 0x77607640;
size = 0x19E118;
end = dexAddress + size;
fp = fopen("D:\\classes.dex", "wb");
for ( ; dexAddress < end; dexAddress++ )
fputc(Byte(dexAddress), fp);
} - 4. 修改内存数据。
- 5. 修改寄存器。修改PC寄存器时,最好在跳转指令改,如果对程序流程掌握很好,可以随便PC,只要程序不出错
- 7. NOP函数或代码。
NOP函数MOV R0,R0(00 00 A0 E1/00 1C)
清空指令(00 00 00 00/00 00)
函数头直接返回mov pc,lr(0E F0 A0 E1/F7 46) - 8.改变执行流程。
修改寄存器的值
修改跳转指令
注意:修改内存 和 修改代码 的时机选择不同,
- 因为修改内存和寄存器,必须调试到某一处,才能让寄存器和内存是想要的值,此时修改数据的值一般对程序影响不大,
- 而修改代码就不同了,如果已经走到了那一句代码,才去修改代码,是要报错的,提前在它运行到前面1-2条指令时修改代码。原因是PC指令预读三级流 水线:
1. 读取指令
2. 解析指令
3. 执行指令
ADD R6,PC,R6 =>R6=PC+R6=847C+1840=9CBC+8? 9CC4
JNI_OnLoad 下断方法技巧
so 内寻找
基址 + 偏移
dvmloadnativecode 函数
dlsym(handle,"jni_onload")
.init 和 .init_array 下断技巧
linker 大法,先勾选三项,F9运行,再执行jdb,当目标so被加载时通过基址+偏移下段
APK 保护策略:
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 导出文件
Dex文件结构:
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
ELF文件结构:
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节头表描述的是节区的信息
动态用段,静态用节
readelf 的使用:
-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的动态加载往往必须要考虑资源怎么办的问题,解决方法往往是资源保护的一个反面,将动态加载需要的资源文件中数据先写在壳的资源文件中。毕竟,在代码与资源保护直接抉择,肯定是保护代码优先。
DEX类加载过程解析:
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
so 的加载、链接、执行过程解析:
begin.S:__linker_init
- 1.dlopen函数。dlfcn.cpp
- 2.整体流程:
linker.cpp
do_dlopen函数:
find_library函数:完成对so的解析装载、分配、链接
CallConstructors函数:完成对so中.init和.initarray的执行 - 3.先看 CallConstructors函数,从后面的执行来看
CallFunction("DT_INIT", init_func);
CallArray("DT_INIT_ARRAY", init_array, init_array_count, false); - 4.回到 find_library 函数,看 so 的具体加载
find_library_interna
load_library 是整个 so 加载的主控函数
5. so 加载 的 详细步骤
解析装载:
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, 符号表修正需要重定位的地址
DEX 类加载过程在加固脱壳应用:
1. dump dex,脱壳。
2. 内存动态替换 dex,dex 自修改,自调用底层函数解析dex。
3. dex 自解析重构,完成 dex 重组脱壳。
Linker 在加固脱壳应用:
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 文件结构拼接隐藏
DEX 文件整体加密
ELF 文件加固:
ELF文件结构变形
so文件结构拼接隐藏
so文件反汇编注入
so文件整体加密、函数加密、区段加密、加壳
Apk保护:
1.app 存在的危险
代码修改(广告植入、替换广告id)
资源修改(界面替换广告、链接替换)
破解(应用收费、内购)
篡改数据(无限金币、钻石)
加入恶意代码(木马、隐私、交易)
动态注入(数据拦截、窃取、修改)
本地数据修改、数据库文件修改
协议修改(服务器欺骗、向服务器发送假数据)
2.加固目的
需要防止逆向分析(防逆向)--防止核心代码被反编译
防止二次打包(防篡改)--校验完整性、签名、防止盗版
防止调式和注入(防调试)--防止动态调式,注入获取关键数据
防止应用数据窃取(防窃取)--加密敏感数据
防止协议直接被盗刷--加密协议通信
3.常见加固厂商
360/娜迦/梆梆
爱加密/阿里
百度/腾讯/网秦/通付盾
4.常用加固方式
类加载技术
针对apk中的classes.dex文件进行处理,放入特定的文件中,通过native代码来对其运行时解密
使用厂商(娜迦/爱加密/梆梆)
对原dex文件整体压缩加密,保存在壳代理的dex文件尾部,加载到内存中解密运行
使用厂商(360)
方法替换技术
将classes.dex文件中的方法代码进行提取,抽取方法,在运行时对其进行动态解密还原
使用厂商(娜迦/梆梆)
5.加固厂商特征
娜迦: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
6. 脱壳手法
修改系统源码自动脱壳
通过hook方式对关键函数进行脱壳
开源工具如zjdroid,dexhunter进行脱壳
利用IDA或者GDB动态调式进行脱壳
分析 virualapp
去框架 HOOK
制作 vm 策略
实现 vm 置换
置换后销毁
其他
dex vmp (制作native oncreate)
典型非主流加固
脱壳机
虚拟机技术
加固技术:
第一代加固 DEX 加密
proguard 等混淆、其他弱加密
DEX字符串加密
静态DEX文件整体加密解密
资源加密(xml与arsc文件加密及十六进制加密)
对抗反编译(添加垃圾类)
反调试
自定义DexClassLoader
第二代加固 DEX抽取与SO加固
DEX动态加载(分为利用jni和自定义jni即自定义底层函数)
DEX代码抽取到外部(类抽取加密按需解密和动态方法修改替换)
SO加密
第三代加固 DEX动态解密与SO混淆
Dex代码动态解密
SO代码膨胀混淆
第四代加固 arm vmp
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进行 强制加载
开源脱壳机参考( 1 之 3 代 壳 )
- 一代壳:ZjDroid(2014)。最早一批的通用脱壳工具,利用Xposed注入,实时对内存dex文件进行baksmali与smali。自带各类辅助性功能,代码写得很好,值得一看。
https://bbs.pediy.com/thread-190494.htm
https://github.com/halfkiss/ZjDroid.git - 二代壳:DexHunter(2015)。首次提出的通过修改系统镜像实现的针对二代壳脱壳功能,自带主动类加载功能,在当时有效对抗各类加固。从这代开始,脱壳机开始涉及到Android源码修改了,研发难度有所上升。
https://bbs.pediy.com/thread-203776.htm
https://github.com/zyq8709/DexHunter.git - 三代壳:FUPK3(2018)。dexhunter 的改进版,首次把脱壳的颗粒度上升到函数维度,配合主动函数调用,能破掉大部分壳的函数抽取功能。
https://bbs.pediy.com/thread-246117.htm
https://github.com/F8LEFT/FUPK3 - FART(2019)。脱壳思路类似于Fupk3的通用脱壳机,目标运行平台修改为art。在脱壳上,art虚拟机要比dalvik虚拟机简单得多,所以该版本能更有效的进行脱壳。可惜的是作者没有提供指令回填程序。
https://bbs.pediy.com/thread-252630.htm
https://github.com/hanbinglengyue/FART - Youpk(2020)。新出的脱壳机,实现上更为成熟。在 FART上更进一步发展,主动函数调用回调进一步深入到解析引擎上,可以控制函数执行的流程,并附带了一键化指令回填。到此为止,基本上终结了三代壳的生命。( https://bbs.pediy.com/thread-259854.htm )
脱壳原理讲解
- apk格式
- dex文件格式(代码文件)
- 系统代码加载流程
- 类抽取脱壳思路
- 参考链接:https://bbs.pediy.com/thread-260052.htm
脱第四代壳
so + vmp
阶段发展(换个角度,要从解密后dex文件的状态来看)
静态:dex文件解密后以静态文件存储(如某一个文件夹下的odex文件)
动态:dex文件解密后也在内存中
整体加密:真实、完整的dex出现在内存中
抽取加密:真实、完整的dex从不出现在内存中
对于整体加密和抽取加密,从实现来看,整体加密就是把dex整体隐藏在so中,而抽取加密就是先从so里解密出一个待修复的dex文件,真正的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类加载的过程
脱壳时机总结(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执行了类的初始化,可以尝试从这里)
方法指令执行(一般不考虑)
DEX自解析重构技术:
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直接文件夹替换,那样模拟器会直接死翘翘。
IDA 自动插件源码分析与 dexhunter 比较:
相同点:
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 升级:
- 它是用DEXHEADER结构的header.stasticsize等来获取静态字段数 量,然而我们初始化类后,只能保证clazz结构是正确的,我们应该尽 量用clazz结构中的内容。
- 对于百度加固来说,由于真实指令只在运行时才出现,此时重构过早。 试想当classdef都是不完整情况下,得到的data数据区肯定也是缺失 的,因为data数据是代码的真实数据区。DEXHUNTER采用直接 dump data区明显是不行的,我们先把oncreate001中指令骗出来, 再去dump data,接着重构。
- DEXHUNTER采取以修复为主的重构,明显是不彻底的,其可以看到在 dump时,DexClassDefItem结构中的anonotionoff等偏移也存在错 误,我们要利用重构的办法对这个地方也进行重构。
脱壳神器 ZjDroid:
1.介绍
zj 是一个比较特殊的通用脱壳工具,是第一个利用系统组件漏洞来进行脱壳的脱壳机,也就是发广播,当zj接受到正确广播后,会根据pid,把广播发给pid,另pid进程返回信息。zj之所以特殊,它无法按照我说的划分为第几代脱壳,乃至现在依然可以风骚使用。这对于一个2014年就诞生的脱壳机而言是不可思议的。zj的核心是mcookie文件,而这个文件却介于解密后文件和解密后内存之间的中间状态。如果我们深入系统源码,就会发现cookie会被直接用于类结构体的生成,如果按照此定义,它应属于第2代-第3代脱壳机。但奇妙之处在于,只要存在解密,无论放不放在文件,也无论是解密的是dex还是jar、odex,理所应当都会有一个cookie出现。因此,除非真正的vmp,即完全通过编译器so壳去解释一个完全虚拟的文件或内存中的指令,否则zj都会有效果。zj很强大,但缺点也很明显,太容易被防。如果壳做好了系统组件保护策略,不允许第三方广播,或者对xpossed框架和类似zj插件进行保护,那么zj就无效。
2.zj 使用全攻略:
zj 不适合模拟器,适合真机,我们的调式机就非常理想,不同机器,运行效率差别很大。
- 安装 xpossed 框架:
adb install d:\xpossed.apk
安装框架,软重启 - 安装zj插件:
adb install d:\ZjDroid.apk
安装插件,勾选模块,软重启 - 安装目标游戏:adb install d:\捕鱼达人3.apk
- 打开一个 cmd,专门用于查看动态逆向信息:
adb shell
logcat | grep zjdroid-shell-org.cocos2d.fishingjoy3
记下PID,建议一定要把命令先写好 - 再打开一个cmd,运行 zj 命令
adb shell
su
查看内存中存在的 dex 的 cookie 信息:
am broadcast -a com.zjdroid.invoke --ei target 4755 --es cmd '{action:dump_dexinfo}'
观察log的cmd窗口,记下选择的dexpath信息,如果有dex就选dex,其次是jar,最后是apk
查看cookie的可加载类,顺便看看是否有自己想要的类:
am broadcast -a com.zjdroid.invoke --ei target 4755 --es cmd '{"action":"dump_class","dexpath":"/data/app/org.cocos2d.fishingjoy3-1.apk"}' - dump 出 dex 文件:am broadcast -a com.zjdroid.invoke --ei target 4755 --es cmd '{"action":"dump_dexfile","dexpath":"/data/app/org.cocos2d.fishingjoy3-1.apk"}'
- dump 出 smali 文件:am broadcast -a com.zjdroid.invoke --ei target 4755 --es cmd '{action:backsmali, "dexpath":"/data/app/org.cocos2d.fishingjoy3-1.apk"}'
- 等待一段时间,查看脱壳后的dex和smali文件:
ll /data/data/org.cocos2d.fishingjoy3/files
ll /data/data/org.cocos2d.fishingjoy3/files/smali/
把 smali 文件夹拷贝到sd卡,然后传到电脑:
adb pull /mnt/sdcard/smali/ d:\1
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":"****"}'
3. zj 拓展
做如下修改即可解除一定的防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大法:
dd if=/dev/zero of=sun.txt bs=1 count=1
if 代表输入文件。如果不指定if,默认就会从stdin中读取输入,/dev/zero 是一个字符设备,会不断返回0值字节(\0)。
of 代表输出文件。如果不指定of,默认就会将stdout作为默认输出。
bs 代表字节为单位的块大小。
count 代表被复制的块数。
抠 DEX:
dex是看0x20处找大小,odex是看0x32处就有。
dd if=/proc/2175/mem of=/data/local/tmp/dump.dex skip=3077606936 bs=1 count=43576
GDB 调试:
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
脱掉360的壳:
1.识别360加固的代数
2.第一代360脱壳:xposed插件脱壳
3.第二代360脱壳:so手动dump并修复、mmap手动脱壳、open+memcmp手动脱壳
4.第三代360脱壳:drizzledumper脱壳、dex2oat脱壳
VM 修复:
一个结构,
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文件的大小