webshell检测方式深度剖析 ---统计学特征检测_neopi-程序员宅基地

技术标签: 恶意脚本检测  

概论

该篇文章讲述了NeoPI如何利用统计学特征来检测webshell,笔者认为NeoPI选择的这些统计学方法在webshell检测上有些鸡肋,没有太大的实用效果。

反而其中的各种统计学方法值得学习一下,因此文章会重点讲解这些统计学特征的原理,以求可以举一反三,并应用在其他领域。

统计学特征

NeoPi使用以下五种统计学特征检测方法,下面分别来分析各种方法的原理和代码实现(代码部分只选择了核心代码并附加了注释,方便大家阅读。):

重合指数

重合指数法是密码分析学的一种工具,主要用于多表代换的密码破译。
以纯英文文本为例,它的基本原理可以定义如下:

X = x 1 x 2 . . . x n X=x_1x_2...x_n X=x1x2...xn是一个长度为 n n n的英文字符串, X X X的重合指数定义为 X X X中的两个随机元素相同的概率,记为 I c ( X ) I_c(X) Ic(X)。假设英文字母 A A A B B B C C C,…在X中的出现次数分别为 f 1 f_1 f1 f 2 f_2 f2,…, f 25 f_{25} f25。显然,从X中任意选择两个元素共有 C 25 2 C^{2}_{25} C252种组合,选取的元素同时为第 i i i个英文字母的情况有 C f i 2 C^{2}_{f_i} Cfi2种组合, 0 < = i < = 25 0<=i<=25 0<=i<=25。因此,有
I c ( X ) = ∑ i = 0 25 ( C f 2 / C n 2 ) = ∑ i = 0 25 ( f i ( f i − 1 ) / n ( n − 1 ) ) I{_c}(X) =\sum_{i=0}^{25}(C_f^2/C_n^2) = \sum_{i=0}^{25}(f_i(f_i-1)/n(n-1)) Ic(X)=i=025(Cf2/Cn2)=i=025(fi(fi1)/n(n1))

根据统计,在英文中各个字母出现的频率是特定的,如下表 :

字母 概率 字母 概率
A 0.082 N 0.067
B 0.015 O 0.075
C 0.028 P 0.019
D 0.043 Q 0.001
E 0.127 R 0.060
F 0.022 S 0.063
G 0.020 T 0.091
H 0.061 U 0.028
I 0.070 V 0.010
J 0.002 W 0.002
K 0.008 X 0.001
L 0.040 Y 0.020
M 0.024 Z 0.001

将英文字母A,B,C,…,Z的期望概率分别记为 p 0 , p 1 , p 2 , . . . , p 25 p_0,p_1,p_2,...,p_{25} p0p1p2...p25,则有一段正常英文文本的期望重合指数为 I c ( X ) ≈ ∑ i = 0 25 ( p i 2 ) = 0.065 I_c(X)\approx \sum_{i=0}^{25}(p_i^2) = 0.065 Ic(X)i=025(pi2)=0.065

如上所述,一个纯英文的且编码风格良好(一般在软件开发时,会采用统一的函数及有意义的变量名编写)的源代码计算出的重合指数会趋近于0.065。考虑到文件中的中文注释,虽然计算出的重合指数会偏离0,065,但同样会趋于相似,呈现正态分布。

而加密或者混淆后的webshell 与原 web 应用不相关,其字符的排列通常没有特征可言,计算出的重合指数与正常文件的重合指数相差较大(混淆后的重合指数通常较小),一定程度上,可以作为webshell判定的依据。

重合指数的计算比较简单,代码如下:

# @param data 从文件中取出的全部内容数据
 # @return ic 返回计算好的重合指数
 def index_of_coincidence(data):
       """计算文件内容的重合指数"""
       if not data:
           return 0
       char_count = 0       # 保存在data中任意选择两个字符,这两个字符相同的情形的数量
       total_char_count = 0 # 保存在data所有字符的数量

        # 遍历单字节代表的256字符
       for x in range(256):
           char = chr(x)
           charcount = data.count(char)              # 计算当前字符在data中的数量
           char_count += charcount * (charcount - 1) # 计算在data中任意选择两个字符,这两个字符都为当前字符的情形的数量,并累加
           total_char_count += charcount             # 计算当前字符在data中的数量,并累加
       
       # 按照重合指数的计算方法进行计算
       ic = float(char_count)/(total_char_count * (total_char_count - 1))
       return ic

