錦州市廣廈電腦維修|上門(mén)維修電腦|上門(mén)做系統(tǒng)|0416-3905144熱誠(chéng)服務(wù),錦州廣廈維修電腦,公司IT外包服務(wù)
topFlag1 設(shè)為首頁(yè)
topFlag3 收藏本站
 
maojin003 首 頁(yè) 公司介紹 服務(wù)項(xiàng)目 服務(wù)報(bào)價(jià) 維修流程 IT外包服務(wù) 服務(wù)器維護(hù) 技術(shù)文章 常見(jiàn)故障
錦州市廣廈電腦維修|上門(mén)維修電腦|上門(mén)做系統(tǒng)|0416-3905144熱誠(chéng)服務(wù)技術(shù)文章
PHP解密:魔方1代 全自動(dòng)反編譯器

作者: 佚名  日期:2018-07-24 17:06:56   來(lái)源: 本站整理

樣本

一個(gè) DiscuzX 插件 keke_xzhseo.class.php

過(guò)程

代碼格式化

參考之前的帖子PHP加密中的“VMProtect”——魔方加密反編譯分析過(guò)程

大致瀏覽一下文件內(nèi)容,可以看到 KIVIUQ VIRTUAL MACHINE ERROR : Access violation at address 

(KIVIUQ虛擬機(jī)錯(cuò)誤:在xxx地址處讀取錯(cuò)誤)這個(gè)東西,可以確定是魔方加密了。

魔方加密是一種基于虛擬機(jī)的加密,他將原本函數(shù)調(diào)用、運(yùn)算符等操作,拆分成參數(shù)壓棧、執(zhí)行指令、結(jié)果出棧

這種步驟,所以“解密”是不可能,只能通過(guò)反編譯的方式嘗試還原代碼。

分析虛擬機(jī)

更牛逼的代碼格式化

  1. 為了方便閱讀,我把亂碼變量名替換成 $v0 這類的可讀變量名了。
  2. 把通過(guò) . 連接的字符串合成了一整個(gè),然后把特別長(zhǎng)的字符串輸出到一個(gè)單獨(dú)的文件 large_string_data.php,
  3. 方便以后使用。
  4. 由于后面破解過(guò)程中發(fā)現(xiàn)替換變量名對(duì)虛擬機(jī)有影響,所以我把 亂碼變量名 => 可讀變量名 輸出到一個(gè)單獨(dú)
  5. 的文件 variables_map.php,方便以后使用。

2018 年 03 月 01 日 nikic/php-parser 為了發(fā)展 PHP 7 更新了 4.0 版本,所以 format.php 的部分代碼與

前面的帖子相比有所更改。有興趣的同學(xué)可以研究我的代碼是怎么寫(xiě)的,沒(méi)興趣的就看看就好了。

$GLOBALS['LARGE_STRING_DATA'] = (include 'large_string_data.php');
if (isset($v0)) {
    array_push($v0, $v1, $v2, $v3, $v4, $v5);
} else {
    $v0 = array();
}
static $v6 = null;
if (empty($v6)) {
    $v6 = $GLOBALS['LARGE_STRING_DATA'][0];
}
$v1 = array(__FILE__);
$v2 = array(0);
$v3 = $v4 = $v5 = 0;
$v7 = $v8 = null;
try {
    while (1) {
        while ($v5 >= 0) {
            $v8 = $v6[$v5++];
            switch ($v8 ^ $v6[$v5++]) {
                // 各種指令,此處省略
            }
            while ($v7-- > 0) {
                $v8 .= $v8[0] ^ $v6[$v5++];
            }
            eval(substr($v8, 1));
        }
        if ($v5 == -1) {
            break;
        } elseif ($v5 == -2) {
            eval($v2[$v4 - 1]);
            $v5 = $v2[$v4];
            $v4 -= 2;
        } else {
            exit('KIVIUQ VIRTUAL MACHINE ERROR : Access violation at address '
 . ($v5 < 0 ? $v5 : sprintf('%08X', $v5)));
        }
    }
} catch (Exception $v8) {
    if (!empty($v0)) {
        $v5 = array_pop($v0);
        $v4 = array_pop($v0);
        $v3 = array_pop($v0);
        $v2 = array_pop($v0);
        $v1 = array_pop($v0);
    }
    throw $v8;
}
if (!empty($v0)) {
    $v5 = array_pop($v0);
    $v4 = array_pop($v0);
    $v3 = array_pop($v0);
    $v2 = array_pop($v0);
    $v1 = array_pop($v0);
}

虛擬機(jī)的運(yùn)行流程

大致瀏覽一下這段代碼,通過(guò)分析可以知道,各個(gè)變量的含義,虛擬機(jī)的運(yùn)行流程。

變量名 含義
$v0 虛擬機(jī)環(huán)境
$v1
$v2 (未知,后文分析可知是報(bào)錯(cuò)等級(jí)棧)
$v3 棧指針
$v4 (未知,后文分析可知是報(bào)錯(cuò)等級(jí)棧指針)
$v5 內(nèi)存指針
$v6 指令 + 指令集 + 數(shù)據(jù)(可以稱之為內(nèi)存,類似 .text 代碼段)
$v7 異或解碼之后的數(shù)值,代表語(yǔ)句的字符串長(zhǎng)度
$v8 臨時(shí)變量(一個(gè)寄存器),用于異或解碼,用于存儲(chǔ)解密之后的指令,用于 try-catch 的異常變量
指令名稱 含義
1 取 2 字節(jié)以內(nèi)的字符串作為二級(jí)指令執(zhí)行
2 取 4 字節(jié)以內(nèi)的字符串作為二級(jí)指令執(zhí)行
3 取 10 字節(jié)以內(nèi)的字符串作為二級(jí)指令執(zhí)行
a 出棧
b 棧解除引用
c 壓棧,壓入 null
d 取數(shù)組元素或字符串中的字符
e 取特殊變量,超全局變量和 this 特殊變量,或其他棧頂變量名的變量
fd 取 100 字節(jié)以內(nèi)的字符串壓到棧頂
fq 取 10^4 字節(jié)以內(nèi)的字符串壓到棧頂
fx 取 10^10 字節(jié)以內(nèi)的字符串壓到棧頂
主循環(huán) eip 對(duì)應(yīng)的操作
>= 0 繼續(xù)虛擬機(jī)主循環(huán),運(yùn)行指令
-1 結(jié)束虛擬機(jī)主循環(huán)
-2 eval($v2[$v4 - 1]); $v5 = $v2[$v4]; $v4 -= 2;
其他 虛擬機(jī)出錯(cuò)

運(yùn)行結(jié)束后,從虛擬機(jī)環(huán)境 $v0 中依次彈出 $v5 $v4 $v3 $v2 $v1。

這里提到一個(gè)詞——“二級(jí)指令”,這個(gè)詞是我隨便起的,就是上述的十幾個(gè)指令是在虛擬機(jī)運(yùn)行環(huán)境的代碼中直接顯式給出的,所以

稱為“一級(jí)指令”,而二級(jí)指令就是指,解析出一個(gè)字符串然后再調(diào)用 eval 來(lái)執(zhí)行的指令。

分析完虛擬機(jī)的邏輯之后,我們發(fā)現(xiàn),不能像上一篇文章中的方法,直接分析每一條虛擬機(jī)指令,反編譯出代碼。我們必須跟隨虛擬機(jī)的

運(yùn)行,然后把每一條二級(jí)指令也還原出來(lái),然后才能分析。

跟隨虛擬機(jī)運(yùn)行一下

我們可以改造一下這個(gè)虛擬機(jī),在每一條指令執(zhí)行時(shí),輸出他們做了什么事,以及他們的指令地址。

注意,我們需要用到 xdebug 來(lái)調(diào)試 php 程序,同時(shí),最好選擇一個(gè) IDE 來(lái)輔助調(diào)試(我用的是 PHPStorm)。

代碼在執(zhí)行過(guò)程中,我們需要利用調(diào)試器,視情況調(diào)整一下環(huán)境:

  • 如果虛擬機(jī)想要使用某些不存在的常量,我們可以提前定義常量,防止程序運(yùn)行錯(cuò)誤。
  • 如果虛擬機(jī)想要使用某些不存在的變量,我們可以提前給他們賦值,防止程序運(yùn)行錯(cuò)誤。
  • 如果虛擬機(jī)想要運(yùn)行某個(gè)不存在的函數(shù),我們可以直接跳過(guò)。
  • 如果虛擬機(jī)想要進(jìn)行條件跳轉(zhuǎn),我們可以改變跳轉(zhuǎn)或不跳轉(zhuǎn)。

改造虛擬機(jī)的過(guò)程

eval(substr($v8, 1));

改成

$v8 = str_replace(array_keys($GLOBALS['VARIABLES_MAP']), array_values($GLOBALS['VARIABLES_MAP']), $v8);
$code = substr($v8, 1);
echo $code, PHP_EOL;
$is_eval = true;
if ($is_eval) {
    eval(substr($v8, 1));
}

然后在 if ($is_eval) { 這句下斷點(diǎn),每次執(zhí)行到這里,如果想跳過(guò)本條語(yǔ)句的話,就 $is_eval = false;

可以大致感覺(jué)到執(zhí)行一條語(yǔ)句的大致過(guò)程是:

  1. 壓棧,壓入 null
  2. 取函數(shù)名
  3. 取變量(特殊變量/字符串),作為第一個(gè)參數(shù)
  4. 繼續(xù)取變量,作為第二個(gè)參數(shù)
  5. 取二級(jí)指令并執(zhí)行(可能是調(diào)用函數(shù)、連接字符串等等)
  6. 出棧
  7. 使用引用+賦值+解除引用的方式,把結(jié)果傳遞到某個(gè)變量

反匯編

基本的反匯編

反匯編,就是脫離運(yùn)行環(huán)境,分析機(jī)器指令。照著虛擬機(jī)的邏輯改就行了。

00000000 - 00000001  壓入null
00000002 - 0000000D  壓入字符串  defined
0000000E - 00000049  執(zhí)行二級(jí)指令  $v1[++$v3]="\111\116\137\104\111\123\103\125\132";
0000004A - 0000007F  執(zhí)行二級(jí)指令  $v1[$v3-2]=$v1[$v3-1]($v1[$v3]);
00000080 - 00000081  出棧
00000082 - 00000083  出棧
00000084 - 00000085  解除引用
00000086 - 000000A8  執(zhí)行二級(jí)指令  $v1[$v3]=!$v1[$v3];
000000A9 - 000000D0  執(zhí)行二級(jí)指令  if($v1[$v3])$v5=0x000000E9;
000000D1 - 000000D2  出棧
000000D3 - 000000E8  執(zhí)行二級(jí)指令  $v5=0x0000012E;
000000E9 - 000000EA  出棧
000000EB - 000000FC  壓入字符串  Access Denied
000000FD - 00000115  執(zhí)行二級(jí)指令  exit($v1[$v3]);
00000116 - 00000117  出棧
00000118 - 0000012D  執(zhí)行二級(jí)指令  $v5=0x0000012E;
0000012E - 0000012F  壓入null
00000130 - 0000013D  執(zhí)行二級(jí)指令  $v5=-1;
內(nèi)存越界

內(nèi)存越界是因?yàn)槲沂前错樞蚍磪R編一級(jí)指令,然后編碼解密二級(jí)指令,沒(méi)有實(shí)際運(yùn)行二級(jí)指令,所以不知道程序什么時(shí)候終止(就是還不知道 $v5=-1; 是什么)。其實(shí)就是代碼沒(méi)了,強(qiáng)行終止了。不用管這個(gè)。

上面這段指令,對(duì)應(yīng)的代碼其實(shí)就是

if (!defined('IN_DISCUZ')) {
    exit('Access Denied');
}

增強(qiáng)的反匯編

只是像這樣簡(jiǎn)單地反匯編還不行,我們必須把每一條二級(jí)指令的代碼都想辦法拆分成指令+數(shù)據(jù)的形式,然后才能供反編譯使用。

這里列舉一些簡(jiǎn)單的二級(jí)指令(指令集可能不止這些)。

// 取數(shù)據(jù)
$stack[$esp] = ???;

// 條件跳轉(zhuǎn)
if ($stack[$esp]) $eip = 0x????;

// 無(wú)條件跳轉(zhuǎn)
$eip = 0x????;

// 調(diào)用函數(shù)
$stack[$esp - 1] = $stack[$esp]();
$stack[$esp - 2] = $stack[$esp - 1]($stack[$esp]);
$stack[$esp - 3] = $stack[$esp - 2]($stack[$esp - 1], $stack[$esp]);

// 比較大小、算數(shù)運(yùn)算、字符串鏈接等等

由于指令較多(共有數(shù)十種),具體指令集請(qǐng)參考成品代碼。

結(jié)果像下面這樣

