常見注入手法第一講EIP寄存器注入
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
鑒於注入手法太多,所以這里自己整理一下,每個注入單獨一片博客。方便大家簡單理解。
但是有的注入可能需要需要注入方法的相結合,什么意思,也就是說以前我們寫的匯編代碼注入,原理就是通過遠程線程注入得來的
所以前提你就要理解遠程線程注入
今天我們講一下EIP寄存器注入。我們上一講是 異常處理(SEH)第一講,但是中間岔開了,也是為了整理一下注入手法。所以異常第二講明天繼續。此篇文章主要講解注入。和異常沒有任何關系,如果你是奔着異常處理而來,那么你可以直接去看異常處理。
廢話不多說,開始講解。
我們昨天,也就是異常第一講的時候,我們知道了我們可以設置寄存器的值,或者獲取寄存器的值,微軟也幫我們提供了API
但是現在這個API正是我們要用的時候了。
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
一丶寄存器注入,之寫入代碼注入
什么是寫代碼注入,簡而言之就是你把代碼寫進了對方進程進行執行,全程沒有任何DLL,而且殺毒不會報毒,屬於很強大的手法,因為我們掛起線程,然后寫內容進去執行,比如你的軟件,你會不會往內存寫內容。所以殺毒不能報毒,這個屬於很正常的操作。
我們開始吧
昨天簡單說了下思路
/* 思路: 1.查找窗口,獲得窗口句柄 2.獲得線程ID進程PID 3.獲得線程句柄,同時也要獲得進程的句柄 4.掛起線程 5.獲得寄存器的值 6.修改EIP的值 7.申請遠程內存 8.寫入遠程內存,把EIP也要寫進去,這樣遠程執行完畢之后會切換回來繼續執行 9.恢復線程 10.關閉線程句柄 */
一看上面,我們發現我們要寫的很多,其實一點也不多,主要上面是思路,體現在代碼上很少。
那么從第一步開始寫吧
今天我們還是拿我們可愛的32位計算器做實驗 :) (其他的我也沒有
)
①.查找窗口獲得窗口句柄
HWND hWnd = FindWindow(TEXT("SciCalc"), TEXT("計算器")); if (NULL == hWnd) { MessageBox(NULL, TEXT("對不起,找不到窗口"), TEXT("錯誤"), MB_OK); return 0; }
這一步不多講了,如果想學習注入,API的知識必不可少,所以不會API,請自己查詢MSDN,或者Google一下API的意思,在這里我認為大家都已經會了API
②.獲得線程的ID
/*2.獲得線程的PID和進程的PID*/
DWORD dwTid = 0;
DWORD dwPid = 0;
dwTid = GetWindowThreadProcessId(hWnd, &dwPid);
③.獲得進程和線程的句柄
HANDLE hThrHandle = NULL; HANDLE hProHandle = NULL; hThrHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTid); if (NULL == hThrHandle) { MessageBox(NULL, TEXT("對不起,獲取線程句柄失敗"), TEXT("Title"), MB_OK); return 0; } hProHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if (NULL == hProHandle) { MessageBox(NULL, TEXT("對不起,獲取進程句柄失敗"), TEXT("Title"), MB_OK); return 0; }
④.掛起線程
SuspendThread(hThrHandle); //給個線程的句柄,掛起這個線程
⑤.獲取寄存器的值
獲取寄存器的值,主要是為了我們要獲取當前的EIP的值.然后還回去的時候也需要.
CONTEXT context = { 0 }; context.ContextFlags = CONTEXT_FULL; //比如初始化標志 BOOL bRet = GetThreadContext(hThrHandle, &context); if (!bRet) { MessageBox(NULL, TEXT("對不起,獲取寄存器信息失敗"), TEXT("Title"), MB_OK); return 0; }
這里需要注意一下,我們初始化的標志,這個在MSDN中是查詢不到的,要到定義結構體地方的位置,看注釋可以看到.
這里簡單看一下,具體怎么組合的,自己詳細去看.
⑥.申請遠程內存,一會要寫入我們的InjectCOde,(也就是把二進制寫進去)
LPVOID lpCode = NULL; lpCode = VirtualAllocEx(hProHandle, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NULL == lpCode) { MessageBox(NULL, TEXT("對不起,申請遠程內存失敗"), TEXT("Title"), MB_OK); return 0; }
我們最好輸出一下,因為一會使用OD調試的時候,要看下內存是否被申請了.
printf("%p \r\n", lpCode);
⑦.注意Release版本和Debug版本的區別
Release版本,調用函數的時候是直接調用
Debug的版本,調用函數的時候,默認會有一層Jmp跳轉
看一下圖片,我們要調用任何一個函數(Debug版本下)
調用
在我們眼中,看着是直接Call,但是F11進去,則會看到一個Jmp
JMP
所以,對於Debug版本,我們要取出Jmp的地址,這個地址才是真正的函數地址.
而Release版本,則沒有,需要用OD調試,大家自己去看看即可.這里不做演示.
而Release版本,則不用怎么麻煩了,直接寫函數地址就行(這里為了下方往我們申請的內存中寫函數里面的內容准備的,所以如果是Release版本,直接填上函數名即可.)
Debug版本的獲取函數地址.
void *lpAddr = (void *)((long)InjectCode + *(long *)((char *)InjectCode +1)+5);
注意: inject和MyAdd都是一個函數,剛才舉例子是用的MyAdd,那個函數沒有,純粹是舉例子的.至於InjectCode
下方會仔細講解.這里簡單知道就行
至於上面為什么那樣寫,我們可以暫時知道這樣寫能獲取函數地址即可.因為重點不在這里.在下方EIP注入的地址重定位問題.鑒於時間關系,大家如果想知道的,自己去OD看下就明白了,或者自己單步拆開來看.
⑧.把InjectCode函數,當做代碼,寫入到我們申請的空間
WriteProcessMemory(hProHandle, lpCode, lpAddr, 100, NULL);//寫入100個字節 如果是Release版本,則不用計算Debug那種公式了.我們直接寫成下方代碼即可 WriteProcessMemory(hProHandle, lpCode, InjectCode, 100, NULL);//寫入100個字節
其實到這里就是簡單調用API,往遠程寫了一塊內存而已.現在我們中間省略幾步,先把框架寫出來,
也就是說這個框架不會變動的.至於中間的這幾步,因為很重要,為了防止大家不太明白,所以框架先寫出來.
下面具體講解這幾步怎么寫.當然,最后我會貼出完整代碼.
⑨.執行我們的核心代碼...
⑩.修改EIP的值,修改為我們的InjectCode的位置,讓EIP跳轉到InjectCode的位置執行代碼
context.Eip = (DWORD)lpCode; SetThreadContext(hThrHandle, &context); 注意,LPcode是我們申請的遠程的內存的首地址,現在是讓EIP指向這個地方, 當做代碼運行.
核心代碼,一定要懂.
11.釋放資源
ResumeThread(hThrHandle);
CloseHandle(hProHandle);
CloseHandle(hThrHandle); 不重要,知道就好
現在我們的框架已經寫出來了,現在我們要知道
我們讓EIP把我們申請的內存的位置當做代碼跑,而我們申請的內存,寫入的是我們的INJECTCODE的代碼,也就是說這個函數中的所有二進制都當做代碼去跑了.
那么我們就可以做點我們的事情了.
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
二丶注入代碼要寫入什么
①.Call的講解,和InjectCode的代碼
我們上面說了很多InjectCode,那么這個函數到底是寫入的什么__declspec(naked) void InjectCode()
{ __asm { NOP NOP //對其一下以后使用 pushad pushf push 0 push 0 push 0 push 0 _emit 0ffh //offh 和 15h相當於Call
_emit 015h _emit 0x01 _emit 0x02 //這段二進制其實是隨便Call 一個地址. 總結出來匯編代碼就是 Call [地址] _emit 0x03 _emit 0x04 popf popad _emit 0ffh //前兩個相當於JMP 下面是地址,總結出來是 Call [地址] _emit 025h _emit 0x00 //跳轉的位置,隨機寫入 _emit 0x00 _emit 0x00 _emit 0x00 label1: _emit 0x1 _emit 0x2 _emit 0x3 _emit 0x4 label2: _emit 0x2 _emit 0x3 _emit 0x4 _emit 0x5 } }
好,看到上方代碼是不是不想往下看了,但是其實很簡單,為啥看上面的代碼
我們不直接寫匯編代碼
這是因為,我用的是2013 (我的天終於換成了2013),但是為什么這樣寫,因為我被坑了,不這樣寫不能操作.
在VC++6.0中的寫法,我下方貼圖
其實你把Call 和我寫的二進制當做匯編看就行,因為2013的匯編,和VC6.0的匯編二進制代碼不一樣,因為段的問題,不太一樣,所以只能寫成那樣了
首先,我們介紹下這兩個函數的作用吧
第一個Call, 這個直接Call 標號2取內容 其實就是把標號2定義的4個字節,當做一個函數地址取運行了. 假設 標號2的地址是
0x00400020 ,那么對它取內容就是第一個定義的00的位置.但是注意,call 后面跟的是一個4個字節的地址.
所以說我們取內容,然后把里面的值我們通過我們的手法把一個函數地址的值給它,那么不就相當於調用了我們的函數了嗎.
如果不懂,看圖:
那么現在經過我講解,知道為什么我們要定義4個 _emit了嗎,因為這個要通過我們的手法,寫入一個函數的地址,然后讓CALL去調用.
那么現在我們介紹下Jmp的作用
②.Jmp的作用
Jmp的作用和上面一樣,就是JMP標號,其實就是JMP 對標號取內容的值當做地址去執行
為什么這樣做,因為我們寫完我們的代碼要讓它回到以前執行的代碼位置處
而正好我們定義了4個_emit 這4個字節可以通過我們上面框架的時候,通過獲取寄存器信息的EIP的值,獲得的EIP,然后寫到這4個字節中
什么意思? 就是我們上面獲得了EIP的值了,那么把這個EIP的值,寫入到這4個字節中,那么JMP的時候,就JMP這4個字節,不就實現了還原EIP的位置了嗎.
看了怎么多的概念,暈了,那么我們現在講我們的核心代碼
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
三丶核心代碼的編寫
我們上面預留出了第九個步驟,為什么,因為這個步驟要知道的知識太多,雖然代碼很少.
我們知道,上面的InjectCode,我們要當做代碼執行,而我們總共預留出了8個字節的空間,也就是標號1和標號2
那么我們現在要把一個函數地址,寫到這個標號中,還有把獲取到的EIP的值,也寫到這里面,那么當我們第十步的時候
EIP的值會切換到我們寫入的這塊內存,而我們寫入的就是INJECTCODE,也就是說變相的等於EIP切換到我們寫的函數
那么現在就回遇到一個問題,執行我們的代碼的時候,如果我們給了函數的地址,那么則會執行這個函數,
如果我們還原了,那么則會注入完成之后還原.
有的人可能會想,很簡單,我用WriteprocessMemory把這兩個值寫入到這里不就完了.
那么現在可以寫入,也是沒問題的,
但是會出現兩個問題.(其實也都算一個問題)
Call的時候我們要Call的標號是不是正確的?
給標號的位置寫入內存的時候是不是正確的?
好,告訴你們吧,不正確,因為在自己進程中Call一個標號,相當於Call一個常量.
那么在別人進程中也是Call一個常量.但是位置就不一樣了
現在我們要解決這個地址重定位問題.
一丶解決Call的時候的問題
我們都知道,Call的時候,是這樣的
Call dword ptr[00400000] 二進制代碼則是 ff 15 00 00 40 00
那么第一步,我們要算出偏移來
Call的地址位置 - InjectCode的地址位置 = call和injectcode位置的偏移
然后這個偏移 +2 的位置則是我們要修改的地址.
為什么要修改,因為你Call的時候不能這樣去Call 我們要保留Call 也就是二進制的 ff 15
那么后邊的地址,我們要通過我們代碼,把它修改為標號的位置.
如果不懂,看下圖片.
那么現在修正了位置,我們就可以寫我們的代碼了.
代碼就兩句,其實主要是為了讓大家懂原理
long DestValue = (long)(char *)lpCode + 0x1C; //定位到標號位置,轉為整數 WriteProcessMemory(hProHandle, (char *)lpCode + 0xF,//call的位置后面 +1修改 &DestValue, sizeof(DestValue), NULL);
看到沒,為什么+1c 為什么 + F,就是上面的內容
+1C 就是首地址 + 1C的偏移,您能找到標號的位置.
+F 也就是首地址 + 偏移,找到Call后面的4個字節地址的位置
現在用 WriteprocessMemory則把Call的地址修改為了標號的位置
Call dwptr[正確的標號] 標號: 00 00 00 00
那么我們的EIP切還的時候,代碼正常執行,遇到這段代碼,則會去Call標號里面的內容去調用了,是不是.
但是現在它里面額內容我們應該寫成函數指針,這樣才會調用函數,現在這是讓它正確的知道去哪里Call了
而修改標號的內容,也是算偏移
找到標號的位置.把你想要修改的值寫上
看代碼
DestValue = (long)GetProcAddress((GetModuleHandle("user32.dll")), "MessageBoxA"); WriteProcessMemory(hProHandle, (char *)lpCode + 0x1C, &DestValue, sizeof(DestValue),NULL);
知道為啥+ 1c了吧,首地址 + 偏移等於標號位置,標號位置修改為函數地址,當Call的時候則會call這個函數了
那么我們要換回去也是一樣的
找到jmp 后面地址的位置, 首地址 + 偏移 + 2 = jmp 后面地址的位置
然后找到另一個標號位置,把這個標號位置,寫入到jmp后面,那么就把jmp的地址修改了.
而標號中的內容,我們可以寫成以前EIP的位置,那么不就注入完成之后返回了.
完整代碼:
#include <stdio.h> #include <windows.h> int MyAdd(int n1, int n2) { return n1 + n2; } __declspec(naked) void InjectCode() { __asm { NOP NOP //對其一下以后使用 pushad pushf push 0 push 0 push 0 push 0 _emit 0ffh _emit 015h _emit 0x01 _emit 0x02 //這段二進制其實是隨便Call 一個地址. _emit 0x03 _emit 0x04 popf popad _emit 0ffh _emit 025h _emit 0x00 //跳轉的位置,隨機寫入 _emit 0x00 _emit 0x00 _emit 0x00 label1: _emit 0x1 _emit 0x2 _emit 0x3 ;寫入EIP返回的地址 _emit 0x4 label2: _emit 0x2 _emit 0x3 _emit 0x4 ;存放我們要寫入的值,可以寫入函數地址,也可以寫入EIP返回的地址 _emit 0x5 } } int main(_In_ int _Argc, _In_reads_(_Argc) _Pre_z_ char ** _Argv, _In_z_ char ** _Env) { /*1.獲取窗口句柄*/ __asm { NOP } //InjectCode(); int r = MyAdd(1, 2); HWND hWnd = FindWindow(TEXT("SciCalc"), TEXT("計算器")); if (NULL == hWnd) { MessageBox(NULL, TEXT("對不起,找不到窗口"), TEXT("錯誤"), MB_OK); return 0; } /*2.獲得線程的PID和進程的PID*/ DWORD dwTid = 0; DWORD dwPid = 0; dwTid = GetWindowThreadProcessId(hWnd, &dwPid); /*3.獲得進程和線程的句柄*/ HANDLE hThrHandle = NULL; HANDLE hProHandle = NULL; hThrHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTid); if (NULL == hThrHandle) { MessageBox(NULL, TEXT("對不起,獲取線程句柄失敗"), TEXT("Title"), MB_OK); return 0; } hProHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if (NULL == hProHandle) { MessageBox(NULL, TEXT("對不起,獲取進程句柄失敗"), TEXT("Title"), MB_OK); return 0; } /*4.掛起線程*/ SuspendThread(hThrHandle); /*5.獲取寄存器的值*/ CONTEXT context = { 0 }; context.ContextFlags = CONTEXT_FULL; //比如初始化標志 BOOL bRet = GetThreadContext(hThrHandle, &context); if (!bRet) { MessageBox(NULL, TEXT("對不起,獲取寄存器信息失敗"), TEXT("Title"), MB_OK); return 0; } /*6.申請內存*/ LPVOID lpCode = NULL; lpCode = VirtualAllocEx(hProHandle, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NULL == lpCode) { MessageBox(NULL, TEXT("對不起,申請遠程內存失敗"), TEXT("Title"), MB_OK); return 0; } printf("%p \r\n", lpCode); /*因為是Debug版本,所以計算一下JMP跳的位置*/ char * ch1 = ((char *)InjectCode + 1); long ch2 = *(long *)ch1; void *lpAddr = (void *)((long)InjectCode + *(long *)((char *)InjectCode +1)+5); // InjectCode(); /*7.寫入內存*/ WriteProcessMemory(hProHandle, lpCode, lpAddr, 100, NULL);//寫入100個字節 /*釋放資源*/ /*8.解決重定位的問題*/ //找到標號的位置,然后找到jmp的位置,在jmp的2個字節后面,寫入標號的位置 //標號的位置 標號 - 首地址 = 偏移 + 指令大小 首地址 + 偏移 = 標號位置 long DestValue = (long)(char *)lpCode + 0x1C; //定位到標號位置,轉為整數 WriteProcessMemory(hProHandle, (char *)lpCode + 0xF,//call的位置后面 +1修改 &DestValue, sizeof(DestValue), NULL); DestValue = (long)GetProcAddress((GetModuleHandle("user32.dll")), "MessageBoxA"); WriteProcessMemory(hProHandle, (char *)lpCode + 0x1C, &DestValue, sizeof(DestValue),NULL); DestValue = (long)(char *)lpCode + 0x20;//找到標號位置 //寫入EIP以前的值,然后JMP跳轉到地方. 20 標號位置 WriteProcessMemory(hProHandle, (char *)lpCode + 0x18, &DestValue, sizeof(DestValue), NULL);//找到EIP的位置 /*9.修改EIP的值,讓其跳轉*/
DestValue = (long)context.Eip; WriteProcessMemory(hProHandle, (char *)lpCode + 0x20, &DestValue, sizeof(DestValue), NULL); context.Eip = (DWORD)lpCode; SetThreadContext(hThrHandle, &context);
ResumeThread(hThrHandle); CloseHandle(hProHandle); CloseHandle(hThrHandle); return 0; }
如果不懂,請私信留言.關於地址重定位問題,當然不止這一個辦法,比如上次我們寫的匯編代碼注入,也是解決了地址重定位問題
當然這個還可以寫成匯編版本,留作作業,也可以把Messagebox變成Loadlibrary,那么則會執行一個Dll,具體功能你自己在Dll里面編寫即可.
這些我會在星期六星期天放到作業當中,自己做一下
下幾節課講解APC注入,以及異常.
課堂資料:
鏈接:http://pan.baidu.com/s/1hr4ukdA 密碼:rlju
原創不易,請愛心點贊評論,轉發.如果不會,請下方留言.
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