PHP-Parser運用之goto解密經歷

一、初識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;
}

三、總結

至此算法已經完美的代碼解密出來。
如果需要完整工程項目可以去這里下載(需付費):
項目地址
安裝好PHP7.0或者以上版本就可以跑了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容