信息熵

熵,是一个热力学的概念,用来度量封闭系统的混乱程度。但在历史的发展中,造就了它非常丰富的内涵,进入了很多学科的视野。

1948年,香农提出了“信息熵”的概念,解决了对信息的量化度量问题。信息量是对信息的度量,就跟时间的度量是秒一样,当我们考虑一个离散的随机变量x的时候,当我们观察到的这个变量的一个具体值的时候,我们接收到了多少信息呢?

多少信息用信息量来衡量,而我们接受到的信息量跟具体发生的事件有关。

信息的大小跟随机事件的概率有关。越小概率的事情发生了产生的信息量越大,如太阳从西边升起来了;越大概率的事情发生了产生的信息量越小,如太阳从东边升起来了(肯定发生,没什么信息量)。
信息熵的公式定义如下:

H ( X ) = − ∑ i = 1 N p ( x i ) l o g ( p ( x i ) ) H(X)=- \sum_{i=1}^{N}p(x_i)log(p(x_i)) H(X)=i=1Np(xi)log(p(xi))
其中, p ( x i ) 代 表 随 机 事 件 p(x_i)代表随机事件 p(xi) x i x_i xi的概率,对数一般以2为底。对应到文件熵上,一般使 p ( x i ) p(x_i) p(xi)为字符 x i x_i xi在文件内容中出现的概率。

那么类似于重合指数,加密混淆后的webshell通常通篇都是没有任何意义和规律的字符, 其通过计算公式得出的信息熵值会偏离平均值较大。

计算信息熵的代码如下:

# @param data 从文件中取出的全部内容数据
 # @return entropy 返回计算出的文件熵
def calculate(self,data):
       """计算文件信息熵."""

       if not data:
           return 0
           
       entropy = 0 # 保存最终熵值
       self.stripped_data =data.replace(' ', '') # 去掉文件内容中的空格
       
       # 遍历所有asci 256个字符
       for x in range(256):
           p_x = float(self.stripped_data.count(chr(x)))/len(self.stripped_data) # 计算单个字符出现的概率
           if p_x > 0:
               entropy += - p_x * math.log(p_x, 2) # 计算该字符的熵值并累加
       return entropy

最长单词

一般在软件开发时,其使用的字符串、函数名、变量名都会尽可能有规律和简短,但是,通过变形和加密往往会构造;超长的字符串, 通过检测代码中的最长字符串,并把最有可能是 webshell 的文件提供给管理员判断。

代码如下:

# @param data 从文件中取出的全部内容数据
# @return longest_word, longest 返回最长单词的内容和长度
def LongestWord(self,data):
       """查找文件内容中长度最长的单词"""
       if not data:
           return "", 0

       longest = 0 # 保存最长单词的长度
       longest_word = "" # 保存最长单词的内容
       
       words = re.split("[\s,\n,\r]", data) # 将文件内容按照空格和换行进行分词
       if words:
           for word in words:
               length = len(word)
               if length > longest: # 循环查找最长单词
                   longest = length
                   longest_word = word
       return longest_word,longest

恶意特征

在文件中搜索已知的恶意代码字符串片段,通过正则表达式,在文件内查找预定义的恶意特征。
这部分其实是静态检测,但是NeoPI也扩展添加了这部分的能力。

代码如下:

# @param data 从文件中取出的全部内容数据
# @return len(matches) 返回匹配的数量
def signature_nasty(self, data): 
       """查找文件的恶意特征"""
       if not data:
           return "", 0
       
       # 查找文件内下面所列的恶意函数 
       valid_regex = re.compile('(eval\(|file_put_contents|base64_decode|python_eval|exec\(|passthru|popen|proc_open|pcntl|assert\(|system\(|shell)', re.I)
       matches = re.findall(valid_regex, data)
       return len(matches)

压缩比

正常的代码通常编码风格良好,并且文件内有一定的空行和空格作为分隔,进行压缩时能有较大的压缩比。但是经过混淆后的代码通常没有空格和空行,而且字符顺序混乱,进行压缩时压缩比较小。

代码如下:

