首页
社区
课程
招聘
[原创]《安卓逆向这档事》第二十六课、Unidbg之补完环境我就睡(下)
发表于: 2025-10-9 10:41 5108

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

2025-10-9 10:41
5108

1.教程 Demo
2.IDEA
3.IDA

系统调用(Syscall)补环境是 Unidbg 模拟执行中一个基础且高级的环节。当原生库(. So 文件)为了性能、对抗或功能需要,绕过标准库(libc)函数,通过 SVC 等底层指令直接向操作系统内核发起请求时,Unidbg 必须能够拦截并模拟这些内核级别的行为。由于 Unidbg 并非一个完整的操作系统,其对内核的模拟是不完备的。如果 so 文件请求了一个 Unidbg 未实现或模拟不完善的系统调用(通过系统调用号 NR 区分),通常会导致模拟流程出错、返回无效数据(如 fstat 对目录返回全零),或进入非预期的逻辑分支,最终使模拟失败或结果失真。因此,系统调用补环境的目的就是识别并接管这些对内核的底层请求,通过自定义 SyscallHandler 提供一个符合目标 so 逻辑预期的模拟行为或返回值,从而“欺骗”so 文件,使其相信自己正与一个真实的 Linux/Android 内核交互。

当 unidbg 遇到一个它无法处理的软中断(SVC)时,通常会打印出如下格式的 WARN 日志:

这个日志包含了定位问题的关键信息:

并且,JNI 调用失败通常会紧跟着抛出 UnsupportedOperationException,并带有清晰的 JNI 方法签名。

解决系统调用问题的通用范式是创建一个继承自 unidbg 原生 SyscallHandler 的子类,并重写或补充其中的方法。
首先,你需要定义一个自己的 SyscallHandler。以 ARM 64 为例:

然后,在构建 Emulator 时,通过 AndroidEmulatorBuilder 将其替换掉默认的处理器。

这是最常见的情况,unidbg 对某个 NR 完全没有实现,直接在 handleInterrupt 处抛出警告。
案例 1:getcpu (NR=168)

在我们的 MySyscallHandler 中,重写 handleUnknownSyscall 方法,捕获未被处理的 NR

案例 2:statx (NR=291)
statx 是 Linux 中一个现代化的、用于获取文件元数据(metadata)的系统调用。它是 stat, fstat, lstat 的继任者和升级版。

文件的元数据,就是描述文件属性的信息,例如:

