当前位置:首页>学习笔记>Unidbg学习笔记(八):文件系统层补环境

Unidbg学习笔记(八):文件系统层补环境

  • 2026-04-21 04:50:13
Unidbg学习笔记(八):文件系统层补环境

文件访问是仅次于 JNI 的第二高频补环境场景,但它也是**最容易被"补漏"**的通道。因为 Android 真机上文件访问失败本来就很常见,你会下意识地觉得"失败没关系" —— 而恰恰是这种默认的沉默,让反检测代码在你眼皮底下通过。


上一篇把你留在了哪里

第七篇讲完 JNI 通道之后,你已经掌握了全部补环境工作量的 90%。这一篇开始的三篇(文件 / 系统调用 / 库函数)占剩下的 10%,但它们每一个都有独特的陷阱和高价值场景。

文件系统这一篇特别值得慢慢读 —— 不是因为它技术上复杂,而是因为它在反检测对抗里占有压倒性的权重。你会看到,很多 App 抓你 Unidbg 就是靠一个 /proc/self/maps 的读取成败判断。


一个让你警惕的现象:沉默的失败

先看一段几乎每个新手都写过的代码:

// 新手写法: 只 override 了 AbstractJni, 完全没碰文件系统
publicclassMyAppextendsAbstractJni{
publicstaticvoidmain(String[] args){
        Emulator<AndroidFileIO> emulator = AndroidEmulatorBuilder
            .for64Bit()
            .build();
// ... 加载 SO, 调 native 方法
// 跑完了, 没报错, 以为通了
    }
}

跑起来,没有 IllegalStateException: resolve failed,没有 UnsatisfiedLinkError,最终 native 方法也返回了一个结果 —— 看起来完全通了

但你把这个结果拿到真机上对照 Frida 抓的"标准答案",发现不一致。于是你开始怀疑是不是 JNI 值写错了、是不是哪个参数对不上。查了半天全是对的。

真相往往是这样一段代码藏在 SO 里:

// SO 内部的反检测逻辑
intcheck_frida(){
    FILE *fp = fopen("/proc/self/maps""r");
if (fp == NULL) {
// 文件打不开, 无法判断 -> 认为环境异常, 返回错误标记
return0x1337DEAD;
    }
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "frida") || strstr(line, "gum")) {
            fclose(fp);
return0x1337DEAD;  // 发现 Frida, 返回错误标记
        }
    }
    fclose(fp);
return0;  // 正常
}

在真机上开 Frida,这段代码会读到含 frida 的行 → 返回错误标记;不开 Frida,读不到 → 返回 0 正常。

在 Unidbg 里呢?

  • Unidbg 默认找不到 /proc/self/mapsfopen 返回 NULL
  • 走第一个分支,直接返回 0x1337DEAD
  • 这个值进入后续签名算法,让最终结果偏离真机

整个过程没有任何报错。Unidbg 认认真真跑完了每一行 SO 代码,你也认认真真看着它跑完 —— 但你们都不知道,有一个检测分支在你眼皮底下被触发了

沉默的失败:同一段 SO 代码在三种环境下的三种结局

这就是文件系统层补环境的核心挑战:失败是沉默的,沉默是致命的


为什么文件访问容易被补漏

三个原因叠加,让这个通道成为盲区:

原因 1:Android 本身就很混乱

Android 的碎片化让文件访问本来就是个"测不准问题"。同一个 /sys/class/power_supply/battery/voltage_now,在不同机型、不同 Android 版本、不同 SELinux 状态下,可能存在、可能不存在、可能能读、可能 permission denied。SO 开发者也知道这一点,他们会写大量的 if (fp == NULL) return default_value 来处理不确定性。

原因 2:SO 往往会"兜底"

因为原因 1 的存在,SO 代码几乎总会给文件读取一个降级分支。Unidbg 里文件打不开时,SO 不会崩溃,它会安静地走降级。新手看到"没报错"就以为通了。

原因 3:开发者下意识觉得文件访问"无所谓"

打开文件失败,在真机上也经常发生 —— 这种直觉让你不会下意识地去盯着 fopenaccessfread。你会觉得 "noisy logging,忽略就行"。但反检测代码恰恰利用了这种轻视

