穿透ghost种植木马

  • 发表于
  • 周边

标 题: 【原创】穿透ghost种植木马
作 者: yirucandy
时 间: 2014-07-25,17:21:33
链 接: http://bbs.pediy.com/showthread.php?t=190489

好不容易过掉各种杀毒软件、防火墙种上的木马,不能当用户使用ghost镜像还原系统后就失效了。本文描述如何把木马写入ghost镜像中,当用户还原后木马仍然启用。

1、前言:

GHO文件系统架构:

GHO文件格式非常复杂。设计者在最初考虑到多种情况,例如是否分卷(分为多个文件),是否支持网络安装(多台机器使用网络同时安装),是否录制在磁带上(要求GHO文件具有流格式),可以支持多少种文件系统等等。这使得GHO文件格式具有分层特性。

最下层的格式为GHO基础流。GHO基础流承载磁盘数据。基础流可以为压缩流或者非压缩流,根据不同的要求,压缩流又分为快速(FAST)压缩流和高比例(HIGH)压缩流。GHO基础流用自定义的结构,将磁盘上的冗余空间排除掉。实验发现,基础流承载的数据,都是磁盘中实际使用的数据,即在文件系统中登记的数据(文件或者目录结构等),而其它空白数据,比如已经删除的文件数据等等,并未在基础流中记录。由于涉及到文件系统,基础流并没有完全脱离上层流的映像。这是GHO文件格式难以破解的第一个原因。

基础流上面是GHO文件系统流。通过IDA反汇编分析可以发现,GHO基础流上面的文件系统流是真正的文件系统操作。比如FAT32流使用的逻辑就是在文件系统中处理FAT32格式的逻辑,NTFS使用的逻辑也是真实的NTFS逻辑。这也意味着,对于Linux使用的EXT3等文件系统,GHO并没有文件系统层次上的支持(高版本可能已经开始支持了)。由于文件系统众多,GHO也不能完全支持一切文件系统,高层文件系统和GHO基础流之间还有相互依赖的关系,这是GHO文件格式难以破解的第二个原因。

GHO文件提供非重整格式。原则上讲,应当是对磁盘的完全无损拷贝,但是试验中测试发现,即便使用非重整格式,生成的GHO文件也被修改过。其修改原则仍然依赖于GHO基础流。比如制作一个NTFS分区的非重整拷贝,根据NTFS文件系统格式寻找二进制位置,仍然不能准确定位,也就是说,非重整模式并不是对磁盘的真正的无损拷贝。这是GHO文件格式难以破解的第三个原因。

经过一段时间研究,发现直接破解GHO文件格式,必须首先破解GHO基础流格式,然后破解基础流和高层文件系统比如FAT32和NTFS之间的关系,最后还要保证附加信息准确的翻译,这个工作有太多的不确定性。所以最后决定使用Symantec出品的GhostExp作为读写工具,进行GHO文件的读写,如下图:

穿透ghost

也就是说,借用现有的GhostExp读写功能,而不是通过分析文件系统格式来进行读写。这样做的好处在于,可以比较容易的实现GHO的压缩和非压缩,磁盘和分区模式,FAT32/NTFS等文件系统中的文件的读写,即只要Symantec支持的格式,我们都能操作。

2、插入代码并控制GhostExp的原理:

Symantec 出产Ghost,必须向用户提供GHO文件的脱机修改功能;但是Symantec拒绝公开任何GHO文件格式的信息,所以Symantec必须自己提供一个修改该工具。这个工具必须能够修改该GHO文件中的内容,准确识别各个GHO文件版本,准确处理压缩非压缩和不同文件系统,准确处理分卷文件等等。这个GhostExp正是我们需要的功能。然而因为某种原因,用户只能手工操作GhostExp,进行文件的增删改操作。GhostExp不提供外部调用或者命令行模式,所以,修改GhostExp以提供命令行模式,进而允许其它进程调用,就成了这个项目的主要工作了。

GhostExp是一个MFC程序,由VS2005开发,我获得的版本为Debug版,有少量Debug符号可以使用,而且IDA 对于MFC 架构的识别也比较好,这对分析较有帮助。

