毕业设计之php RASP(一) hook函数

环境:5.5.9-1ubuntu4.24

创建扩展插件

php扩展开发为了方便,有一个代码生成器ext_skelphp-src-php-5.5.9/ext/便存在此程序

wget https://github.com/php/php-src/archive/php-5.5.9.zip

新建一个phpext.skel,内容如下

string lemon_tree(string str)

生成文件: ./ext_skel --extname=phpext --proto=./phpext.skel,其中--extname为扩展名
这样将会在php-src-php-5.5.9/ext/phpext目录中生成一些文件

1、修改config.m4
PHP_ARG_WITH或PHP_ARG_ENABLE,二选择一,第一种是指扩展需第三方库支持

2、php_phpext.h文件
其中confirm_phpext_compiled函数只是为了测试用的,可以去除,也可以在此添加一些自定义的函数名

PHP_FUNCTION(confirm_phpext_compiled); /* For testing, remove later. */
PHP_FUNCTION(lemon_tree);

3、phpext.c
定位到自定义函数这块,可以写一些自己想实现的功能。

PHP_FUNCTION(lemon_tree)
{
	char *str = NULL;
	int argc = ZEND_NUM_ARGS();
	int str_len;

	if (zend_parse_parameters(argc TSRMLS_CC, "s", &str, &str_len) == FAILURE) 
		return;

	php_error(E_WARNING, "lemon_tree: not yet implemented");
}

4、编译安装phpext
首先安装一下phpize

apt-get install php5-dev

phpize
./configure --with-php-config=/usr/bin/php-config
./configure

编译并安装phpext扩展到php库中

make
make test
make install clean

这样就会多出一个扩展

root@ubuntu:/usr# find / -name "phpext.so"
/usr/lib/php5/20121212/phpext.so

5、加载扩展phpext.so
编辑/etc/php5/apache2/php.ini

添加: extension = phpext.so
重启apache2

GDB调试

下载php版本: http://mirrors.sohu.com/php/

1、重新编译一下php,需要开启--enable-debug

./configure \
--prefix=/opt/php_debug/ \
--enable-debug \
--enable-cli \
--without-pear \
--enable-embed  \
--enable-inline-optimization \
--enable-shared \
--enable-opcache \
--enable-fpm \
--with-gettext \
--enable-mbstring \
--with-iconv \

make   
make install  
mkdir /opt/php_debug/conf/  
cp php.ini-development /opt/php_debug/conf/php.ini  

创建插件如上,但是需要在config.m4最后加入以下内容

if test -z "$PHP_DEBUG"; then
        AC_ARG_ENABLE(debug,
                [--enable-debug  compile with debugging system],
                [PHP_DEBUG=$enableval], [PHP_DEBUG=no]
        )
fi

新建一个bash,为了后面修改好调试

/opt/php_debug/bin/phpize
./configure --with-php-config=/opt/php_debug/bin/php-config --enable-debug
make
make test
make install clean

安装后lib的路径为:
/opt/php_debug/lib/php/extensions/debug-non-zts-20121212/

2、命令行下加载php.ini,可以看到加载了phpext模块

/opt/php_debug/bin/php -c /opt/php_debug/conf/php.ini -m

当然也可以为了方便:/opt/php_debug/bin/php --ini,看会扫描ini的目录是哪个,然后放入php.ini即可

3、设置生成core文件

为0的时候不会生成
ulimit -c unlimited

4、gdb调试错误

gdb /opt/php_debug/bin/php -c core
source /php-src-php-5.5.9/.gdbinit
backtrace

5、如果想动态调,可以先设置一个断点再进行调试

调试前决定一下调试哪个函数,可以选择一个
nm phpext.so

gdb /opt/php_debug/bin/php

由于扩展名加载的so是第三方共享库,事先并未加载,直接下断点是找不到函数,可以在php的`php_execute_script`函数下断点,这里是属于运行阶段,前面扩展加载是属于启动初始化阶段,这个时候就可以对扩展函数进行下断点
b php_execute_script

run /var/www/html/bishe/1.php
b lemon_php_fcall_handler
run /var/www/html/bishe/1.php

Hook函数

如果想要Hook函数,隐形人师傅这里写了一些方法

php内核中有几个常见的变量操作宏

CG    -> Complier Global      编译时信息,包括函数表等(zend_globals_macros.h:32)
EG    -> Executor Global      执行时信息(zend_globals_macros.h:43)
PG    -> PHP Core Global      主要存储php.ini中的信息
SG    -> SAPI Global          SAPI信息

Zend引擎将php函数分为5种类型

#define ZEND_INTERNAL_FUNCTION              1
#define ZEND_USER_FUNCTION                  2  
#define ZEND_OVERLOADED_FUNCTION            3
#define ZEND_EVAL_CODE                      4
#define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5

ZEND_INTERNAL_FUNCTION为内置函数,比如count,strpos
ZEND_USER_FUNCTION为用户自己定义的函数,平时写代码定义的function就是这类

php运行分为两个部分,一个是启动,一个是运行,启动的时候会所有的东西初始化,比如插件加载,php.ini加载等,运行的时候就是执行php代码,所以我们也可以从启动的时候进行入手,比如MINIT部分。

php5与php7结构的一些变化

char -> zend_string

zend_execute_data

下面一些代码可参考php内部函数实现过程:function_existsmethod_exists

1、函数替换:
php5下从CG表中找到函数,然后hook。

static struct lemon_overridden_fucs /* {{{ */ {
    php_func ini_get;
} lemon_origin_funcs;

#define LEMON_O_FUNC(m) (lemon_origin_funcs.m)
/* }}} */

static void lemon_php_override_func(const char *name, php_func handler, php_func *stash){
  zend_function *func;

  if(zend_hash_find(CG(function_table), name, strlen(name) + 1, (void **)&func) == SUCCESS){
    if (stash) {
        *stash = func->internal_function.handler;
    }
    func->internal_function.handler = handler;
  }
}

static void lemon_php_override_functions(){
  const char *f_ini_get = "ini_get";
  lemon_php_override_func(f_ini_get, PHP_FN(lemon_ini_get), &LEMON_O_FUNC(ini_get));
}

PHP_FUNCTION(lemon_ini_get)
{
    char *varname, *str;
    int varname_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &varname, &varname_len) == FAILURE) {
        return;
    }
    zend_error(E_WARNING, "ini_get Hook success");
    LEMON_O_FUNC(ini_get)(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}

遇上一个函数的坑点导致出现部分函数不能覆盖bug。开始用的sizeof,这个还会算上\0的后面的字符

strlen()函数求出的字符串长度为有效长度,既不包含字符串末尾结束符 '\0'
sizeof()操作符求出的长度包含字符串末尾的结束符 '\0'

2、
hook OPCODE

ZEND_INCLUDE_OR_EVAL — eval、require等
ZEND_DO_FCALL — 函数执行system等
ZEND_DO_FCALL_BY_NAME — 变量函数执行 $func = “system”;$func();

php5与php7的_zend_execute_data变化也挺大的
php7

struct _zend_execute_data {
	const zend_op       *opline;           /* executed opline                */
	zend_execute_data   *call;             /* current call                   */
	zval                *return_value;
	zend_function       *func;             /* executed function              */
	zval                 This;             /* this + call_info + num_args    */
	zend_execute_data   *prev_execute_data;
	zend_array          *symbol_table;
#if ZEND_EX_USE_RUN_TIME_CACHE
	void               **run_time_cache;   /* cache op_array->run_time_cache */
#endif
#if ZEND_EX_USE_LITERALS
	zval                *literals;         /* cache op_array->literals       */
#endif
};

php5
/php-src-php-5.5.9/Zend/zend_compile.h

pedef union _zend_function {
    zend_uchar type;    /* MUST be the first element of this struct! */

    struct {
        zend_uchar type;  /* never used */
        const char *function_name;
        zend_class_entry *scope;
        zend_uint fn_flags;
        union _zend_function *prototype;
        zend_uint num_args;
        zend_uint required_num_args;
        zend_arg_info *arg_info;
    } common;

    zend_op_array op_array;
    zend_internal_function internal_function;
} zend_function;

typedef struct _zend_function_state {
    zend_function *function;
    void **arguments;
} zend_function_state;

typedef struct _call_slot {
    zend_function     *fbc;
    zval              *object;
    zend_class_entry  *called_scope;
    zend_bool          is_ctor_call;
    zend_bool          is_ctor_result_used;
} call_slot;

struct _zend_execute_data {
    struct _zend_op *opline;
    zend_function_state function_state;
    zend_op_array *op_array;
    zval *object;
    HashTable *symbol_table;
    struct _zend_execute_data *prev_execute_data;
    zval *old_error_reporting;
    zend_bool nested;
    zval **original_return_value;
    zend_class_entry *current_scope;
    zend_class_entry *current_called_scope;
    zval *current_this;
    struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */
    call_slot *call_slots;
    call_slot *call;
};

如下图所示:

搞了很久,比如获取当前调用的函数名EG(current_execute_data)->function_state.function,当然还看到有这样获取的execute_data->call->fbc,但是不知道为啥一直获取不到

毕业要紧,换个php5.3.28版本,只能换成从opline中来获取函数名

# define LEMON_OP1_CONSTANT_PTR(n) (&(n)->op1.u.constant)

static int lemon_php_fcall_handler(ZEND_OPCODE_HANDLER_ARGS){
    zend_op *opline = execute_data->opline;    
    zval *fname = LEMON_OP1_CONSTANT_PTR(opline);
    char *funcname = Z_STRVAL_P(fname);
    int len = strlen(funcname);
    if (fname) {
        if (strncmp("passthru", funcname, len) == 0
                    || strncmp("system", funcname, len) == 0
                    || strncmp("exec", funcname, len) == 0
                    || strncmp("shell_exec", funcname, len) == 0
                    || strncmp("proc_open", funcname, len) == 0 ) {
                    zend_error(E_WARNING, "Hook success");
            }
    }
    return ZEND_USER_OPCODE_DISPATCH;
}
static void lemon_php_register_handlers(){
    zend_set_user_opcode_handler(ZEND_DO_FCALL, lemon_php_fcall_handler);
    zend_set_user_opcode_handler(ZEND_DO_FCALL_BY_NAME, lemon_php_fcall_handler);
}

还有hook一些语言结构,比如echo、eval、include,等毕业了后再研究吧....

posted @ 2018-04-20 01:02  l3m0n  阅读(3117)  评论(1编辑  收藏  举报