pwn保护措施
一、RELRO: (ReLocation Read-Only)
1.功能:解决延迟绑定问题,将符号重定向表设置为只读,或者在程序启动时就解析并绑定所有动态符号,防止got表被篡改。
2.表现形式:
pwn checksec检查为RELRO: Full RELRO,Partial RELRO保护
3.保护等级:
(1)Partial RELRO:
①一些段(.dynamic、.got等)在初始化后会被标记只读,.got段代表无plt指向的got表,也就是没有发生延迟绑定,没有被外部extern导入的函数被标记为只读。
②但是有被外部extern导入的函数,发生延迟绑定的函数,在.got.plt段,仍然可以篡改got表,这里的got表是.got.plt段。
(2)Full RELRO:直接禁止延迟绑定,无.got.plt段,只有.got段,且被标记为只读,无法修改,无法篡改got表。
4.绕过方法:有啥绕过方法,不改got表不就完了。
二、FORTIFY_SOURCE:
1.功能:将敏感函数如read, fgets, memcpy, printf等等添加保护,替换为__read_chk, __fgets_chk, __memcpy_chk, __printf_chk等函数。这些带了chk的函数会检查读取/复制的字节长度是否超过缓冲区长度,检查诸如%n之类的字符串位置是否位于可能被用户修改的可写地址,避免了格式化字符串漏洞的出现。(如直接%7$x)
2.表现形式:带有chk的函数,checksec可以检测到
3.保护等级:
(1)-Ol -D_FORTIFY_SOURCE=0:关闭
(2)-Ol -D_FORTIFY_SOURCE=1:替换get,memecpy等,格式化字符串仍然可用
(3)-Ol -D_FORTIFY_SOURCE=2:格式化字符串也受到限制:
%n,%3$x不可用(跳过了1,2不可用)
%n$只能从1开始才可以:%1$x%2$x可用
4.绕过方法:
(1)利用整数溢出漏洞,篡改_IO_FILE结构中的_IO_FLAGS2_FORTIFY为0,从而关闭FORTIFY_SOURCE对%n的检查。之后再利用任意地址写,将nargs篡改为0,从而关闭对%n$的检查。
具体论文:
http://phrack.org/issues/67/9.html
https://jackgrence.github.io/phrack-67-9/
https://www.vnsecurity.net/research/2012/02/16/exploiting-sudo-format-string-vunerability.html
都得挂vpn才能看
三、NX:NX enabled (No execute bit)
1.功能:将内存页以数据和指令两种方式进行了分类。被标记为数据页的内存页(如栈和堆)上的数据无法被当成指令执行,即没有X属性,这样就会导致shellcode失效。除了.text之外,其余段,数据(stack、heap等)都不可执行。
2.表现形式:pwn checksec检查
3.绕过方法:ROP、Onegadget、ret2libc等等
四、ASLR(Address Space Layout Randomization)和PIE(Position Independent Executable)
1.功能:该技术是一个针对堆、栈、libc地址、代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,每次加载程序都会随机化这些地址。
2.表现形式:checksec检查,gdb-peda中输入aslr。
3.不同:
(1)ASLR是系统层面的地址随机,针对栈(stack),libc加载地址,堆(heap),没办法在编译时选择是否开启ASLR,不同系统设置下都不一样,只有在程序跑起来,远程调试才能看到随机化。可以在终端输入:
cat /proc/sys/kernel/randomize_va_space
查看当前系统的随机化等级,也可以打开这个文件进行修改等级。共分为3个等级:
A.0:即关闭ASLR
B.1:部分开启,随机化stack和libc加载地址,不随机化heap
C.2:完全开启,随机化stack、libc加载地址、heap
▲注:
由于是系统层面的,做pwn题时肯定是不知道远程的系统环境到底有没有开启ASLR,一般都是默认开启,且都是2等级。做题做到现在没见过哪个题目告诉不开启ASLR的。另外在gdb-peda中查看aslr都是默认为off,因为gdb默认关闭ASLR,可以在gdb-peda中输入aslr on来打开ASLR。
(2)PIE是gcc编译功能时的选项,针对代码段(.text)、初始化数据段(.data)、未初始化数据段(.bss)的防护技术。开启之后,以上提到的都会随机化,同样也是远程调试跑起来才能看到效果。需要注意的是,只有开启了ASLR之后,PIE才能被正常使用。
4.绕过方法:
(1)利用vsyscall或者vdso来滑过一段栈空间,从而将eip挪移到栈底下方我们想要的地址处。
(2)利用栈溢出和打印函数的参数,修改劫持rbp使得利用rbp寻址的打印函数的参数指向栈上其它位置,通过爆破来寻求泄露Libc地址。
(3)利用PIE机制,爆破倒数第四位可以跳转到同一个内存页中的任意函数。
五、Stack Canary/Stack cookies
1.功能:函数退出时,将保存在栈rbp-0x08处的canary和tcbhead_t结构体中的stack_guard来xor操作,若检查到改变则代表执行了栈溢出漏洞,程序调用__stack_chk_fail,打印错误信息,并崩溃。
2.表现形式:
1 | #注释头 |
3.不同类型的canary:
(1)Terminator canaries:
最常见的canary,末尾为\x00,保存在rpb - 0x08的位置。
(2).Random canaries:
在程序初始化时随机生成,保存在一个相对安全的位置。通常由/dev/urandom来生成,有时也使用当前时间的哈希值。
(3).Random XOR canaries:
通过一个随机数和函数栈中所有控制信息,返回地址等异或运算得到的,这样当函数栈中的随机数或与之相关的控制信息,返回地址被修改了,都可以检测到。
2.绕过方法:
(1)有循环时,32或者64位程序下都可以逐字节爆破绕过。
(2)可通过printf字符串漏洞来泄露(%p.%p.%p….)。
(3)通过打印栈上数据的打印函数栈溢出连上canary泄露出来。
(4)当程序读入flag进入内存时,利用函数__stack_chk_fail,加上足够长度的栈溢出覆盖argv[0]为程序中保存flag的地址。这样当__stack_chk_fail运行时就会打印出argv[0]中地址上对应的内容,也就是flag。
(5)由pthread创建出来的线程函数中如果有足够长度的栈溢出,可以直接覆盖canary来源tcbhead_t结构体中的canary和栈中的canary为同一数值,这样检查仍旧通过。
(64位中为fs:[28h],32位中为gs:[14h])
▲长度一般为rbp+2000左右,不同的Libc版本都不太一样,需要调试才能知道。原因是通过pthread出来的线程函数栈会被安置到与TLS相差约2000字节的距离处:
这里可以看到,第一个是main函数栈,第二个是在main函数中通过pthread进程创建并且调用的函数栈,两者相差将近0x700000000这么远,完全不是正常的函数调用相差的栈距离。同时在该函数中rbp指向的始终是0000(全是),该函数结束后会先跳转到libc中的libpthread来恢复栈。
▲64位的tcbhead_t结构体:
1 | #注释头 |