杀不死的Emotet

admin 2020年12月2日19:15:46评论36 views字数 5332阅读17分46秒阅读模式

杀不死的Emotet

 

杀不死的Emotet

介绍

杀不死的Emotet

Emotet是一种计算机恶意软件程序,最初以银行木马程序的形式开发。目的是访问外部设备并监视敏感的私有数据。众所周知,Emotet会欺骗基本的防病毒程序并将其隐藏。一旦被感染,该恶意软件就会像计算机蠕虫一样传播,并试图渗透到网络中的其他计算机。
样本信息
MD5:589ded5798b2dcf227b56142122a6375
File Type: Win32 EXE
Detection: Trojan:Win32/EmotetCrypt.ARJ!MTB

 

杀不死的Emotet

静态分析

杀不死的Emotet

使用Exeinfo查看下是没用通用壳特征的

杀不死的Emotet

发现有个挺奇怪的资源rcdata1E55

杀不死的Emotet

使用ida打开通过这些鬼东东还是可以识别是MFC的程序,MFC的程序用户代码一般都在头部,如果有界面的话还是可以使用xspy进行解析找到按钮事件和定时器等的

杀不死的Emotet

 

杀不死的Emotet

动态分析

杀不死的Emotet

对于MFC程序我一般是在ida里面头部找到用户代码下断点,或者通过xspy来找到按钮的事件来分析函数。这里的话我找到了一段比较感兴趣的用户代码,我们在调试器里面下断点到402f78

杀不死的Emotet

这个函数先初始化了变量V7就是我们之前在资源里面看到的值还记得吗?0x1E55==7765
然后在对字符串进行拼接LdrAccessResource LdrAccessResource LdrFindResource_U
并且获取了LdrFindResource_U这个函数地址

杀不死的Emotet

下面这块代码就是在调用上面的API寻找资源,然后申请内存空间然后解密

杀不死的Emotet

调试到使用LdrFindResource_U加载资源的函数

杀不死的Emotet

LdrFindResource_U和LdrAccessResource都是从NTdll中导出的API,LdrFindResource_U会根据资源ID找到相应的资源,如果找到,则返回相应的句柄,后续应该使用LdrAccessResource来使用该句柄。下面就是函数的声明,到这里也基本都知道流程了。

杀不死的Emotet

杀不死的Emotet

/*NTSTATUS NTAPI  LdrFindResource_U (PVOID BaseAddress, PLDR_RESOURCE_INFO ResourceInfo, ULONG Level, PIMAGE_RESOURCE_DATA_ENTRY *ResourceDataEntry)NTSTATUS NTAPI  LdrAccessResource (IN PVOID BaseAddress, IN PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry, OUT PVOID *Resource OPTIONAL, OUT PULONG Size OPTIONAL)*/status = LdrFindResource_U(DllHandle, (ULONG_PTR*)&IdPath, 3, &DataEntry);        if (NT_SUCCESS(status)) {            status = LdrAccessResource(DllHandle, DataEntry, (PVOID*)&Data, &SizeOfData);            if (NT_SUCCESS(status)) {                if (DataSize) {                    *DataSize = SizeOfData;                }            }        }

这个函数里面就是真正的解密算法,看起来像改的RC4算法,把一个字符串算出一个值来

杀不死的Emotet

杀不死的Emotet

杀不死的Emotet

下面一个函数就在解密资源了

杀不死的Emotet

解密之后上面是代码下面是一个PE

杀不死的Emotet

在资源解密的开头下断点,这些代码就是个PEloader感兴趣的话可以调试看看,这里就不浪费篇章讲解了

杀不死的Emotet

 

杀不死的Emotet

playloder

杀不死的Emotet

样本信息

MD5:F24497D3168A8464E4F13AB4E45458E8
File type: Win32 DLL


静态分析

发现是个dll,点进去10004070进去看

杀不死的Emotet

又是个PE,开始dump吧,猜个这个dll没有功能就是loader

杀不死的Emotet

dump下来的PE还有个可爱的图标

杀不死的Emotet


样本信息

MD5: 4434F871965FB050F1E4BA9361562466
File type: Win32 DLL


静态分析

从入口进去,可以发现这个函数像是被平坦化了的。被平坦化了的函数一般使用符号执行来解决比较好吧?(PS:还有其他好的办法请告诉我谢谢)。看到这种情况我还是打算好好的用OD调试+ida看吧,目前从ida这里看不出来啥。

杀不死的Emotet

杀不死的Emotet

动态分析

先从入口点进入平坦化的函数内部看看 406550

杀不死的Emotet

杀不死的Emotet

进入后经过一系列比较走到的第一个函数406fb0

杀不死的Emotet

406fb0内部有一个函数经过交叉引用发现被调用很多次,但有不是库的,一般可以认为和解密有关,注意看他的模式一般都是mov ecx,xxxxxx 然后再call 406fb0

杀不死的Emotet

进入函数内部也确实可以看出ecx做了一些运算

杀不死的Emotet

动态调试可以发现是在找dll地址(handle) 0A2CE093Fh这个的值对应的是kerenl32.dll

杀不死的Emotet

杀不死的Emotet

获取到dll地址后,然后就返回到上个函数可以看到sub_403ED0这个函数也是交叉引用很多的,发现使用了上个函数返回的dll地址,还有一个参数

杀不死的Emotet

这个函数内部的功能就是加载dll的函数 4FEE74F4h对应的就是GetPorcessHeap

杀不死的Emotet

杀不死的Emotet

说了这么多肯定不是为了手动调试,那样太累了还是体力活,而且效果也不咋滴。所以我把这算法给扣了下来

#include <windows.h>void LibCrc(WCHAR* v2);void FuncCrc(char* v1);int main(int argc,char* argv){    LibCrc(L"kernel32.dll");    FuncCrc("GetProcessHeap");}                         void LibCrc(WCHAR* v3) {    unsigned int v4;    int v6 = 0;        if (*v3)        {            do            {                v4 = (unsigned __int16)*v3;                if (v4 >= 0x41 && v4 <= 0x5A)                    v4 += 32;                ++v3;                v6 = (v6 << 16) + (v6 << 6) + v4 - v6;            } while (*v3);        }        void* crc = (void*)(v6 ^ 0x2DB0EF4D);        printf("%Xrn", crc);  }void FuncCrc(char* v1) {    char *i;    int v3;     v3 = 0;    for (i = v1; *i; v3 = (v3 << 16) + (v3 << 6) + (char)*(i - 1) - v3)        ++i;    printf("%Xrn", v3 ^ 0xBDB9B51);}

嘿嘿结果也是对的,之后可以把常用的dll名字和函数名字跑一下出来crc。然后使用脚本对ida脚本对函数进行标注,但是在之前最后把平坦化给去除了。

杀不死的Emotet

符号执行去除平坦化

文章参考链接利用符号执行去除控制流平坦化
参考的github链接deflat
符号执行第一步先把得到和ida一样的cfg图

filename=r"D:codepyenvflat_control_flow_01CD0000"project=angr.Project(filename, load_options={'auto_load_libs': False})cfg = project.analyses.CFGFast(normalize=True, force_complete_scan=False)target_function = cfg.functions.get(start_addr)
#转为ida一样的cfg图supergraph = am_graph.to_supergraph(target_function.transition_graph)

然后分块 寻找序言 ret 主分发器 寻找真实块和nop块

# 寻找序言 retprologue_node=Noneretn_node_list=[]for node in supergraph.nodes():    if supergraph.in_degree(node)==0:        prologue_node=node    if supergraph.out_degree(node) == 0 :        retn_node_list.append(node)
#寻找主分发器main_dispatcher_node_list=[]main_dispatcher_node_list.append(list(supergraph.successors(prologue_node))[0])main_dispatcher_node_list.append(list(supergraph.successors(main_dispatcher_node_list[0]))[0])
# 寻找真实块和nop块def get_relevant_nop_nodes(supergraph, main_dispatcher_node_list, prologue_node, retn_node_list):
relevant_nodes = [] nop_nodes = [] for node in supergraph.nodes(): if supergraph.has_edge(node, main_dispatcher_node_list[0]) and node.size > 8: relevant_nodes.append(node) continue if supergraph.has_edge(node, main_dispatcher_node_list[1]) and node.size > 8: relevant_nodes.append(node) continue if node.addr == prologue_node.addr: continue for main_dispatcher_node in main_dispatcher_node_list: if node.addr == main_dispatcher_node.addr: continue for retn_node in retn_node_list: if node.addr == retn_node.addr: continue nop_nodes.append(node) return relevant_nodes, nop_nodes

进行符号执行把块给关联起来

def symbolic_execution(project, relevant_block_addrs, start_addr, hook_addrs=None, modify_value=None, inspect=False):    def retn_procedure(state):        ip = state.solver.eval(state.regs.ip)        project.unhook(ip)        return    def statement_inspect(state):        expressions = list(            state.scratch.irsb.statements[state.inspect.statement].expressions)        if len(expressions) != 0 and isinstance(expressions[0], pyvex.expr.ITE):            state.scratch.temps[expressions[0].cond.tmp] = modify_value            state.inspect._breakpoints['statement'] = []    if hook_addrs is not None:        skip_length = 4        if project.arch.name in ARCH_X86:            skip_length = 5        for hook_addr in hook_addrs:            project.hook(hook_addr, retn_procedure, length=skip_length)    state = project.factory.blank_state(addr=start_addr, remove_options={                                        angr.sim_options.LAZY_SOLVES})    if inspect:        state.inspect.b(            'statement', when=angr.state_plugins.inspect.BP_BEFORE, action=statement_inspect)    sm = project.factory.simulation_manager(state)    sm.step()    while len(sm.active) > 0:        for active_state in sm.active:            if active_state.addr in relevant_block_addrs:                return active_state.addr        sm.step()    return None

找到关系后就是patch修复了

 

杀不死的Emotet

总结

杀不死的Emotet

这里最后也没有给出标注ida或者OD函数的脚本,还有完整的修复平坦化的脚本。因为这些东西都不是通用的,给出了没有太大的意义(PS:其实是自己不想整了)。有兴趣的可以继续整下去。

杀不死的Emotet

- End -
精彩推荐

如何使用MVSC编译器生成XFG函数原型哈希

安洵杯部分WP

快报!穆斯林App开发者竟然将用户数据卖给了美军?

竟然可以通过一个售价三十美元的设备来攻击Intel SGX

杀不死的Emotet


戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):杀不死的Emotet

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年12月2日19:15:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   杀不死的Emotethttps://cn-sec.com/archives/194749.html

发表评论

匿名网友 填写信息