查看原文
其他

FlokiBot 银行木马详细分析

2017-03-27 lumou 看雪学院

☀ 介 绍

FlokiBot 是最近一款针对于欧洲和巴西联邦共和国的银行木马,作为一款恶意软件工具集,它在一些黑客论坛上被卖到$1000。它通过垃圾邮件和渗透代码工具包来传播。虽说它是继承于ZeuS(宙斯),FlokiBot 也做了很多有趣的改进。有诸如内存截取(RAM scraping),定制的 dropper 这样的新特性,还有似乎从泄露了源码的Carberp 那里借鉴了几行代码。

FlokiBot 与其 dropper 都有很多常用或不常用的混淆技术,我们将解开它们的神秘面纱,并着重讨论如何使用 IDA 和 IDAPython 脚本来静态脱壳。因为你们已经在最近很多恶意软件上接触过这些技术了,所以我觉得这是一次很好的锻炼。

在看完写的这篇关于 FlokiBot 的 dropper 文章:. 之后,我决定看一下 FlokiBot。尽管大多数关于 FlokiBot 的文章都着重讲它的dropper,我还是想讲得更详细一些,然后再讲讲它的 payload;我们将会看到它有很多有有趣的特性,而且不是你平常所看到的 ZeuS 不一样,虽然它的很多代码是来自 ZeuS 和 Carberp leaks。还是比逆向勒索软件好。

Hash值:

$ rahash2 -a md5,sha1,sha256 -qq floki_dropper.vir 37768af89b093b96ab7671456de894bc 5ae4f380324ce93243504092592c7b275420a338 4bdd8bbdab3021d1d8cc23c388db83f1673bdab44288fccae932660eb11aec2a $ rahash2 -a md5,sha1,sha256 -qq floki_payload32.vir da4ea4e44ea3bb65e254b02b2cbc67e8 e8542a465810ff1396a316d1c46e96e042bf4189 9f1d2d251f693787dfc0ba8e64907e204f3cf2c7320f66007106caac0424a1f3

☀ FlokiBot Dropper

导入:模块/API 哈希处理与系统调用(syscall)

dropper 通过比较经过哈希处理的库名与内置哈希值来加载模块。哈希进程用到了一个基础的 CRC32,之后这个 CRC32 还要跟两个字节的密钥异或,那两个密钥随样本的不同而不同。

有两种方法来检索动态链接库(dll)库名:一是用   来检查 的结构并读取 的值来获取进程已经加载的dll。第二种方法是,通过在 Windows 系统文件夹中罗列库名。

下面的模块都被 dropper 导入了:

CRC Library Method ------------------------------------------------------ 84C06AAD ntdll.dll load_imports_peb 6AE6ABEF kernel32.dll load_imports_peb 2C2B3C88 948B9CAB C7F4511A wininet.dll load_imports_folder F734DCF8 ws2_32.dll load_imports_folder F16EE30D advapi32.dll load_imports_folder C8A18E35 shell32.dll load_imports_folder E20BF2CB shlwapi.dll load_imports_folder 1A50B19C secur32.dll load_imports_folder 630A1C77 crypt32.dll load_imports_folder 0248AE46 user32.dll load_imports_peb BD00960A 4FF44795 gdi32.dll load_imports_peb E069944C ole32.dll load_imports_folder CAAD3C25

之后,FlokiBot 将采取同样的操作来定位和加载这些模块用到的API.首先,当CRC后的名字是匹配的,那么它将检索 LdrGetProcedureAddress 在 ntdll 中的地址,并用它来获取其他 API 的句柄。这样做的话,函数的地址就仅对 debugger 可见。如此,分析代码将会非常困难,因我我们不知道调用了哪个 API。去混淆的一种方法将在下一章提到。

FlokiBot 与其 dropper 的另一有趣之处在于它们调用一些原生 API 函数的方式。这些函数位于 ntdll 中,并且以 Nt* 或者 Zw*为前缀。它们在实现的时候与其他 API 有些许不同,因为它们要用到 syscall 特别是 int 0x2e。下面的截图说明了它们是如何在 ntdll 中实现的。

正如我们所看到,系统调用的值放在 eax(在我64位Windows 7上,NtAllocateVirtualMemory 是在0x15 ),而且参数被传到 edx。x86 和 64 位的所有系统调用号(syscall number)都可以在这张网页找到:.  

在检查 ntdll 中的 API 时,FlokiBot 会先检查函数的第一个操作码是否为 0xB8 = MOV EAX, 若是,并且 CRC 后的 API 名也复合,它将提取 MOV EAX  后面的四个字节,也就是系统调用号,并将它保存在 dwSyscallArray 数组中。

在我的虚拟机上,当所有的系统调用号都被 dropper 提取后,dwSyscallArray 长这样。

Index API Syscall number ------------------------------------------------------- 0x0 NtCreateSection 0x47 0x1 NtMapViewOfSection 0x25 0x2 NtAllocateVirtualMemory 0x15 0x3 NtWriteVirtualMemory 0x37 0x4 NtProtectVirtualMemory 0x4D 0x5 NtResumeThread 0x4F 0x6 NtOpenProcess 0x23 0x7 NtDuplicateObject 0x39 0x8 NtUnmapViewOfSection 0x27

当 FlokiBot 需要调用某个原生函数的时候,它将调用自身的一个函数,那个函数直接从 dwSyscallArray 中检索系统调用号,传参,触发中断 0x2E。这些都跟它在 ntdll 中的实现方式一样。这就是为什么你不会看到任何这些 API 调用的轨迹,而且专门用来钩住这些 API 的监测工具也监测不到有调用它们。

☀ API 调用去混淆

既然 FlokiBot 的 payload 用到了相同的函数和数据结构,你可以用“IDAPython完全静态去混淆”模块,稍稍修改 IDAPython 脚本就可以将 dropper 的 API 调用去混淆。

☀ 解除挂钩模块

FlokiBot的一个有趣之处就在于它的 dropper 和 payload 都有解除挂钩操作。思路是卸载检测工具,沙箱和杀毒软件中的钩子。尽管这并不是恶意软件第一次使用这样的功能,比如说,Carberp 就有一个能   的功能,还有最近的 Carbanak ,但这样的功能能真的非常罕见,应该值得注意。在这一部分,我将描述 FlokiBot 是如何解除挂钩的。

首先,FlokiBot 通过罗列 System32 文件夹中的 dll 来获得 ntdll.dll 的句柄,然后用我们上面所提到的哈希处理过程,最后调用 MapViewOfFile 来映射它在内存中的位置。结果就是,FlokiBot 有两个库在内存中映射的版本:一个是在导入期间导入的,这个可能会被监测工具的钩子改变,另一个是它直接从磁盘中映射的,这个是干净的。

NTDLL在磁盘中的映射——干净版本

由dropper导入的NTDLL——可能被钩住

现在,设置了正确的权限,FlokiBot 把映射干净 DLL 代码块的地址、导入的 dll 的地址入栈,然后调用解除挂钩操作。


因为它要重写它的内存里的一些数据以删除钩子,恶意软件需要改变导入的 NTDLL 代码导出段的内存保护机制。而它是通过用 int 0x2E 和之前提取的系统调用号(在我的 windows 版本是 0x4D)调用 NtProtectVirtualMemory 来做到的。我们可以看到如果某个钩子被发现,某一部分的代码就会变得可写。

解除挂钩函数可以描述为三步:对于 NTDLL 导出的每一个函数……

  • 比较两个映射库的第一个操作码

  • 如果它们不一致,说明导入的 ntdll 里的函数已经被钩住了。

  • 改变导入的dll的内存保护机制,让它变成可写。

  • 用从被映射到磁盘的 dll 复制来的操作码修补这个操作码。

解除挂钩操作的主要程序如下:

这样以来,很多监测工具、杀毒软件和沙箱都无法追踪恶意软件的调用。这个非常有用,如果你想要避免来自像 . 这些网上沙箱的自动分析。

☀ 从资源中提取 Bot

它的 dropper 有 3 个明确命名的资源: key, bot32 和 bot64 。Bot 被 RtlCompressBuffer()  和 LZNT1 压缩,然后用有 16 字节密钥的 RC4 加密它。在我的样本里,密钥是:

A3 40 75 AD 2E C4 30 23 82 95 4C 89 A4 A7 84 00

你可以从 Talos 团队的 Github: . 中找到可以备份这个 payload 和配置文件的 Python 脚本。要注意的是,它们并不能自己正确运行,因为它们要注入一个进程,还需要一些被 dropper 在内存中改写的数据。我们会在下一部分详谈注入进程。

提取资源的常用方法:

BOOL __userpurge extract_bot_from_rsrc@<eax>(int a1@<edi>, HMODULE hModule)

{

  HRSRC v2; // eax@1

  int v3; // eax@2

  const void *v4; // esi@5

  HRSRC v5; // eax@7

  int v6; // eax@8

  HRSRC v7; // eax@10

  unsigned int v8; // eax@11

  int v10; // [sp+4h] [bp-4h]@1

  v10 = 0;

  v2 = FindResourceW(hModule, L"key", (LPCWSTR)0xA);

  if ( v2 )

    v3 = extract_rsrc(hModule, (int)&v10, v2);

  else

    v3 = 0;

  if ( v3 )

  {

    v4 = (const void *)v10;

    if ( v10 )

    {

      qmemcpy((void *)(a1 + 84), (const void *)v10, 0x10u);

      free_heap(v4);

    }

  }

  v5 = FindResourceW(hModule, L"bot32", (LPCWSTR)0xA);

  if ( v5 )

    v6 = extract_rsrc(hModule, a1 + 4, v5);

  else

    v6 = 0;

  *(_DWORD *)(a1 + 12) = v6;

  v7 = FindResourceW(hModule, L"bot64", (LPCWSTR)0xA);

  if ( v7 )

    v8 = extract_rsrc(hModule, a1 + 8, v7);

  else

    v8 = 0;

  *(_DWORD *)(a1 + 16) = v8;

  return *(_DWORD *)(a1 + 4) && *(_DWORD *)(a1 + 12) > 0u && *(_DWORD *)(a1 + 8) && v8 > 0;

}

☀ 注入过程

dropper 并不是用常用的用 NtMapViewOfSection 和 NtWriteVirtualMemory 来将 payload 注入到 explorer.exe (或者svchost.exe,如果失败的话) 。它是用写并运行一个可以在进程内存中解密解压 payload 的 shellcode 来完成的。这很不常见,很有趣。dropping 的过程可以用下面的图片来总结:

  1. dropper 在 explorer.exe / svchost.exe 里写一个 trampoline shellcode 和它自己的一个函数。

  2. 当运行时,trampoline 就会调用那个函数。

  3. 函数自动运行,并且动态解决导入、读取 dropper 的资源,并将它们提取到自己的进程内存中。(比如,在 explorer.exe / svchost.exe 的地址空间里)

  4. 最后,dropper 在目标进程中运行 bot payload 的入口点(entrypoint)。

第一个写在 explorer.exe 的shellcode(称为 trampoline )会休眠100ms,然后调用一个函数,那个函数 dropper 在进程内存中映射在 0x80000000 ,在该 dropper 中默认称为 sub_405E18 。这第二个阶段是要提取 bot payload,解密并解压它们。所有这些都发生在 explorer.exe / svchost.exe  内存中。

$ rasm2 -a x86 -b 32 -D '558BEC51C745FCFF10B4766864000000FF55FCC745FC000008006800000900FF55FC83C4048BE55DC3'

0x00000000   1                       55  push ebp

0x00000001   2                     8bec  mov ebp, esp

0x00000003   1                       51  push ecx

0x00000004   7           c745fcff10b476  mov dword [ebp - 4], 0x76b410ff ; address of sleep()

0x0000000b   5               6864000000  push 0x64

0x00000010   3                   ff55fc  call dword [ebp - 4] ; sleep()

0x00000013   7           c745fc00000800  mov dword [ebp - 4], 0x80000

0x0000001a   5               6800000900  push 0x90000

0x0000001f   3                   ff55fc  call dword [ebp - 4] ; sub_405E18, 2nd stage 0x00000022   3                   83c404  add esp, 4

0x00000025   2                     8be5  mov esp, ebp

0x00000027   1                       5d  pop ebp

0x00000028   1                       c3  ret


sub_405E18 将通过与 dropper 和 payload 相同的进程来导入它需要的资源,用有些许不用的 crc32 和新的异或密钥。

int __stdcall sub_405E18(int a1)

{

  [...]

  if ( a1 && *(_DWORD *)(a1 + 4) && *(_DWORD *)a1 != -1 )

  {

    v1 = 0;

    v34 = 0i64;

    v35 = 0i64;

    v36 = 0i64;

    do  /* CRC Polynoms */

    {

      v2 = v1 >> 1;

      if ( v1 & 1 )

        v2 ^= 0xEDB88320;

      if ( v2 & 1 )

        v3 = (v2 >> 1) ^ 0xEDB88320;

      else

        v3 = v2 >> 1;

      [...]

      if ( v8 & 1 )

        v9 = (v8 >> 1) ^ 0xEDB88320;

      else

        v9 = v8 >> 1;

      v40[v1++] = v9;

    }

    while ( v1 < 0x100 );

    v10 = shellcode_imp_dll((int)v40, 0x6AE6AF84);

    v11 = shellcode_imp_dll((int)v40, 0x84C06EC6);

    v30 = v12;

    v13 = v11;

    LODWORD(v34) = shellcode_imp_api(v10, (int)v40, 0x9CE3DCC);

    DWORD1(v34) = shellcode_imp_api(v10, (int)v40, 0xDF2761CD);

    DWORD2(v34) = shellcode_imp_api(v10, (int)v40, 0xF7C79EC4);

    LODWORD(v35) = shellcode_imp_api(v10, (int)v40, 0xCD53C55B);

    DWORD1(v36) = shellcode_imp_api(v10, (int)v40, 0xC97C2F79);

    LODWORD(v36) = shellcode_imp_api(v10, (int)v40, 0x3FC18D0B);

    DWORD2(v36) = shellcode_imp_api(v13, (int)v40, 0xD09F7D6);

    DWORD1(v35) = shellcode_imp_api(v13, (int)v40, 0x9EEE7B06);

    DWORD2(v35) = shellcode_imp_api(v13, (int)v40, 0xA4160E3A);

    DWORD3(v35) = shellcode_imp_api(v13, (int)v40, 0x90480F70);

    DWORD3(v36) = shellcode_imp_api(v13, (int)v40, 0x52FE165E);

    v14 = ((int (__stdcall *)(_DWORD, _DWORD, signed int, signed int))v34)(0, *(_DWORD *)(a1 + 8), 0x3000, 64);

     

    [...]

}

前两个哈希值,0x6AE6AF84 和 0x84C06EC6 应该是 'kernel32.dll'和'ntdll.dll' 的。我用 Python 来实现哈希过程,证实导入的那两个 DLL 确实是 kernel32 和 ntdll,然后我修改我的 Python 程序去解析它的导出表,想要知道函数导入的 API 的名字。我运行程序得到下面的 API。

Python>run

[+] kernel32.dll (6AE6AF84) : Parsing...

0x09CE3DCC --> VirtualAlloc

0xDF2761CD --> OpenProcess

0xF7C79EC4 --> ReadProcessMemory

0xCD53C55B --> VirtualFree

0xC97C2F79 --> GetProcAddress

0x3FC18D0B --> LoadLibraryA

[+] ntdll.dll (84C06EC6) : Parsing...

0x0D09F7D6 --> NtClose

0x9EEE7B06 --> NtCreateSection

0xA4160E3A --> NtMapViewOfSection

0x90480F70 --> NtUnmapViewOfSection

0x52FE165E --> RtlDecompressBuffer

用这些函数,进程中的代码将可以读取 dropper 的资源(bot和RC4密钥)并且映射 payload 在内存中的位置。最后,远处终止的线程内容将会被修改,这样它的 EIP 将指向第一个 shellcode,该线程继续。

流程图

☀ FlokiBot Payload

这个 payload 是基于熟知并已经分析过的 ZeuS 木马,所以我不会每件事都详细描述。至于 dropper,我会更着重讲去混淆部分以及 FlokiBot 改进部分的实现。

配置

我运行   ConfigDump.py 程序,然后得到下面的C&C :  

$ python ConfigDump.py payload_32.vir 

Successfully dumped config.bin.

URL: https://extensivee[.]bid/000L7bo11Nq36ou9cfjfb0rDZ17E7ULo_4agents/gate[.]php

用 IDAPython 完全静态去混淆

本文由 看雪翻译小组 lumou 编译,来源 Arnaud Delmas


❤ 往期热门内容推荐



更多优秀文章点击左下角“阅读原文”查看!


看雪论坛:http://bbs.pediy.com/

微信公众号 ID:ikanxue

微博:看雪安全

投稿、合作:www.kanxue.com

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存