前面的创建远程线程进行注入等都是直接向"运行中的进程"强制注入DLL。而通过直接修改目标程序的可执行文件,使其在运行时就加载指定的dll文件。 这种方法只需要更改一次,之后每当程序运行时就会自动加载指定的dll文件。所以这其实是一种破解方法。
练习文件下载:
一个查看和编辑文本的程序
链接:https://pan.baidu.com/s/1sGpIhZJ0fbrJIf7aMzH57Q 提取码:0syj
其实就是一个LPVOID型的指针
概况:初始化一个应用程序,以使用 WinINet 函数。
为了使网络连接生效,必须用函数InternetOpen函数创建一个HINTERNET根句柄。InternetOpen函数接收的参数为:用户代理信息(访问Internet函数的应用),访问网络的类型,代理主机和地址以及代理的行为动作。
InternetOpen 的第一个参数lpszCallerName指定正在使用网络函数的应用程序。当HTTP协议使用时,这个名字将变成用户代理。
函数声明:
参数
lpszAgent
指向一个空 结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议 。
dwAccessType
指定访问类型,参数可以是下列值之一:
Untitled
lpszProxyName
指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType 未设置为INTERNET_OPEN_TYPE_PROXY ,则此参数应该设置为NULL。
lpszProxyBypass
指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType 未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
dwFlags
参数可以是下列值的组合:
dwFlags
返回值
成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinINet函数。
失败:返回NULL。
通过一个完整的FTP,Gopher或HTTP网址打开一个资源。
函数声明:
参数:
hInternet
当前的 Internet 会话句柄。句柄必须由前期的 InternetOpen 调用返回。
lpszUrl
一个空字符结束的字符串变量的指针,指定读取的网址。只有以ftp:, gopher:, http:, 或者 https: 开头的网址被支持。
lpszHeaders
一个空字符结束的字符串变量的指针,指定发送到HTTP服务器的头信息。欲了解更多信息,请参阅HttpSendRequest 函数里lpszHeaders参数的说明。
dwHeadersLength
dwFlags
此参数可为下列值之一。
数值 说明
INTERNET_FLAG_EXISTING_CONNECT
如果使用相同的必须属性创建会话 ,会尝试利用现有的InternetConnect对象。这只对FTP操作非常有用,因为FTP是唯一在同一会话中执行多种操作的协议。WinINet API 为每个由InternetOpen 产生的HINTERNET句柄缓冲一个单独链接句柄。InternetOpenUrl使用此标志的HTTP和FTP连接。
INTERNET_FLAG_HYPERLINK
当决定何时从网络重载 时,如果服务器没有返回 Expires time 和 LastModified,那么强制重载。
INTERNET_FLAG_IGNORE_CERT_CN_INVALID
停用检查从服务器对必须的主机 名称返回的SSL/PCT-based证书。 WinINet函数使用简单的比较匹配主机名称和通配符 的规则检查证书。
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
停用检查的SSL/PCT-based的证书的适当的有效日期。
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
禁用检测这中特殊的重定向 。当使用此标志, WinINet 透明允许从HTTPS到HTTP URL的重定向。
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
禁用检测这中特殊的重定向。当使用此标志, WinINet 透明的允许的HTTP到HTTPS URL的重定向。
INTERNET_FLAG_KEEP_CONNECTION
如果可能的话,为连接使用保活语义。这个标志要求微软网络( MSN ),NTLM 和其他类型的身份验证。
INTERNET_FLAG_NEED_FILE
如果要创建的文件不能被缓存 ,创建临时文件 。
INTERNET_FLAG_NO_AUTH
不试图自动验证。
INTERNET_FLAG_NO_AUTO_REDIRECT
不自动处理HttpSendRequest 中的重定向 。
INTERNET_FLAG_NO_CACHE_WRITE
不添加返回实体到缓存。
INTERNET_FLAG_NO_COOKIES
不会自动添加的Cookie头到请求,并且不自动添加返回的cookie 到cookie数据库。
INTERNET_FLAG_NO_UI
禁用Cookie的对话框。
INTERNET_FLAG_PASSIVE
使用被动FTP语义。InternetOpenUrl为FTP的文件和目录使用此标志。
INTERNET_FLAG_PRAGMA_NOCACHE
即使代理中存在缓存副本,也强制要求由源服务器返回。
INTERNET_FLAG_RAW_DATA
检索的Gopher 目录信息时,传回的数据作为GOPHER_FIND_DATA结构,如果检索的FTP目录信息时,作为一个WIN32_FIND_DATA 结构。如果此标志没有指定,或者请求通过CERN代理创建, InternetOpenUrl返回的HTML版本的目录。
INTERNET_FLAG_RELOAD
从原服务器强制下载所要求的文件,对象,或目录列表,而不是从缓存下载。
INTERNET_FLAG_RESYNCHRONIZE
重新加载的HTTP资源,如果资源在最后一次下载后已被修改。所有FTP和Gopher资源将被重载 。
INTERNET_FLAG_SECURE
使用安全传输语义。这次传输使用安全套字节 层/专用通信技术(的SSL / PCT ),这只有在HTTP请求 时有意义。
dwContext
一个指向一个应用程序定义的值,将随着返回的句柄,一起传递给回调函数465。
返回值
如果已成功建立到FTP,Gopher ,或HTTP URL的连接,返回一个有效的句柄,如果连接失败返回NULL。要检索特定的错误讯息,请GetLastError 。要确定为什么对服务器的访问被拒绝,请调用InternetGetLastResponseInfo
函数介绍: 从一个由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数打开的句柄中读取数据
参数:
Parameters
由InternetOpenUrl ,FtpOpenFile , 或HttpOpenRequest 函数返回的句柄
lpBuffer [out]
缓冲器指针
dwNumberOfBytesToRead [in]
欲读数据的字节量
lpdwNumberOfBytesRead [out]
返回值
成功:返回TRUE,失败,返回FALSE
WideCharToMultiByte是一个函数,该函数可以映射一个unicode字符串到一个多字节字符串,执行转换的代码页、接收转换字符串、允许额外的控制等操作。
函数原型:
CodePage :指定执行转换的代码页,这个参数可以为系统已安装或有效的任何代码页所给定的值。
(可理解为要转化成的字符串的编码方式)
lpWideCharStr :指向将被转换的unicode字符串。
cchWideChar :指定由参数lpWideCharStr指向的缓冲区的字符个数。如果这个值为-1,字符串将被设定为以NULL为结束符的字符串,并且自动计算长度。
lpMultiByteStr: 指向接收被转换字符串的缓冲区。
cchMultiByte :指定由参数lpMultiByteStr指向的缓冲区最大值(用字节 来计量)。若此值为零,函数返回lpMultiByteStr指向的目标缓冲区所必需的字节数,在这种情况下,lpMultiByteStr参数通常为NULL。
lpDefaultChar 和pfUsedDefaultChar :只有当WideCharToMultiByte函数遇到一个宽字节字符 ,而该字符在uCodePage参数标识的代码页 中并没有它的表示法时,WideCharToMultiByte函数才使用这两个参数。如果宽字节 字符不能被转换,该函数便使用lpDefaultChar参数指向的字符。如果该参数是NULL(这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符 。pfUsedDefaultChar参数指向一个布尔变量 ,如果Unicode字符串 中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为TRUE。如果所有字符均被成功地转换,那么该函数就将该变量置为FALSE。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该变量。
注意:指针lpMultiByteStr和lpWideCharStr必须不一样。如果一样,函数将失败,GetLastError将返回ERROR_INVALID_PARAMETER的值。
该函数从堆中分配一定数目的字节数。 Win32内存管理器并不提供相互分开的局部和全局堆。提供这个函数只是为了与16位的Windows相兼容。简称全局堆分配.
uFlags
指定如何分配内存,若指定为0,则是默认的GMEM_FIXED.这个值可以是下面其中一个或几个位标识(那些指明不兼容的组合除外)
标识的含意
dwBytes
指定要申请的字节数.若该参数为 0 且参数 uFlags 指定为 GMEM_MOVEABLE 则该函数返回一个内存对象的句柄 ,该内存对象被标识为discarded(可抛弃的)。
返回值:
若函数调用成功,则返回一个新分配的内存对象的句柄 。
若函数调用失败,则返回NULL。可调用GetLastError 以获得更多错误信息。
锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。
说明:
锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。除非用 GlobalUnlock 函数将内存块解锁,否则地址会一直保持有效。 Windows为每个内存对象都维持着一个锁定计数。对这个函数的每次调用都应有一个对应的 GlobalUnlock 调用返回值Long,如成功,返回内存块的地址;如出错,或者这是一个已被丢弃的“可丢弃”内存块,则返回零。会设置GetLastError参数表
获取当前进程一个唯一的标识符。
说明:
获取当前进程的标示符(PID)
返回值:
返回一个标示符(PID)
该函数枚举所有屏幕上的顶层窗口,并将窗口句柄传送给应用程序定义的回调函数。回调函数返回FALSE将停止枚举,否则EnumWindows函数继续到所有顶层窗口枚举完为止。
参数:
lpEnumFunc:指向一个应用程序定义的回调函数 指针 ,请参看EnumWindowsProc 。
lPararm:指定一个传递给回调函数的应用程序定义值。
回调函数原型
BOOL CALLBACK EnumWindowsProc(HWND hwnd ,LPARAM lParam );
参数:
hwnd:顶层窗口的句柄
lparam:应用程序定义的一个值(即EnumWindows中lParam)
用于将一条消息放入到消息队列中。消息队列里的消息通过调用GetMessage和PeekMessage取得。
该函数将一个消息放入(寄送)到与指定窗口创建的线程相联系消息队列里,不等待线程处理消息就返回,是异步消息模式。消息队列里的消息通过调用GetMessage和PeekMessage取得。
函数原型:
BOOL WINAPI PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);
参数:
hWnd :其窗口程序接收消息的窗口的句柄。 可取有特定含义的两个值:
HWND_BROADCAST :消息被寄送到系统的所有顶层窗口,包括无效或不可见的非自身拥有的窗口、 被覆盖的窗口和弹出式窗口。消息不被寄送到子窗口
NULL :此函数的操作和调用参数dwThread设置为当前线程的标识符 PostThreadMessage函数一样
Msg :指定被寄送的消息。
wParam :指定附加的消息特定的信息。
LParam :指定附加的消息特定的信息。
返回值 :
如果函数调用成功,返回非零,否则函数调用返回值为零
其实我们修改的就是(IDT) IMPORT DIRECTORY TABLE(image_import_descriptor,即IID结构体)组成的数组,只要将我们的dll添加到这个数组的结尾就可以让程序运行时自动加载我们的dll了。
我们从Image_Optional_Header的DataDirectory的第二项IMPORT TABLE得到的就是这个结构体数组的RVA和Size。 这里再详细的讲解一下RVA和文件偏移的转化,其实很简单,记住每个节区有个差值是不变的,什么差值呢, 就是每个节区RVA - 对应的文件偏移是不变的,记住这一点,我们找到节区的起始RVA和起始文件偏移,相减就可以得到这个差值,
通过查看image_section_header,发现RVA84CC处于rdata区域
然后我们就可以得出差值:0x6000-0x5200 = 0xE00
然后再用我们知道的RVA,即0x84CC - 差值0xE00 = 0x76CC
得到的0x76CC就是文件偏移。
下面再遇见RVA和文件偏移的转化不再过多赘述
然后我们使用winhex打开可以发现在我们的IDT之后紧贴着其它数据,我们没有足够的空间来添加一个0x14字节的结构体进去。(?)
在这种情况之下,我们只有移动我们的IDT到文件的其它地方
主要有三种方法:①查找文件的空白区域。②增加文件最后一个节区的大小。③在文件末尾添加新的节区。
常用的就是第一种,因为由于内存对齐的原因,一般都会导致节区的最后会有一段Null-padding区域。
查看IMAGE_NT_Headers对应的各节区头的信息 查看rdata的,发现这个节区在文件中的大小为2E00,但是Virtual Size只有2C56,相减得到1AA,足够我们放下(0x14 * 6 = 0x78)字节的数据。(只有分析节区头的这个差值我们才能存放我们的代码,如果是为了对齐而在内存中添加的填充区域,我们是不能存放代码的)
所以我们的新的IDT的RVA就可以从(0x6000+0x2C56)之后选取一段0x78(0x14 * 6)字节的大小来存放我们的IDT
我们从0x8C80开始存放我们的IDT(转化为文件偏移为0x7E80)(新IDT: 0x7E80到(0x7E80+0x78))
直接将原先的IDT(76CC~772F)复制到新区域
原先的值:
根据文件偏移,我们找到那个位置,更改为我们的新的IDT的RVA和Size
这里可以发现为0,就直接可以不用管了,这是一种提高dll加载速度的技术,是个可选项,如果不存在,无影响,如果存在,又发生了错误,那么就会导致程序运行时发生错误。
首先我们设置OriginalFirstThunk,这是一个INT的RVA,我们使用8D00,处于1AA的那个区域之内都可以,TimeDateStamp和ForwarderChain都直接设置为0即可(8字节),Name是用来存放导入模块的名称字符串的,8D10处存放,FirstThunk是IAT的RVA,我们设置为8D20.
如下:
然后我们去设置对应的INT和IAT和Name的值
最后如下:
其实就是INT和IAT都是IMAGE_IMPORT_BY_NAME(hint+函数名)的RVA
在动态链接器刚完成映射没有开始重定位和符号解析的时候,IAT中的元素就是这个,但是如果Windows的动态链接器在完成该模块的链接时,元素值会被动态链接器改为该符号的真正地址。(类似于elf的got)。
原因:加载IAT的时候,PE装载器会修改IAT,写入函数的实际地址,所以对应的节区必须有写的属性。
直接修改image_section_header中的characteristic的值
加上IMAGE_SCN_MEN_WRITE(80000000)的值
即修改那个值为C0000040
(其实我感觉这种方法不好,这样就直接把整个rdata区域都赋上了write的属性,容易被利用)。
补充:另外一种方法
向DataDirectory中的IAT添加值
先将Size增加8字节(变为0x154+0x8 = 0x15C)
然后在对应RVA处就填入对应的IAT的RVA(00008D20)
即可
最后都保存出去运行即可
最后将程序和我们的dll放在同一个文件夹下,运行程序。
Process Explorer查看程序导入的dll
成功导入dll
HINTERNET InternetOpen(
_In_ LPCTSTR lpszAgent,
_In_ DWORD dwAccessType,
_In_ LPCTSTR lpszProxyName,
_In_ LPCTSTR lpszProxyBypass,
_In_ DWORD dwFlags
);
HINTERNET InternetOpen(
_In_ LPCTSTR lpszAgent,
_In_ DWORD dwAccessType,
_In_ LPCTSTR lpszProxyName,
_In_ LPCTSTR lpszProxyBypass,
_In_ DWORD dwFlags
);
HINTERNET InternetOpenUrl(
HINTERNET hInternet,
LPCTSTR lpszUrl,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
DWORD dwFlags,
DWORD_PTR dwContext)
HINTERNET InternetOpenUrl(
HINTERNET hInternet,
LPCTSTR lpszUrl,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
DWORD dwFlags,
DWORD_PTR dwContext)
int
WideCharToMultiByte(
UINT CodePage,
/
/
指定执行转换的[代码页]
DWORD dwFlags,
/
/
允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
LPCWSTR lpWideCharStr,
/
/
指定要转换为宽字节字符串的[缓冲区]
int
cchWideChar,
/
/
指定由参数lpWideCharStr指向的缓冲区的[字符]个数
LPSTR lpMultiByteStr,
/
/
指向接收被转换字符串的缓冲区
int
cchMultiByte,
/
/
指定由参数lpMultiByteStr指向的[缓冲区]最大值
LPCSTR lpDefaultChar,
/
/
遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符
LPBOOL pfUsedDefaultChar
/
/
至少有一个字符不能转换为其多字节形式,函数就会把这个变量设为TRUE
);
int
WideCharToMultiByte(
UINT CodePage,
/
/
指定执行转换的[代码页]
DWORD dwFlags,
/
/
允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
LPCWSTR lpWideCharStr,
/
/
指定要转换为宽字节字符串的[缓冲区]
int
cchWideChar,
/
/
指定由参数lpWideCharStr指向的缓冲区的[字符]个数
LPSTR lpMultiByteStr,
/
/
指向接收被转换字符串的缓冲区
int
cchMultiByte,
/
/
指定由参数lpMultiByteStr指向的[缓冲区]最大值
LPCSTR lpDefaultChar,
/
/
遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-4-18 19:14
被SYJ-Re编辑
,原因: