U盘驱动配置工具开发


1 背景

手里有一个EAGET的U盘,想做一个类似于某宝上卖的加密U盘,基本原理是使用量产工具将 其格式化为CDROM加U盘公开分区和隐藏分区模式,在CDROM中放加解密工具,U盘插上电脑后 可以运行CDROM中的程序来挂载隐藏分区。

遇到的问题是EAGET的USBTOOLBAR工具是建立在WINDOWS系统设置正常的前提下的,如果系统 USB存储驱动被恶意程序修改,比如加上了过滤驱动,会使程序从U盘上读取到数据不是期望 的数据,导致无法显示隐藏盘。

解决的办法只有一种,就是想方设法地让系统针对我们的U盘使用原来正常的驱动。本文的 重点是如何开发一个小工具,解决USB存储驱动设置被篡改的问题。学习和尝试的过程就不 写了,以下主要记录相关知识的要点。

2 驱动的安装和应用

U盘插入系统后驱动安装和设备启动的过程在Microsoft 文档 Adding a pnp Device to a Running System 一文中讲得非常清楚,大概如下:

  • 总线驱动程序发现设备,将通知pnp 管理器
  • Pnp 管理器与总线交互收集设备信息,配置设备并将相关信息存入注册表
  • 内核模式中的Pnp 管理器与用户模式的pnp 管理器和安装组件协调,查找并安装前端过滤 驱动、功能驱动以及后端过滤驱动
  • 分配资源,启动设备

值得注意的是系统为设备匹配驱动的过程,如果U盘在系统上使用过,在注册表中 HKLM\System\CurrentControlSet\Enum\USBSTOR 路径下会有记录,其中包括设备实例所需 的驱动、配置及驱动信息,系统会直接使用这里的设置为U盘匹配驱动,只有当注册表中找 不到U盘实例的信息时才会为其安装驱动。驱动安装的过程在 Microsoft 文档:如何选择驱 动程序中有详细的描述。

而恶意程序修改驱动配置则主要是加入自己写的过滤驱动,其配置位置则在注册表 HKLM\System\CurrentControlSet\Control\Class 下关于USBSTOR Class Guid 项。

3 方案选择

为了不使用系统自动为U盘安装的驱动,则要改变应用于U盘实例的配置及其安装类(错误配 置的位置)。有两种方案,其一是专门针对某U盘设计一个驱动程序,在U盘插入系统后进行 安装,因为驱动的INF文件中可以配置自定义的安装类,即可绕过恶意配置的拦截;其二是 修改当前驱动配置,即前文提到注册表项,直接绕过恶意配置,当然同样需要添加自定义的 CLASS。这里之所以不直接修改{4D36E967-E325-11CE-BFC1-08002BE10318}项的原因是为了 最大限度保持系统稳定,以避免造成其他磁盘不可用。

方案一是基于系统会选择最为匹配的驱动的基础上,方案二则是基于注册表其实是系统查找 驱动的第一位置。

方案一 的优点是通过管理员权限即可安装驱动,缺点是首先要针对自己的U盘准备磁盘驱 动及INF文件,其次是要通过手动更新驱动(不论是通过设备管理器还是通过编写程序), 再次安装设备驱动的过程比较长。此方案经过简单实验论证,参照libusb 项目,应该是可 以成功的。难度不高,主要是INF文件的编写,微软文档INF文件有详细的介绍。

方案二 的优点是速度快,不需要用户过多的操作,缺点是使用程序直接操作注册表关键 位置需要SYSTEM用户权限。此方案通过手动修改注册表进行验证可行。

综合比较,第二案更加适合制作加密U盘的应用场景。

4 方案设计

由于与系统高度相关,选择Windows 编程常用的C/C++语言。据前文所述,难点是提升程 序操作权限。

4.1 提权工作服务程序

由于需要System用户才可以操作相应注册表项,所以需要想法提升程序的权限,参照网上可 查的at 漏洞法、超级Cmd法、Psexe法,在程序中适宜用创建服务的方法来提升权限,基本 原理是创建的系统服务默认是以 System 用户来执行的,查看任务管理器就明白了。

4.2 交互界面程序

如果使用系统服务的方式,与用户的交互就比较弱,为了给用户更好的体验,创建一个对话 框,对话框担负着与用户和服务程序交互任务。

5 服务程序

