简介
学习了Web安全,复现过一些漏洞,并且挖到几个漏洞后,我想要学习一下二进制安全。
目的
该文章会记录二进制的知识点,不只是安全,还包括二进制的基础知识。 本次学习,希望完成以下目标:
- 理解二进制的基础原理
- 了解二进制漏洞的类型
- 理解二进制漏洞的原理
- 能够编写PoC,对于二进制漏洞进行简单地利用
收集材料
以下资料是平时阅读时见到的,个人认为是学习二进制安全的好资料。
- CTF-All-In-One
- CTF竞赛权威指南
- 软件供应链安全――源代码缺陷实例剖析
- 看雪知识库
- 二进制安全pwn基础
- CTF Wiki
- RE4B
- Reverse Engineering For Everyone!
- 恶意代码分析实战
- Reverse Engineering Reading List 前面两本书综合性比较强,讲述了基本的原理和方法,最后一本书统计了漏洞类型。
编程语言
编程语言根据是否可编译,可以分为:
- 编译型语言,比如:C,C++,Go和Rust等
- 解释型语言,比如:Bash,Python,Ruby,JavaScript和Php等
其中,编译型语言按照编译过程,可以分为:
- 源代码
- 汇编语言
- 机器语言
编译过程
下图展示了一个源程序的生命周期:
每个阶段的详细过程如下表所示:
阶段 | 具体过程 |
---|---|
编译 | |
汇编 | |
链接 | |
加载 |
编译过程产生的文件:
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源代码如下:
使用gcc
编译,编译过程如下:
|
|
使用gcc
一次完成上述过程:
|
|
可以看到,实际上,用到了cc1
,as
和collect2
三个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
- 汇编语言中的一个指令或者操作数 对于这种多字节对象,我们如何排列字节的顺序呢?对于这种顺序的描述,就叫做字节序。
一些简单的示例:
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
虚拟内存
可执行程序加载进内存后,就成为了进程,由于安全等因素,进程并不能直接访问物理内存,内核提供了虚拟内存功能,这样,每个进程拥有自己的独立内存空间,
按照链接器的约定, 32位程序会加载到0x08048000
这个地址中。
物理内存
Linux安全机制
Stack Canaries
函数执行时,在ebp附近插入cookie信息,当函数返回时,检查该cookie信息,缓解栈溢出带来的危害。
NX(No-eXecute)
标记栈上的数据没有可执行权限,当栈溢出时,程序崩溃。
该功能在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:
|
|
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.
FORTIFY_SOURCE
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。
漏洞类型
原理
安全的本质是信任,即我们信任哪些数据,不信任哪些数据;产生漏洞的原因在于,程序把数据当作代码执行了。在二进制安全中,漏洞产生的原因在于,我们可以修改内存数据,这样我们就可以控制代码执行。
利用
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 |