✿ 用 IDAPython 完全静态去混淆
→ 鉴别函数
首先,我们注意到 dropper 重用了一些 payload 的重要函数。创造该 dropper 的一个 Rizzo 签名并在 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 脚本:https://gist.github.com/adelmas/8c864315648a21ddabbd6bc7e0b64119.
它基于 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 && 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 的开头来确定发行方。在这个饱含信息量的网页,你可以找到一系列发行方的识别号:
http://www.stevemorse.org/ssn/List_of_Bank_Identification_Numbers.html.
Floki 并没有查看整个IIN (6 位),而是只检查了第一位看它是否符合下面的发行方:
3: Amex / Dinners / JP
4:VISA
5:Mastercard
6: Discover
FlokiBot identify_mii 流程:
然后,它根据 Luhn 算法查看 PAN 是否有效:
char __usercall check_mii_luhn@(void *a1@, _BYTE *a2@)
{
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......省略
感谢阅读。
点击左下角“阅读原文”,查看完整代码~
本文由 看雪翻译小组 lumou 编译,来源 Arnaud Delmas
❤ 往期热门内容推荐
更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!
看雪论坛:http://bbs.pediy.com/
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作:www.kanxue.com