服务程序主要是为了解决操作注册表的权限限制问题。服务程序有自己特定的要求,主要有 3个函数。

  • StartServiceCtrlDispatcher 注册并开始服务
  • RegisterServiceCtrlHandler 注册服务的控制方法(回调)
  • SetServiceStatus 报告服务状态

5.1 基本写法

void main(int argc, char* argv[])
{ 
   SERVICE_TABLE_ENTRY ServiceTable[2];
   ServiceTable[0].lpServiceName = "ServiceName";
   ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

   ServiceTable[1].lpServiceName = NULL;
   ServiceTable[1].lpServiceProc = NULL;
   // Start the control dispatcher thread for our service
   StartServiceCtrlDispatcher(ServiceTable);
}
void ServiceMain(int argc, char** argv) 
{ 
   int error; 
 
   ServiceStatus.dwServiceType = 
      SERVICE_WIN32; 
   ServiceStatus.dwCurrentState = 
      SERVICE_START_PENDING; 
   ServiceStatus.dwControlsAccepted   =  
      SERVICE_ACCEPT_STOP | 
      SERVICE_ACCEPT_SHUTDOWN;
   ServiceStatus.dwWin32ExitCode = 0; 
   ServiceStatus.dwServiceSpecificExitCode = 0; 
   ServiceStatus.dwCheckPoint = 0; 
   ServiceStatus.dwWaitHint = 0; 

   //为服务注册控制方法 ControlHandler
   hStatus = RegisterServiceCtrlHandler(
      "ServiceName", 
      (LPHANDLER_FUNCTION)ControlHandler); 
   if (hStatus == (SERVICE_STATUS_HANDLE)0) 
   { 
      // Registering Control Handler failed
      return; 
   }  

   // 在 InitService 中做一些准备工作,如果不能完成则退出
   error = InitService(); 
   if (error) 
   {
      // Initialization failed
      ServiceStatus.dwCurrentState = 
         SERVICE_STOPPED; 
      ServiceStatus.dwWin32ExitCode = -1; 
      SetServiceStatus(hStatus, &ServiceStatus); 
      return; 
   } 
   // 向服务管理器报告服务状态,否则服务管理器会报告错误
   ServiceStatus.dwCurrentState = 
      SERVICE_RUNNING; 
   SetServiceStatus (hStatus, &ServiceStatus);
 
   // The worker loop of a service
   while (ServiceStatus.dwCurrentState == 
          SERVICE_RUNNING)
   {
      int result = DoSomething()
      if (result)
      {
         ServiceStatus.dwCurrentState = 
            SERVICE_STOPPED; 
         ServiceStatus.dwWin32ExitCode      = -1; 
         SetServiceStatus(hStatus, 
                          &ServiceStatus);
         return;
      }
      Sleep(SLEEP_TIME);
   }
   return; 
}