00000000 - 0000000E  global  $_G
0000000F - 00000022  global  $article
00000023 - 00000024  壓入null
00000025 - 00000030  壓入字符串  defined
00000031 - 0000004C  壓入字符串  CLOUDADDONS_WEBSITE_URL
0000004D - 00000082  調(diào)用函數(shù)  1
00000083 - 00000084  出棧
00000085 - 00000086  出棧
00000087 - 00000088  解除引用
00000089 - 000000AB  取非
000000AC - 000000D3  條件跳轉(zhuǎn)  000000EC
000000D4 - 000000D5  出棧
000000D6 - 000000EB  無(wú)條件跳轉(zhuǎn)  000001E7
000001E7 - 000001E8  壓入null
000001E9 - 00000207  壓入常量  DISCUZ_ROOT
00000208 - 00000236  壓入字符串  source/plugin/keke_xzhseo/identity.inc.php
00000237 - 0000026B  字符串連接
0000026C - 00000281  無(wú)條件跳轉(zhuǎn)  00000288
00000288 - 00000289  出棧
0000028A - 000002B9  include   2
000002BA - 000002BB  出棧
000002BC - 000002D8  壓入空數(shù)組
000002D9 - 000002E2  壓入字符串  check
000002E3 - 000002E4  引用變量
000002E5 - 00000308  棧內(nèi)賦值  1
00000309 - 0000030A  解除引用
0000030B - 0000030C  出棧
0000030D - 0000030E  出棧
0000030F - 00000310  壓入null
00000311 - 0000031B  壓入字符串  substr
0000031C - 0000031D  壓入null
0000031E - 00000340  壓入字符串  md5
00000341 - 00000350  壓入字符串  keke_xzhseo
00000351 - 00000357  壓入字符串  _G
00000358 - 00000359  引用變量
0000035A - 00000365  壓入字符串  siteurl
00000366 - 00000367  取數(shù)組元素
00000368 - 00000369  出棧
0000036A - 0000036B  解除引用
0000036C - 000003A0  字符串連接
000003A1 - 000003A2  出棧
000003A3 - 000003B8  無(wú)條件跳轉(zhuǎn)  000003BF
000003BF - 000003F4  調(diào)用函數(shù)  1
000003F5 - 000003F6  出棧
000003F7 - 0000040C  無(wú)條件跳轉(zhuǎn)  00000413
00000413 - 00000414  出棧
00000415 - 00000416  解除引用
00000417 - 0000042D  壓入數(shù)字  0
0000042E - 00000444  壓入數(shù)字  7
00000445 - 0000049C  調(diào)用函數(shù)  3
0000049D - 0000049E  出棧
0000049F - 000004B4  無(wú)條件跳轉(zhuǎn)  000004BB
000004BB - 000004BC  出棧
000004BD - 000004BE  出棧
000004BF - 000004C0  出棧
000004C1 - 000004D6  無(wú)條件跳轉(zhuǎn)  000004DD
000004DD - 000004DE  解除引用
000004DF - 000004E8  壓入字符串  uskey
000004E9 - 000004EA  引用變量
000004EB - 0000050E  棧內(nèi)賦值  1
0000050F - 00000510  解除引用
00000511 - 00000512  出棧
00000513 - 00000514  出棧
00000515 - 00000516  壓入null
00000517 - 00000524  壓入字符串  loadcache
00000525 - 00000550  壓入字符串  uskey
00000551 - 00000552  引用變量
00000553 - 00000588  調(diào)用函數(shù)  1
00000589 - 0000059E  無(wú)條件跳轉(zhuǎn)  000005A5
000005A5 - 000005A6  出棧
000005A7 - 000005A8  出棧
000005A9 - 000005AA  解除引用
000005AB - 000005AC  出棧
000005AD - 000005B3  壓入字符串  _G
000005B4 - 000005B5  引用變量
000005B6 - 000005E1  壓入字符串  cache
000005E2 - 000005E3  取數(shù)組元素
000005E4 - 000005E5  出棧
000005E6 - 000005FB  無(wú)條件跳轉(zhuǎn)  00000602
00000602 - 0000060B  壓入字符串  uskey

你可以看到這里多出了許多指令,比如 global, 調(diào)用函數(shù), 取非, 條件跳轉(zhuǎn), 無(wú)條件跳轉(zhuǎn),這些指令就是解析之后的二級(jí)指令。

現(xiàn)在我們反匯編之后的結(jié)果是“線性的”了,可以被反編譯了。

DFS反匯編

你或許以為上面得到的反匯編指令是很容易的,其實(shí)不是這樣的,這些指令中有一些“花指令”,就像下面這樣。

0000026C - 00000281  無(wú)條件跳轉(zhuǎn)  00000288
00000288 - 00000289  出棧

這里的 00000282 - 00000288 之間的指令沒(méi)法執(zhí)行,由于指令長(zhǎng)短不一樣,這段花指令打亂了原本解析過(guò)程,所以必須要用較高級(jí)的方法。

  1. 如果遇到無(wú)條件跳轉(zhuǎn),直接跳轉(zhuǎn)。
  2. 如果遇到條件跳轉(zhuǎn)指令,分成兩個(gè)分支來(lái)解析。遇到分支則繼續(xù)分下去(遞歸),直到解析的指令之前已經(jīng)解析過(guò)了、或跳轉(zhuǎn)到 -1(跳轉(zhuǎn)到 -1 就類似 return 語(yǔ)句,代表結(jié)束虛擬機(jī)),直到已經(jīng)解析完所有指令。
  3. 最后按指令在虛擬機(jī)中出現(xiàn)的順序排序即可。

簡(jiǎn)而言之,這就是一個(gè)深度優(yōu)先搜索(DFS)。

通過(guò)這一步驟,我們真正把所有有用的指令提取出來(lái)了,沒(méi)用的指令直接拋棄了,已經(jīng)真正脫離了虛擬機(jī)了,我們得到的可以稱之為更為通用的字節(jié)碼了。

指令分塊(鏈表到圖)

順序的指令都很好解析,也很好反編譯,分支結(jié)構(gòu)是比較麻煩的,最麻煩的就是循環(huán)結(jié)構(gòu)。為了方便之后分析程序流程,這里可以先把“線性”的反匯編程序轉(zhuǎn)換為無(wú)序的“向量圖”。

我采用的方法也是比較好理解的:

  1. 在所有與跳轉(zhuǎn)有關(guān)的位置(跳出和跳入)將代碼分塊,保證每塊中最多 1 個(gè)跳轉(zhuǎn),且跳轉(zhuǎn)指令必須是最后一條。
  2. 遍歷每一個(gè)分塊,分析每一塊結(jié)束時(shí)跳轉(zhuǎn)的去向,構(gòu)造成一個(gè)圖。
  3. 跳轉(zhuǎn)到 -1 的塊將最后跳轉(zhuǎn)到 -1 的指令改成 return 指令。
  4. 對(duì)圖進(jìn)行一些拓?fù)渥儞Q,簡(jiǎn)化圖,例如把連續(xù)幾個(gè)直線串起來(lái)的塊合成一個(gè)等等。(這一步不是必須的,因?yàn)楹竺娴倪M(jìn)行流程分析,自然會(huì)把無(wú)分支的指令連成一整塊的)

如果用流程圖可視化地表示一下,大概就是這樣的。

分塊之后由于沒(méi)有了塊內(nèi)跳轉(zhuǎn),所以我們不再需要每一條指令的地址了,我們只需要給每個(gè)分塊一個(gè)獨(dú)立的 id 即可。同時(shí)也沒(méi)有了“跳轉(zhuǎn)”這種說(shuō)法了,無(wú)條件跳轉(zhuǎn)變成了連續(xù)的指令了,條件跳轉(zhuǎn)變成了分支(或者循環(huán))了。

用過(guò) IDA 或 x64dbg 的同學(xué)可能對(duì)這種圖比較熟悉了。

反編譯

分析流程

前面說(shuō)了,反編譯線性的指令很簡(jiǎn)單,條件分支和循環(huán)比較復(fù)雜,復(fù)雜就因?yàn)樗麄兊牧鞒逃蟹种А⒂袑哟谓Y(jié)構(gòu),不能使用循環(huán)來(lái)解決,需要使用遞歸才比較方便。

在我嘗試反編譯的時(shí)候,個(gè)人感覺(jué)各種指令的反編譯,最簡(jiǎn)單的就是線性代碼了,其次就是單分支結(jié)構(gòu) if,然后就是循環(huán) while、for 等,最麻煩的就是 break 和 continue 了。

我采用的方案如下:

  1. 線性代碼一直運(yùn)行。
  2. 遇到條件分支采用 DFS 分析,先走 yes 再走 no。
  3. 遇到循環(huán)則記錄當(dāng)前環(huán)的所有頂點(diǎn)。然后退回到最后一個(gè)條件分支,如果剛才是 yes 分支,則繼續(xù)嘗試走 no 分支,如果已經(jīng)是 no 分支了,則開(kāi)始分析這個(gè)“條件分支構(gòu)成的循環(huán)”。
  4. 分析“條件分支構(gòu)成的循環(huán)”的方法:將“條件分支構(gòu)成的循環(huán)”轉(zhuǎn)換為“無(wú)條件循環(huán)” +  if-break 語(yǔ)句。
  5. 遇到終點(diǎn)則正常回退到最后的條件分支,執(zhí)行另一個(gè)分支或執(zhí)行分析。
  6. 如果沒(méi)有構(gòu)成循環(huán),分析普通條件分支的方法:將條件分支轉(zhuǎn)換為 if 語(yǔ)句,yes、no 分別構(gòu)成 stmts 和 else 塊。
  7. 假設(shè)不存在循環(huán)交叉(即假設(shè)變異前沒(méi)有極其變態(tài)的 goto 語(yǔ)句)。

  8. 如果遇到無(wú)條件跳轉(zhuǎn),直接跳轉(zhuǎn)。
  9. 如果遇到條件跳轉(zhuǎn)指令,保存當(dāng)前反匯編器的指針位置,以及一些其他的狀態(tài)信息,然后分成兩個(gè)分支來(lái)解析。兩個(gè)分支順序解析,直到遇到另一個(gè)
  10. 分支或者虛擬機(jī)退出指令,交換分支的控制權(quán),直到兩個(gè)分支合成一個(gè)分支時(shí)結(jié)束,繼續(xù)按一個(gè)分支解析。此條語(yǔ)句記為 if。
  11. 同時(shí)建立一個(gè)已經(jīng)分析過(guò)的地址列表,如果跳往分析過(guò)的,則記錄為 while。

說(shuō)了半天就是使用 BFS(廣度優(yōu)先搜索)分析語(yǔ)法分支

最開(kāi)始,反匯編、指令分塊與分析流程這幾步是同時(shí)進(jìn)行的,直接采用 BFS 來(lái)反匯編、分塊、構(gòu)造 if 和 while 結(jié)構(gòu)。后來(lái)感覺(jué)代碼越寫(xiě)越復(fù)雜,分析

了一下每個(gè)步驟可以獨(dú)立開(kāi)來(lái),就使用 DFS 反匯編(因?yàn)?DFS 代碼比 BFS 簡(jiǎn)單),然后簡(jiǎn)單地根據(jù)跳轉(zhuǎn)分塊并優(yōu)化,最后使用 BFS 分析流程。這樣感覺(jué)的確清晰了不少。

舉個(gè)例子

00000001 條件跳轉(zhuǎn) 00000004
00000002 指令塊1
00000003 無(wú)條件跳轉(zhuǎn) 00000006
00000004 指令塊2
00000005 無(wú)條件跳轉(zhuǎn) 00000008
00000006 指令塊3
00000007 無(wú)條件跳轉(zhuǎn) 00000009
00000008 指令塊4
00000009 指令塊5

我們解析的結(jié)果應(yīng)該是

if ($stack[$esp]) {
    指令塊2
    指令塊4
} else {
    指令塊1
    指令塊3
}
指令塊5

再舉個(gè)例子

00000001 條件跳轉(zhuǎn) 00000004
00000002 指令塊1
00000003 無(wú)條件跳轉(zhuǎn) 00000001
00000004 指令塊2

解析得到

while ($stack[$esp]) {
    指令塊1
}
指令塊2

經(jīng)過(guò)我們不懈的努力,上文的第一段反匯編程序(就是這段 if (!defined('IN_DISCUZ')) { exit('Access Denied'); }),分塊結(jié)果如下

壓入null
壓入字符串  defined
壓入字符串  IN_DISCUZ
調(diào)用函數(shù)  1
出棧
出棧
解除引用
取非
如果
    出棧
    壓入字符串  Access Denied
    exit
    出棧
否則
    出棧
壓入null

反編譯

普通的反編譯

普通的反編譯,原理很簡(jiǎn)單,指令對(duì)棧做了什么操作,我們也就同樣根據(jù)他的操作構(gòu)造抽象語(yǔ)法樹(shù)(AST),構(gòu)建 AST 正好是編譯的逆過(guò)程。

由于魔方1代加密是一種僅基于棧的指令集,沒(méi)有寄存器的存在,反編譯算法會(huì)變得簡(jiǎn)單。

比如剛才那段指令,構(gòu)建 AST 用的棧的內(nèi)容變化就是這樣的

  1. null
  2. null, 'defined'
  3. null, 'defined', 'IN_DISCUZ'
  4. defined('IN_DISCUZ'), 'defined', 'IN_DISCUZ'
  5. defined('IN_DISCUZ'), 'defined'
  6. defined('IN_DISCUZ')
  7. defined('IN_DISCUZ')
  8. !defined('IN_DISCUZ')
  9. if (!defined('IN_DISCUZ')) {} else {}
    • stmts 塊:
      1. 'Access Denied'
      2. exit('Access Denied');
    • else 塊:
      • (空)
  10. if (!defined('IN_DISCUZ')) { exit('Access Denied'); } else {}

這樣就還原出來(lái)了這段指令對(duì)應(yīng)的源碼。

表達(dá)式和語(yǔ)句

實(shí)踐中,你可能會(huì)發(fā)現(xiàn),這種方法看上去很簡(jiǎn)單,但是也是存在一些問(wèn)題的。比如,如何區(qū)分表達(dá)式 Expression 和語(yǔ)句 Statement,有些表

達(dá)式會(huì)影響運(yùn)行環(huán)境,而他們運(yùn)行完不會(huì)返回運(yùn)行結(jié)果給棧(或者運(yùn)行結(jié)果被拋棄),如果這時(shí)下一條語(yǔ)句是“出棧”的話,將在 AST 中出現(xiàn)一

個(gè)單獨(dú)的表達(dá)式。在 PHP 中表達(dá)式是不能充當(dāng)語(yǔ)句的,他后面必須有一個(gè)分號(hào)才可以構(gòu)成一個(gè)語(yǔ)句,我們必須得想想方法。

最后我想到一個(gè)好辦法,把所有已經(jīng)被使用過(guò)的表達(dá)式添加一個(gè) used 屬性,每當(dāng)一個(gè)表達(dá)式被丟棄的時(shí)候(出棧或者解除引用都會(huì)使表達(dá)

式從棧中被移除),如果這個(gè)表達(dá)式?jīng)]有被使用過(guò),則使用這個(gè)表達(dá)式構(gòu)建一條語(yǔ)句,放到 AST 中。如果出棧的本來(lái)就是語(yǔ)句,那就直接放到 AST 中就行了,不需要其他處理。

if 語(yǔ)句、邏輯短路、三元運(yùn)算符

If statement, Logical Short-Circuit, Ternary 這三個(gè)東西都可以通過(guò)條件跳轉(zhuǎn)來(lái)表示,只不過(guò)三個(gè)東西對(duì)棧的操作不同

if 語(yǔ)句會(huì)在判斷之后就直接拋棄判斷條件,stmts 塊和 else 塊都會(huì)緊跟一個(gè)出棧,最終的棧會(huì)比執(zhí)行之前少一層(把判斷條件出棧了)。

if ($cond)
    {stmts}
else
    {else}
壓入 $cond
如果
    出棧
    {stmts}
否則
    出棧
    {else}

邏輯短路,通常是“邏輯或”短路,stmts 塊為空,else 塊都會(huì)緊跟一個(gè)出棧,但隨后還會(huì)再壓入一個(gè)值,最終的棧和執(zhí)行之前平衡。

如果和上面的情況相反,else 塊為空,則是“邏輯與”短路。

$a or $b
壓入 $a
如果
否則
    出棧
    壓入 $b

三元運(yùn)算符算是前面兩個(gè)的結(jié)合體,stmts 塊和 else 塊都會(huì)緊跟一個(gè)出棧,兩個(gè)塊隨后都還會(huì)再壓入一個(gè)值,最終的棧和執(zhí)行之前平衡。

$cond ? $a : $b
壓入 $cond
如果
    出棧
    壓入 $a
否則
    出棧
    壓入 $b

我們可以通過(guò)判斷 stmts 塊和 else 塊來(lái)區(qū)分三者,也可以通過(guò)最終的棧和之前的棧進(jìn)行對(duì)比來(lái)區(qū)分。(我選擇了第二種,容錯(cuò)性高,而且出現(xiàn)意外錯(cuò)誤可以拋出異常)

循環(huán)

0000022E - 0000023B 壓入字符串  checkdirs
0000023C - 0000023D 引用
0000023E - 0000023F 解除引用
00000240 - 00000259 reset
0000025A - 0000025F 壓入字符串  k
00000260 - 00000261 引用
00000262 - 00000269 壓入字符串  dir
0000026A - 0000026B 引用
0000026C - 00000306 調(diào)用函數(shù)  0
00000307 - 0000032E 條件跳轉(zhuǎn)  00000347
0000032F - 00000330 出棧
00000331 - 00000346 無(wú)條件跳轉(zhuǎn)  00000DA3
00000347 - 00000348 出棧
中間省去一部分指令
00000B3E - 00000B4B 壓入字符串  writeable
00000B4C - 00000B4D 引用
00000B4E - 00000B4F 解除引用
00000B50 - 00000B72 boolean_not
00000B73 - 00000B9A 轉(zhuǎn)換為bool
00000B9B - 00000BC2 條件跳轉(zhuǎn)  00000BF5
00000BC3 - 00000BD8 無(wú)條件跳轉(zhuǎn)  00000C2B
00000BF5 - 00000BF6 出棧
00000BF7 - 00000BFE 壓入字符串  dir
00000BFF - 00000C00 引用
00000C01 - 00000C02 解除引用
00000C03 - 00000C2A 轉(zhuǎn)換為bool
00000C2B - 00000C52 條件跳轉(zhuǎn)  00000C87
00000C53 - 00000C54 出棧
00000C55 - 00000C6A 無(wú)條件跳轉(zhuǎn)  00000C71
00000C71 - 00000C86 無(wú)條件跳轉(zhuǎn)  00000D72
00000C87 - 00000C88 出棧
00000C89 - 00000C90 壓入字符串  dir
00000C91 - 00000C92 引用
00000C93 - 00000CA8 無(wú)條件跳轉(zhuǎn)  00000CAF
00000CAF - 00000CB0 解除引用
00000CB1 - 00000CBB 壓入字符串  return
00000CBC - 00000CBD 引用
00000CBE - 00000D15 數(shù)組元素獲取  0  
00000D16 - 00000D2B 無(wú)條件跳轉(zhuǎn)  00000D32
00000D32 - 00000D55 賦值  0  1
00000D56 - 00000D57 解除引用
00000D58 - 00000D59 出棧
00000D5A - 00000D5B 出棧
00000D5C - 00000D71 無(wú)條件跳轉(zhuǎn)  00000D72
00000D72 - 00000D8C next
00000D8D - 00000DA2 無(wú)條件跳轉(zhuǎn)  0000026C
0000026C
reset($checkdirs);
if ($k = $dir()) {
} else {
    goto loop_end;
}
loop_start:
// 中間省去一部分指令
if (!$writeable || $dir) {
    $return[] = $dir;
}
next($checkdirs);
goto loop_start;
loop_end:

