有没有想过恶意软件如何能够从Web浏览器中获取凭据?最流行的方法是Man-in-The-Browser(MITB)攻击,是我们熟知的Inline Hooking (有时又称为detours)。Inline hooks特别的通用并且在恶意软件中很常见。使用Inline Hooks,恶意软件可以控制任何进程的流程,它操纵着进程,恶意软件作者可以为所欲为。让我们来看看他们是如何做到这一点。
How do you get your code into the target process?
Inline Hooks的第一步是让你的代码到目标进程中运行。这个过程被称为注入。
有几种常见的注入方法,例如CreateRemoteThread/RtlCreateUserThread/QueueUserApc/SetWindowsHookEx/SetThreadContext/反射注入/atom注入等等。在我们的例子中,我们使用 VirtualAllocEx
在Firefox中分配一个存储区域 0x0D000000
。 CreateRemoteThread
然后将用于对Firefox内的 0x0D000000
可执行区域进行挂钩和初始化工作。(实际注入过程是对自己的一个复杂的话题,不在本文的讨论范围内。)下面是我们注入的代码块的样子。
Inline Hooks有四个部分组成:hook,shellcode(这里用的NOP填充),trample和返回。
What is a Windows API?
这是一组函数和数据的结构,一个Windows程序可以用它来让Windows做一些事情,如打开一个文件,显示消息等。
一个Windows程序几乎所有的操作都调用各种AP??I函数。
总的来说,所有的Windows使可用的API函数被称作“在Windows API”。
The hook
Hook有时被称为 trampoline,类似于一个交通员将交通转移到另一个位置。在我们的例子中,我们将挂钩WS2_32的 send()
函数。下面是ws2_32!send
最初的几个指令的。为了成功挂钩,我们要覆盖调send()函数存在的一些指令。下面绿色方框的指令我们会将它覆盖掉。
请注意,五个字节将要被改写。这是需要我们挂钩的确切字节数:一个 jmp
是跳进我们的代码指令 0x0D000000
。(还有其他的方法来放置钩子比如用 push
地址, ret
指令组合。)挂钩之后 send()
函数如下图所示:
The malicious code
现在,执行流程已经被重定向到我们注入的代码,寄存器必须保存,以确保我们回到send()函数执行的时候不会crash。在32位的过程中,我们可以使用PUSHAD指令将所有的寄存器保存下来。当shellcode被执行完毕,在使用 POPAD 指令恢复所有寄存器的值,回到
send()
最初执行的时候。现在,我们在控制 send()
功能,我们必须想出一些有趣的事情。有一件事我们可以做的是,将一个发送POST请求和发送数据发回到我们,解析看看是否包含登录凭据。
The execution of trampled bytes and the return
我们已经在shellcode中做了一些有用的东西之后,我们必须确保我们的程序返回到它在hook之前完全相同的状态。首先,我们使用的 popad
指令来恢复所有的寄存器。还记得我们Hook覆盖的五个字节吗?这些指令仍需要运行,因此他们在Hook的时候复制到我们shellcode的结尾,让他们在恢复寄存器后无缝的运行。最后,我们安全的调回到send()+ 0×05(我们的挂钩指令的长度)。
Putting it all together
让我们重新看一遍全部的流程。下面箭头是Firefox里面恢复send()钩子之后正常的执行流程:
注入我们的detour后,执行的流程入下图所示
:
Reasons for hooking API calls
你可以想出任何的目的去挂钩每个API。这里有几个:
urlmon!URLDownloadToFile
- 用于拦截下载的文件。挂钩全局的该函数来防止下载新的恶意软件。ws2_32!send
- 用于捕获未加密的流量POST凭据。GetHostByName
- 挂钩该函数为了忽略指定命令和控制站点的流量。ws2_32!recv
- 用于捕获未加密的流量传入数据包的数据。Advapi32!CryptEncrypt
-用于捕获被加密以前的数据,因为send()之后的数据
不会是纯文本 。Advapi32!CryptDecrypt
-用于解密从recv()来的数据。User32!GetMessage
- 用来拦截鼠标点击消息从虚拟键盘中点击图片。Kernel32!ExitProcess
- 防止进程结束自身(绕过游戏anticheats)
Detection
这里有一个简单的解决方案的伪代码,如果攻击者是一个harduser就不是非常有效。这下面的代码将检查ExitProcess的开头预0xE9作比较(操作码JMP)
FARPROC Address = GetProcAddress(GetModuleHandle("kernel32.dll"),"ExitProcess"); if (*(BYTE*)Address == 0xE9) { printf("Api hooked\n");//Do your thing }