三个原因叠加的结果:你没注意到一个失败,SO 走了错分支,最终结果错了


文件访问的两大用途:分清动机

要有意识地补对这个通道,第一步是分清 SO 读文件的两种动机。这两种动机的处理方式完全不同。

文件访问的两大用途

用途一:信息收集

SO 想知道一个客观事实:电池电压、CPU 温度、屏幕密度、Kernel 版本 ……这些信息在 Android API 里不一定有现成的获取方式,或者获取成本较高,直接读文件更方便。

典型路径

/sys/class/power_supply/battery/*    设备电池信息
/sys/class/thermal/*                 CPU 温度传感器
/proc/cpuinfo                        CPU 型号 / 核心数
/proc/meminfo                        内存信息
/sys/block/*/size                    存储容量
/data/local/tmp/install_id           App 自己写的文件

处理原则:尽量伪造看起来合理且稳定的值。不用和真机 100% 一致(因为真机每次读出来也不完全一样),但必须在合理范围内。

用途二:环境检测

SO 想判断自己是不是跑在可疑环境里:有没有 Root?有没有 Frida?有没有 Xposed?是不是在模拟器里?这时候它就会去读特定的路径,看这些路径是否存在、内容是什么。

典型路径

/system/bin/su                       判断 Root (经典)
/system/xbin/su                      判断 Root (备用位置)
/proc/self/maps                      内存映射, 检测 Frida / Xposed 注入
/proc/self/status                    TracerPid 反调试
/data/data/de.robv.android.xposed/   Xposed 安装路径
/system/lib/libxposed_art.so         Xposed native 库
/proc/net/tcp / tcp6                 检测 Frida 的 27042 端口
/sys/devices/system/cpu/kernel_max   某些厂商检测用
/sbin/magisk                         判断 Magisk

处理原则:必须让 SO 觉得"这是一个干净的真实设备"。关键是:

  • Root 相关路径必须报不存在
  • /proc/self/maps 必须返回一份不含 frida / xposed / riru 的内容
  • /proc/self/status 里 TracerPid 必须是 0

核心区别一句话

信息收集 = 伪造一个合理值;环境检测 = 伪造一份干净的设备状态

把这两种用途分清楚,你就知道自己在"补什么"了。下面进入具体的实现机制。


IOResolver:Unidbg 的文件系统钩子

Unidbg 给文件系统设计的钩子叫 IOResolver。它的定位和 AbstractJni 非常对称:

通道
注册入口
核心接口
方法语义
JNI
vm.setJni(jni)Jni
报错驱动,override 对应 callXxx
文件
emulator.getSyscallHandler().addIOResolver(resolver)IOResolver
每次 open/access 时被回调

当 SO 调 open("/proc/self/maps", O_RDONLY) 时,Unidbg 的 SyscallHandler 不会直接去读文件,而是先遍历所有注册的 IOResolver,挨个问:"你能处理这个路径吗?" 第一个给出有效回答的 resolver 赢得这次调用。

这个设计非常经典:责任链模式。你可以注册多个 IOResolver,每个负责不同的路径:

// 注册顺序决定优先级, 先注册的先被问
emulator.getSyscallHandler().addIOResolver(new ProcSelfResolver());       // 专门处理 /proc/self/*
emulator.getSyscallHandler().addIOResolver(new AntiDetectResolver());     // 专门处理反检测路径
emulator.getSyscallHandler().addIOResolver(new DeviceInfoResolver());     // 专门处理设备信息

这种分层让代码可维护,也让你可以把"反检测"和"信息伪造"分成不同模块。

resolve 方法的三种返回语义

IOResolver 只有一个核心方法 resolve,但它的返回值有三种语义,这是整个文件系统层补环境的精髓:

IOResolver.resolve 的三种返回语义
publicinterfaceIOResolver<TextendsNewFileIO{
FileResult<T> resolve(Emulator<T> emulator, String pathname, int oflags);
}

返回值 1:一个有效的 FileResult

表示"这个路径我来处理,这是伪造的内容"。

