加载中...
libUSB-脱掉USB的外套
第1节:libfswatch-文件变动监控
第2节:libiconv-字符集编码转换
第3节:CLI11-命令行参数解析
第4节:nlohmann/json-自然的JSON库
第5节:libb64-理解并玩转base64编码
第6节:libSnappy-快速压缩工具
第7节:spdlog-首选的C++日志库
第8节:libUSB-脱掉USB的外套
第9节:libxlsxwriter-让数据说话
课文封面

是时候让自己硬一次,就此告别 “就会吃软饭” 的“恶”名!
五个视频,从 USB 设备列表展示,到裸读U盘容量,到最终将 “反清复明” 写到 U 盘倒数第 10 块的位置上,轻松制作极具实用价值的身份认证 U 盘……

0. 概述

项目目标:通过 libUSB 学习,动手制作你的身份认证专用U盘;
人生突破:摘掉 “只会吃软饭” 帽子,编程路上从此硬一回;
包含视频:5 段;
动手项目:两个可执行程序
-(1)USBScanner (谁?谁?谁?谁在插我)
-(2)USBIdentifying (制作“红花会”身份证U盘)

核心知识:

  1. USB (通用串行总线)基本概念;
  2. 银行 U 盾基本原理;
  3. libUSB 核心功能编程;

1. 简介、安装、快速使用

迅速了解 USB 基本概念,libUSB 的作用与使命(定位),十数行代码,列出当前插在你的电脑上的 USB 设备……

上机 :USBScanner 简陋版 → USBScanner 标准版本。

  • 视频 1 :USBScanner 标准版开发

  • 重点A:设备(及接口)类型
    USB 设备类型枚举定义-libUSB

2. USB 设备列表 Ultra 版本

深入了解 USB 编程的逻辑层次:设备 → 配置 → 接口 → 接口描述 → 端点。以上层次组成如下图所示的 “USB 设备信息树”。

  • 重点B:USB逻辑层次
    USB设备信息树

  • 视频 2 :USBScanner Ultra 版开发

  • 重点C:设备传输分类

    • 控制传输 (用于系统控制设备,所有USB设备必选实现)
    • 等时传输(音频、音频等注重时间同步的流式传输,宁错勿迟)
    • 批量传输(大容量存储数据的读与写,宁迟勿错)
    • 中断传输(键盘、鼠标等中断式输入数据传输)

3. 裸读 U 盘容量-准备

最关键的准备工作:将 U 盘的驱动降级。

libUSB无法对一块正在接受 Windows (或其它操作系统)高级驱动管理的 USB 设备进行裸读裸写——那样太不安全了。视上层OS之不同,通常需要手工或编程自动(结合管理员权限)将指定USB设备与系统驱动“剥离”后,才能实现。

  • 视频3: 裸读 U 盘容量准备工作

  • 重点D:libUSB 库定位
    以下是 libUSB 在 USB 设备开发分层中,所处位置。

libUSB 定位

  • 重点E:libUSB 批量传输三板斧

① 发送命令与参数 → ② 传输数据(接收IN/或输出/OUT)→ ③ 询问状态
注意点:只要第 ① 步完成,则程序必须匹配完成第 ③ 步。

4. 裸读 U 盘容量-上机

  • 视频4:裸读U盘容量-上机

5. 制作身份认证 U 盘

  • 视频5:制作身份认证U盘

6. 附:关联知识

6.1 BCD 编码

BCD(Binary-Coded Decimal,二进制编码的十进制)是一种将十进制数字(0-9)转换为二进制形式的编码方式,其核心思想是用二进制数来表示每个十进制数位,而非直接将整个十进制数转换为纯二进制。这种编码在需要精确表示十进制数值的场景(如金融、会计、显示设备等)中广泛应用,避免了纯二进制转换时可能出现的精度误差。

十进制与BCD编程对应表(以 四位/bit 为 BCD码长):

十进制数 BCD 十进制数 BCD
0 0000 1 0001
2 0010 3 0011
4 0100 5 0101
6 0110 7 0111
8 1000 9 1001

基于此,假设有一个十进制数 9230, 则可表达为二进制数(空格仅为方便阅读):
1001 0010 0011 0000

libUSB 正是使用如上表所示的 4 位(bit)码长的BCD编码,共使用4组,合计16位(uint16_t,两个字节)来表达 USB 和设备产品的版本号。由于一个字节(byte)包含8位,也就是一个字节中两个 BCD 码,这两个BCD码,谁在高位,谁在低位?比如:0010 0001 是在表达 21 还是在表达 12 ?在 libUSB 的版本号表达中,约定如下:

BCD 版本号表达

注:图中XXXX 均为16位进制表达,且“字”指字节(8个2进制位),“位”指半个字节(4个2进制位)。

6.2 大小端序

问一个问题,在 C/C 程序中,设有数据 char* s="123456",请问:

  1. 在计算机存储中,逻辑上, ‘1’ 所在内存位置(编号)大,还是 ‘6’ 所在内存位置(编号)大?
  2. 如果使用网络程序按字节发送该字符串,请问 ‘1’ 先发,还是 ‘6’ 先发出去?
  3. 以上答案和该程序所在主机的大小端序是否有相关性?

问题 1 答案:‘6’ 所在内存的编号大,因为 : s[0] 是’1’的位置,而s[6] 是’6’的位置,显然,6 比 0 大。
问题 2 答案:‘1’ 先发出去。
问题 3 答案:无关。

如果将数据从 C/C++ 字符串(char *)改为一个整数,事情就开始复杂了。比如数据 int32_t i=0x123456,请问,此时是 1 在内存高位,还是 6 在内存高位?

首先,由于 i 的类型是 int32_t,因此它实际有 32 位 4 个字节,用16进制表达,它应该是: 0x00123456

然后,由于计算机存储的最小单位是1字节,因此,0x00123456实际会被拆分为以下字节:0x00、0x12、0x34、0x56。因此,我们实际要考察的是:

  1. 0x00 和 0x56 这两个字节谁在内存中的逻辑高地址,谁在内存中的逻辑低地址?
  2. 网络发送时,0x00先被发送,还是 0x56 先被发送?

二者都没有标准答案,得看机型(具体实现),得看协议(遵循特定约定)。有两种常见的实现或约定:大端序(Big-Endian)和小端序(Little-Endian)。

首先,不管是大端还是小端,都认同:在程序的表达上,先保存的数据,应位于逻辑低地址上,后保存的数据,应位于逻辑高地址上。这正是字符串 "123456" 的保存和大小端主机无关的基础:对于C/C++字符系列,在逻辑上总是视’1’存储在’2’之前, ‘2’ 存储在’3’之前……

其次,在高低位表达上,二者也都采用人类原有理解来区分,即:不管什么进制的数,比如十进制 123, 1是高位(权值最大,百位),3 是低位(权值最小,个位)。

区别开始出现了:

大端序(Big-Endian):既然人类喜欢把高位的数放在前面,比如:一百二十三会被写成 123 而不是 321,所以,我们兼容这种写法,在计算机,也将高位上的数,写在内存地址靠前的位置上。结论:整数中的最高位,写在内存逻辑地址较小的位置上。

小端序(Little-Endian):我们是程序,干嘛要采用人类的生活习惯?再说,人类也有把“小”的东西写在前面的案例啊,比如英国人信封上地址,就是先有街道号,最后是国家,另外他们表达日期好像也是日月年……

大端序插话:但是伟大的中国人民就没这么乱,数字、时间、地址,都是从大到小……)

小端序:你别插话,反正,在这里照顾人类的习惯毫无作用,应该保持一致:位高的数据就在内存的高位地址,位低的数据就在内存的低位的地址……这多好记忆啊……结论:整数中的最高位,会被写在内存逻辑地址较大的位置上。

当我们使用 libusb_bulk_transfer() 函数,发送 READ(10) WRITE(10) 指令时,此时,需要读或写的位置(lba-index / 块编号) ,就是以大端序的方式,被转换为字符系列(无符号字符系列)中去的:

// LBA地址 (大端序) cbw.CBWCB[2] = (lba_index >> 24) & 0xFF; cbw.CBWCB[3] = (lba_index >> 16) & 0xFF; cbw.CBWCB[4] = (lba_index >> 8) & 0xFF; cbw.CBWCB[5] = lba_index & 0xFF;

即:比较小的内存位置(比如下标 2 的元素),存放了 lba_index 这个整数中比较高(靠左)的字节,(注意 赋值左边的 2,3,4,5 和赋值右边的 右移 24、16、8、0 的关系)。

6.3 USB 端点错误处理

当 USB 设备遇到无法处理的请求时,负责执行该请求的端点,会向主机发送 “STALL” 握手包,表示“设备无法处理该请求,请停止操作”,在 libusb中,通常对应的错误编码为 LIBUSB_ERROR_PIPE 。

一旦端点进入 STALL 状态,后续对该端点的操作,都会失败——除非主机显式的清除该状态。因此,程序在检测到此类问题发生时,应及时在相应的端点上,调用 libusb_clear_halt() 函数,清除此状态。

stall 和 halt 在此都是英文单词本意。

另,当出现更为严重的错误时,可以考虑调用 libusb_reset_device() 对设备进行完全复位。

7. 源码

CMakeLists.txt 最终版本内容:

cmake_minimum_required(VERSION 3.10) project(HelloUSB) set(CMAKE_CXX_STANDARD 17) # 使用 C++17 # 以下三行对所有构建目标生效 link_libraries(usb-1.0) # 添加 libusb-1.0 库 link_directories(C:/msys64/ucrt64/lib) # 指定 库 所在目录 add_link_options(-static) # 使用GCC静态链接 # 第一个构建目标: add_executable(USBScanner main.cpp) # 第二个构建目标: add_executable(USBIdentifying main2.cpp)

7.1 USBScanner (终版)

main.cpp

#include <cassert> #include <cstdlib> #include <iostream> #include <iomanip> #include <memory> #include <map> #include <libusb-1.0/libusb.h> void handle_error(int r, char const* msg) { std::cerr << msg << " - " << libusb_error_name(r) << "\n"; } std::string tab(int depth) // 产生指定个数的缩进符 { return std::string(depth, '\t'); } // BCD版本号转字符串 std::string bcd_to_version_string(uint16_t bcd) { // 0x XXXX uint8_t major = (bcd >> 8) & 0xFF; // 主版本号,在第三个字节 uint8_t minor = (bcd >> 4) & 0x0F; // 次版本号,在第二个字节 uint8_t patch = bcd & 0x0F; // 补丁号 std::ostringstream oss; oss << static_cast<int>(major) << "." << static_cast<int>(minor); if (patch != 0) { oss << "." << static_cast<int>(patch); } return oss.str().c_str(); } // USB 设备分类的枚举值,转成中文说明 std::string const& class_code_to_string(uint8_t code) { static std::string const unknown_class = "未知类型"; static std::map<uint8_t, std::string> const m = { {LIBUSB_CLASS_PER_INTERFACE, "接口层面定义"}, {LIBUSB_CLASS_AUDIO, "音频设备(扬声器、麦克风、MIDI输入等)"}, {LIBUSB_CLASS_COMM, "通信设备(调制解调器,网络适配器等)"}, {LIBUSB_CLASS_HID, "人机接口设备(键盘、鼠标、手柄等)"}, {LIBUSB_CLASS_PHYSICAL, "物理设备(力反馈、运动传感器等)"}, {LIBUSB_CLASS_IMAGE, "图像设备(数码相机、扫描仪等)"}, {LIBUSB_CLASS_PRINTER, "打印机"}, {LIBUSB_CLASS_MASS_STORAGE, "大容量存储类(U盘、移动硬盘、SD读卡器等)"}, {LIBUSB_CLASS_HUB, "集线器"}, {LIBUSB_CLASS_DATA, "数据类(通信设备的配套接口)"}, {LIBUSB_CLASS_SMART_CARD, "智能卡(安全令牌、读卡器等)"}, {LIBUSB_CLASS_CONTENT_SECURITY, "数据版权管理设备(内容安全)"}, {LIBUSB_CLASS_VIDEO, "视频设备(摄像头、视频采集卡等)"}, {LIBUSB_CLASS_PERSONAL_HEALTHCARE, "个人健康设备(心率监测器、血糖仪等)"}, {LIBUSB_CLASS_DIAGNOSTIC_DEVICE, "诊断设备(USB调试工具、协议分析仪等)"}, {LIBUSB_CLASS_WIRELESS, "无线控制器(蓝牙适配器、Wi-Fi网卡等)"}, {LIBUSB_CLASS_MISCELLANEOUS, "复合功能设备(多功能键盘等)"}, {LIBUSB_CLASS_APPLICATION, "特定应用设备(固件更新模式、设备测试模式等)"}, {LIBUSB_CLASS_VENDOR_SPEC, "厂商自定义设备(非标准设备,需专用驱动)"}, }; auto it = m.find(code); return (it != m.cend() ? it->second : unknown_class); } // 传输类型的名称 std::string transfer_type_to_string(uint8_t transfer_type) { switch(transfer_type) { case LIBUSB_TRANSFER_TYPE_CONTROL: return "控制传输"; case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: return "等时传输"; case LIBUSB_TRANSFER_TYPE_BULK: return "批量传输"; case LIBUSB_TRANSFER_TYPE_INTERRUPT: return "中断传输"; default: return "未知传输类型"; } } // 打印端点描述信息 void print_endpoint_descriptor(libusb_endpoint_descriptor const& desc, int depth) { // 传输类型 desc.bmAttributes & 0x03 (LIBUSB_TRANSFER_TYPE_MASK) uint8_t transfer_type = desc.bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; std::cout << tab(depth) << "端点地址:0x" << std::hex << std::setw(2) << static_cast<int>(desc.bEndpointAddress) << " - " << transfer_type_to_string(transfer_type) << "(" << ((desc.bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)? "IN" : "OUT") << ")\n"; } // 打印接口信息 void print_interface_descriptor(libusb_interface_descriptor const& desc, int depth) { // 每个接口有自己的设备类型: std::cout << tab(depth) << "设备类型:" << class_code_to_string(desc.bInterfaceClass) << "\n"; std::cout << std::hex; std::cout << tab(depth) << "设备子类型:0x" << std::setw(2) << static_cast<int>(desc.bInterfaceSubClass) << "\n"; std::cout << tab(depth) << "接口协议:0x" << std::setw(2) << static_cast<int>(desc.bInterfaceProtocol) << "\n"; std::cout << tab(depth) << "包含端点个数:" << std::setw(4) << std::dec << static_cast<int>(desc.bNumEndpoints) << "\n"; for (int i=0; i<desc.bNumEndpoints; ++i) { std::cout << tab(depth) << "#" << std::dec << i << ":\n"; print_endpoint_descriptor(desc.endpoint[i], depth+1); } } // 打印设备配置信息 void print_config_descriptor(libusb_config_descriptor const& desc, int depth) { // labmda : 设备功耗 = 电流 * 电压 (固定 5 伏) auto toW = [](unsigned int mA) -> double { return mA * 5.0 / 1000; }; std::cout << tab(depth) << "最大功耗:" << std::setprecision(2) << toW(desc.MaxPower * 2) << " 瓦(high-speed-mode)," << toW(desc.MaxPower * 8) << "瓦(super-speed-mode)\n"; std::cout << tab(depth) << "包含接口个数:" << std::setw(4) << std::dec << static_cast<int>(desc.bNumInterfaces) << "\n"; for (int i=0; i<desc.bNumInterfaces; ++i) { std::cout << std::dec << tab(depth) << "接口 #" << i; // 接口从 0 起 // 取接口: libusb_interface const* interface = &(desc.interface[i]); if (interface->num_altsetting <= 0) { std::cout << "(无备用配置)\n"; continue; } if (interface->num_altsetting > 1) { std::cout << "(有 " << std::dec << interface->num_altsetting << " 个备用配置)\n"; } else { std::cout << "\n"; } // 取首选配置: libusb_interface_descriptor const* interface_desc = &(interface->altsetting[0]); // 第0个->首选! // 打印接口描述(设置)信息 print_interface_descriptor(*interface_desc, depth + 1); } } void print_device_descriptor(libusb_device* dev, libusb_device_descriptor const& desc, int depth = 1) { std::cout << tab(depth) << "USB 版本:" << bcd_to_version_string(desc.bcdUSB) << "\n"; std::cout << tab(depth) << "设备类型:" << class_code_to_string(desc.bDeviceClass) << "\n"; std::cout << tab(depth) << "设备版本:" << bcd_to_version_string(desc.bcdDevice) << "\n"; std::cout << std::hex; std::cout << tab(depth) << "厂商 ID:0x" << std::setw(4) << desc.idVendor << "\n"; std::cout << tab(depth) << "产品 ID:0x" << std::setw(4) << desc.idProduct << "\n"; std::cout << tab(depth) << "包含配置数:" << std::dec << std::setw(2) << static_cast<int>(desc.bNumConfigurations) << "\n"; libusb_config_descriptor *config_desc = nullptr; // 配置描述 // 只取活动的配置 if (int r = libusb_get_active_config_descriptor(dev, &config_desc); r != LIBUSB_SUCCESS) { handle_error(r, "获取设备的活动配置失败!"); return; } assert(config_desc != nullptr); // 安排哨兵 std::unique_ptr<libusb_config_descriptor, void(*)(libusb_config_descriptor *)> guard(config_desc, libusb_free_config_descriptor); // 打印配置描述 print_config_descriptor(*config_desc, depth+1); } int main() { std::system("chcp 65001 >nul"); // 设置程序运行的控制台编码为 UTF-8 // RAII的一种使用(自定义类型,非智能指针) struct AutoPauser { ~AutoPauser() { std::system("pause"); }} auto_pauser; // 初始化 libusb libusb_context *ctx = nullptr; if (int r = libusb_init(&ctx); r != LIBUSB_SUCCESS) { handle_error(r, "初始化失败!"); return -1; } // 定义哨兵-1 std::unique_ptr<libusb_context, void(*) (libusb_context*)> guard_1 (ctx, libusb_exit); // 获取设备列表 libusb_device** devices; // 设备列表 ssize_t count = libusb_get_device_list(ctx, &devices); if (count < 0) { handle_error(count, "获取设备列表失败!"); return -1; } std::cout << "发现 " << count << " 个 USB 设备。\n"; // 定义哨兵-2 std::unique_ptr<libusb_device*, void(*) (libusb_device**)> guard_2(devices , [](libusb_device** p) { libusb_free_device_list(p, 1); }); std::cout << std::setfill('0'); for (int i=0; i<count; ++i) { std::cout << "设备 " << std::dec << std::setw(2) << i + 1 << ":\n"; libusb_device_descriptor desc; // 设备描述数据 if (int r = libusb_get_device_descriptor(devices[i], &desc); r != LIBUSB_SUCCESS) { handle_error(r, "获取设备描述失败!"); continue; } print_device_descriptor(devices[i], desc); std::cout << "-----------------------------\n"; } }

