mov
这个程序拿来初看可能比较乏味,但也很有趣。马后炮地说,多用一点脚本就会轻松很多,因为在调试时做了很多繁琐的工作。
运行程序
我们得到一个名为 mov
的二进制文件。容易看出,这个二进制文件是用名为 "movfuscator"(https://github.com/xoreaxeaxeax/movfuscator)的混淆器编译的,(因为笔者之前看到过 movfuscator)。
$ ./mov
hello
hello
Nope sorry :(
$ echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" | ./mov
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp��
Nope sorry :(
看来,我们的输入被放在一个长度为32的缓冲区里。在开始逆向之前,先对它进行解密。
去混淆
Google 了一下,发现了反混淆器:Demovfuscator (https://github.com/kirschju/demovfuscator).
Usage: demov [-o patched] [-g graph] [-i idc]
Demovfuscator 不会移除所有的 mov 操作!
它只能做到以下几点:
- 处理加固的二进制文件
- 重构函数和它们的控制流图
- 为 IDA 生成符号
- 产生一个经过修补的二进制文件
- 进行部分指令的重新替换
-> 方便逆向
静态调试
在IDA中观察了一段时间的二进制文件后,我们注意到有一堆
test eax, eax
指令之后是
jne 0x0804XXXX
分析控制流
生成一张控制流图
我们看到这里,可以怀疑二进制每次都是对输入的一个或几个字符进行比较。
Battle plan
如果我们一次发送一个唯一的字符的话,就能看到字符被 check 的顺序。构造输入:
ABCDEFGHIJKLMNOPQRSTUVWXYZ012345
我们创建了一个gdb init文件,在.png中提到的每一个ADDRESS(共42个)设置断点,然后运行程序。
调试
我们注意到在指令 0x804be37
后不久,字符开始被引用。看一下我们的流图,这看起来像是一个循环。
第一个被引用的字符是'G'。同时,'_'也被引用。这两个字符被多次放在eax
和edx
中。再往下,和前面一样,我们遇到了一个test eax, eax
,然后是一个jne
/jnz
。正是这个跳转导致了Nope sorry :(
被打印出来,程序中途终止。
所以我们在'G'和'_'都在寄存器内的地方打一个断点,相应地修改我们的输入字符串并继续。最终获得字符串。
ABCDEFGHIJKLMNOPQRSTUVWXYZ012345
->
ABCDEF_HIJKL_NOPQ_STU_WX_Z012345
看着像 flag 了吧。
在循环之后继续,我们看到的是看起来更像if语句而不是循环条件。换句话说,这个过程在一个基本块中比较一个或多个字符,然后继续到程序的下一个部分。
当程序开始引用两个以上的字符串时,会难一些。在0x080513e7
处,程序似乎要检查我们的第9个字符(I
)减去8
后是否等于j
。这意味着我们的字符实际上应该是 r
。
解决
按照上面的解释,步入程序的过程相当顺利。有些条件我们无法满足(这就是为什么我们应该编写解决方案的脚本!),所以我们最后手动引导程序向正确的方向发展(set $eip=0xcorrect
),以避免程序终止。
最后东拼西凑:
MBkeys_breKt_tObt_bad_:)_!012345
可见其原本是
Mykeys_arent_that_bad_:)_!
然后包上flag提交!
Flag: flag{Mykeys_arent_that_bad_:)_!}