6.2.4 re CSAWCTF2015 wyvern

下载文件

题目解析

$ file wyvern
wyvern: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=45f9b5b50d013fe43405dc5c7fe651c91a7a7ee8, not stripped
$ ./wyvern
+-----------------------+
|    Welcome Hero       |
+-----------------------+

[!] Quest: there is a dragon prowling the domain.
        brute strength and magic is our only hope. Test your skill.

Enter the dragon's secret: AAAAAAAA

[-] You have failed. The dragon's power, speed and intelligence was greater.

看起来是 C++ 写的:

而且不知道是什么操作,从汇编来看程序特别地难理解,我们耐住性子仔细看,在 main 函数里找到了验证输入的函数:

于是我们知道,如果函数 sym.start_quest_std::string_ 返回 0x1337,说明验证成功了。来 patch 一下试试:

果然如此。

然后在验证函数中,又发现了对输入字符长度的验证过程:

它将一个数读入 r9d 中,做 0x73 >> 2 = 28 的操作,然后与输入字符串比较,所以我们猜测输入字符长度应为 28。

由于有下面这段指令,它将字符放到 obj.hero 处的 vector 中,我们有理由认为,验证是一个字符一个字符进行的,而且长度就是 28:

找到这些加密字符:

继续往下看,发现函数:

就是它决定了返回值,如果输入字符串正确,则该函数返回 0x1337

接下来就是跟踪各种交叉引用,从 obj.hero 里依次取值:

继续查找 local_d8h

查找 local_e0h

查找 local_e8h

继续跟踪 local_80,你会发现输入的字符放在 0x6236a8 的位置。

继续往下看,终于看到了曙光,下面这个函数对输入字符做一些变换:

进入该函数,找到字符转换的核心算法:

例如第二个字符是 r,即 0x72 + 0x64 = 0xd6,第三个字符 4,即 0x34 + 0xd6 = 0x10a,依次类推。由此可以写出解密算法:

Bingo!!!

常规方法逆向出来了,但实在是太复杂,我们可以使用一些取巧的方法,想想前面讲过的 Pin 和 angr,下面我们就分别用这两种工具来解决它。

使用 Pin

首先要知道验证是逐字符的,一旦有不相同就会退出,也就是说执行下面语句的次数减一就是正确字符的个数:

另外只有验证成功,才会跳转到地址 0x0040e2af,所以把 6.2.1 节的 pintool 拿来改成下面这样,当 count 为 28+1=29 时,验证成功:

编译 pintool:

执行下看看:

看起来不错,写个脚本自动化该过程:

使用 angr

参考资料

Last updated

Was this helpful?