1.進程的繼承
創建進程的函數:
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 創建進程時打開的exe文件名 LPTSTR lpCommandLine, // 創建進程時的命令行參數 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 安全屬性,可用來設置該進程句柄是否可繼承 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 可用來設置進程的主線程句柄是否可繼承 BOOL bInheritHandles, // 是否繼承父進程的句柄表 DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // new environment block LPCTSTR lpCurrentDirectory, // 進程的當前目錄 LPSTARTUPINFO lpStartupInfo, // 程序狀態設置 LPPROCESS_INFORMATION lpProcessInformation // out參數進程信息 );
lpProcessAttributes ->安全屬性,用來設定進程是否能被繼承;一個SECURITY_ATTRIBUTES結構的指針,第三個成員為TRUE可繼承;
lpThreadAttributes ->安全屬性,線程能否被繼承;SECURITY_ATTRIBUTES結構指針,第三個成員為TRUE可繼承;
dwCreationFlags ->創建標記;如果是控制台程序,CREATE_NEW_CONSOLE表示子進程和父進程都有自己的控制台窗口;為NULL則子進程會把信息打印到父進程的控制台;
lpCurrentDirectory ->進程的當前目錄;如果為NULL,子進程的當前目錄將和父進程一樣;
如果想要當前目錄為子進程exe文件所在目錄,則需要用字符串指定;
獲取進程當前目錄:
char szBuffer[256] = {0}; GetCurrentDirectory(256,szBuffer); printf("%s\n",szBuffer);
當在進程A中CreateProcess創建一個子進程B時,進程A的句柄表中會多有兩個條記錄,一個是進程B的內核對象句柄,一個是B主線程的內核對象句柄;
如果lpProcessAttributes、lpThreadAttributes都為NULL;則兩個句柄都不可繼承,也就是可繼承狀態為0;
進程A句柄表:

此時如果在進程A中創建一個子進程C,那么C將不能繼承A的進程內核和線程內核句柄;
但如果將進程B設為可繼承,那么C可以從A的句柄表中繼承到B的進程句柄和主線程句柄;
利用這一特點可以在進程C中空值進程B;
1)目標
在A進程中創建一個進程(比如瀏覽器進程IE),並設定該子進程的進程內核句柄與主線程內核句柄為可繼承
在A進程中再創建一個進程B,在B中對IE進程控制
2)進程B
#include<stdio.h> #include<windows.h> int main(int argc, char* argv[]){ DWORD dwProcessHandle = -1; DWORD dwThreadHandle = -1; char szBuffer[256] = {0}; memcpy(szBuffer,argv[1],8); sscanf(szBuffer,"%x",&dwProcessHandle); memset(szBuffer,0,256); memcpy(szBuffer,argv[2],8); sscanf(szBuffer,"%x",&dwThreadHandle); printf("獲取IE進程、主線程句柄\n"); Sleep(2000); //掛起主線程 printf("掛起主線程\n"); ::SuspendThread((HANDLE)dwThreadHandle); Sleep(5000); //恢復主線程 ::ResumeThread((HANDLE)dwThreadHandle); printf("恢復主線程\n"); Sleep(5000); //關閉ID進程 ::TerminateProcess((HANDLE)dwProcessHandle,1); ::WaitForSingleObject((HANDLE)dwProcessHandle, INFINITE); printf("ID進程已經關閉.....\n"); return 0; }
3)進程A
#include<stdio.h> #include<windows.h> int main(int argc, char* argv[]){ char szHandle[8] = {0}; char szBuffer[256] = {0}; SECURITY_ATTRIBUTES ie_sa_p; ie_sa_p.nLength = sizeof(ie_sa_p); ie_sa_p.lpSecurityDescriptor = NULL; ie_sa_p.bInheritHandle = TRUE; SECURITY_ATTRIBUTES ie_sa_t; ie_sa_t.nLength = sizeof(ie_sa_t); ie_sa_t.lpSecurityDescriptor = NULL; ie_sa_t.bInheritHandle = TRUE; //創建一個可以被繼承的內核對象,此處是個進程 STARTUPINFO ie_si = {0}; PROCESS_INFORMATION ie_pi; ie_si.cb = sizeof(ie_si); TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe"); CreateProcess( NULL, szCmdline, &ie_sa_p, &ie_sa_t, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &ie_si, &ie_pi); //組織命令行參數 sprintf(szHandle,"%x %x",ie_pi.hProcess,ie_pi.hThread); sprintf(szBuffer,"D:/create_child.exe %s",szHandle); //定義創建進程需要用的結構體 STARTUPINFO si = {0}; PROCESS_INFORMATION pi; si.cb = sizeof(si); //創建子進程 BOOL res = CreateProcess( NULL, szBuffer, NULL, NULL, TRUE, //可以繼承父進程的句柄表 CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); return 0; }
4)結果
運行進程A,ie瀏覽器被打開;然后被進程B結束,瀏覽器關閉;
2.以掛起的方式創建進程
用CreateProcess函數可以創建一個進程;
正常情況下CreateProcess做以下事情:創建一個進程的內核對象、給進程分配一個4GB的空間、加載pe:包括exe、dll、修復dll、創建主線程,把程序入口地址交給EIP
如果參數dwCreationFlags傳入CREATE_SUSPENDED將以掛起的方式創建進程;
以這種方式創建的進程只有一個殼;也就是進程的主線程沒有開始運行;
需要調用函數讓進程的主線程恢復執行:
ResumeThread(ie_pi.hThread);
1)已掛起方式打開進程實例
例如:以掛起方式打開notpad++
#include<stdio.h> #include<windows.h> int main(int argc, char* argv[]){ TCHAR szAppName[256] = TEXT("D:\\Program Files\\Notepad++\\notepad++.exe"); STARTUPINFO si = {0}; //程序啟動設置 si.cb = sizeof(si); //只需要傳遞結構大小即可 PROCESS_INFORMATION pi; //記錄進程句柄信息等 ::CreateProcess( NULL, szAppName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi ); //恢復主進程 //::ResumeThread(pi.hThread); return 0; }
當沒有恢復主進程時,notepad++沒有運行;
但任務管理器中可以看到notpad++的進程,只不過占內存比正常運行小,因為主線程根本沒運行;

當調用ResumeThread恢復主進程時,notepad++正常運行

2)關於加殼
掛起方式創建的進程之是一個外殼;
我們可以修改外殼程序的內容;
這意味着在程序調用ResumeThread恢復執行的時候,外殼還是notepad++,但里面的內容完全變了;
可以將自己的程序拉伸為鏡像文件,然后替換外殼程序,接下來恢復執行時執行的就是自己的程序了;
這就是所謂的加殼;
想以這種借助其它程序的外殼來執行程序時,必須將外殼進程的主線程入口和ImageBase給替換掉;
入口地址=程序的鏡像基址ImageBase + 程序的入口函數地址OEP;
因為外殼程序已掛起的方式運行的;
也就是說外殼主線程是掛起的,可以得到主線程上下文對象CONTEXT;
CONTEXT contx; contx.ContextFlags = CONTEXT_FULL; //CONTEXT_FULL表示除了寄存器的值,還要得到其他信息 GetThreadContext(pi.hThread, &contx); //第一個參數為主線程句柄,主線程句柄在進程創建后會存在PROCESS_INFORMATION結構中;
然后就可以通過CONTEXT結構獲取程序入口點
//獲取入口點 DWORD dwEntryPoint = contx.Eax; //eax中存儲的入口點並不一定是ImageBase+oep,而是真正的入口點; //當程序不是占據ImageBase時,入口點將不等於ImageBase+oep; //獲取ImageBase char* baseAddress = (CHAR *) contx.Ebx+8; //這里的baseAddress不能直接輸出
這里獲取的baseAddress並不是想要的ImageBase,而是ImageBase存在哪里;
但父進程並不能直接通過這個地址找到ImageBase,因為這是子進程中的地址;
需要用到進程間讀取的函數:ReadProcessMemory;
5個參數依次為:進程句柄、從這進程的什么位置開始讀、讀的東西放到哪里、讀多少個字節、(out參數)真正讀了多少個字節;
TCHAR szBuffer[256]; memset(szBuffer,0,256); ReadProcessMemory(pi.hProcess,baseAddress,szBuffer,4,NULL);
這樣就得到了需要修改的外殼進程的入口點和ImageBase;
接下來想借殼上市只需要做相應的修改即可;