分类 PHP 下的文章

Zval中的refcount问题

今天群里面的一位小伙伴问我关于PHP变量中refcount的问题,当时回答给他了,发现自己没注意到一个细节,记录下来。

什么是refcount

“zval结构体中有四个字段,其含义分别为:

属性名 含义 默认值
refcount__gc 表示引用计数 1
is_ref__gc 表示是否为引用 0
value 存储变量的值
type 变量具体的类型”

摘录来自: Reeze Xia. “TIPI: 深入理解PHP内核”。

refcount是php用来对zval变量引用次数技术的一个变量值。

它是一个计数器,用来保存有多少符号表有多少符号指向该zval。在变量生成时,其refcount=1,赋值操作$a = $b会令zval的refcount加1,zval的refcount如果减少到0,会释放该zval所占的内存空间。

问题是什么?

小伙伴原问题是为什么下面的计数会是3.
E0B61238BF90638ECF0A713DC1169A3D.jpg

因为照上面的说法zval $a在被赋值为100的时候应该为1,$b = $a 的时候应该再加1,那么debug_zval_dump()函数照理来说应该显示2,而不应该显示3。

当时我的回答是,初始化的时候值为1,赋值的时候再加1,赋值给$b的时候再加1,所以是3。
但是后来他提出,使用xdebug_zval_dump()出来refcount的值是2。
那么,我之前的答案就是错的,惭愧,学艺不精。

于是我找了一些资料来研究两个函数之间处理的差异。

下面就是差异的原因。

PHP's debug_zval_dump takes a variable as argument for analysis and is thus also bound to the same splitting rules as outlined earlier. This means that's not really well suited for dumping a zval's internal structure as it would always modify the zval. Besides adding 1 to the refcount field, it could also force a split resulting in unexpected output:

<?php
$a = 42;
debug_zval_dump($a);
?>

shows:

long(42) refcount(2)

Xdebug has a similar function to display a zval's internal data: xdebug_debug_zval. This function does requires not a variable to be passed as its argument, but instead requires a variable name to be passed in. With this, the manipulation of the zval is avoided and the proper values are shown:

<?php
$a = 42;
xdebug_debug_zval('a');
?>

which shows:

a: (refcount=1, is_ref=0)=42

所以单纯的$a的refcount=0,赋值操作会+1。

重新编译MAMP的PHP 踩坑

FE94B1093654FFE76CCAD0084AB36CCF.jpg

MAMP自带的PHP版本在编译的时候是没有加上--enable-debug选项的,于是我打算自己重新编译一下PHP方便自己在进行PHP扩展研究的时候可以用gdb来查看core dump文件。

首先使用MAMP中的php版本查看phpinfo()
得到了MAMP在编译时候的参数

--more--
并且在后面加上了--enable-debug选项

sudo './configure' '--with-mysql=mysqlnd' '--with-gd' '--with-jpeg-dir=/Applications/MAMP/Library' '--with-png-dir=/Applications/MAMP/Library' '--with-zlib' '--with-zlib-dir=/Applications/MAMP/Library' '--with-freetype-dir=/Applications/MAMP/Library' '--prefix=/Applications/MAMP/bin/php/php5.5.26' '--exec-prefix=/Applications/MAMP/bin/php/php5.5.26' '--sysconfdir=/Applications/MAMP/bin/php/php5.5.26/conf' '--with-config-file-path=/Applications/MAMP/bin/php/php5.5.26/conf' '--enable-ftp' '--enable-gd-native-ttf' '--with-bz2=/usr' '--with-ldap' '--with-mysqli=mysqlnd' '--with-t1lib=/Applications/MAMP/Library' '--enable-mbstring=all' '--with-curl=/Applications/MAMP/Library' '--enable-sockets' '--enable-bcmath' '--with-imap=shared,/Applications/MAMP/Library/lib/imap-2007f' '--enable-soap' '--with-kerberos' '--enable-calendar' '--with-pgsql=shared,/Applications/MAMP/Library/pg' '--enable-exif' '--with-libxml-dir=/usr/include' '--with-gettext=shared,/Applications/MAMP/Library' '--with-xsl=/Applications/MAMP/Library' '--with-pdo-mysql=mysqlnd' '--with-pdo-pgsql=shared,/Applications/MAMP/Library/pg' '--with-mcrypt=shared,/Applications/MAMP/Library' '--with-openssl' '--enable-zip' '--with-iconv=/Applications/MAMP/Library' '--enable-opcache' '--enable-cgi' '--enable-intl' '--with-icu-dir=/Applications/MAMP/Library' '--with-tidy=shared' '--enable-wddx' '--with-libexpat-dir=/Applications/MAMP/Library' --enable-debug

