【文章标题】: 分析zeroadd算法,再造PE节增加器
【文章作者】: zhujian
【作者邮箱】: zhujian198@gmail.com
【软件名称】: zeroadd
【下载地址】: 自己搜索下载
【编写语言】: 汇编
【操作平台】: win x86
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
在看雪注册这么多年,一直潜水,实在惭愧。今天在修改一个软件时候,由于EXE剩余间隙太少想使用zeroadd增加个节。
刚下载下来就被杀毒软件给杀了。做了个免杀,虽然可以用了。但心里不爽,再加上这两天在学习PE结构,前两天刚用汇编
写了个PE查看器,心里就有了干脆自己写一个zeroadd。虽然知道增加PE增加节的原理,但是还是有些盲点。于是干脆开启神
器OD,分析zeroadd的算法。
分析过程如下:
首先来到事件处理函数
;如果选择了备份,那么就复制一份做为备份
0040110A |. E8 3B040000 call <jmp.&USER32.IsDlgButtonChecked> ; \IsDlgButtonChecked
0040110F |. 83F8 01 cmp eax, 1
00401112 |. 75 38 jnz short 0040114C
00401114 |. 68 20304000 push 00403020 ; /String2 = "C:\Documents and Settings\zhujian\Desktop\zeroadd 1.0\prjVcSp6.exe"
00401119 |. 68 85304000 push 00403085 ; |String1 = zeroadd.00403085
0040111E |. E8 81040000 call <jmp.&KERNEL32.lstrcpyA> ; \lstrcpyA
00401123 |. 68 1B314000 push 0040311B ; /StringToAdd = ".bak"
00401128 |. 68 85304000 push 00403085 ; |ConcatString = ""
0040112D |. E8 6C040000 call <jmp.&KERNEL32.lstrcatA> ; \lstrcatA
00401132 |. 6A 00 push 0 ; /FailIfExists = FALSE
00401134 |. 68 85304000 push 00403085 ; |NewFileName = ""
00401139 |. 68 20304000 push 00403020 ; |ExistingFileName = "C:\Documents and Settings\zhujian\Desktop\zeroadd 1.0\prjVcSp6.exe"
0040113E |. E8 1F040000 call <jmp.&KERNEL32.CopyFileA> ; \CopyFileA
00401143 |. 83F8 00 cmp eax, 0
; 打开要增加节的原始exe文件
00401166 |. 6A 00 push 0 ; /hTemplateFile = NULL
00401168 |. 68 80000000 push 80 ; |Attributes = NORMAL
0040116D |. 6A 03 push 3 ; |Mode = OPEN_EXISTING
0040116F |. 6A 00 push 0 ; |pSecurity = NULL
00401171 |. 6A 00 push 0 ; |ShareMode = 0
00401173 |. 68 000000C0 push C0000000 ; |Access = GENERIC_READ|GENERIC_WRITE
00401178 |. 68 20304000 push 00403020 ; |FileName = "C:\Documents and Settings\zhujian\Desktop\zeroadd 1.0\prjVcSp6.exe"
0040117D |. E8 E6030000 call <jmp.&KERNEL32.CreateFileA> ; \CreateFileA
0040118B |. A3 7C354000 mov dword ptr [40357C], eax ;保存句柄
;将界面上输入的节大小转换成16进制数字
00401190 |. 68 08304000 push 00403008 ; ASCII "200"
00401195 |. E8 13030000 call 004014AD ;转换字符为 16进制数字
0040119A |. A3 98354000 mov dword ptr [403598], eax ;保存 200h
;获得exe文件原始大小
0040119F |. 6A 00 push 0 ; /pFileSizeHigh = NULL
004011A1 |. FF35 7C354000 push dword ptr [40357C] ; |hFile = 000001AC (window)
004011A7 |. E8 D4030000 call <jmp.&KERNEL32.GetFileSize> ; \GetFileSize
;原始大小上 + 欲增加大小 + 0x2710 为什么是2710,估计是随便增大点吧
004011AC |. 0305 98354000 add eax, dword ptr [403598] ; 文件大小 + 200H
004011B2 |. 05 10270000 add eax, 2710 ;再加上 2710h why???
;创建一个文件镜像 大小为上面计算的大小
004011B7 |. 6A 00 push 0 ; /MapName = NULL
004011B9 |. 50 push eax ; |MaximumSizeLow
004011BA |. 6A 00 push 0 ; |MaximumSizeHigh = 0
004011BC |. 6A 04 push 4 ; |Protection = PAGE_READWRITE
004011BE |. 6A 00 push 0 ; |pSecurity = NULL
004011C0 |. FF35 7C354000 push dword ptr [40357C] ; |hFile = 000001AC (window)
004011C6 |. E8 A3030000 call <jmp.&KERNEL32.CreateFileMapping>; \CreateFileMappingA
;保存句柄 , 然后MAPVIEW
004011D3 |. A3 80354000 mov dword ptr [403580], eax
004011D8 |. 6A 00 push 0 ; /MapSize = 0
004011DA |. 6A 00 push 0 ; |OffsetLow = 0
004011DC |. 6A 00 push 0 ; |OffsetHigh = 0
004011DE |. 6A 02 push 2 ; |AccessMode = FILE_MAP_WRITE
004011E0 |. 50 push eax ; |hMapObject
004011E1 |. E8 A6030000 call <jmp.&KERNEL32.MapViewOfFile> ; \MapViewOfFile
;保存上面MapView的句柄
004011EE |. A3 84354000 mov dword ptr [403584], eax ;保存句柄
;判断PE有效?
004011F3 |. 66:8138 4D5A cmp word ptr [eax], 5A4D ;判断DOS标识
004011FE |. 0FB778 3C movzx edi, word ptr [eax+3C]
00401202 |. 033D 84354000 add edi, dword ptr [403584]
00401208 |. 813F 50450000 cmp dword ptr [edi], 4550 ;判断NT标识
;读取PE相关信息 节的数目,可选头大小
00401214 |. 893D 88354000 mov dword ptr [403588], edi ;edi NT头起始地址
0040121A |. 0FB74F 06 movzx ecx, word ptr [edi+6] ;节数
0040121E |. 66:037F 14 add di, word ptr [edi+14] ;可选头的大小
;指针移到节表的开始处 di(nt起始) + 可选头大小 + x018 (PE标识 + nt文件头大小 正好是0x18)
00401222 |. 83C7 18 add edi, 18 ;加18H (PE标识+ NT文件头大小)
;循环节个数次,移动到节表末尾
00401225 |> /83C7 28 /add edi, 28
00401228 |.^\E2 FB \loopd short 00401225
;判断节表区后面 9 个DWORD是否为空,有足够空间不??
0040122A |. B9 0A000000 mov ecx, 0A
0040122F |. BA 28000000 mov edx, 28
00401234 |> 833C3A 00 /cmp dword ptr [edx+edi], 0
00401238 |. 0F85 92010000 |jnz 004013D0
0040123E |. 83C2 04 |add edx, 4
00401241 |.^ E2 F1 \loopd short 00401234
00401243 |. 8B35 88354000 mov esi, dword ptr [403588] ;PE头地址
00401249 |. 66:FF46 06 inc word ptr [esi+6] ;节数目 +1
0040124D |. B9 01000000 mov ecx, 1 ;循环变量 至 1
00401252 |. 837E 38 00 cmp dword ptr [esi+38], 0 ;节对齐 1000H 等于O吗
00401256 |. 75 08 jnz short 00401260
00401258 |. 837E 3C 00 cmp dword ptr [esi+3C], 0 ;文件对齐 等于 0吗
00401260 |> \83EF 28 sub edi, 28 ;获得最后一个节的起始地址
00401263 |. 8B47 0C mov eax, dword ptr [edi+C] ;节内存偏移地址
00401266 |. 0347 08 add eax, dword ptr [edi+8] ;加上 虚拟大小 (数据实际大小)
0040126B |. FF76 38 push dword ptr [esi+38] ;节对齐力度 1000H
0040126E |. 50 push eax
0040126F |. E8 A6020000 call 0040151A
;计算对齐后的地址
0040126B |. FF76 38 push dword ptr [esi+38] ;4000H
0040126E |. 50 push eax
0040126F |. E8 A6020000 call 0040151A ; 5000H
-----------------40151A计算对齐值函数----------------------
0040151A /$ 55 push ebp
0040151B |. 8BEC mov ebp, esp
0040151D |. 53 push ebx
0040151E |. 52 push edx
0040151F |. 8B45 08 mov eax, dword ptr [ebp+8]
00401522 |. 8B5D 0C mov ebx, dword ptr [ebp+C]
00401525 |. 33D2 xor edx, edx
00401527 |> 50 /push eax
00401528 |. F7F3 |div ebx
0040152A |. 85D2 |test edx, edx
0040152C |. 58 |pop eax
0040152D |. 74 03 |je short 00401532
0040152F |. 40 |inc eax
00401530 |.^ EB F5 \jmp short 00401527
00401532 |> 5A pop edx
00401533 |. 5B pop ebx
00401534 |. C9 leave
00401535 \. C2 0800 retn 8
-----------------------------------------------------------
00401279 |. 8B47 14 mov eax, dword ptr [edi+14] ;节文件大小
0040127C |. 0347 10 add eax, dword ptr [edi+10] ;节文件偏移
00401281 |. FF76 3C push dword ptr [esi+3C] ;文件对齐力度
00401284 |. 50 push eax ;上面计算出来的和
00401285 |. E8 90020000 call 0040151A ; 5000H
;修改IMAGESZIE
004012AD |> \0146 50 add dword ptr [esi+50], eax ;修改 IMAGESIZE 大小
004012B0 |. 83C7 28 add edi, 28 ;后移到最后一个节表
;给新增加的节的第一个名字字段赋值
004012B3 |. 57 push edi
004012B4 |. 8D35 00304000 lea esi, dword ptr [403000]
004012BA |. B9 08000000 mov ecx, 8
004012BF |. F3:A4 rep movs byte ptr es:[edi], byte ptr [esi]
004012C1 |. 5F pop edi
; 给新节表的各个字段赋值
004012C2 |. A1 98354000 mov eax, dword ptr [403598]
004012C7 |. 8947 08 mov dword ptr [edi+8], eax
004012CA |. A1 90354000 mov eax, dword ptr [403590]
004012CF |. 8947 0C mov dword ptr [edi+C], eax
004012D2 |. A1 98354000 mov eax, dword ptr [403598]
004012D7 |. 8947 10 mov dword ptr [edi+10], eax
004012DA |. A1 94354000 mov eax, dword ptr [403594]
004012DF |. 8947 14 mov dword ptr [edi+14], eax
004012E2 |. C747 24 20000>mov dword ptr [edi+24], E0000020
004012E9 |. 8B3D 84354000 mov edi, dword ptr [403584] ;最后一节表起始
004012EF |. 033D 94354000 add edi, dword ptr [403594] ;
004012F5 |. 8B0D 98354000 mov ecx, dword ptr [403598]
004012FB |. 32C0 xor al, al
004012FD |. F3:AA rep stos byte ptr es:[edi] ;增加的新节全部清零
;后面就是写入文件,关闭文件,销毁句柄等等了。基本算法就到这里了。
总结,增加新节做了如下几个步骤:
1、基于镜像方式把PE载入内存,方便操作。当然也可以直接写文件
2、读取节的个数,判断节表后面是否有足够空间增加新节,如果没空间就OVER
3、如果有空间就增加新节表,填写节表相关项。主要内存对齐和文件对齐力度
4、重新计算IMAGESIZE大小,并修改
5、新节内存区域全部清零
6、重新把输入写回硬盘
下面是C语言实现代码:
/*
* 作者:zhujian
* 描述:为PE文件增加新节
* 日期:2009/1/30
*/
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include "resource.h"
char szFileName[MAX_PATH] = {0}; //界面上输入的文件名
char szSectionName[MAX_PATH] = {0}; //界面上输入的要增加的节的名字
char szSectionSize[MAX_PATH] = {0}; //界面上输入的节的大小字符串表示
char szBakFileName[MAX_PATH] = {0}; //备份文件的文件名
DWORD dwOrgFileHandle = 0; //原始文件的句柄
DWORD dwSectionSize = 0; // 界面上输入的要增加的节的大小 数字表示
DWORD dwFileSize = 0; //文件大小
DWORD dwFileMap = 0;//MapFile的句柄
DWORD dwMapView = 0;//Map view 句柄
DWORD dwSecNum = 0;//原来节的数目
int i;
char szTemp[50] = {0};//将输入的节大小拼凑成16进制字符串的一个临时变量
//-------
PIMAGE_DOS_HEADER pDosH = NULL;
PIMAGE_NT_HEADERS pNtH = NULL;
PIMAGE_SECTION_HEADER pFirstSecH= NULL; //节表第一项的首地址
PIMAGE_SECTION_HEADER pLastSecH= NULL; //节表最后一项的首地址
PIMAGE_SECTION_HEADER pAddSecH= NULL; //新增加节表项的首地址
HINSTANCE mhInstance = NULL;
HICON mhIcon = NULL;
DWORD dwNewSecAlign = 0; //新增节的内存对齐
DWORD dwNewFileAlign = 0;//新增加节的文件对齐
DWORD dwOkFile = 0; //最后生成的文件的句柄
DWORD dwR = 0;
int translat(char c)
{
if(c<='9'&&c>='0') return c-'0';
if(c>='a' && c<='f') return c-87;
if(c>='A' && c<='F') return c-55;
return -1;
}
//16进制字符串转10进制数字
int Htoi(char *str)
{
int i,n=0,stat;
int length=strlen(str);
if(length==0) return 0;
for(i=0;i<length;i++)
{
stat=translat(str[i]);
if(stat>=0) n=n*16+stat;
}
return n;
}
//计算对齐后的地址
DWORD getAlign(DWORD addr,DWORD base)
{
int j = 0;
for (j = addr;j % base != 0;j++)
{
}
return j;
}
INT_PTR CALLBACK DialogProc(
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
case WM_CLOSE:
EndDialog(hwndDlg,NULL);
return TRUE;
case WM_INITDIALOG:
mhIcon = LoadIcon(mhInstance,IDI_ICON1);
SendMessage(hwndDlg,WM_SETICON,ICON_BIG,mhIcon);
return TRUE;
case WM_COMMAND:
if (wParam == IDC_BUTTON_EXIT)
{
EndDialog(hwndDlg,NULL);
return TRUE;
}
else if (wParam == IDC_BUTTON_SELFILE)
{
OPENFILENAME stOFN = {0};
stOFN.lStructSize = sizeof stOFN;
stOFN.lpstrFilter = "Executable Files(*.exe,*.dll)\0*.exe\0\All File(*.*)\0*.*\0";
stOFN.nMaxFile = MAX_PATH;
stOFN.lpstrFile = szFileName;
stOFN.hwndOwner = hwndDlg;
stOFN.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
if (GetOpenFileName(&stOFN))
{
SetDlgItemText(hwndDlg,IDC_EDIT_SELFILE,szFileName);
}
return TRUE;
}
else if (wParam == IDC_BUTTON_ADD)
{
GetDlgItemText(hwndDlg,IDC_EDIT_NAME,szSectionName,MAX_PATH);
GetDlgItemText(hwndDlg,IDC_EDIT_SIZE,szSectionSize,MAX_PATH);
if (strlen(szSectionSize)==0)
{
MessageBox(hwndDlg,"请输入新节的名字","提示",MB_OK);
return TRUE;
}
if (strlen(szSectionSize)==0)
{
MessageBox(hwndDlg,"请输入新节的大小","提示",MB_OK);
return TRUE;
}
//备份原程序
if (IsDlgButtonChecked(hwndDlg,IDC_CHECK_BAK))
{
strcpy(szBakFileName,szFileName);
strcat(szBakFileName,".bak");
CopyFile(szFileName,szBakFileName,FALSE);
}
//打开原EXE准备增加节
dwOrgFileHandle = CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
strcat(szTemp,"0x");
strcat(szTemp,szSectionSize);
dwSectionSize = Htoi(szTemp);//字符转数字
//获得文件大小
dwFileSize = GetFileSize(dwOrgFileHandle,NULL);
dwFileSize += dwSectionSize;
dwFileSize += 0x00002710;//逆向addzero时候得到的数字,反正就是加大点
dwFileMap = CreateFileMapping(dwOrgFileHandle,NULL,PAGE_READWRITE,0,dwFileSize,NULL);
dwMapView = MapViewOfFile(dwFileMap,FILE_MAP_WRITE|FILE_MAP_READ,NULL,NULL,NULL);
pDosH = (PIMAGE_DOS_HEADER)dwMapView;
pNtH = (PBYTE)(dwMapView)+(pDosH->e_lfanew);
if(IMAGE_DOS_SIGNATURE == pDosH->e_magic)
{
if (IMAGE_NT_SIGNATURE == pNtH->Signature)
{ // 增加节处理开始
dwSecNum = pNtH->FileHeader.NumberOfSections;
//节表开始处
pFirstSecH =(PBYTE)pNtH+(pNtH->FileHeader.SizeOfOptionalHeader + sizeof(IMAGE_FILE_HEADER) + 4);
//将要增加节的其实地址,它在最后一个节的后面
pAddSecH = (PBYTE)(pFirstSecH) + (sizeof(IMAGE_SECTION_HEADER) * dwSecNum);
//判断最后一个节后面是否有多余空间来增加节表
for (i = 0;i < 10;i++)
{
if (*((PDWORD)(pAddSecH)+i)!=0)
{
break;
}
}
if (10 == i)
{//表示有空间增加新节表
pNtH->FileHeader.NumberOfSections++;//节表数加1
//获得最后一个节
pLastSecH = ((PBYTE)(pAddSecH) - sizeof(IMAGE_SECTION_HEADER));
if (pNtH->OptionalHeader.FileAlignment != 0 && pNtH->OptionalHeader.SectionAlignment != 0)
{
//节对齐处理
dwNewSecAlign = getAlign(pLastSecH->VirtualAddress + pLastSecH->Misc.VirtualSize,pNtH->OptionalHeader.SectionAlignment);
//文件对齐处理
dwNewFileAlign = getAlign(pLastSecH->PointerToRawData+pLastSecH->SizeOfRawData,pNtH->OptionalHeader.FileAlignment);
//设置新增加节表的各个项
pNtH->OptionalHeader.SizeOfImage += getAlign(dwSectionSize,pNtH->OptionalHeader.SectionAlignment);
memcpy(pAddSecH->Name,szSectionName,8); //name
pAddSecH->VirtualAddress = dwNewSecAlign;
pAddSecH->Misc.VirtualSize = dwSectionSize;
pAddSecH->PointerToRawData = dwNewFileAlign;
pAddSecH->SizeOfRawData = getAlign(dwSectionSize,pNtH->OptionalHeader.FileAlignment);
pAddSecH->Characteristics = 0xE0000020; //Characteristics
//最后一个节内存全部清零
RtlZeroMemory(((PBYTE)(pDosH) + pAddSecH->PointerToRawData),getAlign(dwSectionSize,pNtH->OptionalHeader.FileAlignment));
//写入硬盘
dwOkFile = CreateFile("swap.pe",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
WriteFile(dwOkFile,dwMapView,pAddSecH->PointerToRawData + pAddSecH->SizeOfRawData,&dwR,NULL);
CopyFile("swap.pe",szFileName,FALSE);
CloseHandle(dwOkFile);
DeleteFile("swap.pe");
MessageBox(hwndDlg,"增加新节成功","恭喜",MB_OK);
}
else
{
MessageBox(hwndDlg,"PE文件对齐或内存对齐为零","失败",MB_ICONINFORMATION);
}
}
else
{
MessageBox(hwndDlg,"没有足够空间增加节表","提示",MB_ICONINFORMATION);
}
}
else
{
MessageBox(hwndDlg,"无效PE文件,NT标识错误","提示",MB_ICONINFORMATION);
}
}
else
{
MessageBox(hwndDlg,"无效PE文件","提示",MB_ICONINFORMATION);
}
UnmapViewOfFile(dwMapView);
CloseHandle(dwFileMap);
CloseHandle(dwOrgFileHandle);
return TRUE;
}
}
return FALSE;
}
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
mhInstance = hInstance;
DialogBoxParam(hInstance,IDD_DIALOG_ADDSECTION,NULL,DialogProc,NULL);
ExitProcess(NULL);
return 0;
}
小弟潜水多年的菜鸟难免有不足之处,代码只是实现了基本功能,有很多地方没做容错判断。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2009年01月31日 14:49:50
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!