一、课程目标
1.了解unicorn与unidbg
2.源码学习unidbg的常用api
3.了解unidbg_hook
4.了解unidbg_patch
二、工具
1.教程Demo
2.IDEA
3.IDA
三、课程内容
一.unicorn&unidbg
1.什么是unicorn
开源地址
Unicorn 是一个由新加坡南洋理工大学团队在2015年开源的CPU模拟器框架,它支持多种架构,包括X86/X64/ARM/ARM64/MIPS等。Unicorn 的主要特点是:
- 多架构支持:它能够模拟不同架构的CPU指令,这使得它在跨平台分析和测试中非常有用。
- 高性能:Unicorn 通过使用JIT(Just-In-Time)编译技术,将模拟的指令翻译成宿主机的本地指令,从而提高了执行效率。
- 丰富的接口:Unicorn 提供了多种语言的绑定,包括Python、Java、C#等,使得在不同编程环境中都能方便地使用。
- Hook和拦截功能:Unicorn 允许用户在模拟执行过程中设置Hook,拦截和处理特定的指令或内存访问,这对于逆向工程和动态分析非常有用。
- 专注于CPU模拟:与一些既模拟CPU又模拟操作系统的模拟器不同,Unicorn 专注于CPU指令的模拟,提供了简单的Hook接口、内存操作接口和指令执行接口,而不涉及操作系统层面的模拟。
2.什么是unidbg?
开源地址
Unidbg(Unicorn Debugger)是一个开源的轻量级模拟器,主要设计用于模拟执行Android平台上的Native代码。它由凯神在2019年开源,基于Maven构建,使用Java语言编写,可以在IDE中打开和运行。Unidbg能够模拟Android Native函数的执行,让逆向工程师和安全研究人员能够分析和理解二进制文件的运行行为。它支持模拟系统调用和JNI调用,使得可以在模拟环境中执行依赖这些调用的代码。Unidbg基于Unicorn项目,Unidbg的优势在于它提供了一种隐蔽的监控手段,可以模拟复杂的Native环境,帮助用户进行深入的动态分析。由于其开源特性,Unidbg得到了社区的广泛支持和持续更新,成为了Android Native逆向分析领域中一个强有力的工具。
竞争者: AndroidNativeEmu 和继任者 ExAndroidNativeEmu (Unidbg优点:模拟实现了更多的系统调用和 JNI)
3.unidbg的使用场景与优缺点
二.初试unidbg
1.配置资源文件
1.下载idea
社区版下载链接
2.下载源代码,并用idea打开,并配置好sdk
3.文件结构解析:
├── README.md # 项目介绍和使用指南
├── LICENSE # 开源许可证文件
├── .gitignore # Git 忽略文件配置
├── pom.xml # Maven 配置文件,定义了项目的依赖和构建配置
├── mvnw # 脚本文件,用于 Maven Wrapper (Linux/Mac)
├── mvnw.cmd # 脚本文件,用于 Maven Wrapper (Windows)
├── test.sh # 测试脚本 (Linux/Mac)
├── test.cmd # 测试脚本 (Windows)
├── .mvn/ # Maven 配置目录
│ └── wrapper/ # Maven Wrapper 相关配置
├── assets/ # 存放模拟过程中使用的资源文件
│ ├── *.dll # Windows 动态链接库文件
│ └── *.so # Linux/Android 动态链接库文件
├── backend/ # 后端逻辑实现,包含核心模拟功能
├── unidbg-api/ # 核心接口和抽象类模块
│ └── src/ # API 模块的源代码目录
├── unidbg-ios/ # iOS 应用模拟模块
│ └── src/ # iOS 模拟模块的源代码目录
├── unidbg-android/ # Android 应用模拟模块
│ ├── pom.xml # Maven 构建文件
│ ├── pull.sh # 拉取 Android 模拟所需依赖文件的脚本
│ └── src/ # unidbg-android 模块的源代码目录
│ ├── main/
│ │ ├── java/ # 核心 Java 源代码
│ │ │ └── com/github/unidbg/ # 包含核心模拟器、文件系统、虚拟机组件
│ │ │ └── net/fornwall/jelf # ELF 文件格式解析实现
│ │ └── resources/ # 资源文件,封装了 JNI 库、Android 系统库等
│ ├── test/
│ │ ├── java/ # 单元测试代码
│ │ ├── native/android/ # 测试 Android 原生库的 C/C++ 源代码
│ │ └── resources/ # 测试资源文件,包含预编译的二进制文件(log4j.properties这个是日志相关配置,可以对open,syscall这类的系统调用进行trace)
└── .mvn/ # Maven Wrapper 相关配置目录
log4j.logger.com.github.unidbg.linux.file=DEBUG //把INFO改成DEBUG
2.项目式学习
package com.kanxue.test2;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.ProxyDvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class MainActivity {
private final AndroidEmulator emulator; // 定义Android模拟器实例
private final VM vm; // 定义Dalvik虚拟机实例
public MainActivity() {
// 创建32位Android模拟器实例,使用Dynarmic后端
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
// 获取模拟器的内存管理接口
Memory memory = emulator.getMemory();
// 设置系统类库
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
// 创建Dalvik虚拟机实例
vm = emulator.createDalvikVM();
// 设置是否输出详细的JNI调用日志
vm.setVerbose(false);
// 加载指定路径的SO库文件,不自动调用JNI_OnLoad函数
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"), false);
// 手动调用JNI_OnLoad方法
dm.callJNI_OnLoad(emulator);
}
public void crack() {
// 创建一个vm对象,模拟Java层的对象传递给JNI层
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
// 记录开始时间
long start = System.currentTimeMillis();
// 遍历所有可能的三字符组合,尝试破解
for (char a : LETTERS) {
for (char b : LETTERS) {
for (char c : LETTERS) {
String str = "" + a + b + c;
// 调用JNI方法,传入当前组合,判断是否成功
boolean success = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str);
if (success) {
// 如果成功,输出结果并结束
System.out.println("Found: " + str + ", off=" + (System.currentTimeMillis() - start) + "ms");
return;
}
}
}
}
}
private static final char[] LETTERS = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
}; // 定义字母表
public static void main(String[] args) {
//记录开始时间
long start = System.currentTimeMillis();
// 创建MainActivity实例
MainActivity main = new MainActivity();
//输出调用结果
System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
// 调用破解方法
main.crack();
}
}
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
- 1.for32Bit()的意思是创建32位Android模拟器实例,for64Bit()则是创建64位,apk lib 里只有armeabi-v7a,那就只能选择 32 位,apk lib 里只有arm64-v8a,就选择 64 位。区别:64位的执行速度较快,浮动10%左右;Unidbg 对 ARM32 的支持和完善程度高于 ARM64
- 2.Unidbg 支持了数个后端,目前共五个 Backend,分别是 Unicorn、Unicorn2、Dynarmic(执行速度较快)、Hypervisor、KVM。new DynarmicFactory(true)中的true,标志着在出现异常时是否使用默认后端unicorn。
.setRootDir() //设置虚拟机的根目录,可以实现io重定向,例如:app读取/data/data/123.txt,rootDir设置为E:/unidbg,那么真正的目录是E:/unidbg/data/data/123.txt,
1.emulator常用Api
getMemory()
Memory
getPid()
int
createDalvikVM()
VM
createDalvikVM(File apkFile)
VM
getDalvikVM()
VM
showRegs()
void
getBackend()
Backend
getProcessName()
String
getContext()
RegisterContext
traceRead(long begin, long end)
void
traceWrite(long begin, long end)
void
traceCode(long begin, long end)
void
isRunning()
boolean
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
23 和 19 分别对应于 sdk23(Android 6.0) 和 sdk19(Android 4.4)的运行库环境,处理 64 位 SO 时只能选择 SDK23。
2.memory常用Api
setLibraryResolver(AndroidResolver resolver)
void
getStackPoint()
long
pointer(long address)
UnidbgPointer
getMemoryMap()
Collection<MemoryMap>
findModule(String moduleName)
Module
findModuleByAddress(long address)
Module
loadLibrary(File file, boolean forceLoad)
ElfModule
Linker.do_dlopen()
方法完成加载。allocatestack(int size)
UnidbgPointer
writestackstring(String value)
UnidbgPointer
writestackBytes(byte[] value)
UnidbgPointer
malloc(int size, boolean runtime)
UnidbgPointer
vm常用Api
// 创建Dalvik虚拟机实例
vm = emulator.createDalvikVM();
// 设置是否输出详细的JNI调用日志
vm.setVerbose(false);
// 加载指定路径的SO库文件,不自动调用JNI_OnLoad函数
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"), false);
// 手动调用JNI_OnLoad方法
dm.callJNI_OnLoad(emulator);
方法名
返回类型
描述
createDalvikVM(File apkFile)
VM
setVerbose(boolean verbose)
void
loadLibrary(File soFile, boolean callInit)
DalvikModule
init
函数。setJni(Jni jni)
void
AbstractJni
。getJNIEnv()
Pointer
getJavaVM()
Pointer
callJNI_OnLoad(Emulator<?> emulator, Module module)
void
JNI_OnLoad
函数。addGlobalObject(DvmObject<?> obj)
int
addLocalObject(DvmObject<?> obj)
int
getObject(int hash)
DvmObject<?>
resolveClass(String className)
DvmClass
DvmClass
对象。getPackageName()
String
getVersionName()
String
getVersionCode()
String
openAsset(String assetName)
InputStream
getManifestXml()
String
AndroidManifest.xml
文件的文本内容。getSignatures()
CertificateMeta[]
findClass(String className)
DvmClass
DvmClass
对象)。getEmulator()
Emulator<?>
emulator
。- 1.
VM dalvikVM = emulator.createDalvikVM(new File("apk file path"))
-创建虚拟机并指定APK文件,加载指定APK文件,unidbg可以帮我们完成一些小操作,例如:解析 Apk 基本信息,Apk 的版本名、版本号、包名、 Apk 签名等信息,减少补环境操作;解析和管理 Apk 资源文件,加载 Apk 后可以通过openAsset
获取 APKassets
目录下的文件。 - 2.
loadLibrary
三个重载方法
/**
* 加载指定名称的库文件。
* @Param libname 库文件的名称,不包括前缀 "lib" 和后缀 ".so"(例如 "example" 对应 "libexample.so")。
* @param forceCallInit 是否强制调用库的初始化函数(如 JNI_OnLoad)。
* @Return 加载后的 DalvikModule 对象,封装了加载的库模块。
*/
DalvikModule loadLibrary(String libname, boolean forceCallInit);
/**
* 从原始字节数组中加载指定的库文件。
* @param libname 库文件的名称,仅用于标识该库,与文件路径无关。
* @param 传入buffer方便解析elf
*/
DalvikModule loadLibrary(String libname, byte[] raw, boolean forceCallInit);
/**
* 从指定路径的加载ELF。
* @param elfFile 表示库的 ELF 文件,必须是有效的 ELF 格式文件。例如:new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so")
* @param forceCallInit 是否强制调用库的初始化函数(如 JNI_OnLoad)。
*/
DalvikModule loadLibrary(File elfFile, boolean forceCallInit);
3.jni函数的调用
//第一个参数传入模拟器实例
//第二个参数传入要调用的函数在java层方法签名信息
//第三个参数传入可变参数列表,这里的参数在旧版本有很多需要自己封装,新版则帮我们封装好了,详细看callJniMethod方法
boolean success = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str);
/**
* 调用 JNI 方法的辅助函数。
*
* @param emulator 模拟器实例。
* @param vm Dalvik 虚拟机实例。
* @param objectType 调用方法的类(DvmClass)。
* @param thisObj 方法调用的对象实例(DvmObject)。
* @param method 要调用的方法签名(例如 "methodName(参数类型)返回类型")。
* @param args 方法的可变参数列表。
* @return 方法执行后的返回值(Number 类型),可能是整数或浮点数。
*/
protected static Number callJniMethod(Emulator<?> emulator, VM vm, DvmClass objectType, DvmObject<?> thisObj, String method, Object... args) {
// 查找对应的本地函数指针(Native 方法)
UnidbgPointer fnPtr = objectType.findNativeFunction(emulator, method);
// 将当前对象添加到本地引用表,防止被垃圾回收
vm.addLocalObject(thisObj);
// 创建用于存放函数参数的列表,初始容量为10
List<Object> list = new ArrayList<>(10);
// 添加 JNI 环境指针(JNIEnv*)
list.add(vm.getJNIEnv());
// 添加 this 对象的引用(jobject)
list.add(thisObj.hashCode());
// 处理传入的参数列表
if (args != null) {
for (Object arg : args) {
if (arg instanceof Boolean) {
// 如果参数是布尔值,转换为 JNI_TRUE 或 JNI_FALSE
list.add((Boolean) arg ? VM.JNI_TRUE : VM.JNI_FALSE);
continue;
} else if (arg instanceof Hashable) {
// 如果参数实现了 Hashable 接口,表示是 DvmObject 或其子类
list.add(arg.hashCode()); // 添加对象引用(jobject)
if (arg instanceof DvmObject) {
// 将 DvmObject 对象添加到本地引用表
vm.addLocalObject((DvmObject<?>) arg);
}
continue;
} else if (arg instanceof DvmAwareObject ||
arg instanceof String ||
arg instanceof byte[] ||
arg instanceof short[] ||
arg instanceof int[] ||
arg instanceof float[] ||
arg instanceof double[] ||
arg instanceof Enum) {
// 如果参数是 DvmAwareObject、字符串、数组或枚举等类型
// 创建一个代{过}{滤}理 DvmObject 对象
DvmObject<?> obj = ProxyDvmObject.createObject(vm, arg);
// 添加对象引用(jobject)
list.add(obj.hashCode());
// 将对象添加到本地引用表
vm.addLocalObject(obj);
continue;
}
// 对于其他类型的参数,直接添加到参数列表
list.add(arg);
}
}
// 调用本地函数,传入参数列表,并返回结果
return Module.emulateFunction(emulator, fnPtr.peer, list.toArray());
}
基本类型直接传递,int、long、boolean、double 等。
- 下面几种对象类型unidbg也帮我们封装好了
- String
- byte 数组
- short 数组
- int 数组
- float 数组
- double 数组
- Enum 枚举类型
4.特殊参数构造
对于其他数据类型需要借助
resolveClass
构造,例如Context
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
5.符号调用与偏移调用
符号调用
Symbol symbol = module.findSymbolByName("导出符号");
if (symbol != null){
//第一个模拟器实例,第二个jnienv,第三个jclass,第四个可变参数
Number numbers = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(实例类), 可变参数);
int result = numbers.intValue();
System.out.println(result);
//如果返回值是string,可以通过vm.getObject(retval)获取
System.out.println(vm.getObject(result).getValue());
}else {
System.out.println("符号未找到");
}
偏移调用
//第一个模拟器实例,第二个偏移地址(thumb记得+1),第三个jnienv,第四个jclass,第五个可变参数
Number number = module.callFunction(emulator, 0x11240, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "超级")));
DvmObject<?> object = vm.getObject(number.intValue());
System.out.println("result:" + object.getValue());
3.unidbg之hook
1.HookZz&Dobby
// 获取 HookZz 实例,用于后续的 Hook 操作
IHookZz hookZz = HookZz.getInstance(emulator);
// 使用 HookZz 的 wrap 方法对导出函数 "ss_encrypt" 进行 Inline Hook
hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
// 在函数调用前执行的操作
// 获取第三个参数的指针
Pointer pointer = ctx.getPointerArg(2);
// 获取第四个参数的整数值
int length = ctx.getIntArg(3);
// 读取指针指向的内存内容,获取密钥数据
byte[] key = pointer.getByteArray(0, length);
// 使用 Inspector 工具输出密钥内容,便于调试和分析
Inspector.inspect(key, "ss_encrypt key");
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
// 在函数调用后执行的操作
// 输出函数返回值的值
System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));
}
});
1.获取 HookZz 实例:
IHookZz hookZz = HookZz.getInstance(emulator);
2.wrap_hook函数:
hookZz.wrap(functionAddress, new WrapCallback<RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, RegisterContext context, HookEntryInfo info) {
// 函数调用前的操作
UnidbgPointer input = ctx.getPointerArg(2);
System.out.println("preCall:"+vm.getObject(input.toIntPeer()));
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext context, HookEntryInfo info) {
// 函数调用后的操作
//获取整数型的返回值
System.out.println("postCall:" + ctx.getLongArg(0));
//获取jstr
UnidbgPointer input = ctx.getPointerArg(0);
System.out.println("postCall:"+vm.getObject(input.toIntPeer()));
}
});
wrap
函数有两个重载,一个基于符号寻址,一个基于地址寻址,本质没区别,符号寻址的最终也是会调用symbol.getAddress()
参数里的WrapCallback的泛型接口有三个RegisterContext(函数 Hook)
、HookZzArm32RegisterContext(针对ARM32位)
和HookZzArm64RegisterContext(针对ARM64位)
因为可以访问某个寄存器的值,所以适用于inline hook
而在HookZzArm64RegisterContext
中则是通过以下的方法去获取对应的寄存器的值
3.instrument_inline_hook函数
// 使用 HookZz 的 instrument 方法对特定地址的指令进行 Inline Hook, thumb记得+1
hookZz.instrument(module.base + 0x11470, new InstrumentCallback<Arm64RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
// 在指定指令执行时触发的操作
// 输出 W0 寄存器的值
System.out.println("W0=" + ctx.getXInt(0));
}
});
**4.replace替换函数***
// 使用 dobby 的 replace 方法替换 "Java_com_zj_wuaipojie_util_SecurityUtil_diamondNum" 函数的实现
Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_diamondNum"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
//替换整数型
return HookStatus.LR(emulator,888888);
//字符串
return HookStatus.LR(emulator, vm.addLocalObject(new StringObject(vm, "超级")));
}
});
2.Unicorn_Hook
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
Arm64RegisterContext context = emulator.getContext();
if (address == module.base + 0x11330) {
int x0=emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0).intValue();
System.out.println("x0:"+vm.getObject(x0));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,vm.addLocalObject(new StringObject(vm, "超级")));
}
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base + 0x11240,module.base + 0x11340,null);
StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "123");
System.out.println("Result: " + Result.getValue());
3.Console Debugge
Console Debugger(控制台调试器)是 Unidbg 提供的一个强大工具,允许用户在模拟执行过程中设置断点、单步调试、查看和修改内存及寄存器等操作,从而深入分析目标程序的行为。
Debugger attach = emulator.attach();
attach.addBreakPoint(module.base + 0x11070); //下断地址
//调用
Number number = module.callFunction(emulator, 0x11014, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "123456")));
System.out.println("result:" + number.intValue());
替换返回值
emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
System.out.println("替换函数 verifyApkSign");
RegisterContext registerContext = emulator.getContext();
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, registerContext.getLRPointer().peer);
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0);
return true;
}
});
命令
功能说明
c
n
bt
st hex
shw hex
shr hex
shx-hex
nb
s
s [decimal]
s (blx)
blx
助记符(性能较低)m (op) [size]
0x70
,大小可为十六进制或十进制mr0-mr7, mfp, mip, msp [size]
m (address) [size]
0x
开头wr0-wr7, wfp, wip, wsp <value>
wb(address), ws(address), wi(address) <value>
0x
开头wx (address) <hex>
0x
开头b (address)
0x
开头,可为模块偏移量b
PC
的断点r
PC
的断点blr
LR
的临时断点p (assembly)
where
trace [begin-end]
traceRead [begin-end]
traceWrite [begin-end]
vm
vbs
d
d (0x)
stop
run [arg]
gc
System.gc()
threads
cc size
4.unidbg之patch
Patch 就是直接对二进制文件进行修改,Patch本质上只有两种形式
- patch 二进制文件
- 在内存里 patch
Patch的应用场景很多,在一些场景比Hook更好用,这就是需要介绍它的原因。Patch 二进制文件的形式是大多数人所熟悉的,在IDA中使用KeyPatch打补丁的体验很友好。这里主要介绍unidbg的内存Patch。
借助Keystone 汇编引擎
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x1146C);
//在进行 Patch 操作前,需确保已正确定位目标函数的地址和指令集类型(如 ARM 或 Thumb)
//如果是32位的,代码。Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
String s = "MOV W0, #0x99"; //具体要修改的汇编指令
byte[] machineCode = keystone.assemble(s).getMachineCode(); //转换为机器码
pointer.write(machineCode); //写入内存
四、请作者喝杯咖啡
六、视频及课件地址
百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压
七、其他章节
《安卓逆向这档事》一、模拟器环境搭建
《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改
《安卓逆向这档事》三、初识smail,vip终结者
《安卓逆向这档事》四、恭喜你获得广告&弹窗静默卡
《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩
《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代{过}{滤}理、IO重定向
《安卓逆向这档事》七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写,常用Api
《安卓逆向这档事》八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook
《安卓逆向这档事》九、密码学基础、算法自吐、非标准加密对抗
《安卓逆向这档事》十、不是我说,有了IDA还要什么女朋友?
《安卓逆向这档事》十二、大佬帮我分析一下
《安卓逆向这档事》番外实战篇1-某电影视全家桶
《安卓逆向这档事》十三、是时候学习一下Frida一把梭了(上)
《安卓逆向这档事》十四、是时候学习一下Frida一把梭了(中)
《安卓逆向这档事》十五、是时候学习一下Frida一把梭了(下)
《安卓逆向这档事》十六、是时候学习一下Frida一把梭了(终)
《安卓逆向这档事》十七、你的RPCvs佬的RPC
《安卓逆向这档事》番外实战篇2-【2024春节】解题领红包活动,启动!
《安卓逆向这档事》十八、表哥,你也不想你的Frida被检测吧!(上)
《安卓逆向这档事》十九、表哥,你也不想你的Frida被检测吧!(下)
《安卓逆向这档事》二十、抓包学得好,牢饭吃得饱(上)
《安卓逆向这档事》番外实战篇3-拨云见日之浅谈Flutter逆向
《安卓逆向这档事》第二十一课、抓包学得好,牢饭吃得饱(中)
《安卓逆向这档事》第二十二课、抓包学得好,牢饭吃得饱(下)