@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator,
                                          String pathname, int oflags)
{
if ("/proc/self/cmdline".equals(pathname)) {
// 伪造进程名: 返回 App 真实的包名
byte[] cmdline = "com.example.app\0".getBytes();
return FileResult.success(new ByteArrayFileIO(oflags, pathname, cmdline));
    }
returnnull;  // 其它路径交给下一个 resolver 或 Unidbg 默认
}

返回值 2:null

表示"我不管这个路径,交给下一个 resolver 或 Unidbg 默认去处理"。这是最常用的返回值,也是责任链模式能工作的关键。

public FileResult<AndroidFileIO> resolve(...){
if (!pathname.startsWith("/proc/self/")) {
returnnull;  // 不是我负责的范围
    }
// ... 处理 /proc/self/* 路径
}

返回值 3:FileResult.failed()

最容易被忽略但最重要的一种返回。含义是:"这个路径确定不存在,不要再交给后续任何 resolver,也不要让 Unidbg 去查真实文件系统。直接告诉 SO 这里没有。"

public FileResult<AndroidFileIO> resolve(...){
// 反检测: Root 相关路径必须报不存在
if (pathname.equals("/system/bin/su")
        || pathname.equals("/system/xbin/su")
        || pathname.equals("/sbin/magisk")) {
// FileResult.failed 告诉 SO "这里没有这个文件"
// 返回 null 会让 Unidbg 去查真实磁盘, 你可能不小心把主机的 /sbin 暴露出去
return FileResult.failed(-1);
    }
returnnull;
}

为什么不能用 null 代替 FileResult.failed?

因为 null 的语义是"我不管,你去问别人"。如果后面没有其他 resolver,Unidbg 会尝试去读真实磁盘。万一你的开发机恰好有 /sbin/magisk(别笑,Mac 上还真有一些 /sbin 下的东西),或者 Unidbg 内置的某些兜底把这个路径当存在,你的"反检测"就失效了。

记住这个三元关系

返回值
语义
什么时候用
FileResult.success(fileIO)
"有,内容是这个"
需要伪造文件内容
null
"不知道,你问别人"
这条路径不在我的职责范围
FileResult.failed(-1)
"确定没有"
反检测路径,必须断然否认

补环境的一个顶级原则:**反检测路径必须用 failed,不能用 null**。这是新手最常犯的错,也是检测代码最容易利用的缝隙。

SimpleFileIO vs ByteArrayFileIO:选哪个?

IOResolver 返回的 FileResult 需要包装一个 NewFileIO 的实现。Unidbg 给了两个常用选择:

SimpleFileIO —— 从磁盘真实文件读

// 从项目资源目录下加载一个事先准备好的 cpuinfo 文件
File cpuinfoFile = new File("target/resources/device/cpuinfo");
return FileResult.success(
new SimpleFileIO(oflags, cpuinfoFile, pathname));

