Win32 程序的API内联与Hook

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Win32 程序的API内联与Hook相关的知识,希望对你有一定的参考价值。

概述

我们有一个程序比较简单界面如下:

我们点击中间的按钮会弹出一个MessageBox 如下图所示

为方便学习这个原始程序我这里也用汇编编写

.386
.model flat, stdcall  ;32 bit memory model
option casemap :none  ;case sensitive

include dlgApp.inc
.data
	g_szTitle db "myTitle",0

.code

start:

	invoke GetModuleHandle,NULL
	mov		hInstance,eax

    invoke InitCommonControls
	invoke DialogBoxParam,hInstance,IDD_DIALOG1,NULL,addr DlgProc,NULL
	invoke ExitProcess,0

;########################################################################

DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

	mov		eax,uMsg
	.if eax==WM_INITDIALOG

	.elseif eax==WM_COMMAND
		mov ebx,wParam
		.if bx == IDC_BTN1
			invoke MessageBox,NULL,offset g_szTitle,offset g_szTitle,MB_OK
		.endif
		

	.elseif eax==WM_CLOSE
		invoke EndDialog,hWin,0
	.else
		mov		eax,FALSE
		ret
	.endif
	mov		eax,TRUE
	ret

DlgProc endp

end start

我们假设想实现一个功能注入一个dll到程序后,会修改程序所有弹出的MessageBox 的标题加上注入前缀

思路如下

Win32程序弹出窗口是调用系统的MessageBox实现的,而其实现位于user32.dll,我们修改程序内的user32.dllMessageBox函数内部即可。
但是请注意修改程序内user32.dll只会修改当前程序MessageBox函数,而不会影响其他程序的MessageBox。不管是win32程序还是linux程序加载共享库机制都差不多,都是公用内存中同一份dll或者so,当某一程序修改内存中的共享库时会将此内存区域做拷贝后做修改并不会影响其他加载此so的程序。修改后的共享库的那块内存区域我们一般称为Shared_Dirty,此块区域修改程序独享
在linux中你可以通过 /proc/$pid/smaps 更加深刻的去理解:
如下图所示:

win32的dll加载后会调用DllMain函数,我们在此函数中调用我们的内存修改函数修改MessageBox其函数实现。

;HookMessageBox.asm
DllMain proc hInst:HMODULE,fdReason:DWORD,pReserve:LPVOID
	
	;此dll被附加到程序中
	.if fdReason == DLL_PROCESS_ATTACH
		;调用hook函数修改MessageBox实现
	    invoke InstallHook
	.endif
	
	mov eax,TRUE
	ret

DllMain endp	

在查看上面InstallHook函数前我们首先查看原始messagebox函数的实现:
首先打开OD附加对应的程序,选中菜单上的View->Excutable module




我们首先对这个函数进行断点查看栈区传参结构

从上面我们可以进行如下操作,MessageBox前5个字节指令我们可以替换为jmp xxx (这个指令正好5个字节)然后再xxx地址编写相关逻辑再跳转回原来的地址。

我们画一个图来描述下

我们首先在汇编代码中定义我们需要的变量

;HookMessageBox.asm
.data
	g_szUser32 db "user32",0
	g_szMessageBox db "MessageBoxA",0
	g_dwMsgBoxAddr dd 0
	
	
	g_szBuff db 256 dup(0)
	g_szFmt db  "注入 %s",0

接着我们需要编写InstallHook函数,当然你需要知道MessgeBox函数的地址,并且由于MessgeBox内存地址由于是代码段具有只读的特性因此我们需要修改为可读写

InstallHook proc
	LOCAL @hUser32:HMODULE
	LOCAL @dwOldProtect:DWORD
	
	
	;获取user32.dll模块
	invoke GetModuleHandle,offset g_szUser32
	;存储模块句柄
	mov @hUser32,eax

	
	;取user32.dll模块MessageBoxA内存地址
	invoke GetProcAddress,@hUser32,offset g_szMessageBox
	
	;保存函数地址
	mov g_dwMsgBoxAddr,eax
	
	;修改地址为可读可写
	invoke VirtualProtect,g_dwMsgBoxAddr,1,PAGE_EXECUTE_READWRITE,addr @dwOldProtect
	
	
	;修改代码
	mov eax,g_dwMsgBoxAddr
	; e9是jmp远跳的机器码
	mov byte ptr[eax] ,0e9h
	
	;偏移5个字节码,jmp指令的格式:e9+下一个指令到跳转地址的偏移量  注意e9是远跳
	mov eax,g_dwMsgBoxAddr
	add eax,5
	;HOOKCODE是一个jmp的具体地址
	mov ebx,offset HOOKCODE
	;计算偏移后
	sub ebx,eax
	;放入偏移量信息到g_dwMsgBoxAddr函数中
	mov eax,g_dwMsgBoxAddr
	;inc是自增1字节,因为eax存储了e9
	inc eax
	;将偏移地址写入e9之后
	mov dword ptr[eax],ebx
	
	
	
   ;将函数的内存只读性质还原
	invoke VirtualProtect,g_dwMsgBoxAddr,1,@dwOldProtect,addr @dwOldProtect
	
	
	xor eax,eax
	ret
 

InstallHook endp	
	

上面看到了offset HOOKCODE 这个就是我们将要执行具体的资源替换函数实现

;InstallHook proc
HOOKCODE proc far
	;将通用寄存器的上下文保存到栈中
	pushad
	;将标志寄存器的上下文保存到栈中
	pushfd
	

     ;esp+0ch是原来调用MsgBox函数传入的字符串
     invoke wsprintf,offset g_szBuff,offset g_szFmt,dword ptr[esp+08h+24h]
	 mov dword ptr[esp+08h+24h],offset g_szBuff


	

	
	;恢复标志寄存器
	popfd
	;恢复通用寄存器的
	popad
	
	;跳转原来的Msg函数的下一行
	mov eax,g_dwMsgBoxAddr
	add eax,5
	
	;被替换jmp之前的指令
	mov edi,edi
	push ebp
	mov ebp,esp
	
	
	jmp eax 

HOOKCODE endp

上面便是我们较为核心的细节。

但是我们忽律了一种情况便是重入,假设在你的HOOKCODE 中存在自己也调用messageBox的情况那么便会出现死循环。

于是我们修改下上面的代码:

.data
	;...忽律其他代码
	g_bIsSelfCall db FALSE ;是否在调用自己的API
	
	
.code 



HOOKCODE proc far
	;将通用寄存器的上下文保存到栈中
	pushad
	;将标志寄存器的上下文保存到栈中
	pushfd
	
	.if g_bIsSelfCall==FALSE
		;esp+0ch是原来调用MsgBox函数传入的字符串
			invoke wsprintf,offset g_szBuff,offset g_szFmt,dword ptr[esp+08h+24h]
			mov dword ptr[esp+08h+24h],offset g_szBuff
		
		mov g_bIsSelfCall,TRUE
		;自己弹出MessageBox
		invoke MessageBox,NULL,offset g_szBuff,offset g_szBuff,MB_OK
		mov g_bIsSelfCall,FALSE
	.endif
	

	
	;恢复标志寄存器
	popfd
	;恢复通用寄存器的
	popad
	
	;跳转原来的Msg函数的下一行
	mov eax,g_dwMsgBoxAddr
	add eax,5
	
	
	mov edi,edi
	push ebp
	mov ebp,esp
	
	
	jmp eax 

HOOKCODE endp

完整代码实现

.586
.model flat,stdcall
option casemap:none

	include windows.inc
	include user32.inc
	include kernel32.inc
	include msvcrt.inc
	
	includelib kernel32.lib
    includelib user32.lib
    includelib msvcrt.lib
   


.data
	g_szUser32 db "user32",0
	g_szMessageBox db "MessageBoxA",0
	g_dwMsgBoxAddr dd 0
	
	
	g_szBuff db 256 dup(0)
	g_szFmt db  "注入 %s",0
	
	g_bIsSelfCall db FALSE ;是否在调用自己的API
	
	
.code 



HOOKCODE proc far
	;将通用寄存器的上下文保存到栈中
	pushad
	;将标志寄存器的上下文保存到栈中
	pushfd
	
	.if g_bIsSelfCall==FALSE
		;esp+0ch是原来调用MsgBox函数传入的字符串
			invoke wsprintf,offset g_szBuff,offset g_szFmt,dword ptr[esp+08h+24h]
			mov dword ptr[esp+08h+24h],offset g_szBuff
		
		mov g_bIsSelfCall,TRUE
		;自己弹出MessageBox
		invoke MessageBox,NULL,offset g_szBuff,offset g_szBuff,MB_OK
		mov g_bIsSelfCall,FALSE
	.endif
	

	
	;恢复标志寄存器
	popfd
	;恢复通用寄存器的
	popad
	
	;跳转原来的Msg函数的下一行
	mov eax,g_dwMsgBoxAddr
	add eax,5
	
	
	mov edi,edi
	push ebp
	mov ebp,esp
	
	
	jmp eax 

HOOKCODE endp



InstallHook proc
	LOCAL @hUser32:HMODULE
	LOCAL @dwOldProtect:DWORD
	
	
	;获取user32模块
	invoke GetModuleHandle,offset g_szUser32
	;将模块句柄放入首地址
	mov @hUser32,eax

	
	;获取MessageBoxA内存地址
	invoke GetProcAddress,@hUser32,offset g_szMessageBox
	
	;存储box函数地址
	mov g_dwMsgBoxAddr,eax
	
	;修改地址为可读可写
	invoke VirtualProtect,g_dwMsgBoxAddr,1,PAGE_EXECUTE_READWRITE,addr @dwOldProtect
	
	
	;修改代码
	mov eax,g_dwMsgBoxAddr
	; e9是jmp远跳的机器码
	mov byte ptr[eax] ,0e9h
	
	;偏移5个字节码,jmp指令的格式:e9+下一个指令到跳转地址的偏移量  注意e9是远跳
	mov eax,g_dwMsgBoxAddr
	add eax,5
	mov ebx,offset HOOKCODE
	sub ebx,eax
	;放入偏移量信息到g_dwMsgBoxAddr函数中
	mov eax,g_dwMsgBoxAddr
	inc eax
	mov dword ptr[eax],ebx
	
	
	
	
	
	
	invoke VirtualProtect,g_dwMsgBoxAddr,1,@dwOldProtect,addr @dwOldProtect
	
	
	xor eax,eax
	ret
 

InstallHook endp	
	
	
	
	

DllMain proc hInst:HMODULE,fdReason:DWORD,pReserve:LPVOID
	
	.if fdReason == DLL_PROCESS_ATTACH
	    invoke InstallHook
	.endif
	
	
	
	mov eax,TRUE
	ret

DllMain endp	


end

以上是关于Win32 程序的API内联与Hook的主要内容,如果未能解决你的问题,请参考以下文章

WIN32 消息Hook API

WIN32 消息Hook API

Detours简介 (拦截x86机器上的任意的win32 API函数)

HOOK函数ZwQuerySystemInformation实现进程隐藏

API HOOK介绍

win32 API 笔记1