一、沙箱快递系统:Android 安全模型
想象 Android 系统是一个严格的 "快递管理系统",每个应用都是独立的 "快递站",由 Linux 内核的 UID/GID 机制分配唯一的 "快递站编号":
1.1 快递站的权限控制
- UID/GID 身份牌:每个应用安装时会获得唯一的 UID(如 u0a19)和 GID,像快递站的营业执照:
java
scss
// 应用进程启动时设置UID/GID
pid = fork();
if (pid == 0) {
setgid(gid); // 设置主组ID
setuid(uid); // 设置用户ID
setgroupsIntarray(gids); // 设置附加组ID
}
- 文件权限锁:每个文件有三层权限(Owner/Group/Other),类似快递包裹的三层锁:
bash
bash
# 查看/data目录权限(只有owner可写)
ls -l /data
drwxrwx--x system system 2023-11-11 data
1.2 快递单 Permission 机制
应用需要提前申请 "快递类型许可"(Permission),否则无法处理特定包裹:
java
typescript
// 安装时PMS检查权限申请
public void installPackage(Uri packageURI) {
mContext.enforceCallingPermission(
Manifest.permission.INSTALL_PACKAGES, "Need permission to install");
// 检查APK的Permission清单与签名
}
二、SO 注入:潜入别家快递站
SO 注入就像将自己的 "快递员"(SO 文件)偷偷塞进别家快递站,实现功能拦截:
2.1 潜入流程:PTrace 劫持技术
- 强制闯入(PTRACE_ATTACH) :用 ptrace 接管目标进程,类似强行进入别家快递站:
cpp
运行
scss
ptrace(PTRACE_ATTACH, targetPid, NULL, NULL); // 附着目标进程
waitpid(targetPid, &status, 0); // 等待进程暂停
- 伪造快递单(SHELL CODE 注入) :在目标进程内存中写入伪造的 "快递处理流程":
cpp
运行
arduino
// 注入的SHELL CODE片段(简化版)
char shellCode[] = "\x48\x31\xd2" // xor r2, r2
"\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00" // mov r3, "/bin/sh"
"\x48\x89\xe7" // mov r7, r3
"\xb0\x3b" // mov al, 0x3b
"\x0f\x05"; // syscall
- 启动潜入快递员(dlopen 加载 SO) :让目标进程加载你的 SO,如同雇佣你的快递员:
cpp
运行
ini
void* handle = dlopen("/data/your.so", RTLD_NOW); // 加载SO
void* func = dlsym(handle, "hookFunction"); // 获取钩子函数
三、SO 加壳:加密快递包裹
SO 加壳是给 SO 文件穿上 "密码箱",防止被逆向分析,核心是内存直接加载:
3.1 密码箱快递流程
- 拆解包裹(解析 ELF 结构) :在内存中解密 SO 文件,模拟 Linker 解析:
cpp
运行
ini
// 内存解析ELF头部
Elf32_Ehdr* header = (Elf32_Ehdr*)soData;
if (header->e_ident[EI_MAG0] != ELFMAG0 ||
header->e_ident[EI_MAG1] != ELFMAG1) {
return NULL; // 非ELF文件
}
- 重建快递路线(内存重定位) :在内存中重建 SO 的执行结构,不落地到文件:
cpp
运行
scss
// 分配内存并映射段
void* mem = mmap(NULL, header->e_shoff,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy(mem, soData, header->e_shoff); // 复制数据
- 密码箱钥匙(动态 Linker) :实现内存版 Linker,直接执行加密后的 SO:
cpp
运行
arduino
// 自定义Linker核心函数
void* customLinker(void* soData, size_t size) {
Elf32_Ehdr* ehdr = (Elf32_Ehdr*)soData;
// 解析段表、重定位GOT表、解析符号
return getEntryPoint(ehdr); // 返回入口地址
}
四、函数拦截:中途劫持快递
4.1 GOT 拦截:修改快递单地址
GOT 拦截就像在快递站的 "地址簿"(GOT 表)上改收件地址:
cpp
运行
scss
// 1. 查找目标函数的GOT地址
void* findGotEntry(void* funcAddr) {
Dl_info info;
dladdr(funcAddr, &info); // 获取函数所在SO信息
// 解析ELF的.got段
Elf32_Ehdr* ehdr = (Elf32_Ehdr*)info.dli_fbase;
Elf32_Shdr* shdr = getSectionHeader(ehdr, ".got");
void* gotBase = (void*)((uintptr_t)ehdr + shdr->sh_addr);
// 查找具体函数的GOT项
uint32_t* gotTable = (uint32_t*)gotBase;
for (int i=0; i<shdr->sh_size/4; i++) {
if (gotTable[i] == (uint32_t)funcAddr) {
return &gotTable[i];
}
}
return NULL;
}
// 2. 修改GOT表指向钩子函数
void hookFunction(void* gotEntry, void* hookAddr) {
uint32_t oldProtect;
mprotect((void*)PAGE_START((uintptr_t)gotEntry),
PAGE_SIZE, PROT_READ|PROT_WRITE);
*(uint32_t*)gotEntry = (uint32_t)hookAddr; // 修改GOT项
mprotect((void*)PAGE_START((uintptr_t)gotEntry),
PAGE_SIZE, oldProtect);
}
4.2 INLINE 拦截:直接修改快递内容
INLINE 拦截如同直接拆开包裹修改内容,针对 ARM 指令集:
cpp
运行
scss
// ARM指令级拦截(以修改前8字节为例)
void armInlineHook(void* funcAddr, void* hookAddr) {
uint8_t originalCode[8];
uint8_t hookCode[8] = {
0xE5,0x1F,0x00,0x04, // ldr pc, [pc, #4] // 跳转指令
0x00,0x00,0x00,0x00 // 钩子地址占位
};
// 1. 保存原指令
memcpy(originalCode, funcAddr, 8);
// 2. 计算跳转偏移并填充钩子代码
uint32_t offset = (uint32_t)hookAddr - ((uint32_t)funcAddr + 8);
*(uint32_t*)&hookCode[4] = offset;
// 3. 写入钩子代码(需先修改内存权限)
uint32_t oldProtect;
mprotect((void*)PAGE_START((uintptr_t)funcAddr),
PAGE_SIZE, PROT_READ|PROT_WRITE);
memcpy(funcAddr, hookCode, 8); // 写入跳转指令
mprotect((void*)PAGE_START((uintptr_t)funcAddr),
PAGE_SIZE, oldProtect);
}
五、DEX 注入与加壳:快递系统升级
5.1 DEX 注入:安装新快递系统
DEX 注入类似在别家快递站安装一套新的 "快递管理系统"(DEX):
java
arduino
// 核心代码:使用DexClassLoader加载内存中的DEX
public Class<?> loadDexFromMemory(byte[] dexData) {
try {
// 创建临时目录存放ODEX
File dexDir = context.getDir("dex", Context.MODE_PRIVATE);
File optDir = context.getDir("odex", Context.MODE_PRIVATE);
// 内存DEX加载(Android 4.0+)
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
DexClassLoader dexLoader = new DexClassLoader(
"", dexDir.getPath(), optDir.getPath(), systemLoader);
// 反射获取隐藏API(Android 4.0以下)
if (Build.VERSION.SDK_INT < 14) {
Class<?> dexFileClass = Class.forName("dalvik.system.DexFile");
Method openMethod = dexFileClass.getMethod(
"openDexFile", byte[].class);
int cookie = (int)openMethod.invoke(null, dexData);
Method defineMethod = dexFileClass.getMethod(
"defineClass", String.class, ClassLoader.class, int.class);
return (Class<?>)defineMethod.invoke(null,
"com.hook.TargetClass", systemLoader, cookie);
}
return dexLoader.loadClass("com.hook.TargetClass");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
5.2 DEX 加壳:加密快递系统数据
DEX 加壳是将 DEX 加密后放入内存,动态解密执行:
cpp
运行
scss
// 内存解密并加载DEX核心函数
jclass loadDexFromMemory(JNIEnv* env, void* encryptedDex, size_t size) {
// 1. 解密DEX数据
void* decryptedDex = decryptDex(encryptedDex, size);
// 2. 模拟DexFile加载(Android 4.0以下)
if (isAndroidBelow40()) {
// 调用libdvm.so的底层函数
void* dexFile = callLibdvmFunction("dexFileOpenPartial", decryptedDex, size);
return (jclass)callLibdvmFunction("dexFileDefineClass",
dexFile, "com.hook.TargetClass", getSystemClassLoader(env));
}
// 3. Android 4.0+直接使用隐藏API
jclass dexFileClass = env->FindClass("dalvik/system/DexFile");
jmethodID openMethod = env->GetStaticMethodID(
dexFileClass, "openDexFile", "([B)I");
jint cookie = env->CallStaticIntMethod(
dexFileClass, openMethod, env->NewByteArrayFromByte((byte*)decryptedDex, size));
jmethodID defineMethod = env->GetStaticMethodID(
dexFileClass, "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;");
return (jclass)env->CallStaticObjectMethod(
dexFileClass, defineMethod,
env->NewStringUTF("com.hook.TargetClass"),
getSystemClassLoader(env), cookie);
}
六、Java 函数拦截:替换快递员
Java 函数拦截如同替换快递站的 "快递员",让他按你的要求处理包裹:
java
scss
// 1. 获取目标Method的底层结构体
public static void hookJavaMethod(Class<?> clazz, String methodName,
Class<?>[] params, final Method hookMethod)
throws Exception {
// 获取Method对象
Method targetMethod = clazz.getDeclaredMethod(methodName, params);
// 通过JNI调用底层函数获取Method结构体
JNIEnv* env = getJNIEnv();
jclass clazzObj = env->FindClass(clazz.getName().replace('.', '/'));
jmethodID getSlotMethod = clazzObj.getMethodID("getSlot", "()I");
int slot = env->CallIntMethod(targetMethod, getSlotMethod);
// 调用底层dvmSlotToMethod获取Method结构体
ClassObject* declaredClass = (ClassObject*)dvmDecodeIndirectRef(
dvmThreadSelf(), clazzObj);
Method* method = dvmSlotToMethod(declaredClass, slot);
// 2. 修改Method为Native并设置钩子函数
SET_METHOD_FLAG(method, ACC_NATIVE); // 设置为Native方法
method->nativeFunc = &javaMethodInterceptor; // 设置钩子函数
// 3. 钩子函数实现
void javaMethodInterceptor(u4* args, JValue* result, Method* method, Thread* self) {
// 转换参数并调用Java钩子方法
JNIEnv* env = attachCurrentThread();
jobject thisObj = (jobject)args[0];
jclass hookClass = env->FindClass(hookMethod.getDeclaringClass().getName());
jmethodID hookId = env->GetMethodID(
hookClass, hookMethod.getName(), getMethodSignature(hookMethod));
// 调用钩子方法并处理返回值
jvalue jargs[10]; // 假设最多10个参数
convertArgs(args, jargs, method); // 参数转换
jobject jresult = env->CallObjectMethod(thisObj, hookId, jargs);
convertResult(jresult, result, method->returnType); // 结果转换
}
}
七、总结:安全攻防的快递战法则
-
沙箱防御:UID/GID 如同快递站的物理隔离,Permission 是快递类型许可,确保每个应用只能处理自己的包裹。
-
SO 注入与加壳:SO 注入是潜入别家快递站安插内鬼,SO 加壳是给内鬼穿上隐身衣,防止被发现。
-
函数拦截:GOT 拦截是改快递单地址,INLINE 拦截是直接改包裹内容,DEX 注入是安装新快递系统,Java 函数拦截是替换快递员。
-
攻防平衡:开发者需用加壳技术保护自己的 "快递系统",同时警惕恶意应用的 "快递劫持",通过权限控制和代码混淆加固防御。
整个 Android 安全机制就像一场复杂的快递攻防战,掌握这些技术既能保护自己的应用数据,也能深入理解系统安全的底层原理。