We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本人是做静态配置解密(自动化静态扫描文件并将其中的IoC提取出来)的,日常遇到有混淆的恶意代码家族很正常。但是碰到这么恶心的还是第一次,所以记录一下,方便以后再次遇到,好参照解决。
IoC
样本md5: 3103007484cb5935efcd0d8abf28251e 5142f81d00403a33c3eb9f71290c2bf6
vt主要将该类识别为
vt
palevo zusy Fosniw
我是把它划分到palevo家族,但是这个不重要,今天最重要的是利用c++从这些混淆中成功提取出IoC信息 首先来看它到底有多么恶心
palevo
c++
如果直接F5,会卡很长时间
F5
之后的伪码
因为这个家族我做静态分析,分析了很多个版本,即使这样混淆,我依然能够很快定位到其中使用的解密函数
真正的解密逻辑在这里
其属于这个函数
同样是加了非常多的printf做混淆,因为我分析过,并且知道解密函数具体操作,所以很容易能够通过人力定位到,但是要想完成静态自动化的配置解密的话,其实很困难。
printf
因为在个人有限的经验里,静态配置解密定位到解密函数,并且定位到加密数据位置,主要有以下几种方法
数据/解密函数位置,存在固定偏移
这类最好解决,我们需要做的就是人工找到位置,之后将解密算法逆出来,静态配置解密也就完成了
数据/解密函数前后存在特定数据
这类主要就是使用正则表达式,匹配到特定数据,之后通过一定的偏移找到需要定位的数据
加密数据本身存在明显的特征情况,比如说一定的数据结构
这类直接定义出该结构,之后扫数据结构就行
定位解密函数自身行为特征(比如opcode/反汇编指令等)
opcode
这类的一般要求比上面的几种的都要严格,很容易出错或者出偏差,代码的话要做好充足的异常处理,而且一旦有各种混淆,而且混淆格式不同的,基本就gg了
无论解密函数还是加密数据都没有办法定位
这类的,我如果遇到了基本上就只能写个yara做一下识别,后来的配置解密模块就不写了
yara
这个只是我个人的纯粹工作经验总结,可能不对,或者对别人不适用
今天遇到这个我就把它划到了第5类,开始我都打算放弃了,但是后来细想了一下,这个家族和一般的家族有个相对比较明显的特征,
该家族的该类变种几乎所有使用的数据都使用了相同的加密算法加密,而且数据众多,结果导致的就是该解密函数必然会被反复调用
我们来粗略看一下
分析的这个样本3103007484cb5935efcd0d8abf28251e,该解密函数足足被调用了179次,正常情况下是肯定不会的,现在我们来将通过聚类处理的样本,做一下扫描,之后排序输出,看看哪些函数会被多次调用
3103007484cb5935efcd0d8abf28251e
179
首先看一下寻找xref的实现
xref
/* * 自定义简单字典 * string: 存储call指令 * vector<uint32_t>: 存储第一个参数 */ typedef std::map<std::string, std::vector<uint32_t>> xref_dictionary_t; /* * 获取所有至少有一个参数的函数引用 * buffer: 需要反汇编的数据 * buffer_size:反汇编数据长度 * xrefs: 所有函数引用的字典 */ static long func_get_xrefs_with_args(uint8_t *buffer, uint32_t buffer_size, xref_dictionary_t &xrefs) { long ret = 0; ud ud_obj; std::string last_assembly_code; std::string current_assembly_code; std::string call_opcode; const ud_operand_t* last_op = NULL; uint32_t last_op_lval_udword = 0; if (buffer == NULL || buffer_size == 0) RC_FINISH(-1); // 反汇编操作 ud_init(&ud_obj); ud_set_mode(&ud_obj, 32); ud_set_input_buffer(&ud_obj, buffer, buffer_size); ud_set_syntax(&ud_obj, UD_SYN_INTEL); ud_disassemble(&ud_obj); last_assembly_code = std::string(ud_insn_asm(&ud_obj), strlen(ud_insn_asm(&ud_obj))); last_op = (ud_operand_t*)ud_insn_opr(&ud_obj, 0); while (ud_disassemble(&ud_obj)) { current_assembly_code = std::string(ud_insn_asm(&ud_obj), strlen(ud_insn_asm(&ud_obj))); if (current_assembly_code.find("call") != std::string::npos && last_assembly_code.find("push") != std::string::npos) { xref_dictionary_t::iterator it; it = xrefs.find(current_assembly_code); // 已经存在 if (it != xrefs.end()) it->second.push_back(last_op_lval_udword); else { // 第一次出现 std::vector<uint32_t> tmp_cout_vec; tmp_cout_vec.push_back(last_op_lval_udword); xrefs[current_assembly_code]=tmp_cout_vec; } } last_assembly_code = current_assembly_code; // last_op 会随着指针变化,没法取到上一个值,直接取值 last_op = ud_insn_opr(&ud_obj, 0); if (last_op) last_op_lval_udword = last_op->lval.udword; } finish: return ret; }
得到了函数调用次数后,输出看一下
// xrefs排序 std::vector<xref_pair_t> xref_vec(xrefs.begin(), xrefs.end()); sort(xref_vec.begin(), xref_vec.end(), compare_by_second_size); for (auto it=xref_vec.begin(); it!= xref_vec.end(); it++) { std::cout << "function: " << it->first ; std::cout << "\tcount:" << it->second.size() << std::endl; }
结果大概是这样的
通过分析该聚类样本可以发现函数被调用最多的,除了用于混淆的printf,之后便是解密函数,并且可以肯定是在[160-200]的范围,所以提取IoC的的信息代码
[160-200]
static long func_extract(void *handle) { long ret = 0; PALEVO_C_HANDLE* _handle = NULL; xref_dictionary_t xrefs; if (handle == NULL) RC_FINISH(-1); _handle = (PALEVO_C_HANDLE*)handle; if (0 == func_get_xrefs_with_args(_handle->buffer, _handle->size, xrefs)) { xref_dictionary_t::iterator it_xrefs; for (it_xrefs=xrefs.begin(); it_xrefs != xrefs.end(); it_xrefs++) { if (it_xrefs->second.size() >= 160 && it_xrefs->second.size() <= 200) for (std::vector<uint32_t>::iterator it_count=it_xrefs->second.begin(); it_count != it_xrefs->second.end(); it_count++) { // 数据一般使用全局变量存储 if (*it_count > 0x40000) { uint32_t raw_data_address = func_get_raw_address(_handle->pe_handle, *it_count); std::vector<uint32_t> encode_data; // 取得所有加密数据 if (0 == func_get_encode_data(_handle->buffer, _handle->size, raw_data_address, encode_data)) { std::string decode_string = func_decode_string_v1(encode_data); // 只过滤http if (decode_string.find("http://") != std::string::npos && decode_string.size() > 7) { std::cout << "domain: "<< decode_string << std::endl; _handle->domains.push_back(decode_string); } encode_data.clear(); } } } } goto finish; } ret = FINAL_CODE; finish: return ret; }
这个是个人针对特定家族的一种提取ioc的方法,不具有普适作用,但是如果真是遇到静态配置解密一筹莫展时,并且很有可能在函数调用数量上有一定规则的话,不妨考虑使用该方法。至少目前我能够想到的就这一种方法
ioc
The text was updated successfully, but these errors were encountered:
No branches or pull requests
记录一次恶心混淆之静态配置解密的处理
分析
本人是做静态配置解密(自动化静态扫描文件并将其中的
IoC
提取出来)的,日常遇到有混淆的恶意代码家族很正常。但是碰到这么恶心的还是第一次,所以记录一下,方便以后再次遇到,好参照解决。vt
主要将该类识别为我是把它划分到
palevo
家族,但是这个不重要,今天最重要的是利用c++
从这些混淆中成功提取出IoC
信息首先来看它到底有多么恶心
如果直接
F5
,会卡很长时间之后的伪码
因为这个家族我做静态分析,分析了很多个版本,即使这样混淆,我依然能够很快定位到其中使用的解密函数
真正的解密逻辑在这里
其属于这个函数
同样是加了非常多的
printf
做混淆,因为我分析过,并且知道解密函数具体操作,所以很容易能够通过人力定位到,但是要想完成静态自动化的配置解密的话,其实很困难。解决
因为在个人有限的经验里,静态配置解密定位到解密函数,并且定位到加密数据位置,主要有以下几种方法
数据/解密函数位置,存在固定偏移
这类最好解决,我们需要做的就是人工找到位置,之后将解密算法逆出来,静态配置解密也就完成了
数据/解密函数前后存在特定数据
这类主要就是使用正则表达式,匹配到特定数据,之后通过一定的偏移找到需要定位的数据
加密数据本身存在明显的特征情况,比如说一定的数据结构
这类直接定义出该结构,之后扫数据结构就行
定位解密函数自身行为特征(比如
opcode
/反汇编指令等)这类的一般要求比上面的几种的都要严格,很容易出错或者出偏差,代码的话要做好充足的异常处理,而且一旦有各种混淆,而且混淆格式不同的,基本就gg了
无论解密函数还是加密数据都没有办法定位
这类的,我如果遇到了基本上就只能写个
yara
做一下识别,后来的配置解密模块就不写了这个只是我个人的纯粹工作经验总结,可能不对,或者对别人不适用
今天遇到这个我就把它划到了第5类,开始我都打算放弃了,但是后来细想了一下,这个家族和一般的家族有个相对比较明显的特征,
该家族的该类变种几乎所有使用的数据都使用了相同的加密算法加密,而且数据众多,结果导致的就是该解密函数必然会被反复调用
我们来粗略看一下
分析的这个样本
3103007484cb5935efcd0d8abf28251e
,该解密函数足足被调用了179
次,正常情况下是肯定不会的,现在我们来将通过聚类处理的样本,做一下扫描,之后排序输出,看看哪些函数会被多次调用首先看一下寻找
xref
的实现得到了函数调用次数后,输出看一下
结果大概是这样的
通过分析该聚类样本可以发现函数被调用最多的,除了用于混淆的
printf
,之后便是解密函数,并且可以肯定是在[160-200]
的范围,所以提取IoC
的的信息代码结果大概是这样的
总结
这个是个人针对特定家族的一种提取
ioc
的方法,不具有普适作用,但是如果真是遇到静态配置解密一筹莫展时,并且很有可能在函数调用数量上有一定规则的话,不妨考虑使用该方法。至少目前我能够想到的就这一种方法The text was updated successfully, but these errors were encountered: