📊
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
  • 技术简介
  • CANARY
  • FORTIFY
  • NX
  • PIE
  • RELRO
  • 编译参数
  • 保护机制检测
  • checksec
  • peda 自带的 checksec
  • 地址空间布局随机化

Was this helpful?

  1. 四、技巧篇

4.4 GCC 堆栈保护技术

Previous4.3 GCC 编译参数解析Next4.5 ROP 防御技术

Last updated 3 years ago

Was this helpful?

技术简介

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。由于程序的堆栈分配与共享库的装载都是在运行时进行,系统在程序每次执行时,随机地分配程序堆栈的地址以及共享库装载的地址。使得攻击者无法预测自己写入的数据区的虚拟地址。

针对该保护机制的攻击,往往是通过信息泄漏来实现。由于同一模块中的所有代码和数据的相对偏移是固定的,攻击者只要泄漏出某个模块中的任一代码指针或数据指针,即可通过计算得到此模块中任意代码或数据的地址。

技术简介
编译参数
保护机制检测
地址空间布局随机化