等價(jià)轉(zhuǎn)換一下

reset($checkdirs);
while ($k = $dir()) {
    // 中間省去一部分指令
    if (!$writeable || $dir) {
        $return[] = $dir;
    } else {
        break;
    }
    next($checkdirs);
}

繼續(xù)分析所有指令

想要全自動(dòng)解析整個(gè)文件,偷懶是不行的,必須得把每一種指令都匹配出來(lái),然后再手動(dòng)寫(xiě)好每一種指令的構(gòu)造 AST 的代碼。

自動(dòng)反編譯與手動(dòng)修改之后的對(duì)照

匯編語(yǔ)言

00000000 壓入常量  false
0000001B 壓入字符串  prefix
00000026 引用
00000028 賦值  0  1
0000004C 解除引用
0000004E 出棧  1
00000050 出棧  1
00000052 壓入字符串  prefix
0000005D 引用
0000005F 解除引用
00000061 壓入常量  false
0000007C 完全相同
000000B3 出棧  1
000000B5 條件跳轉(zhuǎn)  000000F5
000000DD 出棧  1
000000DF 無(wú)條件跳轉(zhuǎn)  00000226
000000F5 出棧  1
000000F7 壓入常量  null
000000F9 壓入字符串  strlen
00000104 壓入字符串  dir
0000010C 引用
0000010E 調(diào)用函數(shù)  1
00000144 出棧  1
00000146 出棧  1
00000148 解除引用
0000014A 壓入數(shù)字  1
00000161 相加
00000196 無(wú)條件跳轉(zhuǎn)  000001B2
000001B2 出棧  1
000001B4 壓入字符串  prefix
000001E4 引用
000001E6 賦值  0  1
0000020A 解除引用
0000020C 出棧  1
0000020E 出棧  1
00000210 無(wú)條件跳轉(zhuǎn)  00000226
00000226 壓入常量  null
00000228 壓入字符串  opendir
00000234 壓入字符串  dir
0000023C 引用
0000023E 調(diào)用函數(shù)  1
00000274 出棧  1
00000276 出棧  1
00000278 解除引用
0000027A 壓入字符串  dh
00000281 引用
00000283 賦值  0  1
000002A7 解除引用
000002A9 出棧  1
000002AB 出棧  1
000002AD 壓入常量  null
000002AF 壓入字符串  readdir
000002BB 壓入字符串  dh
000002DB 引用
000002DD 調(diào)用函數(shù)  1
00000313 出棧  1
00000315 出棧  1
00000317 解除引用
00000319 壓入字符串  file
00000322 引用
00000324 無(wú)條件跳轉(zhuǎn)  00000340
00000340 賦值  0  1
00000364 解除引用
00000366 出棧  1
00000368 壓入常量  false
00000383 完全相同
000003BA 出棧  1
000003BC 取非
000003DF 條件跳轉(zhuǎn)  0000041F
00000407 出棧  1
00000409 無(wú)條件跳轉(zhuǎn)  00000CB3
0000041F 出棧  1
00000421 壓入字符串  file
0000042A 引用
0000042C 解除引用
0000042E 壓入字符串  .
00000434 相等
0000046A 出棧  1
0000046C 取非
0000048F 轉(zhuǎn)換為bool
000004B7 條件跳轉(zhuǎn)  000004F5
000004DF 無(wú)條件跳轉(zhuǎn)  000005C9
000004F5 出棧  1
000004F7 壓入字符串  file
0000051F 引用
00000521 解除引用
00000523 壓入字符串  ..
0000052A 相等
00000560 無(wú)條件跳轉(zhuǎn)  0000057C
0000057C 出棧  1
0000057E 取非
000005A1 轉(zhuǎn)換為bool
000005C9 條件跳轉(zhuǎn)  00000609
000005F1 出棧  1
000005F3 無(wú)條件跳轉(zhuǎn)  00000C9D
00000609 出棧  1
0000060B 壓入字符串  dir
00000613 引用
00000615 解除引用
00000617 壓入字符串  /
0000061D 無(wú)條件跳轉(zhuǎn)  00000639
00000639 字符串鏈接
0000066E 出棧  1
00000670 壓入字符串  file
00000679 引用
0000067B 解除引用
0000067D 字符串鏈接
000006B2 出棧  1
000006B4 無(wú)條件跳轉(zhuǎn)  000006D0
000006D0 壓入字符串  readfile
000006DD 引用
000006DF 賦值  0  1
00000703 解除引用
00000705 出棧  1
00000707 出棧  1
00000709 壓入常量  null
0000070B 壓入字符串  is_dir
00000716 壓入字符串  readfile
00000723 引用
00000725 調(diào)用函數(shù)  1
0000075B 出棧  1
0000075D 出棧  1
00000779 解除引用
0000077B 條件跳轉(zhuǎn)  000007BB
000007A3 出棧  1
000007A5 無(wú)條件跳轉(zhuǎn)  00000C87
000007BB 出棧  1
000007BD 壓入字符串  root
000007C6 引用
000007C8 解除引用
000007CA 壓入字符串  /
000007E5 字符串鏈接
0000081A 出棧  1
0000081C 壓入常量  null
0000081E 壓入字符串  substr
00000829 壓入字符串  readfile
00000836 引用
00000838 壓入字符串  prefix
00000843 引用
00000845 調(diào)用函數(shù)  2
0000088C 無(wú)條件跳轉(zhuǎn)  000008A8
000008A8 出棧  1
000008AA 出棧  1
000008AC 出棧  1
000008AE 解除引用
000008B0 字符串鏈接
000008E5 出棧  1
000008E7 壓入字符串  return
000008F2 引用
00000AF3 數(shù)組元素獲取  0  
00000B4B 賦值  0  1
00000B6F 解除引用
00000B71 出棧  1
00000B73 出棧  1
00000B75 壓入常量  null
00000B77 無(wú)條件跳轉(zhuǎn)  00000B93
00000B93 壓入字符串  cloudaddons_getsubdirs
00000BAE 無(wú)條件跳轉(zhuǎn)  00000BCA
00000BCA 壓入字符串  readfile
00000BD7 引用
00000BD9 壓入字符串  root
00000BE2 引用
00000BE4 壓入字符串  return
00000BEF 引用
00000BF1 調(diào)用函數(shù)  3
00000C49 出棧  1
00000C4B 出棧  1
00000C4D 出棧  1
00000C4F 出棧  1
00000C51 解除引用
00000C53 出棧  1
00000C55 無(wú)條件跳轉(zhuǎn)  00000C87
00000C87 無(wú)條件跳轉(zhuǎn)  00000C9D
00000C9D 無(wú)條件跳轉(zhuǎn)  000002AD
00000CB3 壓入常量  null
00000CB5 無(wú)條件跳轉(zhuǎn)  -1

