第十三章:Native层安全深度剖析
本章字数:约16000字 阅读时间:约55分钟 难度等级:★★★★★
声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理。文中的"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。
引言
在前面的章节中,我们多次提到Native层是安全防护的最后一道防线。本章将深入剖析Native层的安全机制,包括ELF文件格式、SO加载机制、保护技术以及逆向分析方法。
13.1 ELF文件格式
13.1.1 ELF结构概览
scss
┌─────────────────────────────────────────────────────────────────┐
│ ELF文件结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ELF Header │ │
│ │ • 魔数 (0x7F 'E' 'L' 'F') │ │
│ │ • 文件类型 (可执行/共享库/目标文件) │ │
│ │ • 目标架构 (ARM/ARM64/x86等) │ │
│ │ • 入口点地址 │ │
│ │ • Program Header表偏移 │ │
│ │ • Section Header表偏移 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Program Header Table │ │
│ │ • PT_LOAD (可加载段) │ │
│ │ • PT_DYNAMIC (动态链接信息) │ │
│ │ • PT_GNU_RELRO (只读重定位) │ │
│ │ • PT_GNU_STACK (栈属性) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Sections │ │
│ │ • .text (代码段) │ │
│ │ • .rodata (只读数据) │ │
│ │ • .data (已初始化数据) │ │
│ │ • .bss (未初始化数据) │ │
│ │ • .dynsym (动态符号表) │ │
│ │ • .dynstr (动态字符串表) │ │
│ │ • .plt (过程链接表) │ │
│ │ • .got (全局偏移表) │ │
│ │ • .init_array (初始化函数数组) │ │
│ │ • .fini_array (终止函数数组) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Section Header Table │ │
│ │ • 各Section的元信息 │ │
│ │ • 名称、类型、标志、偏移、大小等 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
13.1.2 ELF Header解析
c
// ELF64 Header结构
typedef struct {
unsigned char e_ident[16]; // 魔数和标识
uint16_t e_type; // 文件类型
uint16_t e_machine; // 目标架构
uint32_t e_version; // ELF版本
uint64_t e_entry; // 入口点地址
uint64_t e_phoff; // Program Header偏移
uint64_t e_shoff; // Section Header偏移
uint32_t e_flags; // 处理器特定标志
uint16_t e_ehsize; // ELF Header大小
uint16_t e_phentsize; // Program Header条目大小
uint16_t e_phnum; // Program Header数量
uint16_t e_shentsize; // Section Header条目大小
uint16_t e_shnum; // Section Header数量
uint16_t e_shstrndx; // 字符串表索引
} Elf64_Ehdr;
// e_ident字段
// [0-3]: 魔数 0x7F 'E' 'L' 'F'
// [4]: 类别 (1=32位, 2=64位)
// [5]: 字节序 (1=小端, 2=大端)
// [6]: ELF版本
// [7]: OS/ABI
// [8-15]: 填充
// e_type值
#define ET_NONE 0 // 未知类型
#define ET_REL 1 // 可重定位文件
#define ET_EXEC 2 // 可执行文件
#define ET_DYN 3 // 共享目标文件
#define ET_CORE 4 // Core文件
// e_machine值 (常见)
#define EM_ARM 40 // ARM
#define EM_AARCH64 183 // ARM64
#define EM_386 3 // x86
#define EM_X86_64 62 // x86_64
13.1.3 动态段解析
c
// Dynamic段条目
typedef struct {
int64_t d_tag; // 类型标签
uint64_t d_val; // 值或地址
} Elf64_Dyn;
// 重要的d_tag值
#define DT_NULL 0 // 结束标记
#define DT_NEEDED 1 // 依赖库名称
#define DT_PLTRELSZ 2 // PLT重定位大小
#define DT_PLTGOT 3 // PLT/GOT地址
#define DT_HASH 4 // 符号哈希表
#define DT_STRTAB 5 // 字符串表地址
#define DT_SYMTAB 6 // 符号表地址
#define DT_STRSZ 10 // 字符串表大小
#define DT_SYMENT 11 // 符号表条目大小
#define DT_INIT 12 // 初始化函数地址
#define DT_FINI 13 // 终止函数地址
#define DT_SONAME 14 // 共享库名称
#define DT_RPATH 15 // 库搜索路径
#define DT_JMPREL 23 // PLT重定位表地址
#define DT_INIT_ARRAY 25 // 初始化函数数组
#define DT_FINI_ARRAY 26 // 终止函数数组
#define DT_INIT_ARRAYSZ 27 // 初始化数组大小
#define DT_FINI_ARRAYSZ 28 // 终止数组大小
13.2 SO加载机制
13.2.1 Android Linker
scss
┌─────────────────────────────────────────────────────────────────┐
│ SO加载流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ System.loadLibrary("xxx") │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Runtime.loadLibrary0() │ │
│ │ 查找库文件路径 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ nativeLoad() [JNI] │ │
│ │ 调用dlopen() │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Linker: do_dlopen() │ │
│ │ 1. 查找/加载SO文件 │ │
│ │ 2. 解析ELF头 │ │
│ │ 3. 映射到内存 │ │
│ │ 4. 处理依赖库 │ │
│ │ 5. 重定位 │ │
│ │ 6. 调用构造函数 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ JNI_OnLoad() [如果存在] │ │
│ │ 注册Native方法 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
13.2.2 库搜索路径
java
/**
* Android库搜索路径
*/
public class LibrarySearchPath {
/**
* 库搜索顺序
*/
public static String[] getSearchPaths(Context context) {
return new String[] {
// 1. 应用私有库目录
context.getApplicationInfo().nativeLibraryDir,
// 例如: /data/app/com.example.app/lib/arm64-v8a/
// 2. 系统库目录
"/system/lib64", // 64位
"/system/lib", // 32位
// 3. Vendor库目录
"/vendor/lib64",
"/vendor/lib",
// 4. 其他系统路径
"/system/product/lib64",
"/system/ext/lib64"
};
}
}
13.2.3 JNI_OnLoad机制
c
// JNI_OnLoad函数原型
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
// 获取JNIEnv
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// 动态注册Native方法
JNINativeMethod methods[] = {
{"nativeMethod1", "(I)V", (void*)native_method1},
{"nativeMethod2", "(Ljava/lang/String;)I", (void*)native_method2},
};
jclass clazz = (*env)->FindClass(env, "com/example/NativeClass");
if (clazz == NULL) {
return JNI_ERR;
}
if ((*env)->RegisterNatives(env, clazz, methods,
sizeof(methods)/sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
13.3 Native层保护技术
13.3.1 符号混淆
┌─────────────────────────────────────────────────────────────────┐
│ 符号混淆技术 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 技术 效果 实现方式 │
│ ───────────────────────────────────────────────────────────── │
│ 符号剥离 移除调试符号 strip命令 │
│ 减小文件大小 │
│ │
│ 符号重命名 函数名变为无意义 LLVM混淆 │
│ 增加分析难度 自定义脚本 │
│ │
│ 动态注册 隐藏JNI方法名 JNI_OnLoad │
│ 防止静态分析 RegisterNatives │
│ │
│ 字符串加密 运行时解密字符串 自定义加密 │
│ 防止字符串搜索 OLLVM │
│ │
└─────────────────────────────────────────────────────────────────┘
13.3.2 控制流平坦化
c
// 原始代码
int original_function(int x) {
if (x > 0) {
return x * 2;
} else {
return x + 1;
}
}
// 控制流平坦化后
int flattened_function(int x) {
int state = 0;
int result = 0;
while (1) {
switch (state) {
case 0: // 入口
if (x > 0) {
state = 1;
} else {
state = 2;
}
break;
case 1: // x > 0分支
result = x * 2;
state = 3;
break;
case 2: // x <= 0分支
result = x + 1;
state = 3;
break;
case 3: // 出口
return result;
}
}
}
13.3.3 虚假控制流
c
// 插入永远不会执行的虚假分支
int function_with_bogus_flow(int x) {
int result;
// 虚假条件(永远为假)
if ((x * x) < 0) { // 平方永远非负
// 虚假代码块
result = x ^ 0xDEADBEEF;
result = result >> 16;
// 更多混淆代码...
}
// 真实逻辑
result = x * 2;
// 另一个虚假条件
if ((x | x) != x) { // 永远为假
result = ~result;
}
return result;
}
13.3.4 反调试技术
c
// Native层反调试实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <dlfcn.h>
/**
* 检测ptrace
*/
int detect_ptrace() {
// 方法1: 尝试ptrace自己
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
return 1; // 已被调试
}
ptrace(PTRACE_DETACH, 0, NULL, NULL);
return 0;
}
/**
* 检测/proc/self/status
*/
int detect_status() {
char buf[512];
int fd = open("/proc/self/status", O_RDONLY);
if (fd < 0) return 0;
int len = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (len <= 0) return 0;
buf[len] = '\0';
// 查找TracerPid
char *tracer = strstr(buf, "TracerPid:");
if (tracer) {
int pid = atoi(tracer + 10);
if (pid != 0) {
return 1; // 被调试
}
}
return 0;
}
/**
* 检测/proc/self/wchan
*/
int detect_wchan() {
char buf[64];
int fd = open("/proc/self/wchan", O_RDONLY);
if (fd < 0) return 0;
int len = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (len <= 0) return 0;
buf[len] = '\0';
// 被ptrace时wchan通常是ptrace_stop
if (strstr(buf, "ptrace") != NULL) {
return 1;
}
return 0;
}
/**
* 检测断点
*/
int detect_breakpoint(void *addr) {
// ARM64断点指令: 0xD4200000 (BRK #0)
// ARM断点指令: 0xE7F001F0
unsigned int *ptr = (unsigned int *)addr;
#if defined(__aarch64__)
if ((*ptr & 0xFFE0001F) == 0xD4200000) {
return 1; // 检测到断点
}
#elif defined(__arm__)
if (*ptr == 0xE7F001F0) {
return 1;
}
#endif
return 0;
}
/**
* 时间检测
*/
int detect_timing() {
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
// 执行一些操作
volatile int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
clock_gettime(CLOCK_MONOTONIC, &end);
// 计算耗时(纳秒)
long elapsed = (end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec);
// 如果耗时异常长,可能被调试
if (elapsed > 10000000) { // 10ms
return 1;
}
return 0;
}
/**
* 检测Frida
*/
int detect_frida() {
// 检测Frida特征端口
int ports[] = {27042, 27043};
for (int i = 0; i < sizeof(ports)/sizeof(ports[0]); i++) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) continue;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(ports[i]);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 设置非阻塞
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
int ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
close(sock);
if (ret == 0 || errno == EINPROGRESS) {
return 1; // 端口开放,可能有Frida
}
}
// 检测Frida特征文件
const char *frida_files[] = {
"/data/local/tmp/frida-server",
"/data/local/tmp/re.frida.server",
NULL
};
for (int i = 0; frida_files[i] != NULL; i++) {
if (access(frida_files[i], F_OK) == 0) {
return 1;
}
}
// 检测Frida gadget
char maps[256];
snprintf(maps, sizeof(maps), "/proc/%d/maps", getpid());
FILE *fp = fopen(maps, "r");
if (fp) {
char line[512];
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "frida") || strstr(line, "gadget")) {
fclose(fp);
return 1;
}
}
fclose(fp);
}
return 0;
}
/**
* 反调试主函数
*/
void anti_debug_check() {
if (detect_ptrace() ||
detect_status() ||
detect_wchan() ||
detect_frida()) {
// 检测到调试,采取行动
exit(0); // 或者其他处理
}
}
/**
* 反调试线程
*/
void *anti_debug_thread(void *arg) {
while (1) {
anti_debug_check();
usleep(1000000); // 1秒检测一次
}
return NULL;
}
/**
* 启动反调试
*/
__attribute__((constructor))
void init_anti_debug() {
pthread_t thread;
pthread_create(&thread, NULL, anti_debug_thread, NULL);
pthread_detach(thread);
}
13.4 逆向分析方法
13.4.1 静态分析工具
objectivec
┌─────────────────────────────────────────────────────────────────┐
│ Native逆向工具 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 工具 类型 特点 │
│ ───────────────────────────────────────────────────────────── │
│ IDA Pro 反汇编器 功能最强大 │
│ 支持多架构 │
│ 强大的反编译 │
│ │
│ Ghidra 反汇编器 NSA开源 │
│ 免费 │
│ 反编译质量高 │
│ │
│ radare2 反汇编器 开源免费 │
│ 命令行界面 │
│ 脚本化能力强 │
│ │
│ Binary Ninja 反汇编器 现代化界面 │
│ API友好 │
│ 中间表示强大 │
│ │
│ objdump 命令行工具 简单快速 │
│ Linux自带 │
│ │
│ readelf 命令行工具 ELF分析 │
│ 查看结构信息 │
│ │
└─────────────────────────────────────────────────────────────────┘
13.4.2 动态分析方法
python
# Frida脚本示例:Hook Native函数
import frida
import sys
# Hook脚本
hook_script = """
// Hook dlopen
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
onEnter: function(args) {
var path = Memory.readUtf8String(args[0]);
console.log("[dlopen] Loading: " + path);
},
onLeave: function(retval) {
console.log("[dlopen] Handle: " + retval);
}
});
// Hook特定函数
var targetModule = Process.findModuleByName("libSecurityCore.so");
if (targetModule) {
console.log("Found module at: " + targetModule.base);
// 枚举导出函数
var exports = targetModule.enumerateExports();
exports.forEach(function(exp) {
console.log("Export: " + exp.name + " @ " + exp.address);
});
// Hook特定偏移的函数
var targetAddr = targetModule.base.add(0x1234);
Interceptor.attach(targetAddr, {
onEnter: function(args) {
console.log("Function called!");
console.log(" arg0: " + args[0]);
console.log(" arg1: " + args[1]);
// 打印调用栈
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
},
onLeave: function(retval) {
console.log(" return: " + retval);
}
});
}
// Hook JNI函数
var jniEnv = Java.vm.getEnv();
var RegisterNatives = jniEnv.handle.readPointer().add(215 * Process.pointerSize).readPointer();
Interceptor.attach(RegisterNatives, {
onEnter: function(args) {
var className = Java.vm.getEnv().getStringUtfChars(
Java.vm.getEnv().callObjectMethod(args[1],
Java.vm.getEnv().getMethodId(
Java.vm.getEnv().findClass("java/lang/Class"),
"getName", "()Ljava/lang/String;"
)
), null
);
console.log("[RegisterNatives] Class: " + className);
var methodCount = args[3].toInt32();
var methods = args[2];
for (var i = 0; i < methodCount; i++) {
var methodName = methods.add(i * Process.pointerSize * 3).readPointer().readUtf8String();
var methodSig = methods.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readUtf8String();
var methodPtr = methods.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
console.log(" " + methodName + methodSig + " -> " + methodPtr);
}
}
});
"""
def on_message(message, data):
if message['type'] == 'send':
print("[*] " + message['payload'])
else:
print(message)
# 连接设备
device = frida.get_usb_device()
pid = device.spawn(["com.dreamworld.app"])
session = device.attach(pid)
# 注入脚本
script = session.create_script(hook_script)
script.on('message', on_message)
script.load()
# 恢复进程
device.resume(pid)
# 保持运行
sys.stdin.read()
13.4.3 Unidbg分析技巧
java
/**
* Unidbg高级分析技巧
*/
public class UnidbgAdvancedAnalysis {
private AndroidEmulator emulator;
private VM vm;
private Module module;
/**
* 设置内存断点
*/
public void setMemoryBreakpoint(long address) {
emulator.getBackend().hook_add_new(new ReadHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
System.out.println("Memory read at: 0x" + Long.toHexString(address));
// 打印调用栈
emulator.getUnwinder().unwind();
}
}, address, address + 4, null);
}
/**
* 跟踪函数调用
*/
public void traceFunction(long address) {
emulator.traceCode(address, address + 0x1000);
}
/**
* Hook系统调用
*/
public void hookSyscall() {
emulator.getSyscallHandler().addIOResolver(new IOResolver<AndroidFileIO>() {
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator,
String pathname, int oflags) {
System.out.println("Open file: " + pathname);
return null; // 使用默认处理
}
});
}
/**
* 分析加密函数
*/
public void analyzeEncryption() {
// Hook常见加密函数
Symbol aesEncrypt = module.findSymbolByName("AES_encrypt");
if (aesEncrypt != null) {
emulator.attach().addBreakPoint(aesEncrypt.getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext ctx = emulator.getContext();
// 读取参数
UnidbgPointer input = ctx.getPointerArg(0);
UnidbgPointer output = ctx.getPointerArg(1);
UnidbgPointer key = ctx.getPointerArg(2);
System.out.println("AES_encrypt called:");
System.out.println(" Input: " + bytesToHex(input.getByteArray(0, 16)));
System.out.println(" Key: " + bytesToHex(key.getByteArray(0, 16)));
return true; // 继续执行
}
});
}
}
/**
* 导出内存
*/
public void dumpMemory(long address, int size, String filename) {
byte[] data = emulator.getBackend().mem_read(address, size);
try (FileOutputStream fos = new FileOutputStream(filename)) {
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 搜索内存
*/
public List<Long> searchMemory(byte[] pattern) {
List<Long> results = new ArrayList<>();
for (MemoryBlock block : emulator.getMemory().getMemoryMap()) {
byte[] data = emulator.getBackend().mem_read(block.getAddress(), (int) block.getSize());
for (int i = 0; i <= data.length - pattern.length; i++) {
boolean match = true;
for (int j = 0; j < pattern.length; j++) {
if (data[i + j] != pattern[j]) {
match = false;
break;
}
}
if (match) {
results.add(block.getAddress() + i);
}
}
}
return results;
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}
13.5 案例分析:梦想世界APP的Native保护
13.5.1 保护机制分析
通过对梦想世界APP的libSecurityCore.so分析,我们发现了以下保护机制:
┌─────────────────────────────────────────────────────────────────┐
│ libSecurityCore.so 保护分析 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 保护层次 具体实现 │
│ ───────────────────────────────────────────────────────────── │
│ 符号保护 • 导出符号使用混淆名称 │
│ • 如: oGetPriId2b3c4d5e6f7a8b9c0d1e2f3a4b │
│ • JNI方法动态注册 │
│ │
│ 代码保护 • 控制流平坦化 │
│ • 虚假控制流 │
│ • 指令替换 │
│ │
│ 字符串保护 • 关键字符串加密存储 │
│ • 运行时解密 │
│ │
│ 反调试 • ptrace检测 │
│ • TracerPid检测 │
│ • 时间检测 │
│ • Frida检测 │
│ │
│ 完整性校验 • SO文件哈希校验 │
│ • 代码段校验 │
│ │
└─────────────────────────────────────────────────────────────────┘
13.5.2 绕过策略
java
/**
* 使用Unidbg绕过Native保护
*/
public class NativeProtectionBypass {
/**
* 绕过反调试
* Unidbg本身就不会触发大多数反调试检测
*/
public void bypassAntiDebug() {
// 1. Unidbg不是真实的调试器,不会被ptrace检测
// 2. 模拟环境中没有TracerPid
// 3. 没有Frida特征
// 如果SO内部有时间检测,可以Hook时间函数
emulator.getSyscallHandler().addIOResolver(new IOResolver<AndroidFileIO>() {
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator,
String pathname, int oflags) {
// 对/proc/self/status返回伪造内容
if (pathname.equals("/proc/self/status")) {
return FileResult.success(new ByteArrayFileIO(
oflags, pathname,
"TracerPid:\t0\n".getBytes()
));
}
return null;
}
});
}
/**
* 处理符号混淆
*/
public void handleObfuscatedSymbols() {
// 通过JNI_OnLoad找到真实的方法映射
// 或者通过行为分析确定函数功能
// 枚举所有导出符号
for (Symbol symbol : module.getExportedSymbols()) {
System.out.println("Symbol: " + symbol.getName() +
" @ 0x" + Long.toHexString(symbol.getAddress()));
}
// 通过特征匹配识别函数
// 例如:签名函数通常会调用加密库函数
}
/**
* 处理字符串加密
*/
public void handleStringEncryption() {
// 方法1: 在字符串使用点设置断点,观察解密后的值
// 方法2: 找到解密函数,直接调用
// 方法3: 动态跟踪,记录所有解密的字符串
// Hook常见的字符串操作函数
Symbol strlen = module.findSymbolByName("strlen");
if (strlen != null) {
emulator.attach().addBreakPoint(strlen.getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
UnidbgPointer str = emulator.getContext().getPointerArg(0);
String s = str.getString(0);
if (s != null && !s.isEmpty()) {
System.out.println("String: " + s);
}
return true;
}
});
}
}
}
13.6 本章小结
本章我们深入剖析了Native层的安全机制和逆向分析方法:
13.6.1 技术要点回顾
css
┌─────────────────────────────────────────────────────────────────┐
│ 本章技术要点 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. ELF文件格式 │
│ • ELF Header结构 │
│ • Program Header与Section Header │
│ • 动态段与符号表 │
│ │
│ 2. SO加载机制 │
│ • Android Linker工作流程 │
│ • 库搜索路径 │
│ • JNI_OnLoad机制 │
│ │
│ 3. Native保护技术 │
│ • 符号混淆 │
│ • 控制流平坦化 │
│ • 虚假控制流 │
│ • 反调试技术 │
│ │
│ 4. 逆向分析方法 │
│ • 静态分析工具 │
│ • 动态分析方法 │
│ • Unidbg高级技巧 │
│ │
│ 5. 实战案例 │
│ • 保护机制识别 │
│ • 绕过策略 │
│ │
└─────────────────────────────────────────────────────────────────┘
13.6.2 下一章预告
在下一章《防护设计最佳实践》中,我们将从防御者的角度,探讨如何设计安全的移动应用:
- 安全架构设计原则
- 多层防护策略
- 密钥管理最佳实践
- 安全开发流程
本章完