statx 相对于旧版 stat 的主要优势:
1.更丰富的信息statx 可以获取旧版 stat 无法提供的信息,最典型的就是文件的创建时间(birth time, btime
2.更高精度的时间戳:旧版 stat 的时间戳只精确到秒。而 statx 可以提供**纳秒(nanosecond)级别的精度,这对于现代文件系统和应用至关重要。
3.更高的效率和灵活性 (Mask 机制)
:调用旧版 stat 时,内核会把所有元数据一次性全返回给你,即使你只关心文件大小。statx 引入了一个 mask(掩码)参数,允许调用者明确告诉内核:“我只对文件大小和修改时间感兴趣”。内核就会只获取并返回这些信息,避免了不必要的工作,提高了效率
4.更好的扩展性:它的结构体设计考虑了未来,留有备用字段,方便以后添加新的文件属性而不需要再次设计新的系统调用。
如何填充statx结构:

接下来就是把上面的结构发给 AI 来伪造

Unidbg 实现了该系统调用,但存在缺陷。

案例 1:clock_gettime (NR=113, ARM 64)
clock_gettime(clockid_t clk_id, struct timespec *tp) 用于获取特定时钟的时间。Unidbg 实现了对 CLOCK_REALTIME (clk_id=0) 的处理,但没有实现 CLOCK_PROCESS_CPUTIME_ID (clk_id=2),导致传入 2 时抛出异常。
解决方案:重写 clock_gettime 方法。

案例 2: sched_getaffinity (NR=123)

有时候,我们会发现某个系统调用的实现在 Unidbg 的父类 ARM64SyscallHandler 中,但该实现方法被声明为 final,导致我们无法像 clock_gettime 那样直接 @Override 它。此外,有些系统调用是在一个巨大的 switch 语句(如 handleSyscall 方法)中处理的,我们只想修改其中一个 case 的行为,同样无法直接重写。

在这种情况下,我们需要在更早的阶段介入。系统调用的最顶层入口是 hook() 方法,它负责接收所有 SVC 中断,解析出系统调用号(NR),然后再分发给具体的处理方法。通过重写 hook(),我们可以在 Unidbg 分发之前“截胡”我们关心的系统调用,实现自定义逻辑。

sched_getaffinity 用于检测当前进程可以运行在哪些 CPU 核心上,这常被用于环境检测或设备指纹生成。
解决方案:hook() 方法中,抢先处理目标 NR,然后“屏蔽”它,避免父类重复执行。

案例 3:fstat (NR=80)

fstatstatx 的核心目标都是获取文件元数据,但它们在设计、功能和使用方式上存在显著差异。简单来说,statxfstat 的现代、功能更全面的“超级升级版”

对照着实现,并伪造一些模拟数据

有些库函数(如 popen)的实现依赖一整套复杂的系统调用(vfork, pipe2, dup2 / dup3, execve, wait4 等),这些都和进程创建、IPC(进程间通信)相关,是 unidbg 的弱项。尝试逐个修复这些系统调用会陷入泥潭。
因此,我们采用一种更高级、更高效的“组合拳”策略:在高层 Hook 函数意图,在底层伪造结果

案例:修复 popen
popen 函数会执行一个 shell 命令,并创建一个管道,返回一个文件流指针,后续代码可以通过 fread 等函数从这个文件流中读取命令的输出结果。

**第一步:使用 xHook 拦截 popen 调用意图

使用 unidbg 的 Hook 功能(如 xhook),通过 xHook 拦截对 popen 的调用,主要目的不是替换它,而是获取它将要执行的命令字符串,并将其存入 Unidbg 的上下文中,供后续的底层 SyscallHandler 使用。

第二步:在 SyscallHandler 中伪造关键系统调用的结果
当原始的 popen 函数执行时,它会触发一系列系统调用。我们不需要全部实现它们,只需要伪造其中最关键的几个,形成一个逻辑闭环即可。
1. 伪造 pipe2 (NR=59) - 创建通信管道
popen 首先会调用 pipe2 来创建一个管道,这个管道包含一个“读”端和一个“写”端。这是我们注入伪造结果的最佳时机。

2. 伪造 fork - 创建子进程

popen 接着会调用 fork 来创建子进程。在 Unidbg 中,我们无需真的创建一个进程。fork 系统调用的特点是:在父进程中返回子进程的 PID(一个正整数),在子进程中返回 0。由于 popen 的后续流程在父进程中,我们只需要让它以为子进程创建成功了即可。

通过以上组合拳,popen 的执行流程被我们完美地“欺骗”了:
1.它调用 pipe2,得到了两个文件描述符。
2.它调用 fork,得到了一个看似成功的子进程 PID。
3.接下来,父进程会关闭管道的写端,然后从读端 read_fd 读取数据。
4.当它读取 read_fd 时,实际上读取的是我们预设在 ByteArrayFileIO 中的 stdout 字符串。
最终,so 获取到了我们想让它获取的任何结果,而我们完全没有涉及复杂的多进程模拟。

案例 2:补 gettid
gettid 函数详解
在 Linux 内核中,每个线程都有一个唯一的标识符,即线程 ID(TID)。gettid 函数是应用程序获取这个底层 ID 最直接的方式。
1. 它是做什么的?
gettid 是一个 Linux 特有的 C 函数,原型如下:

2. 为什么它在补环境中如此重要?
gettid 是风控和反调试检测的“常客”,因为它能揭示程序运行的线程环境,而 Unidbg 的线程模型与真实设备有本质区别。检测点通常包括:

如果不修补 gettid,Unidbg 环境下的返回值很可能无法通过上述校验,导致 so 认为自己运行在异常线程或模拟器中,从而改变执行路径或直接退出。

3. 修补方法
对于 gettid,最直接有效的修补方式就是使用 Dobby 进行 Inline Hook,强制它返回一个我们期望的值。通常,我们会让它返回当前的进程 ID(PID),以完美模拟“主线程”的行为。

小结:
深入探讨了 Unidbg 中三种高级 Hook 技术以应对复杂的模拟挑战。首先,介绍了针对不同场景(如 final 方法)的系统调用 Hook 策略,通过重写顶层 hook() 方法实现精准拦截。演示了通过高层函数 Hook(xHook popen)与底层 Syscall 伪造(pipe2, fork)相结合的“组合拳”策略,高效模拟复杂调用链。最后,展示了如何应用 Dobby 直接修补 C 库函数(gettid)以绕过环境一致性检测。

放点gemini-2.5-pro的key给表哥们玩玩

完整代码:
ChallengeTenThree:

MySyscallHandler:

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

白龙unidbg教程

[00:46:49 186]  WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:399) - handleInterrupt intno=2, NR=165, svcNumber=0x0, PC=RX@0x401ba3d4[libc.so]0x6a3d4, LR=RX@0x40000770[libdemo.so]0x770, syscall=null
[00:46:49 186]  WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:399) - handleInterrupt intno=2, NR=165, svcNumber=0x0, PC=RX@0x401ba3d4[libc.so]0x6a3d4, LR=RX@0x40000770[libdemo.so]0x770, syscall=null
[01:00:43 681]  WARN [com.github.unidbg.linux.ARM64SyscallHandler] ... svcNumber=0x16f ...
java.lang.UnsupportedOperationException: com/aliyun/TigerTally/A->ct()Landroid/content/Context;
[01:00:43 681]  WARN [com.github.unidbg.linux.ARM64SyscallHandler] ... svcNumber=0x16f ...
java.lang.UnsupportedOperationException: com/aliyun/TigerTally/A->ct()Landroid/content/Context;
import com.github.unidbg.Emulator;
import com.github.unidbg.linux.ARM64SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
// 继承自ARM64SyscallHandler,如果是32位则继承ARM32SyscallHandler
public class MySyscallHandler extends ARM64SyscallHandler {
    public MySyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
        setVerbose(true); // 可按需开启详细日志
    }
    // 在这里重写或添加你的处理逻辑
}
import com.github.unidbg.Emulator;
import com.github.unidbg.linux.ARM64SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
// 继承自ARM64SyscallHandler,如果是32位则继承ARM32SyscallHandler
public class MySyscallHandler extends ARM64SyscallHandler {
    public MySyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
        setVerbose(true); // 可按需开启详细日志
    }
    // 在这里重写或添加你的处理逻辑
}
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixSyscallHandler;
 
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(true) { 
    [url=home.php?mod=space&uid=1892347]@Override[/url] 
    public AndroidEmulator build() { 
        return new AndroidARM64Emulator(processName, rootDir, backendFactories) { 
            @Override 
            protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) { 
                return new MySyscallHandler(svcMemory); 
            
        }; 
    
}; 
builder.setProcessName("com.zj.wuaipojie"); 
emulator = builder.build();
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixSyscallHandler;
 
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(true) { 
    [url=home.php?mod=space&uid=1892347]@Override[/url] 
    public AndroidEmulator build() { 
        return new AndroidARM64Emulator(processName, rootDir, backendFactories) { 
            @Override 
            protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) { 
                return new MySyscallHandler(svcMemory); 
            
        }; 
    
}; 
builder.setProcessName("com.zj.wuaipojie"); 
emulator = builder.build();
// In MySyscallHandler.java
import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.Arm64Const;
@Override 
protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) { 
    System.err.println(">>> MySyscallHandler is processing syscall NR = " + NR); 
    Backend backend = emulator.getBackend(); 
    switch (NR) { 
   
        /** getcpu (NR=168): x0=cpu*, x1=node* */ 
        case 168: { 
            Pointer cpuPtr = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X0); 
            Pointer nodePtr = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); 
   
            // C++ 代码会多次调用 getcpu,期望 cpu 号码会变化且不为0 
            // 我们用一个随机数来模拟 CPU 核心的切换 
            int currentCpu = rng.nextInt(8); // 模拟8核CPU 
            if (cpuPtr != null) { 
                cpuPtr.setInt(0, currentCpu); 
            
            if (nodePtr != null) { 
                // NUMA 节点通常为 0                nodePtr.setInt(0, 0); 
            
            // 成功返回 0            writeX(backend, Arm64Const.UC_ARM64_REG_X0, 0); 
            return true
        
   
    
    return super.handleUnknownSyscall(emulator, NR); 
}
// In MySyscallHandler.java
import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.Arm64Const;
@Override 
protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) { 
    System.err.println(">>> MySyscallHandler is processing syscall NR = " + NR); 
    Backend backend = emulator.getBackend(); 
    switch (NR) { 
   
        /** getcpu (NR=168): x0=cpu*, x1=node* */ 
        case 168: { 
            Pointer cpuPtr = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X0); 
            Pointer nodePtr = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); 
   
            // C++ 代码会多次调用 getcpu,期望 cpu 号码会变化且不为0 
            // 我们用一个随机数来模拟 CPU 核心的切换 
            int currentCpu = rng.nextInt(8); // 模拟8核CPU 
            if (cpuPtr != null) { 
                cpuPtr.setInt(0, currentCpu); 
            
            if (nodePtr != null) { 
                // NUMA 节点通常为 0                nodePtr.setInt(0, 0); 
            
            // 成功返回 0            writeX(backend, Arm64Const.UC_ARM64_REG_X0, 0); 
            return true
        
   
    
    return super.handleUnknownSyscall(emulator, NR); 
}
struct statx {
    __u32 stx_mask;        /* Mask of fields returned */
    __u32 stx_blksize;     /* Block size for filesystem I/O */
    __u64 stx_attributes;  /* Extra file attribute hints */
    __u32 stx_nlink;       /* Number of hard links */
    __u32 stx_uid;         /* User ID of owner */
    __u32 stx_gid;         /* Group ID of owner */
    __u16 stx_mode;        /* File type and mode */
    __u16 __spare0[1];
    __u64 stx_ino;         /* Inode number */
    __u64 stx_size;        /* Total size in bytes */
    __u64 stx_blocks;      /* Number of 512B blocks allocated */
    __u64 stx_attributes_mask;
    /* The following fields are copied from struct statx_timestamp */
    struct statx_timestamp stx_atime; /* Last access */
    struct statx_timestamp stx_btime; /* Creation */
    struct statx_timestamp stx_ctime; /* Last status change */
    struct statx_timestamp stx_mtime; /* Last modification */
    /* If this structure is extended, then constants described in
       statx(2) will be defined to describe the new fields. */
};
 
struct statx_timestamp {
    __s64 tv_sec;
    __u32 tv_nsec;
    __s32 __spare;
};
struct statx {
    __u32 stx_mask;        /* Mask of fields returned */
    __u32 stx_blksize;     /* Block size for filesystem I/O */
    __u64 stx_attributes;  /* Extra file attribute hints */
    __u32 stx_nlink;       /* Number of hard links */
    __u32 stx_uid;         /* User ID of owner */
    __u32 stx_gid;         /* Group ID of owner */
    __u16 stx_mode;        /* File type and mode */
    __u16 __spare0[1];
    __u64 stx_ino;         /* Inode number */
    __u64 stx_size;        /* Total size in bytes */
    __u64 stx_blocks;      /* Number of 512B blocks allocated */
    __u64 stx_attributes_mask;
    /* The following fields are copied from struct statx_timestamp */
    struct statx_timestamp stx_atime; /* Last access */
    struct statx_timestamp stx_btime; /* Creation */
    struct statx_timestamp stx_ctime; /* Last status change */
    struct statx_timestamp stx_mtime; /* Last modification */
    /* If this structure is extended, then constants described in
       statx(2) will be defined to describe the new fields. */
};
 
struct statx_timestamp {
    __s64 tv_sec;
    __u32 tv_nsec;
    __s32 __spare;
};
/** statx (NR=291): x1=path, x4=statx* */
case 291: {
        // 1. 获取参数:路径指针和用于接收结果的 statx 结构体指针
        Pointer pathPtr = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1);
        Pointer stx = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X4);
        String path = pathPtr != null ? pathPtr.getString(0) : null;
 
        if (stx != null) {
                // 2. 核心技巧:使用ByteBuffer来构建内存中的结构体
                //    分配足够空间,并务必设置为LITTLE_ENDIAN(小端序),匹配ARM架构
                ByteBuffer bb = ByteBuffer.allocate(0x100).order(ByteOrder.LITTLE_ENDIAN);
 
                // 3. 按照 statx 结构体的定义,依次填充字段
                //    具体填充什么值,取决于目标so关心哪些字段。通常给一些非零的、看起来合理的值即可。
                bb.putInt(0x000007ff);  // stx_mask: STATX_ALL
                bb.putInt(4096);        // stx_blksize
                bb.putLong(0);          // stx_attributes
                bb.putInt(1);           // stx_nlink (链接数)
                bb.putInt(1000);        // stx_uid (user id)
                bb.putInt(1000);        // stx_gid (group id)
                 
                // 根据路径判断是目录还是文件,并设置对应的模式
                int S_IFDIR = 0x4000, S_IFREG = 0x8000;
                int mode = (path != null && path.endsWith("/")) ? (S_IFDIR | 0755) : (S_IFREG | 0644);
                bb.putShort((short) mode); // stx_mode
                 
                while (bb.position() < 0x20) bb.put((byte) 0); // 填充对齐
                 
                bb.putLong(123456789L); // stx_ino (inode number)
                bb.putLong(4096L);      // stx_size (文件大小)
                bb.putLong(8L);         // stx_blocks (块数量)
                 
                while (bb.position() < 0x58) bb.put((byte) 0); // 填充对齐
                 
                // 填充时间戳 (statx_timestamp 结构体)
                long now = 1710000000L; // 给一个固定的未来时间戳
                putStatxTs(bb, now); // atime (access time)
                putStatxTs(bb, now); // btime (birth time)
                putStatxTs(bb, now); // ctime (change time)
                putStatxTs(bb, now); // mtime (modify time)
 
                // 4. 将构建好的ByteBuffer内容,写入到so传入的指针地址
                stx.write(0, bb.array(), 0, bb.limit());
        }
        // 5. 设置返回值为0,表示成功
        writeX(backend, Arm64Const.UC_ARM64_REG_X0, 0);
        return true;
}
/** statx (NR=291): x1=path, x4=statx* */
case 291: {
        // 1. 获取参数:路径指针和用于接收结果的 statx 结构体指针
        Pointer pathPtr = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1);
        Pointer stx = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X4);
        String path = pathPtr != null ? pathPtr.getString(0) : null;
 
        if (stx != null) {
                // 2. 核心技巧:使用ByteBuffer来构建内存中的结构体
                //    分配足够空间,并务必设置为LITTLE_ENDIAN(小端序),匹配ARM架构
                ByteBuffer bb = ByteBuffer.allocate(0x100).order(ByteOrder.LITTLE_ENDIAN);
 
                // 3. 按照 statx 结构体的定义,依次填充字段
                //    具体填充什么值,取决于目标so关心哪些字段。通常给一些非零的、看起来合理的值即可。
                bb.putInt(0x000007ff);  // stx_mask: STATX_ALL
                bb.putInt(4096);        // stx_blksize
                bb.putLong(0);          // stx_attributes
                bb.putInt(1);           // stx_nlink (链接数)
                bb.putInt(1000);        // stx_uid (user id)
                bb.putInt(1000);        // stx_gid (group id)
                 
                // 根据路径判断是目录还是文件,并设置对应的模式
                int S_IFDIR = 0x4000, S_IFREG = 0x8000;
                int mode = (path != null && path.endsWith("/")) ? (S_IFDIR | 0755) : (S_IFREG | 0644);
                bb.putShort((short) mode); // stx_mode
                 
                while (bb.position() < 0x20) bb.put((byte) 0); // 填充对齐
                 
                bb.putLong(123456789L); // stx_ino (inode number)
                bb.putLong(4096L);      // stx_size (文件大小)
                bb.putLong(8L);         // stx_blocks (块数量)
                 
                while (bb.position() < 0x58) bb.put((byte) 0); // 填充对齐
                 
                // 填充时间戳 (statx_timestamp 结构体)
                long now = 1710000000L; // 给一个固定的未来时间戳
                putStatxTs(bb, now); // atime (access time)
                putStatxTs(bb, now); // btime (birth time)
                putStatxTs(bb, now); // ctime (change time)
                putStatxTs(bb, now); // mtime (modify time)
 
                // 4. 将构建好的ByteBuffer内容,写入到so传入的指针地址
                stx.write(0, bb.array(), 0, bb.limit());
        }
        // 5. 设置返回值为0,表示成功
        writeX(backend, Arm64Const.UC_ARM64_REG_X0, 0);
        return true;
}
@Override 
protected int clock_gettime(Emulator<?> emulator) { 
    Backend backend = emulator.getBackend(); 
   
    long clkId = readX(backend, Arm64Const.UC_ARM64_REG_X0); // x0 = clk_id 
    Pointer tp = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); // x1 = timespec* 
    if (tp == null) { 
        // 按 Linux 约定:失败返回 -errno;这里给个通用 EFAULT       
        return -14; // -EFAULT 
    
    // 构造各类时钟的返回值(64-bit timespec: tv_sec(8) + tv_nsec(8)) 
    long nowMs = System.currentTimeMillis(); 
    long nowNs = System.nanoTime(); 
   
    long sec, nsec; 
    switch ((int) clkId) { 
        case 0: // CLOCK_REALTIME 
        case 8: // CLOCK_REALTIME_ALARM 
            sec  = nowMs / 1000L; 
            nsec = (nowMs % 1000L) * 1_000_000L; 
            break
        case 1: // CLOCK_MONOTONIC 
        case 4: // CLOCK_MONOTONIC_RAW(有的系统是 4) 
        case 7: // CLOCK_BOOTTIME 
        case 9: // CLOCK_BOOTTIME_ALARM 
            sec  = nowNs / 1_000_000_000L; 
            nsec = nowNs % 1_000_000_000L; 
            break
        case 2: // CLOCK_PROCESS_CPUTIME_ID 
        case 3: // CLOCK_THREAD_CPUTIME_ID 
            // 进程/线程 CPU 时间:给一个非零的、单调增长的“小值”即可 
            // 这里简单用 nanoTime 的低位模拟 
            sec = 0L; 
            nsec = (nowNs % 50_000_000L) + 10_000L; // ~0~50ms,避免全 0            break; 
        default
            // 未识别的 id:退化成 REALTIME,避免抛异常 
            sec = nowMs / 1000L; 
            nsec = (nowMs % 1000L) * 1_000_000L; 
            break
    
   
    tp.setLong(0, sec); 
    tp.setLong(8, nsec); 
    return 0; // 成功 
}
@Override 
protected int clock_gettime(Emulator<?> emulator) { 
    Backend backend = emulator.getBackend(); 
   
    long clkId = readX(backend, Arm64Const.UC_ARM64_REG_X0); // x0 = clk_id 
    Pointer tp = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); // x1 = timespec* 
    if (tp == null) { 
        // 按 Linux 约定:失败返回 -errno;这里给个通用 EFAULT       
        return -14; // -EFAULT 
    
    // 构造各类时钟的返回值(64-bit timespec: tv_sec(8) + tv_nsec(8)) 
    long nowMs = System.currentTimeMillis(); 
    long nowNs = System.nanoTime(); 
   
    long sec, nsec; 
    switch ((int) clkId) { 
        case 0: // CLOCK_REALTIME 
        case 8: // CLOCK_REALTIME_ALARM 
            sec  = nowMs / 1000L; 
            nsec = (nowMs % 1000L) * 1_000_000L; 
            break
        case 1: // CLOCK_MONOTONIC 
        case 4: // CLOCK_MONOTONIC_RAW(有的系统是 4) 
        case 7: // CLOCK_BOOTTIME 
        case 9: // CLOCK_BOOTTIME_ALARM 
            sec  = nowNs / 1_000_000_000L; 
            nsec = nowNs % 1_000_000_000L; 
            break
        case 2: // CLOCK_PROCESS_CPUTIME_ID 
        case 3: // CLOCK_THREAD_CPUTIME_ID 
            // 进程/线程 CPU 时间:给一个非零的、单调增长的“小值”即可 
            // 这里简单用 nanoTime 的低位模拟 
            sec = 0L; 
            nsec = (nowNs % 50_000_000L) + 10_000L; // ~0~50ms,避免全 0            break; 
        default
            // 未识别的 id:退化成 REALTIME,避免抛异常 
            sec = nowMs / 1000L; 
            nsec = (nowMs % 1000L) * 1_000_000L; 
            break
    
   
    tp.setLong(0, sec); 
    tp.setLong(8, nsec); 
    return 0; // 成功 
}
@Override
public void hook(Backend backend, int intno, int swi, Object user) {
    // 1. 在顶层入口,首先读取X8寄存器,拿到系统调用号 NR
    int nr = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X8)).intValue();
 
    // 2. 判断是否是我们想“截胡”的系统调用
    if (nr == 123) { // __NR_sched_getaffinity (arm64)
        // 读取参数:X1 = cpusetsize, X2 = mask 地址
        long cpusetsize = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X1)).longValue();
        long maskAddr    = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X2)).longValue();
 
        // 3. 实现自定义的模拟逻辑
        if (maskAddr != 0 && cpusetsize > 0) {
            final int cores = 8;                      // 模拟一个8核CPU
            final int size  = (int) cpusetsize;
            byte[] buf = new byte[size];              // 初始化为全0的字节数组
            int maxBits   = size * 8;
            int bitsToSet = Math.min(cores, maxBits); // 计算需要设置的位数
            // 构造CPU亲和度的bitmask (例如8核,就把低8位置为1)
            for (int cpu = 0; cpu < bitsToSet; cpu++) {
                int bi = cpu / 8;
                int bit = cpu % 8;
                buf[bi] |= (1 << bit);
            }
            backend.mem_write(maskAddr, buf); // 将结果写入到指针指向的内存
        }
 
        // 4. 设置返回值到 X0 寄存器
        backend.reg_write(Arm64Const.UC_ARM64_REG_X0, cpusetsize);
 
        // 5. 【关键步骤】将X8寄存器设置为一个无效的系统调用号
        //    这是为了防止接下来调用的 super.hook() 再次处理 NR=123,从而覆盖掉我们的结果。
        backend.reg_write(Arm64Const.UC_ARM64_REG_X8, -1);
    }
 
    // 6. 【必须调用】调用父类的hook方法,以确保PC指针能正确推进,
    //    并且其他不被我们处理的系统调用能够正常执行。否则模拟会卡死。
    super.hook(backend, intno, swi, user);
}
@Override
public void hook(Backend backend, int intno, int swi, Object user) {
    // 1. 在顶层入口,首先读取X8寄存器,拿到系统调用号 NR
    int nr = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X8)).intValue();
 
    // 2. 判断是否是我们想“截胡”的系统调用
    if (nr == 123) { // __NR_sched_getaffinity (arm64)
        // 读取参数:X1 = cpusetsize, X2 = mask 地址
        long cpusetsize = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X1)).longValue();
        long maskAddr    = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X2)).longValue();
 
        // 3. 实现自定义的模拟逻辑
        if (maskAddr != 0 && cpusetsize > 0) {
            final int cores = 8;                      // 模拟一个8核CPU
            final int size  = (int) cpusetsize;
            byte[] buf = new byte[size];              // 初始化为全0的字节数组
            int maxBits   = size * 8;
            int bitsToSet = Math.min(cores, maxBits); // 计算需要设置的位数
            // 构造CPU亲和度的bitmask (例如8核,就把低8位置为1)
            for (int cpu = 0; cpu < bitsToSet; cpu++) {
                int bi = cpu / 8;
                int bit = cpu % 8;
                buf[bi] |= (1 << bit);
            }
            backend.mem_write(maskAddr, buf); // 将结果写入到指针指向的内存
        }
 
        // 4. 设置返回值到 X0 寄存器
        backend.reg_write(Arm64Const.UC_ARM64_REG_X0, cpusetsize);
 
        // 5. 【关键步骤】将X8寄存器设置为一个无效的系统调用号
        //    这是为了防止接下来调用的 super.hook() 再次处理 NR=123,从而覆盖掉我们的结果。
        backend.reg_write(Arm64Const.UC_ARM64_REG_X8, -1);
    }
 
    // 6. 【必须调用】调用父类的hook方法,以确保PC指针能正确推进,
    //    并且其他不被我们处理的系统调用能够正常执行。否则模拟会卡死。
    super.hook(backend, intno, swi, user);
}
特性 / 方面 fstat (旧版) statx (现代版)
操作对象 只能作用于一个已经打开的文件描述符 (fd) 更灵活,既可以作用于文件描述符,也可以直接作用于文件路径
信息丰富度 返回 struct stat 结构体,信息相对基础。 返回 struct statx 结构体,信息更丰富,可以提供文件创建时间 (btime) 等。
时间戳精度 传统上只精确到 精确到纳秒 (nanosecond),更能满足现代高精度需求。
执行效率 All-or-Nothing (全量获取)。每次调用,内核都会填充所有字段,不管你是否需要。 On-Demand (按需获取)。可以通过 mask 参数精确指定需要哪些信息,内核按需返回,效率更高。
扩展性 struct stat 结构体已非常拥挤,难以扩展。 struct statx 设计上留有余地,方便未来添加新属性。
// 在 unidbg 源码 unidbg-android\src\main\java\com\github\unidbg\linux\file\DirectoryFileIO.java有相关实现
@Override 
public int fstat(Emulator<?> emulator, StatStructure stat) { 
    stat.st_mode = IO.S_IFDIR; 
    stat.st_dev = 0
    stat.st_size = 0
    stat.st_blksize = 0
    stat.st_ino = 0
    stat.pack(); 
    return 0
}
// 在 unidbg 源码 unidbg-android\src\main\java\com\github\unidbg\linux\file\DirectoryFileIO.java有相关实现
@Override 
public int fstat(Emulator<?> emulator, StatStructure stat) { 
    stat.st_mode = IO.S_IFDIR; 
    stat.st_dev = 0
    stat.st_size = 0
    stat.st_blksize = 0
    stat.st_ino = 0
    stat.pack(); 
    return 0
}
if (nr == 80) { // __NR_fstat (arm64) 
    // 1. 获取参数:fd在x0, stat结构体指针在x1 
    int fd = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X0)).intValue(); 
    UnidbgPointer statbuf = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); 
   
    // 2. 判断这个fd是否是我们关心的目录类型 
    if (this.fdMap.get(fd) instanceof DirectoryFileIO) { 
        System.out.println("Hooked fstat(80) for directory fd=" + fd); 
   
        // 3. 使用Unidbg的StatStructure帮助类来填充数据 
        // 这比自己算偏移量和手动写ByteBuffer要简单得多 
        StatStructure stat = new Stat64(statbuf); 
   
        // 4. 伪造数据
        stat.st_mode = S_IFDIR | 0755; // 模式:目录 + 权限 
        stat.st_dev = 1
        stat.st_size = 4096
        stat.st_blksize = 4096
        stat.st_ino = 12345
        stat.setSt_atim(1668267277L, 999999999L); 
   
   
        // 5. 调用pack()将以上设置好的字段,按照内存布局写入指针 
        stat.pack(); 
   
        // 6. 设置返回值为0(成功),并屏蔽父类逻辑 
        backend.reg_write(Arm64Const.UC_ARM64_REG_X0, 0); 
        backend.reg_write(Arm64Const.UC_ARM64_REG_X8, -1); 
    }
