Featured image of post 二进制安全基础知识

二进制安全基础知识

二进制安全知识总结

简介

学习了Web安全,复现过一些漏洞,并且挖到几个漏洞后,我想要学习一下二进制安全。

目的

该文章会记录二进制的知识点,不只是安全,还包括二进制的基础知识。 本次学习,希望完成以下目标:

  • 理解二进制的基础原理
  • 了解二进制漏洞的类型
  • 理解二进制漏洞的原理
  • 能够编写PoC,对于二进制漏洞进行简单地利用

收集材料

以下资料是平时阅读时见到的,个人认为是学习二进制安全的好资料。

编程语言

编程语言根据是否可编译,可以分为:

  • 编译型语言,比如:C,C++,Go和Rust等
  • 解释型语言,比如:Bash,Python,Ruby,JavaScript和Php等

其中,编译型语言按照编译过程,可以分为:

  • 源代码
  • 汇编语言
  • 机器语言

编译过程

下图展示了一个源程序的生命周期: Source Code Life Cycle

每个阶段的详细过程如下表所示:

阶段 具体过程
编译
汇编
链接
加载

编译过程产生的文件:

Platform Extension Type
Linux .c 源文件
Linux .i 预处理文件
Linux .s 汇编文件
Linux .o 目标文件
Linux .a 静态链接库
Linux .so 动态链接库
Linux .out 可执行文件
Windows .C 源文件
Windows .ASM 汇编文件
Windows .OBJ 目标文件
Windows .LIB 静态链接库
Windows .DLL 动态链接库
Windows .EXE 可执行文件
详细的扩展名列表,请参考gcc的man手册。

编译原理

编译器的原理分为如下5个步骤:

  • Lexical Analysis
  • Syntax Analysis
  • Semantic Analysis
  • 中间代码生成和优化
  • 代码生成和优化

编译示例

hello.c源代码如下:

1
2
3
4
5
#include <stdio.h>

int main() {
	printf("Hello,World!");
}

使用gcc 编译,编译过程如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 预处理:展开宏定义
gcc -E hello.c -o hello.i
# 编译:将预处理后的源代码转换为汇编代码
gcc -S hello.i -o hello.s
# 汇编:将汇编代码转换为二进制的机器语言
gcc -c hello.s -o hello.o
# 链接:链接依赖库,生成可执行文件
gcc hello.o -o hello

# 查看编译各个阶段产生的文件信息
stat hello.c # 60字节
stat hello.i # 17130字节
stat hello.s # 472字节
stat hello.o # 1488字节
stat hello # 15936字节,大约16K

使用gcc 一次完成上述过程:

1
gcc -save-temps --verbose hello.c -o hello # -save-temps会保存产生的临时文件,--verbose会输出冗余信息

可以看到,实际上,用到了cc1ascollect2 三个gcc 中的命令来执行编译;该过程中分别使用了cc1、as和collect2三个工具。其中cc1是编译器,对应第一和第二阶段,用于将源文件hello.c编译为hello.s;as是汇编器,对应第三阶段,用于将hello.s汇编为hello.o目标文件;链接器collect2是对ld命令的封装,用于将C语言运行时库(CRT)中的目标文件(crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o)以及所需的动态链接库(libgcc.so、libgcc_s.so、libc.so)链接到可执行hello。

Endianness(字节序)

字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指电脑内存中或在数字通信链路中,组成多字节的字的字节的排列顺序。

我们都知道,计算机可以处理的最小单元是Bit(位),8位是一个字节。很多时候,我们要传输或者存储一个多字节对象,比如:

  • Unicode字符
  • C语言中一个int类型的变量x
  • 汇编语言中的一个指令或者操作数 对于这种多字节对象,我们如何排列字节的顺序呢?对于这种顺序的描述,就叫做字节序。

一些简单的示例:

1
2
3
4
5
6
7
8
9
from pwn import *
p32(1024,endian='little')
# b'\x00\x04\x00\x00'
p32(1024,endian='big')
# b'\x00\x00\x04\x00'
p64(1024,endian='little')
# b'\x00\x04\x00\x00\x00\x00\x00\x00'
p64(1024,endian='big')
# b'\x00\x00\x00\x00\x00\x00\x04\x00'

ELF

ELF(Executable and Linkable Format),即“可执行可链接格式”,最初由UNIX系统实验室作为应用程序二进制接口(Application Binary Interface –ABI)的一部分而制定和发布,是COFF(Common file format)格式的变种。Linux系统上所运行的就是ELF格式的文件,相关定义在“/usr/include/elf.h”文件里。

在审视一个目标文件时,有两种视角可供选择,一种是链接视角,通过节(Section)来进行划分;另一种是运行视角,通过段(Segment)来进行划分。

类型

  • Relocatable File:由源文件编译而成且尚未链接的目标文件,通常以“.o”作为扩展名。用于与其他目标文件进行链接以构成可执行文件或动态链接库,通常是一段位置独立的代码(Position Independent Code, PIC)。
  • Shared Object File:动态链接库文件。用于在链接过程中与其他动态链接库或可重定位文件一起构建新的目标文件,或者在可执行文件加载时,链接到进程中作为运行代码的一部分。
  • Executable File:经过链接的、可执行的目标文件,通常也被称为程序。
  • Coredump:程序收到某个Signal意外中止后产生的内存转储。

Sections

Section Description
.got.plt
.got(Global Offset Table) 保存所有外部符号的地址信息
.plt(Procedure Linkage Table) 代码,调用链接器来解析某个外部函数的地址, 并填充到.got.plt中, 然后跳转到该函数
.plt.got
symtab/strtab 调试时使用,strip会删除该符号表
dnssym/dynstr

格式

可执行程序启动过程

延时加载

为了引入RELRO保护机制,GOT被拆分为.got节和.got.plt节两个部分,不需要延迟绑定的前者用于保存全局变量引用,加载到内存后被标记为只读;需要延迟绑定的后者则用于保存函数引用,具有读写权限。

进程内存布局

Segments

Memory of Process

虚拟内存

可执行程序加载进内存后,就成为了进程,由于安全等因素,进程并不能直接访问物理内存,内核提供了虚拟内存功能,这样,每个进程拥有自己的独立内存空间, 按照链接器的约定, 32位程序会加载到0x08048000这个地址中。

物理内存

Linux安全机制

Stack Canaries

1
2
3
4
gcc -o test test.c						// 默认情况下,不开启Canary保护
gcc -fno-stack-protector -o test test.c  //禁用栈保护
gcc -fstack-protector -o test test.c   //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码

函数执行时,在ebp附近插入cookie信息,当函数返回时,检查该cookie信息,缓解栈溢出带来的危害。

NX(No-eXecute)

标记栈上的数据没有可执行权限,当栈溢出时,程序崩溃。

1
2
3
gcc -o test test.c                  // 默认情况下, 开启NX保护
gcc -z execstack -o test test.c     // 禁用NX保护
gcc -z noexecstack -o test test.c   // 开启NX保护

该功能在Windows上成为DEP(数据执行保护)。

ASLR(Address Space Layout Randomization)(Kernel Layer)

In order to prevent an attacker from reliably jumping to, for example, a particular exploited function in memory, ASLR randomly arranges the address space positions of key data areas of a process, including the base of the executable and the positions of the stack, heap and libraries.

关闭ASLR:

1
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

PIE(Position Independent Executable)(Application Layer)

With pie, everything in the “binary’s” memory regions is compiled to have an offset versus a fixed address. Each time the binary is run, the binary generates a random number known as a base. Then the address of everything becomes the base plus the offset.

1
2
3
4
5
gcc -o test test.c				// 默认情况下,不开启PIE
gcc -fpie -pie -o test test.c		// 开启PIE,此时强度为1
gcc -fPIE -pie -o test test.c		// 开启PIE,此时为最高强度2
gcc -fpic -o test test.c		// 开启PIC,此时强度为1,不会开启PIE
gcc -fPIC -o test test.c		// 开启PIC,此时为最高强度2,不会开启PIE

FORTIFY_SOURCE

1
2
3
gcc -o test test.c							// 默认情况下,不会开这个检查
gcc -D_FORTIFY_SOURCE=1 -o test test.c		// 较弱的检查
gcc -D_FORTIFY_SOURCE=2 -o test test.c		// 较强的检查

RELRO

RELRO(ReLocation Read-Only)机制的提出就是为了解决延迟绑定的安全问题,它最初于2004年由Redhat的工程师Jakub Jelínek实现,它将符号重定向表设置为只读,或者在程序启动时就解析并绑定所有动态符号,从而避免GOT上的地址被篡改。 如今,RELOR有两种形式:

  • Partial RELRO:一些段(包括.dynamic、.got等)在初始化后将会被标记为只读。在Ubuntu16.04(GCC-5.4.0)上,默认开启Partial RELRO。
  • Full RELRO:除了Partial RELRO,延迟绑定将被禁止,所有的导入符号将在开始时被解析,.got.plt段会被完全初始化为目标函数的最终地址,并被mprotect标记为只读,但其实.got.plt会直接被合并到.got,也就看不到这段了。另外link_map和_dl_runtime_resolve的地址也不会被装入。开启Full RELRO会对程序启动时的性能造成一定的影响,但也只有这样才能防止攻击者篡改GOT。
1
2
3
4
gcc -o test test.c						// 默认情况下,是Partial RELRO
gcc -z norelro -o test test.c			// 关闭,即No RELRO
gcc -z lazy -o test test.c				// 部分开启,即Partial RELRO
gcc -z now -o test test.c				// 全部开启

漏洞类型

原理

安全的本质是信任,即我们信任哪些数据,不信任哪些数据;产生漏洞的原因在于,程序把数据当作代码执行了。在二进制安全中,漏洞产生的原因在于,我们可以修改内存数据,这样我们就可以控制代码执行。

利用

ROP(Return-Oriented Programming)

ROP(Return-oriented programming)是指面向返回编程。在32位系统的汇编语言中,ret相当于pop EIP,即将栈顶的数据赋值给 EIP,并从栈弹出。所以如果控制栈中数据,是可以控制程序的执行流的。由于 NX 保护让我们无法直接执行栈上的 shellcode,那么就可以考虑在程序的可执行的段中通过 ROP 技术执行我们的 shellcode。初级的 ROP 技术包括 ret2text,ret2shellcode,ret2syscall,ret2libc。

ret2shellcode

ret2text

ret2libc

ret2syscall

学习示例

附录

术语表

术语 解释
ISA Instruction Set Architecture,指令集架构
endianness 字节序
Big-endian 大端字节序
Little-endian 小端字节序
MSB Most Significan Byte
LSB Least Significan Byte

参考

使用 Hugo 构建
主题 StackJimmy 设计