- 简介
- 一、基础知识篇
- 二、工具篇
- 三、分类专题篇
- 四、技巧篇
- 五、高级篇
- 六、题解篇
- 6.1 Pwn
- 6.1.1 pwn HCTF2016 brop
- 6.1.2 pwn NJCTF2017 pingme
- 6.1.3 pwn XDCTF2015 pwn200
- 6.1.4 pwn BackdoorCTF2017 Fun-Signals
- 6.1.5 pwn GreHackCTF2017 beerfighter
- 6.1.6 pwn DefconCTF2015 fuckup
- 6.1.7 pwn 0CTF2015 freenote
- 6.1.8 pwn DCTF2017 Flex
- 6.1.9 pwn RHme3 Exploitation
- 6.1.10 pwn 0CTF2017 BabyHeap2017
- 6.1.11 pwn 9447CTF2015 Search-Engine
- 6.1.12 pwn N1CTF2018 vote
- 6.1.13 pwn 34C3CTF2017 readme_revenge
- 6.1.14 pwn 32C3CTF2015 readme
- 6.1.15 pwn 34C3CTF2017 SimpleGC
- 6.1.16 pwn HITBCTF2017 1000levels
- 6.1.17 pwn SECCONCTF2016 jmper
- 6.1.18 pwn HITBCTF2017 Sentosa
- 6.1.19 pwn HITBCTF2018 gundam
- 6.1.20 pwn 33C3CTF2016 babyfengshui
- 6.1.21 pwn HITCONCTF2016 Secret_Holder
- 6.1.22 pwn HITCONCTF2016 Sleepy_Holder
- 6.1.23 pwn BCTF2016 bcloud
- 6.1.24 pwn HITCONCTF2016 HouseofOrange
- 6.1.25 pwn HCTF2017 babyprintf
- 6.1.26 pwn 34C3CTF2017 300
- 6.1.27 pwn SECCONCTF2016 tinypad
- 6.1.28 pwn ASISCTF2016 b00ks
- 6.1.29 pwn Insomni'hackteaserCTF2017 TheGreatEscapepart-3
- 6.1.30 pwn HITCONCTF2017 Ghostinthe_heap
- 6.1.31 pwn HITBCTF2018 mutepig
- 6.1.32 pwn SECCONCTF2017 vmnofun
- 6.1.33 pwn 34C3CTF2017 LFA
- 6.1.34 pwn N1CTF2018 memsafety
- 6.1.35 pwn 0CTF2018 heapstorm2
- 6.1.36 pwn NJCTF2017 messager
- 6.1.37 pwn sixstarctf2018 babystack
- 6.1.38 pwn HITCONCMT2017 pwn200
- 6.1.39 pwn BCTF2018 houseofAtum
- 6.1.40 pwn LCTF2016 pwn200
- 6.1.41 pwn PlaidCTF2015 PlaidDB
- 6.1.42 pwn hacklu2015 bookstore
- 6.1.43 pwn 0CTF2018 babyheap
- 6.1.44 pwn ASIS2017 start_hard
- 6.1.45 pwn LCTF2016 pwn100
- 6.2 Reverse
- 6.3 Web
- 6.1 Pwn
- 七、实战篇
- 7.1 CVE
- 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
- 7.1.2 CVE-2015-0235 glibc _nsshostnamedigitsdots 堆溢出漏洞
- 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
- 7.1.4 CVE-2017-13089 wget skipshortbody 栈溢出漏洞
- 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
- 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
- 7.1.7 CVE-2018-6323 GNU binutils elfobjectp 整型溢出漏洞
- 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
- 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
- 7.1 CVE
- 八、学术篇
- 8.1 The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86)
- 8.2 Return-Oriented Programming without Returns
- 8.3 Return-Oriented Rootkits: Bypassing Kernel Code Integrity Protection Mechanisms
- 8.4 ROPdefender: A Detection Tool to Defend Against Return-Oriented Programming Attacks
- 8.5 Data-Oriented Programming: On the Expressiveness of Non-Control Data Attacks
- 8.7 What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP Defenses
- 8.9 Symbolic Execution for Software Testing: Three Decades Later
- 8.10 AEG: Automatic Exploit Generation
- 8.11 Address Space Layout Permutation (ASLP): Towards Fine-Grained Randomization of Commodity Software
- 8.13 New Frontiers of Reverse Engineering
- 8.14 Who Allocated My Memory? Detecting Custom Memory Allocators in C Binaries
- 8.21 Micro-Virtualization Memory Tracing to Detect and Prevent Spraying Attacks
- 8.22 Practical Memory Checking With Dr. Memory
- 8.23 Evaluating the Effectiveness of Current Anti-ROP Defenses
- 8.24 How to Make ASLR Win the Clone Wars: Runtime Re-Randomization
- 8.25 (State of) The Art of War: Offensive Techniques in Binary Analysis
- 8.26 Driller: Augmenting Fuzzing Through Selective Symbolic Execution
- 8.27 Firmalice - Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware
- 8.28 Cross-Architecture Bug Search in Binary Executables
- 8.29 Dynamic Hooks: Hiding Control Flow Changes within Non-Control Data
- 8.30 Preventing brute force attacks against stack canary protection on networking servers
- 8.33 Under-Constrained Symbolic Execution: Correctness Checking for Real Code
- 8.34 Enhancing Symbolic Execution with Veritesting
- 8.38 TaintEraser: Protecting Sensitive Data Leaks Using Application-Level Taint Tracking
- 8.39 DART: Directed Automated Random Testing
- 8.40 EXE: Automatically Generating Inputs of Death
- 8.41 IntPatch: Automatically Fix Integer-Overflow-to-Buffer-Overflow Vulnerability at Compile-Time
- 8.42 Dynamic Taint Analysis for Automatic Detection, Analysis, and Signature Generation of Exploits on Commodity Software
- 8.43 DTA++: Dynamic Taint Analysis with Targeted Control-Flow Propagation
- 8.44 Superset Disassembly: Statically Rewriting x86 Binaries Without Heuristics
- 8.45 Ramblr: Making Reassembly Great Again
- 8.46 FreeGuard: A Faster Secure Heap Allocator
- 8.48 Reassembleable Disassembling
- 九、附录
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
,依次类推。由此可以写出解密算法:
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 时,验证成功:
// 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
看起来不错,写个脚本自动化该过程:
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
参考资料
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论