PWN保护机制以及编译方法

Posted 逆向萌新

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PWN保护机制以及编译方法相关的知识,希望对你有一定的参考价值。

PWN保护

引入

Ctf中的pwn题,在利用gcc编译的时候,保护是如何开启的,如何编译出来的,保护都有什么由于在ctf中,大部分都是linux pwn,Windows pwn很少见,所以我这里以linux pwn来举例。

PWN的保护

在pwn里,保护一共是四种分别是RELRO、Stack、NX、PIE。
1.RELRO(ReLocation Read-Only):分为两种情况,第一种情况是Partial RELRO,这种情况是部分开启堆栈地址随机化,got表可写,第二种,Full RELRO是全部开启,got表不可写,Got表是全局偏移表,里面包含的是外部定义的符号相应的条目的数据段中,PLT表,是过程链接表/内部函数表,linux延迟绑定,但是最后还是要连接到Got,PLT表只是为一个过渡的作用。
2.Stack(canary):这个保护其实就是在你调用的函数的时候,在栈帧中插入一个随机数,在函数执行完成返回之前,来校验随机数是否被改变,来判断是否被栈溢出,这个我们也俗称为canary(金丝雀),栈保护技术。
3.NX(no execute):为栈不可知性,也就是栈上的数据不可以当作代码区执行的作用。
4.PIE(Position Independent Executable):PIE的中文叫做,地址无关可执行文件,是针对.text(代码段),.data(数据段),.bss(未初始化全局变量段)来做的保护,正常每一次加载程序,加载地址是固定的,但是PIE保护开启,每次程序启动的时候都会变换加载地址。

编译

在gcc中,利用默认参数进行生成,默认生成的是RELRO、PIE、NX保护是全开的情况,但是有一些题,会针对题型的不同,来更变所开启的保护机制。

从头开始,关闭所有的保护开始关闭所有保护开始gcc -z execstack -fno-stack-protector -no-pie -z norelro -o 输出文件名 c语言文件名,来编译一个linux可执行文件,关闭所有的保护,之后在生成一个保护全开的一个文件,在相同的c语言源文件下进行生成利用命令gcc -fstack-protector-all -o b.out lk.c,生成了一个保护全开的一个文件,进行对比分析。
两个文件,一个是保护全开,一个是全关。
这是checksec之后的结果,来看内部的不同,有一些是可以半开,半关的,那些最后说。
NX:在没有开启的时候在利用readelf-l 文件中,发现了对于栈的一个区别,在这里叫做GNU_STACK。
没有开启保护:
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RWE 0x10
开启NX保护:
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
开启保护之后,GNU_STACK从RWE变成了RW,栈只能进行读写,而不可以执行
RELRO:还是进行readelf -l进行查看,发现正常,没有开启RELRO中,缺少了GNU_RELRO类型,而且全部开启,只有读的权限,没有写入和执行的权限,这是RELRO全开的情。
RELRO全开: GNU_RELRO 0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
0x0000000000000250 0x0000000000000250 R 0x1
RELRO半开: GNU_RELRO 0x0000000000002de8 0x0000000000003de8 0x0000000000003de8
0x0000000000000218 0x0000000000000218 R 0x1
RELRO关闭:却是GNU_RELRO类型。
STACK(canary):这个是无法看出的,这个的作用在函数运行之前,插入一个cookie信息,如果你溢出的时候,往往会直接把插入的cookie覆盖掉,从而来保证,缓冲区是否被溢出区别动调的时候会额外到一个地方来确认,是否被覆盖。


左边是未开启canary的状态,是一直直接往下,返回退出,但是右边是开启canary的状态,存在一个je跳转来验证,确定这个函数的栈没有问题的情况下,才会跳转到正常退出这个函数的位置,否则就进入异常处理阶段,报错退出。
PIE:在PIE未开始和开启的话,能在debug调试器中进行分析是否开始,就是多开几个窗口,看看咱们去对这个main函数进行下断电,如果下断点的时候,main函数的地址变了,那么就是说明,这个开启了pie,如果一直不变,那么就是没有开启pie,这是本质的一个区别。
未开启pie:

总结

PWN的保护其实很重要,知道了保护机制,就很可能推出这道题想要考你什么点比如NX没开,可能要考你ret2shellcode,PIE没开,RELRO半开,NX开启,金丝雀未开,可能要考你ret2text,根据这些可以大概的推算出,想要考你什么点,对于做题会有很大的帮助。

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_9 作业讲解

buuctf pwn babyshell wp

BUUCTF pwn warmup_csaw2016 wp

buuctf pwn ciscn_2019_n_1 wp

pwn 之 沙箱机制

攻防世界—pwn—level0