首页 > 其他分享 >修改PE导入表注入DLL——实例图文教程——让你看的明明白白

修改PE导入表注入DLL——实例图文教程——让你看的明明白白

时间:2024-11-02 08:50:49浏览次数:4  
标签:RVA DLL 地址 导入 PE DW 图文

一、前言

其实通过修改PE导入表注入DLL的教程很多,本文也只是其中的沧海一粟而已,但既然写出来,自我感觉应该还是有一点自我的东西的,至少自认为做到了思路清晰,每步都有据可依,让看客应该能做到“看的明明白白”!

本贴以《英雄无敌》1游戏程序为例子,向其添加一个DLL,调用其中的导出函数可以在施展回城术魔法时,让玩家可以选择到达具体的城堡。本文的最后效果与下文是一样的,只是处理手法不同而已!
https://www.cnblogs.com/dark-f/p/18450877

本贴内容如下:

。PE基础知识
。查看导入表是否有足够的空间
。移动导入表
。修改导入表的值
。删除绑定导入表(BOUND IMPORT TABLE)
。创建新导入表
。设置INT,Name,IAT的值
。修改IAT相关节区的属性
。效果检测
。调用导入函数
。游戏测试
。资源下载

使用的工具:

。PEview——PE文件查看软件
。CFF Explorer——PE文件查看修改软件
。010Editor——hex文件编辑软件
。OD或者X32DBG——逆向调式软件

二、PE基础知识

既然你都想动一动导入表了,那自然得了解一点PE的基础知识。
首先得明白,使用hex编辑软件打开程序与OD调式软件打开程序,所看到的地址是不同的。
这是hex编辑软件(010Editor)打开的样子

这是OD类调式软件(X32DBG)打开的样子

基础知识一:FOA和RVA

一眼就可以看出,两者每一行前面的数字是不同的。hex编辑软件是从00开始的,而OD类调式软件一般都是从004XXXXX开始的。这就是pe基础知识中的第一个要点:区别FOA和RVA。
FOA:程序在(磁盘)文件上的地址,hex编辑软件显示的就是FOA;
RVA:程序导入内存后的地址,OD类调式软件显示的就是RVA(实际地址还要加上Image Base)。
因此pe文件具有两种状态,它们的地址分别用FOA和RVA表示,并且一般情况下两者是不同的,但都代表着程序中的同一个地址。关键看你要在哪里修改程序!若要用hex编辑软件修改程序,则你必须知道FOA是多少;同样,若用OD类调式软件修改程序,则你必须要知道RVA的大小。下图显示了pe这两种状态出现的原因:

基础知识二:PE头格式

要搞PE修改,则大致要知道DOS头、PE头、标准pe头和扩展pe头。下图显示了《英雄无敌》1的这几个头的位置。

DOS头:前面4行(每行16字节),就看最后一个DW(DWORD),这里的数值就是pe头的位置,上图中是80,说明pe头在80位置。
PE头:它由三部分组成:PE标识(1个DW),标准pe头(5个DW,也就是一行再加1DW)和扩展pe头(大小为E0)。例如上图中,扩展pe头是从98开始的,加上E0,等于178,说明扩展pe头最后一个DW位置是174。前面说过,这些数值都是FOA,但由于包含区块表在内,pe文件直至区块表结尾,FOA与RVA是一样的。
其中,导入表信息位于从扩展pe头结尾倒数第15个双DW(即每2DW一数,倒着数到15,里面的地址是RVA),上图显示《英雄无敌》1的导入表RVA=D6000,大小等于C8。这个结果,也可以通过PEview来观察。
用PEview打开程序,左栏选择点开Selection .idata,选导入表,右边就能看到这个程序导入表从哪里开始,包含多少DLL。工具栏中还可以调整是FOA显示还是RVA显示,如图:

上图中看到的是导入表开始位置正是RVA=D6000,与前面010Editor中看到的一样。那么要知道它的开始位置FOA是多少,只要选择显示FOA即可,如图:

说明导入表起始位置的FOA=A2600,而RVA=D6000,即用010Editor打开它在A2600位置就是导入表,而用OD打开它,内存里在4D6000位置。
如果查看.idata的区块结构信息,就会从中看到:FOA(A2600),RVA(D6000),文件对齐后大小(1600),加载到内存中的映像大小(1480),如图

PE-DLL-exan-7

基础知识三:导入表结构

既然用到导入表,自然要对导入表的数据格式有个大致印象。
导入表是由IMAGE_IMPORT_DESCRIPTOR结构的数组组成,简称IID,没有特定的成员指出IID项数,但是会由全为0的IID结构作为结束。
IID结构的字段成员如下,其中OriginalFirstThunk、Name以及FirstThunk成员是我们添加DLL文件的关键。

IMAGE_IMPORT_DESCRIPTOR{
  union{
	  characteristics DWORD
	  OriginalFirstThunk DWORD //指向IMAGE_THUNK_DATA结构的数组
  }
  TimeDateStamp DWORD 	//时间标志,不用管,填0
  ForwarderChain DWORD 	//一般为0
  Name DWORD 				//指向DLL名称的指针
  FirstThunk DWORD		//指向IMAGE_THUNK_DATA结构的数组
};IMAGE_IMPORT_DESCRIPTOR

在PE文件尚未执行过时,OriginalFirstThunk与FirstThunk字段指向相同的结构,区别在于OriginalFirstThunk不可以重写,而FirstThunk可以被重写,当PE文件执行后FirstThunk指向的结构会用于存放导入函数的真实地址。因此我们修改时将OriginalFirstThunk与FirstThunk字段指向同个地址即指向IMAGE_THUNK_DATA结构的数组。而Name字段存放的是指向DLL文件名称的指针。

ypedef struct _IMAGE_THUNK_DATA32 {
  union {
    PBYTE ForwarderString;
    PDWORD Function;
    DWORD Ordinal;
    PIMAGE_IMPORT_BY_NAME AddressOfData; 	//指向IMAGE_IMPORT_BY_NAME
  } u1;
} IMAGE_THUNK_DATA32;

IMAGE_THUNK_DATA结构在不同情况下的成员不同,但是重点关注AddresOfData字段即可,该字段指向IMAGE_IMPORT_BY_NAME结构,该结构记录是导入函数的名称。

IMAGE_IMPORT_BY_NAME STRUCT{
  Hint WORD //忽略设置为0
  Name BYTE //导入函数名称
};IMAGE_IMPORT_BY_NAME

IMAGE_IMPORT_BY_NAME结构前面两位为0时,表示函数以字符串类型的函数名方式输入。因此构造时也就是将Hint设为0,后面跟着的字节为导入函数的名称。
上面这些,可能有朋友一看就头大了,不怕,用《英雄无敌》1的具体例子看就会清楚得多。上面已经知道导入表的FOA在A2600了,在010Editor里,向下拖动鼠标来到A2600处,如图

前面说了,这个导入表的大小是C8,看从A2600开始到A26C4结束,就是全部导入表的内容。那么这里有多少个需要导入的DLL呢?每5个DW一数,看看有多少个不全是0的5个DW,那就表示有多少个DLL需要导入,是不是很清楚?上图中看出是9个,这个结果可以从PEview中看到(不给图了),正好有9个DLL需要导入。还可以看到导入表中每5个DW是这样的,第一个DW不为0,第二和第三为0,第四和第五又不为0。这里第一个不为0的DW就是OriginalFirstThunk(什么东西,别管他);第四个DW就是Name,注释是DLL的名称指针;第五个DW就是FirstThunk(又是什么,也别管他)。虽然说不管他,但是要知道它们做什么,总之一句话,就是这里的2个数都是指针,指向的地址里又是一个指向IMAGE_IMPORT_BY_NAME结构的指针。够绕的,但是别心急不明白,马上就知道到底是什么了!
先看上面IMAGE_IMPORT_BY_NAME结构的成员就知道了,它只有2个成员,前面一个WORD(2字节),一般都是不用管的,即取0就行了,而后面跟着的就是DLL中需要导出函数的名称。原来所谓的IMAGE_IMPORT_BY_NAME结构,以《英雄无敌》1添加的Towngate.dll来说,需要导出的函数名称为GetTown,那就是要在文件中建立一个这样的数据00 00 47 65 74 54 6F 77 6E 00 ,它的首地址(RVA)就是指向IMAGE_IMPORT_BY_NAME结构的指针啦。也就是说,上面第一个和第五个DW指向的地址里就填这个RVA。而第一个和第五个DW里就是另外的一个数(RVA),这两个数指向的地址里都装着前面那个RVA。还没有明白吗?不着急,后面有具体的数据,能帮助你明白的。
至于第四个DW里填写什么?最简单啦,只要填上指向DLL名称字符串的首地址(RVA)就行了。
好,有了这些知识,足够来手动注入DLL了。

三、查看导入表是否有足够的空间

实际上修改导入表注入DLL,如果不想手动修改的话,很多PE查看修改软件都是可以直接帮你插入的,例如使用CFF Explorer,就可以直接插入DLL。用CFF Explorer打开要修改的程序(《英雄无敌》1),左侧任务栏里选Import Adder(导入表添加),右边上面点Add(添加)按钮,再选择要添加的DLL文件,软件会自动找到要添加的导出函数,如图

确认是要添加的函数后,点击Rebuild Import Table(重建导入表)按钮后,就会将DLL注入到程序中。

这样做的好处是在你即使不是很明白PE文件结构的情况下,也能方便地注入DLL。只是,工具帮你把DLL注入到程序中什么位置,你可能就不清楚了,当然你可以通过PE查看软件或者hex比较软件,都可以查出究竟注入到程序中什么位置。而且如果你不搞清楚注入到程序什么位置,那么以后调用函数时的使用方式将会不同,这往往会引起重定位表的调整。所以,软件帮你解决了注入的问题,却也给你添加了调用的难度,这正与那句“老外流行的:上帝给你关上了一道门,也必定给你开了一扇窗户”有异曲同工之妙。本文将采用手动注入的方式,所以要自己动手添加导入表。
首先在PEview中可以看到导入表的最后在哪里?在A26C4。

在010Editor里,向下拖动鼠标来到A26C4处,如图

去那里看看是否有插入自己DLL所需的空间,这个空间是5个DW。如果在导入表的最后少于10个DW的00字节,则没有足够的空间插入字节DLL所需的空间,多于则有足够空间。本例中,在A26C4后面从A26C8到A26DC正好还有DWORD[5]=0,说明空间刚好够用。
虽然本例就可以直接在这个后面添加一个导入表,但这样不具备一般性,因此,这里也演示一下如果空间不够怎么处理。
如果没有足够的空间来添加其他的DLL文件,则要考虑将整个导入表迁到别的地址。

四、移动导入表

有三种方式把导入表迁到别的地址,分别是:
1 查找文件中的空白区域
2 增加文件最后一个节区的大小
3 在文件末尾添加新的节区
一般情况下,由于文件对齐的需要,区块之间都会存在大量的由0填充的空白区域,因此,上面说的三种方法中,最常用的还是利用文件中的空白区域。本文就采用第一种方法找空白区域,演示一下怎么迁移导入表的操作。
实际上,在A2600后面的A3800处,就有大片的空白区域,可以把导入表迁移到那里去。原导入表大小是C8,现在要添加一个DLL文件则要加14,即至少需要C8+14=DC大小的空间来安排导入表。
查看这个区域是否可用?该区块内存大小1480,而文件中实际大小是1600,还有180的富余空间,这个区域是可以使用的(看前面PEview那个图)。那么就选择在A3A90这个位置创建导入表。

五、修改导入表的值

原RVA=D6000计算一下移到的新地址RVA,原FOA=A2600,新的FOA=A3A90,偏差Δ=A3A90-A2600=1490。因此新的RVA=D6000+1490=D7490,而大小为C8+14=DC。

用010Editor来修改

那么从现在开始导入表就是位于D7490,大小是DC了。

六、删除绑定导入表(BOUND IMPORT TABLE)

BOUND IMPORT TABLE(绑定导入表)是一种提高DLL加载速度的技术,本例的绑定导入表信息

若想正常导入自己的dll,需要向绑定导入表添加信息,但是该导入表是个可选项,不是必须存在的,可以删除(修改其值为0),当前实例中绑定导入表的值是0,所以也就不需要修改了。

七、创建新导入表

先将原先RVA=D6000(FOA=A2600)位置的导入表复制到RVA=D7490(FOA=A3A90)位置。先选中要复制的内容,右键选择复制:

来到A3A90处,选中同样多的字节,右键选择粘贴

这样原导入表就移到新的地址了。

八、设置INT,NAME,IAT的值

在PE文件尚未执行过时,OriginalFirstThunk(INT)与FirstThunk(IAT)字段指向相同的结构,区别在于OriginalFirstThunk不可以重写,而FirstThunk可以被重写,当PE文件执行后FirstThunk指向的结构会用于存放导入函数的真实地址。
基础知识里已经分析了INT、NAME和IAT中应该怎样添加数值了,下面就具体过程演示。
前面已经说了,要在文件中建立一个这样的数据00 00 47 65 74 54 6F 77 6E 00,共需要10个字节。可以把它放在A3BF0(FOA),它的RVA=A3BF0-A2600+D6000=D75F0。这样第一个DW和第五个DW指向的地址里就可以填写D75F0了。
其次,再把NAME字段存放的是指向DLL文件名称的指针搞好。DLL文件名称是Towngate.dll,即54 6F 77 6E 67 61 74 65 2E 64 6C 6C 00,共13个字节,可以把它安排在A3BD0,即RVA=A3BD0-A2600+D6000=D75D0。也就是说,第四个DW里就填写D75D0了。
最后,只要把上面的要填写的D75F0放在哪2个地址里?就把这2个地址写到第一个DW(INT)和第五个DW(IAT)里即可。
本例中,这2个地址分别设在A3BC0和A3BE0,它们里面都填写D75F0,它们的RVA分别计算下:第一个DW(INT)的RVA=A3BC0-A2600+D6000=D75C0和第五个DW(IAT)的RVA=D75E0,这2个数据分别填写入第一个DW和第五个DW中。至此,全部数据搞好了,就可以设置自己DLL的导入表数据了。
按照上面的计算结果,在A3B44的地方填入:C0 75 0D 00,隔2个DW,A3B50的地方填入:D0 75 0D 00,A3B54的地方填入:E0 75 0D 00,结果如图所示:

九、修改IAT相关节区的属性

加载PE文件到内存时,PE装载器会修改IAT,写入函数的实际地址,所以你将IAT写入到了哪个区块,那么相应的区块头里定义的区块属性必须拥有WRITE(可写)属性。
导入表所在区块是A2600,再加上1600的大小,即到A3C00,都是导入表的范围,导入表自然是具有可写属性的,因此,本例中无需修改相关区块的属性。
但,如果导入表新移到的区块属性没有写属性时,就要将其改成具有写属性。

那如果导入表迁移到了一个没有写属性的区块怎么办呢?举例说明一下怎么修改。假设上面存放第五个DW(INT)里的数据指向的地址不是RVA=D75E0,而是一个别的数据(实际上是地址),那么先要清楚这个地址是在哪个区块里(例如:《英雄无敌》1里共用6个区块,分别是:.text、.data、.rdata、.idata、.rsrc和.reloc,假定那个地址是在.rdata区块)?则根据下图可知,该区块只有读属性,而没有写属性的

在010Editor中来到1C4处,将下图中的数值改为40 00 00 C0,那么该区块就具有读写属性了

至此,全部修改完成,可以看看程序加载后是否已经加载了这个DLL。

十、效果检测

这样修改能否成功加载DLL呢?可以用OD或者X32DBG类的调式软件打开修改后的程序,看看是否DLL已经加载?用X32DBG打开后,查看Symbols

可以看到towngate.dll和它的导出函数GetTown都已经正确加载,说明上面的修改是成功的(这里说明一下,实际修改时,导入表是不用迁移地址的,因为前面说了,原有导入表下正好有添加一个DLL的空白区域,所以下面显示的与上面迁移了导入表的结果在数据上有一点不同)。
在内存区查看第五个DW(IAT)指向的地址,按照前面的说法,在程序被导入内存后,那个地址会被导出函数的真正地址所取代,看看是否变化了?

上图显示,IAT里的数据本应与INT里的数据是一样的,但是现在它被改写了,而且被改写的地址也变成红色了,即这里就是导出函数的真正地址。
由于它是被程序载入内存时由PE系统自动修改的,代表了该函数的真正地址,因此,不管是否存在重定位问题,这个地址都是无需重定位的,所以可以无需重定位它而可以直接调用,下面的调用正是利用这个地址而无惧重定位问题!

十一、调用导入函数

调用函数的方法很简单,就是先把第五个DW(IAT)指向的那个地址XXXXX里的值赋给eax,然后call这个eax就行了,即

mov eax,dword ptr ds:[XXXXX]
call eax

由于本文是在前一篇文章基础上修改的,具体修改程序的说明就不再阐述了,结果如下图

再把原程序中的4351D9的那行跳转改成跳到修改的地方即可

具体是否有效,可以游戏检测一下效果。

十二、游戏测试

打开游戏,让英雄使用回城术

出现了DLL中函数调用结果,选择1

该英雄回到第一个城堡

说明这样修改、这样调用导出函数都是没有问题的。

十三、资源下载

修改后的程序和dll下载地址:
https://wwzd.lanzoup.com/iXoNc2dy469e

标签:RVA,DLL,地址,导入,PE,DW,图文
From: https://www.cnblogs.com/dark-f/p/18521566

相关文章

  • sqlmap的tamper详解
    sqlmap的tamper详解使用方法:sqlmap.pyXXXXX-tamper"模块名"各个tamper的作用下面针对sqlmap自带的tamper做一下简单的解释。apostrophemask.py:returnpayload.replace(''',"%EF%BC%87")ifpayloadelsepayload将单引号url编码,用于过滤了单引号的情况。1'AND'1&......
  • 监听系统属性SystemProperties值变化
    基于Android-S一、实现逻辑相关文件:frameworks/native/libs/binder/Binder.cpp/system/core/libutils/misc.cpp/android/frameworks/base/core/java/android/os/SystemProperties.java/android/frameworks/base/core/jni/android_os_SystemProperties.cpp/android/frameworks/b......
  • 「漏洞复现」F22服装管理软件系统 openfile.aspx 任意文件读取漏洞
    0x01 免责声明请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作者无关,需......
  • Go语言的动态链接库(DLL)创建和使用
    #Go语言的动态链接库(DLL)创建和使用在讨论Go语言的动态链接库(DLL)创建和使用时,核心要点包括:创建DLL的步骤、调用DLL中的函数、跨平台兼容性问题、性能优化策略。创建DLL的步骤是理解和实践Go语言动态链接库的基础,涉及编写DLL源代码、编译为DLL文件以及确保DLL在目标系统上可用。......
  • 遭遇gregn45.dll缺失?完整的应对gregn45.dll缺失修复指南全解析
    在使用电脑的过程中,有时会遇到应用程序或游戏提示“找不到gregn45.dll”或“gregn45.dll缺失”等错误消息。这通常意味着系统无法找到或加载这个关键的动态链接库(DLL)文件,导致相关应用程序或游戏无法正常运行。为了解决这个问题,以下是一份完整的应对gregn45.dll缺失的修复指南。......
  • 《保卫萝卜》客户端缺失pthreadvce2.dll 文件?详解《保卫萝卜》Luobo.exe 加载 pthread
    在享受《保卫萝卜》这款趣味横生的塔防游戏时,部分玩家可能会遇到游戏无法启动的问题,屏幕上弹出错误提示:“由于找不到pthreadvce2.dll,无法继续执行代码。”这一错误通常意味着你的电脑系统中缺失了pthreadvce2.dll这个关键的动态链接库(DLL)文件。别担心,本文将为你提供详细的修......
  • activecontentwizard.dll文件损坏怎么办?修复受损的activecontentwizard.dll有效策略汇
    当您在使用某些软件或程序时,可能会遇到“activecontentwizard.dll文件损坏”的错误提示。这个DLL文件对于程序的正常运行至关重要,一旦损坏,可能会导致程序无法启动或运行不稳定。为了帮助您解决这个问题,以下是一些有效的修复策略汇总:1. 使用系统文件检查器(SFC)Windows系统自......
  • 如何修复aurnianimation.dll文件缺失导致的古剑奇谭启动失败?详解《古剑奇谭》客户端缺
    针对《古剑奇谭》客户端因缺少 aurnianimation.dll 文件而导致的启动失败问题,以下是一系列详细的修复步骤,旨在帮助玩家恢复游戏的正常运行。一、检查回收站与备份检查回收站:打开计算机的回收站,搜索 aurnianimation.dll 文件。如果找到该文件,右键点击并选择“还原”,W......
  • 面对addownload.dll无法打开,如何快速找到解决办法?
    在使用Windows操作系统时,我们可能会遇到各种DLL文件无法打开或加载的问题,其中addownload.dll就是一个例子。这个DLL文件可能与特定的应用程序或服务相关联,当它无法被正确加载或打开时,可能会导致应用程序崩溃、系统错误或性能下降。本文将为您提供一系列快速找到并解决addownloa......
  • 面对ntlanman.dll损坏,如何高效修复?跟随ntlanman.dll修复指南轻松解决
    在Windows操作系统中,ntlanman.dll是一个重要的系统文件,它通常与网络相关的功能和服务相关联。当这个文件损坏时,可能会导致网络连接问题、系统不稳定或应用程序崩溃。为了帮助您高效修复ntlanman.dll损坏的问题,我们特别准备了这份修复指南。一、初步诊断与检查确认错误信息......