前言
感谢看雪论坛,和感谢论坛上每位无私奉献大神,让我在这里学习到很多知识。也感谢看雪论坛能让我可以在这里分享自已的小工具。有位朋友问USB外接设备记录实现原理,当时本想单独抽源代码出来,但在抽取过程中,发现引用了很多单元,比如注册表读写单元是自已实现的,而这个单元又引用了很多其他的单元,如字符串处理(前辈实现),抽了20分钟都没有抽出来,很麻烦,所以在这里发一下大致原理。
正文
重点:注册表有一些键值,平时我们看不到,要模拟系统用户才能读取出来。用“Windows 可视化管理”的“进程页”选中“winlogon.exe”,右键-->创建进程-->以此为父进程创建。会弹出一个输入窗口,在进程路径中输入 C:\Windows\regedit.exe 点确定就可以打开注册表。
这时就可以查询到像下面这些不能查询的键值了。
比如注册表中每个USB设备的properties \{83da6326-97a6-4088-9453-a1923f573b29}子键下,只有系统用户可以读取它们。
安装时间: 指定安装USB设备的日期/时间。仅在以管理员身份运行时才能读取此属性。此属性存储在Properties \ {83da6326-97a6-4088-9453-a1923f573b29}子项下,属性号为0064。
首次安装时间: 指定首次安装USB设备的时间。仅在以管理员身份运行时才能读取此属性。该属性存储在Properties \ {83da6326-97a6-4088-9453-a1923f573b29}子项下,属性号为0065。
连接时间: 指定上次插入USB设备的时间。仅在以管理员身份运行时才能读取此属性。此属性仅在Windows 10/8上可用。此属性存储在Properties \ {83da6326-97a6-4088-9453-a1923f573b29}子项下,属性号为0066。
断开时间: 指定上次拔出USB设备的时间。此属性仅在Windows 10/8上可用。仅在以管理员身份运行时才能读取此属性。此属性存储在Properties \ {83da6326-97a6-4088-9453-a1923f573b29}子项下,属性号为0067。
相关代码如下:
type
TMyUSBDeviceInfo = record // 自定义结构,这些从注册表读入的
USBDI_RegKeyPath : array [0..255] of AnsiChar;
USBDI_ContainerID : array [0..38] of AnsiChar;
USBDI_ClassGUID : array [0..38] of AnsiChar;
USBDI_DeviceType, USBDI_DeviceVolumeName : array [0..38] of AnsiChar; //设备类型
USBDI_DeviceSerialNumber : array [0..67] of AnsiChar; // 设备序列号
USBDI_DeviceMfg : array [0..67] of AnsiChar; //制造商
USBDI_FriendlyName : array [0..127] of AnsiChar; //设备名称
USBDI_LocationInformation : array [0..63] of AnsiChar; // 位置 Port_#0001.Hub_#0001
USBDI_SetupDateTime, USBDI_ConnectTime, USBDI_LastUesrDateTime : TDateTime;
end;
TMyUSBDeviceInfoArray = array of TMyUSBDeviceInfo;
function MySetThreadTokenOnProcess(const dwPID : DWORD; const Thread : PHANDLE; const bSetThreadToken : Boolean; var StrErrorInfo : String) : Boolean;
var
process_handle: THandle;
token_handle: THandle;
dup_token_handle: THandle;
token_attributes: SECURITY_ATTRIBUTES;
begin
Result:=False;
StrErrorInfo:='';
if not bSetThreadToken then
begin
Result:=windows.SetThreadToken(Thread, //指向函数向其分配模拟令牌的线程的句柄的指针。
// 如果 Thread 为 NULL,则该函数会将模拟令牌分配给调用线程。
0); // 如果 Token 为 NULL,该函数将导致线程停止使用模拟令牌。
Exit;
end;
process_handle := MyOpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, dwPID, True);
if (process_handle = 0) then
begin
StrErrorInfo:='OpenProcess 失败:' + MyGetErrorToText;
Exit;
end;
// 打开与进程关联的访问令牌。
if (not OpenProcessToken(
process_handle, // 打开其访问令牌的进程的句柄。该进程必须具有 PROCESS_QUERY_INFORMATION 访问权限。
TOKEN_DUPLICATE, // 指定一个访问掩码,该掩码指定对访问令牌的请求访问类型。这些请求的访问类型与令牌的自由访问控制列表(DACL) 进行比较,以确定哪些访问被授予或拒绝。
token_handle)) then // 指向句柄的指针,该句柄在函数返回时标识新打开的访问令牌。
begin
windows.CloseHandle(process_handle);
StrErrorInfo:='OpenProcessToken 失败: ' + MyGetErrorToText;
Exit;
end;
windows.CloseHandle(process_handle);
token_attributes.nLength := sizeof(SECURITY_ATTRIBUTES);
token_attributes.lpSecurityDescriptor := nil;
token_attributes.bInheritHandle := FALSE;
// 创建一个复制现有令牌的新访问令牌。此函数可以创建主令牌或模拟令牌。
if (not DuplicateTokenEx(
token_handle, // 使用 TOKEN_DUPLICATE 访问打开的访问令牌的句柄。
TOKEN_IMPERSONATE, // 指定新令牌的请求访问权限。
//DuplicateTokenEx函数将请求的访问权限与现有令牌的自主访问控制列表(DACL) 进行比较,以确定授予或拒绝哪些权限。
//要请求与现有令牌相同的访问权限,请指定零。要请求对调用者有效的所有访问权限,请指定 MAXIMUM_ALLOWED。
@token_attributes, // 指向 SECURITY_ATTRIBUTES结构的指针,该结构指定新令牌的安全描述符并确定子进程是否可以继承令牌。
//如果lpTokenAttributes为NULL,则令牌获取默认安全描述符,并且无法继承句柄。
//如果安全描述符包含系统访问控制列表(SACL),则令牌将获得 ACCESS_SYSTEM_SECURITY 访问权限,即使它没有在dwDesiredAccess中请求。
//要在新令牌的安全描述符中设置所有者,调用者的进程令牌必须具有SE_RESTORE_NAME权限集。
SecurityImpersonation, // 从 SECURITY_IMPERSONATION_LEVEL 枚举中指定一个值,该值指示新令牌的模拟级别。
// 安全模拟 服务器进程可以在其本地系统上模拟客户端的安全上下文。服务器无法在远程系统上模拟客户端。
TokenImpersonation, //表示模拟令牌。
dup_token_handle) //指向接收新标记的HANDLE变量的指针。
) then
begin
windows.CloseHandle(token_handle);
StrErrorInfo:='DuplicateTokenEx 失败:' + MyGetErrorToText;
Exit;
end;
windows.CloseHandle(token_handle);
if windows.SetThreadToken(Thread, //指向函数向其分配模拟令牌的线程的句柄的指针。
// 如果 Thread 为 NULL,则该函数会将模拟令牌分配给调用线程。
dup_token_handle // 要分配给线程的模拟令牌的句柄。 此句柄必须已使用 TOKEN_IMPERSONATE 访问权限打开。
// 如果 Token 为 NULL,该函数将导致线程停止使用模拟令牌。
) then
begin
Result:=True;
end
else
begin
StrErrorInfo:='SetThreadToken 失败:' + MyGetErrorToText;
end;
windows.CloseHandle(dup_token_handle);
end;
// 自己简单封装的 RegOpenKeyEx
function MyRegOpenKeyEx(const RootKey : HKEY; const lpSubKey : PChar; const KEYsamDesired : REGSAM; const isKEY_WOW64_64KEY : Boolean; var phkResult : HKEY) : Boolean;
var
samDesired : REGSAM;
begin
samDesired:=KEYsamDesired;
if isKEY_WOW64_64KEY and G_IsWin64 then
begin
samDesired:=samDesired or KEY_WOW64_64KEY; // 注意,这里是禁止重定向
end;
Result:= ERROR_SUCCESS = RegOpenKeyEx(RootKey, lpSubKey, 0, samDesired, phkResult);
end;
// 自己简单封装的 RegEnumKeyEx
function MyRegEnumKeyNameToList(const phkResult: HKEY; var KeyNamesList: TStringList) : Boolean;
var
I : Integer;
dwDataSize, dwLen : DWORD;
status : DWORD;
buffer : array [0..255] of AnsiChar; //255 个字符
begin
Result:=False;
if not Assigned(KeyNamesList) then exit;
dwLen:=Length(buffer);
I:=0;
repeat
ZeroMemory(@buffer[0], dwLen);
dwDataSize:=dwLen;
status:=RegEnumKeyEx(phkResult, // 一个已打开项的句柄,或者指定一个标准项名
I, // 欲获取的子项的索引。第一个子项的索引编号为零
buffer, //用于装载指定索引处项名的一个缓冲区
dwDataSize, // 指定一个变量,用于装载lpName缓冲区的实际长度(包括空字符)。一旦返回,它会设为实际装载到lpName缓冲区的字符数量
nil, //未用,设为零
nil, //项使用的类名。可以为vbNullString
nil, // 用于装载lpClass缓冲区长度的一个变量。一旦返回,它会设为实际装载到缓冲区的字符数量
nil); // 枚举子项上一次修改的时间
if ERROR_SUCCESS = status then //枚举子健
begin
KeyNamesList.Add(buffer);
Inc(I);
end;
until status <> ERROR_SUCCESS; // 返回值, 零(ERROR_SUCCESS)表示成功。其他任何值都代表一个错误代码
Result:=I > 0;
end;
// 取出注册表中二进制(数组长度为8)的值转换为时间
function MyRegGetPropertiesDateTime(const RootKey : HKEY; const lpSubKey : PChar; const isKEY_WOW64_64KEY : Boolean; var DTTime : TDateTime) : Boolean;
var
//RegValue :PChar;
samDesired : REGSAM;
phkResult : HKEY;
AData: TMyArrayOfBytes;
dwOutBufSize : DWORD;
//iValue64 : Int64;
begin
Result:=False;
DTTime:=0;
samDesired:=KEY_READ or KEY_QUERY_VALUE;
if isKEY_WOW64_64KEY and G_IsWin64 then
begin
samDesired:=samDesired or KEY_WOW64_64KEY; // 注意,这里是禁止重定向
end;
if ERROR_SUCCESS = RegOpenKeyEx(RootKey, lpSubKey, 0, samDesired, phkResult) then
begin
if MyRegQueryBinaryValueEx(phkResult, '', AData, dwOutBufSize) then
begin
DTTime:=MyRegBinaryValueToDateTime(AData, 0, dwOutBufSize);
Result:=True;
SetLength(AData, 0);
end
else
begin
Result:=MyRegGetKeyLastWriteTime(phkResult, DTTime);
end;
RegCloseKey(phkResult);
end;
end;
// 读取注册表USB设备信息到 TMyUSBDeviceInfoArray
function MyGetUSBDeviceUserInfo(var MyUSBDeviceInfo : TMyUSBDeviceInfoArray) : Boolean;
var
I, J, iLen, iTtemCount : Integer;
phkResult, phkResult2, phkResult3, hkKeyLastUesrDateTime : HKEY;
KeyNamesList1, KeyNamesList2, KeyNamesList3: TStringList;
StrKeyName, StrKey_WPDBUSENUM : PAnsiChar;
StrFriendlyName, StrClassGUID, StrSerialNumber, StrLastUesrDateTime, StrMfg, StrVolumeName,
StrSetupDateTime, StrConnectTime, StrKeyName2, StrKeyName3, StrProperties, StrContainerID, StrService, StrErrorInfo : string;
DTLastWriteTime, DTConnectTime, DTSetupDateTime : TDateTime;
begin
Result:=False;
KeyNamesList1:=TStringList.Create;
KeyNamesList2:=TStringList.Create;
KeyNamesList3:=TStringList.Create;
// 接入过的 USB 设备都保存在这里
iLen:=0; // 数组长度
iTtemCount:=0;
StrKeyName:='SYSTEM\CurrentControlSet\Enum\USB\';
if MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, StrKeyName, KEY_READ or KEY_QUERY_VALUE, True, phkResult) then
begin
if MyRegEnumKeyNameToList(phkResult, KeyNamesList1) then
begin
for I:=0 to KeyNamesList1.Count-1 do
begin
StrKeyName2:=StrKeyName + KeyNamesList1[I];
if MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, PAnsiChar(StrKeyName2), KEY_READ or KEY_QUERY_VALUE, True, phkResult2) then
begin
KeyNamesList2.Clear;
if MyRegEnumKeyNameToList(phkResult2, KeyNamesList2) then
begin
for J:=0 to KeyNamesList2.Count-1 do
begin
StrKeyName3:= StrKeyName2 + '\' + KeyNamesList2[J];
if MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, PAnsiChar(StrKeyName3), KEY_READ or KEY_QUERY_VALUE, True, phkResult3) then
begin
if MyRegQueryStringValueEx(phkResult3, 'Service', StrService) and (SameText(StrService, 'USBSTOR')) then
begin
if MyRegQueryStringValueEx(phkResult3, 'ContainerID', StrContainerID) then // 通过这里来定位
begin
MyRegQueryStringValueEx(phkResult3, 'LocationInformation', StrMfg);
Inc(iLen);// 数组长度
SetLength(MyUSBDeviceInfo, iLen);
with MyUSBDeviceInfo[iLen - 1] do
begin
StringCbCopyA(@USBDI_RegKeyPath[0], SizeOf(USBDI_RegKeyPath), PAnsiChar(StrKeyName3));
StringCbCopyA(@USBDI_ContainerID[0], SizeOf(USBDI_ContainerID), PAnsiChar(StrContainerID));
StringCbCopyA(@USBDI_LocationInformation[0], SizeOf(USBDI_LocationInformation), PAnsiChar(StrMfg));
end;
end;
end;
RegCloseKey(phkResult3);
end;
end;
end;
RegCloseKey(phkResult2);
end;
end;
end;
RegCloseKey(phkResult);
end;
if (iLen > 0) and MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Enum\USBSTOR', KEY_READ or KEY_QUERY_VALUE, True, phkResult) then
begin
if MyRegEnumKeyNameToList(phkResult, KeyNamesList1) then
begin
if MySetThreadTokenOnProcess(MyGetProcessPIDByNameEx('winlogon.exe', 3), nil, True, StrErrorInfo) then
begin
for I:=0 to KeyNamesList1.Count -1 do
begin
StrKeyName:=PAnsiChar('SYSTEM\CurrentControlSet\Enum\USBSTOR' + '\' + KeyNamesList1[I]);
if MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, StrKeyName, KEY_READ or KEY_QUERY_VALUE, True, phkResult2) then
begin
KeyNamesList2.Clear;
if MyRegEnumKeyNameToList(phkResult2, KeyNamesList2) then
begin
StrSerialNumber:=KeyNamesList2[0];
StrKeyName2:=StrKeyName + '\' + StrSerialNumber;
StrConnectTime:='';
StrLastUesrDateTime:='';
StrSetupDateTime:='';
if MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, PAnsiChar(StrKeyName2), KEY_READ or KEY_QUERY_VALUE, True, phkResult3) then
begin
if MyRegQueryStringValueEx(phkResult3, 'ContainerID', StrContainerID) then
begin
for J:=Low(MyUSBDeviceInfo) to High(MyUSBDeviceInfo) do
begin
if SameText(StrContainerID, MyUSBDeviceInfo[J].USBDI_ContainerID) then
begin
StrProperties:=MyUSBDeviceInfo[J].USBDI_RegKeyPath + '\Properties\{83da6326-97a6-4088-9453-a1923f573b29}\';
// 首次安装时间: 仅在以管理员身份运行时才能读取此属性。
// 该属性存储在Properties \ {83da6326-97a6-4088-9453-a1923f573b29}子项下,属性号为0065。
if MyRegGetPropertiesDateTime(HKEY_LOCAL_MACHINE, PAnsiChar(StrProperties + '0065'), True, DTSetupDateTime) then
begin
end
//安装时间
else if MyRegGetPropertiesDateTime(HKEY_LOCAL_MACHINE, PAnsiChar(StrProperties + '0064'), True, DTSetupDateTime) then
begin
end;
// 所有时间戳均采用标准 Windows 64 位 (FILETIME) 格式。
// 最近插入时间 上一次到达日期
MyRegGetPropertiesDateTime(HKEY_LOCAL_MACHINE, PAnsiChar(StrProperties + '0066'), True, DTConnectTime);
// 最近拔出时间 断开时间 上一次删除日期
MyRegGetPropertiesDateTime(HKEY_LOCAL_MACHINE, PAnsiChar(StrProperties + '0067'), True, DTLastWriteTime);
// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\STORAGE\Volume\**
MyRegQueryStringValueEx(phkResult3, 'FriendlyName', StrFriendlyName);
MyRegQueryStringValueEx(phkResult3, 'ClassGUID', StrClassGUID);
StrClassGUID:=MyGetUSBDeviceClassGUIDToStr(StrClassGUID);
StrKey_WPDBUSENUM:=PAnsiChar('SYSTEM\CurrentControlSet\Enum\SWD\WPDBUSENUM\_??_USBSTOR#' + KeyNamesList1[I] + '#' + StrSerialNumber + '#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}');
if MyRegOpenKeyEx(HKEY_LOCAL_MACHINE, StrKey_WPDBUSENUM, KEY_READ or KEY_QUERY_VALUE, True, hkKeyLastUesrDateTime) then
begin
MyRegQueryStringValueEx(hkKeyLastUesrDateTime, 'Mfg', StrMfg);
MyRegQueryStringValueEx(hkKeyLastUesrDateTime, 'FriendlyName', StrVolumeName);
RegCloseKey(hkKeyLastUesrDateTime);
end
else
begin
StrMfg:='';
StrVolumeName:='';
end;
with MyUSBDeviceInfo[J] do
begin
StringCbCopyA(@USBDI_FriendlyName[0], SizeOf(USBDI_FriendlyName), PAnsiChar(StrFriendlyName));
StringCbCopyA(@USBDI_DeviceType[0], SizeOf(USBDI_DeviceType), PAnsiChar(StrClassGUID)); //设备类型
StrSerialNumber:=MyGetUSBSerialNumberOnRegKey(StrSerialNumber);
StringCbCopyA(@USBDI_DeviceSerialNumber[0], SizeOf(USBDI_DeviceSerialNumber), PAnsiChar(StrSerialNumber)); // 设备序列号
StringCbCopyA(@USBDI_DeviceMfg[0], SizeOf(USBDI_DeviceMfg), PAnsiChar(StrMfg)); // 制造商
StringCbCopyA(@USBDI_DeviceVolumeName[0], SizeOf(USBDI_DeviceVolumeName), PAnsiChar(StrVolumeName)); // 卷标
USBDI_SetupDateTime:=DTSetupDateTime; // 安装时间
USBDI_ConnectTime:=DTConnectTime; // 最近插入时间 连接时间
USBDI_LastUesrDateTime:=DTLastWriteTime; // 最近拔出时间 最后一次使用时间 断开时间
end;
Inc(iTtemCount);
Break;
end;
end;
end;
RegCloseKey(phkResult3);
end;
end;
RegCloseKey(phkResult2);
end;
end;
MySetThreadTokenOnProcess(0, nil, False, StrErrorInfo);
end
else
begin
MyAddDebugInfo('Err Reg Token:' + StrErrorInfo);
end;
end;
KeyNamesList1.Free;
KeyNamesList2.Free;
KeyNamesList3.Free;
RegCloseKey(phkResult);
end;
Result:=iTtemCount > 0;
end;
// 查询 USB 插拔记录
procedure TWindowsVisualManage_MainForm.PopupMenu_other2_USBSTORClick(
Sender: TObject);
var
cListItem : TListItem;
cListColumn : TListColumn;
MyUSBDeviceInfo : TMyUSBDeviceInfoArray;
I : Integer;
begin
MyPopupMenu_other2_AsClick(Sender); // 当初考虑把所有查询功能都共用一个"ListView"控件,以节省不必要的开销。
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='设备名称';
cListColumn.Width:=250;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='设备类型';
cListColumn.Width:=68;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='设备位置';
cListColumn.Width:=132;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='设备序列号';
cListColumn.Width:=158;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='制造商';
cListColumn.Width:=82;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='卷标';
cListColumn.Width:=82;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='首次插入时间';
cListColumn.Width:=126;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='最近插入时间'; // 连接时间
cListColumn.Width:=126;
cListColumn:=ListView_VCLShareQuery.Columns.Add;
cListColumn.Caption:='最近拔出时间'; // 断开时间
cListColumn.Width:=126;
if MyGetUSBDeviceUserInfo(MyUSBDeviceInfo) then
begin
for I:=Low(MyUSBDeviceInfo) to High(MyUSBDeviceInfo) do
begin
with MyUSBDeviceInfo[I] do
begin
cListItem:=ListView_VCLShareQuery.Items.Add;
cListItem.Caption:=USBDI_FriendlyName; //设备名称
cListItem.SubItems.Add(USBDI_DeviceType); //设备类型
cListItem.SubItems.Add(USBDI_LocationInformation); // 位置
cListItem.SubItems.Add(USBDI_DeviceSerialNumber); //设备序列号
cListItem.SubItems.Add(USBDI_DeviceMfg); //制造商
cListItem.SubItems.Add(USBDI_DeviceVolumeName); // 卷标
cListItem.SubItems.Add(FormatDateTime('yyyy/mm/dd hh:mm:ss', USBDI_SetupDateTime)); // 安装时间
cListItem.SubItems.Add(FormatDateTime('yyyy/mm/dd hh:mm:ss', USBDI_ConnectTime)); // 连接时间
cListItem.SubItems.Add(FormatDateTime('yyyy/mm/dd hh:mm:ss', USBDI_LastUesrDateTime)); // 最后一次使用时间 断开时间
end;
end;
StatusBar1.Panels.Items[4].Text:='USB外接设备记录:' + IntToStr(ListView_VCLShareQuery.Items.Count);
end;
end;
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2024-5-9 15:02
被kagayaki编辑
,原因: