代码有点乱。凑合着看吧
一直以来对虚拟光驱的实现都很好奇,也曾想试着做一个,但查遍网上资料,基本上没找到过什么有用的。所以一直没有实现。感于这方面资料的缺少。所以准备研究一下这方面的技术。
在没有什么资料的情况下,要想研究某方面的技术,最好的办法,当然是逆向。选择作为逆向目标的软件。一定不能过大。二要不是商业软件。所以选了MINICD这款软件。这款软件用UPX压缩了。压缩后只有几百K。不过作者对UPX压缩后的软件做了些处理,使用UPX –d 是无法解压缩的。所有只有手脱了。UPX的变化壳手脱几乎可以讲是秒脱。用OD载入。一进来就是一个pushad 往下拉一拉就能看到一个popad 再往下就是一个大跳。跳到的地址就是OEP了。脱壳后发现是使用DELPHI写的。使用DELPHI写的。最好的辅助破解工具当然是DEDE。但用DEDE载入后看不到窗体信息,用EXESCOPE打开资源的时候也会报错。这是UPX压缩的东西解压后的后遗症,使用RESCOPE就可以查看到窗体的信息了。里面也有按钮的信息。利用他就可以找到点击插入按 钮的事件处理过程了。找到后就可以开始分析了。当然这篇文章是来讲虚拟光驱的实现原理的。破解过程和分析过程。在这里不作进一步的说明。会另开一篇文章来描述分析过程。破解过程就不讲了。网上到处可以找得到。在这里。只讲分析的结果。
MINICD实现虚拟光驱。有两个文件共同完成。一个是用户层的EXE文件(MINICD.exe)。一个是驱动文件(minicd.sys)这个驱动文件是由minicd.exe在创建设备失败并且发现system32\drivers\目录里没有minicd.sys文件时创建的(此文件被嵌入到了minicd.exe的内部)
其中minicd.exe 用于向驱动文件发送消息以使驱动工作。驱动则实现虚拟一个设备。在驱动没有被安装的情况下,minicd.exe也负责安装并启动驱动。
故而下面我们将分两篇文章来讲述虚拟光驱的实现原理。这篇文章只讲述用户层也就是minicd.exe里所作的事情。Minicd.sys也就是驱动里所作的事情。在下一篇作描述。
为了使你们跟着做能够实现功能,当然我们要做一些准备工作了。首先是从网上下一个 “迷你虚拟光盘”版本是1.0 在网上搜索minicd 多的很。我的这个后面加了一个皮鲁专版。运行一下。做这个的目的是让MINICD释放已经嵌入到他内部的minicd.sys. 运行后。映射一个ISO。成功后。你就会发现在 system32\drivers目录里多了一个minicd.sys文件。这个就是驱动文件。同时他还帮你安装了这个驱动。故而我们就可以直接对其进行访问。以实现虚拟一个光驱的目的了。下面我们就讲一讲如何通过程序和minicd.sys进行通讯,以创建一个虚拟光驱出来(这个也就是minicd.exe里所作的事了)。至于如何在驱动没有安装的情况进行驱动的安装我准备在讲述完通讯后再接着讲。因为目前驱动已经被你下载的minicd.exe 安装好了。
Minicd.sys 是一个典型的NT驱动文件,所以要实现和其通讯,也就是一般的和NT驱动通讯的基本步骤。当然Minicd.exe里也是这样做的。在驱动已经安装并启动的情况下,只须简单的两步即可。
Device = CreateFile(“\\.\\minicd”…….); // 使用CreateFile创建一个设备(设备名叫\\.\\minicd),这里为了说明过程不必要的参数先不列出
DeviceIOControl(Device,…inBuffer,..,outBuffer,…..); // 使用DeviceIOControl 和设备进行通讯,主要通过 inBuffer 和 outBuffer 在这里只需要使用outBuffer即可。
知道步骤后,下面结合一下。Minicd.exe的实际例子详细的对这两个过程进行描述一下。
第一步:
HANDLE device = CreateFile("\\\\.\\MINICD",
GENERIC_READ,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
0,
0);
CreateFile 是一个万能的API函数。能干很多事情。创建文件,创建管道等。当然也可以创建一个驱动。他的原形如下
HANDLE CreateFile(
LPCTSTR lpFileName, // 如果是文件就是文件名,如果是设备当然是设备名
DWORD dwDesiredAccess, // 对创建的对象的访问权限 GENERIC_READ GENERIC_WRITE
DWORD dwShareMode, // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全属性,一般传null默认就行了
DWORD dwCreationDisposition, // 对象创建方式
DWORD dwFlagsAndAttributes, // 设置一些其它属性或者标志 默认即可
HANDLE hTemplateFile // 默认即可
);
我在注释里已经大致讲了每个参数的作用。这里只作些必要的补充。
lpFileName 在这里用的是 \\.\\minicd 这个是设备的符号名。是在驱动里使用IoCreateSymbolicLink 创建的。主要用于用户层程序访问驱动用的。注:符号名称和设备名是不同的(从IoCreateSymbolicLink就可以看出来,其实符号名就类似于设备的一个链接,这个链接是给用户层程序使用的)。当然这里不明白也不要紧。在这里只知道怎么用的就行了。
dwDesiredAccess 访问权限可以是 GENERIC_READ(只读) GENERIC_WRITE(只写)
或者 GENERIC_READ | GENERIC_WRITE 读写
dwShareMode 共享模式 主要是用于其它程序也使用这个对象时设置共享的方式。这里设置的是FILE_SHARE_READ。也就是其它进程仍然可以对这个设备进行读。
lpSecurityAttributes 标志默认就可以了。只要用于设置权限用的。很多API都用到这个。例如要实现关机,你就必须讲你当前进程的安全属性设置成允许关机的权限才可以实现关机。否则普通的用户层进程的安全权限是无法使用ExitWindows这个函数的。
dwCreationDisposition 创建方式,设置一些诸如文件不存在是创建还是返回错误等的一些方式而已。当然如果是创建设备的不存在刚才讲的这些问题。设置成OPEN_EXISTING 即可。就是如果驱动没有安装就返回失败。
其它两个默认即可,是些额外的属性和标志设置。对于创建文件,这里可以设置诸如只读等的额外属性或者标志等。和dwDesiredAccess的区别是dwDesiredAccess的权限是运行时的权限。而这里设置的是文件自身的权限。当然对设备来讲不需要这两个属性。传0即可
设备创建成功后,就要和 设备通讯了。和设备通讯(就是和驱动通讯)要用到DeviceIOControl (主要用于一些自定义的或者扩展的操作,一般性的读和写有专门的例程)在minicd.exe 里这个参数的调用是这样的
WCHAR outBuffer[501] = L"MF:\\DevelopeTool\\SQL2000SP4.ISO";
DWORD bufferSize = 0x3EA;
DeviceIoControl(
device,
0x22008,
NULL,
0,
outBuffer,
sizeof(outBuffer),
&bufferSize,
NULL)
这个函数的原型
BOOL DeviceIoControl(
HANDLE hDevice, // 设备句柄,也就刚才CreateFile的返回值
DWORD dwIoControlCode, // 控制码 驱动可以接收到
LPVOID lpInBuffer, // 输入缓冲
DWORD nInBufferSize, // 输入缓冲大小
LPVOID lpOutBuffer, // 输出缓冲
DWORD nOutBufferSize, // 输出缓冲大小
LPDWORD lpBytesReturned, // 这是一个返回值,返回驱动存储在输出缓冲里的数据的大小
LPOVERLAPPED lpOverlapped // 直接设null 保留用的吧
);
hDevice 注释里很明白了
dwIoControlCode 用户层发给驱动程序的指令码(发给IRP_MJ_DEVICE_CONTROL例程),可以自定义。驱动程序根据传过来的指令码,进行相应的操作。在minicd.exe 里这个指令码 是通过 移位和或操作得到的。其实就是CTL_CODE 宏。在这里就不用管那么多了,直接传 0x22008即可。
lpInBuffer lpOutBuffer 用于和驱动交换数据用的。这里只用到了lpOutBuffer. LpOutBuffer 指向的是一个LPVOID。你需要传的是一个UNICODE的字符串即可。字符串如下面的样子(前面已列出)
WCHAR outBuffer[501] = L"MF:\\DevelopeTool\\SQL2000SP4.ISO";
第一个字符M。是你求出来的目前可用的盘符。也就是你要映射的盘符。当然这个是要你自己去得出来的。不可能写死在这里,我只是测试用的而已。这方面的资料很多,你可以上网去查。紧跟后面的就是你要映射的ISO文件的全 路径 。 其实这样的输入应该放在lpInBuffer里,但这里放在了lpOutBuffer 感觉不是很爽。但不影响,因为,在驱动里无论是 inBuffer和outBuffer都是一视同仁的。都可以往这两上缓冲区里,写或者 读
如果这个执行成功后。光驱已经被虚拟出来了。
分析minicd.sys得知 在IRP_MJ_DEVICE_CONTROL例程里接收到的指令码是0x22008时。做的事情其实很简单。
1.使用IoCreateDevice 创建一个名为 minicd+盘符索引 的设备 例如 如果是M盘则 创建了个minicd12的设备 其中12 就是(M-A)的值。设备类型为 FILE_DEVICE_UNKNOWN 型的,一般虚拟设备都是这种类型
2.使用 IoCreateSymbolicLink 创建一个 符号 链接 和 这个设备关联起来 。 符号链接 的名字 为 你要创建的盘符名。
如要创建 M盘,则符号名为 M 。 当使用此函数时,符号名若为 A-Z 则系统会自动通知 资源管理器 创建相应的盘符。
当然 这个函数是在驱动层使用的。在用户层有一个 和他类似的函数,也可以实现同样的功能。就是
DefineDosDevice(
__in DWORD dwFlags,
__in LPCTSTR lpDeviceName,
__in LPCTSTR lpTargetPath
);
其中第二个参数 就是 符号名 (如果符号为 A-Z 则同样的系统会自动通知资源管理器创建相应的盘符)
其中第三个参数 是设备名
经过上面两步。就已经创建了 相应的 盘符,而且系统会将对于此盘符的所有操作指令(例如 读数据,写数据等等)全部发给 他所关联的设备。也就是前面讲的诸如minicd12 这样的设备。而发给设备其实就是发给创建设备的驱动(一个驱动可创建多个设备)。所以系统会把对于新创建的盘符的所有操作都发给由minicd.sys所对应的驱动对象。并准确分发到驱动的各个例程。因此,此时只要在minicd.sys的各例程中对所发过来的操作指令进行模拟。并按规定返回相应的数据即可实现虚拟了。例如。当对盘符进行双击查看等操作时,其实,系统会将此操作包装成一个IRP。并发送给 minicd.sys里的 IRP_MJ_READ 例程。这样你只须在这个例程里,分析 IRP 里的 指令,并对 ISO文件进行操作,并将得到的结果返回给操作系统就行了
虽然到这里,虚拟的工作已经做完了。但要想正常工作还得使用
BroadcastSystemMessage 向系统广播消息
在minicd.exe 的实际调用是这样的
DEV_BROADCAST_VOLUME minicd;
minicd.dbcv_size = 0x00000012;
minicd.dbcv_devicetype = 0x00000002;
minicd.dbcv_reserved = 0x00000001;
minicd.dbcv_unitmask = 0x00001000;
minicd.dbcv_flags = 0x0002;
PDEV_BROADCAST_VOLUME pminicd = &minicd;
BroadcastSystemMessage(
BSF_IGNORECURRENTTASK,
BSM_ALLCOMPONENTS,
WM_DEVICECHANGE,
0x8000, //DBT_DEVICEARRIVAL
(LPARAM)pminicd);
这个函数的原型
long BroadcastSystemMessage(
DWORD dwFlags, // 标志值,用于设置一些广播方式
LPDWORD lpdwRecipients, // 指定消息的接收者
UINT uiMessage, // 消息号(消息类型)
WPARAM wParam, // 扩展消息的信息
LPARAM lParam // 一般根据wParam的不同传不同
);
这个函数其实没什么好讲的。和经常使用的SendMessage 等消息处理函数没什么区别。在这里只根据实际的参数作些说明。
在minicd.exe 里使用这个函时
dwFlags 传的是 BSF_IGNORECURRENTTASK 就是消息不发给自己。
lpdwRecipients是 BSM_ALLCOMPONENTS 就是向所有的组件广播消息,主要是向 WINDOWS资源管理器广播了。
uiMessage是 WM_DEVICECHANGE 也就是讲消息类型是 设备改变 消息。这个消息是系统的内部消息。
WParam 是 0x8000 你也可以直接写DBT_DEVICEARRIVAL.不过要引用一下dbt.h头文件 就是设备已经到达的意思。
LParam 是一个 DEV_BROADCAST_VOLUME 类型的指针,DEV_BROADCAST_VOLUME 类型是对 DEV_BROADCAST_HDR的扩展。定义如下
typedef struct _DEV_BROADCAST_VOLUME {
DWORD dbcv_size;
DWORD dbcv_devicetype;
DWORD dbcv_reserved;
DWORD dbcv_unitmask;
WORD dbcv_flags;
} DEV_BROADCAST_VOLUME,
*PDEV_BROADCAST_VOLUME;
DEV_BROADCAST_HDR 的定义如下
typedef struct _DEV_BROADCAST_HDR {
DWORD dbch_size;
DWORD dbch_devicetype;
DWORD dbch_reserved;
} DEV_BROADCAST_HDR,
*PDEV_BROADCAST_HDR;
其中dbch_size 指示这个结构的大小。如果是用在扩展结构中,其实就是此结构大小加上扩展结构其它部分的大小之和,其实也就是扩展结构的大小了。如在这里
DEV_BROADCAST_VOLUME minicd;
minicd.dbch_size = 0x00000012; 是 0x12 也就是18个字节。这明显是 DEV_BROADCAST_VOLUME 的大小。
dbch_devicetype 是指 设备类型 。使用的是什么扩展结构设备类型就是什么。在这里使用的是 DEV_BROADCAST_VOLUME 故而是 DBT_DEVTYP_VOLUME(0x2)
dbch_reserved 保留使用的。这里 设置 为 0x1
这是 DEV_BROADCAST_HDR 的结构介绍,所有的扩展其的结构的第一项必须是 DEV_BROADCAST_HDR 所以我们看 DEV_BROADCAST_VOLUME的定义的前三项和 DEV_BROADCAST_HDR 是一样的。只是名字不同而已。
下面再介绍一下。DEV_BROADCAST_VOLUME 扩展的两个属性。看 DEV_BROADCAST_VOLUME的第四项。
dbcv_unitmask 这一项很重要。具体的意思不好解释。应该类似于网络里的掩码。这是一个整型值。也就是说有32位。从右往左数。每一位都映射一个盘符。如果右边第一个位为0
的话,就是指A盘。依次类推,第二个就是B盘。
还记得这句话不
WCHAR outBuffer[501] = L"MF:\\DevelopeTool\\SQL2000SP4.ISO";
从前面介绍知道,我们要映射的盘符是M盘。那么自然应该是在 M减去A 的那个位上是1. 那么实际上也就是 1 << (M-A) 也就是 2 的 (M-A) 次幂。所以为了测试,当时并没有使用公式计算。只是硬写了一个 minicd.dbcv_unitmask = 0x00001000 进去。 很明显 0x1000 就是 2 的 (M-A)=12 次幂。
最后一项 dbcv_flags 可以有两个选择
DBTF_MEDIA
0x0001
DBTF_NET
0x0002
这里选择 DBTF_NET 直接 写 0x0002 如果要写DBTF_NET 需要引用 dbt.h
好了。到了这里。看一下,是不是M盘已经被映射出来了。当然如果你的M盘已经被占用了。你就得换一个盘作测试用了。如果换盘的话。知道怎么换吧。前面讲的已经很清楚了。只须在DeviceIOControl的lpOutBuffer里的UNICODE字符串的第一个字符改成相应的盘符名就行了。当然还要记的是要把 BroadCastSystemMessage 的参数lParam DEV_BROADCAST_VOLUME 结构的 dbcv_unitmask 重新计算一下。否则映射会不成功的。
通过他们的对于实际传进去的参数的介绍。其实这个函SHU在这里的作用已经很明显了。就是向系统中除了自己以外的所有的程序啊,驱动啊,反正所有的对象广播一条消息。就是讲,有设备改变(WM_DEVICECHANGE)了,是什么改变呢。是设备已经到达(DBT_DEVICEARRIVAL)了.那么他的具体信息呢。就是 到达的是M盘,是DBT_NET网络型驱动器等(DEV_BROADCAST_VOLUME结构里的项)。这样WINDOWS资源管理器也会接到这样一个消息。他就会在我的电脑里建立一个驱动盘符了
好了。到这里,在驱动(minicd.sys)已经被安装和启动的情况下已经可以创建一个虚拟光驱了。
虽然有点成就感,但目前还有很多工作没有做。例如。如果是在一台新机子上,也没有运行MINICD.EXE 这样驱动不就没有安装也没有启动了。这样我们不就不可以和设备进行通讯。当然不用怕。后面,我们继续讲。怎么在没有驱动的情况下。动态安装和启动这个驱动(minicd.sys)。
除了安装和启动驱动,还有一个就是映射了怎么去除映射的问题也留在后面补吧。现在已经快一点了。最近得早点睡。工作要紧啊。要不然明天又没精力了。