在前幾篇文章中LyShark
通過多種方式實作了驅動程式與應用層之間的通信,這其中就包括了通過運用SystemBuf
緩沖區通信,運用ReadFile
讀寫通信,運用PIPE
管道通信,以及運用ASYNC
反向通信,這些通信方式在應對一收一發
模式的時候效率極高,但往往我們需要實作一次性吐出多種資料,例如ARK工具中當我們列舉內核模塊時,往往應用層例程中可以回傳幾條甚至是幾十條結果,如下案例所示,這對于開發一款ARK反內核工具是必須要有的功能,
- 那么如何實作如上述功能呢?
其實,實作這類功能可以從兩個方面入手,但不論使用哪一種方式本質上都是預留一段緩沖區以此來給內核與應用層共享的區域,該區域內可用于交換資料,實作方式有兩種要么在應用層分配空間,要么在內核中分配,LyShark先帶大家在內核層
實作,通過巧妙地運用MDL映射
機制來實作通信需求,
- MDL是什么呢?
MDL記憶體讀寫是最常用的一種讀寫模式,是用于描述物理地址頁面的一個結構,簡單的官方解釋;記憶體描述符串列 (MDL) 是一個系統定義的結構,通過一系列物理地址描述緩沖區,執行直接I/O的驅動程式從I/O管理器接收一個MDL的指標,并通過MDL讀寫資料,一些驅動程式在執行直接I/O來滿足設備I/O控制請求時也使用MDL,
通過運用MDL的方式對同一塊物理記憶體同時映射到R0和R3,這樣我們只需要使用DeviceIoControl
向驅動發送一個指標,通過對指標進行讀寫就可以實作資料的交換,本人在網路上找到了如下兩段被轉載的爛大街的片段,這兩段代碼明顯是存在缺陷的如果你也在尋找映射方法那么不要被這兩段代碼坑了,多數人也根本沒有能力將其變為可用的,也就只能轉載,不知道哪個大哥挖的坑,
用戶態行程分配空間,內核態去映射,
// assume uva is a virtual address in user space, uva_size is its size
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
} __except(EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID kva = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
// use kva
// …
MmUnlockPages(mdl);
IoFreeMdl(mdl);
內核態分配空間,用戶態行程去映射,
PVOID kva = ExAllocatePoolWithTag(NonPagedPool, 1024, (ULONG)'PMET');
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
MmBuildMdlForNonPagedPool(mdl);
} __except(EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID uva = MmMapLockedPagesSpecifyCache(mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
如上的代碼看看就好摘出來只是要提醒大家這個是無法使用的,如下將進入本篇文章的正題,
以內核中開辟空間為例,首先在代碼中要做的就是定義一段非分頁記憶體#define FILE_DEVICE_EXTENSION 4096
這段區域用于給全域變數使用,其次我們需要傳輸結構體那么結構體中的成員就要事先定義好,例如此處使用StructAll
來定義結構結構體成員變數如下所示,通過使用static
將結構體定義為靜態,預先空出1024
的記憶體空間并初始化為0,當然了這種方式是存在弊端的,例如最大只支持1024個結構如果超過了則可能會溢位,當然最好的辦法是用戶空間開辟,在下次章節中再介紹,
// -------------------------------------------------
// MDL資料傳遞變數
// -------------------------------------------------
// 保存一段非分頁記憶體,用于給全域變數使用
#define FILE_DEVICE_EXTENSION 4096
// 定義重復結構(回圈傳遞)
typedef struct
{
char username[256];
char password[256];
int count;
}StructAll;
static StructAll ptr[1024] = { 0 };
為了能夠達到輸出結構體的效果這里我定義一個ShowProcess
用于模擬當前系統內行程數,并自動填充為特定的資料,此處結構體內部count
成員則用于標注當前共有多少個結構體,用于在用戶層讀取判斷,當然了這種方式的另一個弊端就是浪費空間,因為每一個結構體中都存在一個被填充為0的整數型別,但如果只是實作功能的話其實也不是那么重要,
// 模擬行程串列賦值測驗
int ShowProcess(int process_count)
{
memset(ptr, 0, sizeof(StructAll) * process_count);
int x = 0;
for (; x < process_count + 1; x++)
{
strcpy_s(ptr[x].username, 256, "lyshark");
strcpy_s(ptr[x].password, 256, "123456");
}
// 設定總共有多少個結構體,并回傳結構體個數
ptr[0].count = x;
return x;
}
內核態映射: 當定義好如上這些方法時,接下來就是最重要的驅動映射部分了,如下代碼所示,首先當用戶呼叫派遣時第一個執行的函式是ShowProcess()
它用于獲取到當前系統中有多少個行程,接著通過sizeof(MyData) * count
計算出當前MyData
需要分配的記憶體池大小并回傳給pool_size
,呼叫ExAllocatePool
分配一塊非分頁內核空間,創建IoAllocateMdl
MDL映射,將資料MmMapLockedPagesSpecifyCache
映射到用戶空間,最后將指標pShareMM_User
回傳給用戶態,
- ShowProcess(715) 獲取當前行程數,并回傳數量
- sizeof(MyData) * count 計算得到結構體長度
- ExAllocatePool(NonPagedPool, pool_size) 分配非分頁記憶體,長度是pool_size
- IoAllocateMdl() 分配MDL空間,并放入內核態
- MmMapLockedPagesSpecifyCache() 將內核態指標映射到用戶態
- RtlCopyMemory(pShareMM_SYS, &ptr, sizeof(ptr[0]) * count) 將總行程數放入到count計數變數內
- *(PVOID *)pIrp->AssociatedIrp.SystemBuffer = pShareMM_User 直接將指標傳遞給用戶態
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
// 獲取到當前串列資料
int count = ShowProcess(715);
long pool_size = sizeof(MyData) * count;
DbgPrint("總行程數: %d 分配記憶體池大小: %d \n", count, pool_size);
__try
{
// 分配內核空間
PVOID pShareMM_SYS = ExAllocatePool(NonPagedPool, pool_size);
RtlZeroMemory(pShareMM_SYS, pool_size);
// 創建MDL
PMDL pShareMM_MDL = IoAllocateMdl(pShareMM_SYS, pool_size, FALSE, FALSE, NULL);
MmBuildMdlForNonPagedPool(pShareMM_MDL);
// 將內核空間映射到用戶空間
PVOID pShareMM_User = MmMapLockedPagesSpecifyCache(pShareMM_MDL, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
// 拷貝發送資料
RtlCopyMemory(pShareMM_SYS, &ptr, sizeof(ptr[0]) * count);
DbgPrint("[lyshark] 用戶地址空間: 0x%x \n", pShareMM_User);
DbgPrint("[lyshark] 內核地址空間: 0x%p \n", pShareMM_SYS);
// 將字串指標發送給應用層
*(PVOID *)pIrp->AssociatedIrp.SystemBuffer = pShareMM_User;
// ExFreePool(pShareMM_SYS);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
break;
}
status = STATUS_SUCCESS;
break;
用戶態讀取資料: 與內核層一致,用戶層同樣需要定義StructAll
結構體用于接收內核中回傳過來的結構,而重要的代碼則是接收部分,通過IoControl
發送控制碼,并得到ptr
記憶體指標,此處區域就是內核態分配過的指標,用戶只需要通過回圈的方式依次讀出里面的資料即可,
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
// -------------------------------------------------
// MDL資料傳遞變數
// -------------------------------------------------
// 定義重復結構(回圈傳遞)
typedef struct
{
char username[256];
char password[256];
int count;
}StructAll;
// 直接輸出回圈結構體
StructAll *ptr;
// 派遣命令
DriveControl.IoControl(IOCTL_IO_MDLStructAll, 0, 0, &ptr, sizeof(PVOID), 0);
printf("共享記憶體地址: %x \n", ptr);
long size = ptr[0].count;
std::cout << "得到結構體總數: " << size << std::endl;
for (int x = 0; x < size; x++)
{
std::cout << "計數器: " << x << std::endl;
std::cout << "用戶名: " << ptr[x].username << std::endl;
std::cout << "密碼: " << ptr[x].password << std::endl;
std::cout << std::endl;
}
如上就是內核層與應用層的部分代碼功能分析,接下來我將完整代碼分享出來,大家可以自行測驗效果,
驅動程式WinDDK.sys
完整代碼;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#define _CRT_SECURE_NO_WARNINGS
#include <ntifs.h>
#include <windef.h>
// 定義符號鏈接,一般來說修改為驅動的名字即可
#define DEVICE_NAME L"\\Device\\WinDDK"
#define LINK_NAME L"\\DosDevices\\WinDDK"
#define LINK_GLOBAL_NAME L"\\DosDevices\\Global\\WinDDK"
// 定義驅動功能號和名字,提供介面給應用程式呼叫
#define IOCTL_IO_MDLStructAll CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 保存一段非分頁記憶體,用于給全域變數使用
#define FILE_DEVICE_EXTENSION 4096
// 定義傳遞結構體
typedef struct
{
int uuid;
char szUname[1024];
}MyData;
// -------------------------------------------------
// MDL資料傳遞變數
// -------------------------------------------------
// 定義重復結構(回圈傳遞)
typedef struct
{
char username[256];
char password[256];
int count;
}StructAll;
static StructAll ptr[1024] = { 0 };
// 模擬行程串列賦值測驗
int ShowProcess(int process_count)
{
memset(ptr, 0, sizeof(StructAll) * process_count);
int x = 0;
for (; x < process_count + 1; x++)
{
strcpy_s(ptr[x].username, 256, "hello lyshark.com");
strcpy_s(ptr[x].password, 256, "123456");
}
// 設定總共有多少個結構體,并回傳結構體個數
ptr[0].count = x;
return x;
}
// 驅動系結默認派遣函式
NTSTATUS DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
_pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 驅動卸載的處理例程
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
if (pDriverObj->DeviceObject)
{
UNICODE_STRING strLink;
// 洗掉符號連接和設備
RtlInitUnicodeString(&strLink, LINK_NAME);
IoDeleteSymbolicLink(&strLink);
IoDeleteDevice(pDriverObj->DeviceObject);
DbgPrint("[kernel] # 驅動已卸載 \n");
}
}
// IRP_MJ_CREATE 對應的處理例程,一般不用管它
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("[kernel] # 驅動處理例程載入 \n");
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// IRP_MJ_CLOSE 對應的處理例程,一般不用管它
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("[kernel] # 關閉派遣 \n");
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// IRP_MJ_DEVICE_CONTROL 對應的處理例程,驅動最重要的函式
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;
// 獲得IRP里的關鍵資料
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// 獲取控制碼
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 輸入和輸出的緩沖區(DeviceIoControl的InBuffer和OutBuffer都是它)
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
// EXE發送傳入資料的BUFFER長度(DeviceIoControl的nInBufferSize)
uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
// EXE接收傳出資料的BUFFER長度(DeviceIoControl的nOutBufferSize)
uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
// 對不同控制信號的處理流程
switch (uIoControlCode)
{
// 測驗MDL傳輸多次結構體
case IOCTL_IO_MDLStructAll:
{
// 獲取到當前串列資料
int count = ShowProcess(715);
long pool_size = sizeof(MyData) * count;
DbgPrint("總行程數: %d 分配記憶體池大小: %d \n", count, pool_size);
__try
{
// 分配內核空間
PVOID pShareMM_SYS = ExAllocatePool(NonPagedPool, pool_size);
RtlZeroMemory(pShareMM_SYS, pool_size);
// 創建MDL
PMDL pShareMM_MDL = IoAllocateMdl(pShareMM_SYS, pool_size, FALSE, FALSE, NULL);
MmBuildMdlForNonPagedPool(pShareMM_MDL);
// 將內核空間映射到用戶空間
PVOID pShareMM_User = MmMapLockedPagesSpecifyCache(pShareMM_MDL, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
// 拷貝發送資料
RtlCopyMemory(pShareMM_SYS, &ptr, sizeof(ptr[0]) * count);
DbgPrint("[lyshark.com] 用戶地址空間: 0x%x \n", pShareMM_User);
DbgPrint("[lyshark.com] 內核地址空間: 0x%p \n", pShareMM_SYS);
// 將字串指標發送給應用層
*(PVOID *)pIrp->AssociatedIrp.SystemBuffer = pShareMM_User;
// ExFreePool(pShareMM_SYS);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
break;
}
status = STATUS_SUCCESS;
break;
}
}
// 設定DeviceIoControl的*lpBytesReturned的值(如果通信失敗則回傳0長度)
if (status == STATUS_SUCCESS)
{
pIrp->IoStatus.Information = uOutSize;
}
else
{
pIrp->IoStatus.Information = 0;
}
// 設定DeviceIoControl的回傳值是成功還是失敗
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
// 驅動的初始化作業
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING ustrLinkName;
UNICODE_STRING ustrDevName;
PDEVICE_OBJECT pDevObj;
// 初始化其他派遣
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DbgPrint("初始化派遣: %d \n", i);
pDriverObj->MajorFunction[i] = DefaultDispatch;
}
// 設定分發函式和卸載例程
pDriverObj->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
pDriverObj->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
pDriverObj->DriverUnload = DriverUnload;
// 創建一個設備
RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);
// FILE_DEVICE_EXTENSION 創建設備時,指定設備擴展記憶體的大小,傳一個值進去,就會給設備分配一塊非頁面記憶體,
status = IoCreateDevice(pDriverObj, sizeof(FILE_DEVICE_EXTENSION), &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
if (!NT_SUCCESS(status))
{
return status;
}
// 判斷支持的WDM版本,其實這個已經不需要了,純屬WIN9X和WINNT并存時代的殘留物
if (IoIsWdmVersionAvailable(1, 0x10))
{
RtlInitUnicodeString(&ustrLinkName, LINK_GLOBAL_NAME);
}
else
{
RtlInitUnicodeString(&ustrLinkName, LINK_NAME);
}
// 創建符號連接
status = IoCreateSymbolicLink(&ustrLinkName, &ustrDevName);
if (!NT_SUCCESS(status))
{
DbgPrint("創建符號鏈接失敗 \n");
IoDeleteDevice(pDevObj);
return status;
}
DbgPrint("[kernel] # hello lyshark.com \n");
// 回傳加載驅動的狀態(如果回傳失敗,驅動講被清除出內核空間)
return STATUS_SUCCESS;
}
應用層客戶端程式lyshark.exe
完整代碼;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <iostream>
#include <Windows.h>
#include <vector>
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"advapi32.lib")
// 定義驅動功能號和名字,提供介面給應用程式呼叫
#define IOCTL_IO_MDLStructAll 0x805
class cDrvCtrl
{
public:
cDrvCtrl()
{
m_pSysPath = NULL;
m_pServiceName = NULL;
m_pDisplayName = NULL;
m_hSCManager = NULL;
m_hService = NULL;
m_hDriver = INVALID_HANDLE_VALUE;
}
~cDrvCtrl()
{
CloseServiceHandle(m_hService);
CloseServiceHandle(m_hSCManager);
CloseHandle(m_hDriver);
}
// 安裝驅動
BOOL Install(PCHAR pSysPath, PCHAR pServiceName, PCHAR pDisplayName)
{
m_pSysPath = pSysPath;
m_pServiceName = pServiceName;
m_pDisplayName = pDisplayName;
m_hSCManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (NULL == m_hSCManager)
{
m_dwLastError = GetLastError();
return FALSE;
}
m_hService = CreateServiceA(m_hSCManager, m_pServiceName, m_pDisplayName,
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
m_pSysPath, NULL, NULL, NULL, NULL, NULL);
if (NULL == m_hService)
{
m_dwLastError = GetLastError();
if (ERROR_SERVICE_EXISTS == m_dwLastError)
{
m_hService = OpenServiceA(m_hSCManager, m_pServiceName, SERVICE_ALL_ACCESS);
if (NULL == m_hService)
{
CloseServiceHandle(m_hSCManager);
return FALSE;
}
}
else
{
CloseServiceHandle(m_hSCManager);
return FALSE;
}
}
return TRUE;
}
// 啟動驅動
BOOL Start()
{
if (!StartServiceA(m_hService, NULL, NULL))
{
m_dwLastError = GetLastError();
return FALSE;
}
return TRUE;
}
// 關閉驅動
BOOL Stop()
{
SERVICE_STATUS ss;
GetSvcHandle(m_pServiceName);
if (!ControlService(m_hService, SERVICE_CONTROL_STOP, &ss))
{
m_dwLastError = GetLastError();
return FALSE;
}
return TRUE;
}
// 移除驅動
BOOL Remove()
{
GetSvcHandle(m_pServiceName);
if (!DeleteService(m_hService))
{
m_dwLastError = GetLastError();
return FALSE;
}
return TRUE;
}
// 打開驅動
BOOL Open(PCHAR pLinkName)
{
if (m_hDriver != INVALID_HANDLE_VALUE)
return TRUE;
m_hDriver = CreateFileA(pLinkName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (m_hDriver != INVALID_HANDLE_VALUE)
return TRUE;
else
return FALSE;
}
// 發送控制信號
BOOL IoControl(DWORD dwIoCode, PVOID InBuff, DWORD InBuffLen, PVOID OutBuff, DWORD OutBuffLen, DWORD *RealRetBytes)
{
DWORD dw;
BOOL b = DeviceIoControl(m_hDriver, CTL_CODE_GEN(dwIoCode), InBuff, InBuffLen, OutBuff, OutBuffLen, &dw, NULL);
if (RealRetBytes)
*RealRetBytes = dw;
return b;
}
private:
// 獲取服務句柄
BOOL GetSvcHandle(PCHAR pServiceName)
{
m_pServiceName = pServiceName;
m_hSCManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (NULL == m_hSCManager)
{
m_dwLastError = GetLastError();
return FALSE;
}
m_hService = OpenServiceA(m_hSCManager, m_pServiceName, SERVICE_ALL_ACCESS);
if (NULL == m_hService)
{
CloseServiceHandle(m_hSCManager);
return FALSE;
}
else
{
return TRUE;
}
}
// 獲取控制信號對應字串
DWORD CTL_CODE_GEN(DWORD lngFunction)
{
return (FILE_DEVICE_UNKNOWN * 65536) | (FILE_ANY_ACCESS * 16384) | (lngFunction * 4) | METHOD_BUFFERED;
}
public:
DWORD m_dwLastError;
PCHAR m_pSysPath;
PCHAR m_pServiceName;
PCHAR m_pDisplayName;
HANDLE m_hDriver;
SC_HANDLE m_hSCManager;
SC_HANDLE m_hService;
};
void GetAppPath(char *szCurFile)
{
GetModuleFileNameA(0, szCurFile, MAX_PATH);
for (SIZE_T i = strlen(szCurFile) - 1; i >= 0; i--)
{
if (szCurFile[i] == '\\')
{
szCurFile[i + 1] = '\0';
break;
}
}
}
// -------------------------------------------------
// MDL資料傳遞變數
// -------------------------------------------------
// 定義重復結構(回圈傳遞)
typedef struct
{
char username[256];
char password[256];
int count;
}StructAll;
int main(int argc, char *argv[])
{
cDrvCtrl DriveControl;
// 設定驅動名稱
char szSysFile[MAX_PATH] = { 0 };
char szSvcLnkName[] = "WinDDK";;
GetAppPath(szSysFile);
strcat(szSysFile, "WinDDK.sys");
// 安裝并啟動驅動
DriveControl.Install(szSysFile, szSvcLnkName, szSvcLnkName);
DriveControl.Start();
// 打開驅動的符號鏈接
DriveControl.Open("\\\\.\\WinDDK");
// 直接輸出回圈結構體
StructAll *ptr;
// 派遣命令
DriveControl.IoControl(IOCTL_IO_MDLStructAll, 0, 0, &ptr, sizeof(PVOID), 0);
printf("[LyShark.com] 共享記憶體地址: %x \n", ptr);
long size = ptr[0].count;
std::cout << "得到結構體總數: " << size << std::endl;
for (int x = 0; x < size; x++)
{
std::cout << "計數器: " << x << std::endl;
std::cout << "用戶名: " << ptr[x].username << std::endl;
std::cout << "密碼: " << ptr[x].password << std::endl;
std::cout << std::endl;
}
// 關閉符號鏈接句柄
CloseHandle(DriveControl.m_hDriver);
// 停止并卸載驅動
DriveControl.Stop();
DriveControl.Remove();
system("pause");
return 0;
}
手動編譯這兩個程式,將驅動簽名后以管理員身份運行lyshark.exe
客戶端,此時螢屏中即可看到滾動輸出效果,如此一來就實作了回圈傳遞引數的目的,
文章出處:https://www.cnblogs.com/LyShark/p/17134274.html
著作權宣告:本博客文章,除去特殊宣告 [轉載標注/參考文獻] 部分, [均為原創] 作品,禁止任何形式的轉載!
文章著作權所有 ? 孤風洗劍 (王瑞) 保留著作權,請遵守《中華人民共和國著作權法》相關規定,未經著作權人書面許可禁止任何形式的轉載,如若侵權必將竭盡全力打擊!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551489.html
標籤:其他
下一篇:返回列表