if (nr == 80) { // __NR_fstat (arm64) 
    // 1. 获取参数:fd在x0, stat结构体指针在x1 
    int fd = ((Number) backend.reg_read(Arm64Const.UC_ARM64_REG_X0)).intValue(); 
    UnidbgPointer statbuf = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); 
   
    // 2. 判断这个fd是否是我们关心的目录类型 
    if (this.fdMap.get(fd) instanceof DirectoryFileIO) { 
        System.out.println("Hooked fstat(80) for directory fd=" + fd); 
   
        // 3. 使用Unidbg的StatStructure帮助类来填充数据 
        // 这比自己算偏移量和手动写ByteBuffer要简单得多 
        StatStructure stat = new Stat64(statbuf); 
   
        // 4. 伪造数据
        stat.st_mode = S_IFDIR | 0755; // 模式:目录 + 权限 
        stat.st_dev = 1
        stat.st_size = 4096
        stat.st_blksize = 4096
        stat.st_ino = 12345
        stat.setSt_atim(1668267277L, 999999999L); 
   
   
        // 5. 调用pack()将以上设置好的字段,按照内存布局写入指针 
        stat.pack(); 
   
        // 6. 设置返回值为0(成功),并屏蔽父类逻辑 
        backend.reg_write(Arm64Const.UC_ARM64_REG_X0, 0); 
        backend.reg_write(Arm64Const.UC_ARM64_REG_X8, -1); 
    }
