最近在学习逆向核心,在论坛也发了几篇帖子说说自己的经验,帮助自己巩固知识,也方便了大家。
如果帖子中有什么疏漏甚至不对的地方,请大牛们指出,我会积极改正的!
废话不多说,还是我【Miss丿小沫】,上教程!
-----------------------------------------------------------------------------------------------------
我在DLL注入里面也提到了API钩取这一概念,涉及的知识点比较多,也上了难度,我准备细致的介绍,讲解。
我能力有限,如果有错误的地方,欢迎大家指正!
-----------------------------------------------------------------------------------------------------
钩取(Hook)是一种截取信息(比如SetWindowsHook()获取键盘记录),更改程序执行流,添加新功能(DLL注入)的技术。
列一下流程:
①:使用反汇编器(IDA)或者调试器(OD)了解程序的结构和工作原理
②:开发Hook代码
③:灵活操作可执行文件和进程内存,执行Hook
其中,钩取API的技术就叫做API钩取,它将钩取API,改变程序执行流,达到某些我们想要的目的。
关于API的概念就不赘述了,(百度百科:http://baike.baidu.com/link?url= ... 9n63O8bV7We25xVyz-7)
API封装在DLL中,程序运行时就会加载各种DLL(kernel32.dll,ntdll.dll等),上节DLL注入中就可以看出来了
-----------------------------------------------------------------------------------------------------
1.jpg (68.02 KB, 下载次数: 0)
2016-7-15 14:24 上传
以notepad为例,要打开一个文件,notepad会发生如下调用:
1.jpg (21.08 KB, 下载次数: 0)
2016-7-15 14:26 上传
程序正常调用API:
1.jpg (28.71 KB, 下载次数: 0)
2016-7-15 14:28 上传
当我们HookAPI后:
1.jpg (55.36 KB, 下载次数: 0)
2016-7-15 14:30 上传
利用上节的知识,将hook.dll注入目标进程后,hook.dll将钩取目标API,并执行我们自己的代码,改变程序执行流,这样,每当程序调用目标API后,都会经由我们自己的代码
下面给出一张技术图表:
1.jpg (85.31 KB, 下载次数: 0)
2016-7-15 14:33 上传
①:【方法】API钩取一般都采用动态方法
②:【位置】指出API钩取时操作哪部分
<1>:IAT 将其内部的API函数地址修改为Hook函数地址,实施起来简单,但无法钩取不在IAT而在程序中动态加载的DLL中的API。(关于IAT的详细讲解,大家可以参考驿站的【PE文件格式解析】)
<2>:代码 *.dll映射到内存是,从中查找目标API地址,并直接修改代码,应用广泛。
<3>:EAT 并不常用,不在赘述
③:【技术】从表中也可以看出,API钩取可分为两种方法:调试法和注入法(代码注入和DLL注入)
调试法:
调试通过向目标进程附加调试器钩取API,调试器拥有被调试进程的所有权限
注入法:
DLL注入:
(详细见我上一章【VC实现DLL注入】)在Dll中创建Hook代码和设置代码,并在DllMain()中调用,注入的同时也就完成了Hook
代码注入:
(我准备再开一章专讲代码注入)
④:【API】列举了几个各自技术要用到的API
-----------------------------------------------------------------------------------------------------
大致了解完枯燥的理论后,开始实例讲解!
利用【调试法】向notepad附加调试器,尝试钩取WriteFile()API
在这之前,我们有必要了解一下调试器。
调试器工作原理:
被调试进程注册后,每当被调试进程发生调试事件(DebugEvent)时,OS就会暂停进程执行,并汇报给调试器,当调试器处理完相关事件后,使被调试程序继续执行。
一张图说明调试器的异常处理(EXCEPTION):
1.jpg (61.63 KB, 下载次数: 0)
2016-7-15 15:01 上传
列举调试事件(DebugEvent)(共9个):
EXCEPTION_DEBUG_EVENT
CREATE_THREAD_DEBUG_EVENT
CREATE_PROCESS_DEBUG_EVENT
EXIT_THREAD_DEBUG_EVENT
EXIT_PROCESS_DEBUG_EVENT
LOAD_DLL_DEBUG_EVENT
UNLOAD_DLL_DEBUG_EVENT
OUTPUT_DEBUG_STRING_EVENT
RIP_EVENT
我们主要关注EXCEPTION_DEBUG_EVENT,列举出与其相关的异常事件列表:
EXCEPTION_ACCESS_VIOLATION
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_BREAKPOINT
EXCEPTION_DATATYPE_MISALIGNMENT
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_FLT_DIVIDE_BY_ZERO
EXCEPTION_FLT_INEXACT_RESULT
EXCEPTION_FLT_INVALID_OPERATION
EXCEPTION_FLT_OVERFLOW
EXCEPTION_FLT_STACK_CHECK
EXCEPTION_FLT_UNDERDLOW
EXCEPTION_ILLEGAL_INSTRUCTION
EXCEPTION_IN_PAGE_ERROR
EXCEPTION_INT_DIVIDE_BY_ZERO
EXCEPTION_INT_OVERFLOW
EXCEPTION_INVALID_DISPOSITION
EXCEPTION_PRIV_INSTRUCTION
EXCEPTION_SINGLE_STEP
EXCEPTION_STACK_OVERFLOW
(FUCK!我想骂人!刚写好的帖子渣B浏览器崩溃了,结果一堆都没了。。从头开始吧。。。)
我们只关心EXCEPTION_BREAKPOINT这个事件,因为要用int3下断点(断点的IA-32指令是0xCC。),通过这个事件反馈给调试器。
-----------------------------------------------------------------------------------------------------
下面讲解一下整个调试流程:
①:对目标进程附加调试器,开始调试
②:下断点,将目标API函数的起始地址第一个字节设置为0xCC
③:程序调用目标API,断下,反馈信息给调试器
④:执行我们自己的代码
⑤:恢复0xCC,使API正常执行
⑥:再次下断,将目标API函数的起始地址第一个字节设置为0xCC
这就是基本的流程了
-----------------------------------------------------------------------------------------------------
OK,开始真正的实战,
我们对notepad进行API钩取,在其保存文件时,将文本中所有小写字母替换成大写字母。
第一步:
先来用OD分析一下notepad.exe的执行流
(这利用的是XP的notepad.exe,WIN7的貌似有保护,我OD调试后立即崩溃)
notepad.zip
(33.41 KB, 下载次数: 16)
2016-7-15 16:38 上传
点击文件名下载附件
下载积分: 驿站币 -1
(大家要掌握一定的OD基础)
好,OD附加notepad.exe
1.jpg (331.11 KB, 下载次数: 0)
2016-7-15 15:33 上传
我们的目标是WriteFile()API,
OD代码区右键->Search For->name in all modules
查找WriteFile()
双击 export WriteFile()进入代码区
1.jpg (116.54 KB, 下载次数: 0)
2016-7-15 16:32 上传
在 752b75d5 处F2下断,F9运行
回到notepad.exe,输入我们的内容,然后保存
1.jpg (28.18 KB, 下载次数: 0)
2016-7-15 16:40 上传
程序断在了752b75d5,
查看栈
1.jpg (40.35 KB, 下载次数: 0)
2016-7-15 18:56 上传
缓冲区地址是2D9968,存放在ESP + 8处
1.jpg (80.64 KB, 下载次数: 0)
2016-7-15 18:57 上传
在Dump中查看,发现就是我们的文本
我们用调试方法来APIHook,在WriteFile()起始地址设置int3后,此时被调试者的EIP(存储着CPU下一条指令)是多少呢?
EIP = 752b75d5 + 1 = 752b75d6,我们在WriteFile()起始地址设置int3后,EIP值+1,等遇到Breatpoint异常后,会反馈给调试器处理,等我们处理完之后,EIP会恢复WriteFile()起始值,
但这里还有一个问题,如果执行流只返回到WriteFile()起始地址,再次遇到int3还是会返回异常,就进入了无限死循环!为此,我们还应该恢复0xCC处的原始值,以保证API得正常执行。
-----------------------------------------------------------------------------------------------------
好,这一系列问题分析完之后,就可以动手写代码啦!
界面还是上次DLL注入的界面,只是多加了一个APIHook按钮。
1.jpg (6.59 KB, 下载次数: 0)
2016-7-15 19:09 上传
void CCodeInjectDlg::OnBnClickedButtonApihook()
{
CString str;
//获取进程PID
GetDlgItemText(IDC_EDIT_PID,str);
dwPID = FindPID(str);
//附加调试器
if(!DebugActiveProcess(dwPID))
{
AfxMessageBox(_T("附加调试器失败!"),MB_ICONSTOP | MB_OK);
return ;
}
//进入调试器循环
DebugLoop();
}复制代码
这段代码很简单,就是获取进程ID,附加调试器,进入循环,处理来自进程的各种消息
下面着重讲解一下,DebugLoop()
刚刚讲到OnBnClickedButtonApihook()这个函数,这次我们修改一下这个函数
void CCodeInjectDlg::OnBnClickedButtonApihook()
{
HANDLE hThread = CreateThread(NULL,0,ThreadProc,(LPVOID)this,0,NULL);
CloseHandle(hThread);
}复制代码
把主要代码(DebugLoop)放在了ThreadProc()里面,因为DebugLoop()这个循环会阻塞主程序。
ThreadProc()函数:
DWORD WINAPI ThreadProc(LPVOID lpDlg)
{
CCodeInjectDlg* pDlg = (CCodeInjectDlg*)lpDlg;
CString str;
//获取进程PID
pDlg->GetDlgItemText(IDC_EDIT_PID,str);
pDlg->dwPID = pDlg->FindPID(str);
//附加调试器
if(!DebugActiveProcess(pDlg->dwPID))
{
AfxMessageBox(_T("附加调试器失败!"),MB_ICONSTOP | MB_OK);
return 0;
}
//进入调试器循环
pDlg->DebugLoop();
return 0;
}复制代码
----------------------------------------------------------------------------------
下面来看一下DebugLoop()函数:
void CCodeInjectDlg::DebugLoop()
{
DEBUG_EVENT DE;
//等待调试事件
while(WaitForDebugEvent(&DE,INFINITE))
{
switch(DE.dwDebugEventCode)
{
//附加到调试进程
case CREATE_PROCESS_DEBUG_EVENT:
CreateProcessDebugEvent(&DE);
break;
//返回异常
case EXCEPTION_DEBUG_EVENT:
ExceptionDebugEvent(&DE);
break;
//进程退出
case EXIT_PROCESS_DEBUG_EVENT:
return ;
}
ContinueDebugEvent(DE.dwProcessId,DE.dwThreadId,DBG_CONTINUE);
}
}复制代码
WaitForDebugEvent()就是暂停进程,等待被调试者反馈的调试事件了。
DEBUG_EVENT结构:
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;
当程序接收到被调试者反馈的调试事件后就将调试事件设置为DEBUG_EVENT的第一个参数dwDebugEventCode
前面也说过了,共有9种调试事件,dwDebugEventCode就是这9种中的一种,根据事件的种类,也会相应设置DEBUG_EVENT.u中的结构体成员
ContinueDebugEvent()就是使暂停的进程继续执行。
----------------------------------------------------------------------------------
当调试器附加到进程上后,调试事件为CREATE_PROCESS_DEBUG_EVENT,并调用CreateProcessDebugEvent(&DE)函数,该函数的作用就是获取信息(WriteFile()起始地址首字节指令),并下int3断点
当我们保存文件时,就会触发断点,调试事件为EXCEPTION_DEBUG_EVENT,并调用ExceptionDebugEvent(&DE)函数,该函数就取消int3断点,改变代码的执行流,替换小写字母为大写字母,并且再次下int3断点
----------------------------------------------------------------------------------
CreateProcessDebugEvent(&DE)函数:
void CCodeInjectDlg::CreateProcessDebugEvent(LPDEBUG_EVENT lpDE)
{
BYTE bInt3 = 0xCC;
//获取WriteFile()函数地址
lpThread = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"),_T("WriteFile"));
//下断点,更改WriteFile()起始地址为0xCC,并备份原内容
memcpy(&CPDbg_Info,&lpDE->u.CreateProcessInfo,sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(CPDbg_Info.hProcess,lpThread,&bOriByte,sizeof(BYTE),NULL);
WriteProcessMemory(CPDbg_Info.hProcess,lpThread,&bInt3,sizeof(BYTE),NULL);
}复制代码
获取WriteFile()起始地址(DLL注入章节中已经讲解了)
CREATE_PROCESS_DEBUG_INFO结构体:
typedef struct _CREATE_PROCESS_DEBUG_INFO {
HANDLE hFile;
HANDLE hProcess;
HANDLE hThread;
LPVOID lpBaseOfImage;
DWORD dwDebugInfoFileOffset;
DWORD nDebugInfoSize;
LPVOID lpThreadLocalBase;
LPTHREAD_START_ROUTINE lpStartAddress;
LPVOID lpImageName;
WORD fUnicode;
} CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;
结构体里面存储着关于进程的信息
ReadProcessMemory()用来获取WriteFile()起始地址首字节指令,用来取消断点是恢复
WriteProcessMemory()就是下int3断点的
----------------------------------------------------------------------------------
ExceptionDebugEvent(&DE)函数:
void CCodeInjectDlg::ExceptionDebugEvent(LPDEBUG_EVENT lpDE)
{
PEXCEPTION_RECORD per = &lpDE->u.Exception.ExceptionRecord;
CONTEXT cText;
DWORD dwBuf_Num = 0,dwBuf_Buf = 0;
PBYTE pbBuf = NULL;
BYTE bInt3 = 0xCC;
//断点int3,并且当前地址为WriteFile()
if(per->ExceptionCode == EXCEPTION_BREAKPOINT && per->ExceptionAddress == lpThread)
{
//恢复0xCC
WriteProcessMemory(CPDbg_Info.hProcess,lpThread,&bOriByte,sizeof(BYTE),NULL);
//获取线程上下文
cText.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(CPDbg_Info.hThread,&cText);
//获取WriteFile()第2、3参数
ReadProcessMemory(CPDbg_Info.hProcess,(LPVOID)(cText.Esp + 0x8),&dwBuf_Buf,sizeof(DWORD),NULL);
ReadProcessMemory(CPDbg_Info.hProcess,(LPVOID)(cText.Esp + 0xC),&dwBuf_Num,sizeof(DWORD),NULL);
//分配临时缓冲区
pbBuf = (PBYTE)malloc(dwBuf_Buf + 1);
memset(pbBuf,0,dwBuf_Buf + 1);
//复制WriteFile()缓冲区到临时缓冲区
ReadProcessMemory(CPDbg_Info.hProcess,(LPVOID)dwBuf_Buf,pbBuf,dwBuf_Num,NULL);
//小写转大写(ASCII)
for(int i = 0;i < dwBuf_Num;i ++)
{
//判断是否是小写字母
if(pbBuf[i] >= 0x61 && pbBuf[i] <= 0x7A)
{
pbBuf[i] -= 0x20;
}
}
//变换后缓冲区写到WriteFile()缓冲区
WriteProcessMemory(CPDbg_Info.hProcess,(LPVOID)dwBuf_Buf,pbBuf,dwBuf_Num,NULL);
//释放缓冲区
free(pbBuf);
//修改线程上下文
cText.Eip = (DWORD)lpThread;
SetThreadContext(CPDbg_Info.hThread,&cText);
//继续运行被调试进程
ContinueDebugEvent(lpDE->dwProcessId,lpDE->dwThreadId,DBG_CONTINUE);
Sleep(0);
//再次写入int3断点
WriteProcessMemory(CPDbg_Info.hProcess,lpThread,&bInt3,sizeof(BYTE),NULL);
}
}复制代码
PEXCEPTION_RECORD里面储存着异常信息,我们判断该异常是不是断点并且是断在WriteFile()处的
首先我们用开始保存的原始信息恢复0xCC断点
CONTEXT线程上下文里面存储着有关线程的信息(CPU中各寄存器的值)
typedef struct _CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
} CONTEXT;
(具体分析见前面OD分析部分)
下面用WriteProcessMemory()获取WriteFile()的两个参数,第一个参数(ESP + 0x8)里面保存着缓冲区的地址,第二个参数(ESP + 0xC)就是缓冲区大小
接下来就是复制WriteFile()缓冲区数据到临时缓冲区,并转换大小写后,回写到WriteFile()缓冲区,并且将EIP值修改为WriteFile()起始地址,保证API正常运行,ContinueDebugEvent()使进程继续执行,
Sleep(0)呢,会释放当前线程的剩余时间片。。BALABALABALA。。。。如果没有Sleep(0)的话,有时会发生崩溃
最后再次写入int3断点,以保证下次保存时再次钩取API
----------------------------------------------------------------------------------
好了,主要代码就结束了,不知道当大家看到自己写出来的API HOOK有没有一点小激动呢?
我是【Miss丿小沫】,下次定会带来更好的讲解!