[笔记]Windows核心编程《二十二》注入DLL和拦截API

系列文章目录

[笔记]Windows核心编程《一》错误处理、字符编码
[笔记]Windows核心编程《二》内核对象
[笔记]Windows核心编程《三》进程
[笔记]Windows核心编程《四》作业
[笔记]快乐的LInux命令行《五》什么是shell
[笔记]Windows核心编程《五》线程基础
[笔记]Windows核心编程《六》线程调度、优先级和关联性
[笔记]Windows核心编程《七》用户模式下的线程同步
[笔记]Windows核心编程《八》用内核对象进行线程同步
[笔记]Windows核心编程《九》同步设备I/O和异步设备I/O
[笔记]Windows核心编程《十一》Windows线程池
[笔记]Windows核心编程《十二》纤程
[笔记]Windows核心编程《十三》windows内存体系结构
[笔记]Windows核心编程《十四》探索虚拟内存
[笔记]Windows核心编程《十五》在应用程序中使用虚拟内存
[笔记]Windows核心编程《十六》线程栈
[笔记]Windows核心编程《十七》内存映射文件
[笔记]Windows核心编程《十八》堆栈
[笔记]Windows核心编程《十九》DLL基础
[笔记]Windows核心编程《二十》DLL的高级操作技术
[笔记]Windows核心编程《二十一》线程本地存储器TLS
[笔记]Windows核心编程《二十二》注入DLL和拦截API
[笔记]Windows核心编程《二十三》结构化异常处理

相关:

文章目录

  • 系列文章目录
  • 前言
  • 一、插入DLL:一个例子
    • 目的
      • SetWindowLongPtr函数
      • GetWindowLongPtr函数
    • 主要问题
  • 二、使用注册表来插入DLL
    • 使用方法
      • 过程
    • 注册表注入DLL的不足
  • 三、使用Windows挂钩来插入DLL
    • SetWindowsHookEx函数
    • UnhookWindowsHookEx函数
    • GetWindowThreadProcessId函数
    • PostThreadMessage函数
    • GetMessage函数
    • SendMessage函数
    • GetFirstChild宏
      • GetTopWindow函数
    • FindWindow函数
    • 挂载调用
    • 3.1 桌面项目位置保存器实用程序 DIPS
      • 原理
        • DIPS功能
        • DIPSLib功能
      • 过程
  • 四、使用远程线程来插入DLL
    • 原理
    • 特点
    • 基本操作步骤
    • OpenProcess函数
    • VirtualAllocEx函数
    • WriteProcessMemory函数
    • GetProcAddress函数
    • CreateRemoteThread函数
    • 4.1 Inject Library示例应用程序
      • 原理
      • 过程
    • 4.2 Image Walk DLL
      • 原理
  • 五、使用特洛伊DLL来插入DLL
    • 原理
    • 特点
  • 六、将DLL作为调试程序来插入
    • 原理
    • 特点
  • 七、用Windows 98上的内存映射文件插入代码
    • 原理
    • 特点
  • 八、用CreateProcess插入代码
    • 原理
    • 方法
    • 特点
  • 九、拦截API的一个示例
    • 9.1 通过改写代码来拦截API
      • 原理
      • 步骤
      • 特点
    • 9.2 通过操作模块的输入节来拦截API
      • 原理
      • 特点
    • 9.3 LastMsgBoxInfo示例应用程序
  • 总结
    • 1. 第三节 使用Windows挂钩来插入DLL 发现隐藏窗口Wintellect DIPS为NULL

前言

进程之间空间独立,每个进程最大2G地址空间,只有内存映像文件和系统组件才能映射到这个区域。

有些情况下,必须打破进程的界限,访问另一个进程的地址空间,这些情况包括:

  • 当你想要为另一个进程创建的窗口建立子类时。
  • 当你需要调试帮助时(例如,当你需要确定另一个进程正在使用哪个 D L L时)。
  • 当你想要挂接其他进程时。

一、插入DLL:一个例子

目的

为由另一个进程创建的窗口建立一个子类。

你可能记得,建立子类就能够改变窗口的行为特性。若要建立子类,只需要调用 SetWindowLongPtr函数,改变窗口的内存块中的窗口过程地址,指向一个新的(你自己的)WndProc。

子类化和超类化区别(介绍Windows的窗口、消息、子类化和超类化)(转)

当调用的SetWindowLongPtr函数,建立一个窗口的子类时,你告诉系统,发送到或者显示在hWnd设定的窗口中的所有消息都应该送MySubclassProc,而不是送往窗口的正常窗口过程:

SetWindowLongPtr(hwnd,GWLP_WNDPROC,MySubclassProc);

当系统需要将消息发送到指定窗口的 WndProc时,要查看它的地址,然后直接 调用WndProc。在本例中,系统发现MySubclassProc函数的地址与窗口相关联,因此就直接调用MySubclassProc函数。

