分类 编程 下的文章

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去删除。

Java命令行配置加载模块

最近在用Java,写了一个动态从xml中加载命令行提示,支持多语言的模块

package com.common;

import org.apache.commons.cli.*;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.util.Iterator;
import java.util.List;

/**
 * Created by LonelyRain on 16/7/12.
 */

public class ParseCommand {
    static Options optionList;

    public static CommandLine ParseOption(String[] args) {

        Options options = new Options();
        CommandLine UserCommand = null;
        try {
            //根据args对比XML内的命令
            UserCommand = ProgramOptionList.loading(options, args);
        } catch (Exception error) {
            System.err.println("Error:\r\n\t==>" + error.getMessage());
            System.exit(0);
        }
        return UserCommand;
    }

    public static void help() throws Exception {
        String ProgramName = "";
        //创建解析器

        InputStream inputFile = Thread.currentThread().getContextClassLoader().getResourceAsStream("xml/CommandList.xml");
        SAXReader reader = new SAXReader();
        org.dom4j.Document document = reader.read(inputFile);

        //获取根
        org.dom4j.Element root = document.getRootElement();
        ProgramName = root.attributeValue("name");
        HelpFormatter hf = new HelpFormatter();
        hf.printHelp(ProgramName, optionList);

    }


    public static class ProgramOptionList {
        static CommandLine CommandList;


        public static CommandLine loading(Options defaultOptions, String[] args) throws Exception {
            CommandLineParser CommandParser = new DefaultParser();
            __loadConfigFile(defaultOptions, "en");
            CommandLine UserCommand = CommandParser.parse(defaultOptions, args);
            optionList = defaultOptions;
            if (UserCommand.hasOption("l")) {
                Options otherOptions = new Options();
                __loadConfigFile(otherOptions, UserCommand.getOptionValue("l"));
                optionList = otherOptions;
            }
            CommandList = UserCommand;
            return CommandList;
        }

        private static void __loadConfigFile(Options options, String language) throws Exception {
            //创建解析器
            SAXReader reader = new SAXReader();

            //读取文档
            File inputFile = new File("/Users/SilverRat/开发/java/DomainSearcher/src/xml/CommandList.xml");
            org.dom4j.Document document = reader.read(inputFile);

            //获取根
            org.dom4j.Element root = document.getRootElement();

            //获取子节点
            List<org.dom4j.Element> list = root.elements();

            for (org.dom4j.Element e : list) {
                String shortCMD = "";
                String longCMD = "";
                String describetion = "";
                boolean with_args = false;
                for (Iterator s = e.elementIterator(); s.hasNext(); ) {
                    org.dom4j.Element commandInfo = (org.dom4j.Element) s.next();
                    switch (commandInfo.getName()) {
                        case "short":
                            shortCMD = commandInfo.getStringValue();
                            break;
                        case "long":
                            longCMD = commandInfo.getStringValue();
                            break;
                        case "describetion":
                            if (commandInfo.attributeValue("lang").equals(language)) {
                                describetion = commandInfo.getStringValue();
                            }
                            break;
                        case "with_args":
                            with_args = Boolean.parseBoolean(commandInfo.getStringValue());
                            break;
                    }
                }
                options.addOption(shortCMD, longCMD, with_args, describetion);
            }
        }

    }
}

上面的是解析模块
下面的是配置的xml文件

<?xml version="1.0" encoding="utf-8" ?>
<command_list name="DomainSearcher">
    <command id="0">
        <short>h</short>
        <long>help</long>
        <with_args>false</with_args>
        <describetion lang="en">Help Information</describetion>
        <describetion lang="zh">帮助信息</describetion>
    </command>
    <command id="1">
        <short>t</short>
        <long>target</long>
        <with_args>true</with_args>
        <describetion lang="en">Your Target Host/IP</describetion>
        <describetion lang="zh">目标域名/IP</describetion>
    </command>
    <command id="2">
        <short>d</short>
        <long>dict</long>
        <with_args>true</with_args>
        <describetion lang="en">Your Dictionary</describetion>
        <describetion lang="zh">加载字典路径</describetion>
    </command>
    <command id="3">
        <short>l</short>
        <long>language</long>
        <with_args>true</with_args>
        <describetion lang="en">选择程序语言(en / zh)</describetion>
        <describetion lang="zh">Choose Display Language(en / zh)</describetion>
    </command>
</command_list>

下面是运用例子

import ParseCommand.*;
public class DomainSearcher {
    public static void main(String[] args) {
        ParseCommand parsecommand = new ParseCommand();
        parsecommand.Parse(args);
    }

运行效果:
7D40876C-C061-4895-BAE8-EFEBB906607C.png

BCAE6C46-D34C-41FD-89BC-2E273008D235.png