适用场景

  • 文件内容体积大(几 KB 到几 MB),不想塞在代码里
  • 你已经从真机 adb pull 了一份真实文件,放在项目资源下
  • 内容不会变(比如 /proc/cpuinfo

ByteArrayFileIO —— 从内存字节数组读

// 直接构造字节内容, 无磁盘 IO
byte[] cmdline = "com.example.app\0".getBytes();
return FileResult.success(
new ByteArrayFileIO(oflags, pathname, cmdline));

适用场景

  • 内容体积小(几十到几百字节)
  • 内容需要动态生成(比如每次根据当前 PID 计算)
  • 你只想写一行代码不想建文件

选择原则

内容 > 1KB 且固定 → SimpleFileIO (文件准备一次, 永久复用)
内容 < 1KB 或需要动态生成 → ByteArrayFileIO (代码中直接构造)

大多数反检测场景用 ByteArrayFileIO,因为反检测相关的文件通常很小;大多数设备信息伪造用 SimpleFileIO,因为 /proc/cpuinfo/proc/meminfo 之类的文件内容较长。


proc 伪文件系统深入:最关键的一片战场

/proc 值得单独拉一章出来讲,因为它不是普通文件系统。

它的特殊之处:内核动态生成

/proc 是 Linux 内核提供的一个伪文件系统。里面的文件不在磁盘上,它们是内核根据当前进程状态动态生成的。每次你 cat /proc/self/status,内核都重新跑一段代码,把当前进程的信息格式化成文本丢给你。

这意味着两件事:

  1. 不能一字不改地把真机 adb pull /proc/self/status 的内容直接塞给 Unidbg —— 因为 self 指向真机当时的某个进程,里面的 Pid / Tgid 和你 Unidbg 里的不一样,直接复用会穿帮。正确做法是把它当模板,保留大部分原始字段,只按 Unidbg 当前运行时动态替换 Pid / Tgid / TracerPid 等几个关键行(见下文 /proc/self/status 的"进阶技巧")
  2. Unidbg 必须手动模拟每个关键 proc 文件的输出

下面是反检测代码最关心的四个 proc 路径。逐一拆。

/proc 四大反检测战场:cmdline / status / maps / net/tcp 的全景导航

/proc/self/cmdline — 进程名

这是反检测里最基础的一道关。很多 SO 在初始化时会读这个文件,确认自己跑在"预期的包名"下。如果读出来的进程名不对,SO 会怀疑是被注入到其他进程。

if (pathname.equals("/proc/self/cmdline")) {
// 注意: cmdline 的格式是 argv 各段用 \0 分隔, 末尾也要有一个 \0
// 对大多数 App 来说只有一段: 包名\0
    String packageName = "com.example.app";
byte[] cmdline = (packageName + "\0").getBytes(StandardCharsets.UTF_8);
return FileResult.success(new ByteArrayFileIO(oflags, pathname, cmdline));
}

新手陷阱:很多人写成 packageName.getBytes(),忘记末尾的 \0。大部分 SO 用 read 读到 EOF 就停,可能没事;但用 strlen 找结束符的 SO 会读到后续的随机内存,行为未定义,下次跑不同的结果。末尾的 \0 不是可选的。

/proc/self/status — TracerPid 反调试

/proc/self/status 里有一行关键字段:TracerPid: 0。这个值不为 0 就表示有调试器(比如 gdbptrace 附加)正在跟踪这个进程。反调试代码会读这一行,看到非 0 就终止。

真机上的 status 文件内容大约 50 行,包含名称、状态、PID、内存占用等信息。Unidbg 里你必须构造一份包含正确 TracerPid: 0 的内容:

if (pathname.equals("/proc/self/status")) {
// 构造一份完整的 status, 关键是 TracerPid 必须是 0
    StringBuilder sb = new StringBuilder();
    sb.append("Name:\tcom.example.app\n");
    sb.append("State:\tS (sleeping)\n");
    sb.append("Tgid:\t12345\n");
    sb.append("Pid:\t12345\n");
    sb.append("PPid:\t1\n");
    sb.append("TracerPid:\t0\n");    // !!! 这一行是重点 !!!
    sb.append("Uid:\t10101\t10101\t10101\t10101\n");
    sb.append("Gid:\t10101\t10101\t10101\t10101\n");
// ... 其它字段, 可以从真机 pull 一份作为模板
byte[] content = sb.toString().getBytes(StandardCharsets.UTF_8);
return FileResult.success(new ByteArrayFileIO(oflags, pathname, content));
}

进阶技巧:正确姿势不是从零手写,而是用真机做模板、按运行时打补丁

  1. 在真机上 adb pull /proc/self/status 拿到一份真实样本
  2. 在 Unidbg 里用 emulator.getPid() 拿到当前运行时 PID
  3. 只替换 Pid / Tgid 两行为当前 PID,TracerPid 强制写 0,其他字段原样保留
  4. 这样既保持了"和真机完全一致的字段集",又避免了 PID 不一致被穿帮

这是"模板 + 补丁"的思路:整体抄真机(保真),关键字段动态生成(避免穿帮)。下面 /proc/self/maps 用的也是同一个套路。

/proc/self/maps — 内存映射(最重要)

这是反检测代码最爱读的一个文件。原因是 /proc/self/maps 列出了进程加载的所有共享库和内存段,如果有人注入了 Frida / Xposed / 某个 rootkit,一定会在这里留下痕迹。

# 真机上一个干净进程的 maps 片段
70a1234000-70a1500000 r-xp 00000000 103:03 12345    /data/app/.../libapp.so
70a1500000-70a1600000 r--p 002cc000 103:03 12345    /data/app/.../libapp.so
...

# 被 Frida 注入后的 maps
7f12340000-7f12500000 r-xp 00000000 00:00 0         /data/local/tmp/re.frida.server/frida-agent-64.so
7f12500000-7f12600000 r--p 001cc000 00:00 0         /data/local/tmp/re.frida.server/frida-agent-64.so
...

反检测代码只需要 grep -i "frida\|gum\|xposed\|riru",就能识破所有常见的注入。

在 Unidbg 里伪造 maps 的关键

  1. 必须看起来像真实设备(有 /system/lib64/libc.solinker64 等基础库)
  2. 必须包含你自己的 libapp.so(SO 会在里面找自己)
  3. 必须不含 frida / xposed / substrate / riru 等敏感字眼
  4. 地址范围要合理(ARM64 用户空间大致在 0x12c00000 到 0x8000000000 范围)

实战中一般从真机 adb pull /proc/self/maps(针对一个目标 App 进程),然后:

  • 过滤掉 frida / xposed 相关行
  • 替换 libapp.so 的路径为 Unidbg 下的路径(或保留原路径,反正 SO 只看基址)
  • 保存为资源文件,用 SimpleFileIO 返回
if (pathname.equals("/proc/self/maps")) {
// 预先从真机 pull 并清洗过的 maps 文件
    File mapsFile = new File("src/main/resources/device/clean_maps.txt");
return FileResult.success(new SimpleFileIO(oflags, mapsFile, pathname));
}

警告:直接用 Unidbg 默认返回的 maps 往往不够干净。Unidbg 会把自己加载的 ELF 段列在里面,但不会主动加入 /system/lib64/libc.so 等基础库,这种"缺胳膊少腿"的 maps 本身就是特征。真实的检测代码可能不是检测"有没有 frida",而是检测"有没有 libc.so" —— 没有就说明不是正常 Android 进程。

/proc/net/tcp 和 tcp6 — 端口检测

Frida 默认监听 27042 端口(如果你用 frida-server)或者某个随机端口(如果用 gadget)。反检测代码会读 /proc/net/tcp 看有没有进程监听这些可疑端口。

# /proc/net/tcp 的格式
sl local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt ...
 0: 0100007F:69A2 00000000:0000 0A ...
                  ^^^^
                  0x69A2 = 27042, 这就是 Frida 的默认端口

反检测策略:伪造一份只包含正常端口的 tcp 内容,或者直接返回文件不存在(有些系统这个文件本来就难读)。

if (pathname.equals("/proc/net/tcp") || pathname.equals("/proc/net/tcp6")) {
// 只返回一个空表头, 表示没有任何监听的 socket
    String empty = "  sl  local_address rem_address   st tx_queue rx_queue tr"
                 + " tm->when retrnsmt   uid  timeout inode\n";
return FileResult.success(new ByteArrayFileIO(oflags, pathname,
                                                   empty.getBytes()));
}

PID 一致性问题:一个隐蔽的坑

接着上面 /proc/self/status 的话题讲一个特别容易踩的坑。

Unidbg 的 PID 来自哪里

Unidbg 没有"进程"的概念。它是一个 Java 库,跑在宿主 JVM 里。但 SO 代码可能会调 getpid() 或 getppid()。Unidbg 怎么办?

它返回宿主 JVM 的 PID

// Unidbg 内部: UnixSyscallHandler.getpid
@Override
publicintgetpid(Emulator<?> emulator){
// 直接返回 JVM 进程 PID, 每次运行都不一样
return android.os.Process.myPid();  // 实际是 ProcessHandle.current().pid()
}

这导致两个直接后果:

后果 1:每次运行 PID 都不同

你今天跑 Unidbg 拿到 PID=12345,明天拿到 PID=67890。所以任何涉及 PID 的逻辑都不能硬编码

后果 2:不能从真机复制 /proc/12345/ 下的任何内容

新手常干的事:从真机 adb pull /proc/self/ 一堆文件,然后在 IOResolver 里做路径匹配:

// 错误写法!
if (pathname.equals("/proc/12345/status")) {  // 真机当时的 PID
return FileResult.success(...);
}

这段代码永远不会被触发,因为 Unidbg 里 /proc/self/ 被解析成 /proc/{当前 JVM PID}/,而你写死的是真机 PID。

正确的做法:动态匹配

@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator,
                                          String pathname, int oflags)
{
// 拿到当前 Unidbg 里 SO 看到的 PID
int pid = emulator.getPid();
    String selfPrefix = "/proc/" + pid + "/";

// 三种形式都要拦: /proc/self/xxx, /proc/{pid}/xxx, 以及简写
if (pathname.startsWith("/proc/self/") || pathname.startsWith(selfPrefix)) {
// 提取最后一段文件名, 比如 "status" / "maps" / "cmdline"
        String filename = pathname.substring(pathname.lastIndexOf('/') + 1);
return handleProcSelf(filename, pathname, oflags);
    }
returnnull;
}