SetWindowLongPtr函数

用GetClassLong和SetClassLong函数可以读写窗口类的数据;
用GetWindowLong和SetWindowLong可以读写指定窗口实例的数据。使用这些接口,可以在运行时读取或修改窗口类或窗口实例的窗口过程地址。

该函数改变指定窗口的属性。
函数也将指定的一个值设置在窗口的额外存储空间的指定偏移位置。

LONG_PTR SetWindowLongPtr(
  [in] HWND     hWnd,
  [in] int      nIndex,
  [in] LONG_PTR dwNewLong
);

hWnd:窗口句柄,间接给出窗口所属的类。
nIndex:指定将设定的大于等于0的偏移值。

nlndex 说明
GWL_EXSTYLE 设定一个新的扩展风格。更多信息,请见CreateWindowEx。
GWL_STYLE 设定一个新的窗口风格。
GWL_WNDPROC 为窗口消息处理过程设置一个新的地址。
GWL_HINSTANCE 设置一个新的应用程序实例句柄。
GWL_ID 设置一个新的窗口标识符。
GWL_USERDATA 设置与该窗口相关的用户数据。这些用户数据可以在程序创建该窗口时被使用。用户数据的初始值为0。当hWnd参数标识了一个对话框时,也可使用下列值:DWL_DLGPROC设置对话框过程的新地址。
DWL_MSGRESULT 设置对话框中的消息处理程序的返回值。
DWL_USER 设置的应用程序所私有的新的额外信息,例如句柄或指针。

dwNewLong:指定的替换值。

如果函数成功,则返回所指定的偏移量的前一个值。
如果函数失败,则返回0。

GetWindowLongPtr函数

GetWindowLongPtr函数是在指定的窗口中获取信息。也可以在指定window内存偏移量的情况下获取值。

LONG_PTR GetWindowLongPtr(
	HWND hWnd,
	int nIndex
);

hWnd:欲获取信息的窗口(或属于窗口的类)的句柄。
nIndex:为欲获取的信息指定值。

如果函数执行成功,将返回读取的值。
如果执行失败,将返回零,要获取更多扩展信息,请调用GetLastError.

主要问题

为另一个进程创建的窗口建立子类时遇到的问题是:

  • 建立子类的过程位于另一个进程的地址空间中,可能造成内存访问的违规。

下图显示了一个简化了的图形,说明窗口过程是如何接受消息的。进程 A正在运行,并且已经创建了一个窗口。

窗口过程是如何接受消息的:

  • 文件 User32.dll被映射到进程A的地址空间中。对User32.dll文件的映射是为了接收和发送在进程 A中运行的任何线程创建的任何窗口中发送和显示的消息。
  • 当User32.dll的映像发现一个消息时,它首先要确定窗口的WndProc的地址,然后调用该地址,传递窗口的句柄、消息和wParam和lParam值。
  • 当WndProc处理该消息后,User32 .dll便循环运行,并等待另一个窗口消息被处理。
A进程 B进程
EXE file:

LRESUlT WndProc(HWND hend,UNIT uMsg,…){…}
EXE file

void Somefunc(void)
{
HWND hwnd=Findwindow(“class A”,NULL);
SetWindowLongPtr(hwnd,GWLP_WNDPROC,MySubclassProc);
}
USER32.DLL file:

LONG DispatchMessage(CONST MSG*msg)
{
LONG lRet;
WNDPROC lpfnWndProc=
(WNDPROC)GetWindowLongPtr(msg,hwnd,GWLP_WNDPROC);
lRet=lpfnWndProc(msg.hwnd,msg.message,msg.wParam,mag.lParam);
return lRet;
}
USER32.DLL file:

现在假设你的进程是进程B,你想为进程A中的线程创建的窗口建立子类:

  • 你在进程 B中的代码必须首先确定你想要建立子类的窗口的句柄。这个操作使用的方法很多。上图 显示的例子只是调用FindWindow函数来获得需要的窗口。
  • 进程B中的线程调用SetWindowLongPtr函数,试图改变窗口的WndProc的地址。(SetWindowLongPtr函数中的代码要查看是否有一个进程正在试图改变另一个进程创建的窗口的WndProc地址,然后将忽略这个函数的调用。 本节末尾会讲到,微软内部实现限制的)

如果SetWindowLongPtr函数能够改变窗口的 WndProc,那将出现什么情况呢?
系统将把MySubclassProc的地址与特定的窗口关联起来。然后,当有一条消息被发送到这个窗口中时,进程A中的User32.dll代码将检索该消息,获得 MySubclassProc的地址,并试图调用这个地址。但是,这时可能遇到一个大问题。MySubclassProc将位于进程B的地址空间中,而进程 A却是个活动进程。

显然,如果User32想要调用该地址,它就要调用进程 A的地址空间中的一个地址,这就可能造成内存访问的违规。

