已经很久没有参加看雪 CTF 了. 主要原因还是能力有限: 想做攻击方却逆不出来题, 想做防守方又没有什么好点子, 还会被 riatre, ccfer 等诸多高手吊打, 可以说是十分惨烈了. 这一年补了补代数数论的课, 看了看密码学, 奈何脑子不好, 终究学了这个忘那个, 逆向能力早就被别人甩了好几个秒差距. 以前还能看到别人背影而兴叹, 如今连影子都看不到了.
好在看雪论坛里面学习资料不少, 把自己当萌新(划掉, 已经变成萌新了)在论坛里面找了些基础题目做做. 发现自己对于 OLLVM 之类的混淆还是非常头疼, 虽然这些年一直靠 bird 写的利用符号执行自动 patch 的脚本苟活, 但是如果把混淆稍微改改就又一脸懵逼了. 玩了玩 IDA 的 microcode 只感觉一知半解, 试了试 binary ninja 对于太大的函数又难以分析, 每次做题都像是在"启发式逆向", 闭着眼睛逆一点是一点, 颇感力不从心.
所以本着 "打不过它就加入它" 的思路, 想来看雪 CTF 里面出个 OLLVM 的题. 但是 OLLVM 毕竟已是将近 7 年的老技术了, 拿过来出题未免有点贻笑大方. 而且正好看到去年有一道 "叹息之墙" 似乎就是 OLLVM 的混淆, 结果被各路高手轮番攻破, 令在下胆战心惊. 可能正如前人所言, 要想在看雪 CTF 里面存活, 最好的办法就是把这些大佬们拉到一起聚个餐, 觥筹交错, 大醉三日而还.
但后来转念一想, 虽然 OLLVM 拦不住这些逆向机器, 但是这个在编译阶段混淆的思路还是挺不错的. 于是花了点时间研究了一下 LLVM, 实现了几个新的 Obfuscation Pass, 又写了一个 CrackMe, 用这些 Pass 混淆过后, 来参加今年看雪的春季赛, 且看究竟能撑过几天.
题目通过看雪题库提交, 请管理员查收.
之前一直想做一个这样的 CrackMe:
这一次我在看雪题库提交的题目, 除了最后一点不敢保证, 但是前两点均完全满足. 之前虽然也想这样做, 但是因为题目设计思路和题目 flag 都在一个帖子内, 无法在比赛期间公开设计帖. 这次提交题目的通道是看雪题库, 故而可以和本帖分离. 所以希望管理员能在攻击方比赛开始的时候, 把本帖公开. 让大家可以提前了解题目中应用的混淆方法.
执行程序如同跋山涉水, 行路时阡陌纵横, 每一次分支都是一次歧路. 古人曾感慨 "行路难, 行路难. 多歧路, 今安在". 这一个程序所采用的混淆, 恰恰就是增加了许多歧路, 彻底解决 "行路难" 的问题 23333. 但是歧路多了, 走着走着可能就跟丢了, 正巧这次 CTF 赛题皆以十二生肖为名. 那我这个程序, 不如就叫 "歧路亡羊" 吧.
YANSOllvm 是在下这几天编写的一个类 OLLVM 项目, 它的全称是 Yet Another Not So Obfuscated LLVM, 源代码已公开到 Github 上. 记得在 2018 年看雪论坛上似乎发生过有人抄袭 Hikari Obfuscator 的争论, 当时觉得 OLLVM 的代码一定很高大上, 自己写起来多半困难重重. 这几天粗略上手试了一下, 感觉如果不考虑用户体验和各种实际代码的情况, 简单写写自己玩好像还挺容易的. 再后来我又发现, 除了 CFG Flattening, LLVM 上能做的变换实在是太多了, 于是我又自己实现了几个新的混淆 Pass, 抛砖引玉, 供大家开阔思路.
原则上来讲, OLLVM 的代码树完全可以不用包含 LLVM 和 Clang 的部分, 可是为什么 YANSOllvm 的编译需要 LLVM 呢? 一个原因是 YANSOllvm 实现了对 Calling Convention 的混淆, 而 Calling Convention 是 Backend 的约定, 所以需要对 LLVM backend 进行修改. 另外一个原因是 LLVM 有很多代码变换的 Utils, 这些 Utils 稍作修改就是一种不错的代码混淆.
编译的方法在 Github 上已经很详细了, 这里主要说一下本题中所使用的命令行参数:
我们以如下源代码为例, 后面加上每一个 pass 的效果图:
VM Pass 非常简单, 就是将 LLVM IR 中的一些简单二元操作替换成函数调用. 比如将 a = b + c
替换成 a = Add(b, c)
. 上述代码混淆后的效果图:
Merge 的主要作用是将所有 internal linkage 的函数 (比如 static 函数, 和 VM Pass 添加的 vm 函数等) 合并成一个新的函数. 这样做的性能开销并不大, 但是会形成一个大型函数从而可以进一步混淆, 并且大幅拖慢反编译器的速度. 下图中把 abcd 四个函数合并成了同一个函数:
这个 Pass 的作用正好和 Merge 相反, 它可以从一个函数中把较大的 Basic Block 切分, 并且强行提取出来作为新的函数. 因为一般而言, 函数是某个独立的功能单位, 而这样随机抽取的函数往往只是一个功能单位的某一部分, 从而干扰逆向者与反编译器. 并且配合文末介绍的 ObfCall Pass 可以进一步干扰 IDA 的分析.
这个 Flattening 是在 OLLVM 原有的 flattening pass 的基础上进行的修改. OLLVM 的 CFG 平坦化存在一个明显问题: 每一个 basic block 都直接暴露了其后继的 basic block. 抽象来看, Basic Block 都对应着一个 internal state, 而 Switch Dispatcher 的分发则依赖 switchVar. 在 OLLVM 的平坦化算法中, internal state 就等同于 switchVar, 并且 internal state 的转换则通过直接赋值而进行, 这样做会使得静态分析变得尤其容易.
YANSOllvm 将 internal state 和 switchVar 分别看待. switchVar 由 internal state 的 hash 计算得出, 而 internal state 之间的转移则通过 xor 来完成. 这样就能够避免攻击者直接通过静态分析从一个 basic block 获取到其后继的所有 basic block.
这个 Pass 的作用很简单, 利用复杂的恒等式 (如 MBA) 将 IR 中的常量 0 替换成几个变量的运算结果. 从而干扰分析器, 配合 Flattening 和 Connect Pass 可以产生很多虚假分支.
可以看到图中将原有的 if(x == 0)
中的 0 混淆成关于 x 的表达式
Connect 将 Basic Block 切开, 然后通过 switch 来链接他们. 正确的分支自然只有一个, 其他的虚假分支通过 obfZero 来混淆. 并且会在其中一个虚假分支里插入一些简陋的花指令来干扰反汇编:
注意到 a 并没有被成功识别为函数:
patch 掉几个干扰分析的花指令后, CFG 比原来复杂很多, 但是大部分分支是无用的:
在编译时, genObfCC.py
会随机生成 10 个 Calling Convention, 他们的返回寄存器和传参寄存器均不为 AMD64 ABI 中对应的常用寄存器, 从而混淆反汇编器的分析.
写了一大篇帖子好累 orz. YANSOllvm 只是一个兴趣之作, 甚至可以说仅为此次 CTF 而生, 仅供学习参考. 很多情况并没有考虑到, 代码质量极差, 也有许多 bug, 希望大家多多谅解. 最后, 祝大家看雪 CTF 玩得愉快~~
比赛结束后的更新. 先看题目源代码:
本题主要是 YANSOllvm 的应用, 题目本身非常简单. 从设计思路上来讲, 总结了前人以 OLLVM 出题而失败的经验: 只混淆了控制流, 而没有混淆数据流. 攻击者只需要几个内存访问断点, 即可定位核心逻辑.
故而, 本题设计上, 将数据流转化为控制流, 而控制流本身即被 YANSOllvm 所混淆. 比如核心代码里的一堆 if 语句通过判断输入的某个 bit 来进行运算, 以及根据 bit 的值运行不同快慢的函数, 通过 rdtsc 来窃取数据的方法 (即所谓 implicit data flow).
并且, 为了进一步防止攻击者找到正确代码, 题目里的 main 函数通过调用一个修改过的 scanf("%16s", s) 来迷惑攻击者, 使得攻击者以为 flag 仅有 16 字节. 接下来校验的代码中, 甚至会比对第 16 个字节是否为 '}', 进一步迷惑攻击者, 使其得到错误的长度信息. 而实际上, 对于错误长度的输入, 是通过一个安全的 hash 算法 (改编自 blake2b) 来验证的, 故而不可能逆向得出合法 flag.
但是, 在修改过的 scanf 中, 程序会将 stdin 的值放到一个全局变量中. check 函数会根据 stdin 的内部结构 (取决于 ucrt), 直接读取 stdin buffer 中的数据. 从而不通过 scanf/getchar 等函数, "窃取"到用户的输入, 判断长度正确后再进入真正验证流程. 真正的验证流程是一个 GF(2) 下的矩阵向量乘, 一个改版的 Chacha, 和一个简单的置换. 均可逆而无需爆破.
本题最为有趣之处在于, 因为难以有效地逆向控制流, 很多攻击者可能到比赛结束才会发现, 自己连 flag 长度都没找对.
首先, 感谢 ccfer 和 pizza 两位大佬愿意赏光做这道题目. 大佬们速度太恐怖了, 如果不是里面有一个大坑, 可能第一天就被干死了. (一人血书求 writeup)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-6-20 14:18
被半盲道人编辑
,原因: