📊
CTF-All-In-One
  • 简介
  • 前言
  • 一、基础知识篇
    • 1.1 CTF 简介
    • 1.2 学习方法
    • 1.3 Linux 基础
    • 1.4 Web 安全基础
      • 1.4.1 HTML 基础
      • 1.4.2 HTTP 协议基础
      • 1.4.3 JavaScript 基础
      • 1.4.4 常见 Web 服务器基础
      • 1.4.5 OWASP Top Ten Project 漏洞基础
      • 1.4.6 PHP 源码审计基础
    • 1.5 逆向工程基础
      • 1.5.1 C/C++ 语言基础
      • 1.5.2 汇编基础
      • 1.5.3 Linux ELF
      • 1.5.4 Windows PE
      • 1.5.5 静态链接
      • 1.5.6 动态链接
      • 1.5.7 内存管理
      • 1.5.8 glibc malloc
      • 1.5.9 Linux 内核
      • 1.5.10 Windows 内核
      • 1.5.11 jemalloc
    • 1.6 密码学基础
      • 1.6.1 密码学导论
      • 1.6.2 流密码
      • 1.6.3 分组密码
      • 1.6.4 公钥密码
      • 1.6.5 消息认证和哈希函数
      • 1.6.6 数字签名
      • 1.6.7 密码协议
      • 1.6.8 密钥分配与管理
      • 1.6.9 数字货币
    • 1.7 Android 安全基础
      • 1.7.1 Android 环境搭建
      • 1.7.2 Dalvik 指令集
      • 1.7.3 ARM 汇编基础
      • 1.7.4 Android 常用工具
  • 二、工具篇
    • 虚拟化分析环境
      • 2.1.1 VirtualBox
      • 2.1.2 QEMU
      • 2.1.3 Docker
      • 2.1.4 Unicorn
    • 静态分析工具
      • 2.2.1 radare2
      • 2.2.2 IDA Pro
      • 2.2.3 JEB
      • 2.2.4 Capstone
      • 2.2.5 Keystone
      • 2.2.6 Ghidra
    • 动态分析工具
      • 2.3.1 GDB
      • 2.3.2 OllyDbg
      • 2.3.3 x64dbg
      • 2.3.4 WinDbg
      • 2.3.5 LLDB
    • 其他工具
      • 2.4.1 pwntools
      • 2.4.2 zio
      • 2.4.3 metasploit
      • 2.4.4 binwalk
      • 2.4.5 Burp Suite
      • 2.4.6 Wireshark
      • 2.4.7 Cuckoo Sandbox
  • 三、分类专题篇
    • Pwn
      • 3.1.1 格式化字符串漏洞
      • 3.1.2 整数溢出
      • 3.1.3 栈溢出
      • 3.1.4 返回导向编程(ROP)(x86)
      • 3.1.5 返回导向编程(ROP)(ARM)
      • 3.1.6 Linux 堆利用(一)
      • 3.1.7 Linux 堆利用(二)
      • 3.1.8 Linux 堆利用(三)
      • 3.1.9 Linux 堆利用(四)
      • 3.1.10 内核 ROP
      • 3.1.11 Linux 内核漏洞利用
      • 3.1.12 Windows 内核漏洞利用
      • 3.1.13 竞争条件
      • 3.1.14 虚拟机逃逸
    • Reverse
      • 3.2.1 patch 二进制文件
      • 3.2.2 脱壳技术(PE)
      • 3.2.3 脱壳技术(ELF)
      • 3.2.4 反调试技术(PE)
      • 3.2.5 反调试技术(ELF)
      • 3.2.6 指令混淆
    • Web
      • 3.3.1 SQL 注入利用
      • 3.3.2 XSS 漏洞利用
    • Crypto
    • Misc
      • 3.5.1 Lsb
    • Mobile
  • 四、技巧篇
    • 4.1 Linux 内核调试
    • 4.2 Linux 命令行技巧
    • 4.3 GCC 编译参数解析
    • 4.4 GCC 堆栈保护技术
    • 4.5 ROP 防御技术
    • 4.6 one-gadget RCE
    • 4.7 通用 gadget
    • 4.8 使用 DynELF 泄露函数地址
    • 4.9 shellcode 开发
    • 4.10 跳转导向编程(JOP)
    • 4.11 利用 mprotect 修改栈权限
    • 4.12 利用 __stack_chk_fail
    • 4.13 利用 _IO_FILE 结构
    • 4.14 glibc tcache 机制
    • 4.15 利用 vsyscall 和 vDSO
  • 五、高级篇
    • 5.0 软件漏洞分析
    • 5.1 模糊测试
      • 5.1.1 AFL fuzzer
      • 5.1.2 libFuzzer
    • 5.2 动态二进制插桩
      • 5.2.1 Pin
      • 5.2.2 DynamoRio
      • 5.2.3 Valgrind
    • 5.3 符号执行
      • 5.3.1 angr
      • 5.3.2 Triton
      • 5.3.3 KLEE
      • 5.3.4 S²E
    • 5.4 数据流分析
      • 5.4.1 Soot
    • 5.5 污点分析
      • 5.5.1 TaintCheck
    • 5.6 LLVM
      • 5.6.1 Clang
    • 5.7 程序切片
    • 5.8 SAT/SMT
      • 5.8.1 Z3
    • 5.9 基于模式的漏洞分析
    • 5.10 基于二进制比对的漏洞分析
    • 5.11 反编译技术
      • 5.11.1 RetDec
  • 六、题解篇
    • 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 House_of_Orange
      • 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'hack_teaserCTF2017 The_Great_Escape_part-3
      • 6.1.30 pwn HITCONCTF2017 Ghost_in_the_heap
      • 6.1.31 pwn HITBCTF2018 mutepig
      • 6.1.32 pwn SECCONCTF2017 vm_no_fun
      • 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 house_of_Atum
      • 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
    • Reverse
      • 6.2.1 re XHPCTF2017 dont_panic
      • 6.2.2 re ECTF2016 tayy
      • 6.2.3 re CodegateCTF2017 angrybird
      • 6.2.4 re CSAWCTF2015 wyvern
      • 6.2.5 re PicoCTF2014 Baleful
      • 6.2.6 re SECCONCTF2017 printf_machine
      • 6.2.7 re CodegateCTF2018 RedVelvet
      • 6.2.8 re DefcampCTF2015 entry_language
    • Web
      • 6.3.1 web HCTF2017 babycrack
    • Crypto
    • Misc
    • Mobile
  • 七、实战篇
    • CVE
      • 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
      • 7.1.2 CVE-2015-0235 glibc __nss_hostname_digits_dots 堆溢出漏洞
      • 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
      • 7.1.4 CVE-2017-13089 wget skip_short_body 栈溢出漏洞
      • 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
      • 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
      • 7.1.7 CVE-2018-6323 GNU binutils elf_object_p 整型溢出漏洞
      • 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
      • 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
    • Malware
  • 八、学术篇
    • 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 Softwa
    • 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
    • 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
  • 九、附录
    • 9.1 更多 Linux 工具
    • 9.2 更多 Windows 工具
    • 9.3 更多资源
    • 9.4 Linux 系统调用表
    • 9.5 python2到3字符串转换
    • 9.6 幻灯片
Powered by GitBook
On this page
  • 题目解析
  • 使用 Pin
  • 使用 angr
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Reverse

6.2.4 re CSAWCTF2015 wyvern

Previous6.2.3 re CodegateCTF2017 angrybirdNext6.2.5 re PicoCTF2014 Baleful

Last updated 3 years ago

Was this helpful?

题目解析

$ 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

参考资料

CSAW QUALS 2015: wyvern-500
下载文件
题目解析
参考资料