HHVM 函数动态劫持技术探究

百家 作者:百度安全应急响应中心 2020-05-19 21:20:55
1

前言


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中如何实现动态函数劫持做了一定的研究。
2

HHVM执行流程

hhvm入口点在hhvm/main.php中:

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
RuntimeOption::Load(
      ini,
      config,
      po.iniStrings,
      po.confStrings,
      &messages,
      scriptFilePath
    );
      .....
  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函数负责启动线程与接收处理请求。
整体流程为:

3

HHVM扩展

和php相似的是,hhvm同样支持扩展,也可以实现多种功能。一个hhvm扩展主要由c++编写,以官方扩展用例为例,首先需要建立三个文件:
ext_example.cpp  扩展实现文件
ext_exmaple.php  扩展声明文件
config.cmake    编译扩展文件
编写一个最简单的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函数中写入:

HHVM_FE(func_name)
在ext_example.php中声明一个hack函数(hhvm同时支持php和hack):
       <?hh
       <<__Native>>
       functionexample_hook(param_type): return_type

定义函数与php类似,其中函数返回值和参数类型需要与HHVM_FUNCTION中定义C++函数相对应,具体对应关系为:

Hack type
C++ type(参数)
c++ type(返回值)
void
N/A
void
bool
bool
bool
int
int64_t
int64_t
float
double
double
string
const String&
String
array
const Array&
Array
resource
const Resource&
Resource
object
const Object&
Object
ClassName
const Object&
Object
mixed
const Variant&
Variant
mixed&
VRefParam
N/A
最后编写config.cmake文件:
   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/arno
hhvm.dynamic_extensions[example]= example.so
重启hhvm即可让扩展生效。
4

HHVM扩展加载方式


在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函数中。
5

hook实现


以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等函数中无法获取到,因此无法获取某些信息。
6

实时阻断与性能



由于可以实现将exec等函数指向新函数,因此进行实时阻断完全可行。而由于我们的hook层次在php层,只要不实现耗时较多的io操作,都不会对性能产生太大影响
7

问题



尽管我们可以通过上述方式hook绝大多数php函数,甚至比在php中更为简单,但是eval函数却不包含在其中。eval的底层实现为:

而compileEvalString为:

可见与php5中一样,eval属于一种语法结构,不存在HHVM_FUNCTION定义,因此我们在扩展中无法对其进行重命名,同样也无法通过vmfp()获取到函数指针,因此上述hook方法并不适用于eval函数hook,笔者目前并没有找到更好的实现方式,如果有人有好的办法,欢迎一起讨论。与之相对的是,create_function等动态执行代码函数,存在函数体,因此也可以hook。
8

后记


目前由于php7的推出,hhvm已经在性能等多方面呈现出劣势,而facebook也将hhvm全面转向hack,因此未来使用hhvm的可能会越来越少,笔者在此的研究更多的是分享一种hook的思路。


百度安全应急响应中心


百度安全应急响应中心,简称BSRC,是百度致力于维护互联网健康生态环境,保障百度产品和业务线的信息安全,促进安全专家的合作与交流,而建立的漏洞收集以及应急响应平台。地址:https://bsrc.baidu.com

长按关注

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接