1. 从一道“简单”的CTF题说起:ez_byte的初印象
大家好,我是老张,一个在逆向工程和二进制安全领域摸爬滚打了十多年的老家伙。今天想和大家聊聊去年CISCN国赛初赛里一道挺有意思的逆向题,叫 ez_byte。光看名字,你可能会觉得它“简单”,毕竟“ez”嘛。但真正上手之后,你会发现这道题的设计非常精妙,它没有用传统的花指令或者复杂的加密算法来为难你,而是把核心逻辑藏到了一个绝大多数逆向新手都会忽略的地方——C++的异常处理机制和DWARF调试信息里。我第一次看到这题时,也愣了一下,心想:“这玩意儿还能这么玩?” 这就像你满屋子找钥匙,结果发现钥匙一直挂在门把手上一样,思路一打开,豁然开朗。
这道题的目标很明确,就是让你输入一个flag,程序会进行校验。用IDA Pro打开,主函数main看起来平平无奇,就是简单的输入输出。如果你只盯着代码流看,很容易就卡住了。我当时也在这里转了几圈,直到把视线移到了数据区。我发现了一个字符串“yes”,但它并没有在程序的正常执行流中被引用。通过交叉引用(按X键),我发现它前面是一个jmp指令,这意味着在正常情况下,程序永远不会执行到“yes”这块代码。这立刻引起了我的警觉:一定有某种“异常”的路径能跳到这里。这道题的核心,就是找到并理解这条隐藏的路径。它完美地模拟了现实世界中,恶意软件或保护壳利用合法但生僻的运行时机制来隐藏行为的场景,非常具有教学和实战价值。
2. 拨开迷雾:发现隐藏的异常处理路径
当我们发现那个永远执行不到的jmp和“yes”字符串时,逆向的直觉告诉我们,这里一定有鬼。常规思路是尝试patch掉这个jmp,比如把它改成条件跳转jnz,看看后面会发生什么。我实际操作了一下,修改后重新调试,程序流程确实发生了变化,但很快我发现了一个更关键的问题:在判断是否跳向“yes”的汇编代码附近,有一个检查r12寄存器是否为0的判断,但翻遍上下文的代码,根本没有对r12进行赋值或修改的指令。
这太反常了。寄存器里的值不会凭空产生。这说明,对r12(以及其他寄存器)的操作,被某种方式“隐藏”了,没有直接体现在我们看到的反汇编指令流里。这时候,我们需要扩大搜索范围。我习惯性地在IDA里搜索所有函数,一个名为sub_404BF5的函数引起了我的注意。它的代码非常简短:
void __noreturn sub_404BF5() {
_DWORD *exception; // rax
exception = (_DWORD *)_cxa_allocate_exception(4LL);
*exception = 1;
_cxa_throw(exceptio


2091

被折叠的 条评论
为什么被折叠?



