PHPJiaMi免扩展加密分析及解密

0x00 前言

前几天去玩了pwnhub公开赛的题目,源码下载之后发现是PHPJiaMi加密。之前有分析过phpjm加密并写出过解密文件,所以研究下这个PHPJiaMi。
PHP免扩展加密的主流加密方法采用了ascii码 129-255的乱码来实现变量名、函数名混淆,编辑器打开后就是一堆乱码,造成不可读。

加密流程:源码->加密处理(压缩,替换,BASE64,转义)->安全处理(验证文件MD5值,限制IP、限域名、限时间、防破解、防命令行调试)->加密程序成品,再简单的说:源码+加密外壳==加密程序 (该段出处)

0x01 解密准备

这里做演示,我写了phpinfo()然后去http://www.phpjiami.com/生成加密文件,打开之后,果然都是一片乱码。
使用代码修复工具http://zhaoyuanma.com/phpcodefix.html将ascii不可见字符的变量修复成正常的变量名,再PHP代码美化,方便下一步分析。

0x02 函数分析

代码内有三个函数,由于每次加密这三个函数的顺序都不一样,这个以传参方式区分这三个函数
fun1 = ($var1, $var2 = '') = 核心函数,将乱码转成正常字符串
fun2 = (&$var1, $var2) = 校验IP、域名,防止被破解。最后一句是解密整个php文件
fun3 = ($var1) = 将需要用到的函数赋值给N个全局变量

先从fun1开始逆起,在编辑器中双击变量名,该变量高亮之后,可以看到它怎么变化,在哪里被使用。
一句句语句逆下去,发现有语法错误,其实是代码修复后的bug,用winhex打开定位到这句代码,发现是三元运算符

$var2 = !$var2 ? ord('乱码') : $var2;

接下来是一句很奇怪很无用的代码,再下一句是for循环,我猜测是给$i赋值

for($i=0; $i<strlen($var1); $i++)

下个for循环里又是一个三元运算符,手动修复后,基本fun1的代码就出来。
接下来就是运行fun1函数,但是碰到个坑点,fun1有很多处用到乱码做运算,而乱码不能直接拷到编辑器中。
用winhex将16进制的乱码字符复制出来,在运算的时候 pack("H*","乱码")将它还原回乱码即可。

function fun1($var1, $var2 = '')
{
    $md5 = md5(pack("H*",'FBE3FCFAF9E0'));//乱码随机字符串1
    $var2 = !$var2?ord(pack("H*",'8C')):$var2;//乱码随机字符串2
    
    $str = '';
    for($i=0; $i<strlen($var1); $i++)
    {
        $str .= ord($var1{$i}) < ord(pack("H*",'F5')) ? ((ord($var1{$i}) > $var2 && ord($var1{$i}) < ord(pack("H*",'F5'))) ? chr(ord($var1{$i}) / 2) : $var1{$i}) : '';
    }
    $de1 =  base64_decode($str);
    $len = $len2 = strlen($md5);
    $str2 = '';
    for($i=0; $i<strlen($de1); $i++)
    {
        $len = $len ? $len : $len2;
        $len--;
        $str2 .= $de1[$i] ^ $md5[$len];
    }
    return $str2;
}

接着开始还原fun2代码,fun2中前面都调用了fun1解密字符串,解密可得到fun2后面需要用到的函数名。有了fun1,后面解密都非常顺利

function fun2(&$var1, $var2)
{
    $de = str_rot13(strrev(gzuncompress(stripslashes(fun1(pack('H*','8ECA349A37A639E43946ACE4B242F4EED8E8A0CC4444E639E894C2C69CB0384134EEEEC6CA3233B433ECD89CC6D435A437CE98B0D2A092909E96D030EE8EA2A4D044CADCA2DC32CCE02BAADCEEEA4537D8ACB43298F238E0428EC29646A0CAA2EECA43D2B2C442F4C4358E46429434AE44B0DACAF0AE4190C845EE32AE34C82BC49494E22BCAA49CD6D2E8CA94D2D4A6329C929A3596F2CC36CCD6CCAAF0C646D0A69EC8F02FA2A03938D234C2D2E0DCCCCAC4A8A631AC4238C4A490ECD4CCF0DAF4CAB2'))))));
    $exp = explode(',', $de);
    $var1 = $exp[$var2];
}

$de解出来是一堆函数名,然后赋值给全局变量,后面解密都需要用到。fun2解出来是固定的字符串

,chr,addslashes,rand,gzuncompress,assert_options,assert,file_exists,file_get_contents,substr,unpack,constant,strpos,create_function,str_rot13,md5,set_include_path,dirname,preg_replace,base64_encode,base64_decode,

接着暂且先不看fun3,回到主代码中。
QQ截图20170922040008.png

这里创建了一堆全局变量,通过fun2赋值,每个变量都代表一个函数名
QQ截图20170922040511.png

接着还原fun3函数,步骤一样。

function fun3()
{
    php_sapi_name() == 'cli' ? die():'';
    $file = file_get_contents(__FILE__);
    if(!isset($_SERVER['HTTP_HOST']) && !isset($_SERVER['SERVER_ADDR']) && !isset($_SERVER['REMOTE_ADDR']))
    {
        die();
    }
    $time = microtime(true) * 1000;
    if(microtime(true) * 1000 - $time > 100)
    {
        die();
    }
    if(strpos(__FILE__, gtmclaei) !== 0){$exitfunc();}
    !strpos(de1(substr($file, de1('A8414145'), de1('A841AA3D'))), md5(substr($file, de1('AAA23D3D'), de1('A8414190')))) ? unkonw1() : unkonw2();
    $loc1 = fun1('A841AA45ACEE3D3D');
    $loc1 = fun1('A8414190');
    $de = str_rot13(gzuncompress(fun1(substr($file, $loc1, $loc2))));//核心解密
    return $de;
}

后面的代码已经不用再看了,fun2解出来的就是解密后的原代码。
QQ截图20170922131714.png

最后附上解密脚本http://sec2hack.com/phpjiami.zip

返回文章列表 文章二维码
本页链接的二维码
打赏二维码
评论列表
  1. [...]这种方法我最佩服了,作者甚至给出了解密脚本,文章如下: http://sec2hack.com/web/phpjiami-decode.html 。[...]

  2. [...]这种方法我最佩服了,作者甚至给出了解密脚本,文章如下: http://sec2hack.com/web/phpjiami-decode.html 。[...]

  3. 大神!十分感谢~~

  4. 许先生 许先生

    哈哈哈,题主有所不知,你的这个加密文件解密过程中,用到了修复代码zhao_yuan_ma
    .com(zym)这个网站,其实这一个加密一个解密的二个网站,都是同一个作者的.也就是说,有人找他加密他收一次钱,然后它再收一次钱去解你加的密。所以说PHPJiaMi.com的这个人毫无人品可言,猪狗不如。

  5. 一个快疯掉的PHP码农 一个快疯掉的PHP码农

    被phpjiami这个网站加密的PHP文件快整疯了,什么都看不懂~~~

添加新评论