引子
十一假期期间,打完第13届Flare-on,虽然拿到了Prize Token,但还是怅然若失。AI的滥用,让很多东西失去了意义。而且Google的逆向题更偏实战,格调上反而逊色于看雪。不过很遗憾,看雪杯赛程太长,实在是没有时间正经参与。
所以想着自己设计一道CrackMe,回应一下那些喊着“逆向已死”的朋友们,世界上很多事不是为了有用而去做的,甚至不是为了未来有用。
很多年以前,Stephen Dolan 发表过一篇论文:“mov是图灵完备的”。大致意思是,世界上只要有mov这一种指令,就能实现任何运算。后来git上有人在x86上完成了生产级实现(M/o/VFuscator),作为一种保护壳,可以把任意程序编译成只剩下mov指令。

事实上,Dolan的论文和M/o/VFuscator项目,都没有真正意义上做到“单指令mov”。
延续这条思路,我做了一些无谓的探索和尝试,有了这道题最小PoC题:“call是图灵完备的吗?”。(见附件)

图灵完备的书面定义是:“一个能够模拟任意图灵机的计算系统”。而图灵机本身能完成什么,又是一个大而抽象的概念。这种左脚踩右脚的定义方式,在计算机领域还挺常见,比如:“NP-hard问题,是指与NP问题中最难的那类一样难的问题”。这里我们不做过多纠缠。
一般情况下认为,具备以下三种能力的计算系统,就算图灵完备了:
而这里真正的问题是:
(本段跳过不影响文章理解)
抛开图灵完备来说,图灵机本身的定义,会具象很多。
图灵机可以表达为:
其中:
注:
我没有查到Q这个符号的辞源,猜测此处是借用了有理数集合的符号。
有理数是所有能转化为两个整数相除的数,那么符号Q大概率就来自于商的英语Quotient。
其中:

在实现运算上,可以使用查表来代替运算。
比如要实现以下代码:
可以在内存中预存一张乘法口诀表:
当计算3*4时,直接从对应行列取数即可:
(注:这里便于理解,用mul[3][4]表示,实际语法遵循x86寻址表达式)
解决了运算的问题,接下来就是控制流
在不允许操作ip的情况下,影响控制流的方法,几乎完全等价于PWN的常用思路
M/o/Vfuscator项目的实现,选择了以下方式:
类似于一种CFF的编排,而非真正意义上的VMP。

严格来说,这还是属于借助了外力。利用了设备自身的缺陷,数学洁癖上是难以接受的。
Dolan的论文中先否定了SMC形式。他认为这种方式可以往未来的text段中写入任意指令,属于作弊行为。

比如:
实际上我觉也不尽然,如果我们只允许往未来的text段中写入mov指令,把命中当前逻辑分支的代码,搬运覆写到接下来要执行到的行。这种递归给自己铺路的方式,也有一定探讨价值。
类似这种操作:

Dolan最终采用了VMP的方案,在text段使用mov作为解释器,把虚拟指令放在内存中。
不过可惜的是:他解释器的最后一行代码中必须保留一条jmp指令,真是纯学术流氓行为。

有了前人的两个失败先例,我依然有冲动想去打造这台永动机。
虽然能隐约感觉到,单指令-完备-不借助外力,这个不可能三角难以兼得。
我们从图灵完备的定义出发,需要回答一个问题:是否存在一种指令,可以同时满足以下两点?
我枚举了下x86所有指令,筛选出一些满足条件的指令:
显然 call 是最特殊的。
在x86中,call是一条伪指令。为了创建新的调用栈帧,它会原子操作以下步骤:
一行 call 指令大致可以等价于:
这看起来简直就是为单指令图灵完备而生的 !!
另外,恰好PWN常用的ROP链相反,连续call会不停在stack中留下轨迹,同时影响栈顶指针。ROP能被广泛利用,同理call在我们的母题下也应该可以发挥作用。
有了一个朦胧的方向之后,就开始构思如何落地。
由于物理指令受限,能走的路几乎只有借用 VMP 的机制,做到更高语义上的功能拓展。
call的一个要命的问题在于,程序无法线性执行。当前行的call执行完之后,要被迫路由走,这导致预编排变得极为困难。
这里想到的机制,依然是沿用类似ROP链的思路:
内存结构大致是这样:

这样还有个好处,就是 ESP+固定offset 未来必然踩到自己历史上 call 时 push 进去的地址。如此在虚拟指令层面,也能形成永久的循环
可以通过 ESP 的低位 SP 来控制奇偶循环触发的不同链路,达到逻辑分支的效果。
还是大 switch 形式,先在程序段中写好主体,每个handler中预留好 call $+5 占位。(单指令模式下,还是避免用NOP了)

几番尝试后,实现了一个最小 PoC 的 CrackMe。当然,这个简陋的系统,距离图灵完备相去甚远,只能博君一笑。
程序我写成了python代码,便于大家脱机调试。
(题面见附件)

从我个人的观感上来说,现阶段的 AI还不具备逆向研究真正稀缺的能力:
尤其是2和3,AI做得很差。
AI远没有超出的不是人类智慧的总和。尤其是Agent的广泛应用,使得以 抄袭存量思想 + 蛮力尝试 为主的AI,显得异常惊艳。然而它只是人类存量思想的投影,在更多的创新、体系化的任务上,AI表现得极为笨拙。
比如:
打个不恰当的比方:AI或许能比肩陈景润1+2的高度(即:把已知的筛法技巧应用到极致),但远达不到伽罗瓦创建群论的高度。
我们真正该反思的是:人类安全研究员是否一直在自我抄袭?所以当AI具备存量机制的泛化能力后,我们才会显得那么不堪一击?
M = (Q,q₀,Σ,σ,δ)
δ(q,a) = (q',b,D)
a = 3
b = 4
c = a * b
× | 2 3 4 5 6 7 8 9
2 | 4 6 8 10 12 14 16 18
3 | 6 9 12 15 18 21 24 27
4 | 8 12 16 20 24 28 32 36
5 | 10 15 20 25 30 35 40 45
6 | 12 18 24 30 36 42 48 54
7 | 14 21 28 35 42 49 56 63
8 | 16 24 32 40 48 56 64 72
9 | 18 27 36 45 54 63 72 81
mov r1, 3
mov r2, 4
mov r3, mul[3][4]
mov next_ea, 'add eax, ebx'