private FileResult<AndroidFileIO> handleProcSelf(String filename,
                                                   String pathname, int oflags)
{
switch (filename) {
case"status":  return buildStatus(pathname, oflags);
case"maps":    return buildMaps(pathname, oflags);
case"cmdline"return buildCmdline(pathname, oflags);
// ...
    }
returnnull;
}

关键点

  • 用 emulator.getPid() 拿到运行时 PID
  • 同时拦截 /proc/self/ 和 /proc/{pid}/ 两种前缀(SO 两种写法都可能用)
  • 永远不硬编码 PID

反检测实战:一份最小 IOResolver 模板

把前面所有东西串起来,这是一个覆盖大多数反检测场景的最小 IOResolver 模板。你可以直接拿去用:

publicclassAntiDetectIOResolverimplementsIOResolver<AndroidFileIO{

// Root 相关路径, 必须报不存在
privatestaticfinal Set<String> ROOT_PATHS = new HashSet<>(Arrays.asList(
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/sbin/magisk",
"/sbin/.magisk",
"/cache/.disable_magisk",
"/dev/.magisk.unblock"
    ));

// Xposed 相关路径, 必须报不存在
privatestaticfinal Set<String> XPOSED_PATHS = new HashSet<>(Arrays.asList(
"/system/lib/libxposed_art.so",
"/system/lib64/libxposed_art.so",
"/system/framework/XposedBridge.jar",
"/data/data/de.robv.android.xposed.installer/"
    ));

@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator,
                                              String pathname, int oflags)
{
// 1. 反检测: 确定性报"不存在"
if (ROOT_PATHS.contains(pathname) || XPOSED_PATHS.contains(pathname)) {
// !!! 用 failed 而不是 null, 防止 Unidbg 默认去查真实磁盘 !!!
return FileResult.failed(-1);
        }

// 2. /proc/self/* 系列, 动态构造
int pid = emulator.getPid();
if (pathname.startsWith("/proc/self/")
            || pathname.startsWith("/proc/" + pid + "/")) {
return handleProcSelf(pathname, oflags);
        }

// 3. /proc/net/tcp 系列, 空表返回
if (pathname.equals("/proc/net/tcp") || pathname.equals("/proc/net/tcp6")) {
            String empty = "  sl  local_address rem_address   st tx_queue\n";
return FileResult.success(
new ByteArrayFileIO(oflags, pathname, empty.getBytes()));
        }

// 4. 其它路径交给下一个 resolver 或 Unidbg 默认
returnnull;
    }