7.2 USBIdentifying (终版)

main2.cpp

#include <cassert> #include <cstdlib> #include <iostream> #include <iomanip> #include <string> #include <sstream> #include <memory> #include <vector> #include <algorithm> #include <libusb-1.0/libusb.h> #pragma pack(push, 1) // 命令块 struct CommandBlockWrapper { uint32_t dCBWSignature = 0x43425355; // "USBC" uint32_t dCBWTag = 0xCAFE1234; uint32_t dCBWDataTransferLength; uint8_t bmCBWFlags; // 0x00=OUT, 0x80=IN uint8_t bCBWLUN = 0; uint8_t bCBWCBLength; uint8_t CBWCB[16] = {0}; // SCSI 指令区 }; // 命令执行状态块 struct CommandStatusWrapper { uint32_t dCSWSignature = 0x53425355; // "USBS" uint32_t dCSWTag; // 匹配 CommandBlockWrapper.dCBWTag uint32_t dCSWDataResidue; uint8_t bCSWStatus; }; #pragma pack(pop) // 处理错误 void handle_error(char const* msg) { std::cerr << msg << "\n"; } // 处理有(libusb库)出错代码的错误 void handle_error(int r, char const* msg) { std::cerr << msg << " - " << libusb_error_name(r) << "\n"; } // 处理传输字节数不匹配的错误 void handle_error_transferred(char const* msg, int transferred, int expected) { std::cerr << msg << " - 传输字节数:" << transferred << ",期望字节数:" << expected << "\n"; } // 处理错误的执行状态 void handle_error_status(char const* msg, uint8_t status, libusb_device_handle* dev_handle) { std::cerr << msg << " - 执行状态有误:" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(status) << "\n"; if (status == 0x01) { std::cerr << "设备拒绝执行当前命令,请检查SCSI格式与内容\n"; return; } if (status == 0x02) // 命令时序阶段性错误(Phase Error),通常需要重置设备 { if (!dev_handle) { std::cerr << "命令阶段错误,请重置设备\n"; } else { std::cerr << "命令阶段错误,将自动重置设备\n"; int r = libusb_reset_device(dev_handle); // 重置!!! if (r != LIBUSB_SUCCESS) { handle_error(r, "自动重置设备失败,请手工尝试"); } } } } // 处理命令执行“成功”,但有残留数据? void handle_error_residue(char const* msg, uint32_t residue) { std::cerr << msg << " - 有残留数据:" << residue << " 字节\n"; } // 检查指定的ID组合是否(在你现在的电脑上)唯一 bool verify_device_id_unique(uint16_t const vid, uint16_t const pid) { libusb_device** devices = nullptr; ssize_t count = libusb_get_device_list(nullptr, &devices); if (count < 0) { handle_error(count, "获取设备列表失败!"); return false; } // 安排负责释放设备列表的哨兵 std::unique_ptr<libusb_device*, void (*)(libusb_device **)> guard (devices, +[](libusb_device** ptr) { libusb_free_device_list(ptr, 1); }); int found = 0; for (int i=0; i<count; ++i) { libusb_device* dev = devices[i]; libusb_device_descriptor desc; if (int r = libusb_get_device_descriptor(dev, &desc); r != LIBUSB_SUCCESS) { handle_error(r, "获取设备描述符失败"); continue; } if (desc.idVendor == vid && desc.idProduct == pid) { ++found; if (found > 1) { handle_error("该ID组合的设备存在重复"); return false; } } } if (found == 0) { handle_error("未找到该ID组合的设备"); return false; } return true; } // 查找指定接口用于批量传输的输出与输入端点 bool find_bulk_endpoints(libusb_interface_descriptor const* interface_desc , uint8_t* bulk_out_endpoint, uint8_t* bulk_in_endpoint) { assert(bulk_out_endpoint != nullptr && bulk_in_endpoint != nullptr); for (int i=0; i < interface_desc->bNumEndpoints; ++i) { libusb_endpoint_descriptor const* endpoint_desc = &(interface_desc->endpoint[i]); if ((endpoint_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) if ((endpoint_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) *bulk_out_endpoint = endpoint_desc->bEndpointAddress; else if ((endpoint_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) *bulk_in_endpoint = endpoint_desc->bEndpointAddress; } if (0 == *bulk_out_endpoint || 0 == *bulk_in_endpoint) { handle_error("未找到用于批量传输的输出或输入端点"); return false; } return true; } // 读取指令执行状态 bool read_command_status(libusb_device_handle* dev_handle , uint8_t bulk_in_endpoint, unsigned int timeout) { // 定义 CSW 数据 CommandStatusWrapper csw; // 读取 CSW int transferred = 0; if (int r = libusb_bulk_transfer(dev_handle, bulk_in_endpoint, reinterpret_cast<unsigned char*>(&csw), sizeof(csw) , &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "读取 CSW 失败"); return false; } else if (static_cast<size_t>(transferred) != sizeof(csw)) { handle_error_transferred("读取 CSW 失败", transferred, sizeof(csw)); return false; } else if (csw.bCSWStatus != 0) { handle_error_status("前一命令执行失败", csw.bCSWStatus, dev_handle); return false; } else if (csw.dCSWDataResidue > 0) { handle_error_residue("前一命令执行“半成功”?", csw.dCSWDataResidue); return false; } return true; } // U 盘容量 struct USBCapacity { uint32_t max_lba; // 该U盘存储单元共有多少 “块” uint32_t block_size; // 每一“块”可存储多少字节 // 计算字段 uint64_t total_bytes; // max_lba * block_size 总容量 }; // 读 U 盘容量信息 bool read_usb_capacity(libusb_device_handle * dev_handle, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint, USBCapacity* capacity) { assert(capacity != nullptr); // 第一板斧:组装并输出指令块 CommandBlockWrapper cbw; cbw.dCBWDataTransferLength = 8; // 期待传输(收到)的数据大小 cbw.bmCBWFlags = LIBUSB_ENDPOINT_IN; // 期待收到数据 cbw.bCBWCBLength = 10; // 本指令(+参数)的最大长度 cbw.CBWCB[0] = 0x25; // SCSI READ CAPACITY(10) unsigned int const timeout = 1000; // ms if (int transferred = 0, r = libusb_bulk_transfer(dev_handle , bulk_out_endpoint , reinterpret_cast<unsigned char*>(&cbw), sizeof(cbw) , &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "发送 READ CAPACITY(10) 命令块失败"); return false; } else if (static_cast<size_t>(transferred) != sizeof(cbw)) { handle_error_transferred("写 READ CAPACITY(10) 指令,字节数有误" , transferred, sizeof(cbw)); return false; } // 第二板斧:传输业务数据(此处为读) bool data_transfer_fail = false; // 本步是否失败了? uint8_t capacity_buffer [8] {}; // 接收缓存区 if (int transferred = 0, r = libusb_bulk_transfer(dev_handle , bulk_in_endpoint, capacity_buffer, sizeof(capacity_buffer) , &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "读 READ CAPACITY(10) 执行状态失败!"); data_transfer_fail = true; } else if (transferred != sizeof(capacity_buffer)) { handle_error_transferred("读 READ CAPACITY(10) 传输字节数错误" , transferred, sizeof(capacity_buffer)); data_transfer_fail = true; } if (data_transfer_fail) // 数据传输过程发生错误 { // 先清除 In 端点的状态 libusb_clear_halt(dev_handle, bulk_in_endpoint); } // 第三板斧:读状态 (当第一板成功,本板一定要使出) if (!read_command_status(dev_handle, bulk_in_endpoint, timeout)) { handle_error("READ CAPACITY(10) 指令执行结果状态有误"); return false; } if (data_transfer_fail) { return false; } // 解析读到的 8 字节,转成容量信息 capacity->max_lba = (capacity_buffer[0] << 24) | (capacity_buffer[1] << 16) | (capacity_buffer[2] << 8) | capacity_buffer[3]; capacity->block_size = (capacity_buffer[4] << 24) | (capacity_buffer[5] << 16) | (capacity_buffer[6] << 8) | capacity_buffer[7]; // 计算总容量 (字节) capacity->total_bytes = static_cast<uint64_t>(capacity->max_lba + 1) * capacity->block_size; return true; } // 将块索引和需要占用的块数(固定1块),以大头序拆分到字节数组 void init_cbwcb_for_lba(CommandBlockWrapper& cbw , uint32_t const lba_index) { // LBA地址 (大端序) cbw.CBWCB[2] = (lba_index >> 24) & 0xFF; cbw.CBWCB[3] = (lba_index >> 16) & 0xFF; cbw.CBWCB[4] = (lba_index >> 8) & 0xFF; cbw.CBWCB[5] = lba_index & 0xFF; // 块数 (写入1个块) cbw.CBWCB[7] = 0x00; cbw.CBWCB[8] = 0x01; } // 读取一块大小的字节数据到内存 bool read_bytes_from_lba(libusb_device_handle* dev_handle , std::vector<uint8_t>& bytes , uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint , uint32_t lba_index, unsigned int timeout) { // 构造 READ(10) 的命令块 CommandBlockWrapper cbw; cbw.dCBWDataTransferLength = bytes.size(); cbw.bmCBWFlags = LIBUSB_ENDPOINT_IN; // 期待输入 cbw.bCBWCBLength = 10; // 指令+数据长度标志 cbw.CBWCB[0] = 0x28; // READ(10) 指令操作码 // 数据位置(块索引)与大小(固定一块) init_cbwcb_for_lba(cbw, lba_index); // 发送 CBW: if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_out_endpoint , reinterpret_cast<unsigned char*>(&cbw) , sizeof(cbw), &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "发送 READ(10) 命令块失败"); return false; } else if (static_cast<size_t>(transferred) != sizeof(cbw)) { handle_error_transferred("写 READ(10) 指令,字节数有误", transferred, sizeof(cbw)); return false; } bool data_transfer_fail = false; // 接收数据 if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_in_endpoint , bytes.data(), bytes.size(), &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "读业务数据失败"); data_transfer_fail = true; } else if (static_cast<size_t>(transferred) != bytes.size()) { handle_error_transferred ("实际读入业务数据字节数有误", transferred, bytes.size()); data_transfer_fail = true; } if (data_transfer_fail) { libusb_clear_halt(dev_handle, bulk_in_endpoint); // 清除 IN 端点 STALL 状态 } // 接收CSW状态 if (!read_command_status(dev_handle, bulk_in_endpoint, timeout)) { handle_error("READ(10) 指令执行结果状态有误"); return false; } return !data_transfer_fail; } std::string read_flag_from_usb(libusb_device_handle* dev_handle , uint32_t lba_index, uint32_t block_size , uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint) { assert(block_size > 0); std::cout << "正在从 " << lba_index + 1 << " 块读取身份标志数据……\n"; std::vector<uint8_t> bytes(block_size, '\0'); // 接收缓存区 unsigned int const timeout = 1000; //ms if (!read_bytes_from_lba(dev_handle, bytes , bulk_out_endpoint, bulk_in_endpoint, lba_index, timeout)) { return ""; } std::cout << "读取成功!\n"; std::string flag; auto pos = std::find(bytes.begin(), bytes.end(), '\0'); flag.assign(bytes.begin(), pos); return flag; } // 写出一块大小的字节数据到U盘 bool write_bytes_to_lba(libusb_device_handle* dev_handle , std::vector<uint8_t> const& bytes , uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint , uint32_t lba_index, unsigned int timeout) { // 构造 WRITE(10) 的命令块 CommandBlockWrapper cbw; cbw.dCBWDataTransferLength = bytes.size(); cbw.bmCBWFlags = LIBUSB_ENDPOINT_OUT; // 期待输出 cbw.bCBWCBLength = 10; // 指令+数据长度标志 cbw.CBWCB[0] = 0x2A; // WRITE(10) 指令操作码 // 数据位置(块索引)与大小(固定一块) init_cbwcb_for_lba(cbw, lba_index); // 发送 CBW: if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_out_endpoint , reinterpret_cast<unsigned char*>(&cbw), sizeof(cbw) , &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "发送 WRITE(10) 命令块失败"); return false; } else if (static_cast<size_t>(transferred) != sizeof(cbw)) { handle_error_transferred("写 WRITE(10) 指令,字节数有误", transferred, sizeof(cbw)); return false; } bool data_transfer_fail = false; // 输出数据 if (int transferred = 0, r = libusb_bulk_transfer(dev_handle, bulk_out_endpoint , const_cast<unsigned char*>(bytes.data()) , bytes.size(), &transferred, timeout); r != LIBUSB_SUCCESS) { handle_error(r, "写业务数据失败"); data_transfer_fail = true; } else if (static_cast<size_t>(transferred) != bytes.size()) { handle_error_transferred ("实际写出业务数据字节数有误", transferred, bytes.size()); data_transfer_fail = true; } if (data_transfer_fail) { libusb_clear_halt(dev_handle, bulk_out_endpoint); // 清除 OUT 端点 STALL 状态 } // 接收CSW状态 if (!read_command_status(dev_handle, bulk_in_endpoint, timeout)) { handle_error("WRITE(10) 指令执行结果状态有误"); return false; } return !data_transfer_fail; } // 将指定身份标志串,写入到U盘的指定位置 bool write_flag_to_usb(libusb_device_handle* dev_handle, uint32_t lba_index , uint32_t block_size, uint8_t bulk_out_endpoint, uint8_t bulk_in_endpoint , std::string const& flag) { assert(block_size > 0); assert(flag.size() <= block_size); std::vector<uint8_t> bytes(block_size, '\0'); std::copy(flag.begin(), flag.end(), bytes.begin()); unsigned int timeout = 1200; //ms return write_bytes_to_lba(dev_handle, bytes , bulk_out_endpoint, bulk_in_endpoint, lba_index, timeout); } int main(int argc, char const* argv[]) { std::system("chcp 65001 >nul"); // 设备程序运行的控制台编码为 UTF-8 struct AutoPauser {~AutoPauser() { std::system("pause"); }} auto_pauser; bool write = (argc > 1) && (std::string(argv[1]) == "--write"); if (int r = libusb_init(nullptr); r != LIBUSB_SUCCESS) { handle_error(r, "初始化 libusb 失败!"); return -1; } // 哨兵 1 std::unique_ptr<libusb_context, void(*)(libusb_context*)> GUARD_1 (nullptr, &libusb_exit); uint16_t const vid = 0x058F, pid = 0x6387; if (!verify_device_id_unique(vid, pid)) // 俩ID是否合法:存在且唯一 { return -1; } std::cout << std::hex << std::setfill('0'); std::cout << "将使用 VID: 0x" << std::setw(4) << vid << " PID: 0x" << std::setw(4) << pid << " 打开设备" << std::endl; libusb_device_handle* dev_handle = libusb_open_device_with_vid_pid(nullptr, vid, pid); if (!dev_handle) { handle_error("打开指定ID组合的设备失败"); return -1; } // 哨兵 2 std::unique_ptr<libusb_device_handle, void(*)(libusb_device_handle*)> GUARD_2 (dev_handle, &libusb_close); // 取设备 (取配置描述需要) libusb_device* dev = libusb_get_device(dev_handle); assert(dev != nullptr); // 取当前活动的配置描述数据 libusb_config_descriptor* config_desc = nullptr; if (int r = libusb_get_active_config_descriptor(dev, &config_desc) ; r != LIBUSB_SUCCESS) { handle_error(r, "获取设备当前活动的配置描述数据失败!"); return -1; } assert(config_desc != nullptr); // 哨兵 3 std::unique_ptr<libusb_config_descriptor, void(*)(libusb_config_descriptor*)> GUARD_3 (config_desc, &libusb_free_config_descriptor); // 该配置是否拥有接口? if (config_desc->bNumInterfaces == 0) { std::cerr << "设备描述中,接口个数为0!" << std::endl; return -1; } // 申请占用接口0 (接口0是U盘的标准接口) if (int r = libusb_claim_interface(dev_handle, 0); r != LIBUSB_SUCCESS) { handle_error(r, "申请接口占用失败!"); return -1; } // 哨兵 4 std::unique_ptr<libusb_device_handle, void(*)(libusb_device_handle*)> GUARD_4 (dev_handle, +[](libusb_device_handle* h) { libusb_release_interface(h, 0); // 释放指定设备的 0 号接口占用 }); // 取出接口 libusb_interface const * interface = &(config_desc->interface[0]); // config_desc->interface + 0; // 检查可选设置个数 if (interface->num_altsetting <= 0) { handle_error("接口可选设置个数为0!"); return -1; } // 取到接口的首个描述数据 (忽略后续的) libusb_interface_descriptor const * interface_desc = &(interface->altsetting[0]); // 检查该接口是不是 “大容量存储” if (interface_desc->bInterfaceClass != LIBUSB_CLASS_MASS_STORAGE) { handle_error("该接口不是大容量存储类型!"); return -1; } // 找到该接口下用于批量输出输入的 endpoint: uint8_t bulk_out_endpoint = 0, bulk_in_endpoint = 0; if ( !find_bulk_endpoints(interface_desc , &bulk_out_endpoint, &bulk_in_endpoint)) { return -1; } std::cout << std::hex << std::setfill('0'); std::cout << "将使用输出端点 0x" << std::hex << std::setw(2) << static_cast<int>(bulk_out_endpoint) << ", 输入端点 0x" << std::setw(2) << static_cast<int>(bulk_in_endpoint) << std::endl; // 获取存储容量: USBCapacity capacity = {}; if (!read_usb_capacity(dev_handle , bulk_out_endpoint, bulk_in_endpoint, &capacity)) { return -1; } std::cout << "该USB大容量存储设备的容量是:" << std::dec << capacity.total_bytes << " 字节,块数:" << capacity.max_lba + 1 << ",每块大小:" << capacity.block_size << " 字节" << std::endl; assert(capacity.max_lba >= 9); auto lba_index = capacity.max_lba - 9; // 取倒数第10块 std::string const flag_id = "d2school.com"; if (flag_id.size() > capacity.block_size) { handle_error("身份标志串长度超过了当前USB设备存储块大小!"); return -1; } if (write) { if (!write_flag_to_usb(dev_handle, lba_index, capacity.block_size , bulk_out_endpoint, bulk_in_endpoint, flag_id)) { return -1; } std::cout << "已经成功往专用盘写入您尊贵的身份标志串!请妥善保管!" << std::endl; } // 读取身份标志串: std::string flag = read_flag_from_usb(dev_handle, lba_index , capacity.block_size, bulk_out_endpoint, bulk_in_endpoint); if (flag.empty()) { std::cout << "不是合法的身份认证专用盘,请联系 d2school.com 在线购买!" << std::endl; // 示例,非真实商品 } if (flag != flag_id) { std::cout << "你不是第2学堂的人!" << std::endl; } else { std::cout << "亲,欢迎回到组织!" << std::endl; } }