private void hook_popen() { 
    IxHook xHook = XHookImpl.getInstance(emulator); 
    // 监控目标so对popen的调用
    xHook.register("libszstone.so", "popen", new ReplaceCallback() { 
        @Override 
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { 
            RegisterContext registerContext = emulator.getContext(); 
            String command = registerContext.getPointerArg(0).getString(0); 
            // 将捕获到的命令存入上下文,键为 "command"
            emulator.set("command", command); 
            System.out.println("command:"+command); 
            // 让原始的 popen 函数继续执行,我们只“偷听”,不干扰
            return HookStatus.RET(emulator, originFunction); 
        
    }, true); 
    // 使其生效 
    xHook.refresh(); 
}
// 在调用目标函数前,先执行 hook_popen ();
challenge.hook_popen();
private void hook_popen() { 
    IxHook xHook = XHookImpl.getInstance(emulator); 
    // 监控目标so对popen的调用
    xHook.register("libszstone.so", "popen", new ReplaceCallback() { 
        @Override 
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { 
            RegisterContext registerContext = emulator.getContext(); 
            String command = registerContext.getPointerArg(0).getString(0); 
            // 将捕获到的命令存入上下文,键为 "command"
            emulator.set("command", command); 
            System.out.println("command:"+command); 
            // 让原始的 popen 函数继续执行,我们只“偷听”,不干扰
            return HookStatus.RET(emulator, originFunction); 
        
    }, true); 
    // 使其生效 
    xHook.refresh(); 
}
// 在调用目标函数前,先执行 hook_popen ();
challenge.hook_popen();
@Override
protected int pipe2(Emulator<?> emulator) {
    // AArch64下,第一个参数通过 X0 寄存器传递,这里是指向 int pipefd[2] 的指针
    UnidbgPointer pipefd = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X0);
 
    // 分配两个新的文件描述符(fd)
    int write_fd = getMinFd();
    int read_fd = getMinFd();
 
    this.fdMap.put(write_fd, new DumpFileIO(write_fd));
 
    // 从上下文中取出刚才hook_popen时存入的命令
    String cmd = (String) emulator.get("command");
    if (cmd == null) cmd = "stat /data"; // 兜底
 
    // 【核心】根据命令,准备好要伪造的输出内容
    String stdout = "\n";
    if ("stat /data".equals(cmd)) { 
        stdout = 
            "  File: /data\n"
                    "  Size: 4096     Blocks: 16      IO Blocks: 512 directory\n"
                    "Device: 10305h/66309d    Inode: 2        Links: 53\n"
                    "Access: (0771/drwxrwx--x)       Uid: ( 1000/  system)   Gid: ( 1000/  system)\n"
                    "Access: 2022-04-22 16:08:42.656423789 +0800\n"
                    "Modify: 1970-02-05 00:02:38.459999996 +0800\n"
                    "Change: 1971-12-21 21:33:28.769999994 +0800\n"
    }
 
    // 将伪造的输出内容包装成一个ByteArrayFileIO,并与“读”端fd关联
    this.fdMap.put(read_fd, new ByteArrayFileIO(0, "pipe2_read_side", stdout.getBytes()));
 
    // 【关键细节】将两个fd写回so传入的指针地址。
    // 即使在64位系统,pipefd参数也是一个 int[2] 数组,每个int占4字节。
    pipefd.setInt(0, read_fd);   // 写入读端fd
    pipefd.setInt(4, write_fd);  // 写入写端fd
 
    // Unidbg会自动将此方法的返回值(0)写入X0寄存器,表示调用成功
    return 0;
}
@Override
protected int pipe2(Emulator<?> emulator) {
    // AArch64下,第一个参数通过 X0 寄存器传递,这里是指向 int pipefd[2] 的指针
    UnidbgPointer pipefd = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X0);
 
    // 分配两个新的文件描述符(fd)
    int write_fd = getMinFd();
    int read_fd = getMinFd();
 
    this.fdMap.put(write_fd, new DumpFileIO(write_fd));
 
    // 从上下文中取出刚才hook_popen时存入的命令
    String cmd = (String) emulator.get("command");
    if (cmd == null) cmd = "stat /data"; // 兜底
 
    // 【核心】根据命令,准备好要伪造的输出内容
    String stdout = "\n";
    if ("stat /data".equals(cmd)) { 
        stdout = 
            "  File: /data\n"
                    "  Size: 4096     Blocks: 16      IO Blocks: 512 directory\n"
                    "Device: 10305h/66309d    Inode: 2        Links: 53\n"
                    "Access: (0771/drwxrwx--x)       Uid: ( 1000/  system)   Gid: ( 1000/  system)\n"
                    "Access: 2022-04-22 16:08:42.656423789 +0800\n"
                    "Modify: 1970-02-05 00:02:38.459999996 +0800\n"
                    "Change: 1971-12-21 21:33:28.769999994 +0800\n"
    }
 
    // 将伪造的输出内容包装成一个ByteArrayFileIO,并与“读”端fd关联
    this.fdMap.put(read_fd, new ByteArrayFileIO(0, "pipe2_read_side", stdout.getBytes()));
 
    // 【关键细节】将两个fd写回so传入的指针地址。
    // 即使在64位系统,pipefd参数也是一个 int[2] 数组,每个int占4字节。
    pipefd.setInt(0, read_fd);   // 写入读端fd
    pipefd.setInt(4, write_fd);  // 写入写端fd
 
    // Unidbg会自动将此方法的返回值(0)写入X0寄存器,表示调用成功
    return 0;
}
// In MySyscallHandler.java
private int fakePid = 4242; // 维护一个假的PID
 
@Override
protected long fork(Emulator<?> emulator) {
    // 直接返回一个递增的、非零的假PID
    // 这会让调用fork的父进程逻辑认为子进程已成功创建
    return ++fakePid;
}
// In MySyscallHandler.java
private int fakePid = 4242; // 维护一个假的PID
 
@Override
protected long fork(Emulator<?> emulator) {
    // 直接返回一个递增的、非零的假PID
    // 这会让调用fork的父进程逻辑认为子进程已成功创建
    return ++fakePid;
}
#include <sys/types.h>
pid_t gettid(void);
#include <sys/types.h>
pid_t gettid(void);
// 修补方法:使用Dobby直接Hook原生C函数
private static void hookLibcGettidWithDobby(Emulator<?> emulator, int forcedTid) {
    // 第1步:定位gettid函数所在的模块 libc.so
    Module libc = emulator.getMemory().findModule("libc.so");
    if (libc == null) {
        throw new IllegalStateException("libc.so not found");
    }
 
    // 第2步:在模块中查找函数符号(Symbol),即函数的地址
    // 注意:不同Android版本,函数名可能是 gettid 或 __gettid
    Symbol sym = libc.findSymbolByName("gettid", false);
    if (sym == null) sym = libc.findSymbolByName("__gettid", false);
    if (sym == null) {
        throw new IllegalStateException("gettid/__gettid symbol not found in libc.so");
    }
 
    // 第3步:获取Dobby实例,并调用 replace 方法执行Hook
    Dobby dobby = Dobby.getInstance(emulator);
    dobby.replace(sym, new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            // C函数原型: pid_t gettid(void);
            // 函数无参数,我们直接伪造返回值即可。
 
            // forcedTid 可以是 emulator.getPid() 或任何你期望的值
            int fakeTid = forcedTid;
            System.out.println("[HOOK] gettid(): Faking return value to -> " + fakeTid);
 
            // 【核心】使用HookStatus.LR()直接返回,不再执行原始函数。
            // AArch64架构下,函数返回值在X0寄存器。
            // Dobby会将 fakeTid 写入X0寄存器,并立即返回到调用者。
            return HookStatus.LR(emulator, (long) (fakeTid & 0xffffffffL));
        }
    });
}
 
// 在主流程中调用:
// 通常我们会让TID等于PID来模拟主线程
hookLibcGettidWithDobby(emulator, emulator.getPid());
// 修补方法:使用Dobby直接Hook原生C函数
private static void hookLibcGettidWithDobby(Emulator<?> emulator, int forcedTid) {
    // 第1步:定位gettid函数所在的模块 libc.so
    Module libc = emulator.getMemory().findModule("libc.so");
    if (libc == null) {
        throw new IllegalStateException("libc.so not found");
    }
 
    // 第2步:在模块中查找函数符号(Symbol),即函数的地址
    // 注意:不同Android版本,函数名可能是 gettid 或 __gettid
    Symbol sym = libc.findSymbolByName("gettid", false);
    if (sym == null) sym = libc.findSymbolByName("__gettid", false);
    if (sym == null) {
        throw new IllegalStateException("gettid/__gettid symbol not found in libc.so");
    }
 
    // 第3步:获取Dobby实例,并调用 replace 方法执行Hook
    Dobby dobby = Dobby.getInstance(emulator);
    dobby.replace(sym, new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            // C函数原型: pid_t gettid(void);
            // 函数无参数,我们直接伪造返回值即可。
 
            // forcedTid 可以是 emulator.getPid() 或任何你期望的值
            int fakeTid = forcedTid;
            System.out.println("[HOOK] gettid(): Faking return value to -> " + fakeTid);
 
            // 【核心】使用HookStatus.LR()直接返回,不再执行原始函数。
            // AArch64架构下,函数返回值在X0寄存器。
            // Dobby会将 fakeTid 写入X0寄存器,并立即返回到调用者。
            return HookStatus.LR(emulator, (long) (fakeTid & 0xffffffffL));
        }
    });
}
 
