PHP Webshell AI Engine Based On Runtime Apicall Monitor Log
1. PHP Runtime Apicall日志监控
0x1:如何完整获取文件的运行时Apicall
我们知道,PHP这类脚本型文件,按照行为模式可以大致分为几类:
- 自驱动型:这类样本本身带有明确的行为目的,例如写一段shell到本地磁盘文件
- 外部参数驱动型:这类样本本质上是一种shellcode loader的角色,常常通过$_POST/GET这类外部参数获取实际要执行的代码
- 可选菜单型:这类样本内置了一系列功能模块,使用者可以通过传入不同的参数,调用不同的功能模块,实现不同的功能
对于第一种自驱动型,直接运行样本即可,通过zend hook可以获取到样本的所有runtime apicall行为。
但是对于第2,3种,就需要依赖智能词法分析,通过参数填充,使其可以正常运行。
2. 面临的挑战
我们从面临的攻防对抗角度来看特征工程的侧重点和改进方向
0x1:加密加壳问题
php和二进制病毒一样,会采用加壳手段对文件的源代码进行保护。例如下图:
对于apicall序列监控来说,每个文件最开始的apicall肯定全部是相同的,即都是进行脱壳过程的一段api序列。
但是对于序列建模来说,正常文件和异常文件还是存在区别的:
正常文件运行:脱壳api(S1,S2,....,Sn)+ 正常apicalls
异常文件运行:脱壳api(S1,S2,....,Sn)+ 异常apicall
正常文件和异常文件的区别就在于脱壳后的序列执行。
0x2:正常文件插马问题
和二进制病毒的shellcode植入一样,webshell是可以插入正常的文件中寄生的,例如下图:
对这类文件来说,正常文件代码的apicall会和webshell shellcode的apicall混在一起,对序列模型的挑战在于“局部特征敏感性”,即正常文件和被插马文件的apicall序列特征的区别只会在局部的某一段或某几段序列里体现。
0x3:序列执行完整性
因为沙箱是属于runtime形式的apicall监控系统,因此对待检测样本的“仿真完整度”是一个很重要的考核指标,主要问题有2个
1. 参数依赖 - 参数补丁技术
因为缺少GPC外部参数,所以runtime sandbox的apicall运行结果为:
api __main__-key argv "{"value": [[0, "arg", []]]}" return "{"value": null}"
2. 控制流依赖 - 控制流智能选取技术
webshell除了依赖外部参数之外,可能还设置了某些if-else判断流支,只有符合特定的参数条件才能进入,如上图所示,如果参数不对,则无法进入到if code block,也就无法执行到关键的eval函数,apicall序列在if-else前的unserialize()就停了下来了。
3. 特征工程
0x1:apicall序列
0x2:apicall的统计分布特征
0x3:argv参数
0x4:return返回值
0x5:专家经验特征
count(opc):总序列长度:在样本观察中发现,正常样本的apicall会倾向于很长。
count(distinct opc):出现的opc统计个数
4. 模型设计
__________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== apicall_seq_lstm_input (InputLa (None, 1024) 0 __________________________________________________________________________________________________ argv_raw_ascii_input (InputLaye (None, 1024) 0 __________________________________________________________________________________________________ return_raw_ascii_input (InputLa (None, 1024) 0 __________________________________________________________________________________________________ lambda_1 (Lambda) (None, 1024) 0 apicall_seq_lstm_input[0][0] __________________________________________________________________________________________________ lambda_2 (Lambda) (None, 1024) 0 argv_raw_ascii_input[0][0] __________________________________________________________________________________________________ lambda_3 (Lambda) (None, 1024) 0 return_raw_ascii_input[0][0] __________________________________________________________________________________________________ lambda_4 (Lambda) (None, 1024) 0 apicall_seq_lstm_input[0][0] __________________________________________________________________________________________________ lambda_5 (Lambda) (None, 1024) 0 argv_raw_ascii_input[0][0] __________________________________________________________________________________________________ lambda_6 (Lambda) (None, 1024) 0 return_raw_ascii_input[0][0] __________________________________________________________________________________________________ model_1 (Model) (None, 1) 50850305 lambda_1[0][0] lambda_2[0][0] lambda_3[0][0] lambda_4[0][0] lambda_5[0][0] lambda_6[0][0] __________________________________________________________________________________________________ main_output (Concatenate) (None, 1) 0 model_1[1][0] model_1[2][0] ================================================================================================== Total params: 50,850,305 Trainable params: 50,850,305 Non-trainable params: 0 __________________________________________________________________________________________________