吾爱破解 - LCG - LSG |安卓破解|病毒分析|破解软件|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

领取今日签到奖励
查看: 17874|回复: 229

[调试逆向] PHP解密:魔方加密2代 全自动反编译器

    [复制链接]
发表于 2018-7-23 19:44 | 显示全部楼层
本帖最后由 Ganlv 于 2018-11-13 02:21 编辑

样本

一个 DiscuzX 插件 keke_xzhseo.class.php

关于魔方加密

我没自己用过魔方加密,不知道这种到底是一代还是二代,如果就加密强度来说,这个帖子的样本不如上一个帖子的强度高。这个样本的代码是可以完全复原的,而 上一个帖子 的样本不能完全复原,最后只能推导出 $v0 $v1 这类局部变量名,所有的函数也都无法还原参数名。

如果看网上的说明,本文的样本应该是二代加密,就我破解过程中的理解,这种加密方式较为简单,使用者不需要改动什么源代码,但缺点就是相对地“容易”被破解,而且文件体积很大,大量的 eval 会导致运行效率极低。

过程

代码格式化

参考之前的帖子PHP加密中的“VMProtect”——魔方加密反编译分析过程

大致浏览一下文件内容,可以看到 KIVIUQ VIRTUAL MACHINE ERROR : Access violation at address (KIVIUQ虚拟机错误:在xxx地址处读取错误)这个东西,可以确定是魔方加密了。

魔方加密是一种基于虚拟机的加密,他将原本函数调用、运算符等操作,拆分成参数压栈、执行指令、结果出栈这种步骤,所以“解密”是不可能,只能通过反编译的方式尝试还原代码。

01.png

分析虚拟机

更牛逼的代码格式化

  1. 为了方便阅读,我把乱码变量名替换成 $v0 这类的可读变量名了。
  2. 把通过 . 连接的字符串合成了一整个,然后把特别长的字符串输出到一个单独的文件 large_string_data.php,方便以后使用。
  3. 由于后面破解过程中发现替换变量名对虚拟机有影响,所以我把 乱码变量名 => 可读变量名 输出到一个单独的文件 variables_map.php,方便以后使用。

2018 年 03 月 01 日 nikic/php-parser 为了发展 PHP 7 更新了 4.0 版本,所以 format.php 的部分代码与前面的帖子相比有所更改。有兴趣的同学可以研究我的代码是怎么写的,没兴趣的就看看就好了。

$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);
}

虚拟机的运行流程

大致浏览一下这段代码,通过分析可以知道,各个变量的含义,虚拟机的运行流程。

变量名 含义
$v0 虚拟机环境
$v1
$v2 (未知,后文分析可知是报错等级栈)
$v3 栈指针
$v4 (未知,后文分析可知是报错等级栈指针)
$v5 内存指针
$v6 指令 + 指令集 + 数据(可以称之为内存,类似 .text 代码段)
$v7 异或解码之后的数值,代表语句的字符串长度
$v8 临时变量(一个寄存器),用于异或解码,用于存储解密之后的指令,用于 try-catch 的异常变量
指令名称 含义
1 取 2 字节以内的字符串作为二级指令执行
2 取 4 字节以内的字符串作为二级指令执行
3 取 10 字节以内的字符串作为二级指令执行
a 出栈
b 栈解除引用
c 压栈,压入 null
d 取数组元素或字符串中的字符
e 取特殊变量,超全局变量和 this 特殊变量,或其他栈顶变量名的变量
fd 取 100 字节以内的字符串压到栈顶
fq 取 10^4 字节以内的字符串压到栈顶
fx 取 10^10 字节以内的字符串压到栈顶
主循环 eip 对应的操作
>= 0 继续虚拟机主循环,运行指令
-1 结束虚拟机主循环
-2 eval($v2[$v4 - 1]); $v5 = $v2[$v4]; $v4 -= 2;
其他 虚拟机出错

运行结束后,从虚拟机环境 $v0 中依次弹出 $v5 $v4 $v3 $v2 $v1

这里提到一个词——“二级指令”,这个词是我随便起的,就是上述的十几个指令是在虚拟机运行环境的代码中直接显式给出的,所以称为“一级指令”,而二级指令就是指,解析出一个字符串然后再调用 eval 来执行的指令。

分析完虚拟机的逻辑之后,我们发现,不能像上一篇文章中的方法,直接分析每一条虚拟机指令,反编译出代码。我们必须跟随虚拟机的运行,然后把每一条二级指令也还原出来,然后才能分析。

跟随虚拟机运行一下

我们可以改造一下这个虚拟机,在每一条指令执行时,输出他们做了什么事,以及他们的指令地址。

