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?