首页
社区
课程
招聘
萌新逆向学习笔记——API钩取
发表于: 2020-8-26 21:21 4259

萌新逆向学习笔记——API钩取

2020-8-26 21:21
4259

著名的迪奥布兰多先生曾说过“人类是有极限的。”

确实如此,但也正因为我们不能飞,所以才造出了飞机。正因为我们跑的不快,所以才造出了汽车。人类确实存在极限,但人的学习能力却是无限的。而作为一个正在入门逆向萌新的来说,正确认识自身的极限和意识到学习的无穷是很重要的。

为什么这么说呢。回想几个星期前,笔者还在苦苦做论坛上总结的120个CM。每当破解并分析了一个CM感到满足的同时,也为自己耗费长时间去破解分析CM的行为是否值得而感到疑惑,似乎就在原地踏步,而没有学到新的知识。

后来我才意识到,这正是目前自身的极限。缺乏C++编程知识,缺乏系统的API知识,缺乏常规破解手段的知识等等。所以笔者才决定突破自身的极限,学习《逆向工程核心思想》一逆向书籍,才有了今天这篇文章。

可这谈何容易呢?要想突破极限就必须付出相应的努力,去提高自己极限的程度。笔者必须学习C++,必须跟着书籍编写win32程序。当我们知识储备扩展后,极限的程度也就高了,而只有不断提高自身的极限,或许才能成为真正的“大师”吧。

本篇文章从原理和实现过程两个角度总结书中API钩取一章节,笔者也是萌新。因此难免会有令人困惑和误解甚至错误的地方。倘若存有疑问请务必留言指教。

为了读者能有更好的阅读体验,最好但不一定必要具备以下知识和工具:

再次提醒一下,就算读者没有C++编程基础,也可浏览此文到原理部分,实践部分若无编程基础可忽略不看。当熟知原理以及记住几个关键的函数过后,全文核心内容基本可算基本掌握。

我们为什么要做API钩取,它有什么作用。在描述原理之前,这是我们必须要弄清楚的问题。如同我们去逆向分析软件,都会问自己为什么会这么做。可能是兴趣使然,可能是好玩,也可能是为了钻研等等。为什么很重要,因为它是我们唯一的学习动力。

API钩取它让我们能够直接修改程序中的流程或代码。说的通俗易懂一点,它可以让我们在别人的程序里为所欲为。

例如我们可以直接修改弹窗的标题或内容;又如可以改变我们输入。只要我们知道要修改的地址,我们就可以让他断下来,然后进行各种操作,来达到我们的目的。如果用过IDA或者olldbg的读者一定会发现,这不正是这些调试器的功能吗?

6

那到底要怎么做呢?为什么别人写的程序本来好好的却能够突然停下来让你操作,这难道是什么魔法吗?

然而并不是,众所周知Window的编程是函数编程,说的明白一点,程序员在写诸如EXE的Window程序时,都会去使用微软早已做好的函数。而API钩取,也正是使用微软提供的函数去拦截程序使用微软的函数(有点拗口)。有点以毒攻毒的味道呢。

7

当一个程序被注册了调试程序,他们关系就会发生3600°的变化。本来互不干扰的俩,被注册之后只要产生了调试事件,它就会过问调试自己的程序,把自己的控制权交给调试者。而我们正是利用这个原理,注册成为别人的程序的调试者,再促使被调试程序产生断点调试事件,来获得其生死大权。

如何促使被调试程序产生断点调试事件呢?这里有两个关键点:

结合这两点,我们可以在注册成功的时候,改写相应的地址为CC,使其下次遇到CC时触发中断调试事件。

8

这样我们接收CC的中断调试事件后便可更改被调试程序的相关数据。

实践步骤需要有C++的基础知识,否则可能会难以下咽,没有C++的基础可以跳过。

