打造VC++伪装器
楚茗
前几天,在一个黑客站点看到VC++伪装器这么一个小工具,突然感到很眼熟。对了,以前在看雪学院(www.pediy.com)看到过欺骗查壳工具的文章,赶紧去复习复习。当然了,现在我已经复习好了,这里我就告诉大家原理和带大家一起用VC打造一个简单的VC++伪装器。
首先我们简单的认识一下PE文件。因为只有对PE文件很熟悉的情况下读本文才会更加游刃有余。PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。大概的PE文件格式如下:
DOS MZ header―所有PE文件(甚至32位的DLLs)必须以一个简单的DOS MZ header开始
DOS stub-DOS stub实际上是个有效的EXE,大多数情况下它是由汇编器/编译器自动生成
PE header-PE header是PE相关结构 IMAGE_NT_HEADERS的简称
Section table-IMAGE_SECTION_HEADER
Section 1-PE文件的真正内容划分成块,称之为sections,同sections中的代码/数据属性相同
Section 2
Section ...
Section n
上面的表格就是PE文件的物理结构。还有VC程序的基地址是 0x400000,也就是说exe程序运行时会被加载内存的该地址处。PE的各种结构中,涉及到很多地址,偏移。有些是指在文件中的偏移,有的是指在内存中的偏移。一定要搞清楚了才能真正的理解本文,当然套用其中的一些代码也是可以的。关于PE文件的更多知识,大家可以到看雪学院的论坛上查找。我在说下去就太多了。
其实大家在作木马免杀时使用到的修改程序入口点和添加花指令技术就是手工伪造。呵呵,我们这里就是用程序来做罢了。基本思路相同,就是先增加一个节写入VC++入口处的特征代码,然后JMP到原程序的入口点,最后修改程序的入口点到我们增加的那个节处。侦壳工具检测到VC的特征代码就把壳识别为VC++了。木马免杀中用到的G!X Protector v1.2和木马彩衣等可能也是这个原理。
编程语言:VC++
侦壳工具:Peid
特征码提取工具:OllyDbg
首先我们用OD打开一个MFC程序,来到程序的入口点,代码如下:
地址 16进制 反汇编
00401D70 | 55 push ebp
00401D71 |. 8BEC mov ebp,esp
00401D73 |. 6A FF push -1
00401D75 |. 68 50354000 push VC++伪装.00403550
00401D7A |. 68 F61E4000 push <jmp.&MSVCRT._except_handler>; SE 句柄安装
00401D7F |. 64:A1 00000000 mov eax,dword ptr fs:[0]
00401D85 |. 50 push eax
00401D86 |. 64:8925 00000000 mov dword ptr fs:[0],esp
00401D8D |. 83EC 68 sub esp,68
00401D90 |. 53 push ebx
16进制表示的内容就是我们需要的特征码。下面我带大家分析一下主要代码,如下:
CFile myFile;
_IMAGE_DOS_HEADER myDosHeader;
_IMAGE_NT_HEADERS myNtHeader;
_IMAGE_SECTION_HEADER mySectionHeader;
int NumberOfSections,myBufSize;
DWORD VOffset=0,VSize=0,ROffset=0,RSize=0,myVOffset=0,myROffset=0;
DWORD OldEP,NewEP,Jmp;
BYTE VCBuf[33]={//VC++的特征码
0x55,0x8b,0xec,0x6a,0xff,0x68,0x50,0x35,0x40,0x00,
0x68,0xF6,0xE4,0x00,0x00,0x64,0xa1,0x00,0x00,0x00,
0x00,0x50,0x64,0x89,0x25,0x00,0x00,0x00,0x00,0x83,
0xec,0x68,0x53};
BYTE myBuf[100]={0};
CString m_bakname=mumapath+".bak";
::CopyFile((LPCTSTR)mumapath,(LPCTSTR)m_bakname,FALSE); //备份文件
if (!myFile.Open((LPCTSTR)mumapath,CFile::modeReadWrite|CFile::typeBinary,NULL))
return;
//判断文件的有效性
myFile.Read(&myDosHeader,sizeof(_IMAGE_DOS_HEADER));
if (myDosHeader.e_magic!=IMAGE_DOS_SIGNATURE)
{
AfxMessageBox("不是有效的MZ文件!");
return;
}
myFile.Seek(myDosHeader.e_lfanew,CFile::begin);
myFile.Read(&myNtHeader,sizeof(_IMAGE_NT_HEADERS));
if (myNtHeader.Signature!=IMAGE_NT_SIGNATURE)
{
AfxMessageBox("不是有效的PE文件!");
return;
}
NumberOfSections=myNtHeader.FileHeader.NumberOfSections;
myNtHeader.FileHeader.NumberOfSections++;//由于自定义了一个段
myFile.Seek(myDosHeader.e_lfanew,CFile::begin);
myFile.Write(&myNtHeader,sizeof(_IMAGE_NT_HEADERS));
for (int i=0;i<NumberOfSections;i++)
{
myFile.Read(&mySectionHeader,sizeof(_IMAGE_SECTION_HEADER));
if (mySectionHeader.VirtualAddress>VOffset)
{
VOffset=mySectionHeader.VirtualAddress;
VSize=mySectionHeader.Misc.VirtualSize;
}
if (mySectionHeader.PointerToRawData>ROffset)
{
ROffset=mySectionHeader.PointerToRawData;
RSize=mySectionHeader.SizeOfRawData;
}
}//得到最大的Offset
while (myVOffset<VOffset+VSize)
{
myVOffset+=0x1000;
}
while (myROffset<ROffset+RSize)
{
myROffset+=0x200;
}
//为的定义的段随便起个段名
for (i=0;i<8;i++)
mySectionHeader.Name[i]=0;
mySectionHeader.Name[0]=‘C‘;
mySectionHeader.Name[1]=‘h‘;
mySectionHeader.Name[2]=‘u‘;
mySectionHeader.Name[3]=‘M‘;
mySectionHeader.Name[3]=‘i‘;
mySectionHeader.Name[3]=‘n‘;
//增加一个新段
mySectionHeader.Misc.VirtualSize=0x1000;
mySectionHeader.VirtualAddress=myVOffset;
mySectionHeader.SizeOfRawData=0x200;
mySectionHeader.PointerToRawData=myROffset;
mySectionHeader.Characteristics=0xE0000020;
myFile.Write(&mySectionHeader,sizeof(_IMAGE_SECTION_HEADER));
//修改入口点到新入口点
OldEP=myNtHeader.OptionalHeader.AddressOfEntryPoint;//原入口点
NewEP=myVOffset;
myNtHeader.OptionalHeader.AddressOfEntryPoint=NewEP;
myNtHeader.OptionalHeader.MajorLinkerVersion=6;
myNtHeader.OptionalHeader.MinorLinkerVersion=0;
myNtHeader.OptionalHeader.SizeOfImage=myVOffset+0x1000;
myFile.Seek(myDosHeader.e_lfanew,CFile::begin);
myFile.Write(&myNtHeader,sizeof(_IMAGE_NT_HEADERS));
//写入特征码
myBufSize=sizeof(VCBuf);
memcpy(myBuf,VCBuf,myBufSize);
myFile.SetLength(myROffset+0x200);
myFile.Seek(-0x200,CFile::end);
myFile.Write(&myBuf,myBufSize);
//跳回原入口点
Jmp=OldEP-(NewEP+myBufSize)-5;
BYTE JmpBuf=0xE9;
myFile.Write(&JmpBuf,1);
myFile.Write(&Jmp,sizeof(Jmp));
AfxMessageBox("伪装成功!",MB_OK|MB_ICONINFORMATION,0);
原理和代码就是这样。大家现在知道为什么要熟悉PE文件了?要正确的寻址当然要熟悉PE的结构了。如果看的不是很明白就去了解和熟悉PE文件格式吧。
我们拿delphi和VB写的程序做测试,修改后,用侦壳工具Peid看看,显示“Microsoft Visual C++”,成功!而且该伪装对一些压缩壳也有作用哦,UPX加过壳的作测试,也显示。根据上面的原理,你不是还可以作出伪装delphi等的伪装工具呢?赶快动手试试吧。
楚茗
前几天,在一个黑客站点看到VC++伪装器这么一个小工具,突然感到很眼熟。对了,以前在看雪学院(www.pediy.com)看到过欺骗查壳工具的文章,赶紧去复习复习。当然了,现在我已经复习好了,这里我就告诉大家原理和带大家一起用VC打造一个简单的VC++伪装器。
首先我们简单的认识一下PE文件。因为只有对PE文件很熟悉的情况下读本文才会更加游刃有余。PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。大概的PE文件格式如下:
DOS MZ header―所有PE文件(甚至32位的DLLs)必须以一个简单的DOS MZ header开始
DOS stub-DOS stub实际上是个有效的EXE,大多数情况下它是由汇编器/编译器自动生成
PE header-PE header是PE相关结构 IMAGE_NT_HEADERS的简称
Section table-IMAGE_SECTION_HEADER
Section 1-PE文件的真正内容划分成块,称之为sections,同sections中的代码/数据属性相同
Section 2
Section ...
Section n
上面的表格就是PE文件的物理结构。还有VC程序的基地址是 0x400000,也就是说exe程序运行时会被加载内存的该地址处。PE的各种结构中,涉及到很多地址,偏移。有些是指在文件中的偏移,有的是指在内存中的偏移。一定要搞清楚了才能真正的理解本文,当然套用其中的一些代码也是可以的。关于PE文件的更多知识,大家可以到看雪学院的论坛上查找。我在说下去就太多了。
其实大家在作木马免杀时使用到的修改程序入口点和添加花指令技术就是手工伪造。呵呵,我们这里就是用程序来做罢了。基本思路相同,就是先增加一个节写入VC++入口处的特征代码,然后JMP到原程序的入口点,最后修改程序的入口点到我们增加的那个节处。侦壳工具检测到VC的特征代码就把壳识别为VC++了。木马免杀中用到的G!X Protector v1.2和木马彩衣等可能也是这个原理。
编程语言:VC++
侦壳工具:Peid
特征码提取工具:OllyDbg
首先我们用OD打开一个MFC程序,来到程序的入口点,代码如下:
地址 16进制 反汇编
00401D70 | 55 push ebp
00401D71 |. 8BEC mov ebp,esp
00401D73 |. 6A FF push -1
00401D75 |. 68 50354000 push VC++伪装.00403550
00401D7A |. 68 F61E4000 push <jmp.&MSVCRT._except_handler>; SE 句柄安装
00401D7F |. 64:A1 00000000 mov eax,dword ptr fs:[0]
00401D85 |. 50 push eax
00401D86 |. 64:8925 00000000 mov dword ptr fs:[0],esp
00401D8D |. 83EC 68 sub esp,68
00401D90 |. 53 push ebx
16进制表示的内容就是我们需要的特征码。下面我带大家分析一下主要代码,如下:
CFile myFile;
_IMAGE_DOS_HEADER myDosHeader;
_IMAGE_NT_HEADERS myNtHeader;
_IMAGE_SECTION_HEADER mySectionHeader;
int NumberOfSections,myBufSize;
DWORD VOffset=0,VSize=0,ROffset=0,RSize=0,myVOffset=0,myROffset=0;
DWORD OldEP,NewEP,Jmp;
BYTE VCBuf[33]={//VC++的特征码
0x55,0x8b,0xec,0x6a,0xff,0x68,0x50,0x35,0x40,0x00,
0x68,0xF6,0xE4,0x00,0x00,0x64,0xa1,0x00,0x00,0x00,
0x00,0x50,0x64,0x89,0x25,0x00,0x00,0x00,0x00,0x83,
0xec,0x68,0x53};
BYTE myBuf[100]={0};
CString m_bakname=mumapath+".bak";
::CopyFile((LPCTSTR)mumapath,(LPCTSTR)m_bakname,FALSE); //备份文件
if (!myFile.Open((LPCTSTR)mumapath,CFile::modeReadWrite|CFile::typeBinary,NULL))
return;
//判断文件的有效性
myFile.Read(&myDosHeader,sizeof(_IMAGE_DOS_HEADER));
if (myDosHeader.e_magic!=IMAGE_DOS_SIGNATURE)
{
AfxMessageBox("不是有效的MZ文件!");
return;
}
myFile.Seek(myDosHeader.e_lfanew,CFile::begin);
myFile.Read(&myNtHeader,sizeof(_IMAGE_NT_HEADERS));
if (myNtHeader.Signature!=IMAGE_NT_SIGNATURE)
{
AfxMessageBox("不是有效的PE文件!");
return;
}
NumberOfSections=myNtHeader.FileHeader.NumberOfSections;
myNtHeader.FileHeader.NumberOfSections++;//由于自定义了一个段
myFile.Seek(myDosHeader.e_lfanew,CFile::begin);
myFile.Write(&myNtHeader,sizeof(_IMAGE_NT_HEADERS));
for (int i=0;i<NumberOfSections;i++)
{
myFile.Read(&mySectionHeader,sizeof(_IMAGE_SECTION_HEADER));
if (mySectionHeader.VirtualAddress>VOffset)
{
VOffset=mySectionHeader.VirtualAddress;
VSize=mySectionHeader.Misc.VirtualSize;
}
if (mySectionHeader.PointerToRawData>ROffset)
{
ROffset=mySectionHeader.PointerToRawData;
RSize=mySectionHeader.SizeOfRawData;
}
}//得到最大的Offset
while (myVOffset<VOffset+VSize)
{
myVOffset+=0x1000;
}
while (myROffset<ROffset+RSize)
{
myROffset+=0x200;
}
//为的定义的段随便起个段名
for (i=0;i<8;i++)
mySectionHeader.Name[i]=0;
mySectionHeader.Name[0]=‘C‘;
mySectionHeader.Name[1]=‘h‘;
mySectionHeader.Name[2]=‘u‘;
mySectionHeader.Name[3]=‘M‘;
mySectionHeader.Name[3]=‘i‘;
mySectionHeader.Name[3]=‘n‘;
//增加一个新段
mySectionHeader.Misc.VirtualSize=0x1000;
mySectionHeader.VirtualAddress=myVOffset;
mySectionHeader.SizeOfRawData=0x200;
mySectionHeader.PointerToRawData=myROffset;
mySectionHeader.Characteristics=0xE0000020;
myFile.Write(&mySectionHeader,sizeof(_IMAGE_SECTION_HEADER));
//修改入口点到新入口点
OldEP=myNtHeader.OptionalHeader.AddressOfEntryPoint;//原入口点
NewEP=myVOffset;
myNtHeader.OptionalHeader.AddressOfEntryPoint=NewEP;
myNtHeader.OptionalHeader.MajorLinkerVersion=6;
myNtHeader.OptionalHeader.MinorLinkerVersion=0;
myNtHeader.OptionalHeader.SizeOfImage=myVOffset+0x1000;
myFile.Seek(myDosHeader.e_lfanew,CFile::begin);
myFile.Write(&myNtHeader,sizeof(_IMAGE_NT_HEADERS));
//写入特征码
myBufSize=sizeof(VCBuf);
memcpy(myBuf,VCBuf,myBufSize);
myFile.SetLength(myROffset+0x200);
myFile.Seek(-0x200,CFile::end);
myFile.Write(&myBuf,myBufSize);
//跳回原入口点
Jmp=OldEP-(NewEP+myBufSize)-5;
BYTE JmpBuf=0xE9;
myFile.Write(&JmpBuf,1);
myFile.Write(&Jmp,sizeof(Jmp));
AfxMessageBox("伪装成功!",MB_OK|MB_ICONINFORMATION,0);
原理和代码就是这样。大家现在知道为什么要熟悉PE文件了?要正确的寻址当然要熟悉PE的结构了。如果看的不是很明白就去了解和熟悉PE文件格式吧。
我们拿delphi和VB写的程序做测试,修改后,用侦壳工具Peid看看,显示“Microsoft Visual C++”,成功!而且该伪装对一些压缩壳也有作用哦,UPX加过壳的作测试,也显示。根据上面的原理,你不是还可以作出伪装delphi等的伪装工具呢?赶快动手试试吧。