注意,我们需要用到 xdebug 来调试 php 程序,同时,最好选择一个 IDE 来辅助调试(我用的是 PHPStorm)。

代码在执行过程中,我们需要利用调试器,视情况调整一下环境:

  • 如果虚拟机想要使用某些不存在的常量,我们可以提前定义常量,防止程序运行错误。
  • 如果虚拟机想要使用某些不存在的变量,我们可以提前给他们赋值,防止程序运行错误。
  • 如果虚拟机想要运行某个不存在的函数,我们可以直接跳过。
  • 如果虚拟机想要进行条件跳转,我们可以改变跳转或不跳转。

改造虚拟机的过程

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) { 这句下断点,每次执行到这里,如果想跳过本条语句的话,就 $is_eval = false;

02.gif

可以大致感觉到执行一条语句的大致过程是:

  1. 压栈,压入 null
  2. 取函数名
  3. 取变量(特殊变量/字符串),作为第一个参数
  4. 继续取变量,作为第二个参数
  5. 取二级指令并执行(可能是调用函数、连接字符串等等)
  6. 出栈
  7. 使用引用+赋值+解除引用的方式,把结果传递到某个变量

反汇编

基本的反汇编

反汇编,就是脱离运行环境,分析机器指令。照着虚拟机的逻辑改就行了。

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

内存越界是因为我是按顺序反汇编一级指令,然后编码解密二级指令,没有实际运行二级指令,所以不知道程序什么时候终止(就是还不知道 $v5=-1; 是什么)。其实就是代码没了,强行终止了。不用管这个。

上面这段指令,对应的代码其实就是

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

增强的反汇编

只是像这样简单地反汇编还不行,我们必须把每一条二级指令的代码都想办法拆分成指令+数据的形式,然后才能供反编译使用。

这里列举一些简单的二级指令(指令集可能不止这些)。

// 取数据
$stack[$esp] = ???;

// 条件跳转
if ($stack[$esp]) $eip = 0x????;

// 无条件跳转
$eip = 0x????;

// 调用函数
$stack[$esp - 1] = $stack[$esp]();
$stack[$esp - 2] = $stack[$esp - 1]($stack[$esp]);
$stack[$esp - 3] = $stack[$esp - 2]($stack[$esp - 1], $stack[$esp]);

// 比较大小、算数运算、字符串链接等等

由于指令较多(共有数十种),具体指令集请参考成品代码。

结果像下面这样

00000000 - 0000000E  global  $_G
0000000F - 00000022  global  $article
00000023 - 00000024  压入null
00000025 - 00000030  压入字符串  defined
00000031 - 0000004C  压入字符串  CLOUDADDONS_WEBSITE_URL
0000004D - 00000082  调用函数  1
00000083 - 00000084  出栈
00000085 - 00000086  出栈
00000087 - 00000088  解除引用
00000089 - 000000AB  取非
000000AC - 000000D3  条件跳转  000000EC
000000D4 - 000000D5  出栈
000000D6 - 000000EB  无条件跳转  000001E7
000001E7 - 000001E8  压入null
000001E9 - 00000207  压入常量  DISCUZ_ROOT
00000208 - 00000236  压入字符串  source/plugin/keke_xzhseo/identity.inc.php
00000237 - 0000026B  字符串连接
0000026C - 00000281  无条件跳转  00000288
00000288 - 00000289  出栈
0000028A - 000002B9  include   2
000002BA - 000002BB  出栈
000002BC - 000002D8  压入空数组
000002D9 - 000002E2  压入字符串  check
000002E3 - 000002E4  引用变量
000002E5 - 00000308  栈内赋值  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  取数组元素
00000368 - 00000369  出栈
0000036A - 0000036B  解除引用
0000036C - 000003A0  字符串连接
000003A1 - 000003A2  出栈
000003A3 - 000003B8  无条件跳转  000003BF
000003BF - 000003F4  调用函数  1
000003F5 - 000003F6  出栈
000003F7 - 0000040C  无条件跳转  00000413
00000413 - 00000414  出栈
00000415 - 00000416  解除引用
00000417 - 0000042D  压入数字  0
0000042E - 00000444  压入数字  7
00000445 - 0000049C  调用函数  3
0000049D - 0000049E  出栈
0000049F - 000004B4  无条件跳转  000004BB
000004BB - 000004BC  出栈
000004BD - 000004BE  出栈
000004BF - 000004C0  出栈
000004C1 - 000004D6  无条件跳转  000004DD
000004DD - 000004DE  解除引用
000004DF - 000004E8  压入字符串  uskey
000004E9 - 000004EA  引用变量
000004EB - 0000050E  栈内赋值  1
0000050F - 00000510  解除引用
00000511 - 00000512  出栈
00000513 - 00000514  出栈
00000515 - 00000516  压入null
00000517 - 00000524  压入字符串  loadcache
00000525 - 00000550  压入字符串  uskey
00000551 - 00000552  引用变量
00000553 - 00000588  调用函数  1
00000589 - 0000059E  无条件跳转  000005A5
000005A5 - 000005A6  出栈
000005A7 - 000005A8  出栈
000005A9 - 000005AA  解除引用
000005AB - 000005AC  出栈
000005AD - 000005B3  压入字符串  _G
000005B4 - 000005B5  引用变量
000005B6 - 000005E1  压入字符串  cache
000005E2 - 000005E3  取数组元素
000005E4 - 000005E5  出栈
000005E6 - 000005FB  无条件跳转  00000602
00000602 - 0000060B  压入字符串  uskey

你可以看到这里多出了许多指令,比如 global, 调用函数, 取非, 条件跳转, 无条件跳转,这些指令就是解析之后的二级指令。

现在我们反汇编之后的结果是“线性的”了,可以被反编译了。

DFS反汇编

你或许以为上面得到的反汇编指令是很容易的,其实不是这样的,这些指令中有一些“花指令”,就像下面这样。

0000026C - 00000281  无条件跳转  00000288
00000288 - 00000289  出栈

这里的 00000282 - 00000288 之间的指令没法执行,由于指令长短不一样,这段花指令打乱了原本解析过程,所以必须要用较高级的方法。

  1. 如果遇到无条件跳转,直接跳转。
  2. 如果遇到条件跳转指令,分成两个分支来解析。遇到分支则继续分下去(递归),直到解析的指令之前已经解析过了、或跳转到 -1(跳转到 -1 就类似 return 语句,代表结束虚拟机),直到已经解析完所有指令。
  3. 最后按指令在虚拟机中出现的顺序排序即可。

简而言之,这就是一个深度优先搜索(DFS)。

通过这一步骤,我们真正把所有有用的指令提取出来了,没用的指令直接抛弃了,已经真正脱离了虚拟机了,我们得到的可以称之为更为通用的字节码了。

指令分块(链表到图)

顺序的指令都很好解析,也很好反编译,分支结构是比较麻烦的,最麻烦的就是循环结构。为了方便之后分析程序流程,这里可以先把“线性”的反汇编程序转换为无序的“向量图”。

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

  1. 在所有与跳转有关的位置(跳出和跳入)将代码分块,保证每块中最多 1 个跳转,且跳转指令必须是最后一条。
  2. 遍历每一个分块,分析每一块结束时跳转的去向,构造成一个图。
  3. 跳转到 -1 的块将最后跳转到 -1 的指令改成 return 指令。
  4. 对图进行一些拓扑变换,简化图,例如把连续几个直线串起来的块合成一个等等。(这一步不是必须的,因为后面的进行流程分析,自然会把无分支的指令连成一整块的)

如果用流程图可视化地表示一下,大概就是这样的。

03.gif

分块之后由于没有了块内跳转,所以我们不再需要每一条指令的地址了,我们只需要给每个分块一个独立的 id 即可。同时也没有了“跳转”这种说法了,无条件跳转变成了连续的指令了,条件跳转变成了分支(或者循环)了。

用过 IDA 或 x64dbg 的同学可能对这种图比较熟悉了。

反编译

分析流程

前面说了,反编译线性的指令很简单,条件分支和循环比较复杂,复杂就因为他们的流程有分支、有层次结构,不能使用循环来解决,需要使用递归才比较方便。

在我尝试反编译的时候,个人感觉各种指令的反编译,最简单的就是线性代码了,其次就是单分支结构 if,然后就是循环 whilefor 等,最麻烦的就是 breakcontinue 了。

我采用的方案如下:

  1. 线性代码一直运行。
  2. 遇到条件分支采用 DFS 分析,先走 yes 再走 no。
  3. 遇到循环则记录当前环的所有顶点。然后退回到最后一个条件分支,如果刚才是 yes 分支,则继续尝试走 no 分支,如果已经是 no 分支了,则开始分析这个“条件分支构成的循环”。
  4. 分析“条件分支构成的循环”的方法:将“条件分支构成的循环”转换为“无条件循环” +  if-break 语句。
  5. 遇到终点则正常回退到最后的条件分支,执行另一个分支或执行分析。
  6. 如果没有构成循环,分析普通条件分支的方法:将条件分支转换为 if 语句,yesno 分别构成 stmtselse 块。
  7. 假设不存在循环交叉(即假设变异前没有极其变态的 goto 语句)。

  8. 如果遇到无条件跳转,直接跳转。
  9. 如果遇到条件跳转指令,保存当前反汇编器的指针位置,以及一些其他的状态信息,然后分成两个分支来解析。两个分支顺序解析,直到遇到另一个分支或者虚拟机退出指令,交换分支的控制权,直到两个分支合成一个分支时结束,继续按一个分支解析。此条语句记为 if
  10. 同时建立一个已经分析过的地址列表,如果跳往分析过的,则记录为 while

说了半天就是使用 BFS(广度优先搜索)分析语法分支

最开始,反汇编、指令分块与分析流程这几步是同时进行的,直接采用 BFS 来反汇编、分块、构造 ifwhile 结构。后来感觉代码越写越复杂,分析了一下每个步骤可以独立开来,就使用 DFS 反汇编(因为 DFS 代码比 BFS 简单),然后简单地根据跳转分块并优化,最后使用 BFS 分析流程。这样感觉的确清晰了不少。

举个例子

00000001 条件跳转 00000004
00000002 指令块1
00000003 无条件跳转 00000006
00000004 指令块2
00000005 无条件跳转 00000008
00000006 指令块3
00000007 无条件跳转 00000009
00000008 指令块4
00000009 指令块5

我们解析的结果应该是

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

再举个例子

00000001 条件跳转 00000004
00000002 指令块1
00000003 无条件跳转 00000001
00000004 指令块2

解析得到

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

经过我们不懈的努力,上文的第一段反汇编程序(就是这段 if (!defined('IN_DISCUZ')) { exit('Access Denied'); }),分块结果如下

压入null
压入字符串  defined
压入字符串  IN_DISCUZ
调用函数  1
出栈
出栈
解除引用
取非
如果
    出栈
    压入字符串  Access Denied
    exit
    出栈
否则
    出栈
压入null

反编译

普通的反编译

普通的反编译,原理很简单,指令对栈做了什么操作,我们也就同样根据他的操作构造抽象语法树(AST),构建 AST 正好是编译的逆过程。

由于魔方1代加密是一种仅基于栈的指令集,没有寄存器的存在,反编译算法会变得简单。

比如刚才那段指令,构建 AST 用的栈的内容变化就是这样的

  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 {}

这样就还原出来了这段指令对应的源码。

表达式和语句

实践中,你可能会发现,这种方法看上去很简单,但是也是存在一些问题的。比如,如何区分表达式 Expression 和语句 Statement,有些表达式会影响运行环境,而他们运行完不会返回运行结果给栈(或者运行结果被抛弃),如果这时下一条语句是“出栈”的话,将在 AST 中出现一个单独的表达式。在 PHP 中表达式是不能充当语句的,他后面必须有一个分号才可以构成一个语句,我们必须得想想方法。

最后我想到一个好办法,把所有已经被使用过的表达式添加一个 used 属性,每当一个表达式被丢弃的时候(出栈或者解除引用都会使表达式从栈中被移除),如果这个表达式没有被使用过,则使用这个表达式构建一条语句,放到 AST 中。如果出栈的本来就是语句,那就直接放到 AST 中就行了,不需要其他处理。

if 语句、逻辑短路、三元运算符

If statement, Logical Short-Circuit, Ternary 这三个东西都可以通过条件跳转来表示,只不过三个东西对栈的操作不同

if 语句会在判断之后就直接抛弃判断条件,stmts 块和 else 块都会紧跟一个出栈,最终的栈会比执行之前少一层(把判断条件出栈了)。

if ($cond)
    {stmts}
else
    {else}
压入 $cond
如果
    出栈
    {stmts}
否则
    出栈
    {else}

逻辑短路,通常是“逻辑或”短路,stmts 块为空,else 块都会紧跟一个出栈,但随后还会再压入一个值,最终的栈和执行之前平衡。

如果和上面的情况相反,else 块为空,则是“逻辑与”短路。

$a or $b
压入 $a
如果
否则
    出栈
    压入 $b

三元运算符算是前面两个的结合体,stmts 块和 else 块都会紧跟一个出栈,两个块随后都还会再压入一个值,最终的栈和执行之前平衡。

$cond ? $a : $b
压入 $cond
如果
    出栈
    压入 $a
否则
    出栈
    压入 $b

我们可以通过判断 stmts 块和 else 块来区分三者,也可以通过最终的栈和之前的栈进行对比来区分。(我选择了第二种,容错性高,而且出现意外错误可以抛出异常)

循环

0000022E - 0000023B 压入字符串  checkdirs
0000023C - 0000023D 引用
0000023E - 0000023F 解除引用
00000240 - 00000259 reset
0000025A - 0000025F 压入字符串  k
00000260 - 00000261 引用
00000262 - 00000269 压入字符串  dir
0000026A - 0000026B 引用
0000026C - 00000306 调用函数  0
00000307 - 0000032E 条件跳转  00000347
0000032F - 00000330 出栈
00000331 - 00000346 无条件跳转  00000DA3
00000347 - 00000348 出栈
中间省去一部分指令
00000B3E - 00000B4B 压入字符串  writeable
00000B4C - 00000B4D 引用
00000B4E - 00000B4F 解除引用
00000B50 - 00000B72 boolean_not
00000B73 - 00000B9A 转换为bool
00000B9B - 00000BC2 条件跳转  00000BF5
00000BC3 - 00000BD8 无条件跳转  00000C2B
00000BF5 - 00000BF6 出栈
00000BF7 - 00000BFE 压入字符串  dir
00000BFF - 00000C00 引用
00000C01 - 00000C02 解除引用
00000C03 - 00000C2A 转换为bool
00000C2B - 00000C52 条件跳转  00000C87
00000C53 - 00000C54 出栈
00000C55 - 00000C6A 无条件跳转  00000C71
00000C71 - 00000C86 无条件跳转  00000D72
00000C87 - 00000C88 出栈
00000C89 - 00000C90 压入字符串  dir
00000C91 - 00000C92 引用
00000C93 - 00000CA8 无条件跳转  00000CAF
00000CAF - 00000CB0 解除引用
00000CB1 - 00000CBB 压入字符串  return
00000CBC - 00000CBD 引用
00000CBE - 00000D15 数组元素获取  0  
00000D16 - 00000D2B 无条件跳转  00000D32
00000D32 - 00000D55 赋值  0  1
00000D56 - 00000D57 解除引用
00000D58 - 00000D59 出栈
00000D5A - 00000D5B 出栈
00000D5C - 00000D71 无条件跳转  00000D72
00000D72 - 00000D8C next
00000D8D - 00000DA2 无条件跳转  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:

等价转换一下

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

继续分析所有指令

想要全自动解析整个文件,偷懒是不行的,必须得把每一种指令都匹配出来,然后再手动写好每一种指令的构造 AST 的代码。

自动反编译与手动修改之后的对照

汇编语言

00000000 压入常量  false
0000001B 压入字符串  prefix
00000026 引用
00000028 赋值  0  1
0000004C 解除引用
0000004E 出栈  1
00000050 出栈  1
00000052 压入字符串  prefix
0000005D 引用
0000005F 解除引用
00000061 压入常量  false
0000007C 完全相同
000000B3 出栈  1
000000B5 条件跳转  000000F5
000000DD 出栈  1
000000DF 无条件跳转  00000226
000000F5 出栈  1
000000F7 压入常量  null
000000F9 压入字符串  strlen
00000104 压入字符串  dir
0000010C 引用
0000010E 调用函数  1
00000144 出栈  1
00000146 出栈  1
00000148 解除引用
0000014A 压入数字  1
00000161 相加
00000196 无条件跳转  000001B2
000001B2 出栈  1
000001B4 压入字符串  prefix
000001E4 引用
000001E6 赋值  0  1
0000020A 解除引用
0000020C 出栈  1
0000020E 出栈  1
00000210 无条件跳转  00000226
00000226 压入常量  null
00000228 压入字符串  opendir
00000234 压入字符串  dir
0000023C 引用
0000023E 调用函数  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 调用函数  1
00000313 出栈  1
00000315 出栈  1
00000317 解除引用
00000319 压入字符串  file
00000322 引用
00000324 无条件跳转  00000340
00000340 赋值  0  1
00000364 解除引用
00000366 出栈  1
00000368 压入常量  false
00000383 完全相同
000003BA 出栈  1
000003BC 取非
000003DF 条件跳转  0000041F
00000407 出栈  1
00000409 无条件跳转  00000CB3
0000041F 出栈  1
00000421 压入字符串  file
0000042A 引用
0000042C 解除引用
0000042E 压入字符串  .
00000434 相等
0000046A 出栈  1
0000046C 取非
0000048F 转换为bool
000004B7 条件跳转  000004F5
000004DF 无条件跳转  000005C9
000004F5 出栈  1
000004F7 压入字符串  file
0000051F 引用
00000521 解除引用
00000523 压入字符串  ..
0000052A 相等
00000560 无条件跳转  0000057C
0000057C 出栈  1
0000057E 取非
000005A1 转换为bool
000005C9 条件跳转  00000609
000005F1 出栈  1
000005F3 无条件跳转  00000C9D
00000609 出栈  1
0000060B 压入字符串  dir
00000613 引用
00000615 解除引用
00000617 压入字符串  /
0000061D 无条件跳转  00000639
00000639 字符串链接
0000066E 出栈  1
00000670 压入字符串  file
00000679 引用
0000067B 解除引用
0000067D 字符串链接
000006B2 出栈  1
000006B4 无条件跳转  000006D0
000006D0 压入字符串  readfile
000006DD 引用
000006DF 赋值  0  1
00000703 解除引用
00000705 出栈  1
00000707 出栈  1
00000709 压入常量  null
0000070B 压入字符串  is_dir
00000716 压入字符串  readfile
00000723 引用
00000725 调用函数  1
0000075B 出栈  1
0000075D 出栈  1
00000779 解除引用
0000077B 条件跳转  000007BB
000007A3 出栈  1
000007A5 无条件跳转  00000C87
000007BB 出栈  1
000007BD 压入字符串  root
000007C6 引用
000007C8 解除引用
000007CA 压入字符串  /
000007E5 字符串链接
0000081A 出栈  1
0000081C 压入常量  null
0000081E 压入字符串  substr
00000829 压入字符串  readfile
00000836 引用
00000838 压入字符串  prefix
00000843 引用
00000845 调用函数  2
0000088C 无条件跳转  000008A8
000008A8 出栈  1
000008AA 出栈  1
000008AC 出栈  1
000008AE 解除引用
000008B0 字符串链接
000008E5 出栈  1
000008E7 压入字符串  return
000008F2 引用
00000AF3 数组元素获取  0  
00000B4B 赋值  0  1
00000B6F 解除引用
00000B71 出栈  1
00000B73 出栈  1
00000B75 压入常量  null
00000B77 无条件跳转  00000B93
00000B93 压入字符串  cloudaddons_getsubdirs
00000BAE 无条件跳转  00000BCA
00000BCA 压入字符串  readfile
00000BD7 引用
00000BD9 压入字符串  root
00000BE2 引用
00000BE4 压入字符串  return
00000BEF 引用
00000BF1 调用函数  3
00000C49 出栈  1
00000C4B 出栈  1
00000C4D 出栈  1
00000C4F 出栈  1
00000C51 解除引用
00000C53 出栈  1
00000C55 无条件跳转  00000C87
00000C87 无条件跳转  00000C9D
00000C9D 无条件跳转  000002AD
00000CB3 压入常量  null
00000CB5 无条件跳转  -1

自动反编译结果

${'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 {
    }
}

