前言
最近看了《加密与解密》,跟着大佬们的思路学习了Hook相关知识,如理解有误请不吝赐教,以免误导他人。
Inline HOOK
API函数都保存在操作系统提供的DLL文件中,当在程序中调用某个API函数并运行程序后,程序会隐式地将API函数所在的DLL文件加载入内存中,这样,程序就会像调用自己的函数一样调用API。Inline Hook这种方法是在程序流程中直接进行嵌入jmp指令来改变流程的。
简而言之,就是将函数开头修改为jmp指令,跳转到我们自定义的函数上去。
首先用CreateProcessA API写一个测试程序,功能很简单,程序启动后,按下任意键,调用CreateProcessA创建进程,为了直观这里是直接弹一个计算器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | BOOL CreateProcessR(char * szExePath) {
SECURITY_ATTRIBUTES psa = { 0 };
SECURITY_ATTRIBUTES tsa = { 0 };
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
BOOL Ret;
Ret = CreateProcessA(szExePath, NULL, &psa, &tsa, false, 0 , NULL, NULL, &si, &pi);
/ / TerminateProcess(pi.hProcess, 0 ); / / 结束进程
return Ret;
}
int main( int argc,char * argv[]) {
system( "pause" );
CreateProcessR(EXE_PATH);
return 0 ;
}
|
下面就HOOK CreateProcessA:
将生成的EXE拖到Xdbg中,定位到CreateProceessA这里,在调用CreateProcessA前有一段汇编代码:
mov edi edi,
push ebp
mov ebp,esp
16进制:8B FF 55 8B EC
HOOK的话肯定要准备一个自定义的函数, call addr ,假如它的地址为12345678,那么我们HOOK的操作就是将上面的命令替换为:JMP 12345678,也就是e9 addr。
解除的HOOK的话就是将替换的字节恢复。
Inline Hook流程
- 构造跳转指令。
- 在内存中找到欲Hook函数地址,并保存欲Hook位置处的前5字节。
- 将构造的跳转指令写入需Hook的位置处。
- 当被Hook位置被执行时会转到自己的流程执行。
- 如果要执行原来的流程,那么取消Hook,也就是还原被修改的字节。
- 执行原来的流程。
- 继续Hook住原来的位置。
代码如下,关键步骤已注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | / / Myhook.cpp
CHOOK::CHOOK()
{
MyFuncaAddress = NULL;
memset(MyOldBytes, 0 , 5 );
memset(MyNewBytes, 0 , 5 );
}
CHOOK::~CHOOK()
{
UnHOOK();
MyFuncaAddress = NULL;
memset(MyOldBytes, 0 , 5 );
memset(MyNewBytes, 0 , 5 );
}
BOOL CHOOK::Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc)
{
HMODULE hModule = GetModuleHandle(pszModuleName);
MyFuncaAddress = (PROC)GetProcAddress(hModule, pszFuncName);
if (MyFuncaAddress = = NULL) {
return FALSE;
}
/ / 读地址 将原来的 5 个字节的数据保存
ReadProcessMemory(GetCurrentProcess(), MyFuncaAddress, MyOldBytes, 5 , 0 );
/ / JMP ADDRESS JMP 123456789 E9
MyNewBytes[ 0 ] = '\xE9' ;
* (DWORD * )(MyNewBytes + 1 ) = (DWORD)pfnHookFunc - (DWORD)MyFuncaAddress - 5 ;
WriteProcessMemory(GetCurrentProcess(), MyFuncaAddress, MyNewBytes, 5 , 0 );
return TRUE;
}
VOID CHOOK::UnHOOK()
{
if (MyFuncaAddress ! = NULL) {
WriteProcessMemory(GetCurrentProcess(), MyFuncaAddress, MyOldBytes, 5 , 0 );
}
return VOID();
}
BOOL CHOOK::ReHook()
{
if (MyFuncaAddress ! = NULL) {
WriteProcessMemory(GetCurrentProcess(), MyFuncaAddress, MyNewBytes, 5 , 0 );
}
return 0 ;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | / / dllmain.cpp : 定义 DLL 应用程序的入口点。
CHOOK MyHookObject;
BOOL
WINAPI
MyCreateProcessA(
_In_opt_ LPCSTR lpApplicationName,
_Inout_opt_ LPSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOA lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
)
{
if (MessageBox(NULL, "是否拦截" , "Notice" , MB_YESNO) = = IDYES)
{
MessageBox(NULL, "程序已拦截" , "Notice" , MB_OK);
}
else
{
MyHookObject.UnHOOK();
CreateProcessA(
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation
);
MyHookObject.ReHook();
}
return true;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MyHookObject.Hook((LPSTR) "Kernel32.dll" , (LPSTR) "CreateProcessA" ,(PROC)MyCreateProcessA);
break ;
case DLL_THREAD_ATTACH:
break ;
case DLL_THREAD_DETACH:
break ;
case DLL_PROCESS_DETACH:
MyHookObject.UnHOOK();
break ;
}
return TRUE;
}
|
效果如下:
IAT Hook
IAT Hook是 Address Hook的一种方式,顾名思义就是通过修改函数的地址进行Hook。
IAT(Import Address Table,输入表)是PE中的一种结构,如图:
再用一张图来理解导入表结构:
需要注意的是:因为IAT具体指某个PE模块的IAT,所以他的作用范围只针对被Hook的模块,且必须在以静态链接的方式调用API时才会被Hook,所以它的作用范围只针对被Hook的模块,且必须以静态链接的方式调用API时才会被Hook,在使用Loadlibrary或GetProcAddress进行动态调用时不受影响。要想对已加载的所有模块起作用,就必须遍历进程内的所有模块,对目标API进行Hook。
现在开始Hook,这里是通过注入来Hook其他的程序,将代码写在DLL里面
为了保证原来的函数不受影响,我们先将要被Hook的函数的地址保存下来:
1 | OldMesageBoxA = (FncMessageBoxA)GetProcAddress(GetModuleHandleA( "user32.dll" ), "MessageBoxA" );
|
然后就是解析PE文件,获取函数导入表中的函数地址表,并替换:
解析PE文件前面已经学习过了,下面过程直接走一遍:
1 2 | PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandleA(NULL);
/ / 当传入参数为NULL时,获取的是PE文件的imagebase,通过类型强制转换就获取到了dos_header
|
1 | PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader - >e_lfanew);
|
pDosHeader->e_lfanew的数据类型为DWORD,前面定义了pDosHeader的数据类型为PIMAGE_DOS_HEADER,要和pDosHeader相加需将pDosHeader类型转换为DWORD,最后再将得到的结果转换为PIMAGE_NT_HEADERS。
获取扩展头
1 2 | PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNtHeader - >OptionalHeader;
/ / 扩展头是NT头的一个成员
|
从扩展头中获取数据目录表中的导入表
获取导入表前,要先找到它的偏移:
1 | DWORD dwImportTableOffset = pOptionalHeader - >DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; / / 获取导入表偏移
|
获取导入表:
1 | PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(DWORD)pDosHeader + dwImportTableOffset;
|
有多个导入表结构,所以要遍历每个导入表:
因为导入表是依靠一个全零的结构来判断结束的,所以我们就采取对比pImportTable->Characteristics为0和pImportTable->FirstThunk为NULL时,来判断结束,然后在其中判断函数地址是否与我们所得到的原函数地址一致,如果一致说明找到了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | DWORD * pFirstThunk;
/ / 遍历导入表结构
while (pImprotTable - >Characteristics && pImprotTable - >FirstThunk ! = NULL)
{
pFirstThunk = (DWORD * )(pImprotTable - >FirstThunk + (DWORD)pDosHeader);
while ( * (DWORD * )pFirstThunk ! = NULL)
{
/ / 如果相当了,就说明当前的数组元素就是我们要找的函数地址表中的函数地址
if ( * (DWORD * )pFirstThunk = = (DWORD)OldMesageBoxA)
{
DWORD oldProtected;
VirtualProtect(pFirstThunk, 0x1000 , PAGE_EXECUTE_READWRITE, &oldProtected);
DWORD dwFuncAddr = (DWORD)MyMessageBoxA;
memcpy(pFirstThunk, (DWORD * )&dwFuncAddr, 4 );
VirtualProtect(pFirstThunk, 0x1000 , oldProtected, &oldProtected);
}
pFirstThunk + + ;
}
pImprotTable + + ;
}
|
最后,为了保证程序的稳定,我们需要构造与被 HOOK 的函数一样结构的函数,同时为了保证原函数功能的正常运行,再定义一个函数指针,在自己的功能执行完成后,调用原来程序正常的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | typedef int
(WINAPI *
FncMessageBoxA)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
FncMessageBoxA OldMesageBoxA = NULL;
int
WINAPI MyMessageBoxA(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType)
{
SECURITY_ATTRIBUTES psa = { 0 };
SECURITY_ATTRIBUTES tsa = { 0 };
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcessA(EXE_PATH, NULL, &psa, &tsa, false, 0 , NULL, NULL, &si, &pi);
return 0 ;
}
|
要HOOK的API是MessageBoxA (X32),写一个简单的程序:
1 2 3 4 5 6 7 | int main() {
system( "pause" );
MessageBoxA( 0 , 0 , 0 , 0 );
system( "pause" );
return 0 ;
}
|
要达到的目的是当测试程序运行时,注入DLL,Hook住MessageBoxA,使其指向CreateProcessA api:
正常运行:
Hook后:
VirtualTable(虚函数) Hook
在代码编译为程序后,虚函数表就是一个固定的表了,它位于PE的.data段。在对虚函数表进行Hook时,虽然原理也是查找原函数的位置,修改页面属性,写入Detour函数这样的过程,但是虚函数有些特殊。
同样是Hook地址,它不能像IAT Hook那样直接定义一个函数来替换目标函数,而必须把它定义为类的成员函数。我们知道面向对象的三要素:封装、继承、多态 。在多态里有类特殊的是虚函数(以virtual修饰), 32位系统下,对象里有4个字节保存虚表的数组,其值为每一项虚函数的地址。
针对虚函数的HOOK就是通过保存对象中的虚表的值,针对每一项进行替换。
这次虚函数的Hook就在程序本身执行了,dll注入的方式需要对程序进行逆向分析,暂时放一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | class MyClass
{
public:
MyClass();
~MyClass();
virtual void print ();
private:
};
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
void MyClass:: print ()
{
printf( "hello\r\n" );
}
void Myfunc() {
MessageBoxA(NULL, "hello" , "title" , NULL);
}
int main() {
MyClass obj;
MyClass& vobj = obj;
vobj. print ();
/ / 寻找虚表指针,虚表指针通常情况下位于对象的头 4 字节上
int nAddr = * ( int * )&obj;
/ / 更改内存属性
DWORD dwOldProtect = 0 ;
VirtualProtect((void * )nAddr, 0x100 , PAGE_EXECUTE_READWRITE,&dwOldProtect);
/ / 将自己的函数地址替换过去,前面说到了数组的值就是虚函数的地址,我们写的测试例子中就写了一个虚函数,所以其地址就是第一个值
( * ( int * )nAddr) = ( int )Myfunc;
VirtualProtect((void * )nAddr, 0x100 , dwOldProtect,&dwOldProtect);
vobj. print ();
system( "pause" );
}
|
效果如下:
HotPatch (热补丁)Hook
热补丁Hook是微软提供的一种安全的Hook的机制,也是将函数开头修改为jmp指令,跳转到自定义的函数地址执行,和IAT Hook类似却又有不同。
以IAT Hook中的测试程序为例:
我们可以看到CreateProcessA函数的首字节为 mov edi,edi(88 FF),这句汇编意思就是将edi的值放入edi,实际上并没有什么用。
我们还看到在这个API上边有大段的int3 中断。这就给了我们一种新的Hook思路,即将前两个字节改为短跳转指令(EB E9),使其跳到函数上边五字节处,这五个字节的int3中断实际上就是一段空闲空间:
然后再将这五个字节改为长跳转指令(E9 xxxxxxxx)。这样,即使Hook失败,也不影响函数的继续执行。
这样,hook函数的时候,先是一个短跳跳到自定义的函数然后执行。如果要恢原流程,则找到函数地址并+2,直接跳过E8 F9 ,从push ebp开始执行。
然后开始写代码,有了其他Hook方式的经验,这次直接写一个通用的Hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | / / dllmain.cpp : 定义 DLL 应用程序的入口点。
int
WINAPI MyFunc(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType)
{
SECURITY_ATTRIBUTES psa = { 0 };
SECURITY_ATTRIBUTES tsa = { 0 };
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcessA(EXE_PATH, NULL, &psa, &tsa, false, 0 , NULL, NULL, &si, &pi);
return 0 ;
}
BOOL HOOK(const char * szModuleName, const char * szFuncName, PROC pfnFunc)
{
BYTE ShortJmp[ 2 ] = { 0xEB , 0xF9 }; / / 短跳
BYTE LongJmp[ 5 ] = { 0xE9 , 0 , }; / / 长跳
DWORD dwOldProtect = 0 ;
FARPROC pOldFuncAddr = (FARPROC)GetProcAddress(GetModuleHandleA(szModuleName), szFuncName); / / 找原函数地址
VirtualProtect((LPVOID)((DWORD)pOldFuncAddr - 5 ), 7 , PAGE_EXECUTE_READWRITE, &dwOldProtect); / / 修改页保护属性
DWORD dwAddr = ((DWORD)pfnFunc - (DWORD)pOldFuncAddr);
* (DWORD * )(LongJmp + 1 ) = dwAddr;
memcpy((LPVOID)((DWORD)pOldFuncAddr - 5 ), LongJmp, 5 );
memcpy(pOldFuncAddr, ShortJmp, 2 );
VirtualProtect((LPVOID)((DWORD)pOldFuncAddr - 5 ), 7 , dwOldProtect, &dwOldProtect); / / 还原页保护属性
return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HOOK( "User32.dll" , "MessageBoxA" , (PROC)MyFunc);
break ;
case DLL_THREAD_ATTACH:
break ;
case DLL_THREAD_DETACH:
break ;
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
|
还是以这个测试程序为例:
将生成的dll注入,hook后,当执行到MessageboxA时会劫持住它原来流程去执行CreateProcessA,如图弹出了notepad.exe:
补充:并不是所有的api都能使用HotPatch的方式进行Hook,比如CreateProcess,当然或许是我代码写错了:)
VEH异常(基于软件断点) Hook
简单介绍一下windows的异常机制:
Intel在386开始的IA-32家族处理器中引入了异常和中断。中断是指外部硬件设备或异步事件引发的,而异常是由内部事件产生的,又可分为故障,陷阱和终止三类。故障和陷阱是可恢复的,终止是不可恢复的,如果出现了了终止异常,则需要重启操作系统解决。
Windows中主要的异常处理机制:VEH、SEH、C++EH。
SEH:结构化异常处理。就是平时用的__try
__finally
__try
__except
,是对c的扩展。
VEH:向量异常处理。一般来说用AddVectoredExceptionHandler
去添加一个异常处理函数,可以通过第一个参数决定是否将VEH函数插入到VEH链表头,插入到链表头的函数先执行,如果为1,则会最优先执行。
C++EH是C++提供的异常处理方式,执行顺序将排在最后。
在用户模式下发生异常时,异常处理分发函数在内部会先调用遍历 VEH 记录链表的函数, 如果没有找到可以处理异常的注册函数,再开始遍历 SEH 注册链表。
主要区分一下SEH和VEH:
- 优先级:VEH优先SEH调用。如果VEH处理了异常,则SEH无法处理该异常。
- 作用范围:SEH机制基于线程,VEH基于进程。就是说同一进程里的A线程无法用SEH捕获B线程的异常,而VEH在整个进程范围内都有效,可以捕获和处理所有线程产生的异常。
- 注册机制:SEH的相关信息主要保存在栈中,而且后注册的回调函数总是处于SEH链的前端。当异常发生时,异常总是由内层回调函数优先处理,只有在内存回调函数不处理异常时,外部回调函数才有机会获得控制权。而VEH的相关信息保存在独立的链表中,在注册VEH时可以指定回调函数是位于VEH链表的首部还是尾部。
- VEH不需要栈展开。VEH的实现不依赖栈,所以VEH在调用VEH回调函数前不需要栈展开,只有一次被调用的机会。而SEH的注册和使用依赖于函数调用的栈帧,在调用SEH回调函数时会涉及栈展开的问题,SEH由两次被调用的机会。
然后开始写代码,主要流程如下:
- 1.注册一个VEH异常回调
- 2.找到一个API
- 3.API头部第一个字节改为CC
- 4.当API被调用,会执行CC,然后触发INT3异常,接着异常被VEH回调接管,最后执行自定义函数
在调试模式下,当MessageBox被调用时,会触发int3异常:
将项目编译为PE文件再次运行,已经成功Hook:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | using namespace std;
struct EXCEPTION_HOOK {
ULONG_PTR ExceptionAddress;
UCHAR oldCode;
};
list <EXCEPTION_HOOK> HookInfo;
/ / 1. 注册一个VEH回调
/ / 2. 找到一个API
/ / 3.API 头部第一个字节改为CC
/ / 4. 当这个API被调用,就会执行到CC,然后出发 int 3 异常,然后异常被VEH回调接管,然后为所欲为。
LONG Handler(
struct _EXCEPTION_POINTERS * ExceptionInfo
)
{
for ( list <EXCEPTION_HOOK>::iterator i = HookInfo.begin(); i ! = HookInfo.end(); + + i)
{
if (i - >ExceptionAddress = = (ULONG_PTR)ExceptionInfo - >ExceptionRecord - >ExceptionAddress && ExceptionInfo - >ExceptionRecord - >ExceptionCode = = EXCEPTION_BREAKPOINT)
{
ULONG OldProtect;
BOOL bRet = VirtualProtect((PVOID)ExceptionInfo - >ContextRecord - >Eip, 1 , PAGE_EXECUTE_READWRITE, &OldProtect);
if (!bRet)
{
cout << "属性修改失败" << endl;
return FALSE;
}
* (UCHAR * )ExceptionInfo - >ContextRecord - >Eip = i - >oldCode;
/ / MessageBoxA 已经调用 cc中段 stack 中保存了传入的数据
const char * szStr = "hello" ;
* (DWORD * )(ExceptionInfo - >ContextRecord - >Esp + 0x8 ) = (DWORD)szStr;
VirtualProtect((PVOID)ExceptionInfo - >ContextRecord - >Eip, 1 , OldProtect, &OldProtect);
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
BOOL Hook(ULONG_PTR ulAddress) {
EXCEPTION_HOOK ExceptionInfo;
ExceptionInfo.ExceptionAddress = ulAddress;
for ( list <EXCEPTION_HOOK>::iterator i = HookInfo.begin(); i ! = HookInfo.end(); + + i)
{
if (i - >ExceptionAddress = = ulAddress)
{
cout << "HOOK success" << endl;
return FALSE;
}
}
ULONG OldProtect;
BOOL bRet = VirtualProtect((PVOID)ulAddress, 1 , PAGE_EXECUTE_READWRITE, &OldProtect);
if (!bRet)
{
cout << "属性修改失败!" << endl;
return FALSE;
}
ExceptionInfo.oldCode = * (UCHAR * )ulAddress;
* (UCHAR * )ulAddress = 0xCC ;
HookInfo.push_back(ExceptionInfo);
BOOL bRet2 = VirtualProtect((PVOID)ulAddress, 1 , OldProtect, &OldProtect);
return TRUE;
}
int main()
{
PVOID pVeh = AddVectoredExceptionHandler( 1 , (PVECTORED_EXCEPTION_HANDLER)Handler);
if (pVeh = = NULL)
{
cout << "AddVectoredExceptionHandler Failed" << endl;
system( "pause" );
return - 1 ;
}
Hook(ULONG_PTR(MessageBoxA));
MessageBoxA( 0 , 0 , 0 , 0 );
return 0 ;
}
|
如果要实现Hook其他程序,只需将函数写到dll文件中然后进行注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | / / dllmain.cpp : 定义 DLL 应用程序的入口点。
size_t MessageBoxAddr = (size_t)GetProcAddress(GetModuleHandleA( "user32.dll" ), "MessageBoxA" );
struct EXCEPTION_HOOK
{
ULONG_PTR ExceptionAddress;
UCHAR OldCode;
};
EXCEPTION_HOOK HookInfo;
LONG NTAPI Handler(
struct _EXCEPTION_POINTERS * ExceptionInfo
) {
if (ExceptionInfo - >ExceptionRecord - >ExceptionCode = = EXCEPTION_BREAKPOINT)
{
if ((ULONG_PTR)ExceptionInfo - >ExceptionRecord - >ExceptionAddress = = HookInfo.ExceptionAddress)
{
const char * szSrt = "hello" ;
* (DWORD * )(ExceptionInfo - >ContextRecord - >Esp + 0x8 ) = (DWORD)szSrt;
ExceptionInfo - >ContextRecord - >Eip + = 2 ;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
}
VOID SetHook(ULONG_PTR Address)
{
DWORD dwOldProtect = 0 ;
VirtualProtect((LPVOID)Address, 1 , PAGE_EXECUTE_READWRITE, &dwOldProtect);
HookInfo.ExceptionAddress = Address;
HookInfo.OldCode = * (UCHAR * )Address;
* (UCHAR * )Address = 0xCC ;
VirtualProtect((LPVOID)Address, 1 , dwOldProtect, &dwOldProtect);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AddVectoredExceptionHandler( 1 , Handler);
SetHook(MessageBoxAddr);
break ;
case DLL_THREAD_ATTACH:
break ;
case DLL_THREAD_DETACH:
break ;
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
|
[课程]Android-CTF解题方法汇总!
最后于 2021-12-23 11:10
被soloz编辑
,原因: