☰
AI 自动化逆向(二)AI 会相信它自己上一轮写的表
2026/10/14 1:59:51 网站建设 项目流程

AI 自动化逆向(二)AI 会相信它自己上一轮写的表

本文是「AI 自动化逆向」系列第 2 篇。上一篇:最贵的错误是「看起来像结论的错答案」;下一篇预告:我把一个逆向项目交给了 AI:它做到了什么程度。

前言

上一篇的陷阱 4/5 只写了一页,末尾留了一个扣子:AI 有一条判据,在同一个问题上连续推翻自己五次,从"这段是数据"一路改到"这段是夹层",而每一个中间结论,都曾经写进过文档。

这一篇把那条链完整讲开。

这条链是在一句很简单的目标下跑出来的:把剩余的未解条目,逐个给出明确结论。它最终也做到了——107 个"受保护函数"第一次全部有了定案:68 个可继续挖、39 个判定为纯桩、0 个"纯数据";有名字的从 37 涨到 43,三个被误撤的条目全部恢复。

但过程是:六轮工作,五次推翻。每一次推翻之前,它交出的东西都长得像结论——格式完整的表格、自洽的数字、跑得通的回归脚本;五次里每一次,都曾经是它手里"最好的答案"。

(同上一篇:文中"AI"指执行分析的模型,"我"指设定目标、审结果的人。所有地址、常量、数值一律脱敏。)

一、AI 在这里踩的坑

任务的起点很不起眼:给这批受保护函数做命名。

AI 手里有一张清单——它自己早先产出的——每个条目带起点和大小。它默认[起点, 起点+大小)就是函数体,然后跑进每个区间里提取字符串引用、API 引用,作为"这个函数引用了什么"的证据。

产出很快、很整齐:107 个条目、一张规整的表、每条结论都带着格式一致的证据行。

问题就出在这里:那张表是它自己造的,所以它默认表是对的。人对自己的手抄表有天然的不信任;AI 对自己生成的表天生信任,它从来不问一句"这张表本身成立吗"。

裂缝来自一次普通的审计:对全部 107 个条目逐字节解码,统计每个区间"能按顺序解出指令的字节比例"。

解码率条目数
≥90%84
50–90%1
20–50%10
<20%12

22 个条目的"函数体"解不出来。最差的一条,数千字节里只有0.4%能按顺序解码——把它打开看:第一条指令就是一个跳进保护段的跳转,跳转之后全是垃圾。

但比"22 个"更要命的是分布的形状:从 0.005 到 0.45 之间没有天然断层。AI 当时注意到了这一点,却把它解释成"阈值有任意性"——于是它拍了一个阈值,建了一个闸门:函数级解码率低于阈值的条目,判定"函数体不是代码",把从里面提取的证据整批撤回。

复盘到这里,这一篇的题目已经出现了:它在老表的上面,又摞了一张新表。

人应该加的检查:任何"某函数体内有 X"的结论,先过一关——那段字节本身是不是代码,且不能用函数级的单一比例来判(为什么不行见第 3 轮)。以及,"阈值选不出来"不是小事,它是模型不对的信号。

二、过程:六轮工作,五次推翻

第 1 轮:建闸门,整批撤回

闸门装上后,全部引用被分成两拨:干净引用76条(落在 11 个"正常"条目里),可疑引用41条(落在 5 个"垃圾"条目里)。41 条被整批撤回。

同一轮里,闸门自己先崩了一次:第一版对"不在清单里的地址"返回"判为数据"——它自己的对照组从 26 个崩到 2 个,因为一大批正常的普通函数被当成数据拦掉了。

修法就一句话:“我不认识"和"我知道它是坏的”,必须分开。

净结果有点反直觉:有名字的条目 37 → 39,丢了 4 个、补回 6 个。撤回证据的同时,它做了一件对的事——把名字的来源拆开了:有些名字从来就不依赖"读函数体",那些名字被单独标注成"不是因为能读到它的函数体"。这一步防的是将来有人把已撤回的证据,悄悄挂在独立来源的名字后面"洗白"。

第 2 轮:一个"分界线上"的条目里,捞出了两段真逻辑

它顺着一条待办去读一对"无参布尔函数"的实现。结果发现两件事:

  • 其中一个是纯桩——十几个字节全是跳转和ret,没有一条真逻辑;真正在做判断的是保护段里的代码。"谓词"这个标签只对了一半。
  • 另一个的尾部藏着两段真逻辑——用一种新写法捞出来的:在每个偏移都尝试解码,保留以ret结尾、且不含跳进保护段的转移的连续段。捞出来的一段直读就是:
; 尾部捞出的第二段(示意): cmp dword ptr [SLOT_A], 2 ; 全局槽 A == 2 ? je 继续 xor al, al ret ; A != 2 → 返回假 继续: setg al ; al = (signed char)SLOT_B > 0 ret ; 返回 (B > 0)

(语义完整:return (A == 2) && (B > 0)。注意它是从一段"没有天然断层"的灰区字节里捞出来的。)

⇒ 第一次推翻:上一轮说"这些区间的函数体是数据"——这里明明有语义自洽的真代码。

难得的是,它当场自己把风险写下来了:这个"捞片段"的规则会产生假阳性(垃圾里也能随机撞出以ret结尾的短段),这一例是靠语义自洽人工确认的,不是自动可判的;而且只在 1 个条目上验证过——是不是系统性问题,还没测。

第 3 轮:模型错了——不是"数据",是"夹层"

下一轮,它把"捞片段"跑遍了全部 22 个可疑条目:

  • 11 / 22 夹着带证据的真代码,共恢复255 个片段;
  • 没有一个条目是"零片段"——连最差的那条也能捞出东西来。

最能说明问题的是反差:

条目解码率恢复片段数其中带证据
解码率最低的那条0.0052817

解码率与"含真代码多少"完全不成正比——最低分拿到第二名多的证据。

还有一条决定性的:解码率最低的那个区间里,有一整条完整的if-else 链,逐个比较 8 个名字,jne串联,语义完全自洽。(上一篇陷阱 6 的那条反例,就是它。)

⇒ 第二次推翻:不是"数据 or 代码"的二分,是"夹层"。保护壳把原始指令逐条换成跳进保护段的桩,但原始字节留在原地;于是幸存的真指令夹在桩与桩之间,被字节流隔断——线性解码必然失步。低解码率不等于"不是代码",而是"大部分不是代码,但剩下的部分可能是"。

同两轮的判决一起下来:上一轮那批"整批撤回"是过度撤回;闸门机制废弃,判据的单位从"函数"换成"片段"。

它还自己给这条链做了一个总复查——一份很难得的自我诊断:

有的轮次是模型错(拿函数级比例判"是不是代码");有的是方法错(把整段区间的字节流当指令读);有的是预期错;还有一次是发现得太晚——夹层已经露头,却没有立刻推广。
共同根因只有一个:一直想找一个"函数级"的二元判据,而真实结构是"区间内的分层混合"。
该早点问的是:“这段字节是均匀的,还是分层的?”

第 4 轮:重建——单位从"函数"换成"片段"

重建的方案是"片段 + 区间双证据":优先用捞出来的片段作证据;捞不到片段的,回退到整区间提取,但单独降级标注。"有没有代码"和"有多少证据"是两个正交信号,不合成一个布尔。

重建里它又踩了一步,而且是熟悉的一脚:第一版把"带证据的片段数 == 0"直接判"数据"——66 个条目被判数据。荒谬。66 个里包含解码率 100%、单纯"不引用任何字符串"的干净代码:“没有证据"被当成了"没有代码”。

抓住这一步的不是感觉,是已知真值回归:拿 17 个此前由独立方法命名的函数当真值,测新判据是否还找得到它们的证据——结果片段法会漏掉 3 个。于是定下"片段优先、无片段回退整区间",并把回退证据标成低置信。它自己把这条标成"本轮最有价值的方法论"。

⇒ 第三次推翻:"整批撤回"改判——3 个条目被证明是误撤、已恢复。"全撤"和"全留"是同一种错。

结果:

指标闸门版重建后
未解1713
高置信名字1925
有名字合计3943
被判"纯数据"220

0/107 被判"纯数据"——107 个区间全部含代码(片段或干净解码),只是可读比例不同。

还有一个工程动作值得单独记:被废弃的闸门被改成**“调用即抛异常”**——将来任何一个脚本想悄悄启用旧判据,都会当场炸掉。错误的判据不会自己消失,必须让它无法静默复活。

第 5 轮:量化假阳性——那批"垃圾串"的真身

一个洞一直挂着,是它自己记的:捞片段的规则会产生假阳性,率是多少,没测。(第 2 轮就警告过。)

它先做阴性对照,结果两次都失败:

  • 拿保护段当"肯定不是代码"的对照 → 恢复出0 个片段;
  • 换成数据段里的字符串块 →还是 0 个。

看起来是"规则不乱报",其实——恢复规则只在"已知指令边界"起步,而那两块区域里没有任何已知边界。测到的是"规则压根没跑",不是"规则很干净"。它自己指出:这和第一轮"没有天然断层"是同一类错误——度量设计本身无效,而不是结果好。

然后才是量化:全部片段里,23.8% 带垃圾特征。判据两条——

  • 含编译器几乎不会产出的指令(aaa/iretd/xlatb这类);
  • call/jmp的立即数目标不在另一套独立扫描得到的指令起点集合里。

真阳性回归:那 8 个"名字"片段8/8 全部通过;加过滤器后真证据零损失——丢掉的全是垃圾串。

⇒ 第四次推翻:"恢复出来的片段 = 证据"的默认被推翻。顺带,它把上一篇陷阱 4 里那条假证据的真身也查清了:那些"形如!Q+的短乱码串",是误码片段把随机字节当成指令、又把随机的立即数解析成了字符串地址——不是"数据里的巧合",是"误码的产物"。

同一轮里,它还把自己刚写的另一个判据否掉了一次:用"片段数 == 0"判"无代码",误伤了一个靠指针恒等式命名的函数。修法:“找到过、但判它是垃圾” ≠ “没找到”——这是三值,不是布尔。

第 6 轮:最后一块拼图——"干净代码"这一类不存在

重建时它曾把 7 个条目判为"解码率 100%、代码全可读、只是不引用任何字符串",标注为"静态到顶"。

下一轮逐个做结构检验:7/7 全部是纯桩——各含2–9 条跳进保护段的跳转、零可恢复片段、无标准函数序言。那个"解码率 100%"是垃圾解出来的(上一篇陷阱 7 的那段反汇编,就是它)。

⇒ 第五次推翻:单看解码率,永远证明不了"这段是代码"——它对垃圾偏高,而且在最难的情形(纯桩)上给出最漂亮的值。

判"是不是桩"要用合取:可恢复片段 == 0且跳进保护段的跳转 > 0。干净函数的零片段伴随零跳转;桩的零片段伴随 2–9 个。

终态账:13 个未解分成7 个纯桩 + 6 个可继续挖的混合区间;全量 107 个三分——68 可挖 / 39 桩 / 0 纯数据。

最后是这个项目里最有用的一个结论:桩 ≠ 不可命名。39 个桩里,27 个已经有了名字或类型,全部来自非函数体来源——指针恒等式、调用者证据、数据槽与表。函数体不可读,不等于函数不可命名;13 个未解里的那 7 个桩,缺的正是这些非函数体证据。

三、判据与验证

前面是"它翻了什么",这一节是"下次靠什么不翻"。

1. 阈值选不出来时,先怀疑模型,别怀疑阈值。
分布没有天然断层,不是"阈值有任意性"的借口,而是"模型选错了"的信号。22 个条目的解码率从 0.005 到 0.45 平滑分布——那是夹层结构在说:不存在一条线,能把它们切干净。

2. 判"是不是 X",不要用单一量压成一个是/否。这条链里,同一种动作反复出现了六次:

#单一量 → 是/否后果
1函数级解码率 → “这段是数据”22 个条目整段判错;41 条证据整批撤回,其中 3 个是误撤
2“清单里没有” → “判为数据”对照组从 26 崩到 2,当场折断
3整区间当成指令读 → 提取"体内证据"把字节流读成指令:既漏真串,又造假串
4强片段数 == 0 → “判为数据”66 个条目被判数据,含解码率 100% 的干净代码
5片段数 == 0 → “判为无代码”误伤一个靠指针恒等式命名的函数
6零片段 + 解码率 100% → “判为干净代码”7 个其实是纯桩