手动反编译结果

$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;

可以看出来,还是有一定差距的,某些问题还是出在循环语句上。

变量引用追踪

一个变量在被引用的时候是可以被赋值的,解除引用之后只能在赋值号右边,是只读的,不能更改原来的变量,也不能作为引用参数传给函数。

变量引用计数

000002AD 压入常量  null
000002AF 压入字符串  readdir
000002BB 压入字符串  dh
000002DB 引用
000002DD 调用函数  1
00000313 出栈  1
00000315 出栈  1
00000317 解除引用
00000319 压入字符串  file
00000322 引用
00000324 无条件跳转  00000340
00000340 赋值  0  1
00000364 解除引用
00000366 出栈  1
00000368 压入常量  false
00000383 完全相同
000003BA 出栈  1
000003BC 取非
000003DF 条件跳转  0000041F

这段代码,正常来说,反编译结果会是

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

但实际上,应该是

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

这个虚拟机在栈中出现逆序赋值是很奇怪的,虚拟机代码是 $stack[$esp] = $stack[$esp - 1]; 用下层栈的内容改写上层栈,这个不符合先入先出原则。尽管这个写法很别扭,但是既然别人已经做出来了,我们就要想办法弥补。我采用的方法是“引用计数”,这是一种垃圾回收的方式,我们在最后一次这个变量从栈中消失的时候,把表达式从栈中移动到 AST 中并转换为语句。

