介绍
Emotet是一种计算机恶意软件程序,最初以银行木马程序的形式开发。目的是访问外部设备并监视敏感的私有数据。众所周知,Emotet会欺骗基本的防病毒程序并将其隐藏。一旦被感染,该恶意软件就会像计算机蠕虫一样传播,并试图渗透到网络中的其他计算机。
样本信息
MD5:589ded5798b2dcf227b56142122a6375
File Type: Win32 EXE
Detection: Trojan:Win32/EmotetCrypt.ARJ!MTB
静态分析
使用Exeinfo查看下是没用通用壳特征的
发现有个挺奇怪的资源rcdata\1E55
使用ida打开通过这些鬼东东还是可以识别是MFC的程序,MFC的程序用户代码一般都在头部,如果有界面的话还是可以使用xspy进行解析找到按钮事件和定时器等的
动态分析
对于MFC程序我一般是在ida里面头部找到用户代码下断点,或者通过xspy来找到按钮的事件来分析函数。这里的话我找到了一段比较感兴趣的用户代码,我们在调试器里面下断点到402f78
这个函数先初始化了变量V7就是我们之前在资源里面看到的值还记得吗?0x1E55==7765
然后在对字符串进行拼接LdrAccessResource LdrAccessResource LdrFindResource_U
并且获取了LdrFindResource_U这个函数地址
下面这块代码就是在调用上面的API寻找资源,然后申请内存空间然后解密
调试到使用LdrFindResource_U加载资源的函数
LdrFindResource_U和LdrAccessResource都是从NTdll中导出的API,LdrFindResource_U会根据资源ID找到相应的资源,如果找到,则返回相应的句柄,后续应该使用LdrAccessResource来使用该句柄。下面就是函数的声明,到这里也基本都知道流程了。
/*
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算法,把一个字符串算出一个值来
下面一个函数就在解密资源了
解密之后上面是代码下面是一个PE
在资源解密的开头下断点,这些代码就是个PEloader感兴趣的话可以调试看看,这里就不浪费篇章讲解了
payloder
样本信息
MD5:F24497D3168A8464E4F13AB4E45458E8
File type: Win32 DLL
静态分析
发现是个dll,点进去10004070进去看
又是个PE,开始dump吧,猜个这个dll没有功能就是loader
dump下来的PE还有个可爱的图标
样本信息
MD5: 4434F871965FB050F1E4BA9361562466
File type: Win32 DLL
静态分析
从入口进去,可以发现这个函数像是被平坦化了的。被平坦化了的函数一般使用符号执行来解决比较好吧?(PS:还有其他好的办法请告诉我谢谢)。看到这种情况我还是打算好好的用OD调试+ida看吧,目前从ida这里看不出来啥。
动态分析
先从入口点进入平坦化的函数内部看看 406550
进入后经过一系列比较走到的第一个函数406fb0
406fb0内部有一个函数经过交叉引用发现被调用很多次,但有不是库的,一般可以认为和解密有关,注意看他的模式一般都是mov ecx,xxxxxx 然后再call 406fb0
进入函数内部也确实可以看出ecx做了一些运算
动态调试可以发现是在找dll地址(handle) 0A2CE093Fh这个的值对应的是kerenl32.dll
获取到dll地址后,然后就返回到上个函数可以看到sub_403ED0这个函数也是交叉引用很多的,发现使用了上个函数返回的dll地址,还有一个参数
这个函数内部的功能就是加载dll的函数 4FEE74F4h对应的就是GetPorcessHeap
说了这么多肯定不是为了手动调试,那样太累了还是体力活,而且效果也不咋滴。所以我把这算法给扣了下来
#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("%X\r\n", 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("%X\r\n", v3 ^ 0xBDB9B51);
}
嘿嘿结果也是对的,之后可以把常用的dll名字和函数名字跑一下出来crc。然后使用脚本对ida脚本对函数进行标注,但是在之前最后把平坦化给去除了。
符号执行去除平坦化
文章参考链接利用符号执行去除控制流平坦化
参考的github链接deflat
符号执行第一步先把得到和ida一样的cfg图
filename=r"D:\code\py\env\flat_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块
# 寻找序言 ret
prologue_node=None
retn_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修复了
总结
这里最后也没有给出标注ida或者OD函数的脚本,还有完整的修复平坦化的脚本。因为这些东西都不是通用的,给出了没有太大的意义(PS:其实是自己不想整了)。有兴趣的可以继续整下去。
发表评论
您还未登录,请先登录。
登录