正确的形态是合取或多值:判"是不是桩"用两个信号的合取;表达"能读出来多少"用两个数值列,而不是一个布尔列。

3. 新判据必须用已知真值回归。66 这个荒谬值,是靠"拿 17 个独立命名的函数当真值"现形的;"片段法会漏 3 个"也是靠它发现的。"看起来合理"不是验收标准,"能通过已知样本"才是。

4. 阴性对照必须落在规则的作用域内。两次"恢复出 0 个"的对照,都因为区域里没有任何已知指令边界而根本起不了步——测到的是"规则没跑",不是"规则很干净"。对照区域必须落在被测规则的射程里,否则它只是一块更安静的 0。

5. 撤回与断言同罪。"整批撤回"和"整批断言"是同一种布尔化。撤掉 3 个真函数的那次,和给 22 个区间贴错标签的那次,是同一条链上的两个方向。撤回也要逐条复核。

6. 废弃的判据要上锁。被废掉的闸门被改成"调用即抛异常"——任何脚本想悄悄启用旧判据,都会当场炸掉。错误的判据必须失去"静默复活"的能力。

7. “找到过但判它是垃圾” ≠ “没找到”。三个状态:找到了干净证据 / 找到了但判为垃圾 / 什么都没找到。前两个在旧输出里长得一样,含义相反。

人加的检查:这条链里所有的"推翻",都是按规矩跑出来的检查干掉的;而这些规矩能被立出来,是因为流程里有一条底线——产出必须附带能被机器证伪的检查。人不需要懂夹层和片段,只需要在每一轮问一句:“这个结论,该被什么检查打脸?”——然后要求它把那个检查跑出来。

四、结论

五次被推翻的判词,一张总账:

被推翻的判词它出自哪一轮被什么推翻
“低解码率 = 不是代码”第 1 轮第 2 轮从一个灰区条目里捞出真逻辑;第 3 轮全量否证
"数据 or 代码"的二分第 1 轮夹层模型:桩 + 字节流 + 幸存指令
“整批撤回是对的”第 1 轮3 个条目被证明误撤、恢复
“恢复出来的片段 = 证据”第 3–4 轮假阳性率量化:23.8%;过滤后零真证据损失
“干净代码这一类存在”第 4 轮7/7 全是纯桩

终态:107 = 68 可挖 / 39 桩 / 0 纯数据;名字 43 个(高置信 25);39 个桩里 27 个靠非函数体证据命名。这一整条链是静态结论——"零运行时验证"是它在每一页里自己重复标注的限制。

三句话收束:

  1. AI 会把自己上一轮的产出当事实。所以链条上的每一步,都必须重新验证它的输入,而不只是它的输出。
  2. 分层的现实,会被压成分明的布尔。拦住这件事的不是"更聪明的提问",是"合取 + 多值 + 已知真值回归"这类能被执行的检查。
  3. 产能与自信是同一个东西的两面。六轮、几十个脚本、每次全量回归重跑——这是 AI 的产能;五次推翻、每次都格式完整——这是 AI 的自信。

五、边界声明

  • 本文只讨论AI 自动化逆向中的判据设计与审核方法,不涉及任何具体产品、厂商或服务的分析结论。
  • 文中所有地址、偏移、常量、字段名一律为占位符或示意值,
    不代表任何真实目标中的取值;示例代码为说明判据而编写,不构成可运行的针对特定目标的工具。
  • 文中提到的比例与数字均来自作者项目内的测量记录,仅用于说明方法失效的方式。
  • 请勿将本文内容用于未授权的用途。

六、收尾

AI 从一个"自己造的假前提"出发,用六轮把它拆干净——顺带把"怎么判断一段字节是不是代码"这件事,从手感变成了判据。

下一篇计划聊:我把一个逆向项目交给了 AI:它做到了什么程度。


转载请注明出处:
https://lizhiyong.blog.csdn.net/article/details/167813706

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询