最近在学习逆向核心,在论坛也发了几篇帖子说说自己的经验,帮助自己巩固知识,也方便了大家。

如果帖子中有什么疏漏甚至不对的地方,请大牛们指出,我会积极改正的!

废话不多说,还是我【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丿小沫】,下次定会带来更好的讲解!

linux vc 调试方法,VC实现【API钩取】【调试法】附加调试器相关推荐

  1. android的反调试方法,Android平台融合多特征的APP反调试方法与流程

    本发明涉及Android平台融合多特征的APP反调试方法,属于计算机与信息科学技术领域. 背景技术: 应用程序本身并不具备反调试的功能,但是动态调试是动态分析应用逻辑.动态脱壳等攻击方式所采取的必要手 ...

  2. 逆向工程核心原理读书笔记-API钩取之IE浏览器连接控制

    我们通过一个示例来练习钩取IE8的InternetConnect函数,用IE8连接指定网站时,使之连接到另一个网站.和以前钩取CreateProcess不同,这次我们钩取更低级的ZwResumeThr ...

  3. 逆向工程核心原理读书笔记-API钩取之隐藏进程(一)

    简评: 整体看了下代码,其实就是应用层的inline hook, 钩子勾住ntdll.dll里面的ZwQuerySystemInformation函数, xp环境测试成功了,win7测试失败了,不知道 ...

  4. 逆向工程核心原理读书笔记-API钩取之记事本小写转大写

    我们通过一个示例来练习钩取Notepad.exe的WriteFile,保存文件时将小写字母全部转换为大写字母.下面我们先测试一下代码. 运行notepad.exe并查看PID. 在命令行窗口中输入命令 ...

  5. 逆向工程核心原理读书笔记-API钩取之隐藏进程(二)

    上一篇文章我们实现的隐藏进程如果重新打开任务管理器或者被隐藏的进程就没有隐藏的效果了.为了弥补这个问题,我们不仅需要钩取当前运行的所有进程,还要钩取将来运行的所有进程.由于所有的进程都是由父进程使用C ...

  6. 逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

    我们通过一个示例来练习向计算机进程插入用户的DLL文件,钩取负责向计算器显示文本的SetWindowTextW,使得计算器中显示中文数字而不是原来的阿拉伯数字.钩取前后的原理图如下所示. 下面我们先测 ...

  7. 4分钟告诉你python是什么_Python调试方法有哪些,3分钟告诉你Python调试命令怎么用...

    程序员能一次写完程序并正常运行的概率很小,基本不超过1%.总会有各种各样的bug需要修正.有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值 ...

  8. plc.单片机的串口通讯的调试方法——通过虚拟串口工具和串口助手来调试串口

    加一个,串口助手和虚拟串口工具统合使用的链接,可以一起看哦~https://blog.csdn.net/qq_32278309/article/details/101384003 博主是新入坑的新人, ...

  9. idea 断点线程_在IntelliJ IDEA中多线程并发代码的调试方法

    通常来说,多线程的并发及条件断点的debug是很难完成的,或许本篇文章会给你提供一个友好的调试方法.让你在多线程开发过程中的调试更加的有的放矢. 我们将通过一个例子来学习.在这里,我编写了一个多线程程 ...

最新文章

  1. Nginx rewrite正则匹配重写
  2. 润乾报表实现组内排序报表及改进
  3. PKG_CONFIG_PATH 、LD_LIBRARY_PATH、PATH三个的作用
  4. 初学python之路-day18
  5. 开正交时候卡顿_王者荣耀:不管用WiFi还是流量都是卡顿咋办!4个办法让你变流畅...
  6. 1.15 Python基础知识 - 函数
  7. 关于小程序·云开发峰会,你想get的干货全在这了!
  8. 第六届中国开源年会(COSCon'21)开心开源精彩收官
  9. 将游戏成绩传到排名页面html,用野狗开发实时游戏排行榜
  10. 复合消隐信号的作用_南大《AFM》:可拉缩、粘合、导电的双信号柔性彩色薄膜...
  11. java移动接口发短信_天天都会写接口,但它的用途和好处有多少人能说得清楚?...
  12. linux文件编码无法修改,在Linux系统中修改文本的字符编码的方法
  13. 在html中直接使用%3c php%3e,HTB-靶机-Calamity
  14. 存储过程(Stored Procedure)
  15. 使用Lua GD库动态生成验证码图片(2)
  16. 如何查看自己电脑显卡对应的cuda版本
  17. openfeign集成Hystrix的备选方案处理
  18. 形状因子对禁带的调控
  19. C#支付宝支付接口H5版(手机网页支付)
  20. 约数的和及约数的个数

热门文章

  1. node+express+mock
  2. 解决win10笔记本内置麦克风不能用的历程和方法
  3. 淘宝开放平台开发指南之熟悉API族
  4. pyHook pyHook3 区别_成熟男人和幼稚男区别,男人不成熟的5个特征
  5. loopy()代表什么意思,怎么用
  6. 紫光旗下企业正和美光科技谈判存储技术授权及设立合资公司
  7. Functionlan通过星际文件系统免费使用云应用程序
  8. 在linux安装java过程_挑战Java在Linux上安装过程分享
  9. 计算机网络03:数据链路层
  10. java 电子围栏_电子围栏