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 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡2 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi002 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil4 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你4 小时前
Android View的绘制原理详解
android
移动开发者1号7 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号7 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best12 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk12 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin