转自:https://bbs.pediy.com/thread-223317.htm
你有没有想过,恶意软件是如何从web浏览器中获取口令密码凭证的呢?最流行的攻击方法是Man-in-The-Browser (MiTB),这也是大家所说的内联挂钩(inline hooking或者detours)。内联钩子技术在恶意软件中非常常见而且难以置信的好用。有了内联钩子技术后,恶意软件就成为了进程的傀儡师,可以按照恶意软件作者的意图来操纵进程来做任何想做的事情。让我们一起来看看他们是怎样做到的。
你怎样在目标进程上下文里来跑你的代码呢?
内联挂钩要做的第一件事就是让你的代码在目标进程上下文里跑。这个过程被称作注射(injection)。
我们常见的有几种注射方法。我举个栗子,我们使用VirtualAllocEx
函数在Firefox进程的0x0D000000处创建一块内存空间。然后在0x0D000000处执行CreateRemoteThread
函数来设置钩子和做一些初始化的工作(实际的注入过程本身就是一个复杂的话题,超出了本文的范围)。下面的图片中展示了我们的注射代码块的样子。随着我们继续下去,高亮显示的区域将变得更加简洁。
内联钩子有四个要素:钩子,恶意代码(这里暂时用NOPs指令填充), 要覆盖的字节和恶意代码执行后的恢复返回
什么是 Windows API?
它是一组函数和数据结构的集合,Windows程序可以调用这些函数来让Windows操作系统做一些事情,比如打开一个文件,显示一条信息等等。
几乎所有的Windows程序都会调用各种API函数。
所以所有的这些Windows提供的能被调用的API函数就称为“The Windows API”。
钩子
钩子有时候也被叫做蹦床(trampoline),就类似与交警把一处的流量引导到另一处。在我们这个例子中,我会钩住ws2_32库里的send()函数。以下是ws2_32!send函数中前几个指令正如图片里显示的那样。为了放置钩子,我们需要覆盖函数中现有的一些指令。 绿色框突出显示我们将覆盖的指令。
注意上面这五个字节就是将要被覆盖的。这也是放置我们钩子所需要的确切数字:一个跳转到我们的0x0D000000处的代码的jmp指令(其实还有其他的方法,比如使用push addr和ret指令的结合)。下面这里我们放置好钩子后的send()函数:
恶意代码
现在执行流已经被重定向到我们注入的代码中去了,记得,寄存器值一定得保存好以确保我们正确的返回到send()函数的执行中去的时候不会崩。在32位进程中,我们可以用pusha指令保存所有的寄存器。当恶意代码执行完毕后,popad指令会将所有寄存器值恢复到最初执行send()调用时所处的状态。既然我们已经控制了send()函数了,我们可以做一些好玩的事情了。一个我们能做的就是去看看POST请求是否正在发送,并将这些要发送的数据同时传给我们的代码,看看会不会有登录口令凭证。
恶意代码执行后的恢复返回
在我们的恶意代码执行完后,我们必须确保我们的进程恢复到我们的钩子接管之前的完全相同的状态。首先我们使用popad指令来恢复所有的寄存器值。还记得我们放置钩子时覆盖的那五个字节吗?这些指令其实也是需要运行的,所以在放置钩子时我把它放到了我们的恶意代码的最末尾处,以便在寄存器值恢复后可以无缝继续运行。最后我们安全的跳回send()函数+0x5的地址(0x5是我们钩子的字节长度)。
总结一下
让我们再看看我们所做的一切,获得一个整体的认识。按照箭头查看Firefox中正常的没有放置钩子的send()函数的执行流程:
在注射我们的detour后,执行流现在看起来是这样的:
选择要钩住的API原则
你可能会在想到底要钩哪些API调用呢,这里有一些选择的理由:
urlmon!URLDownloadToFile
– 用于拦截下载的文件。 Shifu(注:原文就是Shifu,我猜测作者想写师傅)在每个进程中都挂钩了这个函数,以防止新的恶意软件下载。ws2_32!send
– 用于从未加密的通信流量中捕获POST口令凭据。GetHostByName
– Shifu挂钩此函数,以忽略到他的命令和控制站点的流量。ws2_32!recv
– 用于从未加密的流量中捕获传入的数据包数据。Advapi32!CryptEncrypt
– 用于在数据被加密之前捕获数据,因为它在send()函数时不会是纯文本了。Advapi32!CryptDecrypt
– 用于解密来自recv()函数的加密数据User32!GetMessage
– Shifu在这里使用钩子来拦截鼠标点击消息,来获得键盘输入。Kernel32!ExitProcess
– 用于防止进程自行关闭(绕过一些游戏中的反作弊措施)。
检测
这是一个简单的解决方案的伪代码,但是如果攻击者是一个厉害角色的话,那么这个伪代码可能就不是很有效了。以下代码将检查api函数ExitProcess是否以0xE9开头(操作码JMP):
FARPROC Address = GetProcAddress(GetModuleHandle("kernel32.dll"),"ExitProcess"); if (*(BYTE*)Address == 0xE9) { printf("Api hooked\n");//Do your thing }
谢谢
原文链接:https://userpc.net/2017/12/03/understanding-detecting-inline-hooks-winapi-hooks-ring3/
译者:knowit