// Control Handler
void ControlHandler(DWORD request) 
{ 
   switch(request) 
   { 
      case SERVICE_CONTROL_STOP: 
		 OutputDebugString("Monitoring stopped.");
         WriteToLog("Monitoring stopped.");

         ServiceStatus.dwWin32ExitCode = 0; 
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         SetServiceStatus (hStatus, &ServiceStatus);
         return; 
 
      case SERVICE_CONTROL_SHUTDOWN: 
		 OutputDebugString("Monitoring stopped.");
         WriteToLog("Monitoring stopped.");

         ServiceStatus.dwWin32ExitCode = 0; 
         ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
         SetServiceStatus (hStatus, &ServiceStatus);
         return; 
        
      default:
         break;
    } 

5.2 注册表操作

注册表操作的大体流程是先打用RegOpenKey打开注册表,而后增、删、查、改等操作,最后 RegCloseKey 关闭注册表。

RegOpenKey 打开注册表,第二个参数是要打开的路径,第三个参数是返回的句柄。

lRet = RegOpenKey(HKEY_LOCAL_MACHINE, OLD_CLASS_KEY, &hKey);

RegSaveKey 备份指定项,第二个参数是要保存到磁盘上的路径。

lRet = RegSaveKey(hKey,service_class_back_file_path,NULL);

SHDeleteKey 删除注册表,不同于RegDeleteKey,它可以递归删除所有的子项及键值。但需 要提供不同的头文件。

#include <shlwapi.h>
lRet = SHDeleteKey(hKey, NEW_CLASS_GUID);

RegCreateKey 创建项,如果存在则打开它,参数同RegOpenKey

lRet = RegCreateKey(hKey, NEW_CLASS_GUID, &hKeyClass);

RegRestoreKeyA 恢复注册表项,第二个参数是文件路径,第三个参数表示是否强制恢复

lRet = RegRestoreKeyA(hKeyClass,service_class_back_file_path,REG_FORCE_RESTORE); 

RegSetValueExA 设置键的值,第一个参数是打开注册表项的句柄,第二个是其下的键名, 第三个未用,第四个表示值的类型,第五个是要设置的值,第六个是指值的长度。这里要注 意的是第四个参数是强制转换为BYTE指针的,第六个参数也是指字节长度。

lRet = RegSetValueExA(hKeyClass, "Class", 0, REG_SZ, (BYTE*)NEW_CLASS_NAME,11); 

RegQueryKey 查询键的值,第二个参数是键名,第三个未用,第四个接收值的类型,第五个 接收值,第六个接收值的长度。

lRet = RegQueryValueExA(hKey,"Driver",0,&gv_type,(LPBYTE)old_driver,(LPDWORD)&old_driver_size);

RegCloseKey 关闭句柄。

RegCloseKey(hKey);

在本案例中采取的方法是,先将原Class( {4D36E967-E325-11CE-BFC1-08002BE10318}项 ) 备份出来,而后恢复到新的项上,修改成新Class的名字,恢复过滤驱动设置为系统默认设 置,然后修改注册表中U盘枚举项下我们U盘的设置,使它使用新的Class(即系统默认设置), 以绕过恶意程序对U盘的劫持。

5.3 权限管理

在恢复注册表项时可能还会遇到权限问题,这时需要 EnablePrivilege 和 SetPrivilege 这两个函数来设置权限,他们的定义如下。

int EnablePrivilege(LPCTSTR lpszPrivilege, BOOL bEnable)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;
    if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_READ, &hToken))
        return 1;
    if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
        return 2;
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = (bEnable) ? SE_PRIVILEGE_ENABLED : 0;
    AdjustTokenPrivileges(hToken, FALSE, &tp, NULL, NULL, NULL);
    CloseHandle(hToken);
    return 0;
}

bool SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
    HANDLE hToken = NULL;
    TOKEN_PRIVILEGES tp;
    LUID luid;
    if(OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) == 0)
    {
        return false;
    }
    if (!LookupPrivilegeValueA(NULL, lpszPrivilege, &luid))
    {
        return false;
    }
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if(bEnablePrivilege)
    {
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    }
    else
    {
        tp.Privileges[0].Attributes = 0;
    }
    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL))
    {
        return false;
    }
    return 0;
}

5.4 管道通信

管道通信其实是服务端与客户端的通信,读取端作为服务端建立管道,等候连接,待客户端 连接上管道并写入数据即可读出。

#define MY_STATUS_PIPE "\\\\.\\Pipe\\mydisk_status"
#define MY_CMD_PIPE "\\\\.\\Pipe\\mydisk_cmd"

    //创建状态管道等候连接
    hPipeCmd = CreateNamedPipe(MY_CMD_PIPE,PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,0);

my_message * ReadCmd()
{
    //等待客户端连接
    if(ConnectNamedPipe(hPipeCmd,NULL)==NULL)
    {
        //如果上次连接没有使用 DisconnectNamedPipe 断开,则会出现此错误
        if(GetLastError()!=ERROR_PIPE_CONNECTED)
        {
            WriteToLog("Waiting user to connect command pipe error.\n");
            return FALSE;
        }
    }
    my_message * cmd = (my_message *)malloc(sizeof(my_message));
    //注意要在适当的地方释放内存
    if(cmd==NULL)return NULL;
    BOOL fSuccess = false;
    DWORD len=0;
    LPBYTE p = (LPBYTE)cmd;
    while(TRUE)
    {
        fSuccess = ReadFile(hPipeCmd,p,sizeof(my_message)-(p- (LPBYTE)cmd),&len,NULL);
        if(!fSuccess || p+len>= (LPBYTE)cmd+sizeof(my_message))break;
        p = p +len;
    }
    //断开连接
    DisconnectNamedPipe(hPipeCmd);
    if(fSuccess)
    {
        return cmd;
    }
    return NULL;
}

在客户端(GUI)中,采取如下方式写入命令,并等待读取完成状态。