private FileResult<AndroidFileIO> handleProcSelf(String pathname, int oflags){
        String filename = pathname.substring(pathname.lastIndexOf('/') + 1);
switch (filename) {
case"cmdline":
byte[] cmdline = "com.example.app\0".getBytes();
return FileResult.success(
new ByteArrayFileIO(oflags, pathname, cmdline));

case"status":
// 实际工程中这里应该加载一份预先清洗过的真机 status
return FileResult.success(
new SimpleFileIO(oflags,
new File("src/main/resources/device/status.txt"),
                        pathname));

case"maps":
// 清洗过的 maps, 已去除 frida/xposed
return FileResult.success(
new SimpleFileIO(oflags,
new File("src/main/resources/device/maps.txt"),
                        pathname));

default:
returnnull;
        }
    }
}

使用方式

// 在创建 emulator 之后, 加载 SO 之前注册
emulator.getSyscallHandler().addIOResolver(new AntiDetectIOResolver());

验证闭环:怎么知道自己补对了

和前两篇一样,"跑通"不等于"跑对"。文件系统层的验证方式有自己的特点:

方法 1:Frida trace fopen / open / access

在真机上用 Frida hook 这三个函数,记录 SO 实际访问过的所有路径。对照 Unidbg 里的日志,看路径覆盖是否一致。