修改一个现有的可执行文件,并使得它提供特殊功能,有很多限制。最简单点的方法,是在这个可执行文件的引入表中增加一个DLL,在适当的位置调用这个DLL中的特定函数,在DLL函数中再实现需要控制可执行文件的相关代码。也就是说,DLL将会分析命令行输入,按照命令行输入的要求,模拟人的操作,产生需要的效果。

添加的DLL为GWDLL.DLL。DLL讲先于Application的执行。在DLL启动时,调用Initialize函数,使用
LPSTR cmd = ::GetCommandLineA();
获取当前命令行内容,对命令行内容进行分析,获得参数列表。稍后会创建一个线程,进行手工操作的模拟。

为什么选择模拟操作,能否直接调用GhostExp中的特定代码,实现直接读写?

根据分析,GhostExp为提高性能,在文件系统格式分析和GHO文件读写上使用了多线程。比如启动GhostExp并打开GHO文件的时候,左边显示文件目录树的过程,就是由文件系统加载线程实现的。

对于当前打开的GHO文件,文件系统加载线程(GhostImageLoadingThread,参看IDB文件)负责分析当前GHO文件,并建立内部的目录树表示。这个目录树表示再被主线程转化为TreeNode表示,最后显示出来。

如果可以准确的使用内部目录树表示,那么也可以绕过GUI操作。可是内部目录树表示存在于不确定位置,而且结构随着文件系统不同而不同,所以只能使用GUI层操作TreeNode的方法定位特定目录和文件。

为了找到TreeView和ListView,在CWinApp::OnInitialize的某个早期位置,生成主窗口之后,修改代码指向GWDLL中的Connect方法,传递CFrameWnd指针,从此指针+0x20处获得主窗口句柄。

代码:

通过FindWindowEx找到TreeView和ListView的句柄,并子类化主窗口。
代码:

为什么要子类化主窗口?

添加删除等操作的结束,需要有一个通知。模拟线程必须得到这个通知,才能知道何时退出命令状态,也就是说,知道GhostExp完成了需要的操作以后,可以安全关闭GhostExp。内联线程并使用WaitForSingleObject是首选的方法,但是实验表明,这个方法导致不明原因的死锁。GhostExp线程在完成操作之后,对主窗口发送一个值为0x48C的消息,这个消息标志着操作结束(进而在状态条上显示“完成”),我们通过子类化主窗口就可以截获这个消息,进而得知操作完成,然后才能安全的结束进程。
子类化Proc定义如下:
代码:

此后,GhostExp启动文件系统加载线程。

我们在启动文件系统加载线程的函数的尾部手工插入代码,使得调用转移到GWDLL中的HOOK函数。
代码:

这个函数启动MainThread线程,这个线程就是模拟手工操作的线程。MainThread等待加载线程结束之后,进行手工操作模拟。
代码:

MainThread完成ProcessGhostCommand之后,直接PostMessage(WM_QUIT)退出。不用PostQuitMessage的原因在于,PostQuitMessage将会把WM_QUIT推送到MainThread线程的MessagePool之中,而这不是我们需要的。我们要把这个WM_QUIT推送到主窗口对应的主线程之中,保证主线程退出,即保证应用程序退出。

模拟手工操作是怎样实现的?
由于GhostExp内部工作原理比较复杂,直接调用内部函数的实验经常得到死锁的结果。使用GUI操作模拟显然更为安全可靠。具体的说,就是用MainThread模拟用户点击左边的TreeView中的节点,展开并定位到特定目录,然后模拟用户选择右边的ListView中的项目,实现添加、删除、提取等操作。

无论添加删除还是任意操作,首先要定位到目标位置,也就是说,保证左边的TreeView展开到特定目录,然后保证右边显示了需要显示的项目,在必要的时候(比如获取或者删除)要选定右边的项目。这个过程称为目标定位(LocateTarget)。具体工作方式和原理,请参阅LocateTarget,FindTreeNode等函数(在GWDLL项目,GWDLL.CPP文件中)

找到特定文件或者目录之后,MainThread会根据最开始记录的命令行参数,选择特定的操作。添加和替换操作实际上模拟了GhostExp的粘贴操作。通过Spy++可以获取GhostExp右键菜单中的粘贴、复制删除等MenuID(由vs打开即可看到)。通过发送WM_COMMAND消息就可以发送这些命令到主窗口,进而得到执行。