BOOL service_exec_cmd(LONG cmd,HWND hwndDlg,LPCSTR device)
{
    if(WaitNamedPipe(MY_CMD_PIPE,NMPWAIT_WAIT_FOREVER)==FALSE)
    {
        MessageBox(hwndDlg,"Maybe the service is not ready. You can restart this application or try again.","ERROR",MB_OK|MB_ICONERROR);
        return FALSE;
    }
    hPipeCmd = CreateFile(MY_CMD_PIPE,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if(hPipeCmd == INVALID_HANDLE_VALUE)
    {
        MessageBox(hwndDlg,"Open command pipe failed.","ERROR",MB_OK|MB_ICONERROR);
        return FALSE;
    }
    DWORD dwWrite;
    my_message *msg =( my_message *) malloc(sizeof(my_message));
    memset(msg,'\0',sizeof(my_message));
    msg->p1=cmd;
    strcpy(msg->p2,device);
    if(!WriteFile(hPipeCmd,(LPBYTE)msg,sizeof(my_message),&dwWrite,NULL))
    {
        MessageBox(hwndDlg,"Send command to service failed.","ERROR",MB_OK|MB_ICONERROR);
        free(msg);
        msg = NULL;
        return FALSE;
    }

    //重置为0用来接收响应
    memset(msg,'\0',sizeof(my_message));
    //读取工作响应

    if(ConnectNamedPipe(hPipeStatus,NULL)==NULL)
    {
        if(GetLastError()!=ERROR_PIPE_CONNECTED)
        {
            MessageBox(hwndDlg,"Waiting service connect to status pipe error.","ERROR",MB_OK|MB_ICONERROR);
            free(msg);
            msg=NULL;
            return FALSE;
        }
    }

    BOOL fSuccess = false;
    DWORD len=0;
    LPBYTE lpMsg = (LPBYTE)msg;
    LPBYTE p = lpMsg;
    char log_buffer[MAX_PATH];
    while(TRUE)
    {
        fSuccess = ReadFile(hPipeStatus,p,sizeof(my_message)-(p-lpMsg),&len,NULL);
        sprintf(log_buffer,"%d",GetLastError());
        if(!fSuccess)MessageBox(hwndDlg,log_buffer,"ERROR",MB_OK|MB_ICONERROR);
        if(!fSuccess || p+len>=lpMsg+sizeof(my_message))break;
        p = p +len;
    }
    DisconnectNamedPipe(hPipeStatus);

    if(fSuccess && msg->p1 ==ERROR_SUCCESS)
    {
        if(msg!=NULL)free(msg);
        return TRUE;
    }
    if(fSuccess && cmd!=ID_GET_INFO)MessageBox(hwndDlg,msg->p2,"ERROR",MB_OK|MB_ICONERROR);
    if(msg!=NULL)free(msg);
    return FALSE;
}

关于 my_message,这是自定义的结构体,定义如下:


typedef struct _my_message{
    LONG p1;
    char p2[MAX_PATH];
} my_message;

因为系统不保证结构体内存空间的连续性,所以采取动态分配空间的方式,保证在写入管道 的时候是连续的内存块。

6 界面程序

界面主要为用户提供直观的交互接口,不需要太复杂,需要一些按钮来发送命令,需要一些 简单信息的显示。

6.1 文件检查

6.2 创建与删除服务

bool_t usb_service_create(const char *name, const char *display_name,
                          const char *binary_path, unsigned long type,
                          unsigned long start_type)
{
    SC_HANDLE scm = NULL;
    SC_HANDLE service = NULL;
    bool_t ret = FALSE;

    if (!usb_service_load_dll())
    {
        return FALSE;
    }

    do
    {
        scm = open_sc_manager(NULL, SERVICES_ACTIVE_DATABASE,
                              SC_MANAGER_ALL_ACCESS);

        if (!scm)
        {
            USBERR("opening service control "
                   "manager failed: %s", usb_win_error_to_string());
            break;
        }

        service = open_service(scm, name, SERVICE_ALL_ACCESS);

        if (service)
        {
            if (!change_service_config(service,
                                       type,
                                       start_type,
                                       SERVICE_ERROR_NORMAL,
                                       binary_path,
                                       NULL,
                                       NULL,
                                       NULL,
                                       NULL,
                                       NULL,
                                       display_name))
            {
                USBERR("changing config of "
                       "service '%s' failed: %s",
                       name, usb_win_error_to_string());
                break;
            }
            ret = TRUE;
            break;
        }

        if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
        {
            service = create_service(scm,
                                     name,
                                     display_name,
                                     GENERIC_EXECUTE,
                                     type,
                                     start_type,
                                     SERVICE_ERROR_NORMAL,
                                     binary_path,
                                     NULL, NULL, NULL, NULL, NULL);

            if (!service)
            {
                USBERR("creating "
                       "service '%s' failed: %s",
                       name, usb_win_error_to_string());
            }
            ret = TRUE;
        }
    }
    while (0);

    if (service)
    {
        start_service(service,0,NULL);//创建以后则打开
        close_service_handle(service);
    }

    if (scm)
    {
        close_service_handle(scm);
    }

    usb_service_free_dll();

    return ret;
}

这一段代码来自于 libusb-win32 ,这是一个值得一读的项目,删除服务的方法在其中也有。

6.3 对话框

创建一个对话框也是很简单了,就一句话。第一个参数来自于WinMain 的第一个参数,第二 个参数是使用 MAKEINTRESOURCE 宏对 DLG_MAIN * 资源 * 的处理,第三个参数是一个消 息处理回调函数。

return DialogBox(hInst,MAKEINTRESOURCE(DLG_MAIN),NULL,(DLGPROC)DialogProc);

6.4 消息处理

先说消息处理函数 DialogProc,就像下面这个样子,WM_INITDIALOG,WM_CLOSE的意思就一 目了然了,前者是在对话框初始化,后者是对话框关闭时收到的消息,而 WM_COMMAND 则表 示用户点击按钮发送了某个消息,具体是哪个按钮呢,从wParam 参数可以找到。

BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_INITDIALOG:
    {

    }
    return TRUE;

    case WM_CLOSE:
    {
        EndDialog(hwndDlg, 0);

    }
    return TRUE;

    case WM_COMMAND:
    {
        switch(LOWORD(wParam))
        {
        case ID_MOUNT_PUBLIC:
        case ID_MOUNT_HIDE:
        case ID_MOUNT_SECRET:
        case ID_RESET_CONFIG:
        case ID_DELETE_USBSTOR_ENUM:
            if(strlen(device_id)<=0)
            {
                MessageBox(hwndDlg,"You must select one device.","ERROR",MB_OK|MB_ICONERROR);
                return TRUE;
            }
            if(!PostThreadMessage(nThreadID,LOWORD(wParam),(WPARAM)device_id,0))
            {
                char log_buffer[MAX_PATH];
                sprintf(log_buffer,"Send command to working thread failed.[%d]",GetLastError());
                MessageBox(hwndDlg,log_buffer,"ERROR",MB_OK|MB_ICONERROR);
            }
            break;
        case ID_EXIT:
            PostThreadMessage(nThreadID,ID_EXIT,NULL,0);
            PostMessage(hwndDlg,WM_CLOSE,NULL,NULL);
            break;
        case ID_FRESH_DEVICE_LIST:
            FreshDeviceList(dev_cmbox);
            break;
        }
    }
    return TRUE;

    }
    return FALSE;
}