自動(dòng)反編譯結(jié)果

${'prefix'} = false;
if (${'prefix'} === false) {
    ${'prefix'} = ('strlen')(${'dir'}) + 1;
} else {
}
${'dh'} = ('opendir')(${'dir'});
while (true) {
    ${'file'} = ('readdir')(${'dh'});
    if (!(('readdir')(${'dh'}) === false)) {
    } else {
        return null;
    }
    if ((bool) (!(${'file'} == '.')) and (bool) (!(${'file'} == '..'))) {
        ${'readfile'} = ${'dir'} . '/' . ${'file'};
        if (('is_dir')(${'readfile'})) {
            ${'return'}[] = ${'root'} . '/' . ('substr')(${'readfile'}, ${'prefix'});
            ('cloudaddons_getsubdirs')(${'readfile'}, ${'root'}, ${'return'});
        } else {
        }
    } else {
    }
}

手動(dòng)反編譯結(jié)果

$prefix = false;
if ($prefix === false) {
    $prefix = strlen($dir) + 1;
}
$dh = opendir($dir);
while ($file = readdir($dh)) {
    if ($file != '.' && $file != '..') {
        $readfile = $dir . '/' . $file;
        if (is_dir($readfile)) {
            $return[] = $root . '/' . substr($readfile, $prefix);
            cloudaddons_getsubdirs($readfile, $root, $return);
        }
    }
}
return null;

可以看出來(lái),還是有一定差距的,某些問(wèn)題還是出在循環(huán)語(yǔ)句上。

變量引用追蹤

一個(gè)變量在被引用的時(shí)候是可以被賦值的,解除引用之后只能在賦值號(hào)右邊,是只讀的,不能更改原來(lái)的變量,也不能作為引用參數(shù)傳給函數(shù)。

變量引用計(jì)數(shù)

000002AD 壓入常量  null
000002AF 壓入字符串  readdir
000002BB 壓入字符串  dh
000002DB 引用
000002DD 調(diào)用函數(shù)  1
00000313 出棧  1
00000315 出棧  1
00000317 解除引用
00000319 壓入字符串  file
00000322 引用
00000324 無(wú)條件跳轉(zhuǎn)  00000340
00000340 賦值  0  1
00000364 解除引用
00000366 出棧  1
00000368 壓入常量  false
00000383 完全相同
000003BA 出棧  1
000003BC 取非
000003DF 條件跳轉(zhuǎn)  0000041F

這段代碼,正常來(lái)說(shuō),反編譯結(jié)果會(huì)是