代码简化

逻辑运算简化

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

化简为

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

非运算简化

!($file == '.')

化简为

$file != '.'

While、Foreach语句简化

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;
    }
}

化简为

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 简化

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';
            }
        }
    }
}

化简为

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';
}

全自动解析

  1. 先格式化代码,把指令数据提取出来。
  2. 便利格式化之后的代码,匹配虚拟机的代码,找出虚拟机的栈、栈指针、指令指针等变量的名称。
  3. 根据刚才找出的虚拟机变量,以及找到的指令数据反汇编并分块
  4. 反编译这部分指令。
  5. 代码简化。
  6. 把虚拟机部分挖掉,换上反编译之后的指令。

未完待续

这里的原理暂时还没有讲完

之后可能会做一个在线解析

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

反编译代码简介

目前不保证反编译结果的正确性,仅供参考。

反汇编和结构化之后的汇编指令应该没什么问题。

用法

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)
            )
        )
    )
);

源代码文件

10.jpg

DfsDisassembler.php  主反汇编器(DFS算法)
Disassembler1.php    一级指令反汇编器
Disassembler2.php    二级指令反汇编器
instructions.php     二级指令匹配列表

GraphViewer.php                       反汇编指令列表->有向图转换器
DirectedGraph.php                     有向图类
DirectedGraphSimplifier.php           用于简化有向图的抽象类
DirectedGraphSimpleSimplifier.php     简单地合并1进1出和没有指令的节点
DirectedGraphStructureSimplifier.php  分析流程结构生成if、loop、break等语句

BaseDecompiler.php  基础反编译器
Decompiler.php      反编译指令
Beautifier.php      反编译后代码美化

VmDecompiler.php    自动将从ast中找到VM,并对其进行反编译的类
AutoDecompiler.php  全自动反汇编器

Helper.php                       助手函数
Formatter.php                    测试过程中用于把乱码变量名替换成英文
instructions_display_format.php  指令翻译

部分结果展示

keke_xzhseo.class.php

06.jpg

123.txt

07.jpg
08.jpg

comiis_admin.inc.php

09.jpg

附件

examples.zip (502.26 KB, 下载次数: 51)

05.jpg
04.jpg

免费评分

参与人数 72吾爱币 +74 热心值 +68 收起 理由
laohucai + 1 + 1 谢谢@Thanks!
guogxd + 1 + 1 我很赞同!
Coxxs + 3 + 1 很强..
阿锋 + 1 + 1 大侠在线解密好了吗
zwslmk + 1 + 1 用心讨论,共获提升!
魔术师_ + 1 + 1 谢谢@Thanks!
流浪的猫眼石 + 2 + 1 虽然看不懂但好厉害的样子
青-枫 + 1 我很赞同!
liu741741 + 1 + 1 谢谢@Thanks!
testqkl + 1 + 1 相当的牛X
善良的果仁 + 1 + 1 我很赞同!
tchivs + 2 + 1 我很赞同!
流浪天涯 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
圣血轩辕 + 1 + 1 写的很清楚,我很感动,就是看不懂0_0
何故 + 2 + 1 用心讨论,共获提升!
如实知见 + 1 + 1 谢谢@Thanks!
yzy9943 + 1 哇 真滴流批
北漂小子 + 1 + 1 吾爱破解论坛特聘技术专家。。。。。。
Tartarus-G + 1 + 1 用心讨论,共获提升!
ylh465283492 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
ifrea + 1 就服你
蛋总 + 1 + 1 真的很强!
lxcjy + 1 + 1 谢谢@Thanks!
风不语 + 1 + 1 非常不错,楼主很强大
湖南津彩 + 1 + 1 我很赞同!
libingzhi + 1 + 1 我很赞同!
漫步时光 + 1 + 1 谢谢@Thanks!
baichihgl + 1 + 1 厉害
咸鱼而已 + 1 + 1 用心讨论,共获提升!
zq1992zq2008 + 1 不明觉厉啊
yiqingjs + 1 楼主犯了一个错误,或许会导致反编译后陷入作者的圈套
JMFANS + 1 用心讨论,共获提升!
tinglie + 1 + 1 谢谢@Thanks!
cs007 + 1 + 1 谢谢@Thanks!
samwang919 + 1 我很赞同!
willowsun + 1 + 1 我很赞同!
jcyk1n9 + 1 + 1 用心讨论,共获提升!
gqdsc + 1 + 1 大神厉害
poisonbcat + 1 + 1 谢谢@Thanks!
okij12589 + 1 + 1 用心讨论,共获提升!
Crazypolice + 1 + 1 谢谢@Thanks!
黑的思想 + 2 + 1 神仙
Linkasyan + 1 + 1 用心讨论,共获提升!
max0960 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
wapj3425 + 1 + 1 热心回复!
zy1234 + 1 + 1 谢谢@Thanks!
Sibe + 1 + 1 我很赞同!
猫爷 + 1 用心讨论,共获提升!
塞北的雪 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
missdiog + 1 + 1 我很赞同!
wdh + 1 + 1 我很赞同!
evea + 1 + 1 我很赞同!
plwt + 1 + 1 我很赞同!
wangyeyu2015 + 1 + 1 热心回复!
xiaoxi2011 + 1 + 1 谢谢@Thanks!
smile1110 + 1 我很赞同!
loteer + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
涛之雨 + 1 虽然看不懂,但不影响小白对大神的崇拜
珍惜幸福涙 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
凋零灬枫叶 + 1 + 1 谢谢@Thanks!
高冷的狼人 + 1 + 1 热心回复!
wmsuper + 2 + 1 谢谢@Thanks!
索马里的海贼 + 3 + 1 神仙你好.....
Ravey + 1 + 1 谢谢@Thanks!
当合 + 1 + 1 热心回复!
很快再相见123 + 1 + 1 感谢楼主无私分享
Six_Feet_Under + 1 + 1 谢谢@Thanks!
redapple2015 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
tankc07 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
zhangaiping1 + 1 + 1 用心讨论,共获提升!
hz52 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
a952135763 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-30 18:35 | 显示全部楼层
        楼主犯了一个错误,或许会导致反编译后陷入作者的圈套

点评

给你置顶,期待您的解答。作者有什么样的圈套,您的想法是什么,还有修改之后的代码,欢迎分析讨论,希望您能贡献至少一篇精华帖吧。  发表于 2018-7-31 01:38

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-30 21:11 | 显示全部楼层
本帖最后由 A00 于 2018-7-30 21:17 编辑

真是好解密方法,现在sg11也是很美的,楼主有机会分享一下?

点评

本文只是提供一种虚拟机指令加密的解密死思路。希望您能成为内容的创作者,而不仅仅只是指使他人去做你想做的事。既然你知道sg11是很美的,说明你还是研究过的,为什么不发个帖子解释一下呢,说不定还能搞个精华。  发表于 2018-7-31 01:43

免费评分

参与人数 1吾爱币 -1 收起 理由
Ganlv -1 用心讨论,共获提升!

查看全部评分

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-8-5 10:28 | 显示全部楼层
大佬考虑看下红盟PHP加密吗?@ Ganlv

jm.zip

4.5 KB, 下载次数: 11, 下载积分: 吾爱币 -1 CB

点评

不知道这个对你有没有帮助 https://www.52pojie.cn/thread-695189-1-1.html  发表于 2018-8-22 21:57

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-8-16 07:46 | 显示全部楼层
本帖最后由 热锅上的蚂蚁 于 2018-8-16 08:09 编辑
热锅上的蚂蚁 发表于 2018-8-15 08:41
请楼主研究一下EX4格式文件的反编译。