此例中,wParam 是一个数字,被按下的按钮的ID 号,按钮是怎么定义和运用的呢,请看下 一节 。

6.5 资源

我的工程中有一个文件 resource.h,定义了对话框和按钮的ID号,其中的内容大致如下:

#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif

#define DLG_MAIN                                100

#define ID_MOUNT_PUBLIC WM_USER+1
#define ID_MOUNT_HIDE WM_USER+2
#define ID_MOUNT_SECRET WM_USER+3
#define ID_RESET_CONFIG WM_USER+4
#define ID_EXIT WM_USER+5
#define ID_GET_INFO WM_USER+5
#define ID_DELETE_USBSTOR_ENUM WM_USER+7
#define ID_FRESH_DEVICE_LIST WM_USER+8

这里要注意的是ID号最好不要乱定义,否则在执行过程中可能存在莫名其妙的错误,一般是 在WM_USER段以上。

而资源文件则是 resource.rc,是关于对话框、图标等具体定义,微软文档中资源定义语法 有详细介绍。下面只是一个例子。

#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"

MYICON ICON "favicon.ico"
//
// Dialog resources
//
LANGUAGE 0, SUBLANG_NEUTRAL
DLG_MAIN DIALOG 0, 0, 210, 150
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "MyApp"
FONT 10, "Ms Shell Dlg"
{
ICON MYICON,106,10,125
COMBOBOX IDI_DEVICE,55,5,150,20,WS_VSCROLL|WS_TABSTOP|CBS_DROPDOWNLIST
DEFPUSHBUTTON "刷新设备",ID_FRESH_DEVICE_LIST,55,25,70,20
DEFPUSHBUTTON "挂载分区",ID_MOUNT_PUBLIC,135,25,70,20
DEFPUSHBUTTON "挂载隐藏区",ID_MOUNT_HIDE,135,45,70,20
DEFPUSHBUTTON "挂载加密区",ID_MOUNT_SECRET,135,65,70,20
DEFPUSHBUTTON "重置驱动",ID_RESET_CONFIG,135,85,70,20
DEFPUSHBUTTON "清除U盘记录",ID_DELETE_USBSTOR_ENUM,135,105,70,20
DEFPUSHBUTTON "退出",ID_EXIT,135,125,70,20
LTEXT "选择U盘",IDC_STATIC,5,5,50,20

LTEXT "当前系统",IDC_STATIC,5,50,50,20
LTEXT "",IDT_SYSTEM,55,50,70,20,WS_BORDER
LTEXT "配置结果",IDC_STATIC,5,70,50,20
LTEXT "",IDT_DRIVER,55,70,70,20,WS_BORDER
LTEXT "程序版本",IDC_STATIC,5,90,50,20
LTEXT "Version 1.0",IDC_STATIC,55,90,70,20,WS_BORDER

LTEXT "解决驱动配置错误引起的U盘读写问题。",IDC_STATIC,55,120,70,30
}

