美文网首页PHP经验分享
PHP-Parser运用之goto加密的解密经历

PHP-Parser运用之goto加密的解密经历

作者: 凌波飞翔 | 来源:发表于2020-02-14 15:02 被阅读0次

    一、初识PHP-Parser

    19年底跟着一个旧同事搞一个商城类小程序的项目,开发过程中遇到了一些加密的PHP文件,询问后才知道这是微擎的2C加密,里面很多goto的跳转。每次同事都是发一个微信好友解密(说是以前加的专门做解密的)。百度了一下发现也很多网站在做在线解密的。这种都是按照文件大小来收费(kb),一个文件几块钱,但对于一个项目的文件费用也不少。同事说PHP-Parser 解析器可以做到解密。不管怎么加密都要能被解析器解析,都会转换为AST-抽象语法树。春节遇到疫情不能出去就闲着折腾下。

    • PHP Parser 是由 nikic 开发的一款 php 抽象语法树(AST)解析工具,github下载链接php-parse 地址https://github.com/nikic/PHP-Parser
      下载好后新建文件试运行官方例子,成功后能获得官方例子一样的抽象树。

      image.png
    • 主要方法:

    $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);(创建解析器)
    $ast = $parser->parse($code);(传入代码并返回抽象语法树)
    $prettyPrinter->prettyPrintFile($ast);(把抽象语法树转换为php代码)
    

    二、探索解密方法

    1.百度找到一篇怎样手动解密微擎框架的goto语句?的百度经验。文中很清楚的说明了解密原理。加密文件都是大量的label和goto语句,label里面就是代码内容,只是label顺序是打乱的。需要通过goto语句跳转串联起来。最后把label都去掉就是源代码了。
    原理简单,对于代码少的是可行的,但对于代码量几千行的文件这手动的工作量是够呛的。
    2.理解了上面原理后,想着能不能用php写个方法去解决?然后尝试用PHP Parser传入加密的代码获取AST抽象语法树,获取到如下图的AST。对比php加密文件发现$ast数组中的每个元素对应内容中的每一行。Label与Goto_中间就是我们要的代码。按照goto_的跳转把顺序排出来,然后把Laber和Goto_删掉就是源代码了。排序后的数组通过prettyPrintFile()方法转为php代码。

    AST抽象树.png

    3.特别要说明一下的是\color{red}{if、for、foreach、switch、class里面代码块是在stmts中也需要独立排序}
    另外php的goto语句是有限制的:只能在同一个文件和作用域中跳转,无法跳出一个函数或者类方法,也无法跳入另一个函数,更无法跳入任何循环或者switiche结构中。
    写了个递归传入第一个语句Label把代码按goto顺序串联起来。试了两个加密文件成功的代码解密出来了,有点点小激动,刚试第三个文件就翻车了,报内存溢出而且解密时间很久。然后把php配置中php.ini(memory_limit )改到1024m了。再试了其他,发现文件稍微大点的(40kb)文件就会超时或者内存溢出。

    4.各个节点输出内存定位到调用prettyPrintFile()的地方,一开始以为是自己组装ast数组的算法有问题,对比解密出来的文件逻辑上没发现有什么,就真以为内存不够用问题,然后就在错误的路上越走越远,猜想是不是ast数组太大一次转化不了,然后就搞成了一句一句代码去调用方法转换php代码再输入到文件中。最后成功的输出解密后的代码,但解密后的文件大小既然有10M。难怪会内存溢出,再想想不科学啊。谁会写代码一个文件写这么多。所以问题应该还是出在组装的ast数组。

    5.再仔细的看了小文件解密出来的代码发现很多重复的代码,主要在发生在if语句里面的跟下面的内容。如下图

    image_cf.png
    图中switch里面的内容是一样的。但是对比加密文件中的逻辑又是对的,但怎么看代码都是写法有问题。然后就百度想找点相关资料,然后看到一位大佬(破解微擎2C(goto混淆)解密之旅)说goto混淆加密在if代码块有做混淆,需要反向解密。

    二、浅谈AST语法

    1.需要做反向解密就需要知道ast中if元素中的语法内容,然后去修改条件condition。这就必须要去弄懂ast的语法,之前基本没接触过,关于写ast的资料也很少,应该是属于比较底层的技术。看了很多也还一知半解。有兴趣的可以去了解下AST(抽象语法树)超详细,有助于写出好的框架,或者处理代码等。
    2.查看ast知道if类中cond字段主要保存的是条件,依然不是很懂if里面的语法,就想到把相关加密内容抽出来,然后修改条件对比ast抽象树的内容。
    if ($request > 1) {}

    大于1的条件.png if ($request ) {} 有值不为空的条件.png if (!$request ) {} 取非的条件.png

    针对纯if块,去修改cond达到条件反向,然后把if中里面的goto和外面的goto调换。如下的B1K3t和o3vsQ调换。

    if (!$condition) {
        goto B1K3t;
    }
    goto o3vsQ;
    

    测试的加密代码

    goto PQNsu;
    d3ERK:
    $do = $_GPC["\144\157"];
    goto EPIqU;
    o3vsQ:
    $xtitlea = urldecode($_GPC["\x78\x74\x69\x74\154\x65\x61"]);
    goto tD6yl;
    EoytB: global $_GPC, $_W;
    goto ogF_j;
    vBbWf:
    $op = strlen($_GPC["\x6f\160"]) > 1 ? $_GPC["\157\x70"] : "\154\x69\163\164";
    goto Md80W;
    EPIqU:
    if (!(strlen($_GPC["\x78\164\x69\x74\x6c\145\141"]) > 0)) {
        goto B1K3t;
    }
    goto o3vsQ;
    y3LjA:
    switch ($op) {
        case "\163\150\141\162\x65":
            goto URziN;
            ka34Z:
            goto GM3zF;
            goto dJE3d;
            Suxnm:
            goto t5lwU;
            goto vJNaD;
            tL1wK:
            if (!empty($_GPC["\156\x65\167"]) && $_GPC["\156\x65\167"] == 1) {
                goto qWt4U;
            }
            goto VpT2e;
            VpT2e:
            include $this->template("\157\154\x64\x2f\122\145\x63\x6f\162\144\x2f" . $op);
            goto Suxnm;
            vJNaD: qWt4U:
            goto A2IVc;
            URziN:
            $times = array("\x73\164\141\x72\x74" => date("\131\55\x6d\55\x64") . "\40\x30\60\x3a\60\x30\72\x30\x30", "\145\x6e\144" => date("\x59\x2d\x6d\55\x64") . "\40\x32\63\x3a\65\x39\x3a\65\x39");
            goto tL1wK;
            A2IVc:
            include $this->template("\155\171\155\x61\156\x61\147\145\57" . strtolower($_GPC["\144\x6f"]) . "\57" . $op);
            goto OUmi_;
            OUmi_: t5lwU:
            goto ka34Z;
            dJE3d:
        case "\x67\145\164\x73\145\x61\x63\150\x6a\163\x6f\x6e":
            goto MEVAi;
            H7vbb:
            $params = array();
            goto VPN1q;
            GW6qL: Nq9hx:
            goto BI3pO;
            ZpEwm:
            $params["\x3a\x73\x74\141\x72\x74\x5f\x74\x69\155\x65"] = strtotime($times["\x73\164\x61\x72\x74"]);
            goto IJ5Yo;
            jjraf: KizGW:
            goto IDenM;
            VFMDk:
            $where = "\x20\127\110\x45\122\105\40\165\x6e\151\141\x63\x69\x64\75\x3a\x75\156\x69\x61\143\x69\x64\40";
            goto H7vbb;
            Ic4HW:
            $params["\x3a\157\160\145\x6e\151\144"] = "\x25" . $_GPC["\157\160\x65\156\x69\x64"] . "\45";
            goto uCX6b;
            j0Xsg:
            $listmodel = pdo_fetchall($sql, $params);
            goto k23WD;
            KIS67:
            $where .= "\40\101\116\x44\40\165\x6e\x69\x78\x5f\164\x69\x6d\x65\x73\164\141\155\x70\50\143\x72\x65\141\x74\145\x74\x69\155\145\51\x3e\x3d\72\163\x74\141\x72\x74\137\164\151\155\145\40\x41\116\104\x20\165\156\x69\x78\137\x74\151\155\x65\x73\164\x61\x6d\160\50\x63\162\x65\141\164\x65\164\x69\x6d\x65\x29\x3c\75\x3a\x65\156\x64\137\164\x69\155\145\40";
            goto ZpEwm;
            zE3CJ:
            $sql = "\x53\x45\x4c\x45\103\x54\x20\x43\117\x55\116\124\50\x2a\51\x20\x46\122\117\115\x20\x20\40" . $fulltable . $where;
            goto F3hpp;
            JoJ5V:
            $fulltable = tablename($tablename);
            goto zE3CJ;
            VOG6F:
            $times = $_GPC["\x74\151\155\145\x73"];
            goto KIS67;
            W0s2c:
            $where .= "\x20\x41\116\104\x20\157\160\x65\x6e\x69\144\x20\x4c\x49\113\105\x20\72\x6f\160\145\x6e\x69\x64\40";
            goto Ic4HW;
            MDXuE:
            $sql = "\123\105\x4c\105\103\x54\x20\x2a\40\106\122\117\x4d\40\x20{$fulltable}\40\x20\40{$where}\40\x4f\x52\x44\x45\x52\40\x42\x59\x20" . $ararysort["\157\x72\144\145\162"] . "\40\x4c\x49\115\x49\x54\x20" . $ararysort["\157\146\x66\x73\145\x74"] . "\x2c" . $ararysort["\x6c\151\155\151\x74"];
            goto j0Xsg;
            MEVAi:
            $ararysort = ararysorts();
            goto VFMDk;
            VPN1q:
            if (empty($_GPC["\x6f\160\x65\x6e\x69\144"])) {
                goto Cu8TN;
            }
            goto W0s2c;
            HZsFd:
            $jsondate["\162\157\x77\163"] = array();
            goto ZmKAL;
            IJ5Yo:
            $params["\x3a\145\156\x64\137\x74\x69\155\x65"] = strtotime($times["\145\x6e\144"]);
            goto GW6qL;
            ZmKAL:
            xc_ajax($jsondate);
            goto jjraf;
            xZbm2: Mi4bU:
            goto HZsFd;
            leRgM:
            $jsondate = array();
            goto z12xr;
            pg3e7:
            goto KizGW;
            goto xZbm2;
            ehhW4:
            if (empty($jsondate["\164\157\164\x61\x6c"])) {
                goto Mi4bU;
            }
            goto MDXuE;
            BI3pO:
            $params["\165\x6e\x69\141\143\151\144"] = $_W["\165\x6e\151\141\143\151\x64"];
            goto JoJ5V;
            O4Ft1:
            if (empty($_GPC["\164\151\155\x65\163"])) {
                goto Nq9hx;
            }
            goto VOG6F;
            IDenM:
            goto GM3zF;
            goto O_71I;
            F3hpp:
            $total = pdo_fetchcolumn($sql, $params);
            goto leRgM;
            z12xr:
            $jsondate["\x74\157\164\141\154"] = pdo_fetchcolumn($sql, $params);
            goto ehhW4;
            lQ1Aw:
            xc_ajax($jsondate);
            goto pg3e7;
            k23WD:
            $jsondate["\x72\x6f\x77\x73"] = $listmodel;
            goto lQ1Aw;
            uCX6b: Cu8TN:
            goto O4Ft1;
            O_71I:
        case "\x73\x74\141\164\x75\163\137\x63\150\x61\156\147\145":
            goto WyrOc;
            WyrOc:
            $request = pdo_update($tablename, array("\x73\x74\141\164\x75\163" => $_GPC["\163\x74\141\x74\x75\163"], "\141\x70\x70\x6c\x79\164\151\x6d\145" => date("\x59\55\155\55\x64\40\110\x3a\x69\x3a\x73")), array("\x75\156\151\x61\x63\x69\x64" => $_W["\165\156\x69\141\143\x69\144"], "\151\x64" => $_GPC["\x69\x64"]));
            goto B6H7M;
            tt_Il:
            goto POSQh;
            goto fpjiG;
            JTPLL:
            xc_message(-1, null);
            goto tt_Il;
            qm8pg:
            goto GM3zF;
            goto mH417;
            zAERF:
            xc_message(1, null);
            goto WQO1O;
            fpjiG: FutW7:
            goto zAERF;
            B6H7M:
            if ($request) {
                goto FutW7;
            }
            goto JTPLL;
            WQO1O: POSQh:
            goto qm8pg;
            mH417:
    }
    goto ih3iG;
    PQNsu:
    defined("\111\116\137\111\x41") or exit("\x41\x63\143\x65\163\x73\x20\104\145\156\x69\x65\x64");
    goto EoytB;
    cw07p: B1K3t:
    goto y3LjA;
    ogF_j:
    $uniacid = $_W["\x75\156\x69\x61\x63\x69\x64"];
    goto vBbWf;
    tD6yl:
    $xtitleb = urldecode($_GPC["\170\x74\151\x74\154\145\142"]);
    goto cw07p;
    ih3iG: L8RpU:
    goto Oyht_;
    Md80W:
    $tablename = "\x78\143\x5f\142\x65\x61\165\x74\171\x5f\141\160\160\x6c\171";
    goto d3ERK;
    Oyht_: GM3zF:
    

    解密后的代码

    defined("IN_IA") or exit("Access Denied");
    global $_GPC, $_W;
    $uniacid = $_W["uniacid"];
    $op = strlen($_GPC["op"]) > 1 ? $_GPC["op"] : "list";
    $tablename = "xc_beauty_apply";
    $do = $_GPC["do"];
    if (strlen($_GPC["xtitlea"]) > 0) {
        $xtitlea = urldecode($_GPC["xtitlea"]);
        $xtitleb = urldecode($_GPC["xtitleb"]);
    }
    switch ($op) {
        case "share":
            $times = array("start" => date("Y-m-d") . " 00:00:00", "end" => date("Y-m-d") . " 23:59:59");
            if (!empty($_GPC["new"]) && $_GPC["new"] == 1) {
                include $this->template("mymanage/" . strtolower($_GPC["do"]) . "/" . $op);
            }
            include $this->template("old/Record/" . $op);
            break;
        case "getseachjson":
            $ararysort = ararysorts();
            $where = " WHERE uniacid=:uniacid ";
            $params = array();
            if (!empty($_GPC["openid"])) {
                $where .= " AND openid LIKE :openid ";
                $params[":openid"] = "%" . $_GPC["openid"] . "%";
            }
            if (empty($_GPC["times"])) {
                $params["uniacid"] = $_W["uniacid"];
                $fulltable = tablename($tablename);
                $sql = "SELECT COUNT(*) FROM   " . $fulltable . $where;
                $total = pdo_fetchcolumn($sql, $params);
                $jsondate = array();
                $jsondate["total"] = pdo_fetchcolumn($sql, $params);
                if (empty($jsondate["total"])) {
                    $jsondate["rows"] = array();
                    xc_ajax($jsondate);
                }
                $sql = "SELECT * FROM  {$fulltable}   {$where} ORDER BY " . $ararysort["order"] . " LIMIT " . $ararysort["offset"] . "," . $ararysort["limit"];
                $listmodel = pdo_fetchall($sql, $params);
                $jsondate["rows"] = $listmodel;
                xc_ajax($jsondate);
            }
            $times = $_GPC["times"];
            $where .= " AND unix_timestamp(createtime)>=:start_time AND unix_timestamp(createtime)<=:end_time ";
            $params[":start_time"] = strtotime($times["start"]);
            $params[":end_time"] = strtotime($times["end"]);
            $params["uniacid"] = $_W["uniacid"];
            $fulltable = tablename($tablename);
            $sql = "SELECT COUNT(*) FROM   " . $fulltable . $where;
            $total = pdo_fetchcolumn($sql, $params);
            $jsondate = array();
            $jsondate["total"] = pdo_fetchcolumn($sql, $params);
            if (empty($jsondate["total"])) {
                $jsondate["rows"] = array();
                xc_ajax($jsondate);
            }
            $sql = "SELECT * FROM  {$fulltable}   {$where} ORDER BY " . $ararysort["order"] . " LIMIT " . $ararysort["offset"] . "," . $ararysort["limit"];
            $listmodel = pdo_fetchall($sql, $params);
            $jsondate["rows"] = $listmodel;
            xc_ajax($jsondate);
            break;
        case "status_change":
            $request = pdo_update($tablename, array("status" => $_GPC["status"], "applytime" => date("Y-m-d H:i:s")), array("uniacid" => $_W["uniacid"], "id" => $_GPC["id"]));
            if ($request) {
                xc_message(1, null);
            }
            xc_message(-1, null);
            break;
    }
    

    三、总结

    至此算法已经完美的代码解密出来。

    相关文章

      网友评论

        本文标题:PHP-Parser运用之goto加密的解密经历

        本文链接:https://www.haomeiwen.com/subject/hdlhfhtx.html