将重新下载的php对应版本新建了一个目录,解压在了以下的文件夹中

/Applications/MAMP/bin/php/php5.5.26/include/php

第一次使用上面的参数第一次编译报错:

configure: error: Cannot find OpenSSL's <evp.h>

brew link openssl --forece

再编译,又获得如下错误

configure: error: jpeglib.h not found

使用brew安装jpeglib

sudo brew install jpeglib

接下来又报错

configure: error: png.h not found
sudo brew install t1lib

报错

configure: error: Cannot locate header file libintl.h

用以下来解决

sudo brew install gettext

1) 安装 gettext:

brew install gettext

2) 编辑configure文件
将:

for i in $PHP_GETTEXT /usr/local /usr ; do

更改为:

for i in $PHP_GETTEXT /usr/local /usr /usr/local/opt/gettext; do

3)重新运行 ./configure

报错

configure: error: utf8_mime2text() has new signature, but U8T_CANONICAL is missing. This should not happen. Check config.log for additional information

安装

sudo brew install imap-uw

报错

configure: error: mcrypt.h not found. Please reinstall libmcrypt.

安装

sudo brew install libmcrypt

报错

configure: error: Cannot find libpq-fe.h. Please specify correct PostgreSQL installation path

安装

sudo brew install PostgreSQL

之后

sudo make && sudo make install

会报错找不到libxml/parse.h

改成

/usr/include/libxml2

安装iconv

sudo brew install homebrew/dupes/libiconv

在修复libiconv报错的时候,网上一篇文章写移动/use/lib/目录底下的libiconv.*移动到其他目录,替换刚刚安装的libiconv来交叉编译,结果刚移动系统就崩溃了。这个库应该是系统很多程序调用的库, 花了一晚上重装了个系统,打算明天用docker来装个PHP环境。

MAMP + VLD配置在OS X下查看OPCODE

最近在看PHP内部实现的东西,想要了解PHP是如何从一个.php文件经过词法/语法/语义等分析生成OPCODE的。

鸟哥在博客里面安利了vld.so这个php opcode dump工具,网上没有在osx下mamp安装opcode的教程,自己鼓捣了一下,记录一下方便后人。

前提需要安装xcode command line tool这个网上教程比较多,自己去搜一下吧。

首先在/Applications/MAMP/bin/php中新建一个目录include

下载一个对应版本的php源码解压到这个目录,并且

./configure
make && make install

之后导入shell环境变量到当前目录

echo "export PATH=/Applications/MAMP/bin/php/php{对应版本}/bin:$PATH" >> ~/.profile

运行

. ~/.profile
pear config-set php_ini /Applications/MAMP/bin/php/php{对应版本}/conf/php.ini
pecl config-set php_ini /Applications/MAMP/bin/php/php{对应版本}/conf/php.ini

更新pecl

pecl channel-update pecl.php.net

下载与安装VLD

wget http://pecl.php.net/get/vld-{对应版本}.tgz
tar zxvf vld-{对应版本}.tgz
cd ./vld-{对应版本}
/Applications/MAMP/bin/php/php{对应版本}/bin/phpize
./configure --with-php-config=/Applications/MAMP/bin/php/php{对应版本}/bin/php-config --enable-vld
make && make install

然后在
/Applications/MAMP/bin/php/php{对应版本}/conf/php.ini中添加

[vld]
extension=vld.so

并且在mamp的选项edit template里修改php.ini

然后source ~/.profile

之后php运行

php -dvld.active=1 /path/to/your/code.php
就可以看到opcode的输出了

5855BC7E-5AAC-4C7C-BC42-850485624AC5.png

挖某CMS任意文件删除碰到文件路径问题

在挖某CMS漏洞的时候,挖到了一个API函数可以删除文件,想着可以调用这个API来删除install.lock文件,进行初始化,进管理员后台再getshell。
但是遇到了一个问题,调用这个API的时候,我传入的是绝对路径的话,文件是可以删除掉的,但是如果我传入了一个相对路径的话,却无法删除那个文件。
我一度以为是我写错了文件的路径。
经过检查,最后发现了原来是没有注意到是应该采用活动目录来构造目录。

举个例子:

假设有那么个文件目录结构:

src/A.php
    |
    |dir2/subdir/B.php
    |
    |dir3/flag.txt

A代码:

<?php
define('SITE_PATH', getcwd() . '/');
require SITE_PATH . 'dir2/subdir/B.php';

B代码:

<?php
@unlink($_GET['FilePath']);
echo "\r\ndelete ." . $_GET['FilePath'];

假设我们想要删除flag.txt这个文件路径到底应该是什么呢?

一般我们都会想../../dir3/flag.txt这个来删除,因为会想unlink函数在B.php里面,肯定是以B.php为基点,再运用相对路径去删除文件,实际上的话这是不对的。

里面涉及到当前工作目录和是当前文件目录的区别,当前工作目录可以使用getcwd()函数来获取,而当前文件目录就不多解释了,上面的代码应该用当前文件目录去删除。

所以上面的代码应该使用A.php?FilePath=dir3/flag.txt去删除,而不应该使用../../dir3/flag.txt去删除。

从PHP源代码学习PHP--PHP类型

u=3778279685,2362934562&fm=58.jpeg

PHP类型

PHP类型基础知识

PHP是弱类型语言(依照laruence的说法是一种中强类型语言)

所谓的弱类型就是在申明一个变量的时候,并不需要显式指明它保存的数据的类型,在程序执行的过程中可以根据需求动态的改变变量的类型.

那PHP是如何实现这个功能的呢?

首先PHP定义了一个保存变量的结构体(C语言)

typedef struct _zval_struct zval;

typedef union _zvalue_value {
    long lval;                    /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;                /* hash table value */
    zend_object_value obj;
    zend_ast *ast;
} zvalue_value;

struct _zval_struct {
    /* Variable information */
    zvalue_value value;        /* value */
    zend_uint refcount__gc;   /* PHP的内存回收机制需要这个值,现在我们并不需要对它特别关注 */
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

保存变量的地方是_zvalue_value,_zvalue_value定义了一个联合体①来储存用户输入的变量

_zvalue_value可以把内存中的值相对应的解释为longdoublestr等类型,比较特殊的有三种HashTable *ht(数组)obj(对象)*ast(资源)

这个变量是什么类型由zend_uchar type; /* active type */决定,

type的种类在Zend/zend.h:583-601中定义.

/* data types */
/* All data types <= IS_BOOL have their constructor/destructors skipped */
#define IS_NULL        0
#define IS_LONG        1
#define IS_DOUBLE    2
#define IS_BOOL        3
#define IS_ARRAY    4
#define IS_OBJECT    5
#define IS_STRING    6
#define IS_RESOURCE    7
#define IS_CONSTANT    8
#define IS_CONSTANT_AST    9
#define IS_CALLABLE    10

#define IS_CONSTANT_TYPE_MASK        0x00f
#define IS_CONSTANT_UNQUALIFIED        0x010
#define IS_LEXICAL_VAR                0x020
#define IS_LEXICAL_REF                0x040
#define IS_CONSTANT_IN_NAMESPACE    0x100

#define IS_CONSTANT_TYPE(type) (((type) & IS_CONSTANT_TYPE_MASK) >= IS_CONSTANT && ((type) & IS_CONSTANT_TYPE_MASK) <= IS_CONSTANT_AST)

PHP中实现转换就是当zval.type = IS_LONG是,就把结构体里的value转换成long型,当zval.type = IS_STRING时,就把结构体里面的value转换成str类型

明白了以上基础知识就可以来探究zval.type会在哪些场景带来哪些问题了.


①为什么要用联合体呢?

K&R<<Pointer on C>>中我们可以翻阅到这么一段文字

联合的声明和结构类似,但它的行为方式却和结构不同.联合的所有成员引用的是内存中相同的位置.当你想在不同的时刻把不同的东西储存在同一个位置的时候,就可以使用联合.

联合体可以用来动态的表示同一个内存中的值的类型.假如使用结构体来储存用户输入地变量的话,那么假设用户输入了1,如果想要把1char,int,double等等来表示的话,就会成倍的增加内存占用.