//
// Manifest resources
//
LANGUAGE 0, SUBLANG_NEUTRAL
1                  RT_MANIFEST    ".\\manifest.xml"

6.6 线程

我们的应用中涉及到使用管道进行通信,存在阻塞的可能情况,为了给用户较好的操作体验, 需要把这种费时的读写操作分离出来,使用单独线程进行处理,主线程与子线程之间采取消 息进行通信。

HANDLE hStartEvent;
HANDLE hThread;
unsigned nThreadID;
//创建线程
hThread = (HANDLE)_beginthreadex(NULL,0,ProcessThread,(LPVOID)hwndDlg,0,&nThreadID);
if(hThread==0)
{
    MessageBox(hwndDlg,"Create working thread failed.","ERROR",MB_OK|MB_ICONERROR);
    CloseHandle(hStartEvent);
    return FALSE;
}
WaitForSingleObject(hStartEvent,INFINITE);
CloseHandle(hStartEvent);

线程的定义。

unsigned __stdcall ProcessThread(LPVOID lpParameter)
{
    MSG msg;
    //查看有无消息,这里主要是保证消息队列被创建
    PeekMessage(&msg,NULL,WM_USER,WM_USER,PM_NOREMOVE);
    if(!SetEvent(hStartEvent))
    {
        return 1;
    }
    char file_path[MAX_PATH];
    HWND hwndDlg = (HWND)(lpParameter);
    while(TRUE)
    {
        //获取消息
        if(GetMessage(&msg,0,0,0))
        {
            char * device = (char *)msg.wParam;
            switch(msg.message)
            {
            case ID_MOUNT_PUBLIC:
                break;
            case ID_EXIT:
                return 0;

            case ID_MOUNT_HIDE:
                break;
            case ID_MOUNT_SECRET:
                break;
            case ID_RESET_CONFIG:
                break;
            case ID_DELETE_USBSTOR_ENUM:
                break;
            }
        }
    }
}

消息的发送。

PostThreadMessage(nThreadID,LOWORD(wParam),(WPARAM)device_id,0); 

关于PeekMessage、GetMessage 和 PostThreadMessage 的介绍可以从这里了解到。

7 程序编译

如果使用CodeBlocks,在创建工程的时候,服务程序可以选择 win32 console 程序,而界 面程序要选择 win32 Gui 程序,这会减少一些麻烦,因为IDE 为你设置好了编译和连接选 项,当然你也可以采取手动方式在设置工程的编译和连接选项。有时这还是必须的,比如提 醒undefined reference to xxxx 的时候,就需要添加一些连接库,比如 setupapi 等等。

8 后记

使用 UltraISO 将开发的驱动配(重)置工具打包到ISO镜像中,再使用量产工具重新量产U 盘为USB CDROM+USB DISK,驱动配(重)置工具位于CD中,U盘插入系统后,运行程序重置驱动, 成功解决U盘驱动被劫持的问题。当然,对于USB CDROM也被禁用的情况,此法就不可用了。

本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议 进行许可。

posted on 2022-09-09 10:41  YourTech-WuPeng  阅读(421)  评论(1编辑  收藏  举报

导航