程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> WinNT及Win2K下實現進程的完全隱藏

WinNT及Win2K下實現進程的完全隱藏

編輯:關於C++

面對眾多的計算機高手,考慮許久,終於還是決定出來獻丑一下,文章內盡量使用最簡潔易懂的詞匯及例子來介紹,希望能夠對一些初學與進階者有所幫助。

關於進程的隱藏,98下的例子數不勝數。WinNT/Win2K下的隱藏方法,西祠的高手shotgun在去年的6月就已經在網上發布出實例《揭開木馬的神秘面紗<四>》 ,我也多次拜讀他的文章,對他的計算機水平及熱心幫助朋友的作風十分敬佩。這裡也可算是對shotgun的文章的補充與深入介紹吧,好了,閒話少說。

在WinNT下"真正隱藏進程"這一說法,可以講是根本不可能實現,只要我們的程序是以進程內核的形式運行,都是不可能逃離CTRL+ALT+DEL的法眼。那麼奇怪了,這豈不是與我們的標題《WinNT & Win2K下實現進程的完全隱藏》相矛盾嗎?是的,實際上應該是:以非進程方式執行目標代碼,而逃避進程查看器的檢查,從而達到"進程隱藏"的目的。

我們這裡用的,是在宿主進程中,以線程的方式執行我們的代碼。實現起來非常簡單。首先,我們先建立一個不執行任何語句的線程

DWORD stdcall ThreadProc(LPVOID *lpVoid){
return 0;
}

然後,將線程代碼拷備至宿主進程所能夠執行的任何地方(即頁面屬性為PAGGE_EXECUTE_READWRITE),如:共享內存影射區、宿主進程內。這裡我們選擇宿主進程,拷備的時侯,我們需要先在宿主進程中使用VirtualAllocEx函數申請一段內存,然後再使用WriteProcessMemory將線程體寫入宿主進程中。

以上工作完成後,我們便可CreateRemoteThread函數激活其執行。下面給出一個完整的例子

//遠程線程執行體

DWORD __stdcall ThreadProc (void *lpPara){
return 0;
}
int main(int argc, char* argv[]){
const DWORD THREADSIZE=1024*4;//暫定線程體大小為4K,實際上沒這麼大,稍後我將會介紹
DWORD byte_write;
//獲得指定進程ID句柄,並設其權限為PROCESS_ALL_ACCESS,992是宿進程的ID號,獲取ID號的方法這裡我就不多講了
HANDLE hWnd = ::OpenProcess (PROCESS_ALL_ACCESS,FALSE,992);
if(!hWnd)return 0;
void *pRemoteThread =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT| MEM_RESERVE,PAGE_EXECUTE_READWRITE);//申請
if(!pRemoteThread)return 0;
if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc,THREADSIZE,0))//寫入進程
return 0;
//啟動線程
HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread ,NULL,0,&byte_write);
if(!hThread){ //還有內存分配未釋放
return 0;
}
return 0;
}

到這裡,對於隱藏的方法就算告一段落,相信看過的朋友對這個思路有個非常明確的概念了吧。

在理解隱藏的方法後,我們著重開始寫線程的執行部分了。如下:

DWORD __stdcall ThreadProc(void *lpPara){
MessageBox(NULL,"hello","hello",0);
return 0;
}

編譯執行後,你會發現出現一個非法操作錯誤,為什麼呢?在我們以段頁式內存管理的win2K操作系統中,編譯時會把所有的常量編譯在PE文件的.data節中,而代碼段則在.text中,所以,我們拷備到宿主進程中的代碼是在.text中的代碼,MessageBox(NULL,(char *)指針,p,0);所指向的地址是本進程的內存虛擬地址。而在宿主進程中是無法訪問的。解決的方法很簡單,按舊照搬的將"hello"也拷備到目標進程中,然後再引用。同理,MessageBox函數地址編譯時,也是保存在.Import中,寫過Win2k病毒的朋友都知道,所有常量與函數入口地址都需在代碼段定義與得出,我們這裡也與他有點類似。言歸正傳,同樣情況我們也把函數的入口地址一起寫入目標進程中。//先定義參數結構

typedef struct _RemotePara{//參數結構
char pMessageBox[12];
DWORD dwMessageBox;
}RemotePara;
//付值
RemotePara myRemotePara;
::ZeroMemory(&myRemotePara,sizeof(RemotePara));
HINSTANCE hUser32 = ::LoadLibrary ("user32.dll");
myRemotePara.dwMessageBox =(DWORD) ::GetProcAddress (hUser32 , "MessageBoxA");
strcat(myRemotePara.pMessageBox,"hello\0");
//寫進目標進程
RemotePara *pRemotePara =(RemotePara *) ::VirtualAllocEx (hWnd ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//注意申請空間時的頁面保護屬性
if(!pRemotePara)return 0;
if(!::WriteProcessMemory (hWnd ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
//啟動進將參數傳遞進入
HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread ,pRemotePara,0,&byte_write);
if(!hThread){
return 0;
}好了,就這麼簡單,下在給出一個彈出一個MessageBox的實例:// RemoteThread.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
typedef struct _RemotePara{//參數結構
char pMessageBox[12];
DWORD dwMessageBox;
}RemotePara;
//遠程線程
DWORD __stdcall ThreadProc (RemotePara *lpPara){
typedef int (__stdcall *MMessageBoxA)(HWND,LPCTSTR,LPCTSTR,DWORD);//定義MessageBox函數
MMessageBoxA myMessageBoxA;
myMessageBoxA =(MMessageBoxA) lpPara->dwMessageBox ;//得到函數入口地址
myMessageBoxA(NULL,lpPara->pMessageBox ,lpPara->pMessageBox,0);//call
return 0;
}
void EnableDebugPriv();//提升應用級調試權限
int main(int argc, char* argv[]){
const DWORD THREADSIZE=1024*4;
DWORD byte_write;
EnableDebugPriv();//提升權限
HANDLE hWnd = ::OpenProcess (PROCESS_ALL_ACCESS,FALSE,992);
if(!hWnd)return 0;
void *pRemoteThread =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT| MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(!pRemoteThread)return 0;
if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc,THREADSIZE,0))
return 0;
//再付值
RemotePara myRemotePara;
::ZeroMemory(&myRemotePara,sizeof(RemotePara));
HINSTANCE hUser32 = ::LoadLibrary ("user32.dll");
myRemotePara.dwMessageBox =(DWORD) ::GetProcAddress (hUser32 , "MessageBoxA");
strcat(myRemotePara.pMessageBox,"hello\0");
//寫進目標進程
RemotePara *pRemotePara =(RemotePara *) ::VirtualAllocEx (hWnd ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//注意申請空間時的頁面屬性
if(!pRemotePara)return 0;
if(!::WriteProcessMemory (hWnd ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
//啟動線程
HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread ,pRemotePara,0,&byte_write);
if(!hThread){
return 0;
}
return 0;
}
//提升權限
void EnableDebugPriv( void )
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if ( ! OpenProcessToken( GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
return;
if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) ){
CloseHandle( hToken );
return;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) )
CloseHandle( hToken );
}

好了,程序編譯執行後會在進程號為992的進程中創建一線程,彈出hello對話框。是不是非常簡單呢!

這裡有幾個地方需要注意的:

1、遠程線程在宿主進程中申請空間時,空間大小的確定了是我一直無法解決的問題。我曾使用兩個貼近一起的線程,以線程間的距離大小,並加上參數大小,作為申請空間時,仍然會出現非法操作,如下:

static void StartThread (LPVOID *lpPara){
return ;
}
static void EndThread(LPVOID *lpPara){
return;
}

然後使用DWORD dwLenght = (DWORD)((char *)&StartThread - (char *)&EndThread);//得到StartThread線程代碼長度,

dwLenght += sizeof(ThreadPara);

仍會出現非法操作讓我很迷惑。在win2k中,線程的默認堆棧的頁大小是4KB,這裡我在為線程申請內存時,申請的大小暫時使用一個常數,始終為4KB的倍數,選取時盡量取大,在線程可成功運行後,再一點點改小。辦法是笨了點,如這裡的朋友有更好的方法,請不吝賜教。

2、什麼時侯,什麼參數是需要從外部傳遞進來的呢?我這裡並沒有一個十分有力的答案,我的理解是:PE文件中除了.text節以外的所有節,均需使用外部參數傳遞到線程中使用,如:.rsrc、.data、rdata等其他的15個節。在我們實際編寫的過程中,初學者並不知道我們的代碼會編譯在什麼地方,這個時侯,我們可以在運行的時侯ALT + 8(VC中快捷鍵)反編譯過來,一般有lea eax p、push offset p等取地址語句,這個時侯,我們大都需要以參數傳遞進來。所以,大家在編寫的時侯,一定要注意參數,因為線程的執行是在別的進程中,一個普通權限的應用程序是無法跨越進程來調試其他進程的。包括VC,也無法調試我們的遠程線程,熟悉匯編的朋友,可用softice調試,這需要一定的功底。

3、權限,這一點很重要,shotgun在這方面也介紹得很清楚了,網上相關的文章也很多,我就不多說了。文中的EnableDebugPriv函數可使本進程在internet、winLogin、lsass等進程中創建線程。win2k的進程查看器無法將其殺除。

4、進程ID獲方法較多,如:EnumProcesses、CreateToolhelp32Snapshot/Process32First/Process32Next、NtQuerySystemInformation等函數均可,為減少代碼,例子中的進程ID是直接在進程查看器中得到的。最後,我們再回到shotgun的文章中,這時侯我們因已經非常清楚他的方法中為何會多出一個DLL文件了。遠程線程的線程體本身就是LoadLibrary函數,即,線程的入口地址就是LoadLibrary的入口地址,這是系統Kernel32.dll中的函數,任何進程都可調用。線程中使用LoadLibrary函數將我們的DLL加載到系統空間內,線程一執行,我們的DLL就開始工作了。線程執行結束後,別忘了使用VirtualFreeEx將其申請的內存區釋放。

兩種方法一比較,很明顯:

1、在使用DLL時,創建十分簡單,也不需要太多的操作系統與內存操作知識,並可直接調試DLL文件。實現起來比較簡單。

2、直接拷備到進程中的方法稍為復雜一點,一不小心,很容易出現非法操作。當然,也去掉那了個讓人討厭DLL文件。程序執行後,很難找到他的來源地,是除了病毒以外的木馬隱藏的首選方法。這裡我大量參考了nongmin.cn(農民)程序的源碼,他的程序對我的幫助非常大。雖然未有謀面,但對他的計算機水平與作為十分的敬佩,並尊從他的作風,以後我所寫的所有非商業軟件或小代碼,均以源碼形式出現。這裡寫得有點亂,希望對大家能夠有所幫助,願與所有愛好計算機,從事計算機工作的朋友們共勉。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved