mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-24 19:21:15 +07:00
335 lines
16 KiB
Markdown
335 lines
16 KiB
Markdown
# 6.2.4 re CSAWCTF2015 wyvern
|
||
|
||
- [题目解析](#题目解析)
|
||
- [参考资料](#参考资料)
|
||
|
||
|
||
[下载文件](../src/writeup/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++ 写的:
|
||
```
|
||
[0x004013bb]> iI~lang
|
||
lang cxx
|
||
```
|
||
而且不知道是什么操作,从汇编来看程序特别地难理解,我们耐住性子仔细看,在 `main` 函数里找到了验证输入的函数:
|
||
```
|
||
[0x004013bb]> pdf @ main
|
||
...
|
||
| 0x0040e261 e8ea60ffff call sym.start_quest_std::string_ ; 验证函数
|
||
| 0x0040e266 898564feffff mov dword [local_19ch], eax
|
||
| ,=< 0x0040e26c e900000000 jmp 0x40e271
|
||
| | ; JMP XREF from 0x0040e26c (main)
|
||
| `-> 0x0040e271 8b8564feffff mov eax, dword [local_19ch]
|
||
| 0x0040e277 2d37130000 sub eax, 0x1337 ; 返回值 eax = eax -0x1337
|
||
| 0x0040e27c 0f94c1 sete cl ; 如果 eax 为零,则设置 cl = 1
|
||
| 0x0040e27f 488dbdc8feff. lea rdi, [local_138h]
|
||
| 0x0040e286 898560feffff mov dword [local_1a0h], eax
|
||
| 0x0040e28c 888d5ffeffff mov byte [local_1a1h], cl ; [local_1a1h] = cl
|
||
| 0x0040e292 e8e92cffff call method.std::basic_string<char,std::char_traits<char>,std::allocator<char>>.~basic_string()
|
||
| ,=< 0x0040e297 e900000000 jmp 0x40e29c
|
||
| | ; JMP XREF from 0x0040e297 (main)
|
||
| `-> 0x0040e29c 8a855ffeffff mov al, byte [local_1a1h] ; al = [local_1a1h]
|
||
| 0x0040e2a2 a801 test al, 1 ; 1 ; al & 1,即检查 al 是否为 0
|
||
| ,=< 0x0040e2a4 0f8505000000 jne 0x40e2af ; 如果 al != 0,跳转,成功
|
||
| ,==< 0x0040e2aa e9bd000000 jmp 0x40e36c ; 否则,失败
|
||
...
|
||
```
|
||
于是我们知道,如果函数 `sym.start_quest_std::string_` 返回 `0x1337`,说明验证成功了。来 patch 一下试试:
|
||
```
|
||
[0x004013bb]> s 0x40e271
|
||
[0x0040e271]> pd 2
|
||
| ; JMP XREF from 0x0040e26c (main)
|
||
| 0x0040e271 8b8564feffff mov eax, dword [local_19ch]
|
||
| 0x0040e277 2d37130000 sub eax, 0x1337
|
||
[0x0040e271]> wa mov eax, 0x1337
|
||
Written 5 bytes (mov eax, 0x1337) = wx b837130000
|
||
[0x0040e271]> pd 2
|
||
| ; JMP XREF from 0x0040e26c (main)
|
||
| 0x0040e271 b837130000 mov eax, 0x1337
|
||
| 0x0040e276 ff2d37130000 ljmp [0x0040f5b3] ; [0x40f5b3:8]=0xe4100000000a5ff
|
||
[0x0040e271]> s 0x0040e276
|
||
[0x0040e276]> wx 90
|
||
[0x0040e276]> pd 2
|
||
| 0x0040e276 90 nop
|
||
| 0x0040e277 2d37130000 sub eax, 0x1337
|
||
```
|
||
```
|
||
$ ./wyvern_patch
|
||
+-----------------------+
|
||
| 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: hello world
|
||
|
||
[+] A great success! Here is a flag{hello world}
|
||
```
|
||
果然如此。
|
||
|
||
然后在验证函数中,又发现了对输入字符长度的验证过程:
|
||
```
|
||
[0x004013bb]> pdf @ sym.start_quest_std::string_
|
||
...
|
||
| :| 0x0040469c e8afc8ffff call method.std::string.length()const ; 返回值 rax,是输入字符串长度 +1,因为字符末尾的 `\x00'
|
||
| :| 0x004046a1 482d01000000 sub rax, 1 ; rax = rax - 1
|
||
| :| 0x004046a7 448b0c253801. mov r9d, dword str.sd______________ ; obj.legend ; [0x610138:4]=115 ; U"sd\xd6\u010a\u0171\u01a1\u020f\u026e\u02dd\u034f\u03ae\u041e\u0452\u04c6\u0538\u05a1\u0604\u0635\u0696\u0704\u0763\u07cc\u0840\u0875\u08d4\u0920\u096c\u09c2\u0a0f" ; r9d = 115
|
||
| :| 0x004046af 41c1f902 sar r9d, 2 ; 115 >> 2 = 28
|
||
| :| 0x004046b3 4963c9 movsxd rcx, r9d ; rcx = 28
|
||
| :| 0x004046b6 4839c8 cmp rax, rcx ; 比较 rax 和 rcx
|
||
...
|
||
[0x004013bb]> px 1 @ 0x610138
|
||
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
||
0x00610138 73
|
||
```
|
||
它将一个数读入 r9d 中,做 `0x73 >> 2 = 28` 的操作,然后与输入字符串比较,所以我们猜测输入字符长度应为 28。
|
||
|
||
由于有下面这段指令,它将字符放到 `obj.hero` 处的 `vector` 中,我们有理由认为,验证是一个字符一个字符进行的,而且长度就是 28:
|
||
```
|
||
| :||`-``-> 0x00404c13 48bff8026100. movabs rdi, obj.hero ; 0x6102f8
|
||
| :|| : 0x00404c1d 48be3c016100. movabs rsi, obj.secret_100 ; 0x61013c ; U"d\xd6\u010a\u0171\u01a1\u020f\u026e\u02dd\u034f\u03ae\u041e\u0452\u04c6\u0538\u05a1\u0604\u0635\u0696\u0704\u0763\u07cc\u0840\u0875\u08d4\u0920\u096c\u09c2\u0a0f"
|
||
| :|| : 0x00404c27 e8240b0000 call method.std::vector<int,std::allocator<int>>.push_back(intconst&)
|
||
... ; 中间省略 26 段
|
||
| :|| : 0x00404eb6 48bff8026100. movabs rdi, obj.hero ; 0x6102f8
|
||
| :|| : 0x00404ec0 48bea8016100. movabs rsi, obj.secret_2575 ; 0x6101a8
|
||
| :|| : 0x00404eca e881080000 call method.std::vector<int,std::allocator<int>>.push_back(intconst&)
|
||
```
|
||
找到这些加密字符:
|
||
```
|
||
[0x004013bb]> px 28*4 @ 0x61013c
|
||
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
||
0x0061013c 6400 0000 d600 0000 0a01 0000 7101 0000 d...........q...
|
||
0x0061014c a101 0000 0f02 0000 6e02 0000 dd02 0000 ........n.......
|
||
0x0061015c 4f03 0000 ae03 0000 1e04 0000 5204 0000 O...........R...
|
||
0x0061016c c604 0000 3805 0000 a105 0000 0406 0000 ....8...........
|
||
0x0061017c 3506 0000 9606 0000 0407 0000 6307 0000 5...........c...
|
||
0x0061018c cc07 0000 4008 0000 7508 0000 d408 0000 ....@...u.......
|
||
0x0061019c 2009 0000 6c09 0000 c209 0000 0f0a 0000 ...l...........
|
||
```
|
||
|
||
继续往下看,发现函数:
|
||
```
|
||
| :|||:| 0x0040484f e86cd4ffff call sym.sanitize_input_std::string_
|
||
```
|
||
就是它决定了返回值,如果输入字符串正确,则该函数返回 `0x1337`。
|
||
|
||
接下来就是跟踪各种交叉引用,从 `obj.hero` 里依次取值:
|
||
```
|
||
[0x0040484f]> pdf @ sym.sanitize_input_std::string_ ~obj.hero
|
||
| --------> 0x00402726 b8f8026100 mov eax, obj.hero ; 0x6102f8
|
||
| --------> 0x00402d37 b8f8026100 mov eax, obj.hero ; 0x6102f8
|
||
[0x0040484f]> pd 5 @ 0x00402726
|
||
| ; JMP XREF from 0x0040271b (sym.sanitize_input_std::string_)
|
||
| 0x00402726 b8f8026100 mov eax, obj.hero ; 0x6102f8
|
||
| 0x0040272b 89c7 mov edi, eax
|
||
| 0x0040272d 488bb530ffff. mov rsi, qword [local_d0h]
|
||
| 0x00402734 e8072f0000 call method.std::vector<int,std::allocator<int>>.operator[](unsignedlong)
|
||
| 0x00402739 48898528ffff. mov qword [local_d8h], rax ; 将取出的值存入 [local_d8h]
|
||
[0x0040484f]> pd 5 @ 0x00402d37
|
||
| ; JMP XREF from 0x00402d2c (sym.sanitize_input_std::string_)
|
||
| 0x00402d37 b8f8026100 mov eax, obj.hero ; 0x6102f8
|
||
| 0x00402d3c 89c7 mov edi, eax
|
||
| 0x00402d3e 488bb508ffff. mov rsi, qword [local_f8h]
|
||
| 0x00402d45 e8f6280000 call method.std::vector<int,std::allocator<int>>.operator[](unsignedlong)
|
||
| 0x00402d4a 48898500ffff. mov qword [local_100h], rax
|
||
```
|
||
继续查找 `local_d8h`:
|
||
```
|
||
[0x0040484f]> pdf @ sym.sanitize_input_std::string_ ~local_d8h
|
||
| ; var int local_d8h @ rbp-0xd8
|
||
| |:||:|: 0x00402739 48898528ffff. mov qword [local_d8h], rax
|
||
| --------> 0x00402819 488b8528ffff. mov rax, qword [local_d8h]
|
||
[0x0040484f]> pd 15 @ 0x00402819
|
||
| ; JMP XREF from 0x00403ea9 (sym.sanitize_input_std::string_)
|
||
| ; JMP XREF from 0x0040280e (sym.sanitize_input_std::string_)
|
||
| 0x00402819 488b8528ffff. mov rax, qword [local_d8h] ; 将 [local_d8h] 的值存入 rax
|
||
| 0x00402820 8b08 mov ecx, dword [rax] ; 将 [rax] 存入 ecx
|
||
| 0x00402822 8b1425940561. mov edx, dword [obj.x17] ; [0x610594:4]=0
|
||
| 0x00402829 8b3425340461. mov esi, dword [obj.y18] ; [0x610434:4]=0
|
||
| 0x00402830 89d7 mov edi, edx
|
||
| 0x00402832 81ef01000000 sub edi, 1
|
||
| 0x00402838 0fafd7 imul edx, edi
|
||
| 0x0040283b 81e201000000 and edx, 1
|
||
| 0x00402841 81fa00000000 cmp edx, 0
|
||
| 0x00402847 410f94c0 sete r8b
|
||
| 0x0040284b 81fe0a000000 cmp esi, 0xa ; 10
|
||
| 0x00402851 410f9cc1 setl r9b
|
||
| 0x00402855 4508c8 or r8b, r9b
|
||
| 0x00402858 41f6c001 test r8b, 1 ; 1
|
||
| 0x0040285c 898d20ffffff mov dword [local_e0h], ecx ; 将 ecx 存入 [local_e0h]
|
||
```
|
||
查找 `local_e0h`:
|
||
```
|
||
[0x0040484f]> pdf @ sym.sanitize_input_std::string_ ~local_e0h
|
||
| ; var int local_e0h @ rbp-0xe0
|
||
| |:||:|: 0x0040285c 898d20ffffff mov dword [local_e0h], ecx
|
||
| --------> 0x00402a73 8b8520ffffff mov eax, dword [local_e0h]
|
||
[0x0040484f]> pd 4 @ 0x00402a73
|
||
| ; JMP XREF from 0x00403f39 (sym.sanitize_input_std::string_)
|
||
| ; JMP XREF from 0x00402a68 (sym.sanitize_input_std::string_)
|
||
| 0x00402a73 8b8520ffffff mov eax, dword [local_e0h] ; 将 [local_e0h] 的值存入 eax,即 eax 是加密字符
|
||
| 0x00402a79 8b8d18ffffff mov ecx, dword [local_e8h] ; ecx 是经过处理的输入字符
|
||
| 0x00402a7f 39c8 cmp eax, ecx ; 进行比较。逐字符比较,不相等时退出。
|
||
| 0x00402a81 0f94c2 sete dl
|
||
```
|
||
查找 `local_e8h`:
|
||
```
|
||
[0x0040484f]> pdf @ sym.sanitize_input_std::string_ ~local_e8h
|
||
| ; var int local_e8h @ rbp-0xe8
|
||
| |:||:|: 0x00402a25 898518ffffff mov dword [local_e8h], eax
|
||
| |:||:|: 0x00402a79 8b8d18ffffff mov ecx, dword [local_e8h]
|
||
[0x0040484f]> pd -2 @ 0x00402a25
|
||
| ; JMP XREF from 0x00402a11 (sym.sanitize_input_std::string_)
|
||
| 0x00402a1c 488b7d80 mov rdi, qword [local_80h]
|
||
| 0x00402a20 e88beaffff call sym.transform_input_std::vector_int_std::allocator_int___
|
||
[0x0040484f]> pdf @ sym.sanitize_input_std::string_ ~local_80h
|
||
| ; var int local_80h @ rbp-0x80
|
||
| :||:| 0x00401e23 4c895580 mov qword [local_80h], r10
|
||
| --------> 0x0040286d 488b7d80 mov rdi, qword [local_80h]
|
||
| --------> 0x00402a1c 488b7d80 mov rdi, qword [local_80h]
|
||
| --------> 0x00402b58 488b7d80 mov rdi, qword [local_80h]
|
||
```
|
||
继续跟踪 `local_80`,你会发现输入的字符放在 `0x6236a8` 的位置。
|
||
|
||
继续往下看,终于看到了曙光,下面这个函数对输入字符做一些变换:
|
||
```
|
||
| 0x00402a20 e88beaffff call sym.transform_input_std::vector_int_std::allocator_int___
|
||
```
|
||
进入该函数,找到字符转换的核心算法:
|
||
```
|
||
| |:||:|: 0x004017dd e85e3e0000 call method.std::vector<int,std::allocator<int>>.operator[](unsignedlong) ; 获得一个输入字符的地址 rax
|
||
| |:||:|: 0x004017e2 8b08 mov ecx, dword [rax] ; 将该字符赋值给 ecx
|
||
| |:||:|: 0x004017e4 488b45e0 mov rax, qword [local_20h] ; 获得上一个加密字符的地址 rax
|
||
| |:||:|: 0x004017e8 0308 add ecx, dword [rax] ; 上一个加密字符加上当前输入字符
|
||
| |:||:|: 0x004017ea 8908 mov dword [rax], ecx ; 将当前加密字符放回
|
||
```
|
||
例如第二个字符是 `r`,即 `0x72 + 0x64 = 0xd6`,第三个字符 `4`,即 `0x34 + 0xd6 = 0x10a`,依次类推。由此可以写出解密算法:
|
||
```python
|
||
array = [0x64, 0xd6, 0x10a, 0x171, 0x1a1, 0x20f, 0x26e,
|
||
0x2dd, 0x34f, 0x3ae, 0x41e, 0x452, 0x4c6, 0x538,
|
||
0x5a1, 0x604, 0x635, 0x696, 0x704, 0x763, 0x7cc,
|
||
0x840, 0x875, 0x8d4, 0x920, 0x96c, 0x9c2, 0xa0f]
|
||
|
||
flag = ""
|
||
base = 0
|
||
for num in array:
|
||
flag += chr(num - base)
|
||
base = num
|
||
|
||
print flag
|
||
```
|
||
|
||
Bingo!!!
|
||
```
|
||
$ ./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: dr4g0n_or_p4tric1an_it5_LLVM
|
||
success
|
||
|
||
[+] A great success! Here is a flag{dr4g0n_or_p4tric1an_it5_LLVM}
|
||
```
|
||
|
||
常规方法逆向出来了,但实在是太复杂,我们可以使用一些取巧的方法,想想前面讲过的 Pin 和 angr,下面我们就分别用这两种工具来解决它。
|
||
|
||
#### 使用 Pin
|
||
首先要知道验证是逐字符的,一旦有不相同就会退出,也就是说执行下面语句的次数减一就是正确字符的个数:
|
||
```
|
||
| 0x00402a7f 39c8 cmp eax, ecx ; 进行比较。逐字符比较,不相等时退出。
|
||
```
|
||
另外只有验证成功,才会跳转到地址 `0x0040e2af`,所以把 6.2.1 节的 pintool 拿来改成下面这样,当 count 为 28+1=29 时,验证成功:
|
||
```C
|
||
// This function is called before every instruction is executed
|
||
VOID docount(void *ip) {
|
||
if ((long int)ip == 0x00402a7f) icount++; // 0x00402a7f cmp eax, ecx
|
||
if ((long int)ip == 0x0040e2af) icount++; // 0x0040e2a2 jne 0x0040e2af
|
||
}
|
||
```
|
||
编译 pintool:
|
||
```
|
||
$ cp dont_panic.cpp source/tools/MyPintool
|
||
[MyPinTool]$ make obj-intel64/wyvern.so TARGET=intel64
|
||
```
|
||
执行下看看:
|
||
```
|
||
$ python -c 'print("A"*28)' | ../../../pin -t obj-intel64/wyvern.so -o inscount.out -- ~/wyvern ; cat inscount.out
|
||
+-----------------------+
|
||
| 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:
|
||
[-] You have failed. The dragon's power, speed and intelligence was greater.
|
||
Count 1
|
||
$ python -c 'print("d"+"A"*27)' | ../../../pin -t obj-intel64/wyvern.so -o inscount.out -- ~/wyvern ; cat inscount.out
|
||
+-----------------------+
|
||
| 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:
|
||
[-] You have failed. The dragon's power, speed and intelligence was greater.
|
||
Count 2
|
||
```
|
||
看起来不错,写个脚本自动化该过程:
|
||
```python
|
||
import os
|
||
|
||
def get_count(flag):
|
||
cmd = "echo " + "\"" + flag + "\"" + " | ../../../pin -t obj-intel64/wyvern.so -o inscount.out -- ~/wyvern "
|
||
os.system(cmd)
|
||
with open("inscount.out") as f:
|
||
count = int(f.read().split(" ")[1])
|
||
return count
|
||
|
||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+*'"
|
||
|
||
flag = list("A" * 28)
|
||
count = 0
|
||
for i in range(28):
|
||
for c in charset:
|
||
flag[i] = c
|
||
# print("".join(flag))
|
||
count = get_count("".join(flag))
|
||
# print(count)
|
||
if count == i+2:
|
||
break
|
||
if count == 29:
|
||
break;
|
||
print("".join(flag))
|
||
```
|
||
|
||
#### 使用 angr
|
||
|
||
|
||
## 参考资料
|
||
- [CSAW QUALS 2015: wyvern-500](https://github.com/ctfs/write-ups-2015/tree/master/csaw-ctf-2015/reverse/wyvern-500)
|