整个过程如下:
附加要修改的程序使目标程序成为被调试者——>收取附加成功消息——>找到要修改的地址并下断点——>触发断点——>收到异常,程序断下——>实现自己的操作(修改数据)——>让程序继续运行

为什么要先附加程序,使其成为被调试者?从流程上讲因为他是必须的,就像有了加法才有乘法,有了减法才有除法一样。

而从原理上讲,附加程序就是向目标程序注册成为调试器,只有拥有调试与被调试的关系后,每当目标程序产生调试事件(如断点产生的异常)后才会报告给调试者。因此我们附加程序,是为了获得目标程序产生异常而造成的空隙,这样我们才有修改数据等操作的机会。

从代码中看,使用DebugActiveProcess函数,同时传递目标程序PID作为参数,来使特定的程序成为被调试者,而自己的程序成为调试者。以下为函数文档

为了给特定地址下断点,我们需要合适的机会,而这个机会便是程序被附加成功的时候。因此我们只要在接收到附加成功的事件后进行断点操作即可。

同样,贴心的微软提供了一个接收调试事件的方法给我们:

WaitForDebugEvent函数会接收调试事件,例如附加成功,遇到断点产生异常等。那我们如何才知道哪个事件是附加成功呢?

当事件接收成功后,会将一个叫DEBUG_EVENT 的结构体存储在函数的第一个参数lpDebugEvent中,这个结构体的结构如下

通过dwDebugEventCode的事件代码我们能判断当前事件的类型,而附加成功的代码是CREATE_PROCESS_DEBUG_EVENT,当产生这个事件,CreateProcessInfo中的另一个结构体——CREATE_PROCESS_DEBUG_INFO ,它储存着一些关于目标程序的详细信息

是不是看的头晕眼花,简单来说,我们只要使用WaitForDebugEvent来接收调试事件,用其结果中的dwDebugEventCode来判断是否为附加成功。最后再用其结果产生的CreateProcessInfo来初始化一些设置:

从上面的代码注释可以看到,在判断为附加成功的事件后,调用了一个onCreateProEvent的函数。这是微软提供的吗?当然不是,这是笔者自己写的:

首先我们要获取地址。比如笔者想修改弹窗的标题,那我们就要知道目标程序调用弹窗的代码地址,而当前已知目标程序调用弹窗的函数名叫MessageBoxA。利用系统函数地址不变的原理,使用GetProcAddress函数获取其弹窗函数地址:

address = GetProcAddress(hmod, "MessageBoxA");

然后我们只要在这地址下断点就行了。这样当目标程序调用弹窗函数时,便会产生调试事件并断下。

可我们要如何在这地址下断呢?其实很简单,我们只要把这地址的首部替换成0xCC即可。这是因为0xCC在汇编中代表的是一个int3中断指令,当程序遇到它时会报告给调试者

例如我们通过GetProcAddress获得MessageBoxA的地址为76EA13D0,那我们下断点后就断成了:CCEA13D0了。

为了修改数据后能让程序正常运行,同时还要保存原地址开头的76,用来复原。

触发断点很简单,如果像笔者一样是断在弹框处,那只要让目标程序弹出弹窗即可。

同样的,我们接收断点调试事件也是使用步骤二的方法WaitForDebugEvent函数,只不过这次的调试事件类型为EXCEPTION_DEBUG_EVENT

在接收到类型为EXCEPTION_DEBUG_EVENT的调试事件后,结果里会产生一个叫EXCEPTION_DEBUG_INFO的结构体:

然后里面又会有一个叫EXCEPTION_RECORD的结构体(套娃)

我们需要用到ExceptionCode来判断是不是我们的断点事件,然后再用ExceptionAddress来判断是不是我们断下的窗口地址:

从上面代码可以看到,程序断点调试事件收到后会调用onExceptionEvent函数,这个也是自己书写的函数,他的内容便是修改弹窗标题。

那要如何获得弹窗的标题呢?我们可以使用GetThreadContext来进行获取CONTEXT结构体,再从其中来获取我们的标题。

对照MessageBoxA文档:

观察文档可以看到,我们要的标题在第三个参数(lpCaption)。在提取标题前我们必须知道一个汇编的知识:ESP代表调用栈的栈顶,当调用一个函数时,根据调用约束的不同,对函数的参数进行不同方式的传递。而C/C++的默认约定便是将参数压入栈。

听不懂不要紧,我们只要知道ESP+4是第一个参数,ESP+8是第二个参数,ESP+C就是我们要的标题。

而所谓的ESP我们可以从CONTEXT结构体获得:

然后通过WriteProcessMemory更改标题即可:

最后我们要把更改成CC了的地址恢复为原样,并把EIP复原。因为EIP的值指示着当前代码的地址,地址改了当然EIP也要改:

当调试事件发生后,程序会断下来。而我们为了在修改程序后还能继续运行,必须通知程序没事了你继续吧。因此我们可以调用ContinueDebugEven函数来通知:

例子是将弹框标题t改成z:

1

3

2

以下为一些较为关键的函数(仅名称):
DebugActiveProcess——附加程序,成为目标程序的调试者
WaitForDebugEvent——接收调试事件
GetProcAddress——获取要下断的目标地址
GetThreadContext——获取上下文,以此来获取对应数据,如弹窗标题内容等。
SetThreadContext——上下文设置,用来恢复原来的地址
ContinueDebugEvent——让程序继续运行
ReadProcessMemory——读取目标相关数据
WriteProcessMemory——更改目标数据,如写入CC断点,修改弹窗标题,内容等。
VirtualProtectEx——更改虚拟内存权限,若无法写入数据可使用

随着学习的不断深入,知识的难度越来越难是理所当然的。在写这篇文章的时,如何去用简洁的语言去描述过程,并让那些没有编程基础的读者也能看的明白就成为了一个问题。因为知识难度越高,解释中用到的其他的知识铺垫也会越来越多,这就会陷入一个“不断解释”的问题。

例如给别人解释2x3的意义,我们可以用加法来告诉他2x3就是2个3相加或者3个2相加。但这前提是他必须懂得加法。

那如果说我解释的东西用了n个知识铺垫呢?那我是不是得解释这n个知识。所以说,倘若读者不太看得懂这篇文章那只有两种可能:

所以说只有不断提高自己,才能向更高的层次进发。

当我们改写标题的时候,如果新标题的长度超过了4个字节,会使新标题溢出到弹窗内容区:

4

如上图所示,原标题为t,原内容为z。写入新标题I am title title title title后内容也变成了title tile tile tile。

这是为什么呢?因为在虚拟内存当中,弹窗的标题和内容是连在一起的:

5

因此标题过长会覆盖到隔壁的内容区域。

可如果我们想用一段任意长的内容替代它而不造成溢出,该怎么做呢?笔者想过是否可以替换掉指向标题的地址:

9

可一直没成功,如有解决方法希望读者可以留言指点!


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (5)
雪    币: 360
活跃值: (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
跟着dalao一起学习
2020-8-26 21:22
0
雪    币: 1567
活跃值: (905)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
论坛上120个CrackMe哪里下载啊?
2020-8-27 01:23
0
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
不错,赞了
2020-8-27 07:30
0
雪    币: 3523
活跃值: (3454)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
5
sYstemk1t 论坛上120个CrackMe哪里下载啊?
我记得看雪里有人发过没找到。
我是在吾爱下的https://www.52pojie.cn/thread-709699-1-1.html
2020-8-27 17:04
1
雪    币: 1567
活跃值: (905)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
psycongroo 我记得看雪里有人发过没找到。 我是在吾爱下的https://www.52pojie.cn/thread-709699-1-1.html
好的!!!!
2020-8-27 18:59
0
游客
登录 | 注册 方可回帖
返回
//