常見注入手法第一講EIP寄存器注入


 

             常見注入手法第一講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/

轉載請注明出處,謝謝

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM