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
2
3
4
5
6
7
8
9
10
11
#注释头

mov rax,fs:28h
mov [rsp+28h+var_20], rax
------------------------------------------------------
mov rax, [rsp+28h+var_20]
xor rax, fs:28h
call __stack_chk_fail
------------------------------------------------------
v_canary = __readfsqword(0x28u);
return __readfsqword(0x28u) ^ v_canary;

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字节的距离处:

img img

这里可以看到,第一个是main函数栈,第二个是在main函数中通过pthread进程创建并且调用的函数栈,两者相差将近0x700000000这么远,完全不是正常的函数调用相差的栈距离。同时在该函数中rbp指向的始终是0000(全是),该函数结束后会先跳转到libc中的libpthread来恢复栈。

▲64位的tcbhead_t结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#注释头

typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;//即为canary,fs:28h处
uintptr_t pointer_guard;
...
} tcbhead_t;