// 在主流程中调用:
// 通常我们会让TID等于PID来模拟主线程
hookLibcGettidWithDobby(emulator, emulator.getPid());
AIzaSyDeYcYUxAN52DNrgZeFNcEfceVMoWJDjWk
AIzaSyDLMJARxMYqUt4Tl8ADjPPPB1m672tTVmY
AIzaSyAWY438PG4XacWuZU8nQtQ0popo9YAVB18
AIzaSyBouea_CZifzNGN_vC72TtyxYwQLR42tpM
AIzaSyBJtRKb0W0cg1f3Tv65yU1NaYAZ3Hiqn3g
AIzaSyBYPYP-SgvrqsdVrfgOFwvPSGsIFdRLlRs
AIzaSyDN1EtiR1ef6LG4xjtSiiDPNPkkJMh0auQ
AIzaSyCNbdDb9FsvXA8B2Gn_qzhQSxmuTHo_ifA
AIzaSyBJEAGhQB1Wou6yzHe1QCQPv2GaHgsdeTA
AIzaSyDgpkOpIGCuZif3BU9lcrrFOkY-onE263E
AIzaSyBmOVAdUB54vGOHroYOJ7OtC06YrFDOST0
AIzaSyAymb7h68s42zt6xY7jmJMBUjqPiMyU27M
AIzaSyCSHZuRBP1RPGcirYaCopJFi9FSM7le6Zc
AIzaSyBLlTKQaxeXm7DWa5Q1mmSIHBpHyWRdwMA
AIzaSyCxt7zx-cUzvkuroP1uaSp_m0SkoFU_j4A
AIzaSyBnOqAk-VfAjdFpbkveT2yJnNmFXv9-7Mw
AIzaSyDtsLIwVZgT5T8tqfYN2PIQKlflAk-90NI
AIzaSyCvXozHylyHSEFYAW-SZ1QXLyQeKJnt-ac
AIzaSyCy_5IaKck7-MrnJRYLjyV3n2FeCJ9FDGs
AIzaSyBTxaEzGGLVrskzx7UK374NgV4BXn-4nqs
AIzaSyCXSEStgw39a9znv6ATo8mRQZ0eRI5T-n0
AIzaSyA_tSR5vUDWRSIIXFdsOJtnSsueC496njQ
AIzaSyBl56gfbyjTY5qI1mzTP9-uK_Jg7c2ckaY
AIzaSyCQxdrjSaHUfAYikI64Co_mux09RRtYLe0
AIzaSyCl25sgM6WAbqYmY_QTqlMnYQCQxQEQQO8
AIzaSyBjuyyFRTJ5b4XOSAVosrfZShx1K-e6FyE
AIzaSyChOZyn4FgZm41gyg2VEValfDV47IIuw3Q
AIzaSyCg_axGIeK7mMj_XeYtozTQdj4iREPufSo
AIzaSyCmje3P-kGPwqE5hENYujKLKjIievNvmvM
AIzaSyDeQ2QYCW9bu32DX9nmuNrctgKC2oTyZW8
AIzaSyBySkCG5yLgtz4IANJySl5Y59Xxt9pdVWI
AIzaSyCrDCA1le2EY2tSMUu-JTXEusSnXFaXv6k
AIzaSyAevOZ8wu6LmrS6XQDH-S1prUb4Zs-WOqk
AIzaSyDwdVZuLn4W9Xz4YGNtqRcaKXFrl6HE3dA
AIzaSyDx5bncjiKOnbQ_1mz8j6kgwHlKGRWxQIo
AIzaSyDj14MIi4L7pCxXIxMfJVFTTaZ5h_stxdI
AIzaSyCxrE6u0KsbiAR6vLrBnC5PPqG05wl-bTA
AIzaSyDX3U9UEgus4Gdx7960ABTn7Ll3LYMxaF4
AIzaSyDEDxFwPbMXPv7GUb1sQduNLCgbpgvU8_I
AIzaSyCCGyjLLZNZnSm0OZOMtxQWe--ZOnsfe_4
AIzaSyDtDKmMytfIq9Ld5VUIOffIoOY7yXxWEv4
AIzaSyCuz7VK0DU7EtdmMHhNidccJfU36j6CADs
AIzaSyCV9dD4V90z7Sf_fWenXEb83ne0Iav5i3o
AIzaSyAqb4ZV4vCfhrfvinj_rGs3lZOP1--zyqc
AIzaSyCo1NhsZBJg5Yloi5HcrdeMnkNGrSloJ9E
AIzaSyCL9cN9Y3-qQbS5wmr9gxk3J9ZICWHsyiQ
AIzaSyDBacTP1FYjR61hHBUNLCEtLAyk5vqFPiI
AIzaSyCV077Q-1nwxMbPM_3f4Z1h1b7ZHjIBE78
AIzaSyDuExne7oG9NnfNZeaqDOSXVtxUdau7IBU
AIzaSyBHXLYc4hrlVFafKd3hsvqniK1Nytw6pDI
AIzaSyDRizynqIPgiZfgrMdqcosDOWxsTGNtwEM
AIzaSyCIjgG29HPVa7_db5NMrQL5O4oBgW18S5o
AIzaSyDa0wwQKBtfwVxP8wmZSTpq2hFxV0QeCY8
AIzaSyAT2aZC-EMqdjYaz2VgBWCtNFp8rE2If34
AIzaSyAvXG6KKI65N1jNOwIRwEQNoyWbXpdDcsg
AIzaSyAEWasCSVrPKoBXH8SEwFYMu6rbKjTFPcQ
AIzaSyAMWdJ5n3Ro5FdnQNJzw0wrUmNYBw0wzKU
AIzaSyDJnRYm3-t06ks4zrBFyEglV6qJyFvn8Qo
AIzaSyDfNTj4zJWiaXbzwvur812s9DPb5CE9y2w
AIzaSyBqopZ5Q_RKyzYYMI-LmcbSaJwONy16TzU
AIzaSyDR49YtJPhn0QJB8Jh6FZM4gSliW9W47xg
AIzaSyBMT1vW_L-VEqZdF_fMCowgtNbzIQduqMI
AIzaSyDsMf87ZAzDFUZHBnc1HQXmidOGDu8anhk
AIzaSyDweH2jQe7RLYDYUUfcJlAVxgcOmLkd3is
AIzaSyDzAOJND8nS3c7Mwzc0rU330eHucH9c-8E
AIzaSyBU-hzfR-sgVp_-OSb3YcPmUwbUOApUIPE
AIzaSyAXVwlY7urRPxPMlkE_T8Z-4hIc_dzgGE0
AIzaSyDRpJORsoLZMRG60l_68TEzH5b3jd6DGZ4
AIzaSyCBHcP4Seer76nBjvy9vOciNxHumZg7eFE
AIzaSyDqH5QxZxhb8SpQ-_LEmhTteXmzYmpSHgE
AIzaSyDHlry6Ma_CLUVyBGOH7ph0WZLKRG2ccbg
AIzaSyAq2b17eIBC4UJpyateDA3fwRgGe2uC6Ko
AIzaSyCfgPZn0kZZPmk5eaaVvlkR-SrGH2rXH4k
AIzaSyBoiXy-lnXJhOOF0YxMeFFbn7F4oOXtTIc
AIzaSyCHCrl4O31xv4msRwR9B52dpghDPmpKwKM
AIzaSyCQddEzfQidcNdtuDbq8SSlO6K3Ds-ayOc
AIzaSyADe2YtszUhEcTR546CNlc3UVssE7PStMU
AIzaSyCD8uwm3Ye7tBS6uFxOM6mao1sqssf_SHw
AIzaSyDlBv9Br45qcfbzGyr3AlcScyWQo3eSOPU
AIzaSyAu_PNVM03Pu1o380ZI_TKU61FSDeDV1Ks
AIzaSyCDz4gjhidaJvdzPjYHxdo1o0CAqiqll9k
AIzaSyDL12z-qYxT4uAv2v2hwjMHUe8iXcK0ETU
AIzaSyDtwjdZFSAW1pJSnAj1kURTN8kGeFWmATU
AIzaSyAUKqVhKd51nSHwZUxQrYTGqPsr31IdyCY
AIzaSyAUfL_KrYf9rY599fGi2v63IR2Z_PmMMcw
AIzaSyAxpV4OY_zVDst3jGaYNwnyHhFmZ0V4XJE
AIzaSyBoJIB7xIHrxVCw6dd8Zv_eJHSe67lmoQM
AIzaSyCaxTDfzcE5UqPCiZ5QmDEXsgQkdgLhp1w
AIzaSyDzQChJoC_jnb5xwn9UHkFyePD3qZYRaBU
AIzaSyCuZ0zCypYmrGT5GXopw3JPu3WGLzEMpGU
AIzaSyBMlGsH4vA9gcEimwnOyMTAFcPZ33J3akI
AIzaSyCXzlJRd3krr5e32pXTAS14mfS3a8dDsMA
AIzaSyAMgFD2kmgb7Cri49Z7-sOAFBejb37LWgQ
AIzaSyAQPkvdoV4_nzsaaYTZu-QqU__kV9988JI
AIzaSyASjNCk1oxREML4AFhcx5joggaSyvDVj9Q
AIzaSyBl5lM6Md8_YbDt7o-rmB2SiIyppPT32dU
AIzaSyDo3Y0qZ5HPmGGoTNGl_6xmxbOAE8q3cSY
AIzaSyBGFC9cnNJEC822NAKicHbX4PJsE1PGn4c
AIzaSyDUlDBDxfl7c33vb3ILKLayrQNROqHhUDg
AIzaSyDYlLPKXjcrvyTMl8ANYH_FgvKM1mrIdcM
AIzaSyCtJ4cwA90mL25n2f-aFUfnOP0t9zEgs7A
AIzaSyCX_TzqcMio44YuQnkFe0HAaUQNE5qa4n4
AIzaSyDQFUa1LGkonbd7152FK6roHyLs4FhMsVM
AIzaSyDNwmGY0aTaE0GFAndRe773JmZtoPBO8WU
AIzaSyBhITH3uj_6kHz4cIHQ5p7-M5cuOXvGwW0
AIzaSyBidS2L33bSz73nCywuv64Z-lxIx3IaifE
AIzaSyBe8kjRD-siRLDQh30xVRka5TmrsAZVwYc
AIzaSyDfO3oWskF7Pj99ua_72pZxEDQgfGEl8Fo
AIzaSyAFsWzfDNb-OE9DM7ucb4Nwx2rgr_5Horg
AIzaSyCNSdZWCnONcfrmjYI3STJAEO3TRGXxgbQ
AIzaSyCd5zymLO6nQPtaSBtDZJbIggZbQFeRCvc
AIzaSyAF5WnWgLOWJthnfD6_KpSuD7eI5FdAQBk
AIzaSyAj9qY_NbobH5D7wkF1QjWWJ0Wb-agLEgI
AIzaSyDRY5Iga1IYEZjK0MroKACshZYDSoDa5kQ
AIzaSyDQs4bJr4VHcO5XZMHM5Gg3GKVSi7hifoU
AIzaSyDnhCxgEgCMPtBgYi9evlCDm8NKWHrFbMc
AIzaSyDegIQZAzMZeiVajsStxrgWvGDMkfZ4GUg
AIzaSyAOIVHb7xcAMqx07D4kM45qwHNZeNu0Qas
AIzaSyChcczCZLpZbYlw7J_vcU5LE-pfKUFiS3o
AIzaSyBOUpORGxljVDusUs-fMVpLKfZNh02AnmU
AIzaSyCUfqhUXwgUzkw_t67wF3D4gsRCBgYm-50
AIzaSyAMZ7sJlD2ZlUyqObwLOZgJ9BGneYATG2o
AIzaSyBN5WVkfPSZ4EcqoTy06XfmaRjXrl0DbnU
AIzaSyBAn3hVZfgMUQYIm-o_-U0TaM1lGCCDItw
AIzaSyBhoK80uMGiY-885co3xqdnCOzXBjBC24k
AIzaSyAr8fG-NsCNjVe3QmnS_zMsu9Kaf6jVGa4
AIzaSyCLExLEYHiMK3Hyo3XoJBEug5wvfLdJ-p0
AIzaSyCLx3zZ7xGSMpuE0jc87gdJV7VNQ5DcSZM
AIzaSyCpoWTEbR9ggzR_74bkUUdjir_2Kw8ALm0
AIzaSyBeUkVJzv1yx9bVIQsveMma-dsAi8TcqME
AIzaSyCmS2TuyUXfXRREsiEEgmWeG8Plu__0SqA
AIzaSyDlwNcNuyR-HnUctFz8Bn2uS0ienqJz2zc
AIzaSyBaUKIYRqv4Bj2V-Kqx1C7IJ5zvlRwLO_E
AIzaSyDm4gztlWTcR2SUla8Y9bH3iXZbshuXD_4
AIzaSyDZ93djmNzYIRkFs8qxTJmRPWVaYf_fPxM
AIzaSyDo-2Ubl_SukYv1QoIHPREB0CYy-uFrHJc
AIzaSyCxdHELZeZykp4jGMNp8JGJKGFexRjc-i8
AIzaSyBJsFQon7QDK7QRslWWBGPxhbuUJBcWB0k
AIzaSyCF4NKD4O01ktWWoRPWluyx9fD2MaFg8iU
AIzaSyCKEpwjAEcy979dinuoTSsCD4eMB8JaHLA
AIzaSyBkunWP4l4SVzCSbaP_oVNHrXO9OI_R33k
AIzaSyCLiVBiLWYKH04qVhd8Dj45mmNaG-s8Bzc
AIzaSyBkm3UaXYRKLKwmXyFsjOfNQtG4slPAtMM
AIzaSyCXPk-S3qNYYU7f960YP1XIwKQMIUigWAE
AIzaSyDoYoM0xCbpbnkzmEOUzrOQKE2I3tGEiPw
AIzaSyCWDo6tT1HFlsXT50RPzFn3glMhzTXVHDg
AIzaSyDJzAJF422SyZ99Z6CGihnJqKIDlQn4CfM
AIzaSyCjH6NCAmwcrqxaWEhxeSaMqKc9A10Osec
AIzaSyBjTvXHlFStQbzCOg88jjbe1cAaxlaE9M8
AIzaSyBugkmHyJf32u7vlRtU3VviO3mE2RZGVQQ
AIzaSyALDOFJnoigN_k0lFRydMnnfJJjHJcDfiI
AIzaSyDCGHHwwKmlDYtLdXLiEnxzNfSKw6ewSbY
AIzaSyDNOtokPHTUm9WCJ1pOPaweUp_Rks9DhjI
AIzaSyCv4H5U4WJ7DDC4O-2N-sBSnH9GPMwurqk
AIzaSyBAKQdzi4bjkeHz_xnpVaUp0JbSgGB5mWc
AIzaSyCnTROIII2xrUW6rpzDn_WdRFhiLKuze5I
AIzaSyBE-m0-Np0mqdwHrGg1XNue4cz5qC3ecDA
AIzaSyBU41uwxx-G-Mkff20r0OzJWfOVI35rZwQ
AIzaSyDw79xgZTh2lYEPMF2JPs_RqUrteKz1eZU
AIzaSyBjatV-0puUSHYY-PYbUVyj3GuYJYV918g
AIzaSyAlrRP3TQWTaI_essPz0oaNQAhXeq8SaOw
AIzaSyBBdjoUVKVJZb1Ovv-6WbQD7XYIRg3BSKo
AIzaSyAcWQruU56ZLIvQQpMDoHBkRjBjnY4Ja2I
AIzaSyAduQyY6xC4Nma4uPvHwve2fxKNXIYe2dQ
AIzaSyBbhTEJfa9ArGgKKnjUOV3XL60UMpL_gd4
AIzaSyCqTYLyCG37Y2S8s_8NxY0RJD9zj-JgSGI
AIzaSyAAvmxbVy8qcLotR34MB36klygYgNGnS2g
AIzaSyDlyjDbO-HdJyLXVrpzd8R1zqd_KE8Mq4E
AIzaSyAzhXIpf500NeZdfl44Qjh5k7epNaa8WS8
AIzaSyAvuhTgURgvzTRIp51CzIggks-top10DRs
AIzaSyAUgrPIOR1Kokk1f29rvxhkIZom9mToA_o
AIzaSyBe6EkfS-eVWMOQalm2TpxL6ljGBC2z9dI
AIzaSyBIoS20jBYkfdUnMII-bfVMprmCpwWr9Lg
AIzaSyCW1nB9SIPs0BcAy_rUoR6oT0E1ePKaRuo
AIzaSyDu6pH5nfKPJzzanYwEp2yCsubvl495oyk
AIzaSyCEzmk05J35sn0ADAjbX3PPTrRjbl2Pqn0
AIzaSyANY9CZz7W7VRh8hObUhJMdAWTyLD3ngrk
AIzaSyALWlbXC2RpSrGqzSr7Ubjm_UwSY8W7IbY
AIzaSyCAVsYKBiAL0oXtZ5weAXXRbX7J6GOrJII
AIzaSyAN40P9Ad_3COVtCUTzxKzAmmw9D1vUrZM
AIzaSyDeYcYUxAN52DNrgZeFNcEfceVMoWJDjWk
AIzaSyDLMJARxMYqUt4Tl8ADjPPPB1m672tTVmY
AIzaSyAWY438PG4XacWuZU8nQtQ0popo9YAVB18
AIzaSyBouea_CZifzNGN_vC72TtyxYwQLR42tpM
AIzaSyBJtRKb0W0cg1f3Tv65yU1NaYAZ3Hiqn3g
AIzaSyBYPYP-SgvrqsdVrfgOFwvPSGsIFdRLlRs
AIzaSyDN1EtiR1ef6LG4xjtSiiDPNPkkJMh0auQ
AIzaSyCNbdDb9FsvXA8B2Gn_qzhQSxmuTHo_ifA
AIzaSyBJEAGhQB1Wou6yzHe1QCQPv2GaHgsdeTA
AIzaSyDgpkOpIGCuZif3BU9lcrrFOkY-onE263E
AIzaSyBmOVAdUB54vGOHroYOJ7OtC06YrFDOST0
AIzaSyAymb7h68s42zt6xY7jmJMBUjqPiMyU27M
AIzaSyCSHZuRBP1RPGcirYaCopJFi9FSM7le6Zc
AIzaSyBLlTKQaxeXm7DWa5Q1mmSIHBpHyWRdwMA
AIzaSyCxt7zx-cUzvkuroP1uaSp_m0SkoFU_j4A
AIzaSyBnOqAk-VfAjdFpbkveT2yJnNmFXv9-7Mw
AIzaSyDtsLIwVZgT5T8tqfYN2PIQKlflAk-90NI
AIzaSyCvXozHylyHSEFYAW-SZ1QXLyQeKJnt-ac
AIzaSyCy_5IaKck7-MrnJRYLjyV3n2FeCJ9FDGs
AIzaSyBTxaEzGGLVrskzx7UK374NgV4BXn-4nqs
AIzaSyCXSEStgw39a9znv6ATo8mRQZ0eRI5T-n0
AIzaSyA_tSR5vUDWRSIIXFdsOJtnSsueC496njQ
AIzaSyBl56gfbyjTY5qI1mzTP9-uK_Jg7c2ckaY
AIzaSyCQxdrjSaHUfAYikI64Co_mux09RRtYLe0
AIzaSyCl25sgM6WAbqYmY_QTqlMnYQCQxQEQQO8
AIzaSyBjuyyFRTJ5b4XOSAVosrfZShx1K-e6FyE
AIzaSyChOZyn4FgZm41gyg2VEValfDV47IIuw3Q
AIzaSyCg_axGIeK7mMj_XeYtozTQdj4iREPufSo
AIzaSyCmje3P-kGPwqE5hENYujKLKjIievNvmvM
AIzaSyDeQ2QYCW9bu32DX9nmuNrctgKC2oTyZW8
AIzaSyBySkCG5yLgtz4IANJySl5Y59Xxt9pdVWI
AIzaSyCrDCA1le2EY2tSMUu-JTXEusSnXFaXv6k
AIzaSyAevOZ8wu6LmrS6XQDH-S1prUb4Zs-WOqk
AIzaSyDwdVZuLn4W9Xz4YGNtqRcaKXFrl6HE3dA
AIzaSyDx5bncjiKOnbQ_1mz8j6kgwHlKGRWxQIo
AIzaSyDj14MIi4L7pCxXIxMfJVFTTaZ5h_stxdI
AIzaSyCxrE6u0KsbiAR6vLrBnC5PPqG05wl-bTA
AIzaSyDX3U9UEgus4Gdx7960ABTn7Ll3LYMxaF4
AIzaSyDEDxFwPbMXPv7GUb1sQduNLCgbpgvU8_I
AIzaSyCCGyjLLZNZnSm0OZOMtxQWe--ZOnsfe_4
AIzaSyDtDKmMytfIq9Ld5VUIOffIoOY7yXxWEv4
AIzaSyCuz7VK0DU7EtdmMHhNidccJfU36j6CADs
AIzaSyCV9dD4V90z7Sf_fWenXEb83ne0Iav5i3o
AIzaSyAqb4ZV4vCfhrfvinj_rGs3lZOP1--zyqc
AIzaSyCo1NhsZBJg5Yloi5HcrdeMnkNGrSloJ9E
AIzaSyCL9cN9Y3-qQbS5wmr9gxk3J9ZICWHsyiQ
AIzaSyDBacTP1FYjR61hHBUNLCEtLAyk5vqFPiI
AIzaSyCV077Q-1nwxMbPM_3f4Z1h1b7ZHjIBE78
AIzaSyDuExne7oG9NnfNZeaqDOSXVtxUdau7IBU
AIzaSyBHXLYc4hrlVFafKd3hsvqniK1Nytw6pDI
AIzaSyDRizynqIPgiZfgrMdqcosDOWxsTGNtwEM
AIzaSyCIjgG29HPVa7_db5NMrQL5O4oBgW18S5o
AIzaSyDa0wwQKBtfwVxP8wmZSTpq2hFxV0QeCY8
AIzaSyAT2aZC-EMqdjYaz2VgBWCtNFp8rE2If34
AIzaSyAvXG6KKI65N1jNOwIRwEQNoyWbXpdDcsg
AIzaSyAEWasCSVrPKoBXH8SEwFYMu6rbKjTFPcQ
AIzaSyAMWdJ5n3Ro5FdnQNJzw0wrUmNYBw0wzKU
AIzaSyDJnRYm3-t06ks4zrBFyEglV6qJyFvn8Qo
AIzaSyDfNTj4zJWiaXbzwvur812s9DPb5CE9y2w
AIzaSyBqopZ5Q_RKyzYYMI-LmcbSaJwONy16TzU
AIzaSyDR49YtJPhn0QJB8Jh6FZM4gSliW9W47xg
AIzaSyBMT1vW_L-VEqZdF_fMCowgtNbzIQduqMI
AIzaSyDsMf87ZAzDFUZHBnc1HQXmidOGDu8anhk
AIzaSyDweH2jQe7RLYDYUUfcJlAVxgcOmLkd3is
AIzaSyDzAOJND8nS3c7Mwzc0rU330eHucH9c-8E
AIzaSyBU-hzfR-sgVp_-OSb3YcPmUwbUOApUIPE
AIzaSyAXVwlY7urRPxPMlkE_T8Z-4hIc_dzgGE0
AIzaSyDRpJORsoLZMRG60l_68TEzH5b3jd6DGZ4
AIzaSyCBHcP4Seer76nBjvy9vOciNxHumZg7eFE
AIzaSyDqH5QxZxhb8SpQ-_LEmhTteXmzYmpSHgE
AIzaSyDHlry6Ma_CLUVyBGOH7ph0WZLKRG2ccbg
AIzaSyAq2b17eIBC4UJpyateDA3fwRgGe2uC6Ko
AIzaSyCfgPZn0kZZPmk5eaaVvlkR-SrGH2rXH4k
AIzaSyBoiXy-lnXJhOOF0YxMeFFbn7F4oOXtTIc
AIzaSyCHCrl4O31xv4msRwR9B52dpghDPmpKwKM
AIzaSyCQddEzfQidcNdtuDbq8SSlO6K3Ds-ayOc
AIzaSyADe2YtszUhEcTR546CNlc3UVssE7PStMU
AIzaSyCD8uwm3Ye7tBS6uFxOM6mao1sqssf_SHw
AIzaSyDlBv9Br45qcfbzGyr3AlcScyWQo3eSOPU
AIzaSyAu_PNVM03Pu1o380ZI_TKU61FSDeDV1Ks
AIzaSyCDz4gjhidaJvdzPjYHxdo1o0CAqiqll9k
AIzaSyDL12z-qYxT4uAv2v2hwjMHUe8iXcK0ETU
AIzaSyDtwjdZFSAW1pJSnAj1kURTN8kGeFWmATU
AIzaSyAUKqVhKd51nSHwZUxQrYTGqPsr31IdyCY
AIzaSyAUfL_KrYf9rY599fGi2v63IR2Z_PmMMcw
AIzaSyAxpV4OY_zVDst3jGaYNwnyHhFmZ0V4XJE
AIzaSyBoJIB7xIHrxVCw6dd8Zv_eJHSe67lmoQM
AIzaSyCaxTDfzcE5UqPCiZ5QmDEXsgQkdgLhp1w
AIzaSyDzQChJoC_jnb5xwn9UHkFyePD3qZYRaBU
AIzaSyCuZ0zCypYmrGT5GXopw3JPu3WGLzEMpGU
AIzaSyBMlGsH4vA9gcEimwnOyMTAFcPZ33J3akI
AIzaSyCXzlJRd3krr5e32pXTAS14mfS3a8dDsMA
AIzaSyAMgFD2kmgb7Cri49Z7-sOAFBejb37LWgQ
AIzaSyAQPkvdoV4_nzsaaYTZu-QqU__kV9988JI
AIzaSyASjNCk1oxREML4AFhcx5joggaSyvDVj9Q
AIzaSyBl5lM6Md8_YbDt7o-rmB2SiIyppPT32dU
AIzaSyDo3Y0qZ5HPmGGoTNGl_6xmxbOAE8q3cSY
AIzaSyBGFC9cnNJEC822NAKicHbX4PJsE1PGn4c
AIzaSyDUlDBDxfl7c33vb3ILKLayrQNROqHhUDg
AIzaSyDYlLPKXjcrvyTMl8ANYH_FgvKM1mrIdcM
AIzaSyCtJ4cwA90mL25n2f-aFUfnOP0t9zEgs7A
AIzaSyCX_TzqcMio44YuQnkFe0HAaUQNE5qa4n4
AIzaSyDQFUa1LGkonbd7152FK6roHyLs4FhMsVM
AIzaSyDNwmGY0aTaE0GFAndRe773JmZtoPBO8WU
AIzaSyBhITH3uj_6kHz4cIHQ5p7-M5cuOXvGwW0
AIzaSyBidS2L33bSz73nCywuv64Z-lxIx3IaifE
AIzaSyBe8kjRD-siRLDQh30xVRka5TmrsAZVwYc
AIzaSyDfO3oWskF7Pj99ua_72pZxEDQgfGEl8Fo
AIzaSyAFsWzfDNb-OE9DM7ucb4Nwx2rgr_5Horg
AIzaSyCNSdZWCnONcfrmjYI3STJAEO3TRGXxgbQ
AIzaSyCd5zymLO6nQPtaSBtDZJbIggZbQFeRCvc
AIzaSyAF5WnWgLOWJthnfD6_KpSuD7eI5FdAQBk
AIzaSyAj9qY_NbobH5D7wkF1QjWWJ0Wb-agLEgI
AIzaSyDRY5Iga1IYEZjK0MroKACshZYDSoDa5kQ
AIzaSyDQs4bJr4VHcO5XZMHM5Gg3GKVSi7hifoU
AIzaSyDnhCxgEgCMPtBgYi9evlCDm8NKWHrFbMc
AIzaSyDegIQZAzMZeiVajsStxrgWvGDMkfZ4GUg
AIzaSyAOIVHb7xcAMqx07D4kM45qwHNZeNu0Qas
AIzaSyChcczCZLpZbYlw7J_vcU5LE-pfKUFiS3o
AIzaSyBOUpORGxljVDusUs-fMVpLKfZNh02AnmU
AIzaSyCUfqhUXwgUzkw_t67wF3D4gsRCBgYm-50
AIzaSyAMZ7sJlD2ZlUyqObwLOZgJ9BGneYATG2o
AIzaSyBN5WVkfPSZ4EcqoTy06XfmaRjXrl0DbnU
AIzaSyBAn3hVZfgMUQYIm-o_-U0TaM1lGCCDItw
AIzaSyBhoK80uMGiY-885co3xqdnCOzXBjBC24k
AIzaSyAr8fG-NsCNjVe3QmnS_zMsu9Kaf6jVGa4
AIzaSyCLExLEYHiMK3Hyo3XoJBEug5wvfLdJ-p0
AIzaSyCLx3zZ7xGSMpuE0jc87gdJV7VNQ5DcSZM
AIzaSyCpoWTEbR9ggzR_74bkUUdjir_2Kw8ALm0
AIzaSyBeUkVJzv1yx9bVIQsveMma-dsAi8TcqME
AIzaSyCmS2TuyUXfXRREsiEEgmWeG8Plu__0SqA
AIzaSyDlwNcNuyR-HnUctFz8Bn2uS0ienqJz2zc
AIzaSyBaUKIYRqv4Bj2V-Kqx1C7IJ5zvlRwLO_E
AIzaSyDm4gztlWTcR2SUla8Y9bH3iXZbshuXD_4

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2025-10-9 10:55 被正己编辑 ,原因:
收藏
免费 37
支持
分享
最新回复 (18)
雪    币: 104
活跃值: (7159)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
tql
2025-10-9 15:08
0
雪    币: 1495
活跃值: (3698)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
123
2025-10-10 11:49
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
6
2025-10-10 16:01
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
谢谢分享
2025-10-13 15:54
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
谢谢分享
2025-10-13 15:56
0
雪    币: 52
活跃值: (1023)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
6
2025-10-13 19:51
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
1
2025-10-26 00:52
0
雪    币: 0
活跃值: (1540)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
感谢分享
2025-11-14 00:16
0
雪    币: 38
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
感谢分享
2025-11-17 23:22
0
雪    币: 764
活跃值: (2897)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
666
2025-11-18 21:37
0
雪    币: 2218
活跃值: (398)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
大佬威武啊,学习了。
2025-11-18 21:55
0
雪    币: 439
活跃值: (964)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
6
2025-11-25 17:22
0
雪    币: 3664
活跃值: (5802)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
14
感谢分享
2025-11-29 21:45
0
雪    币: 205
活跃值: (31)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
2025-11-30 13:50
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
2025-12-1 11:51
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
666
2025-12-1 14:37
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
66666666
1天前
0
雪    币: 14
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
6666666
1天前
0
游客
登录 | 注册 方可回帖
返回