$file = readdir($dh);
if (!(readdir($dh) === false)) {

但實(shí)際上,應(yīng)該是

if (!(($file = readdir($dh)) === false)) {

這個(gè)虛擬機(jī)在棧中出現(xiàn)逆序賦值是很奇怪的,虛擬機(jī)代碼是 $stack[$esp] = $stack[$esp - 1]; 用下層棧的內(nèi)容改寫(xiě)上層棧,這個(gè)不符合先入先出原則。

盡管這個(gè)寫(xiě)法很別扭,但是既然別人已經(jīng)做出來(lái)了,我們就要想辦法彌補(bǔ)。我采用的方法是“引用計(jì)數(shù)”,這是一種垃圾回收的方式,

我們?cè)谧詈笠淮芜@個(gè)變量從棧中消失的時(shí)候,把表達(dá)式從棧中移動(dòng)到 AST 中并轉(zhuǎn)換為語(yǔ)句。

代碼簡(jiǎn)化

邏輯運(yùn)算簡(jiǎn)化

(bool) ((bool) $_GET['aid'] or (bool) $_G['tid']) or (bool) (CURSCRIPT == 'admin')

化簡(jiǎn)為

$_GET['aid'] || $_G['tid'] || CURSCRIPT == 'admin'

非運(yùn)算簡(jiǎn)化

!($file == '.')

化簡(jiǎn)為

$file != '.'

While、Foreach語(yǔ)句簡(jiǎn)化

while (true) {
    if (!(($file = readdir($dh)) === false)) {
        if ((bool) (!($file == '.')) and (bool) (!($file == '..'))) {
            $readfile = $dir . '/' . $file;
            if (is_dir($readfile)) {
                $return[] = $root . '/' . substr($readfile, $prefix);
                cloudaddons_getsubdirs($readfile, $root, $return);
            }
        }
    } else {
        break;
    }
}

化簡(jiǎn)為

while ($file = readdir($dh)) {
    if ($file != '.' && $file != '..') {
        $readfile = $dir . '/' . $file;
        if (is_dir($readfile)) {
            $return[] = $root . '/' . substr($readfile, $prefix);
            cloudaddons_getsubdirs($readfile, $root, $return);
        }
    }
}

ElseIf 簡(jiǎn)化

if ($lx == 1) {
    $where = '&queryType=0&sortType=5';
} else {
    if ($lx == 2) {
        $where = '&sortType=9&shopTag=';
    } else {
        if ($lx == 3) {
            $where = '&sortType=4&shopTag=';
        } else {
            if ($lx == 4) {
                $where = '&dpyhq=1&shopTag=dpyhq';
            }
        }
    }
}

化簡(jiǎn)為

if ($lx == 1) {
    $where = '&queryType=0&sortType=5';
} elseif ($lx == 2) {
    $where = '&sortType=9&shopTag=';
} elseif ($lx == 3) {
    $where = '&sortType=4&shopTag=';
} elseif ($lx == 4) {
    $where = '&dpyhq=1&shopTag=dpyhq';
}

全自動(dòng)解析

  1. 先格式化代碼,把指令數(shù)據(jù)提取出來(lái)。
  2. 便利格式化之后的代碼,匹配虛擬機(jī)的代碼,找出虛擬機(jī)的棧、棧指針、指令指針等變量的名稱。
  3. 根據(jù)剛才找出的虛擬機(jī)變量,以及找到的指令數(shù)據(jù)反匯編并分塊
  4. 反編譯這部分指令。
  5. 代碼簡(jiǎn)化。
  6. 把虛擬機(jī)部分挖掉,換上反編譯之后的指令。

未完待續(xù)

這里的原理暫時(shí)還沒(méi)有講完

之后可能會(huì)做一個(gè)在線解析

程序代碼有興趣的可以在 GitHub 上自行搜索 mfenc-decompiler

反編譯代碼簡(jiǎn)介

目前不保證反編譯結(jié)果的正確性,僅供參考。

反匯編和結(jié)構(gòu)化之后的匯編指令應(yīng)該沒(méi)什么問(wèn)題。

用法

use Ganlv\MfencDecompiler\AutoDecompiler;
use Ganlv\MfencDecompiler\Helper;

require __DIR__ . '/../vendor/autoload.php';

file_put_contents(
    $output_file,
    Helper::prettyPrintFile(
        AutoDecompiler::autoDecompileAst(
            Helper::parseCode(
                file_get_contents($input_file)
            )
        )
    )
);

源代碼文件

DfsDisassembler.php  主反匯編器(DFS算法)
Disassembler1.php    一級(jí)指令反匯編器
Disassembler2.php    二級(jí)指令反匯編器
instructions.php     二級(jí)指令匹配列表

GraphViewer.php                       反匯編指令列表->有向圖轉(zhuǎn)換器
DirectedGraph.php                     有向圖類
DirectedGraphSimplifier.php           用于簡(jiǎn)化有向圖的抽象類
DirectedGraphSimpleSimplifier.php     簡(jiǎn)單地合并1進(jìn)1出和沒(méi)有指令的節(jié)點(diǎn)
DirectedGraphStructureSimplifier.php  分析流程結(jié)構(gòu)生成if、loop、break等語(yǔ)句

BaseDecompiler.php  基礎(chǔ)反編譯器
Decompiler.php      反編譯指令
Beautifier.php      反編譯后代碼美化

VmDecompiler.php    自動(dòng)將從ast中找到VM,并對(duì)其進(jìn)行反編譯的類
AutoDecompiler.php  全自動(dòng)反匯編器

Helper.php                       助手函數(shù)
Formatter.php                    測(cè)試過(guò)程中用于把亂碼變量名替換成英文
instructions_display_format.php  指令翻譯

部分結(jié)果展示

keke_xzhseo.class.php

123.txt


comiis_admin.inc.php

附件

 examples.zip

附件中不包含反編譯器!不包含反編譯器!需要代碼自行到 GitHub 搜索

包含:

  1. 我自己找的樣本 keke_xzhseo.class.php 及反編譯結(jié)果(Discuz!插件)
  2. 來(lái)自 某PHP加密文件調(diào)試解密過(guò)程 中 @索馬里的海賊 的回帖 中的樣本 123.txt 及反編譯之后的結(jié)果(微擎應(yīng)用)
  3. @jane35622 的帖子 【原創(chuàng)】PHP 魔方一代加密 逆向調(diào)試過(guò)程筆記外加討論 中的樣本 comiis_admin.inc.php 及反編譯之后的結(jié)果(Discuz!插件)

05.jpg (55.6 KB, 下載次數(shù): 0)

05.jpg
04.jpg
 


熱門(mén)文章
  • 機(jī)械革命S1 PRO-02 開(kāi)機(jī)不顯示 黑...
  • 聯(lián)想ThinkPad NM-C641上電掉電點(diǎn)不...
  • 三星一體激光打印機(jī)SCX-4521F維修...
  • 通過(guò)串口命令查看EMMC擦寫(xiě)次數(shù)和判...
  • IIS 8 開(kāi)啟 GZIP壓縮來(lái)減少網(wǎng)絡(luò)請(qǐng)求...
  • 索尼kd-49x7500e背光一半暗且閃爍 ...
  • 樓宇對(duì)講門(mén)禁讀卡異常維修,讀卡芯...
  • 新款海信電視機(jī)始終停留在開(kāi)機(jī)界面...
  • 常見(jiàn)打印機(jī)清零步驟
  • 安裝驅(qū)動(dòng)時(shí)提示不包含數(shù)字簽名的解...
  • 共享打印機(jī)需要密碼的解決方法
  • 圖解Windows 7系統(tǒng)快速共享打印機(jī)的...
  • 錦州廣廈電腦上門(mén)維修

    報(bào)修電話:13840665804  QQ:174984393 (聯(lián)系人:毛先生)   
    E-Mail:174984393@qq.com
    維修中心地址:錦州廣廈電腦城
    ICP備案/許可證號(hào):遼ICP備2023002984號(hào)-1
    上門(mén)服務(wù)區(qū)域: 遼寧錦州市區(qū)
    主要業(yè)務(wù): 修電腦,電腦修理,電腦維護(hù),上門(mén)維修電腦,黑屏藍(lán)屏死機(jī)故障排除,無(wú)線上網(wǎng)設(shè)置,IT服務(wù)外包,局域網(wǎng)組建,ADSL共享上網(wǎng),路由器設(shè)置,數(shù)據(jù)恢復(fù),密碼破解,光盤(pán)刻錄制作等服務(wù)

    技術(shù)支持:微軟等
    主站蜘蛛池模板: 亚洲av永久无码精品秋霞电影影院 | 久久无码一区二区三区少妇| 色欲狠狠躁天天躁无码中文字幕| 国产成人无码一区二区在线播放| 日韩网红少妇无码视频香港| 无码人妻精一区二区三区| 国产精品无码无片在线观看3D| 久久伊人中文无码| 麻豆国产精品无码视频| 精品一区二区三区无码免费直播 | 日韩AV无码精品人妻系列| 亚洲AV无码乱码精品国产| 国产精品无码亚洲一区二区三区| 中文一国产一无码一日韩| 免费无码国产V片在线观看| 亚洲精品中文字幕无码AV| 中文字幕乱偷无码AV先锋| 成人免费无码精品国产电影| 精品无码成人片一区二区98| 久久精品九九热无码免贵| 国产精品无码成人午夜电影| 少妇无码太爽了不卡在线观看 | 久久久久av无码免费网| 亚洲中文字幕久久精品无码APP| 日韩成人无码一区二区三区| 精品久久久久久无码不卡| 久久AV高清无码| 无码专区AAAAAA免费视频| 特级做A爰片毛片免费看无码| 国产激情无码一区二区| 亚洲中文字幕无码久久精品1 | 中文无码精品A∨在线观看不卡| 国产在线无码不卡影视影院| 人妻丰满熟妇AV无码区HD| 丰满日韩放荡少妇无码视频| 日韩va中文字幕无码电影| 亚洲精品无码高潮喷水在线| 亚洲AV无码一区二区乱孑伦AS| 国产精品无码一区二区三级| 无码午夜人妻一区二区三区不卡视频 | 无码夫の前で人妻を犯す中字|