为了避免这个问题的产生,应该让系统知道 MySubclassProc是在进程B的地址空间中,然后,在调用子类的过程之前,让系统执行一次上下文转换(即切换到进程B地址空间)。

Microsoft没有实现这个辅助函数功能,原因是:

  • 应用程序很少需要为其他进程的线程创建的窗口建立子类。大多数应用程序只是为它们自己创建的窗口建立子类,Windows的内存结构并不阻止这种创建操作。
  • 切换活动进程需要占用许多CPU时间。
  • 进程B中的线程必须执行MySubclassProc中的代码。系统究竟应该使用哪个线程呢?是现有的线程,还是新线程呢?
    -User32.dll怎样才能说明与窗口相关的地址是用于另一个进程中的过程,还是用于同一个进程中的过程呢?

由于对这个问题的解决并没有什么万全之策,因此 Microsoft决定不让 Set WindowsLongPtr改变另一个进程创建的窗口过程。

不过仍然可以为另一个进程创建的窗口建立子类—只需要用另一种方法来进行这项操作。这并不是建立子类的问题,而是进程的地址空间边界的问题。

如果能将你的子类过程的代码放入进程A的地址空间,就可以方便地调用SetWindowLongPtr函数,将进程A的地址传递给MySubclassProc函数。我将这个方法称为将DLL“插入”进程的地址空间。有若干种方法可以用来进行这项操作。下面将逐个介绍它们。

二、使用注册表来插入DLL

cmd 输入 regedit 快速打开注册表。
以下是通过注册表路径会加载DLL的注册表键:

计算机\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

注意:
Windows 98 Windows 98将忽略注册表的这个关键字。在Windows 98下,无法使用该方法插入DLL。

使用方法

以下是加入D:\MyDLL.dll载入:
[笔记]Windows核心编程《二十二》注入DLL和拦截API_第1张图片
AppInit_DLLs该关键字的值包含一个DLL文件名或者一组DLL文件名(用空格或逗号隔开)。

过程

  • 当重新启动计算机及 Windows进行初始化时,系统将保存这个关键字的值。

  • 然后,当User32.dll库被映射到进程中时,它将接收到一个 DLL_PROC ESS_ATTACH通知。

  • 当这个通知被处理时,User32.dll便检索保存的这个关键字中的值,并且为字符串中指定的每个 D L L调用LoadLibrary函数。当每个库被加载时,便调用与该库相关的 DllMain函数(fdwReason为DLL_PROCESS_ATTACH)
    这样,每个库就能够对自己进行初始化。

    由于插入的 D L L在进程的寿命期中早早地就进行了加载,因此在调用函数时应该格外小心。调用 kernel32.dll
    中的函数时应该不会出现什么问题,不过调用其他 D L L中的函数时就可能产生一些问题。

  • User32.dll并不检查每个库是否已经加载成功,或者初始化是否取得成功。

注册表注入DLL的不足

在插入D L L时所用的所有方法中,这是最容易的一种方法。要做的工作只是将一个值添加到一个已经存在的注册表关键字中。

不过这种方法也有它的某些不足:

  • 修改值后必须重新启动你的计算机: 由于系统在初始化时要读取这个关键字的值,因此在修改这个值后必须重新启动你的计算机—即使退出后再登录,也不行。当然,如果从这个关键字的值中删除 D L L,那么在计算机重新启动之前,系统不会停止对库的映射操作。
  • 你的D L L只会映射到使用User32.dll的进程中: 所有基于GUI的应用程序均使用User32.dll,不过大多数基于 CUI的应用程序并不使用它。因此,如果需要将 D L L插入编译器或链接程序,这种方法将不起作用。
  • 你的D L L将被映射到每个基于 G U I的应用程序中,但是必须将你的库插入一个或几个进程中。 你的D L L映射到的进程越多,“容器”进程崩溃的可能性就越大。毕竟在这些进程中运行的线程是在执行你的代码。如果你的代码进入一个无限循环,或者访问的内存不正确,就会影响代码运行时所在进程的行为特性和健壮性。因此,最好将你的库插入尽可能少的进程中。
  • 你的D L L将被映射到每个基于 G U I的应用程序中。这与上面的问题相类似。在理想的情况下,你的D L L只应该映射到需要的进程中,同时,它应该以尽可能少的时间映射到这些进程中。假设在用户调用你的应用程序时你想要建立 WordPad的主窗口的子类。在用户调用你的应用程序之前,你的 D L L不必映射到WordPad的地址空间中。如果用户后来决定终止你的应用程序的运行,那么你必须撤消 WordPad的主窗口。在这种情况下,你的D L L将不再需要被插入WordPad的地址空间。最好是仅在必要时保持D L L的插入状态。

三、使用Windows挂钩来插入DLL

可以使用挂钩将D L L插入进程的地址空间。为了使挂钩能够像它们在 1 6位Windows中那样工作,微软不得不设计了一种方法,使得D L L能够插入另一个进程的地址空间中。

