Android 安全机制:应用沙箱与攻防技术的 "快递战"

一、沙箱快递系统: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 劫持技术

  1. 强制闯入(PTRACE_ATTACH) :用 ptrace 接管目标进程,类似强行进入别家快递站:

cpp

运行

scss 复制代码
ptrace(PTRACE_ATTACH, targetPid, NULL, NULL); // 附着目标进程
waitpid(targetPid, &status, 0); // 等待进程暂停
  1. 伪造快递单(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
  1. 启动潜入快递员(dlopen 加载 SO) :让目标进程加载你的 SO,如同雇佣你的快递员:

cpp

运行

ini 复制代码
void* handle = dlopen("/data/your.so", RTLD_NOW); // 加载SO
void* func = dlsym(handle, "hookFunction"); // 获取钩子函数

三、SO 加壳:加密快递包裹

SO 加壳是给 SO 文件穿上 "密码箱",防止被逆向分析,核心是内存直接加载:

3.1 密码箱快递流程

  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文件
}
  1. 重建快递路线(内存重定位) :在内存中重建 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); // 复制数据
  1. 密码箱钥匙(动态 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); // 结果转换
    }
}

七、总结:安全攻防的快递战法则

  1. 沙箱防御:UID/GID 如同快递站的物理隔离,Permission 是快递类型许可,确保每个应用只能处理自己的包裹。

  2. SO 注入与加壳:SO 注入是潜入别家快递站安插内鬼,SO 加壳是给内鬼穿上隐身衣,防止被发现。

  3. 函数拦截:GOT 拦截是改快递单地址,INLINE 拦截是直接改包裹内容,DEX 注入是安装新快递系统,Java 函数拦截是替换快递员。

  4. 攻防平衡:开发者需用加壳技术保护自己的 "快递系统",同时警惕恶意应用的 "快递劫持",通过权限控制和代码混淆加固防御。

整个 Android 安全机制就像一场复杂的快递攻防战,掌握这些技术既能保护自己的应用数据,也能深入理解系统安全的底层原理。

相关推荐
敲代码的剑缘一心1 小时前
手把手教你学会写 Gradle 插件
android·gradle
青蛙娃娃1 小时前
漫画Android:动画是如何实现的?
android·android studio
aningxiaoxixi2 小时前
android 之 CALL
android
用户2018792831673 小时前
Android 核心大管家 ActivityManagerService (AMS)
android
春马与夏4 小时前
Android自动化AirScript
android·运维·自动化
键盘歌唱家5 小时前
mysql索引失效
android·数据库·mysql
webbin5 小时前
Compose @Immutable注解
android·android jetpack
无知的前端6 小时前
Flutter开发,GetX框架路由相关详细示例
android·flutter·ios
玲小珑6 小时前
Auto.js 入门指南(十二)网络请求与数据交互
android·前端
webbin6 小时前
Compose 副作用
android·android jetpack