需要注意的是,由于模拟复制和粘贴操作会使用剪切板,在一个极短的时间里面,剪切板中可能包含文件路径。

此外,为了保证命令直接执行而不弹出任何提示(因为这里不可能有用户交互),删除、替换和添加等操作的提示对话框都必须被屏蔽,这是通过手工二进制修改该完成的。

如何判断命令结束并退出:
使用SendMessage发送WM_COMMAND命令并携带MenuID,仅仅是触发命令开始执行。因为GhostExp使用线程处理添加删除等操作,所以必须等待线程完成,才能确认命令完成。先前子类化主窗口,就是为了这个目的。

代码:

当命令发送之后,MainThread将进入循环等待状态,直到发现退出信号才能退出。这个退出信号是由子类化过程发送的,这就保证了操作的结束通知一定被收到。

对GhostExp的二进制改动的细节:

(1)引入表修改
增加 GWDLL.DLL,引入函数 Connect, Hook, Block。 其中Block函数暂时未使用。但是为了保证其它二进制修改正常工作,请保证引入。
建议使用LordPE或者PEeditor添加引入表项目。

(2)指令修改
代码:

3、如何使用修改后的GhostExp:

修改后的GhostExp,引用了GWDLL.DLL,所以必须和GWDLL.DLL同时出现,或者GWDLL.DLL存在于可以找到的位置。修改后的GhostExp,正常情况下和普通GhostExp的使用没有区别,只是它不会将自己注册为GHO的打开方式,也不会显示Splash画面;删除特定文件不会有提示,发现重复文件则直接替换。

使用修改该后的GhostExp的标准场景为:在木马或者其它应用程序中使用CreateProcess启动修改后的GhostExp,设定标志使得GhostExp不显示界面。
代码:

创建进程后,等待进程终止,并获得返回代码。

如果返回代码为非负数,则执行成功;否则执行失败。
调用CreateProcess的时候传入的commandline参数,应具有如下格式:
“GhostExp全路径” “GHO文件全路径” “操作符” “目标路径” “源路径”
每个参数必须由引号括起来,以防路径中的空格打乱参数位置。

例如要种植的木马实现Ghost穿透,先拷贝Ghostexp.exe和GWDLL.DLL到被控端C盘下,然后调用RunProcess函数,并传入如下参数:C:\Ghostexp.exe“C:\ghost.gho ” “+” “C:\Documents and Settings\All Users\「开始」菜单\程序\启动\muma.exe” “C:\muma.exe”。这样当用户还原ghost时,muma.exe就回存在于自启动目录了。

测试程序:
GW.EXE 为修改后的GhostExp的使用示例,GhostExp.EXE以及GW.DLL 应在同一目录中。

GW.exe 使用以下格式调用:
Gw.exe “gho 文件完整路径” “操作” “目标路径” “源路径”
注意!必须在所有的参数上都使用英文半角引号。因为大多涉及到路径,而路径中可能存在空格。

如果操作成功,返回非负整数,否则返回-1或者其它负数。

其中操作分为:

  • + 添加文件,如果文件已经存在则添加失败
  • -删除文件,如果文件不存在则失败
  • *替换文件,如果文件不存在则添加,否则替换
  • ?测试存在,如果文件存在则返回成功
  • #获取内容,如果文件存在则获取其中的内容,否则返回失败

以上操作都针对文件,添加、替换和获取不针对目录,即不支持目录级别的添加、替换、获取操作。

目标路径:在Gho文件中的路径称为目标路径。由于Gho文件不记录盘符,在GhostExp中也无法看到C:、D:等盘,目标路径给出的盘符是一个人为定义。其中C:指Gho文件中的第一个分区。如果有多个分区,则D:指第二个分区,依次类推。所有操作都涉及目标路径。

源路径:在真实文件系统上的文件路径为源路径。源路径用于添加,替换和获取。在获取操作中,源路径实际上是文件被获取到的位置,相当于常规意义上的目标路径。

下图为在命令行中调用gw.exe。

穿透备份种木马

下图为命令执行成功后,muma.exe被植入的截图:

穿透木马后门

源码及文档:http://pan.baidu.com/s/1eQtK77s