# @param data 从文件中取出的全部内容数据
# @return ratio 返回计算出的压缩比
def calculate(self, data):
       if not data:
           return "", 0
       compressed = zlib.compress(data)
       ratio =  float(len(data)) / float(len(compressed))
       self.results.append({
    "filename":filename, "value":ratio})
       return ratio

检测结果评测

NeoPI本身不给出一个文件是不是webshell的判断,它只是计算各种统计特征值,然后针对每一个特征值做出一个排名。在实际应用中,可以选择任意特征值的排名组合来判断。

为了让测试更有代表性,笔者采用如下策略:

首先进行如下形式化定义:

W m i n ( x ) = W o r d P r e s s 中 相 应 特 征 最 小 的 x 个 文 件 的 平 均 值 W_{min}(x) = WordPress中相应特征最小的x个文件的平均值 Wmin(x)=WordPressx
W m a x ( x ) = W o r d P r e s s 中 相 应 特 征 最 大 的 x 个 文 件 的 平 均 值 W_{max}(x) = WordPress中相应特征最大的x个文件的平均值 Wmax(x)=WordPressx
B m i n ( x ) = 300 个 黑 样 本 中 相 应 特 征 最 小 的 x 个 文 件 的 平 均 值 B_{min}(x) = 300个黑样本中相应特征最小的x个文件的平均值 Bmin(x)=300x
B m a x ( x ) = 300 个 黑 样 本 中 相 应 特 征 最 大 的 x 个 文 件 的 平 均 值 B_{max}(x) = 300个黑样本中相应特征最大的x个文件的平均值 Bmax(x)=300x

1、重合指数判断策略

2、信息熵判断策略

3、最长单词判断策略
同信息熵的判断策略

4、恶意特征
存在恶意特征则判定为webshell

5、压缩比
同信息熵的判断策略

实际测试结果如下:

统计特征 检出率 误报率
重合指数 94% 0%
信息熵 58% 0.5%
最长单词 42% 0%
恶意特征 79% 4%
压缩比 10% 0%

notes:由于NeoPI主要用来检测混淆webshell,所以笔者的阈值选择优先于黑样本和白样本中的混淆文件的特征值。

总结

NeoPi的检测重心在于识别混淆代码,它常常在识别模糊代码或者混淆编排的木马方面表现良好,但是也依赖于检测阈值的选取。同时,NeoPi的检测机制对未经模糊处理的代码检测能力较弱。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_31032141/article/details/107207911

智能推荐

实现椭圆曲线上离散对数问题的求解_椭圆离散-程序员宅基地

文章浏览阅读5.3k次,点赞3次,收藏14次。密码学实验课的题目,分享一下自己的代码,可能不够完美,供参考~自己动手才是王道哦!一、实验目的掌握椭圆曲线上的加法定律;熟练求解椭圆曲线上的离散对数问题。二、实验原理(1)有限域GF§上的椭圆曲线:对于固定的a和b,满足形如方程y2≡x3+ax+b(mod p) ( a,b,x,yGF§且4a3+27b2(mod p)≠0).(2)椭圆曲线Ep(a,b)上的加法定义如下:设P,..._椭圆离散

【golang】go语言中用gotest进行单元测试提示undefined的解决办法-程序员宅基地

文章浏览阅读211次,点赞4次,收藏3次。最近在学习zinx框架时,写完单元测试代码运行go test 时报undefined错误。

大数据认知实习的实习目的_如何在数据实习中取得成功-程序员宅基地

文章浏览阅读3.4k次。大数据认知实习的实习目的With internship season well underway, we reached out to some Alteryx ACEs, top analytics experts and participants in the Alteryx Community, to see what advice they’d offer to interns in da..._实习目的大数据

地理信息系统学习笔记——地图开发相关介绍_地图数据发布系统的开发是否属于研发活动-程序员宅基地

文章浏览阅读3k次。最近看了一些地图开发的文章,在此做一下记录。 估计很大一部分人认为地图开发就是基于高德地图、百度地图、腾讯地图结合自己的业务进行二次开发,这只是一个方面。在这些地图上做的二次开发仅仅是方便用户导航到自己业务所在的位置,要是再往深层次走的话,做一些定制化的地图的开发则不止力不从心,很多时候也无法实现。比如城市给水排水管网信息管理,这就是定制性开发,也可称行业应用。本人通过学习Naa(李晓辉) 该博主_地图数据发布系统的开发是否属于研发活动

