HHVM (HipHop VirtualMachine)会将PHP代码转换成高级别的字节码(通常称为中间语言),然后在运行时通过即时(JIT)编译器将这些字节码转换为x64的机器码。在这些方面,HHVM十分类似于C#的CLR和Java的JVM。它是Facebook在php7之前为了解决php5的性能问题推出的一种PHP解释引擎,能够大幅度提升PHP执行性能,在国内外拥有一定的粉丝量。
RASP(Runtimeapplication self-protection)运行时应用自我保护技术,通过将自身注入到应用程序中,实时监测,阻断攻击,从而使程序拥有自保护的能力。目前的RASP主要有java和php的版本。笔者所在公司拥有一定的hhvm使用量,因此在研发rasp产品过程中需要调研适配hhvm,也因此对hhvm中如何实现动态函数劫持做了一定的研究。int main(int argc, char** argv) {
....
return HPHP::execute_program(args.size(), &args[0]);
}
execute_program函数位于runtime/base/program-functions.php中:int execute_program(int argc, char **argv) {
....
ret_code =execute_program_impl(argc, argv);
}
}
直接调用了execute_program_impl函数:
static int execute_program_impl(int argc, char** argv) { //导入主要的配置文件,也包括扩展设置的moduleLoad if (po.mode == "daemon" || po.mode== "server") { if (!po.user.empty())RuntimeOption::ServerUser = po.user; returnstart_server(RuntimeOption::ServerUser, po.xhprofFlags);其中RuntimeOption::Load函数负责加载配置文件中的配置项,start_server函数负责启动线程与接收处理请求。和php相似的是,hhvm同样支持扩展,也可以实现多种功能。一个hhvm扩展主要由c++编写,以官方扩展用例为例,首先需要建立三个文件:编写一个最简单的hhvm扩展,cpp文件中代码为:#include"hphp/runtime/base/array-data.h"
#include"hphp/runtime/base/array-init.h"
#include"hphp/runtime/ext/extension.h"
#include"hphp/runtime/ext/std/ext_std_function.h"
namespace HPHP {
static int64_t HHVM_FUNCTION(example_hook){
//Array arr = HHVM_FN(get_defined_functions)();
return 1;
}
static class ExampleExtension : publicExtension {
public:
ExampleExtension() : Extension("hook") {}
virtual void moduleInit() {
HHVM_FE(example_hook);
loadSystemlib();
}
} s_hook_extension;
HHVM_GET_MODULE(hook)
首先需要引入所有扩展的父类定义文件extension.h,然后所有的扩展都定义在HPHP作用域中。每个用户定义扩展需要继承Extension类,Extension类中主要的方法包括:virtual void moduleLoad(constIniSetting::Map& /*ini*/, Hdf /*hdf*/) {
virtual void moduleInit() {}
virtual void moduleShutdown() {}
virtual void threadInit() {}
virtual void threadShutdown() {}
virtual void requestInit() {}
virtual void requestShutdown() {}
实现一个简单扩展,只需要使用HHVM_FUNCTION关键词定义一个函数:
return_valueHHVM_FUNCTION(func_name,params)
其中return_value为返回值,func_name为函数名,params为不固定参数,然后在moduleInit或者moduleLoad函数中写入:
在ext_example.php中声明一个hack函数(hhvm同时支持php和hack): functionexample_hook(param_type): return_type定义函数与php类似,其中函数返回值和参数类型需要与HHVM_FUNCTION中定义C++函数相对应,具体对应关系为:
HHVMEXTENSION(hookext_example.cpp) HHVM SYSTEMLIB(hook ext_example.php)编译生成hhvm文件:phpize &cmake . & make执行时hhvm –d extension_dir=. –dhhvm.extensions[]=example.so test.php,或者修改/etc/hhvm/php.ini文件,添加:hhvm.dynamic_extension_path= /home/arnohhvm.dynamic_extensions[example]= example.so
在Extension类的实现中,会在初始化时执行:ExtensionRegistry::registerExtension(this)而在ExtensionRegistry中,则实现了ModuleLoad等多个函数,以moduleLoad为例:void moduleLoad(const IniSetting::Map&ini, Hdf hdf) {
std::set<std::string> extFiles;
…..
//Load up any dynamic extensions from extension_dir
std::string extDir = RuntimeOption::ExtensionDir;
for(std::string extFile : extFiles) {
moduleLoad(extFile);
}
…..
for(auto& ext : s_ordered) {
ext->moduleLoad(ini, hdf);
}
}
在这个函数中,首先获取所有扩展的路径,然后通过路径加载扩展类ext,最后通过ext遍历所有动态扩展的moduleLoad函数,从而完成扩展的配置加载。因此整体的调用图为:moduleLoad()->threadInit()->moduleInit()->requestInit()->requestShutdown()->moduleShutdown()->threadShutdown()moduleLoad函数中只做配置工作,此时执行进程还未开启,无法进行hook,因此hook点可以选在moduleInit或者requestInit函数中。
以exec函数为例,exec函数定义在runtime/ext/std/ext_std_process.cpp中:StringHHVM_FUNCTION(exec,const String& command, VRefParamoutput = uninit_null(), VRefParamreturn_var = uninit_null())在php中hook有两种方式,一种是直接hook php函数,一种是hook php函数底层实现c函数,而在hhvm中由于在moduleInit和requestInit中无法直接获取到内存中的c++函数,因此我们采用hook php函数的方式实现。具体方案为:1)新建扩展1,在扩展1中定义函数my_exec,在函数中加入hook代码,然后执行内置exec函数2)新建扩展2,在扩展2中,首先定义一个函数reNameFunc:void reNameFunc(const String& old_name,const String& new_name)
{
VMRegAnchor _;
auto const old = old_name.get();
auto const oldNe =const_cast<NamedEntity*>(NamedEntity::get(old));
Func* func_2 = Unit::lookupFunc(oldNe);
auto const fnew = new_name.get();
auto const newNe =const_cast<NamedEntity*>(NamedEntity::get(fnew));
oldNe->setCachedFunc(nullptr);
newNe->m_cachedFunc.bind(rds::Mode::Normal);
newNe->setCachedFunc(func_2);
}
通过lookupFunc获取原函数名func_2,然后将当前进程中的函数缓存设置为新函数名,即可替换原函数,从而修改hhvm当前进程中的函数名。为了达到hook的目的,我们需要首先将原exec函数重命名为s_exec,然后将自定义的my_exec函数重命名为exec,从而实现偷梁换柱,将exec指向hook的函数:然后加载两个扩展,并运行,即可hook任意php函数:
而在hook中常常需要获取程序的执行链路,这个可以通过hhvm自带的backtrace函数获取。该函数定义在backtrace.h中,使用可以参考ext_errorfunc.cpp。获取http参数则更为简便,直接通过php_global(“_GET”).toArray()即可,具体的函数定义在runtime/base/php-globals-inl.h中,使用可以参考runtime/ext/filter/ext_filter.h。另外,vmfp()、vmRegs()定义在runtime/vm/vm-regs.h中,可以获取当前进程php函数调用栈,但在moduleInit等函数中无法获取到,因此无法获取某些信息。
由于可以实现将exec等函数指向新函数,因此进行实时阻断完全可行。而由于我们的hook层次在php层,只要不实现耗时较多的io操作,都不会对性能产生太大影响。
尽管我们可以通过上述方式hook绝大多数php函数,甚至比在php中更为简单,但是eval函数却不包含在其中。eval的底层实现为:可见与php5中一样,eval属于一种语法结构,不存在HHVM_FUNCTION定义,因此我们在扩展中无法对其进行重命名,同样也无法通过vmfp()获取到函数指针,因此上述hook方法并不适用于eval函数hook,笔者目前并没有找到更好的实现方式,如果有人有好的办法,欢迎一起讨论。与之相对的是,create_function等动态执行代码函数,存在函数体,因此也可以hook。
目前由于php7的推出,hhvm已经在性能等多方面呈现出劣势,而facebook也将hhvm全面转向hack,因此未来使用hhvm的可能会越来越少,笔者在此的研究更多的是分享一种hook的思路。
百度安全应急响应中心,简称BSRC,是百度致力于维护互联网健康生态环境,保障百度产品和业务线的信息安全,促进安全专家的合作与交流,而建立的漏洞收集以及应急响应平台。地址:https://bsrc.baidu.com
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/