首页
社区
课程
招聘
[原创]《安卓逆向这档事》第二十五课、Unidbg之补完环境我就睡(中)
发表于: 2025-10-4 12:47 4990

[原创]《安卓逆向这档事》第二十五课、Unidbg之补完环境我就睡(中)

2025-10-4 12:47
4990

1.教程 Demo
2.IDEA
3.IDA

文件访问 (File Access) 补环境是 Unidbg 应用中仅次于 JNI 补环境的重要环节。当 Unidbg 模拟执行的原生库(. So 文件)尝试通过文件 I/O 操作访问文件系统时——例如读取设备信息 (/proc/cpuinfo)、检测运行环境 (/proc/self/maps/xbin/su) 或校验自身完整性 (APK 文件)——Unidbg 必须能够对这些访问请求作出响应。在一个纯净的 Unidbg 环境中,这些被访问的目标文件通常是不存在的。如果一个关键的文件访问失败或返回了不符合预期的内容,原生库可能会改变其执行逻辑、触发风控机制或直接中断执行。因此,文件访问补环境的目的就是通过 Unidbg 的 IOResolver 责任链机制拦截这些文件系统调用,并提供一个内容、权限都符合原生库逻辑预期的虚拟文件或目录。

Unidbg 并未将所有文件处理逻辑写死,而是采用了一种灵活的责任链模式。当代码尝试访问一个文件时,请求会依次通过一个处理器链,直到被成功处理或最终失败。
这个处理链的顺序通常是:

resolve 方法中,你可以返回一个 FileResult 对象,它有三种状态:
4. FileResult.success(FileIO): 表示文件访问成功,返回一个 FileIO 对象。这是最常用的方式。
5. FileResult.failed(errno): 表示文件访问失败,并返回一个指定的错误码,如 UnixEmulator.EACCES (权限不足)。
6. FileResult.fallback(): 表示回退,只有当其他所有处理器都无法处理时,才会由它来处理。

处理文件访问需要结合三方面的知识:Linux/Android 文件系统知识、对业务(风控/检测)的经验以及 Unidbg 的实现方法。

当需要返回一个真实存在的文件时,SimpleFileIO 是最佳选择。你需要将文件从真机上导出,并放置在你的项目路径下。
案例:补 boot_id 和 APK 文件
boot_id 常用于生成设备指纹,而 APK 文件访问常用于签名校验或资源读取,两者都必须处理。
boot_id 文件在开机时生成,在设备关机前不会改变内容。我们将这个文件从真机上 push 出来

当文件内容需要动态生成或随机化时(例如躲避基于设备指纹的风控),ByteArrayFileIO 非常有用。它直接接收一个字节数组作为文件内容。
案例:随机化 boot_id 和补 CPU 频率

注意ByteArrayFileIO 不支持写入操作,如果样本需要写入文件,使用它会抛出 UnsupportedOperationException

多数情况下,对于环境检测类的文件(如 su 文件),我们什么都不做,让它自然失败即可。但在某些特殊场景,需要手动返回失败。
案例:模拟无 Root 权限
有些 Root 检测会尝试访问 /data 目录的权限。由于 Unidbg 的虚拟文件系统会自动创建 /data 目录导致访问成功,我们需要手动拦截并返回“权限不足”。

/proc 目录下的文件访问频率极高,且容易出错,需要特殊对待。

maps 文件记录了进程的内存映射,是所有文件访问中处理起来最复杂也最重要的一项。原生库通过读取它来检查环境中是否存在 Frida、Xposed 等 Hook 框架的特征模块,或者获取自身 APK 文件的路径以进行签名校验。

处理 maps 的三种核心策略:
面对 maps 的复杂性,理论上的最佳方案是完全逆向分析其所有检测点,然后构造一份完美的 maps 文件。但在实践中,我们可以采用以下三种更高效的策略,并根据情况灵活选择。

}
```

首先,一个应用程序(例如一个 App 的 .so 文件)通常不会自己从零开始实现所有功能。它会依赖大量由操作系统或第三方提供的共享库(Shared Libraries),在 Android/Linux 中,这些库通常是 .so 文件(Shared Object)。
库函数就是这些共享库中提供的、可供应用程序调用的、预先编译好的函数。

当你的 so 文件调用 fopen 时,它实际上是在请求操作系统加载 libc.so,并执行其中的 fopen 函数代码。

案例 1:补 getProperty
__system_property_get 函数详解
在 Android 系统中,App 获取设备信息(如型号、品牌、系统版本等)最常用、最底层的手段之一就是调用 libc.so 库中的 __system_property_get 函数。理解并控制这个函数,是 Unidbg 环境模拟的重中之重。
1. 它是做什么的?
__system_property_get 是一个 C 函数,原型如下:

小结:
深入讲解了 Unidbg 实现此功能的核心机制——基于责任链模式的 IOResolver,并介绍了如何通过 SimpleFileIO(处理真实文件)、ByteArrayFileIO(动态生成内容)等 FileIO 对象来制定精细化的文件处理策略。课程重点剖析了 /proc/self/maps/proc/self/status 等高频检测文件的处理技巧与策略,强调了按需构造、精准伪造的核心思想。
解释了原生库对 libc.so 等系统共享库函数的依赖,并指出直接 Hook 这些底层函数是伪造设备信息、绕过风控检测的关键。课程以 Android 系统中最常见的属性获取函数 __system_property_get 为例,详细演示了如何使用 Unidbg 自带的 SystemPropertyHook 或更底层的 Dobby 框架来拦截函数调用,从而实现对运行环境的深度伪造。

完整代码:

百度云
阿里云
哔哩哔哩
教程开源地址
PS: 解压密码都是 52 pj,阿里云由于不能分享压缩包,所以下载 exe 文件,双击自解压

// BiliIOResolver.java - 独立的处理器类
public class BiliIOResolver implements IOResolver<AndroidFileIO> {
    @Override
    public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {       
        // 打印所有文件访问请求,无论是否处理
        System.out.println("File open request: " + pathname);
        // 在这里添加具体的文件处理逻辑
        return null; // 返回null表示未处理,交由下一个处理器
    }
}
// Main.java - 在主类中添加处理器
public class Bili extends AbstractJni {
    public Bili() {
        // ...模拟器初始化代码...
        emulator.getSyscallHandler().addIOResolver(new BiliIOResolver());
        // ...加载SO和后续操作...
    }
}
// BiliIOResolver.java - 独立的处理器类
public class BiliIOResolver implements IOResolver<AndroidFileIO> {
    @Override
    public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {       
        // 打印所有文件访问请求,无论是否处理
        System.out.println("File open request: " + pathname);
        // 在这里添加具体的文件处理逻辑
        return null; // 返回null表示未处理,交由下一个处理器
    }
}
// Main.java - 在主类中添加处理器
public class Bili extends AbstractJni {
    public Bili() {
        // ...模拟器初始化代码...
        emulator.getSyscallHandler().addIOResolver(new BiliIOResolver());
        // ...加载SO和后续操作...
    }
}
C:\Users\zhengji>adb shell
* daemon not running; starting now at tcp:5037
* daemon started successfully
vermeer:/ $ cat /proc/sys/kernel/random/boot_id
c7de54b2-f238-481d-b8e1-41c05413b2cd
vermeer:/ $ cp /proc/sys/kernel/random/boot_id /sdcard
vermeer:/ $ exit
 
C:\Users\zhengji>adb pull /sdcard/boot_id D:\unidbg-master\unidbg-android\src\test\resources
/sdcard/boot_id: 1 file pulled, 0 skipped. 0.0 MB/s (37 bytes in 0.012s)
C:\Users\zhengji>adb shell
* daemon not running; starting now at tcp:5037
* daemon started successfully
vermeer:/ $ cat /proc/sys/kernel/random/boot_id
c7de54b2-f238-481d-b8e1-41c05413b2cd
vermeer:/ $ cp /proc/sys/kernel/random/boot_id /sdcard
vermeer:/ $ exit
 
C:\Users\zhengji>adb pull /sdcard/boot_id D:\unidbg-master\unidbg-android\src\test\resources
/sdcard/boot_id: 1 file pulled, 0 skipped. 0.0 MB/s (37 bytes in 0.012s)
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    switch (pathname) {
        // 注意:这里的路径需要和样本访问的完全一致
        case "/proc/sys/kernel/random/boot_id":{
                return FileResult.<AndroidFileIO>success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/resources/cpu/boot_id"), pathname));
            }
    }
    return null;
}
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    switch (pathname) {
        // 注意:这里的路径需要和样本访问的完全一致
        case "/proc/sys/kernel/random/boot_id":{
                return FileResult.<AndroidFileIO>success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/resources/cpu/boot_id"), pathname));
            }
    }
    return null;
}
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    switch (pathname) {
        case "/proc/sys/kernel/random/boot_id": {
            // 动态生成UUID作为boot_id
            String randomBootId = UUID.randomUUID().toString();
            return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, randomBootId.getBytes(StandardCharsets.UTF_8)));
        }
        case "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq": {
            // 直接返回一个字符串作为文件内容
            return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, "1766400".getBytes()));
        }
    }
    return null;
}
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    switch (pathname) {
        case "/proc/sys/kernel/random/boot_id": {
            // 动态生成UUID作为boot_id
            String randomBootId = UUID.randomUUID().toString();
            return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, randomBootId.getBytes(StandardCharsets.UTF_8)));
        }
        case "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq": {
            // 直接返回一个字符串作为文件内容
            return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, "1766400".getBytes()));
        }
    }
    return null;
}
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    if ("/data".equals(pathname)) {
        return FileResult.failed(UnixEmulator.EACCES); // EACCES = 13, Permission denied
    }
    return null;
}
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    if ("/data".equals(pathname)) {
        return FileResult.failed(UnixEmulator.EACCES); // EACCES = 13, Permission denied
    }
    return null;
}
文件路径 (Path) 主要用途 (Purpose) 推荐处理策略 (Recommended Strategy) 注意事项 (Key Points)
/proc/self/cmdline 获取当前进程名 必须补。使用 ByteArrayFileIO 动态生成。 路径中的 pid 需用 emulator.getPid() 动态获取;内容末尾必须包含 \0
/proc/self/status 获取进程状态,检测 TracerPid 必须补。使用 ByteArrayFileIO 动态生成。 TracerPid 必须为 0PidTgid 字段应动态替换为 emulator.getPid()
/proc/self/maps 获取内存映射,检测 frida 等 策略性处理
1. 优先:用真实 maps 文件 (SimpleFileIO)。
2. 备选:若发生内存错误,则不处理,退回使用 Unidbg 默认的 fakeMaps
真实 maps 可能导致内存地址访问异常;fakeMaps 缺少 APK等映射信息。
/data/app/.../base.apk 签名校验、资源读取 必须补。使用 SimpleFileIO 提供真实的 APK 文件。 路径在不同设备上可能不同,注意日志中的实际访问路径。
/proc/sys/kernel/random/boot_id 生成设备指纹 建议补。用 SimpleFileIO (固定) 或 ByteArrayFileIO (随机化)。 有些风控会记录 boot_id,随机化可避免关联。
/sys/.../cpuinfo_max_freq 读取设备硬件信息 建议补。使用 ByteArrayFileIO 提供一个常见值。 直接返回一个字符串即可,如 "2867200"。
/xbin/su, /system/bin/su Root 环境检测 无需处理或显式失败。让其自然失败 (返回 null) 或通过 stat 返回 ENOENT 不要提供一个假文件,让文件访问失败是最真实的无 Root模拟。
/proc/net/tcp, /proc/net/udp 检测代理、抓包工具 强烈建议不补 高版本 Android 已禁止访问。不补能模拟高版本系统行为,且避免暴露主机网络信息。
case "/proc/self/cmdline":  
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, PACKAGE_NAME.getBytes()));
case "/proc/self/cmdline":  
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, PACKAGE_NAME.getBytes()));
case "/proc/self/status"
    // 返回一个包含 "TracerPid: 0" 的文件内容,表示未被调试 
    String statusContent = "Name:\t" + PACKAGE_NAME + "\n"
            "Umask:\t0077\n"
            "State:\tS (sleeping)\n"
            "Tgid:\t12345\n"
            "Pid:\t12345\n"
            "PPid:\t1\n"
            "TracerPid:\t0\n"; // 关键行 
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, statusContent.getBytes()));
case "/proc/self/status"
    // 返回一个包含 "TracerPid: 0" 的文件内容,表示未被调试 
    String statusContent = "Name:\t" + PACKAGE_NAME + "\n"
            "Umask:\t0077\n"
            "State:\tS (sleeping)\n"
            "Tgid:\t12345\n"
            "Pid:\t12345\n"
            "PPid:\t1\n"
            "TracerPid:\t0\n"; // 关键行 
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, statusContent.getBytes()));
// 在 IOResolver 的 resolve 方法中
case "/proc/self/maps": { 
    final String APK_PATH = "/data/app/com.zj.wuaipojie-1/base.apk"
    String maps = "7fbe852000-7fbe853000 r-xp 00000000 00:00 0 " + APK_PATH + "\n"
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, maps.getBytes(StandardCharsets.UTF_8)));
// 在 IOResolver 的 resolve 方法中
case "/proc/self/maps": { 
    final String APK_PATH = "/data/app/com.zj.wuaipojie-1/base.apk"
    String maps = "7fbe852000-7fbe853000 r-xp 00000000 00:00 0 " + APK_PATH + "\n"
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, maps.getBytes(StandardCharsets.UTF_8)));
int __system_property_get(const char* name, char* value);
int __system_property_get(const char* name, char* value);
// 方式1:使用unidbg自带类 SystemPropertyHook
SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
    @Override
    public String getProperty(String key) {
        // 在这里根据请求的key,返回伪造的value
        switch (key){
            case "ro.board.platform": {
                return "kalama";
            }
            case "ro.product.model": {
                return "23113RKC6C";
            }
        }
        // 如果返回null,则会尝试使用Unidbg的默认属性
        return null;
    }
});
// 将Hook注册到内存中
memory.addHookListener(systemPropertyHook);
 
 
// 方式2:使用Dobby直接Hook原生C函数
private void hookSystemPropertyGet() {
    Module libc = emulator.getMemory().findModule("libc.so");
    Symbol propertyGetSymbol = libc.findSymbolByName("__system_property_get");
 
    Dobby dobby = Dobby.getInstance(emulator);
    dobby.replace(propertyGetSymbol, new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            // C函数原型: int __system_property_get(const char *key, char *value);
            // 手动从寄存器获取参数指针
            UnidbgPointer keyPtr = context.getPointerArg(0);
            UnidbgPointer valuePtr = context.getPointerArg(1);
 
            String key = keyPtr.getString(0);
            String value = null;
 
            // 判断关心的key
            if ("ro.board.platform".equals(key)) {
                value = "kalama";
            } else if ("ro.product.model".equals(key)) {
                value = "23113RKC6C";
            }
 
            // 如果是我们处理的key
            if (value != null) {
                System.out.println("[HOOK] __system_property_get: Intercepted key=" + key + ", Faking value=" + value);
                // 将伪造的值写入到so传入的value内存缓冲区
                byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
                valuePtr.write(0, valueBytes, 0, valueBytes.length);
                valuePtr.setByte(valueBytes.length, (byte) 0); // 写入字符串结束符'\0'
 
                // 使用HookStatus.LR()直接返回,不再执行原始函数
                // 返回值是字符串的长度
                return HookStatus.LR(emulator, valueBytes.length);
            }
 
            // 如果不是我们关心的key,则放行,执行原始的__system_property_get函数
            return HookStatus.RET(emulator, originFunction);
        }
    });
}
// 方式1:使用unidbg自带类 SystemPropertyHook
SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
    @Override
    public String getProperty(String key) {
        // 在这里根据请求的key,返回伪造的value
        switch (key){
            case "ro.board.platform": {
                return "kalama";
            }
            case "ro.product.model": {
                return "23113RKC6C";
            }
        }
        // 如果返回null,则会尝试使用Unidbg的默认属性
        return null;
    }
});
// 将Hook注册到内存中
memory.addHookListener(systemPropertyHook);
 
 
// 方式2:使用Dobby直接Hook原生C函数
private void hookSystemPropertyGet() {
    Module libc = emulator.getMemory().findModule("libc.so");
    Symbol propertyGetSymbol = libc.findSymbolByName("__system_property_get");
 
    Dobby dobby = Dobby.getInstance(emulator);
    dobby.replace(propertyGetSymbol, new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            // C函数原型: int __system_property_get(const char *key, char *value);
            // 手动从寄存器获取参数指针
            UnidbgPointer keyPtr = context.getPointerArg(0);
            UnidbgPointer valuePtr = context.getPointerArg(1);
 
            String key = keyPtr.getString(0);
            String value = null;
 
            // 判断关心的key
            if ("ro.board.platform".equals(key)) {
                value = "kalama";
            } else if ("ro.product.model".equals(key)) {
                value = "23113RKC6C";
            }
 
            // 如果是我们处理的key
            if (value != null) {
                System.out.println("[HOOK] __system_property_get: Intercepted key=" + key + ", Faking value=" + value);
                // 将伪造的值写入到so传入的value内存缓冲区
                byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
                valuePtr.write(0, valueBytes, 0, valueBytes.length);
                valuePtr.setByte(valueBytes.length, (byte) 0); // 写入字符串结束符'\0'
 
                // 使用HookStatus.LR()直接返回,不再执行原始函数
                // 返回值是字符串的长度
                return HookStatus.LR(emulator, valueBytes.length);
            }
 
            // 如果不是我们关心的key,则放行,执行原始的__system_property_get函数
            return HookStatus.RET(emulator, originFunction);
        }
    });
}
package com.zj.wuaipojie.util; 
   
import com.github.unidbg.AndroidEmulator; 
import com.github.unidbg.Emulator; 
import com.github.unidbg.Module; 
import com.github.unidbg.arm.HookStatus; 
import com.github.unidbg.file.FileResult; 
import com.github.unidbg.file.IOResolver; 
import com.github.unidbg.file.linux.AndroidFileIO; 
import com.github.unidbg.hook.HookContext; 
import com.github.unidbg.hook.ReplaceCallback; 
import com.github.unidbg.hook.hookzz.Dobby; 
import com.github.unidbg.linux.android.AndroidEmulatorBuilder; 
import com.github.unidbg.linux.android.AndroidResolver; 
import com.github.unidbg.linux.android.SystemPropertyHook; 
import com.github.unidbg.linux.android.SystemPropertyProvider; 
import com.github.unidbg.linux.android.dvm.*; 
import com.github.unidbg.linux.android.dvm.array.ArrayObject; 
import com.github.unidbg.linux.file.ByteArrayFileIO; 
import com.github.unidbg.linux.file.SimpleFileIO; 
import com.github.unidbg.memory.Memory; 
import com.github.unidbg.pointer.UnidbgPointer; 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.nio.charset.StandardCharsets; 
   
public class ChallengeTenTwo implements IOResolver<AndroidFileIO> { 
   
    private final AndroidEmulator emulator; 
    private final VM vm; 
    private final Module module; 
    // 将所有需要伪造的数据定义为常量,方便管理和修改。 
    private static final String PACKAGE_NAME = "com.zj.wuaipojie\0"
    private static final String EXPECTED_BOOT_ID = "8442043f-98e2-48bb-8707-525e98a66898"
    private static final String EXPECTED_BOARD_PLATFORM = "kalama"
    private static final String EXPECTED_PHONE_MODEL = "23113RKC6C"
    private static final String EXPECTED_SIG =  "3082034f30820237a00302010202046c07fed4300d06092a864886f70d01010b05003058310f300d06035504061306303030303030310b300906035504081302626a310b300906035504071302626a310c300a060355040a13036f727a310c300a060355040b13036f727a310f300d06035504030c06e6ada3e5b7b1301e170d3232303832313132323934305a170d3437303831353132323934305a3058310f300d06035504061306303030303030310b300906035504081302626a310b300906035504071302626a310c300a060355040a13036f727a310c300a060355040b13036f727a310f300d06035504030c06e6ada3e5b7b130820122300d06092a864886f70d01010105000382010f003082010a0282010100e6f5d0394247b0a7bb02daedeb78f4d373d394cde087c8edbd9f5fa87781f88ed733f822c10e6573a4f9fa52c46fa7cbc48136f35f0a554fe6ab6c605cab00c1edaa18ab2d9d052968413053fc72c945e9be05dea00d3ebb9a2404d2fd2e5520d784d739de28b9a3611c7ccc51df75e057d054c984931627381d572ff545047ae089d36a99ac5866aa236e971fa322e500335474ae5932142b14464e000605c53ce902a53aaba2bcd576a255db6913c849e144d4cb83ca9043078160025219205ef859f2611c89a168f153dff6020a992a77fe2ab8d37ec3d65e97a4c74577f2f5c6ae690afe26a2f97de7697046ae1ef18c986d437c77a83e22c272485487bd0203010001a321301f301d0603551d0e04160414cd08d93fc2483c6afa2e182a5357acbc6cc21e17300d06092a864886f70d01010b05000382010100a46400714e97485805062c6fe392f61a52f52083b616a170affb043cc3272332132adfe55db2b13abc7bbbcbf2ae83741e767d196deb3b924cd7037ed6afa5360ea10505ed8ba37c33ce7ac5a24aa6192b49c02ad711b99c1eec185aed272671c3dfdc7df5c3c0511b41b581c94ddf1076e397b27a75a0b73e5e207f4ec636067540b85e3e750ce59599ddb1d954e4537275d27227f2db9eb2fa3a2a29ea1c8a5415d6058e798517ebec4194cfb8715e7c17c043c0309c7e9dd7c17c502b3dd27153929ba5462182a0aa58c15972bc75a7ede1849ef5c9341d0ca747c9fb41ccabfa2c04c1147ea884cf951481bca37ffe929552962b35cbffff21675e4d5581"
   
    public ChallengeTenTwo() { 
   
        emulator = AndroidEmulatorBuilder.for64Bit() 
                .setProcessName(PACKAGE_NAME) 
                .build(); 
   
        final Memory memory = emulator.getMemory(); 
        memory.setLibraryResolver(new AndroidResolver(23)); 
   
        // 注册 IOResolver 来处理文件访问 
        emulator.getSyscallHandler().addIOResolver(this); 
   
        // 创建 VM        vm = emulator.createDalvikVM(); 
        //vm.setVerbose(true); 
//        SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator); 
//        systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() { 
//            @Override 
//            public String getProperty(String key) { 
//                switch (key){ 
//                    case "ro.board.platform":{ 
//                        return "kalama"; 
//                    } 
//                    case "ro.product.model":{ 
//                        return "23113RKC6C"; 
//                    } 
//                } 
//                return null; 
//            } 
//        }); 
//        memory.addHookListener(systemPropertyHook); 
   
   
   
        vm.setJni(new MyJni(vm)); 
        // 加载 so 并执行 JNI_OnLoad        File soFile = new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"); 
        DalvikModule dm = vm.loadLibrary(soFile, true); 
        module = dm.getModule(); 
        dm.callJNI_OnLoad(emulator); 
   
        // 【关键】Hook libc.so 中的 __system_property_get 函数 
        hookSystemPropertyGet(); 
    
   
   
    /** 
     * Hook __system_property_get 函数,用于伪造系统属性 
     */ 
   
    private void hookSystemPropertyGet() { 
        Module libc = emulator.getMemory().findModule("libc.so"); 
        if (libc == null) { 
            throw new IllegalStateException("Failed to find libc.so"); 
        
        long address = libc.findSymbolByName("__system_property_get").getAddress(); 
   
        Dobby dobby = Dobby.getInstance(emulator); 
        dobby.replace(address, new ReplaceCallback() { 
            @Override 
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { 
                UnidbgPointer keyPtr = context.getPointerArg(0); 
                UnidbgPointer valuePtr = context.getPointerArg(1); 
   
                String key = keyPtr.getString(0); 
                String value = null
   
                if ("ro.board.platform".equals(key)) { 
                    value = EXPECTED_BOARD_PLATFORM; 
                    System.out.println("[HOOK] __system_property_get: Intercepted key=" + key + ", Faking value=" + value); 
                } else if ("ro.product.model".equals(key)) { 
                    value = EXPECTED_PHONE_MODEL; 
                    System.out.println("[HOOK] __system_property_get: Intercepted key=" + key + ", Faking value=" + value); 
                
   
                if (value != null) { 
                    byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8); 
                    valuePtr.write(0, valueBytes, 0, valueBytes.length); 
                    valuePtr.setByte(valueBytes.length, (byte) 0); 
                    return HookStatus.LR(emulator, valueBytes.length); 
                
   
                return HookStatus.RET(emulator, originFunction); 
            
        }); 
    
   
   
   
   
    public void callUnidbgLevel2() { 
        System.out.println("====== 开始执行 unidbg_level2 函数 ======"); 
        DvmClass securityUtilClass = vm.resolveClass("com/zj/wuaipojie/util/SecurityUtil"); 
        DvmClass ctxClz = vm.resolveClass("android/content/Context"); 
        DvmObject<?> mockCtx = ctxClz.newObject(null); 
        StringObject result = securityUtilClass.callStaticJniMethodObject( 
                emulator, "unidbg_level2(Landroid/content/Context;)Ljava/lang/String;"
                mockCtx); 
        System.out.println("JNI 函数返回结果: " + (result != null ? result.getValue() : null)); 
   
    
   
   
   
    public static void main(String[] args) throws FileNotFoundException { 
        ChallengeTenTwo challenge = new ChallengeTenTwo(); 
        challenge.callUnidbgLevel2(); 
    
   
   
    @Override 
    public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) { 
        // 无论是否处理,都打印出来,这是发现未知文件访问的关键。 
        System.out.println("[IOResolver] Intercepted file access -> Path: '" + pathname + "', Flags: " + oflags); 
   
        // 使用 switch 语句来处理不同的文件路径 
        switch (pathname) { 
            // 关卡1: 校验 /proc/sys/kernel/random/boot_id            case "/proc/sys/kernel/random/boot_id": 
                // 返回一个包含预设 boot_id 的文件 
                return FileResult.success(new ByteArrayFileIO(oflags, pathname, EXPECTED_BOOT_ID.getBytes())); 
   
//            // 关卡2: 校验 /proc/self/cmdline            case "/proc/self/cmdline": 
                return FileResult.success(new ByteArrayFileIO(oflags, pathname, PACKAGE_NAME.getBytes())); 
   
//            // 关卡3: 校验 /proc/self/status,反调试检测 
            case "/proc/self/status"
                // 返回一个包含 "TracerPid: 0" 的文件内容,表示未被调试 
                String statusContent = "Name:\t" + PACKAGE_NAME + "\n"
                        "Umask:\t0077\n"
                        "State:\tS (sleeping)\n"
                        "Tgid:\t12345\n"
                        "Pid:\t12345\n"
                        "PPid:\t1\n"
                        "TracerPid:\t0\n"; // 关键行 
                return FileResult.success(new ByteArrayFileIO(oflags, pathname, statusContent.getBytes())); 
   
//            // 关卡4: 校验 /proc/self/maps,应用完整性检测 
            case "/proc/self/maps": { 
                final String APK_PATH = "/data/app/com.zj.wuaipojie-1/base.apk"
                String maps = "7fbe852000-7fbe853000 r-xp 00000000 00:00 0 " + APK_PATH + "\n"
                return FileResult.success(new ByteArrayFileIO(oflags, pathname, maps.getBytes(StandardCharsets.UTF_8))); 
            
   
        
   
        // 对于其他未处理的文件路径,返回 null,让 unidbg 使用默认的文件处理逻辑 
        return null
    
    /** 自定义 JNI,伪造 PackageManager / PackageInfo / Signature */ 
    static class MyJni extends AbstractJni { 
        private final VM vm; 
        MyJni(VM vm) { this.vm = vm; } 
        @Override 
        public DvmObject<?> callObjectMethodV(BaseVM baseVm, DvmObject<?> obj, String signature, VaList vaList) { 
            // Context.getPackageManager() 
            if (signature.equals("android/content/Context->getPackageManager()Landroid/content/pm/PackageManager;")) { 
                DvmClass pmClz = vm.resolveClass("android/content/pm/PackageManager"); 
                return pmClz.newObject("mockPM"); 
            
   
            // PackageManager.getPackageArchiveInfo(String, int) -> PackageInfo 
            if (signature.equals("android/content/pm/PackageManager->getPackageArchiveInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;")) { 
                StringObject apkPathObj = vaList.getObjectArg(0); 
                int flags = vaList.getIntArg(1); // 64 (GET_SIGNATURES) 
                // 这里不做文件解析,直接返回一个填好字段的 PackageInfo                DvmClass pkgInfoClz = vm.resolveClass("android/content/pm/PackageInfo"); 
                // 用 Map 保存字段,后续在 getObjectField 里按字段名返回 
                java.util.HashMap<String, Object> fields = new java.util.HashMap<>(); 
                fields.put("packageName", new StringObject(vm, PACKAGE_NAME)); 
   
                // signatures: [Landroid/content/pm/Signature; 
                DvmClass sigClz = vm.resolveClass("android/content/pm/Signature"); 
                DvmObject<?> sigObj = sigClz.newObject("mockSignature"); // 值随便放个占位 
                ArrayObject sigArr = new ArrayObject(sigObj); 
                fields.put("signatures", sigArr); 
   
                return pkgInfoClz.newObject(fields); 
            
   
            // Signature.toCharsString() -> String 
            if (signature.equals("android/content/pm/Signature->toCharsString()Ljava/lang/String;")) { 
                return new StringObject(vm, EXPECTED_SIG); 
            
   
            return super.callObjectMethodV(baseVm, obj, signature, vaList); 
        
   
        @Override 
        public DvmObject<?> getObjectField(BaseVM baseVm, DvmObject<?> obj, String signature) { 
            // PackageInfo.packageName 
            if (signature.equals("android/content/pm/PackageInfo->packageName:Ljava/lang/String;")) { 
                Object v = ((java.util.Map<?, ?>) obj.getValue()).get("packageName"); 
                return (DvmObject<?>) v; 
            
            // PackageInfo.signatures 
            if (signature.equals("android/content/pm/PackageInfo->signatures:[Landroid/content/pm/Signature;")) { 
                Object v = ((java.util.Map<?, ?>) obj.getValue()).get("signatures"); 
                return (DvmObject<?>) v; 
            
            return super.getObjectField(baseVm, obj, signature); 
        
    
}
package com.zj.wuaipojie.util; 
   
import com.github.unidbg.AndroidEmulator; 
import com.github.unidbg.Emulator; 
import com.github.unidbg.Module; 
import com.github.unidbg.arm.HookStatus; 
import com.github.unidbg.file.FileResult; 
import com.github.unidbg.file.IOResolver; 
import com.github.unidbg.file.linux.AndroidFileIO; 
import com.github.unidbg.hook.HookContext; 
import com.github.unidbg.hook.ReplaceCallback; 
import com.github.unidbg.hook.hookzz.Dobby; 
import com.github.unidbg.linux.android.AndroidEmulatorBuilder; 
import com.github.unidbg.linux.android.AndroidResolver; 
import com.github.unidbg.linux.android.SystemPropertyHook; 
import com.github.unidbg.linux.android.SystemPropertyProvider; 
import com.github.unidbg.linux.android.dvm.*; 
import com.github.unidbg.linux.android.dvm.array.ArrayObject; 
import com.github.unidbg.linux.file.ByteArrayFileIO; 
import com.github.unidbg.linux.file.SimpleFileIO; 
import com.github.unidbg.memory.Memory; 
import com.github.unidbg.pointer.UnidbgPointer; 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.nio.charset.StandardCharsets; 
   
public class ChallengeTenTwo implements IOResolver<AndroidFileIO> { 
   
    private final AndroidEmulator emulator; 
    private final VM vm; 
    private final Module module; 
    // 将所有需要伪造的数据定义为常量,方便管理和修改。 
    private static final String PACKAGE_NAME = "com.zj.wuaipojie\0"
    private static final String EXPECTED_BOOT_ID = "8442043f-98e2-48bb-8707-525e98a66898"
    private static final String EXPECTED_BOARD_PLATFORM = "kalama"
    private static final String EXPECTED_PHONE_MODEL = "23113RKC6C"
    private static final String EXPECTED_SIG =  "3082034f30820237a00302010202046c07fed4300d06092a864886f70d01010b05003058310f300d06035504061306303030303030310b300906035504081302626a310b300906035504071302626a310c300a060355040a13036f727a310c300a060355040b13036f727a310f300d06035504030c06e6ada3e5b7b1301e170d3232303832313132323934305a170d3437303831353132323934305a3058310f300d06035504061306303030303030310b300906035504081302626a310b300906035504071302626a310c300a060355040a13036f727a310c300a060355040b13036f727a310f300d06035504030c06e6ada3e5b7b130820122300d06092a864886f70d01010105000382010f003082010a0282010100e6f5d0394247b0a7bb02daedeb78f4d373d394cde087c8edbd9f5fa87781f88ed733f822c10e6573a4f9fa52c46fa7cbc48136f35f0a554fe6ab6c605cab00c1edaa18ab2d9d052968413053fc72c945e9be05dea00d3ebb9a2404d2fd2e5520d784d739de28b9a3611c7ccc51df75e057d054c984931627381d572ff545047ae089d36a99ac5866aa236e971fa322e500335474ae5932142b14464e000605c53ce902a53aaba2bcd576a255db6913c849e144d4cb83ca9043078160025219205ef859f2611c89a168f153dff6020a992a77fe2ab8d37ec3d65e97a4c74577f2f5c6ae690afe26a2f97de7697046ae1ef18c986d437c77a83e22c272485487bd0203010001a321301f301d0603551d0e04160414cd08d93fc2483c6afa2e182a5357acbc6cc21e17300d06092a864886f70d01010b05000382010100a46400714e97485805062c6fe392f61a52f52083b616a170affb043cc3272332132adfe55db2b13abc7bbbcbf2ae83741e767d196deb3b924cd7037ed6afa5360ea10505ed8ba37c33ce7ac5a24aa6192b49c02ad711b99c1eec185aed272671c3dfdc7df5c3c0511b41b581c94ddf1076e397b27a75a0b73e5e207f4ec636067540b85e3e750ce59599ddb1d954e4537275d27227f2db9eb2fa3a2a29ea1c8a5415d6058e798517ebec4194cfb8715e7c17c043c0309c7e9dd7c17c502b3dd27153929ba5462182a0aa58c15972bc75a7ede1849ef5c9341d0ca747c9fb41ccabfa2c04c1147ea884cf951481bca37ffe929552962b35cbffff21675e4d5581"
   
    public ChallengeTenTwo() { 
   
        emulator = AndroidEmulatorBuilder.for64Bit() 
                .setProcessName(PACKAGE_NAME) 
                .build(); 
   
        final Memory memory = emulator.getMemory(); 
        memory.setLibraryResolver(new AndroidResolver(23)); 
   
        // 注册 IOResolver 来处理文件访问 
        emulator.getSyscallHandler().addIOResolver(this); 
   
        // 创建 VM        vm = emulator.createDalvikVM(); 
        //vm.setVerbose(true); 
//        SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator); 
//        systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() { 
//            @Override 
//            public String getProperty(String key) { 
//                switch (key){ 
//                    case "ro.board.platform":{ 
//                        return "kalama"; 
//                    } 
//                    case "ro.product.model":{ 
//                        return "23113RKC6C"; 
//                    } 
//                } 
//                return null; 
//            } 
//        }); 
//        memory.addHookListener(systemPropertyHook); 
   
   
   
        vm.setJni(new MyJni(vm)); 
        // 加载 so 并执行 JNI_OnLoad        File soFile = new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"); 
        DalvikModule dm = vm.loadLibrary(soFile, true); 
        module = dm.getModule(); 
        dm.callJNI_OnLoad(emulator); 
   
        // 【关键】Hook libc.so 中的 __system_property_get 函数 
        hookSystemPropertyGet(); 
    

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 40
支持
分享
最新回复 (22)
雪    币: 3032
活跃值: (3884)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2025-10-4 14:54
0
雪    币: 204
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
nb
2025-10-4 18:06
0
雪    币: 0
活跃值: (2439)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2025-10-7 14:15
0
雪    币: 198
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
高手
2025-10-7 14:32
0
雪    币: 27
活跃值: (1328)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
其他集呢,不全呢
2025-10-8 23:26
0
雪    币: 5622
活跃值: (4238)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
7
cuituo 其他集呢,不全呢
前面的内容太简单了,就没移动过来,要看可以去吾爱
2025-10-9 10:36
0
雪    币: 104
活跃值: (7159)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
tql
2025-10-9 15:09
0
雪    币: 387
活跃值: (1536)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
tql
2025-10-9 16:20
0
雪    币: 425
活跃值: (1155)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Da_
10
666
2025-10-9 16:32
0
雪    币: 228
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
tql
2025-10-10 13:55
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
6
2025-10-10 15:48
0
雪    币: 3368
活跃值: (6470)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
13
睡了吗
2025-10-11 16:55
0
雪    币: 5622
活跃值: (4238)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
14
Thehepta 睡了吗[em_014]
睡了,睡得很安详
2025-10-12 09:19
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
谢谢分享
2025-10-13 18:24
0
雪    币: 52
活跃值: (1023)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
1
2025-10-13 19:44
0
雪    币: 180
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
666
2025-10-16 21:57
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
11
2025-10-26 00:51
0
雪    币: 217
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
666
2025-11-4 19:45
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
谢谢
2025-11-17 21:55
0
雪    币: 764
活跃值: (2897)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
666
2025-11-18 21:57
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
66
2025-12-1 14:36
0
雪    币: 203
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
超赞
2025-12-12 11:06
0
游客
登录 | 注册 方可回帖
返回