全面理清概念,C风格字符串(多重定义方式,区别,易错点)-程序员宅基地

文章浏览阅读1.5k次,点赞4次,收藏11次。字符数组方式定义C风格字符串编译器会自动计算字符串长度,并为字符数组分配适当的空间(包括空字符)。这个例子中,str 是一个包含14个字符的数组,包括13个实际字符和1个空字符。指针方式定义C风格字符串str 是一个指针,它指向一个字符串常量。因此,尽管你可以更改指针指向的位置,但不能通过指针来更改字符串内容。试图修改字符串常量的内容可能导致未定义的行为。// 错误!不能修改字符串常量的内容在使用这种写法时,字符串的长度不需要显式指定。编译器会自动计算字符串长度,并在末尾添加空字符。_c风格字符串

c语言指针定义和使用-程序员宅基地

文章浏览阅读948次,点赞2次,收藏9次。数据内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。定义指针变量定义指针变量与定义普通变量非常相似,不过要在变量名前加星号 * ,格式为:datatype *name ; 或者dataype *name = value;* 表示一个指针变量,datatype表示该指针变量所指向的数据的类型。例如:int *p1;p1 是一个指向int...

随便推点

微而无界,广而无界-程序员宅基地

文章浏览阅读225次,点赞4次,收藏7次。所以研究了人体,然后去研究组织,研究了组织,然后去研究细胞,研究了细胞然后研究基因,研究了基因,然后学物理化学去了,物理有分子,分子有原子,原子有质子中子,然后有电子,可以预测随着科技进步,不会出现最小的粒子。比如说一个api函数,你只需要怎么用就可以了,不用管是怎么具体实现的,如果每个函数都去搞懂怎么实现的,怕是要先把系统彻底搞懂,我怕系统源码你看不完。比如一台光刻机都是按照亿来计算,大的东西比如火箭,飞机也是按照亿来计算。如果资源不足,钱不够,我们就应该研究不大不小的东西。汽车都还是大了,材料贵。

[debug] PyTorch报错:ConnectionResetError: [Errno 104] Connection reset by peer_pytorch issue connection reset by peer-程序员宅基地

文章浏览阅读1w次。问题描述:使用PyTorch 1.10.0,训练报错:ConnectionResetError: [Errno 104] Connection reset by peer问题解析参见pytorch的issueI believe the issue is only triggered for the case that bothpersistent_workers and pin_memory are turned on and iteration isterminated at the t_pytorch issue connection reset by peer

openwrt 之 DNS配置文件修改_openwrt nameserver-程序员宅基地

文章浏览阅读2.9w次,点赞2次,收藏5次。参考:http://www.openwrt.org.cn/bbs/thread-1639-1-1.html我在openwrt的web界面修改的wan口dns服务器为8.8.8.8。但是通过ssh查看/etc/resolv.conf文件发现总是被重置为了:root@Wrv54g:~# cat /etc/resolv.conf search lannam_openwrt nameserver

Linux学习必备基础知识_链接程序,共同完成任务-程序员宅基地

文章浏览阅读257次。1. 计算机的功能及组成_链接程序,共同完成任务

【推荐】骡友们推荐的各个学习英文网站的汇总【选择自CSDN i_like_cpp 的 Blog】...-程序员宅基地

文章浏览阅读2.9k次。骡友们推荐的各个学习英文网站的汇总。--------------------------------------------------------------------------------http://www.edunet.com/elt 主题:是一个全方位的学英语作为第二语言的网站 功能:聊天室,语法讲解,练习,小测试,成语讲解 特色:特别深入地介绍了语法,听力,沟通技巧等 对象:ESL..._qc 学习网站

DCN神州数码WAF-P-2021命令行恢复出厂_dcn-waf怎么用console重置-程序员宅基地

文章浏览阅读213次。注意:执行该命令将会清除设备的所有配置信息,包括网络配置、安全策略等,并将设备恢复到出厂设置时的默认配置。在执行该操作之前,请务必备份重要的设备配置信息。启动后,网线接G5口web登陆https://192.168.254.1输入帐号密码admin/yunke1234!Console接入波特率9600,输入帐号密码admin/yunke1234!进入后输入facroryreset -R回车,设备自动重启。_dcn-waf怎么用console重置

推荐文章

热门文章

相关标签