SetWindowsHookEx函数

该函数将一个应用程序定义的挂钩处理过程安装到挂钩链中去,你可以通过安装挂钩处理过程来对系统的某些类型事件进行监控,这些事件与某个特定的线程或系统中的所有事件相关。

HHOOK WINAPI SetWindowsHookEx(
 	int idHook, \\钩子类型
	HOOKPROC lpfn, \\回调函数地址
	HINSTANCE hMod, \\实例句柄
	DWORD dwThreadId \\线程ID
); 

idHook: 指明要安装的挂钩的类型。
lpfn: 指明窗口准备处理一个消息时系统应该调用的函数的地址。
hMod:包含GetMsgProc函数的D L L实例句柄。
dwThreadId :线程ID,0用于指明要挂接所有 G U I线程。
返回值:
若此函数执行成功,则返回值就是该挂钩处理过程的句柄;若此函数执行失败,则返回值为NULL(0).若想获得更多错误信息,请调用GetLastError函数.

UnhookWindowsHookEx函数

删除挂钩链中安装的钩子。

BOOL WINAPI UnhookWindowsHookEx( __in HHOOK hhk);

hhk:要删除的钩子的句柄。这个参数是上一个函数SetWindowsHookEx的返回值.

GetWindowThreadProcessId函数

找出某个窗口的创建者(线程或进程),返回创建者的标志符。

DWORD GetWindowThreadProcessId(
	HWND hWnd,
	LPDWORD lpdwProcessId
);

hWnd: (向函数提供的)被查找窗口的句柄.
lpdwProcessId:[out] 进程号的存放地址(变量地址)

返回值:返回线程号,注意,lpdwProcessId 是存放进程号的变量。返回值是线程号,lpdwProcessId 是进程号存放处。

PostThreadMessage函数

将一个队列消息放入(寄送)到指定线程的消息队列里,不等待线程处理消息就返回。

BOOLPostThreadMessage(
	DWORDidThread,
	UINTMsg,
	WPARAMwParam,
	LPARAMIParam
);

idThread:其消息将被寄送的线程的线程标识符。如果线程没有消息队列,此函数将失败。当线程第一次调用一个Win 32 USER或GDI函数时,系统创建线程的消息队列。要得到更多的信息,参见备注。
Msg:指定将被寄送的消息的类型。
wParam:指定附加的消息特定信息。
IParam:指定附加的消息特定信息。

返回值:返回线程号。

GetMessage函数

从调用线程的消息队列里取得一个消息并将其放于指定的结构。此函数可取得与指定窗口联系的消息和由PostThreadMessage寄送的线程消息。此函数接收一定范围的消息值。GetMessage不接收属于其他线程或应用程序的消息。获取消息成功后,线程将从消息队列中删除该消息。函数会一直等待直到有消息到来才有返回值。

GetMessage(
	LPMSG lpMsg,
	HWND hWnd,
	UINT wMsgFilterMin,
	UINT wMsgFilterMax
)

lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
hWnd:取得其消息的窗口的句柄。当其值取NULL时,GetMessage为任何属于调用线程的窗口检索消息,线程消息通过PostThreadMessage寄送给调用线程。
wMsgFilterMin:指定被检索的最小消息值的整数。
wMsgFilterMax:指定被检索的最大消息值的整数。

返回值:如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。如果出现了错误,返回值是-1。例如,当hWnd是无效的窗口句柄或lpMsg是无效的指针时。若想获得更多的错误信息,请调用GetLastError函数。

SendMessage函数

将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回。

LRESULT SendMessage(
		HWND hWnd,
		UINT Msg,
		WPARAM wParam,
		LPARAM IParam
)

hWnd:指定要接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
Msg:指定被发送的消息。
wParam:指定附加的消息特定信息。
IParam:指定附加的消息特定信息。

返回值:返回值指定消息处理的结果,依赖于所发送的消息。

GetFirstChild宏

GetFirstChild()函数其实是一个宏定义:

#define GetFirstChild(hwnd) GetTopWindow(hwnd)\

GetTopWindow函数

GetTopWindow函数检查与特定父窗口相联的子窗口z序,并返回在z序顶部的子窗口的句柄。

HWND GetTopWindow(HWND hWnd);

hWnd:被查序的父窗口的句柄。如果该参数为NULL,函数返回Z序顶部的窗口句柄。

返回值;如果函数成功,返回值为在Z序顶部的子窗口句柄。如果指定的窗口无子窗口,返回值为NULL。

FindWindow函数

FindWindow这个函数检索处理顶级窗口的类名和窗口名称匹配指定的字符串。这个函数不搜索子窗口。

HWND FindWindow(LPCSTR lpClassName,LPCSTR lpWindowName);

lpClassName:指向一个以NULL字符结尾的、用来指定类名的字符串或一个可以确定类名字符串的原子。如果这个参数是一个原子,那么它必须是一个在调用此函数前已经通过GlobalAddAtom函数创建好的全局原子。这个原子(一个16bit的值),必须被放置在lpClassName的低位字节中,lpClassName的高位字节置零。如果该参数为null时,将会寻找任何与lpWindowName参数匹配的窗口。
lpWindowName:指向一个以NULL字符结尾的、用来指定窗口名(即窗口标题)的字符串。如果此参数为NULL,则匹配所有窗口名。

返回值
如果函数执行成功,则返回值是拥有指定窗口类名或窗口名的窗口的句柄。
如果函数执行失败,则返回值为 NULL 。

挂载调用

HHO0K hHook = SetWindowsHookEx(
				WH_GETMESSAGE,
				GetMsgProc,
				hinstDll,
				0
			 );
  1. 进程B中的一个线程准备将一条消息发送到一个窗口。
  2. 系统查看该线程上是否已经安装了WH_GETMESSAGE挂钩。
  3. 系统查看包含GetMsgProc函数的DLL是否被映射到进程B的地址空间中。
  4. 如果该DLL尚未被映射,系统将强制该 DLL映射到进程B的地址空间,并且将进程 B中 的DLL映像的自动跟踪计数递增1。
  5. 当D L L的hinstDll用于进程B时,系统查看该函数,并检查该 D L L的hinstDll是否与它用于进程A时所处的位置相同。
  6. 系统将进程B中的D L L映像的自动跟踪计数递增1。
  7. 系统调用进程B的地址空间中的GetMsgProc函数。
  8. 当GetMsgProc函数返回时,系统将进程B中的D L L映像的自动跟踪计数递减1。

3.1 桌面项目位置保存器实用程序 DIPS

桌面项目位置保存器实用程序 DIPS。

当你将 S 作为命令行参数传递给 DIPS时,它就创建下面这个注册表子
关键字,并且给桌面窗口上的每个项目添加一个值:

HKEY_CURRENT_USER\Software\Richter\Desktop Item Position Saver

[笔记]Windows核心编程《二十二》注入DLL和拦截API_第2张图片

每个项目都有一个与它一起保存的位置值。当改变屏幕分辨率以便玩游戏之前,运行 DIPS的S。当玩完游戏后,将屏幕的分辨率改为原来的状态,并且运行 DIPS R。这使得DIPS打开注册表子关键字,对于桌面上与注册表中保存的项目相匹配的每个项目来说,当运行 DIPS S时,项目的位置将被重新设置为原来的值。

原理

DIPS应用程序使用窗口挂钩将一个DIPSLib.dll插入Explorer.exe的地址空间。

Explorer.exe进程用于管理Windows的图形界面,包括开始菜单、任务栏、桌面和文件管理,如果结束这个该程序会导致Windows图形界面无法使用,就是电脑只会剩下一张壁纸,其他的东西统统消失。所以这个Explorer.exe进程的正常运行对系统的稳定性有很大的帮助。如果用户发现任务管理器中有两个或多个Explorer.exe进程,那么就要检查是否中了远控了,最好用木马专杀工具扫扫。

windows下使用Hook实现对远程进程注入DLL技术,通过共用操作系统下同一份DLL,实现对目标进程的特定消息传输,甚至达到控制的技术。

DIPS功能

DIPS主要调用DIPSLib,DIPSLib主要实现Hook以及保存屏幕信息至注册表。

DIPS主要功能:

  • 界面显示 save、resave等
  • 获取ProgMan的线程ID
  • 调用SetDPISHook和UnHook
  • 作为客户端像DIPSLib发送WM_APP消息

ProgMan是显示出来的总桌面,也是总程序,如果你向Progman窗口发送一个WM_CLOSE消息,Windows就会提醒你是否要关机。
参考 编写Windows动态壁纸(关于Windows10桌面的了解)(一)
参考 Windows动态桌面原理
可以通过spy++ 查看
[笔记]Windows核心编程《二十二》注入DLL和拦截API_第3张图片

DIPSLib功能

DIPSLib主要功能:

  • 导出SetDIPSHook接口用来Hook线程
  • 保存DIPS信息到注册表。(SaveListViewItemPositions/RestoreListViewItemPositions)
  • 共享数据段g_hHook、g_dwThreadIdDIPS
#pragma data_seg("Shared")
HHOOK g_hHook = NULL;
DWORD g_dwThreadIdDIPS = 0;
#pragma data_seg()
  • 作为服务端 接受处理WM_APP消息

过程

  1. 首先通过ProgMan进程,得到桌面的ListView控件的窗口句柄
 HWND hWndLV = GetFirstChild(GetFirstChild(
      FindWindow(TEXT("ProgMan"), NULL)));
  1. 通过调用GetWindowThreadProcessId函数,就能够确定创建窗口的线程的ID。将这个ID传递给 SetDIPSHook函数(在DIPSLib.cpp中实现)。SetDIPSHook负责在该线程上安装一个WH_GETMESSAGE挂钩,然后调用PostThreadMessage函数,以强制Windows Explorer的线程醒来。
