- 简介
- 一、基础知识篇
- 二、工具篇
- 三、分类专题篇
- 四、技巧篇
- 五、高级篇
- 六、题解篇
- 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.4 GCC 堆栈保护技术
技术简介
Linux 中有各种各样的安全防护,其中 ASLR 是由内核直接提供的,通过系统配置文件控制。NX,Canary,PIE,RELRO 等需要在编译时根据各项参数开启或关闭。未指定参数时,使用默认设置。
CANARY
启用 CANARY 后,函数开始执行的时候会先往栈里插入 canary 信息,当函数返回时验证插入的 canary 是否被修改,如果是,则说明发生了栈溢出,程序停止运行。
下面是一个例子:
#include <stdio.h>
void main(int argc, char **argv) {
char buf[10];
scanf("%s", buf);
}
我们先开启 CANARY,来看看执行的结果:
$ gcc -m32 -fstack-protector canary.c -o f.out
$ python -c 'print("A"*20)' | ./f.out
*** stack smashing detected ***: ./f.out terminated
Segmentation fault (core dumped)
接下来关闭 CANARY:
$ gcc -m32 -fno-stack-protector canary.c -o fno.out
$ python -c 'print("A"*20)' | ./fno.out
Segmentation fault (core dumped)
可以看到当开启 CANARY 的时候,提示检测到栈溢出和段错误,而关闭的时候,只有提示段错误。
下面对比一下反汇编代码上的差异:
开启 CANARY 时:
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x000005ad <+0>: lea ecx,[esp+0x4]
0x000005b1 <+4>: and esp,0xfffffff0
0x000005b4 <+7>: push DWORD PTR [ecx-0x4]
0x000005b7 <+10>: push ebp
0x000005b8 <+11>: mov ebp,esp
0x000005ba <+13>: push ebx
0x000005bb <+14>: push ecx
0x000005bc <+15>: sub esp,0x20
0x000005bf <+18>: call 0x611 <__x86.get_pc_thunk.ax>
0x000005c4 <+23>: add eax,0x1a3c
0x000005c9 <+28>: mov edx,ecx
0x000005cb <+30>: mov edx,DWORD PTR [edx+0x4]
0x000005ce <+33>: mov DWORD PTR [ebp-0x1c],edx
0x000005d1 <+36>: mov ecx,DWORD PTR gs:0x14 ; 将 canary 值存入 ecx
0x000005d8 <+43>: mov DWORD PTR [ebp-0xc],ecx ; 在栈 ebp-0xc 处插入 canary
0x000005db <+46>: xor ecx,ecx
0x000005dd <+48>: sub esp,0x8
0x000005e0 <+51>: lea edx,[ebp-0x16]
0x000005e3 <+54>: push edx
0x000005e4 <+55>: lea edx,[eax-0x1940]
0x000005ea <+61>: push edx
0x000005eb <+62>: mov ebx,eax
0x000005ed <+64>: call 0x450 <__isoc99_scanf@plt>
0x000005f2 <+69>: add esp,0x10
0x000005f5 <+72>: nop
0x000005f6 <+73>: mov eax,DWORD PTR [ebp-0xc] ; 从栈中取出 canary
0x000005f9 <+76>: xor eax,DWORD PTR gs:0x14 ; 检测 canary 值
0x00000600 <+83>: je 0x607 <main+90>
0x00000602 <+85>: call 0x690 <__stack_chk_fail_local>
0x00000607 <+90>: lea esp,[ebp-0x8]
0x0000060a <+93>: pop ecx
0x0000060b <+94>: pop ebx
0x0000060c <+95>: pop ebp
0x0000060d <+96>: lea esp,[ecx-0x4]
0x00000610 <+99>: ret
End of assembler dump.
关闭 CANARY 时:
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x0000055d <+0>: lea ecx,[esp+0x4]
0x00000561 <+4>: and esp,0xfffffff0
0x00000564 <+7>: push DWORD PTR [ecx-0x4]
0x00000567 <+10>: push ebp
0x00000568 <+11>: mov ebp,esp
0x0000056a <+13>: push ebx
0x0000056b <+14>: push ecx
0x0000056c <+15>: sub esp,0x10
0x0000056f <+18>: call 0x59c <__x86.get_pc_thunk.ax>
0x00000574 <+23>: add eax,0x1a8c
0x00000579 <+28>: sub esp,0x8
0x0000057c <+31>: lea edx,[ebp-0x12]
0x0000057f <+34>: push edx
0x00000580 <+35>: lea edx,[eax-0x19e0]
0x00000586 <+41>: push edx
0x00000587 <+42>: mov ebx,eax
0x00000589 <+44>: call 0x400 <__isoc99_scanf@plt>
0x0000058e <+49>: add esp,0x10
0x00000591 <+52>: nop
0x00000592 <+53>: lea esp,[ebp-0x8]
0x00000595 <+56>: pop ecx
0x00000596 <+57>: pop ebx
0x00000597 <+58>: pop ebp
0x00000598 <+59>: lea esp,[ecx-0x4]
0x0000059b <+62>: ret
End of assembler dump.
FORTIFY
FORTIFY 的选项 -D_FORTIFY_SOURCE
往往和优化 -O
选项一起使用,以检测缓冲区溢出的问题。
下面是一个简单的例子:
#include<string.h>
void main() {
char str[3];
strcpy(str, "abcde");
}
$ gcc -O2 fortify.c
$ checksec --file a.out
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 0 a.out
$ gcc -O2 -D_FORTIFY_SOURCE=2 fortify.c
In file included from /usr/include/string.h:639:0,
from fortify.c:1:
In function ‘strcpy’,
inlined from ‘main’ at fortify.c:4:2:
/usr/include/bits/string3.h:109:10: warning: ‘__builtin___memcpy_chk’ writing 6 bytes into a region of size 3 overflows the destination [-Wstringop-overflow=]
return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ checksec --file a.out
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 2 2 a.out
开启优化 -O2
后,编译没有检测出任何问题,checksec 后 FORTIFY 为 No。当配合 -D_FORTIFY_SOURCE=2
(也可以 =1
)使用时,提示存在溢出问题,checksec 后 FORTIFY 为 Yes。
NX
No-eXecute,表示不可执行,其原理是将数据所在的内存页标识为不可执行,如果程序产生溢出转入执行 shellcode 时,CPU 会抛出异常。
在 Linux 中,当装载器将程序装载进内存空间后,将程序的 .text 段标记为可执行,而其余的数据段(.data、.bss 等)以及栈、堆均为不可执行。因此,传统利用方式中通过修改 GOT 来执行 shellcode 的方式不再可行。
但这种保护并不能阻止攻击者通过代码重用来进行攻击(ret2libc)。
PIE
PIE(Position Independent Executable)需要配合 ASLR 来使用,以达到可执行文件的加载时地址随机化。简单来说,PIE 是编译时随机化,由编译器完成;ASLR 是加载时随机化,由操作系统完成。ASLR 将程序运行时的堆栈以及共享库的加载地址随机化,而 PIE 在编译时将程序编译为位置无关、即程序运行时各个段加载的虚拟地址在装载时确定。开启 PIE 时,编译生成的是动态库文件(Shared object)文件,而关闭 PIE 后生成可执行文件(Executable)。
我们通过实际例子来探索一下 PIE 和 ASLR:
#include<stdio.h>
void main() {
printf("%p\n", main);
}
$ gcc -m32 -pie random.c -o open-pie
$ readelf -h open-pie
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x400
Start of program headers: 52 (bytes into file)
Start of section headers: 6132 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 29
$ gcc -m32 -no-pie random.c -o close-pie
$ readelf -h close-pie
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048310
Start of program headers: 52 (bytes into file)
Start of section headers: 5964 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 29
可以看到两者的不同在 Type
和 Entry point address
。
首先我们关闭 ASLR,使用 -pie
进行编译:
# echo 0 > /proc/sys/kernel/randomize_va_space
# gcc -m32 -pie random.c -o a.out
# checksec --file a.out
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 2 a.out
# ./a.out
0x5655553d
# ./a.out
0x5655553d
我们虽然开启了 -pie
,但是 ASLR 被关闭,入口地址不变。
# ldd a.out
linux-gate.so.1 (0xf7fd7000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000)
/lib/ld-linux.so.2 (0xf7fd9000)
# ldd a.out
linux-gate.so.1 (0xf7fd7000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000)
/lib/ld-linux.so.2 (0xf7fd9000)
可以看出动态链接库地址也不变。然后我们开启 ASLR:
# echo 2 > /proc/sys/kernel/randomize_va_space
# ./a.out
0x5665353d
# ./a.out
0x5659753d
# ldd a.out
linux-gate.so.1 (0xf7727000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7529000)
/lib/ld-linux.so.2 (0xf7729000)
# ldd a.out
linux-gate.so.1 (0xf77d6000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf75d8000)
/lib/ld-linux.so.2 (0xf77d8000)
入口地址和动态链接库地址都变得随机。
接下来关闭 ASLR,并使用 -no-pie
进行编译:
# echo 0 > /proc/sys/kernel/randomize_va_space
# gcc -m32 -no-pie random.c -o b.out
# checksec --file b.out
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 2 b.out
# ./b.out
0x8048406
# ./b.out
0x8048406
# ldd b.out
linux-gate.so.1 (0xf7fd7000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000)
/lib/ld-linux.so.2 (0xf7fd9000)
# ldd b.out
linux-gate.so.1 (0xf7fd7000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000)
/lib/ld-linux.so.2 (0xf7fd9000)
入口地址和动态库都是固定的。下面开启 ASLR:
# echo 2 > /proc/sys/kernel/randomize_va_space
# ./b.out
0x8048406
# ./b.out
0x8048406
# ldd b.out
linux-gate.so.1 (0xf7797000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7599000)
/lib/ld-linux.so.2 (0xf7799000)
# ldd b.out
linux-gate.so.1 (0xf770a000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf750c000)
/lib/ld-linux.so.2 (0xf770c000)
入口地址依然固定,但是动态库变为随机。
所以在分析一个 PIE 开启的二进制文件时,只需要关闭 ASLR,即可使 PIE 和 ASLR 都失效。
ASLR(Address Space Layout Randomization)
关闭:
# echo 0 > /proc/sys/kernel/randomize_va_space
部分开启(将 mmap 的基址,stack 和 vdso 页面随机化):
# echo 1 > /proc/sys/kernel/randomize_va_space
完全开启(在部分开启的基础上增加 heap的随机化:
# echo 2 > /proc/sys/kernel/randomize_va_space
RELRO
RELRO(ReLocation Read-Only)设置符号重定向表为只读或在程序启动时就解析并绑定所有动态符号,从而减少对 GOT(Global Offset Table)的攻击。
RELOR 有两种形式:
- Partial RELRO:一些段(包括
.dynamic
)在初始化后将会被标记为只读。 - Full RELRO:除了 Partial RELRO,延迟绑定将被禁止,所有的导入符号将在开始时被解析,
.got.plt
段会被完全初始化为目标函数的最终地址,并被标记为只读。另外link_map
和_dl_runtime_resolve
的地址也不会被装入。
编译参数
各种安全技术的编译参数如下:
安全技术 | 完全开启 | 部分开启 | 关闭 |
---|---|---|---|
Canary | -fstack-protector-all | -fstack-protector | -fno-stack-protector |
NX | -z noexecstack | -z execstack | |
PIE | -pie | -no-pie | |
RELRO | -z now | -z lazy | -z norelro |
关闭所有保护:
gcc hello.c -o hello -fno-stack-protector -z execstack -no-pie -z norelro
开启所有保护:
gcc hello.c -o hello -fstack-protector-all -z noexecstack -pie -z now
- FORTIFY
-D_FORTIFY_SOURCE=1
:仅在编译时检测溢出-D_FORTIFY_SOURCE=2
:在编译时和运行时检测溢出
保护机制检测
有许多工具可以检测二进制文件所使用的编译器安全技术。下面介绍常用的几种:
checksec
$ checksec --file /bin/ls
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 5 15 /bin/ls
peda 自带的 checksec
$ gdb /bin/ls
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : ENABLED
NX : ENABLED
PIE : disabled
RELRO : Partial
地址空间布局随机化
最后再说一下地址空间布局随机化(ASLR),该技术虽然不是由 GCC 编译时提供的,但对 PIE 还是有影响。该技术旨在将程序的内存布局随机化,使得攻击者不能轻易地得到数据区的地址来构造 payload。由于程序的堆栈分配与共享库的装载都是在运行时进行,系统在程序每次执行时,随机地分配程序堆栈的地址以及共享库装载的地址。使得攻击者无法预测自己写入的数据区的虚拟地址。
针对该保护机制的攻击,往往是通过信息泄漏来实现。由于同一模块中的所有代码和数据的相对偏移是固定的,攻击者只要泄漏出某个模块中的任一代码指针或数据指针,即可通过计算得到此模块中任意代码或数据的地址。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论