5.10 Windows驅動開發:摘除InlineHook內核鉤子

在筆者上一篇文章《內核層InlineHook掛鉤函數》中介紹了通過替換函數頭部代碼的方式實現Hook掛鉤,對于ARK工具來說實現掃描與摘除InlineHook鉤子也是最基本的功能,此類功能的實現一般可在應用層進行,而驅動層只需要保留一個讀寫字節的函數即可,將復雜的流程放在應用層實現是一個非常明智的選擇,與《內核實現進程反匯編》中所使用的讀寫驅動基本一致,本篇文章中的驅動只保留兩個功能,控制信號IOCTL_GET_CUR_CODE用于讀取函數的前16個字節的內存,信號IOCTL_SET_ORI_CODE則用于設置前16個字節的內存。

之所以是前16個字節是因為一般的內聯Hook只需要使用兩條指令就可實現劫持,如下是通用ARK工具掃描到的被掛鉤函數的樣子。

首先將內核驅動程序代碼放到如下,內核驅動程序沒有任何特別的,僅僅只是一個通用驅動模板,在其基礎上使用CR3讀寫,如果不理解CR3讀寫的原理您可以去看《內核CR3切換讀寫內存》這一篇中的詳細介紹。

#include <ntifs.h>
#include <intrin.h>
#include <windef.h>#define DEVICE_NAME         L"\\Device\\WinDDK"
#define LINK_NAME           L"\\DosDevices\\WinDDK"
#define LINK_GLOBAL_NAME    L"\\DosDevices\\Global\\WinDDK"// 控制信號 IOCTL_GET_CUR_CODE 用于讀 | IOCTL_SET_ORI_CODE 用于寫
#define IOCTL_GET_CUR_CODE  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SET_ORI_CODE  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)// 引用__readcr0等函數必須增加
#pragma intrinsic(_disable)
#pragma intrinsic(_enable)// 定義讀寫結構體
typedef struct
{PVOID Address;ULONG64 Length;UCHAR data[256];
} KF_DATA, *PKF_DATA;KIRQL g_irql;// 關閉寫保護
void WPOFFx64()
{ULONG64 cr0;g_irql = KeRaiseIrqlToDpcLevel();cr0 = __readcr0();cr0 &= 0xfffffffffffeffff;__writecr0(cr0);_disable();
}// 開啟寫保護
void WPONx64()
{ULONG64 cr0;cr0 = __readcr0();cr0 |= 0x10000;_enable();__writecr0(cr0);KeLowerIrql(g_irql);
}// 設備創建時觸發
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;DbgPrint("[LyShark] 設備已創建 \n");IoCompleteRequest(pIrp, IO_NO_INCREMENT);return STATUS_SUCCESS;
}// 設備關閉時觸發
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;DbgPrint("[LyShark] 設備已關閉 \n");IoCompleteRequest(pIrp, IO_NO_INCREMENT);return STATUS_SUCCESS;
}// 主派遣函數
NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;PIO_STACK_LOCATION pIrpStack;ULONG uIoControlCode;PVOID pIoBuffer;ULONG uInSize;ULONG uOutSize;// 獲取當前設備棧pIrpStack = IoGetCurrentIrpStackLocation(pIrp);uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;// 獲取緩沖區pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;// 獲取緩沖區長度uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;// 輸出緩沖區長度uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;switch (uIoControlCode){// 讀內存case IOCTL_GET_CUR_CODE:{KF_DATA dat = { 0 };// 將緩沖區格式化為KF_DATA結構體RtlCopyMemory(&dat, pIoBuffer, 16);WPOFFx64();// 將數據寫回到緩沖區RtlCopyMemory(pIoBuffer, dat.Address, dat.Length);WPONx64();status = STATUS_SUCCESS;break;}// 寫內存case IOCTL_SET_ORI_CODE:{KF_DATA dat = { 0 };// 將緩沖區格式化為KF_DATA結構體RtlCopyMemory(&dat, pIoBuffer, sizeof(KF_DATA));WPOFFx64();// 將數據寫回到緩沖區RtlCopyMemory(dat.Address, dat.data, dat.Length);WPONx64();status = STATUS_SUCCESS;break;}}if (status == STATUS_SUCCESS)pIrp->IoStatus.Information = uOutSize;elsepIrp->IoStatus.Information = 0;pIrp->IoStatus.Status = status;IoCompleteRequest(pIrp, IO_NO_INCREMENT);return status;
}// 驅動卸載
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{UNICODE_STRING strLink;// 刪除符號鏈接卸載設備RtlInitUnicodeString(&strLink, LINK_NAME);IoDeleteSymbolicLink(&strLink);IoDeleteDevice(pDriverObj->DeviceObject);
}// 驅動程序入口
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{NTSTATUS status = STATUS_SUCCESS;UNICODE_STRING ustrLinkName;UNICODE_STRING ustrDevName;PDEVICE_OBJECT pDevObj;// 初始化派遣函數pDriverObj->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;pDriverObj->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;DbgPrint("hello lysahrk.com \n");// 初始化設備名RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);// 創建設備status = IoCreateDevice(pDriverObj, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);if (!NT_SUCCESS(status)){return status;}// 創建符號鏈接RtlInitUnicodeString(&ustrLinkName, LINK_NAME);status = IoCreateSymbolicLink(&ustrLinkName, &ustrDevName);if (!NT_SUCCESS(status)){IoDeleteDevice(pDevObj);return status;}pDriverObj->DriverUnload = DriverUnload;return STATUS_SUCCESS;
}

接著來分析下應用層做了什么,首先GetKernelBase64函數的作用,該函數內部通過GetProcAddress()函數動態尋找到ZwQuerySystemInformation()函數的內存地址(此函數未被到處所以只能動態找到),找到后調用ZwQuerySystemInformation()直接拿到系統中的所有模塊信息,通過pSystemModuleInformation->Module[0].Base得到系統中第一個模塊的基地址,此模塊就是ntoskrnl.exe,該模塊也是系統運行后的第一個啟動的,此時我們即可拿到KernelBase也就是系統內存中的基地址。

此時通過LoadLibraryExA()函數動態加載,此時加載的是磁盤中的被Hook函數的所屬模塊,獲得映射地址后將此地址裝入hKernel變量內,此時我們擁有了內存中的KernelBase以及磁盤中加載的hKernel,接著調用RepairRelocationTable()讓兩者的重定位表保持一致。

此時當用戶調用GetSystemRoutineAddress()則執行如下流程,想要獲取當前內存地址,則需要使用當前內存中的KernelBase模塊基址加上通過GetProcAddress()動態獲取到的磁盤基址中的函數地址減去磁盤中的基地址,將內存中的KernelBase加上磁盤中的相對偏移就得到了當前內存中加載函數的實際地址。

  • address1 = KernelBase + (ULONG64)GetProcAddress(hKernel, “NtWriteFile”) - (ULONG64)hKernel
  • address2 = KernelBase - (ULONG64)hKernel + (ULONG64)GetProcAddress(hKernel, “NtWriteFile”)

調用GetOriginalMachineCode()則用于獲取相對偏移地址,該地址的獲取方式如下,用戶傳入一個Address當前地址,該地址減去KernelBase內存中的基址,然后再加上hKernel磁盤加載的基址來獲取到相對偏移。

  • OffsetAddress = Address - KernelBase + hKernel

有了這兩條信息那么功能也就實現了,通過GetOriginalMachineCode()得到指定內存地址處原始機器碼,通過GetCurrentMachineCode()得到當前內存機器碼,兩者通過memcmp()函數比對即可知道是否被掛鉤了,如果被掛鉤則可以通過CR3切換將原始機器碼覆蓋到特定位置替換即可,這段程序的完整代碼如下;

#include <stdio.h>
#include <Windows.h>#pragma comment(lib,"user32.lib")
#pragma comment(lib,"Advapi32.lib")#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#endif#define BYTE_ARRAY_LENGTH 16
#define SystemModuleInformation 11
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)typedef long(__stdcall *ZWQUERYSYSTEMINFORMATION)
(IN ULONG SystemInformationClass,IN PVOID SystemInformation,IN ULONG SystemInformationLength,IN PULONG ReturnLength OPTIONAL
);typedef struct
{ULONG Unknow1;ULONG Unknow2;ULONG Unknow3;ULONG Unknow4;PVOID Base;ULONG Size;ULONG Flags;USHORT Index;USHORT NameLength;USHORT LoadCount;USHORT ModuleNameOffset;char ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;typedef struct
{ULONG Count;SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;typedef struct
{PVOID Address;ULONG64 Length;UCHAR data[256];
} KF_DATA, *PKF_DATA;HANDLE hDriver = 0;
HMODULE hKernel = 0;
ULONG64 KernelBase = 0;
CHAR NtosFullName[260] = { 0 };// 生成控制信號
DWORD CTL_CODE_GEN(DWORD lngFunction)
{return (FILE_DEVICE_UNKNOWN * 65536) | (FILE_ANY_ACCESS * 16384) | (lngFunction * 4) | METHOD_BUFFERED;
}// 發送控制信號的函數
BOOL IoControl(HANDLE hDrvHandle, DWORD dwIoControlCode, PVOID lpInBuffer, DWORD nInBufferSize, PVOID lpOutBuffer, DWORD nOutBufferSize)
{DWORD lDrvRetSize;return DeviceIoControl(hDrvHandle, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, &lDrvRetSize, 0);
}// 動態獲取ntdll.dll模塊的基地址
ULONG64 GetKernelBase64(PCHAR NtosName)
{ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation;PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;ULONG NeedSize, BufferSize = 0x5000;PVOID pBuffer = NULL;NTSTATUS Result;// 該函數只能通過動態方式得到地址ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation");do{pBuffer = malloc(BufferSize);if (pBuffer == NULL) return 0;// 查詢系統中的所有模塊信息Result = ZwQuerySystemInformation(SystemModuleInformation, pBuffer, BufferSize, &NeedSize);if (Result == STATUS_INFO_LENGTH_MISMATCH){free(pBuffer);BufferSize *= 2;}else if (!NT_SUCCESS(Result)){free(pBuffer);return 0;}} while (Result == STATUS_INFO_LENGTH_MISMATCH);// 取模塊信息結構pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)pBuffer;// 得到模塊基地址ULONG64 ret = (ULONG64)(pSystemModuleInformation->Module[0].Base);// 拷貝模塊名if (NtosName != NULL){strcpy(NtosName, pSystemModuleInformation->Module[0].ImageName + pSystemModuleInformation->Module[0].ModuleNameOffset);}free(pBuffer);return ret;
}// 判斷并修復重定位表
BOOL RepairRelocationTable(ULONG64 HandleInFile, ULONG64 BaseInKernel)
{PIMAGE_DOS_HEADER       pDosHeader;PIMAGE_NT_HEADERS64     pNtHeader;PIMAGE_BASE_RELOCATION  pRelocTable;ULONG i, dwOldProtect;// 得到DOS頭并判斷是否符合DOS規范pDosHeader = (PIMAGE_DOS_HEADER)HandleInFile;if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE){return FALSE;}// 得到Nt頭pNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)HandleInFile + pDosHeader->e_lfanew);// 是否存在重定位表if (pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size){// 獲取到重定位表基地址pRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG64)HandleInFile + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);do{// 得到重定位號ULONG   numofReloc = (pRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;SHORT   minioffset = 0;// 得到重定位數據PUSHORT pRelocData = (PUSHORT)((ULONG64)pRelocTable + sizeof(IMAGE_BASE_RELOCATION));// 循環或直接判斷*pRelocData是否為0也可以作為結束標記for (i = 0; i<numofReloc; i++){// 需要重定位的地址PULONG64 RelocAddress;// 重定位的高4位是重定位類型,判斷重定位類型if (((*pRelocData) >> 12) == IMAGE_REL_BASED_DIR64){// 計算需要進行重定位的地址// 重定位數據的低12位再加上本重定位塊頭的RVA即真正需要重定位的數據的RVAminioffset = (*pRelocData) & 0xFFF; // 小偏移// 模塊基址+重定位基址+每個數據表示的小偏移量RelocAddress = (PULONG64)(HandleInFile + pRelocTable->VirtualAddress + minioffset);// 直接在RING3修改: 原始數據+基址-IMAGE_OPTINAL_HEADER中的基址VirtualProtect((PVOID)RelocAddress, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 因為是R3直接LOAD的所以要修改一下內存權限*RelocAddress = *RelocAddress + BaseInKernel - pNtHeader->OptionalHeader.ImageBase;VirtualProtect((PVOID)RelocAddress, 4, dwOldProtect, NULL);}// 下一個重定位數據pRelocData++;}// 下一個重定位塊pRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG64)pRelocTable + pRelocTable->SizeOfBlock);} while (pRelocTable->VirtualAddress);return TRUE;}return FALSE;
}// 初始化
BOOL InitEngine(BOOL IsClear)
{if (IsClear == TRUE){// 動態獲取ntdll.dll模塊的基地址KernelBase = GetKernelBase64(NtosFullName);printf("模塊基址: %llx | 模塊名: %s \n", KernelBase, NtosFullName);if (!KernelBase){return FALSE;}// 動態加載模塊到內存,并獲取到模塊句柄hKernel = LoadLibraryExA(NtosFullName, 0, DONT_RESOLVE_DLL_REFERENCES);if (!hKernel){return FALSE;}// 判斷并修復重定位表if (!RepairRelocationTable((ULONG64)hKernel, KernelBase)){return FALSE;}return TRUE;}else{FreeLibrary(hKernel);return TRUE;}
}// 獲取原始函數機器碼
VOID GetOriginalMachineCode(ULONG64 Address, PUCHAR ba, SIZE_T Length)
{ULONG64 OffsetAddress = Address - KernelBase + (ULONG64)hKernel;RtlCopyMemory(ba, (PVOID)OffsetAddress, Length);
}// 獲取傳入函數的內存地址
ULONG64 GetSystemRoutineAddress(PCHAR FuncName)
{return KernelBase + (ULONG64)GetProcAddress(hKernel, FuncName) - (ULONG64)hKernel;
}// 獲取當前函數機器碼
VOID GetCurrentMachineCode(ULONG64 Address, PUCHAR ba, SIZE_T Length)
{ULONG64 dat[2] = { 0 };dat[0] = Address;dat[1] = Length;IoControl(hDriver, CTL_CODE_GEN(0x800), dat, 16, ba, Length);
}// 清除特定位置的機器碼
VOID ClearInlineHook(ULONG64 Address, PUCHAR ba, SIZE_T Length)
{KF_DATA dat = { 0 };dat.Address = (PVOID)Address;dat.Length = Length;// 直接調用寫出控制碼RtlCopyMemory(dat.data, ba, Length);IoControl(hDriver, CTL_CODE_GEN(0x801), &dat, sizeof(KF_DATA), 0, 0);
}// 打印數據
VOID PrintBytes(PCHAR DescriptionString, PUCHAR ba, UINT Length)
{printf("%s", DescriptionString);for (UINT i = 0; i<Length; i++){printf("%02x ", ba[i]);}printf("\n");
}int main(int argc, char *argv[])
{UCHAR OriginalMachineCode[BYTE_ARRAY_LENGTH];UCHAR CurrentMachineCode[BYTE_ARRAY_LENGTH];ULONG64 Address = 0;hDriver = CreateFileA("\\\\.\\WinDDK", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);// 初始化if (!InitEngine(TRUE) || hDriver == 0){return 0;}// 需要獲取的函數列表CHAR *FunctionList[128] = { "PsLookupProcessByProcessId", "NtCommitEnlistment", "NtCommitComplete", "NtCommitTransaction" };for (size_t i = 0; i < 4; i++){// 清空緩存RtlZeroMemory(OriginalMachineCode, 0, BYTE_ARRAY_LENGTH);RtlZeroMemory(CurrentMachineCode, 0, BYTE_ARRAY_LENGTH);// 獲取到當前函數地址Address = GetSystemRoutineAddress(FunctionList[i]);printf("\n函數地址: %p | 函數名: %s\n", Address, FunctionList[i]);if (Address == 0 || Address < KernelBase){return 0;}GetOriginalMachineCode(Address, OriginalMachineCode, BYTE_ARRAY_LENGTH);PrintBytes("原始機器碼: ", OriginalMachineCode, BYTE_ARRAY_LENGTH);GetCurrentMachineCode(Address, CurrentMachineCode, BYTE_ARRAY_LENGTH);PrintBytes("當前機器碼: ", CurrentMachineCode, BYTE_ARRAY_LENGTH);/*// 不相同則詢問是否恢復if (memcmp(OriginalMachineCode, CurrentMachineCode, BYTE_ARRAY_LENGTH)){printf("按下[ENTER]恢復鉤子");getchar();ClearInlineHook(Address, OriginalMachineCode, BYTE_ARRAY_LENGTH);}*/}// 注銷InitEngine(FALSE);system("pause");return 0;
}

首先編譯驅動程序WinDDK.sys并通過KmdManager將驅動程序拉起來,運行客戶端lyshark.exe程序會輸出當前FunctionList列表中,指定的4個函數的掛鉤情況。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/207980.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207980.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207980.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

得帆云助力容百科技構建CRM系統,實現LTC全流程管理

寧波容百新能源科技股份有限公司 寧波容百新能源科技股份有限公司&#xff08;以下簡稱“容百科技”&#xff09;于2014年9月建立&#xff0c;是高科技新能源材料行業的跨國型集團公司。專業從事鋰電池正極材料的研發、生產和銷售&#xff0c;于2019年登陸上交所科創板&#x…

Python 數據分析:日期型數據的玩轉之道

更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com 在數據分析的領域中&#xff0c;處理日期型數據是至關重要的一環。Python 提供了豐富的工具和庫&#xff0c;使得對日期進行分析、處理、可視化變得更加輕松。本文將深入探討 Python 中如何玩轉日期型數據&#…

連鎖零售企業如何優化網絡性能?

在傳統的WAN網絡中&#xff0c;分支機構通常通過專線或者MPLS連接到總部或數據中心&#xff0c;但這種連接受制于地理位置。而SD-WAN&#xff08;Software-Defined Wide Area Network&#xff09;這種創新的網絡架構&#xff0c;它通過軟件定義和虛擬化技術&#xff0c;將分支機…

Javascript 前端分頁——根據頁面大小(pageSize)和總行數(total)計算總頁面數(totalPage)

分頁時&#xff0c;根據頁面大小&#xff08;pageSize&#xff09;和總行數&#xff08;total&#xff09;&#xff0c;計算總頁面數&#xff08;totalPage&#xff09; 一&#xff1a;總行數取余頁面大小&#xff0c;等于0&#xff0c;則頁數為整頁數&#xff0c;否則有余數&a…

解讀鏈上經濟“一等公民”:加密AI代理的優勢和前沿應用

機器人正在成為加密經濟的“一等公民”&#xff0c;最近的案例就能印證這一趨勢。 搜索者&#xff08;Searchers&#xff09;部署像Jaredfromsubway.eth這樣的機器人&#xff0c;利用真人用戶對便利的渴望在DEX搶先交易。Banana Gun和Maestro允許真人用戶通過Telegram的便利進…

力扣每日一題day31[101. 對稱二叉樹]

給你一個二叉樹的根節點 root &#xff0c; 檢查它是否軸對稱。 示例 1&#xff1a; 輸入&#xff1a;root [1,2,2,3,4,4,3] 輸出&#xff1a;true示例 2&#xff1a; 輸入&#xff1a;root [1,2,2,null,3,null,3] 輸出&#xff1a;fals 思路 對于二叉樹是否對稱&#xff…

二分查找算法

文章目錄 二分查找二分的實戰講解二分查找普通二分模版 在排序數組中查找元素的第一個和最后一個位置萬能二分模版 總結 二分查找 什么是二分查找:就是定義左右2個指針(此指針非真指針)取中間值 通過一次次取中間值找到要找到的數 二分的實戰講解 二分查找 題目:地址 題目解析…

ELK技術棧介紹及簡單使用實例

1. ELK技術棧介紹 引言 在當今數據驅動的世界里&#xff0c;有效地管理和分析大量日志數據變得至關重要。這里我們將深入探討ELK技術棧&#xff0c;這是一種流行的日志管理解決方案&#xff0c;它結合了三個開源項目&#xff1a;Elasticsearch、Logstash和Kibana。ELK技術棧因…

測試文檔---智力沖刺

文章目錄 項目背景測試計劃UI測試接口測試手工測試 測試總結 項目背景 項目描述&#xff1a;“智力沖刺”是一款網頁小游戲&#xff0c;就像我們平時看到的網頁游戲一樣&#xff0c;前端頁面負責展示游戲效果&#xff0c;后端服務器來實現游戲的邏輯。在我們的“智力沖刺”游戲…

YOLOv7 學習筆記

文章目錄 前言一、YOLOv7貢獻和改進二、YOLOv7核心概念三、YOLOv7架構改進總結 前言 在深度學習和計算機視覺領域&#xff0c;目標檢測一直是一個極具挑戰性和實用性的研究領域。特別是在實時目標檢測方面&#xff0c;準確率和速度之間的平衡成為了關鍵考量因素。YOLO&#xf…

C語言精選——選擇題Day40

第一題 1. int a[10] {2,3,5}, 請問a[3]及a[3]之后的數值是&#xff08;&#xff09; A&#xff1a;不確定的數據 B&#xff1a;5 C&#xff1a;0 D&#xff1a;0xf f f f f f f f 答案及解析 C 數組的不完全初始化&#xff0c;會自動把沒初始化的部分初始化為0&#xff1b; 第…

postman做接口自動化測試

接口是用來連接服務端和客戶端&#xff0c;一般返回的數據都是json。 get和post請求的區別&#xff1a; 1. get請求比post請求安全 2. get請求參數有長度限制&#xff0c;post請求沒有 3. get請求沒有body&#xff0c;參數都是放在url里面&#xff0c;而post請求是放在body…

大華DSS S2-045 OGNL表達式注入漏洞復現

0x01 產品簡介 大華DSS安防監控系統平臺是一款集視頻、報警、存儲、管理于一體的綜合安防解決方案。該平臺支持多種接入方式,包括網絡視頻、模擬視頻、數字視頻、IP電話、對講機等。此外,該平臺還支持多種報警方式,包括移動偵測、區域入侵、越線報警、人員聚集等。 0x02 漏…

元宇宙:重塑游戲行業體驗下一個前沿

游戲行業在其整個歷史中經歷了顯著的轉變&#xff0c;從超級馬里奧的像素化冒險發展到Red Dead Redemption等游戲中迷人的開放世界體驗。隨著時間的推移&#xff0c;游戲不斷突破數字領域所能達到的極限。然而&#xff0c;被稱為元宇宙的突破性演變將徹底改變游戲行業&#xff…

PO模式在selenium自動化測試框架有什么好處

PO模式是在UI自動化測試過程當中使用非常頻繁的一種設計模式&#xff0c;使用這種模式后&#xff0c;可以有效的提升代碼的復用能力&#xff0c;并且讓自動化測試代碼維護起來更加方便。 PO模式的全稱叫page object model&#xff08;POM&#xff09;&#xff0c;有時候叫做 p…

網工內推 | 外企、合資公司急招網工,國內外旅游,健身年卡

01 深圳市耐施菲信息科技有限公司 招聘崗位&#xff1a;網絡工程師 職責描述&#xff1a; 1、負責項目的計劃、實施、過程管控、項目驗收等工作&#xff1b; 2、負責大型項目設備實施、安裝調試等售后維護工作&#xff1b; 3、分析、設計網絡拓撲結構、配置H3C、華為等交換機…

SQL FOREIGN KEY 約束- 保障表之間關系完整性的關鍵規則

SQL FOREIGN KEY 約束 SQL FOREIGN KEY 約束用于防止破壞表之間關系的操作。FOREIGN KEY 是一張表中的字段&#xff08;或字段集合&#xff09;&#xff0c;它引用另一張表中的主鍵。具有外鍵的表稱為子表&#xff0c;具有主鍵的表稱為被引用表或父表。 以下是兩個表的例子&a…

dll動態鏈接庫【C#】

1說明&#xff1a; 在C#中&#xff0c;dll是添加 【類庫】生成的。 2添加C#的dll&#xff1a; &#xff08;1&#xff09;在VS中新建一個Windows應用程序項目&#xff0c;并命名為TransferDll。 &#xff08;2&#xff09;打開Windows窗體設計器&#xff0c;從工具箱中為窗體…

Unity 性能優化的手段【更新中】

目錄 對象池 減少Draw Calls 批處理 合并網格 貼圖集 LOD 基本原理 應用 優點 挑戰 LightMap 基本概念 如何工作 優點 缺點 對象池 使用對象池&#xff1a;頻繁地創建和銷毀對象會導致性能下降和內存碎片化。對象池可以預先創建一些對象&#xff0c;然后在需要時…

【數據開發】Hive 多表join中的條件過濾與指定分區

1、條件過濾 left join 中 on 后面加條件 where 和 and 的區別 1、 on條件是在生成臨時表時使用的條件&#xff0c;它不管and中的條件是否為真&#xff0c;都會保留左邊表中的全部記錄。2、where條件是在臨時表生成好后&#xff0c;再對臨時表進行過濾的條件。這時已經沒有le…