// Install the hook on the specified thread
     g_hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInstDll, dwThreadId);
     bOk = (g_hHook != NULL);
     if (bOk) {
        // The hook was installed successfully; force a benign message to 
        // the thread's queue so that the hook function gets called.
        bOk = PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
     }
  1. 由于已经在该线程上安装了一个 WH_GETMESSAGE挂钩,因此操作系统能够自动将DIPSLib. dll 文件插入Explorer.exe的地址空间,并且调用GetMsgProc函数。该函数首先查看它是否是初次被调用,如果是,那么它就创建一个隐藏的窗口,其标题是Wintellect DIPS,请记住,Explorer的线程正在创建这个隐藏窗口。
 // The DLL just got injected.
 bFirstTime = FALSE;
 // Uncomment the line below to invoke the debugger 
 // on the process that just got the injected DLL.
 // ForceDebugBreak();
 // Create the DIPS Server window to handle the client request.
 CreateDialog(g_hInstDll, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc);
 // Tell the DIPS application that the server is up 
 // and ready to handle requests.
 PostThreadMessage(g_dwThreadIdDIPS, WM_NULL, 0, 0);
  1. 调用GetMessage函数,这次函数调用将使线程进入睡眠状态,直到队列中显示一条消息为止。

  2. 服务端DIPS.dll隐藏窗口Wintellect DIPS创建完成后,客户端DIPS.exe 通过FindWindow其标题Wintellect DIPS获得其句柄,向其发送消息WM_APP,让服务端 DIPS.dll 此时处理S、R操作。

  3. 客户端DIPS.exe 发送消息WM_CLOSE

  4. SetDIPSHook(0),卸载挂钩,Explorer的地址空间中卸载DIPSLib.dll。

四、使用远程线程来插入DLL

插入DLL的第三种方法是使用远程线程。这种方法具有更大的灵活性。

原理

特点

基本操作步骤

执行的操作步骤:

  1. 使用VirtualAllocEx函数,分配远程进程的地址空间中的内存。
  2. 使用WriteProcessMemory函数,将D L L的路径名拷贝到第一个步骤中已经分配的内存中。
  3. 使用GetProcAddress函数,获取 LoadLibraryA或LoadLibraryW函数的实地址(在Kernel32.dll中)。
  4. 使用CreateRemoteThread函数,在远程进程中创建一个线程,它调用正确的 LoadLibrary函数,为它传递第一个步骤中分配的内存的地址。这时, DLL已经被插入远程进程的地址空间中,同时 DLL的DllMain函数接收到一个DLL_PROCESS_ATTACH通知,并且能够执行需要的代码。当 DllMain函数返回时,远程线程从它对LoadLibrary的调用返回到 BaseThreadStart函数(第 6章中已经介绍)。然后BaseThreadStart调用ExitThread,使远程线程终止运行。现在远程进程拥有第一个步骤中分配的内存块,而 DLL则仍然保留在它的地址空间中。若要将它删除,需要在远程线程退出后执行下面的步骤:
  5. 使用VirtualFreeEx函数,释放第一个步骤中分配的内存。
  6. 使用GetProcAddress函数,获得FreeLibrary函数的实地址(在Kernel32.dll中)。
  7. 使用CreateRemoteThread函数,在远程进程中创建一个线程,它调用 FreeLibrary函数,传递远程DLL的HINSTANCE。

OpenProcess函数

OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。

HANDLE OpenProcess(
	DWORD dwDesiredAccess, //渴望得到的访问权限(标志)
	BOOL bInheritHandle, // 是否继承句柄
	DWORD dwProcessId// 进程标示符
);

dwDesiredAccess:获取的权限,可分为以下几种
PROCESS_ALL_ACCESS:获取所有权限
PROCESS_CREATE_PROCESS:创建进程
PROCESS_CREATE_THREAD:创建线程
PROCESS_DUP_HANDLE:使用DuplicateHandle()函数复制一个新句柄
PROCESS_QUERY_INFORMATION:获取进程的令牌、退出码和优先级等信息
PROCESS_QUERY_LIMITED_INFORMATION:获取进程特定的某个信息
PROCESS_SET_INFORMATION:设置进程的某种信息
PROCESS_SET_QUOTA:使用SetProcessWorkingSetSize函数设置内存限制
PROCESS_SUSPEND_RESUME:暂停或者恢复一个进程
PROCESS_TERMINATE:使用Terminate函数终止进程
PROCESS_VM_OPERATION:在进程的地址空间执行操作
PROCESS_VM_READ:使用ReadProcessMemory函数在进程中读取内存
PROCESS_VM_WRITE:使用WriteProcessMemory函数在进程中写入内存
SYNCHRONIZE:使用wait函数等待进程终止
bInheritHandle:TRUE或者FALSE
dwProcessId:pid

VirtualAllocEx函数

分配远程进程的地址空间中的内存

WriteProcessMemory函数

GetProcAddress函数

获取 LoadLibraryA或LoadLibraryW函数的实地址(在Kernel32.dll中)

CreateRemoteThread函数

能够创建一个在其它进程地址空间中运行的线程(也称为创建远程线程)。

HANDLE CreateRemoteThread(
     HANDLE hProcess,
     LPSECURITY_ATTRIBUTES lpThreadAttributes,
     SIZE_T dwStackSize,
     LPTHREAD_START_ROUTINE lpStartAddress,
     LPVOID lpParameter,
     DWORD dwCreationFlags,
     LPDWORD lpThreadId
);

hProcess:进程句柄
lpThreadAttributes : 线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针
dwStackSize:线程栈大小,以字节表示
lpStartAddress: 一个LPTHREAD_START_ROUTINE类型的指针,指向在远程进程中执行的函数地址
lpParameter: 传入参数
dwCreationFlags:创建线程的其它标志
lpThreadId:[输出] 线程身份标志,如果为NULL,则不返回

返回值:成功返回新线程句柄,失败返回NULL,并且可调用GetLastError获得错误值。

4.1 Inject Library示例应用程序

在这里插入图片描述使用任务管理获取进程的I D。使用这个I D,该程序将设法通过调用 OpenProcess函数来打开正在运行的进程的句柄,申请相应的访问权。

原理

InjLib.exe应用程序使用CreateRemoteThread函数来插入ImgWalk.DLL

过程

  1. 使用界面输入的ProcesssId,然后获取ImgWalk.dll文件位置。
  2. 调用InjectLib:InjectLib内部调用OpenProcess获得ProcessId的句柄hProcess
  3. 通过进程句柄hProcess,
    调用VirtualAllocEx,分配远程进程的地址空间中的内存
  4. 使用WriteProcessMemory函数,将D L L的路径名拷贝到第一个步骤中已经分配的内存中。
  5. 调用GetProcAddress
    获取 LoadLibraryA或LoadLibraryW函数的实地址(在Kernel32.dll中)
  6. CreateRemoteThread,创建远程线程。
  7. 结束InjectLib后释放句柄hProcess,线程hThread,内存。
  8. 注入成功后,调用EjectLib,同InjectLib,只不过获得FreeLibrary地址,然后通过CreateRemoteThread调用FreeLibrary。

4.2 Image Walk DLL

ImgWalk.dll 是个D L L,一旦它被插入进程的地址空间,就能够报告该进程正在使用的所有 DLL。

原理

ImgWalk遍历进程的地址空间,查找已经映射的文件映像,反复调用 Virtual Query函数,填入一个MEMORY_BASIC_ INFORMATION结构中。运用循环的每个重复操作, ImgWalk找出一个文件路径名,并与一个字符串相连接。该字符串显示在消息框中。

五、使用特洛伊DLL来插入DLL

原理

利用自己伪造的同名DLL取代软件内的将要加载的Xyz.DLL。

特点

  • 在你的Xyz.dll中,输出的全部符号必须与原始的Xyz.dll输出的符号相同

实现方法 :

  1. 使用函数转发器

注意:
虽然函数转发器使你能够非常容易地挂接某些函数,你应该避免使用这种方法,因为它不具备版本升级能力。例如,如果你取代了一个系统 D L L, 而M i c r o s o f t在将来增加了一些新函数,那么你的 D L L将不具备它们的函数转发器。引用这些新函数的应用程序将无法加载和执行。

  1. 改变应用程序的.exe模块的输入节

注意:。
输入节只包含模块需要的 D L L的名字。你可以仔细搜索文件中的这个输入节,并且将它改变,使加载程序加载你自己的 D L L。这种方法相当不错,但是必须要非常熟悉. e x e和D L L文件的格式。

六、将DLL作为调试程序来插入

原理

调试程序能够对被调试的进程执行特殊的操作。

当被调试进程加载时,在被调试进程的地址空间已经作好准备,但是被调试进程的主线程尚未执行任何代码之前,系统将自动将这个情况通知调试程序时,调试程序可以强制将某些代码插入被调试进程的地址空间中(比如使用WriteProcessMemory函数来插入),然后使被调试进程的主线程执行该代码。

特点

  • 这种方法要求你对被调试线程的 CONTEXT结构进行操作,意味着必须编写特定 CPU的代码。必须修改你的源代码,使之能够在不同的 C P U平台上正确地运行。另外,必须对你想让被调试进程执行的机器语言指令进行硬编码。而且调试程序与它的被调试程序之间必须存在固定的关系。

  • 如果调试程序终止运行,Windows将自动撤消被调试进程。而你则无法阻止它。

七、用Windows 98上的内存映射文件插入代码

