PWN二进制安全总结篇
Posted 鸿渐之翼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PWN二进制安全总结篇相关的知识,希望对你有一定的参考价值。
Linux常见保护机制
1.Canary
2.Fortify
3.NX/DEP
4.PIE/ALSR
5.RELRO
Linux保护机制
NX保护
将数据(堆、栈)所在内存页识别为不可执行,当程序溢出成功转入shellcode时,
程序尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
编译选项:
关闭: -z execstack
开启: -z noexecstack
PIE保护
作用:
使得程序地址空间随机化,增加ROP等利用的难度。(地址不确定)
编译选项:
关闭:-no-pie
开启:-pie -fPIC
Canary保护
作用:
函数开始执行的时候先往往栈里插入cannary值,当函数真正返回的时候会验证
cannary值是否合法,如果不合法就停止程序运行。可以防止栈溢出覆盖返回地址。
编译选项:
关闭:-fno-stack-protetor
启用:只为局部变量含有char的函数插入保护代码):-fstack-protector -fstack-protector-all
Fortify保护
作用:
主要用来防止格式化字符串漏洞。包含%n的格式化字符串不能位于程序内存中的可写地址。当使用位置参数时,
必须使用范围内的所有参数,如果要使用%7$x,必须使用1%,2%,3%,4%,5%
编译选项:
关闭:-D_FORTIFY_SOURCE=0
开启:-D_FORTIFY_SOURCE=2
RELRO保护
作用:
设置符号重定向表为只读并在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)表的攻击
编译选项:
开启(部分): -z lazy
开去(全部): -z now
对抗DEP/NX保护技术
基础栈结构
栈是一种典型的后进先出(Last in First Out)的数据结构,其操作主要有压栈(push)和出栈(pop)两种操作。
用于保护函数调用信息和局部变量。
X86:函数参数保存在栈上,在函数返回地址的上方。
x64:前六个整型或指针参数依次保存在RDI,RSI,RDX,RCX,R8和R9寄存器中,如果存在更多参数保存在栈上。
基础栈结构(进入函数时)
CALL xxx
push retaddr (call的下一条指令)
jmp xxx
xxx(函数调用机制)
push rbp(保存栈)
mov rbp,rsp(栈抬上去)
sub rsp,xxh(抬高栈顶给局部函数预留栈空间)
leave
mov rsp,rbp (把rsp弄回来)
pop rbp (把rbp弄回来)
ret
pop rip
栈溢出原理
栈溢出指的是程序中向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数。
因而导致与相邻的栈变量值被改变。栈溢出漏洞轻则使程序崩溃,重则使攻击者控制程序
执行流程。
程序必须向栈上写入数据
写入的数据大小没有被良好地控制
ret2text控制执行程序已有函数的代码.text
example:return2text
from pwn import *
context.log_level = 'debug'
io = process('./pwn1')
#gdb.attach(io)
#pause()
payload = 'A'*0x10
payload += p64(0xdeabdbeef) #rbp
payload += p64(0x400686) #rip
io.sendlineafter('Input',payload)
io.interactive()
ret2shellcode
shellcode是指用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的system shell。
在栈溢出的基础上,想要执行shellcode,需要对应的binary在运行时,shellcode所在区域具有可执行权限。
ret2_shellcode
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
io = process('/pwn2')
shellcode = asm(shellcraft.sh())
io.sendater('Name:',shellcode)
payload = 'A'*0x20
payload += p64(0xdeadbeef)
payload += p64(0x601040)
io.sendlineafter('Input',payload)
io.interactive()
vmmp 查看bss段权限
利用ROP方法绕过NX保护
有了NX保护之后,堆、栈、bss段就没有执行权限了
目前主要绕过方法是ROP(return oriented programming)技术,其思想是在栈溢出基础上,
利用程序中已有的小片段(gadgets)来改变某些寄存器或者变量的值,从而控制程序的执行流。
所谓的gadgets就是以ret结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,
方便连续地控制程序执行流程。
之所以称为ROP,因为核心在于利用了指令集中的ret指令,改变了指令流执行顺序,并且利用
gadgets片段的ret,可以实现连续控制。
ROP攻击一般得满足如下条件:
程序存在溢出,并且可以控制放回地址
可以找到满足条件的gadgets以及相应的gadgets地址
(如果gadgets每次地址不是固定的,那么我们需要想办法动态获取对应的地址)
大致过程:
1.精心构造栈结构
2.利用返回地址ret的跳转节点
3.不在栈中或bss段执行代码,而是程序的可执行段寻找可以执行的小组件(gadget)
4.把gadget串起来,构造而成的叫做rop链。
依次劫持EIP寄存器
pop ebx
pop esi
pop edi
pop ebp
retn #pop rip
add esp,4
pop ebx
pop ebp
retn
对抗ALSR/PIE技术
ROP(ret2libc)
如果需要跳转的目标函数,在程序中没有?如system函数
Libc中应有尽有
然而ASLR/PIE保护技术使得程序基地址和libc基地址每次加载的都不同
延迟绑定机制
假设需要利用ROP调用system(’/bin/sh’)
利用思路:
1.泄露GOT表中某个函数的libc地址
2.在libc中找到system,’/bin/sh’和这个函数的相对偏移
3.得到system的地址和’/bin/sh’的地址
4.构造ROP链,成功利用。
注意:只能泄露已经执行过一次的函数的libc地址,。
因为动态链接库的加载机制是lazy原则(got表),即用加载,
第二次不用加载
假设需要利用ROP调用system(’/bin/sh’)
在执行一次某函数之后,GOT表就会把一个函数在程序中的终极偏移存起来。
终极偏移 = libc基址(每次加载不一样)+库内函数相对偏移
system = libc基址 + system在库中的相对偏移
‘/bin/sh’ =libc基址 +’/bin/sh’在库中的相对偏移
对抗Canary保护技术
泄露Canary的值泄露 fs:28h的值
覆写副本值
需要进行位置的爆破
劫持stack_chk_fail
可以修改全局偏移表(GOT)中存储的_stack_chk_fail函数地址,便可以触发
canary检查失败时,跳转到指定的地址继续执行。
stack smashing
当canry被覆盖后,会call到_stack_chk_fail打印argv[0]这个指针指向的字符串,
默认是程序的名字。
如果我们把它覆盖为其他地址时,它就会把其他内存地址的信息给输出。
逐字节爆破(BROP)
攻击条件:
1)远程程序必须先存在一个已知的stack overflow的漏洞,而且攻击者知道如何触发这个漏洞
2)服务器进程crash之后会重新复活,并且复活的进程不会被re-rand
(意味着虽然有ASLR保护,但是复活的进程和之前的进程地址随机化与canary一样)
核心思想是想办法泄露程序更多信息。
由于我们不知道攻击程序的内存布局,所以首先要做的事情是通过某种方法从远程服务器dump出该程序
的内存到本地。
write(int sock,void *buf,int len)
BROP基本思路:
1.判断栈溢出的长度
2.逐字节爆破Canry
3.寻找stop gadget
4.寻找useful gadget(brop gadget)
5.寻找可用PLT表
6.利用PLT表中的puts(write)函数,配合useful gadget,来远程dump
逐字节爆破canary
一个一个字节顺序地进行尝试来还原出真实的canary
#寻找stop gadget
都目前为止,已经得到了合适的canary来绕开stack canary的保护。
但如果我们把返回的地址覆盖成某些我们随意选取的内存地址的话,程序很大可能会crash
存在另外一种情况,即该return address指向了一块代码区域,当程序执行流跳转到那段区域
之后,程序不会crash,而是进入无限循环,这时程序仅仅是hang在那里,攻击者能够一直
保持连接状态,于是,我们把这种类型的gadget,称为stop gadget
可以不断爆破覆盖返回地址尝试stop gadget。
假设我们找到了某个可以造成程序block住的stop gadget,比如一个无限循环。
我们只能对栈操作,而且只能通过覆盖返回地址进行后续操作。
我们猜测到了某个useful gadget,比如pop rdi;ret,但是由于在执行完这个gadget之后
进程还会跳转到栈上的下一个地址,如果该地址是一个非法地址,那么进程最后还是会crash。
在这个过程中攻击者其实,并不知道这个useful gadget被执行过去。
#寻找useful gadget
就是一类可以操纵有用的寄存器的gadget。
#寻找可用的PLT表
程序中plt表具有比较规整的结构,每一个plt表项都是16字节。而且,在每一个表项的6字节偏移出,
是该表项对应的函数的解析路径,即程序最初执行该函数的时候,会执行该路径对函数的got地址进行解析。
如果攻击者发现了好多条连续的16个字节对齐的地址都不会造成进程crash,而且这些地址加6得到的
地址也不会造成进程crash,那么很有可能这就是某个PLT对应的项了。
payload ='A’length+canary+p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget)
在某个程序还没有开启PIE保护的情况下,0x400000处为ELF文件的头部,其内容为\\x7fELF
SROP
SROP的全称是Sigreturn Oriented Programming 'sigreturn’是一个系统调用,在unix中发生signal的时候会间接地调用
signal机制
signal机制是类unix系统中进程之间互相传递信息地一种方法。
一般我们称为软中断信号,或者软中断。
比如说,进程之间可以通过系统调用kill来发送软中断信号。
内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn系统的调用地址。内核主要工作是保存上下文,恢复上下文。
signal frame被保存在用户的地址空间中,所以用户是可以读写的。
由于内核与信息处理程序无关(kernel agnostic about signal handlers),它并不会去记录这个signal对应的 siganl frame,所以当执行sigreturn系统调用时,此时signal fram并不一定是之前内核为用户进程保存的signal frame。
利用SROP思路:
如果希望执行一些列函数,只需要做两处修改即可。
1.控制栈指针。
2.把原来的rip指向syscall gadget换成syscall; ret gadget
可以通过栈溢出控制栈内容
需要知道
“/bin/sh”
Signal Frame
syscall
sigreturn
##格式化字符串
#格式化字符串漏洞利用技术
利用思路
的
使
用
的使用 %11
的使用n =%x%x%x%x%x%x%x%x%x%x%x
%n
%n:dword:4 bytes
%hn:word:2 bytes
%hhn:byte:1 bytes
Try to write value 0x6a686664 to 0x08045566
0x64 to 0x08045566
0x66 to 0x08045567
0x68 to 0x08045568
0x6a to 0x08045569
Payload will work like this:
\\x66\\x55\\x04\\x08\\x67\\x55\\x04\\x08 //8 characters
\\68\\x55\\x04\\x08\\x69\\x55\\x04\\x08 //8 characters
%84c%4
h
h
n
hhn %2c%5
hhnhhn
%2c%6
h
h
n
hhn %2c%7
hhnhhn
如何保护格式化字符串漏洞利用技术
如何被针对
Fortify保护作用:
主要用来防止格式化字符串漏洞。包含%n的格式化字符串不能位于程序内存
中的可写地址。当使用位置参数时,必须使用范围内的所有参数,如果要使用
%7
x
,
必
须
使
用
1
x,必须使用1
x,必须使用1,2
,
3
,3
,3,4$,5%和6%
RELRO保护作用:
设置符号重定向表为只读并在程序启动时就解析并绑定所有动态符号,从而减
少堆GOT(Global Offset Table)的攻击。
一般情况下,都把格式化字符串放在栈上,这样就能把地址也放在格式化字符串中,
通过偏移来索引到栈上的地址,就能实现向该地址写入。但是如果跟程序的格式化字符串是放在堆上,就没有办法把任意地址放在栈上,再去通过栈偏移索引。
由于栈上的rbp会形成链式结构,利用栈上的rbp值会自动形成链式结构。
仅用于已有的数据间构造格式化字符串利用链,实现任意地址写入值。
核心原理是修改栈上的RBP数据的一个字节,由于该RBP数值还是指向栈。
这一个字节的修改,就能让它指向栈上一片区域的任意一个地址。进一步就能
将栈上的这片区域内构造出一个"任意地址",再利用这个任意地址结合格式化字符串去任意写值。
dll_runtime_resolve高级利用技术
ELF简述:
pwn类型题目一般会提供一个可执行程序,同时会提供libc库,通过libc.so可以得到库函数偏移地址,在结合泄露GOT表中libc函数的地址,计算出进程中实际函数的地址,绕过ASLR,ret2libc技术。
ELF程序的基本相关结构
ELF可执行文件由ELF文件,程序头部表和其对应的段,节区头部段和其对应的节组成。如果一个可执行文件参与动态链接,它的程序头部表将包含PT_DYNAMIC的段。
包含.dynamic节区
typedef struct{
Elf32_Sword d_tag:
union{
Elf32_Word d_val;
Elf32_Addr d_ptr;
}d_un;
}Elf32_Dyn;
以上是关于PWN二进制安全总结篇的主要内容,如果未能解决你的问题,请参考以下文章
硬核二进制安全:Pwn Return To Text溢出漏洞的分析与利用
二进制CTF-Wiki PWN里面的一些练习题(Basic-ROP篇)