请问我求破的哪个文件?我只是请大牛们研究一下这种格式文件的反编译方法。因为我不知道该从哪方面下手,所以请大牛们,研究,做个教程。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-12-1 00:55 | 显示全部楼层
链接: https://pan.baidu.com/s/1NVyOv0fDal5l2gwewaumcQ 提取码: kxwx 想问下大神,这种的怎么提取时间,谢谢了。是有关lua追过来的,谢谢了,看了你所有的关于lua解密的教程都没弄懂,求提点

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

 楼主| 发表于 2018-8-25 18:46 | 显示全部楼层
xhkj5com 发表于 2018-8-25 17:55
魔方二代解密时候php7.2提示Fatal error: Allowed memory size of 2097152 bytes exhausted (tried to allo ...

这只是一个参考,我不保证我的代码不会出现死循环,我对这个东西也没什么兴趣了,我也不靠这个东西赚钱,所以如果你有能力的话,可以提交pull request,如果等着我去完善,至少得等我半年之后有较多的空闲时间。就这样,我帮不了什么忙。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 15:29 | 显示全部楼层
大佬啊,,,,

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 15:29 | 显示全部楼层
非常感谢,收藏以后会用到~

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 15:43 | 显示全部楼层
这……真的是神仙级别的操作了吧【笑】
VM真是一个史诗级能让人头痛的东西啊【苦笑】

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 16:34 | 显示全部楼层
感觉好厉害啊 没看懂

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 16:58 | 显示全部楼层
感觉好厉害啊 没看懂!

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 17:20 | 显示全部楼层
完全懵逼中。。。。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 17:42 | 显示全部楼层
虽然是看的天书,不过还是回个贴装着很懂的样子~

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 17:52 | 显示全部楼层
认真看看,学习一下

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

发表于 2018-7-24 17:58 | 显示全部楼层
真是大佬,没看懂

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则


免责声明:
吾爱破解所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Service@52PoJie.Cn

快速回复 收藏帖子 返回列表 搜索

RSS订阅|手机版|小黑屋|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2018-12-14 20:27

Powered by Discuz!

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表