原理

在Windows 98上插入你自己的代码是非常简单的。在 Windows 98上运行的所有 3 2位 Wi n d o w s应用程序均共享同样的最上面的2 GB地址空间。如果你分配这里面的某些存储器,那么该存储器在每个进程的地址空间中均可使用。若要分配 2 GB以上的存储器,只要使用内存映射文件(第 1 7章已经介绍)。可以创建一个内存映射文件,然后调用 MapViewOfFile函数,使它显示出来。然后将数据填入你的地址空间区域(这是所有进程地址空间中的相同区域)。

特点

  • 必须使用硬编码的机器语言来进行填入数据到地址空间区域,很难移植到别的CPU平台。
  • 必须让其他进程中的线程来执行内存映射文件中的代码。要做到这一点,需要某种方法来控制远程进程中的线程。CreateRemoteThread函数能够很好地执行这个任务,可惜Windows 98不支持该函数的运行。

八、用CreateProcess插入代码

原理

通过当前进程来生成目标进程,在目标进程作为子进程启动之前先将其挂起。同时会得到其主线程的句柄。通过这个句柄可以对线程执行的代码进行修改。

方法

参考

让一个进程对其子进程的主线程进行控制的一种方法:

  1. 让进程生成一个被挂起的子进程
  2. 从exe模块的文件头中得到主线程的起始内存地址
  3. 将位于该内存地址处的机器指令保存起来
  4. 强制将一些手动编写的机器指令写入到该内存地址处。这些指令应该调用一个LoadLibrary来载入一个DLL
  5. 让子进程的主线程恢复运行,从而让这些指令得到执行。
  6. 把保存起来的原始指令恢复到起始地址处
  7. 让进程从起始地址继续执行,就好像什么都没发生一样。

特点

优点:

  • 它在应用程序执行之前就能得到地址空间
  • 它既能在Windows 98上使用,也能在Windows 2000上使用
  • 由于你不是调试者,因此能够很容易使用插入的D L L来调试应用程序
  • 这种方法可以同时用于控制台和 G U I应用程序。

缺点:

  • 只有当你的代码在父进程时,才能插入 DLL。
  • 不能跨越不同的C P U来运行,必须对不同的C P U平台进行相应的修改。

九、拦截API的一个示例

9.1 通过改写代码来拦截API

原理

用汇编代码JUMP覆盖需要拦截目标函数。

步骤

拦截ExitProcess的一个方案是用代码覆盖来拦截。

工作方式如下:

  1. 在内存中,对想要拦截的函数(假设是Kernel32.dll中的ExitProcess)进行定位,从而得到它的内存地址。
  2. 把这个函数起始的几个字节保存到自己的内存中
  3. 用CPU的一条JUMP指令来覆盖这个函数的起始几个字节,这条JUMP指令用来跳转到我们替代函数的内存地址。所有替代函数的签名必须要与拦截函数的签名完全相同:所有参数必须相同,返回值必须相同,调用约定也必须相同。
  4. 当线程调用被拦截函数(hooked function)的时候,跳转指令实际上会跳转到我们的替代函数。这是可以执行自己想要执行的代码。
  5. 为了撤销对该函数的拦截,必须把步骤2)中保存下来的直接放回被拦截函数的起始字节。
  6. 我们调用被拦截函数,让该函数正常处理
  7. 当原来的函数返回时,再次执行步骤2和步骤3,这样我们的代替函数将来还好被调用到。

特点

  • 它对cpu有依赖性x86, x64, ia-64其他cpu的JUMP指令各不相同,为了让这种方法工作必须手工编写机器指令。

  • 其次,这种方法在抢占式,多线程环境下根本不能工作。一个线程覆盖一个函数起始位置的代码是需要时间的,这个过程如果另一个线程试图调用同一个函数,结果是灾难性的。(除非保证任何时候只有一个线程会调用该函数)

9.2 通过操作模块的输入节来拦截API

一个模块的导入段包含一组DLL,为了让模块能够运行,这些DLL是必须的。此外导入段还包含一个符号表,列出了该模块从各DLL中导入的符号。当该模块调用一个导入函数的时候,实际上是先从该模块的导入表中得到相应的导入函数的地址,然后再跳转到那个地址。

原理

修改特点函数在模块的输入节(导入段)中的地址。

特点

  • 完全不存在对cpu的依赖性。
  • 而且并不需要修改函数的代码,
  • 也不用担心线程同步问题。

9.3 LastMsgBoxInfo示例应用程序

该例子拦截了所有对MessageBox(位于User32.dll中)的调用,为了在所有进程中拦截这个函数,应用程序使用了Windows挂钩技术来注入DLL。

总结

1. 第三节 使用Windows挂钩来插入DLL 发现隐藏窗口Wintellect DIPS为NULL

使用64位运行

你可能感兴趣的:(#,windows核心编程,windows,visual,studio,c++)