// Frida 脚本: 抓 SO 调用的所有文件访问
var openPtr = Module.findExportByName('libc.so''open');
Interceptor.attach(openPtr, {
onEnterfunction(args{
this.path = args[0].readCString();
    },
onLeavefunction(retval{
console.log('[open] ' + this.path + ' => fd=' + retval);
    }
});
// 类似地 hook fopen 和 access

方法 2:在 IOResolver 里加 log

@Override
public FileResult<AndroidFileIO> resolve(...){
    System.out.println("[IOResolver] " + pathname + " flags=" + oflags);
// ...
}

方法 3:开 SyscallHandler 的 verbose 日志

// 让 Unidbg 打印所有 open 系统调用, 包括默认处理的
emulator.getSyscallHandler().setVerbose(true);

真正的检验:对照 Frida 抓到的路径列表和 Unidbg 实际处理的路径列表,两者必须完全一致。如果 Frida 里 SO 读了 /proc/self/maps 而 Unidbg 日志里没有,说明 Unidbg 把它兜底处理了,你没看到;或者 SO 没读到你的 resolver,说明它走了另一条路径。


总结:文件系统层的五条铁律

  1. 分清动机:信息收集 vs 环境检测,处理方式不同
  2. resolve 三返回:success / null / failed —— 反检测必须用 failed
  3. ByteArray 放小的,SimpleFileIO 放大的:选错了要么性能差要么难维护
  4. PID 永远不硬编码:用 emulator.getPid(),同时拦截 /proc/self/ 和 /proc/{pid}/
  5. 沉默是魔鬼:文件访问失败不报错,必须主动 log + 对照 Frida 验证

文件系统层补不好,SO 会在你看不见的地方走错分支。一旦你建立起"每条文件访问都要明确处理"的本能,大部分反检测就不再神秘。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-04-21 06:20:02 HTTP/2.0 GET : https://67808.cn/a/482371.html
  2. 运行时间 : 0.092537s [ 吞吐率:10.81req/s ] 内存消耗:4,705.09kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=2e604721176b13382482f0b08661a40f
  1. /yingpanguazai/ssd/ssd1/www/no.67808.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/no.67808.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/no.67808.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/no.67808.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/no.67808.cn/runtime/temp/6df755f970a38e704c5414acbc6e8bcd.php ( 12.06 KB )
  140. /yingpanguazai/ssd/ssd1/www/no.67808.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000524s ] mysql:host=127.0.0.1;port=3306;dbname=no_67808;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000787s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000323s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000323s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000515s ]
  6. SELECT * FROM `set` [ RunTime:0.000206s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000507s ]
  8. SELECT * FROM `article` WHERE `id` = 482371 LIMIT 1 [ RunTime:0.001350s ]
  9. UPDATE `article` SET `lasttime` = 1776723602 WHERE `id` = 482371 [ RunTime:0.009269s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 65 LIMIT 1 [ RunTime:0.001125s ]
  11. SELECT * FROM `article` WHERE `id` < 482371 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000508s ]
  12. SELECT * FROM `article` WHERE `id` > 482371 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000956s ]
  13. SELECT * FROM `article` WHERE `id` < 482371 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000760s ]
  14. SELECT * FROM `article` WHERE `id` < 482371 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.007203s ]
  15. SELECT * FROM `article` WHERE `id` < 482371 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001186s ]
0.094275s