实现组件指纹识别burp插件

Posted on 2025-08-15  69 Views


大伙在开发web应用的时候经常会大改开源框架,或者调用外部组件包;大伙如果大改像ruoyi那种人尽皆知的框架,其实都没关系,就算是它化成灰了我应该都能认得出来,但是一些相对冷门的,可能我知道有洞,感觉看起来像之前自己研究过,但是就是想不起来是哪个框架,这时再去盲目找之前的笔记就会焦头烂额

还有一些组件特征,一些报错,流量大的时候就不小心没看到,漏掉了,这对绩效提升是没有优势的,也少了一些潜在的吹牛资本,于是为了避免此类危害发生,我做了这个东西

为了让自己用的放心,我决定先看看别人有没有现成的工具可以用,毕竟这种事情以前出过事故:我周末努力2天做出来的国密代理,发现别人已经做出来现成的了,而且包好用;那天周一我很难受,都在想是不是请假回家重新过周末

这次我放心了,现有的插件不够猛,大部分是基于单一关键字检测做的

那大伙就要问了:你又能端上来什么菜呢?

这次我还原古法,用二进制机械古语的方式实现指纹识别功能,而用户只需要编写python风味赞美诗即可;“勤劳的机魂会为你处理圣典,将朴素的文字锻造成神圣的机械古语……编译成功!赞美欧姆尼赛亚!赞美万机之神!”

CA8BADB543CD6054E57B1D6D052D46A3

大伙先看看效果,我先写下面这段指纹,这是根据经典款ruoyi前端文件的特征写的

image

然后访问官方demo站,可以看到对应的前端文件已经被识别出来了

image

大伙可能到这里突然看懂了,你之前不是做了一个表达式翻译的东西吗?这不是一样?炒冷饭

这次也不算炒冷饭,效率比之前应该高一点(吧?),我把隔夜饭放在这里,喜欢吃隔夜饭的同学们可以再吃:

基于语义分析的表达式代码转换 – zgbsm's note

之前的表达式转换是直接遍历了AST,但是这次因为每个http请求都要过一遍指纹,所以性能还是要优化一下

了解编译过程的大伙应该都知道,编译到最后我们写的代码会变成exe,exe里面是计算机能直接执行的汇编机器码,虽然因为java是一种比较高层的语言,很难直接执行汇编,但是我们可以把AST“编译”成汇编风味执行链

在遍历AST的过程中,需要对不同的关键字做判断;比如说表达式里面有个函数调用,那具体是调用哪个函数呢?这就需要在java里面写代码判断。如果我们不做汇编风味执行链,直接跑AST的话,那每个http请求、每个指纹都要进行这种判断,指纹多了,判断的数量就很多,很恐怖

这里我简化了真实世界的汇编执行环境,只做了10个通用寄存器,没有flags,有个小栈,没有函数栈帧,堆内存由java管理;默认第0个寄存器保存返回值,所有指令的结果都会存储在第0个寄存器上,call指令只接受1个参数,参数放在第几个寄存器上我也忘了,栈一般是用来临时存一下寄存器的,只有push和pop指令才能访问栈,mov指令只能用来将立即数赋值给寄存器

但是实际开发的时候我又有点后悔没有直接照抄真实的汇编环境了,因为汇编它发展的这么复杂,一定是有它自己的道理,还好我只是用来写表达式引擎,否则写一半就要考虑重构了

在java里面做这个风味汇编并不能让我的表达式引擎效率达到极致,巧妙借助C语言和汇编的混编才能将执行效率推向新的巅峰。我是天天发电的渗透工程师,这是我被电晕前最后的想法:我觉得应该在C语言里面VirtualAlloc一片可执行的内存,然后直接把表达式汇编“溢出”进去,再pushad然后jmp进去跑,每个指纹跑完之后直接把结果“任意地址写入”到神秘的堆数组上,最后再popad当作无事发生,对于C语言来说,整个过程就像是做卷子的时候睡着了,睡着了还做梦,感觉自己的脑子被人拿去用了,身体还一抽一抽的,最后醒来发现卷子已经做完

这样实现固然好玩,但是不能忘记一个基本事实:burp是java写的,每个http请求都要内存共享给c语言,内存共享的操作让人犯嘀咕,稳定性这块感觉需要千锤百炼,不是靠“俺寻思”就能做出来的

回到我们风味汇编的设计上来,当我们遍历抽象语法树的时候,通常会遇到树的结点,其实风味汇编的生成非常简单,我们只用关注当前节点就行,其他不用管

比如下面这个树,这个boolean_operator有“左”、“右”两个子树,子树怎么执行我不关心,反手递归调用自己,先生成left子树的风味汇编,按照约定,这一坨子树的汇编会把执行结果放在0号寄存器,那我就先push到栈上保存下来,然后再生成right子树的,同样,right子树的风味汇编运行结果会存储在0号寄存器,这时我就把栈里的值pop到1号寄存器,再调用cmp %1, %0即可

image

cmp的逻辑和真实的汇编不一样,因为它是风味汇编,底层是java代码,所以我直接就把比较的结果存在0号寄存器上

万事俱备,只是事情没那么简单,我们不得不考虑and表达式,这个表达式有一种人尽皆知的优化操作,就是当这个表达式的左边值不为true时,右边就直接砍掉,这时就需要在“编译”的过程中记录右边表达式的风味汇编长度,然后在左边表达式后面插入jne进行判断和跳过

最后把所有的指纹都转成风味汇编,连到一起,就变成了现在的“汇编风味java执行链”,在每个指纹的风味汇编串末尾,我都插了桩,新增了一个“callback”汇编,用来获取每段指纹执行结束之后,0号寄存器的值,因为这时的0号寄存器的值代表的是该指纹是否命中

// 再写5毛钱的

因为这篇文章是放学回家写的,要先做完作业之后才能开始写这篇文章,所以写了3天,今天是第3天,我看到昨天留了注释,说是要再写5毛钱的,但是不记得要写什么内容了,所以这个注释我先留着,万一哪天想起来了,就再写5毛钱的


你好