【文章標(biāo)題】: 打造屬于自己的任務(wù)管理器 【文章作者】: SniperChan 【作者郵箱】: SniperChan@126.com 【編寫語言】: VC 【使用工具】: VS.NET 2008 SP1 【操作平臺】: XP SP3 【作者聲明】: 軟件可以任意修改和傳播 想想自己在看雪也混了有一些日子了,再看看自己的ID還是個臨時會員,所以想寫一些技術(shù)性的文章來申請個邀請碼.本人無才,破解剛接觸難以寫出一篇能收買版主的文章.破文就寫不來了,不過自己也學(xué)過編程一段時間了,那就寫一些關(guān)于編程的吧,希望以下這段亂碼能博君一笑. 任務(wù)管理器是在windows系統(tǒng)用得最頻繁的一個軟件之一吧.你是否會覺得任務(wù)管理器的功能過于簡單,不能滿足你的需要呢?那好,就讓我們一起來動手打造一個更強(qiáng)大的任務(wù)管理器. 我就覺得任務(wù)管理器的網(wǎng)絡(luò)項的功能不夠,如果能顯示實時的上傳,下載速度就好了.這樣我們就可以隨時關(guān)注自己的網(wǎng)絡(luò)狀態(tài).雖然顯示實時速度的軟件很多,但是每次查看的時候都要找出來打開,不夠方便,加在任務(wù)管理器就方便調(diào)出來查看了,并且它本身已經(jīng)有了曲線顯示了.好,開始吧! 最終效果圖: 思路: 怎么樣才能在任務(wù)管理器中插入兩列速度呢?我認(rèn)為有下面兩種方法: 1.PEDIY技術(shù),對原來的任務(wù)管理器進(jìn)行修改擴(kuò)充,增加一定的代碼以增加原程序的功能. 2.通過DLL注入的方法,把已編寫好的DLL注入到原程序中已實現(xiàn)想要的功能. 對于第一種方法,修改了原來的程序,不方便切換為沒修改時的狀態(tài).第二種就可以很方便的進(jìn)行加載和卸載DLL,并且對原程序不需要做任何修改,不會破壞到原來的程序.下面就討論第二種方法. 步驟: 一,遠(yuǎn)程注入DLL; 二,編寫DLL實現(xiàn)相關(guān)功能; 一,遠(yuǎn)程注入DLL在微軟的Windows中,每個進(jìn)程都能獲得自身的私有地址空間.當(dāng)使用指針引用內(nèi)存時,指針值將引用自身進(jìn)程地址空間中的一個內(nèi)存地址.進(jìn)程不能創(chuàng)建一個引用屬于其他進(jìn)程的內(nèi)存指針. 進(jìn)程不能創(chuàng)建一個引用屬于其他進(jìn)程的內(nèi)存指針,否則就會出現(xiàn)內(nèi)存的錯誤,該錯誤不會影響到其他進(jìn)程所使用的內(nèi)存.那如何才能將自己編寫的DLL放到任務(wù)管理器的進(jìn)程空間中呢?通過遠(yuǎn)程注入!遠(yuǎn)程注入技術(shù)需要滿足一個條件就是要求目標(biāo)進(jìn)程中的一個線程調(diào)用LoadLibrary函數(shù)來加載所需要的DLL.由于除了自己進(jìn)程的線程中之外,我們不能簡單的控制其他進(jìn)程中的線程,因此該解決方法要求我們在目標(biāo)進(jìn)程中創(chuàng)建一個新線程,這樣我們就可以控制它執(zhí)行任何代碼.Windows提供了一個 CreateRomoteThread的函數(shù)可以簡單地在另一個進(jìn)程中創(chuàng)建線程. 代碼:
HANDLE CreateRomoteThread( HANDLE hProcess //目標(biāo)進(jìn)程句柄 PSECURITY_ATTRIBUTES psa, //指向SECURITY_ATTRIBUTES型態(tài)的結(jié)構(gòu)的指針。在Windows 98中忽略該參數(shù)。在Windows NT中,它被設(shè)為NULL,表示使用缺省值。 DWORD dwStackSize, //線程堆棧大小,一般=0,在任何情況下,Windows根據(jù)需要動態(tài)延長堆棧的大小。 PTHREAD_START_ROUTINE pfnstartAddr, //指向線程函數(shù)的指針,形式:@函數(shù)名,函數(shù)名稱沒有限制,但是必須以下列形式聲明:DWORD WINAPI ThreadProc (LPVOID pParam) ,格式不正確將無法調(diào)用成功。 PVOID pvParam, //向線程函數(shù)傳遞的參數(shù),是一個指向結(jié)構(gòu)的指針,不需傳遞參數(shù)時,為NULL。 DWORD fdwCreate, //線程標(biāo)志 PDWORD pdwThreadId //保存新線程的id。 ); 函數(shù)成功,返回線程句柄;函數(shù)失敗返回false。 現(xiàn)在已經(jīng)知道了如何在另一個進(jìn)程中創(chuàng)建線程了,但是如何讓該線程來加載DLL呢?答案很簡單:讓線程去調(diào)用LoadLibrary函數(shù): 代碼:
HANDLE LoadLibrary(PCTSTR pszLibFile) 返回值: 成功則返回庫模塊的句柄,零表示失敗; 歸結(jié)起來就應(yīng)該是如下顯示的一行代碼: 代碼:
HANDLE hThreadd = CreateRemoteThread(hProcessRemote,NULL,0,LoadLibrary,"E:\\MyLib.dll",0,NULL); 可是還它不能達(dá)到預(yù)期的目的,里面還存在兩個問題. 1.如果目標(biāo)程序并沒有使用到LoadLibrary函數(shù),生成的exe的導(dǎo)入表就沒有該函數(shù),如果直接這樣使用一些不可以預(yù)測的東西,可能會導(dǎo)致訪問為例等錯誤.為強(qiáng)制使用LoadLibrary函數(shù),必須通過調(diào)用GetProcAddress得到該函數(shù)的確切內(nèi)存位置. LoadLibrary函數(shù)在Kernel32.dll中,函數(shù)應(yīng)該變成如下: 代碼:
PTHREAD_START_ROUTINE pfnThreadRtn =(PTHREAD_START_ROUTINE )GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibrary"); HANDLE hThread = CreateRemoteThread(hProcessRemote,NULL,0,pfnThreadRtn ,"E:\\MyLib.dll",0,NULL); 代碼:
LPVOID VirtualAllocEx( HANDLE hProcess, //申請內(nèi)存所在的進(jìn)程句柄。 LPVOID lpAddress, //保留頁面的內(nèi)存地址;一般用NULL自動分配 。 SIZE_T dwSize, //欲分配的內(nèi)存大小,字節(jié)單位;注意實際分 配的內(nèi)存大小是頁內(nèi)存大小的整數(shù)倍 DWORD flAllocationType, //可取下列值MEM_PHYSICAL ,MEM_RESERVE,MEM_RESET ,MEM_TOP_DOWN,MEM_WRITE_WATCH DWORD flProtect //可取下列值PAGE_READONLY,PAGE_EXECUTE,PAGE_EXECUTE_READ ,PAGE_EXECUTE_READWRITE,PAGE_GUARD,PAGE_NOACCESS,PAGE_NOCACHE ); 代碼:
BOOL VirtualFreeEx( HANDLE hProcess, // 要釋放內(nèi)存所在進(jìn)程的句柄 LPVOID lpAddress, // 區(qū)域地址 DWORD dwSize, // 區(qū)域大小,字節(jié) DWORD dwFreeType //類型 ); 代碼:
BOOL ReadProcessMemory( HANDLE hProcess,// 遠(yuǎn)程進(jìn)程句柄 LPCVOID lpBaseAddress, //遠(yuǎn)程進(jìn)程中內(nèi)存地址 LPVOID lpBuffer, //本地進(jìn)程中內(nèi)存地址. 函數(shù)將讀取的內(nèi)容寫入此處 DWORD nSize,// 要傳送的字節(jié)數(shù) LPDWORD lpNumberOfBytesRead //實際傳送的字節(jié)數(shù). 函數(shù)返回時報告實際寫入多少 ); BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten ); 1)使用VirtualAllocEx函數(shù)分配遠(yuǎn)程的地址空間中的內(nèi)存. 2)使用WriteProcessMemory函數(shù)將DLL的路徑名復(fù)制到步驟1)所分配的內(nèi)測中. 3)使用GetProcAddress函數(shù)得到LoadLibrary函數(shù)的實際地址(在Kernel32.dll中). 4)使用CreateRemoteThread函數(shù)在遠(yuǎn)程進(jìn)程中創(chuàng)建一個線程,調(diào)用正確的LoadLibrary函數(shù),將步驟1)所分配的內(nèi)存地址傳遞給他. 這時,DLL已經(jīng)注入到了遠(yuǎn)程進(jìn)程的地址空間中,DLL的DllMain函數(shù)接收到一個DLL_PROCESS_ATTACH通知,并且可以執(zhí)行所需要執(zhí)行的代碼了. 具體代碼如下: 代碼:
BOOL Inject(DWORD dwProcessId/*進(jìn)程ID*/, PCWSTR pszLibFile/*DLL路徑和名稱*/) { BOOL bOk = FALSE; HANDLE hProcess = NULL, hThread = NULL; PWSTR pszLibFileRemote = NULL; __try { // 獲取目標(biāo)進(jìn)程的句柄 hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | // Required by Alpha PROCESS_CREATE_THREAD | // For CreateRemoteThread PROCESS_VM_OPERATION | // For VirtualAllocEx/VirtualFreeEx PROCESS_VM_WRITE, // For WriteProcessMemory FALSE, dwProcessId); if (hProcess == NULL) __leave; // 計算DLL路徑的長度 int cch = 1 + lstrlenW(pszLibFile); int cb = cch * sizeof(wchar_t); //在遠(yuǎn)程進(jìn)程為DLL的名字和路徑分配內(nèi)存 pszLibFileRemote = (PWSTR) VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE); if (pszLibFileRemote == NULL) __leave; //把路徑復(fù)制的遠(yuǎn)程進(jìn)程的內(nèi)存中 if (!WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID) pszLibFile, cb, NULL)) __leave; //獲取LoadLibraryW 在Kernel32.dll中的實際內(nèi)存地址 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); if (pfnThreadRtn == NULL) __leave; //創(chuàng)建遠(yuǎn)程線程 hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL); if (hThread == NULL) __leave; // 等待線程結(jié)束 WaitForSingleObject(hThread, INFINITE); bOk = TRUE; } __finally { // 釋放內(nèi)存 if (pszLibFileRemote != NULL) VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE); if (hThread != NULL) CloseHandle(hThread); if (hProcess != NULL) CloseHandle(hProcess); } return(bOk); } 二,編寫DLL實現(xiàn)相關(guān)功能 好了,DLL就注入到了目標(biāo)進(jìn)程了,下面就開始編寫DLL來實現(xiàn)想要的功能了. 我想要在任務(wù)管理器的這一個控件后面加上兩列,該如何做呢? 要想在這個控件中添加兩列就必須要知道這個控件的句柄,通過EnumChildWindows函數(shù)可以枚舉父窗口的所有子窗口.這個控件也在其中. 代碼:
BOOL EnumChildWindows(HWND hWndParent,WNDENUMPROC lpEnumFunc, LPARAM lParam); HWND hWndParent, //父窗口句柄 WNDENUMPROC lpEnumFunc, // 回調(diào)函數(shù)的地址 LPARAM lParam//你自已定義的參數(shù) 代碼:
HANDLE hwndTaskManager = FindWindow( (LPCTSTR)32770, L"Windows 任務(wù)管理器"); 看到了吧,32770就是任務(wù)管理器的類名,也是一般對話框的類名. 前面說到,既然是枚舉所有控件,我怎么知道那個控件的句柄才是我要找的控件句柄呢?也是通過SPY++可以找到該控件的ID: 控件ID: 00000A28 ; EnumChildWindows第二個參數(shù)是回調(diào)的函數(shù),所以寫一個_EnumChildProc函數(shù)來比較該控件是否是想要的控件,最后一個參數(shù)是一個自定義參數(shù),所以我們需要定義一個結(jié)構(gòu)來保存獲取到句柄和需要查找的控件ID,傳遞給EnumChildProc; 代碼:
struct StructFindTaskManagerDlgItem { DWORD itemID;//控件ID HWND hwnd;//該控件的句柄 }; BOOL CALLBACK _EnumChildProc( HWND hwnd, LPARAM lParam ) { StructFindTaskManagerDlgItem* pParam = (StructFindTaskManagerDlgItem*)lParam; if ( (DWORD)GetDlgCtrlID( hwnd ) == pParam->itemID )//判斷是否為需要的控件 { pParam->hwnd = hwnd; return FALSE; } return TRUE; } HWND FindTaskManagerDlgItem( DWORD itemID ) { StructFindTaskManagerDlgItem param; param.itemID = (DWORD)itemID; param.hwnd = NULL; EnumChildWindows( hwndTaskManager, _EnumChildProc, (LPARAM)¶m ); return param.hwnd; } 代碼:
//獲取listview的數(shù)量 int GetListColmnCount(HWND hList) { int count=0; WCHAR caption[MAX_PATH]; LVCOLUMN lvc; lvc.mask=LVCF_TEXT; lvc.cchTextMax=MAX_PATH; lvc.pszText=caption; for (int i=0;i<50;i++) { ZeroMemory(caption,sizeof(caption)); SendMessage(hList,LVM_GETCOLUMN,i,(long)&lvc); if (caption[0]==0&&caption[1]==0) { count=i; break; } } return count; } //向listview插入兩列 void InsertColToNetworkList(HWND hNetworkList)//hNetworkList即為該控件的句柄 { int n=GetListColmnCount(hNetworkList);//獲取列數(shù),加載所有列的后面 // 添加列 LV_COLUMN colmn; // 列 ZeroMemory(&colmn, sizeof(LV_COLUMN)); colmn.mask = LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; // 風(fēng)格 colmn.fmt=LVCFMT_RIGHT; colmn.cx = 0x60;//寬度 colmn.pszText = L"下載";//列名 SendMessage(hNetworkList, LVM_INSERTCOLUMN, n, (LPARAM)&colmn); colmn.pszText = L"上傳";//列名 SendMessage(hNetworkList, LVM_INSERTCOLUMN, n+1, (LPARAM)&colmn); } GetIfTable()可以從操作系統(tǒng)維護(hù)的MIB庫中讀出本機(jī)各個接口的當(dāng)前信息,如接口數(shù)目、類型、速率、物理地址、接收/發(fā)送字節(jié)數(shù)、錯語字節(jié)數(shù)等. 代碼:
DWORD GetIfTable( __out PMIB_IFTABLE pIfTable,//指向PMIB_IFTABLE 的指針 __inout PULONG pdwSize,//pIfTable的大小 __in BOOL bOrder//是否排序 ); typedef struct _MIB_IFTABLE { DWORD dwNumEntries; MIB_IFROW table[ANY_SIZE]; }MIB_IFTABLE, *PMIB_IFTABLE; typedef struct _MIB_IFROW { WCHAR wszName[MAX_INTERFACE_NAME_LEN]; DWORD dwIndex;//端口索引號 DWORD dwType;//端口類型 DWORD dwMtu;//最大傳輸包字節(jié)數(shù) DWORD dwSpeed;//端口速度 DWORD dwPhysAddrLen;//物理地址長度 BYTE bPhysAddr[MAXLEN_PHYSADDR];//物理地址 DWORD dwAdminStatus;//管理狀態(tài) DWORD dwOperStatus;//操作狀態(tài) DWORD dwLastChange;//上次狀態(tài)更新時間 DWORD dwInOctets;//輸入字節(jié)數(shù) DWORD dwInUcastPkts;//輸入非廣播包數(shù) DWORD dwInNUcastPkts;//輸入廣播包數(shù) DWORD dwInDiscards;//輸入包丟棄數(shù) DWORD dwInErrors;//輸入包錯誤數(shù) DWORD dwInUnknownProtos;//輸入未知協(xié)議包數(shù) DWORD dwOutOctets;//輸出字節(jié)數(shù) DWORD dwOutUcastPkts;//輸出非廣播包數(shù) DWORD dwOutNUcastPkts;//輸出廣播包數(shù) DWORD dwOutDiscards;//輸出包丟棄數(shù) DWORD dwOutErrors;//輸出包錯誤數(shù) DWORD dwOutQLen;//輸出隊長 DWORD dwDescrLen;//端口描述長度 BYTE bDescr[MAXLEN_IFDESCR]; //端口描述 }MIB_IFROW, *PMIB_IFROW; 當(dāng)前下載速率=(當(dāng)次輸入的字節(jié)數(shù)-上次輸入的字節(jié)數(shù))/1秒. 當(dāng)前上傳速率=(當(dāng)次輸出的字節(jié)數(shù)-上次輸出的字節(jié)數(shù))/1秒. 具體代碼如下: 代碼:
int NetInformation() { MIB_IFTABLE *pIfTable = NULL; MIB_IFROW *pIfRow=NULL; ULONG dwSize = 0; DWORD dwRet; dwRet = GetIfTable( pIfTable, &dwSize, TRUE ); //第一次調(diào)用獲取結(jié)構(gòu)大小 if ( dwRet == ERROR_INSUFFICIENT_BUFFER ) { pIfTable = ( MIB_IFTABLE * ) new char[dwSize]; if ( pIfTable != NULL ) { dwRet = GetIfTable( pIfTable, &dwSize, TRUE ); //獲得相關(guān)信息 if ( dwRet == NO_ERROR ) { pIfRow = (MIB_IFROW *) & pIfTable->table[1]; CurrentInBytes=pIfRow->dwInOctets; //保存當(dāng)次的輸入字節(jié)數(shù) CurrentOutBytes=pIfRow->dwOutOctets; //保存當(dāng)次的輸出字節(jié)數(shù) } else { printf( "Some error occured!\n" ); return FALSE; } } else { printf( "Memory allocate failue\n" ); return FALSE; } } else { printf( "Some error occured!\n" ); return FALSE; } return TRUE; } 下面是創(chuàng)建一個線程,每秒讀取一次,然后更新到界面上 代碼:
CreateThread(NULL,NULL,ThreadTimerProc,NULL,NULL,NULL); DWORD WINAPI ThreadTimerProc(PVOID param) { NetInformation(); lastInBytes=CurrentInBytes; lastOutBytes=CurrentOutBytes; while(TRUE) { if(NetInformation()) { float InBps=(float(CurrentInBytes-lastInBytes))/1024; float OutBps=(float(CurrentOutBytes-lastOutBytes))/1024; lastInBytes=CurrentInBytes; lastOutBytes=CurrentOutBytes; item.mask = LVIF_TEXT; item.cchTextMax = MAX_PATH; int iSubItem=GetListItemIndex(hwndNetworkList,L"下載"); if (iSubItem>0) { ZeroMemory(szBPS,sizeof(szBPS)); swprintf(szBPS,L"%0.2f KB/s",InBps); //swprintf(szBPS,L"%ld KB/s",CurrentInBytes); item.pszText=szBPS; item.iSubItem =iSubItem; ListView_SetItem(hwndNetworkList,(LPARAM)&item); ZeroMemory(szBPS,sizeof(szBPS)); swprintf(szBPS,L"%0.2f KB/s",OutBps); //swprintf(szBPS,L"%ld KB/s",CurrentOutBytes); item.pszText=szBPS; item.iSubItem =iSubItem+1; ListView_SetItem(hwndNetworkList,(LPARAM)&item); } } Sleep(1000); } } 到此為止,整個程序已經(jīng)完成了90%了,還有些就是關(guān)于一些如何判斷DLL已經(jīng)加載了,如何卸載等請看源代碼,這里就不一一介紹了.這個程序還可以做得跟好,比如在每個進(jìn)程那個列表加上一個實時速度,這樣就可以看到每個進(jìn)程網(wǎng)絡(luò)情況等,有興趣的朋友可以自己添加完善. |
|