goby识别指纹的功能很不错,想试试能不能把goby指纹识别里面的指纹提取出来,移植到自己的扫描器中,结果后面到了逆向虚拟机的程度。。于是搜索YARA关键字,就能再次定位到goby的指纹规则部分。yara编译后文件的规则真是多变,并且每个版本的规则还不兼容。。yara虚拟机整个虚拟机执行在exec.c文件的yr_execute_code函数上,在上面打上断点。OP_HALT是中断标志,这个opcode出现代表执行完毕。接下来就是理解这些yara指令了。

goby识别指纹的功能很不错,想试试能不能把goby指纹识别里面的指纹提取出来,移植到自己的扫描器中,结果后面到了逆向虚拟机的程度。。
goby的crules在goby 最初的版本中,翻看了一些感兴趣的资源文件后看到有个crules文件。
它的文件头是yara文件
并且里面包含了它的指纹
通过文本提取,可以把指纹规则提取出来,但是多个指纹规则之间有and、or之类的逻辑关系,并不清楚。
新版的crules提取现在goby使用了最新的go版本1.16
在go1.16,嵌入资源有个官方实现,叫go embed,可以根据在二进制中搜索embed.FS来确认是否使用了这个特性。
然后我自己测试了一下,看看go embed内嵌资源是如何实现的,结果发现资源直接在二进制中明文显示。
于是搜索YARA关键字,就能再次定位到goby的指纹规则部分。
yara结构分析因为不知道指纹规则之间的逻辑关系如何提取,所以只能看yara代码是怎么做的。
看到goby使用了这个库https://github.com/hillu/go-yara,应该就是用来解析规则的,看这个库的实现,它只是c版本yara源码的封装,所以还是要去看yara的代码。
官方仓库是 https://github.com/VirusTotal/yara
我下载了2.0 3.0 和4.x最新版的源码,发现里面的文件版本和这个都对不上。一度怀疑是魔改的yara?
yara编译后文件的规则真是多变,并且每个版本的规则还不兼容。。这上面踩了一下坑。
最后我在yara-3.10.0找到了可以符合goby crules文件头的代码
yara加载编译后的文件在yr_arena_load_stream,可以看到读取magic为yara的标记以及版本判断相关代码。
crules文件开头对应的数据结构如下
typedef struct _ARENA_FILE_HEADER{charmagic[4];uint32_tsize;uint32_tversion;} ARENA_FILE_HEADER;
之后读取指定长度的字节后,剩余的字节都是用于重定向地址用
再之后在 yr_rules_load_stream,先通过一个结构映射头
typedef struct _YARA_RULES_FILE_HEADER{DECLARE_REFERENCE(YR_RULE*, rules_list_head);DECLARE_REFERENCE(YR_EXTERNAL_VARIABLE*, externals_list_head);DECLARE_REFERENCE(const uint8_t*, code_start);DECLARE_REFERENCE(YR_AC_MATCH_TABLE, match_table);DECLARE_REFERENCE(YR_AC_TRANSITION_TABLE, transition_table);} YARA_RULES_FILE_HEADER;
然后再解析出每个节的地址。
new_rules->code_start = header->code_start;new_rules->externals_list_head = header->externals_list_head;new_rules->rules_list_head = header->rules_list_head;new_rules->match_table = header->match_table;new_rules->transition_table = header->transition_table;
得到每个表的位置。yara编译后的文件加载过程到此完成。
简单来说,文件头前面一部分是映射为_ARENA_FILE_HEADER的结构,再之后的结构就是一些表的地址。
yara结构总结说的可能比较抽象,得自己看yara代码,不断去调试,大概就知道yara是怎么处理和加载编译后的yara文件的了。
yara的编译机制就是把内存中的yara数据结构保存为一个文件,然后对一些重定向内容做一些处理。
加载过程也是同理,之后程序运行位置交到new_rules->code_start这个地址上,它将会运行yara的虚拟机程序,虚拟机对所有规则做出判断。
yara虚拟机整个虚拟机执行在exec.c文件的yr_execute_code函数上,在上面打上断点。
此时的调用堆栈
单步运行下来,发现这是一个栈式虚拟机,从地址获取opcode,根据opcode执行。
OP_HALT是中断标志,这个opcode出现代表执行完毕。
exec.h定义了各种opcode和int的关系
经过调试,发现每次规则开始的时候,都会调用OP_INIT_RULE,
这个执行地址后面的地址就是RULE的数据结构,记录了meta等信息。
typedef struct _YR_INIT_RULE_ARGS{DECLARE_REFERENCE(YR_RULE*, rule);DECLARE_REFERENCE(const uint8_t*, jmp_addr);} YR_INIT_RULE_ARGS;
typedef struct _YR_RULE{int32_t g_flags;// Global flagsint32_t t_flags[MAX_THREADS];// Thread-specific flagsDECLARE_REFERENCE(const char*, identifier);DECLARE_REFERENCE(const char*, tags);DECLARE_REFERENCE(YR_META*, metas);DECLARE_REFERENCE(YR_STRING*, strings);DECLARE_REFERENCE(YR_NAMESPACE*, ns);// Used only when PROFILING_ENABLED is definedclock_t clock_ticks;} YR_RULE;
typedef struct _YR_META{int32_t type;YR_ALIGN(8) int64_t integer;DECLARE_REFERENCE(const char*, identifier);DECLARE_REFERENCE(char*, string);} YR_META;
通过对虚拟机整个执行过程的理解,可以编写一个yara反编译器了。
搜索了下github,有一个开源的 https://github.com/jbgalet/yaradec,但是不支持这个版本,需要自己改改。
所以我根据yara源码和这个开源的反编译器,编写了一个yara反编译器。
反编译运行反编译器,将yara的opcode转换为指令的形式,我的反编译器运行后输出如下
有的opcode会和一些字符串作关联,所以我把它读取出来放到注释部分,opcode就是yara定义的opcode,最前面的数字代表当前执行的位置,因为有的语句会跳转到其他位置,方便看。
接下来就是理解这些yara指令了。
一个简单的例子
205 : OP_INIT_RULE;{"flags": 0, "identifier": "rule_2", "metadata": {"rule_id": "2", "level": "5", "softhard": "2", "product": "CNPOWER-OA(OA8000)", "company": "CNPOWER", "category": "OA", "parent_category": "Enterprise Application"}, "ns": "default:"}222 : OP_OBJ_LOAD 5050743 ;fofa231 : OP_OBJ_FIELD 5050748 ;body_contains240 : OP_PUSH 5050762 ;/oaapp/webobjects/oaapp.woa249 : OP_CALL 5050801 ;s258 : OP_OBJ_VALUE;259 : OP_MATCH_RULE 260 ;
OP_INIT_RULE 是初始化这个规则的meta信息
OP_OBJ_LOAD 是载入fofa模块
OP_OBJ_FIELD是模块的字段,即 body_contains
OP_PUSH 将/oaapp/webobjects/oaapp.woa压入堆栈
之后OP_CALL调用函数,OP_OBJ_VALUE 获取结果,OP_MATCH_RULE匹配完成。
所以,可以想象这一段指令对应的原先规则为
fofa.body_contains("/oaapp/webobjects/oaapp.woa")
上述是一个简单匹配过程,如果一些规则含有逻辑运算,是怎样的呢?
例如下面的例子
445 : OP_INIT_RULE;{"flags": 0, "identifier": "rule_5", "metadata": {"rule_id": "5", "level": "3", "softhard": "2", "product": "MongoDB", "company": "MongoDB, Inc", "category": "Database System", "parent_category": "Software System"}, "ns": "default:"}462 : OP_OBJ_LOAD 5051424 ;fofa471 : OP_OBJ_FIELD 5051429 ;body_contains480 : OP_PUSH 5051443 ;<a href="/_replset">replica set status</a></p>489 : OP_CALL 5051501 ;s498 : OP_OBJ_VALUE;499 : OP_JTRUE 642 ;if sucess508 : OP_OBJ_LOAD 5051503 ;fofa517 : OP_OBJ_FIELD 5051508 ;protocol_contains526 : OP_PUSH 5051526 ;mongodb535 : OP_CALL 5051545 ;s544 : OP_OBJ_VALUE;545 : OP_JTRUE 640 ;if sucess554 : OP_OBJ_LOAD 5051547 ;fofa563 : OP_OBJ_FIELD 5051552 ;body_contains572 : OP_PUSH 5051566 ;you are trying to access mongodb581 : OP_CALL 5051610 ;s590 : OP_OBJ_VALUE;591 : OP_JTRUE 638 ;if sucess600 : OP_OBJ_LOAD 5051612 ;fofa609 : OP_OBJ_FIELD 5051617 ;title_contains618 : OP_PUSH 5051632 ;mongod.exe627 : OP_CALL 5051654 ;s636 : OP_OBJ_VALUE;637 : OP_OR;638 : OP_NOP;639 : OP_OR;640 : OP_NOP;641 : OP_OR;642 : OP_NOP;643 : OP_MATCH_RULE 644 ;
从这段指令可以精简为
验证规则1验证规则2验证规则3验证规则4ororor
可以看出这是一个后缀表达式(逆波兰表达式)处理的逻辑关系。
要提取逻辑表达式的话,就是把这段后缀表达式转换为可读的中缀表达式。
我写了一个python脚本来完成这个操作
def zhuanh(l: list):l.reverse()s = []fuhao = ['OP_AND', 'OP_OR', 'OP_NOT']while len(l) > 0:x = l.pop()if x not in fuhao:s.append(x)else:r1 = s.pop()if x == 'OP_NOT':s.append("!{}".format(r1))else:r2 = s.pop()x = x.replace("OP_AND", "and")x = x.replace("OP_OR", "or")s.append(f"({r1} {x} {r2})")if len(s) != 1:raise Exception("错误")return s[0]if __name__ == '__main__':a = [1, 2, 3, 4, 'OP_OR', 'OP_OR', 'OP_OR']s = zhuanh(a)print(s)
1,2,3,4代表这四个规则,最后输出结果即
接下来找一个规则复杂一些的尝试一下
1229: OP_INIT_RULE;{"flags": 0, "identifier": "rule_9", "metadata": {"rule_id": "9", "level": "5", "softhard": "2", "product": "TongDa-OA", "company": "TongTa", "category": "OA", "parent_category": "Enterprise Application"}, "ns": "default:"}1246: OP_OBJ_LOAD 5052822 ;fofa1255: OP_OBJ_FIELD 5052827 ;body_contains1264: OP_PUSH 5052841 ;/static/templates/2013_01/index.css/1273: OP_CALL 5052889 ;s1282: OP_OBJ_VALUE;1283: OP_JTRUE 1810 ;if sucess1292: OP_OBJ_LOAD 5052891 ;fofa1301: OP_OBJ_FIELD 5052896 ;body_contains1310: OP_PUSH 5052910 ;javascript:document.form1.uname.focus()1319: OP_CALL 5052961 ;s1328: OP_OBJ_VALUE;1329: OP_JTRUE 1808 ;if sucess1338: OP_OBJ_LOAD 5052963 ;fofa1347: OP_OBJ_FIELD 5052968 ;body_contains1356: OP_PUSH 5052982 ;href="/static/images/tongda.ico"1365: OP_CALL 5053026 ;s1374: OP_OBJ_VALUE;1375: OP_JTRUE 1806 ;if sucess1384: OP_OBJ_LOAD 5053028 ;fofa1393: OP_OBJ_FIELD 5053033 ;body_contains1402: OP_PUSH 5053047 ;<link rel="shortcut icon" href="/images/tongda.ico" />1411: OP_CALL 5053113 ;s1420: OP_OBJ_VALUE;1421: OP_JTRUE 1804 ;if sucess1430: OP_OBJ_LOAD 5053115 ;fofa1439: OP_OBJ_FIELD 5053120 ;body_contains1448: OP_PUSH 5053134 ;oa提示:不能登录oa1457: OP_CALL 5053171 ;s1466: OP_OBJ_VALUE;1467: OP_JFALSE 1514 ;if sucess1476: OP_OBJ_LOAD 5053173 ;fofa1485: OP_OBJ_FIELD 5053178 ;body_contains1494: OP_PUSH 5053192 ;紧急通知:今日10点停电1503: OP_CALL 5053236 ;s1512: OP_OBJ_VALUE;1513: OP_AND;1514: OP_NOP;1515: OP_JTRUE 1802 ;if sucess1524: OP_OBJ_LOAD 5053238 ;fofa1533: OP_OBJ_FIELD 5053243 ;title_contains1542: OP_PUSH 5053258 ;office anywhere 20131551: OP_CALL 5053290 ;s1560: OP_OBJ_VALUE;1561: OP_JTRUE 1800 ;if sucess1570: OP_OBJ_LOAD 5053292 ;fofa1579: OP_OBJ_FIELD 5053297 ;title_contains1588: OP_PUSH 5053312 ;office anywhere 20151597: OP_CALL 5053344 ;s1606: OP_OBJ_VALUE;1607: OP_JTRUE 1798 ;if sucess1616: OP_OBJ_LOAD 5053346 ;fofa1625: OP_OBJ_FIELD 5053351 ;body_contains1634: OP_PUSH 5053365 ;tongda.ico1643: OP_CALL 5053387 ;s1652: OP_OBJ_VALUE;1653: OP_JFALSE 1748 ;if sucess1662: OP_OBJ_LOAD 5053389 ;fofa1671: OP_OBJ_FIELD 5053394 ;title_contains1680: OP_PUSH 5053409 ;oa1689: OP_CALL 5053423 ;s1698: OP_OBJ_VALUE;1699: OP_JTRUE 1746 ;if sucess1708: OP_OBJ_LOAD 5053425 ;fofa1717: OP_OBJ_FIELD 5053430 ;title_contains1726: OP_PUSH 5053445 ;办公1735: OP_CALL 5053463 ;s1744: OP_OBJ_VALUE;1745: OP_OR;1746: OP_NOP;1747: OP_AND;1748: OP_NOP;1749: OP_JTRUE 1796 ;if sucess1758: OP_OBJ_LOAD 5053465 ;fofa1767: OP_OBJ_FIELD 5053470 ;body_contains1776: OP_PUSH 5053484 ;>新oa办公系统1785: OP_CALL 5053528 ;s1794: OP_OBJ_VALUE;1795: OP_OR;1796: OP_NOP;1797: OP_OR;1798: OP_NOP;1799: OP_OR;1800: OP_NOP;1801: OP_OR;1802: OP_NOP;1803: OP_OR;1804: OP_NOP;1805: OP_OR;1806: OP_NOP;1807: OP_OR;1808: OP_NOP;1809: OP_OR;1810: OP_NOP;1811: OP_MATCH_RULE 1812 ;
优化为的后缀表达式
123456and7891011orand12orororororororor
后缀表达式转中缀表达式结果
((((((((12 or ((11 or 10) and 9)) or 8) or 7) or (6 and 5)) or 4) or 3) or 2) or 1)
就这样,能将所有规则之间的逻辑关系处理了。
中缀表达式处理程序有一个小问题,就是表达式的括号会很多,这个自行优化下吧 = -
最后我将规则处理为了json格式,方便阅读和扫描器引用,类似如下
goby因为只需要访问一次首页,剩下的由yara规则进行识别,所以速度会很快。用yara识别web指纹学习到了。
如果要自己写扫描器引入fofa指纹的话,还需要写个表达式解析器来解析各种规则,这个我以前也写过了 https://x.hacking8.com/post-383.html
那么能不能直接用yara调用goby的指纹呢?
理论上是可以的,只是需要自己写一个名称为fofa的模块,实现所有判断的方法,在编译到yara中。
申明:本文仅供技术交流,请自觉遵守网络安全相关法律法规,切勿利用文章内的相关技术从事非法活动,如因此产生的一切不良后果与文章作者无关 本文原创作者:w9ay
