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.dll
的MessageBox
函数内部即可。
但是请注意修改程序内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的主要内容,如果未能解决你的问题,请参考以下文章
HOOK函数ZwQuerySystemInformation实现进程隐藏