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

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 12227|回复: 105

[调试逆向] PHP加密中的“VMProtect”——魔方加密反编译分析过程

  [复制链接]
发表于 2018-2-4 02:16 | 显示全部楼层
本帖最后由 Ganlv 于 2018-2-11 15:18 编辑

这篇文章中的东西我研究了大概两天,但愿点进来的你能在十几分钟之内看懂。
(看不懂就算了,真的没什么大用,我就是发出来装逼的)

另一篇 PHP 解密分析:某PHP加密文件调试解密过程

本文部分内容在上一篇文章中提到过,这里只做简要说明。

本文的后续:PHP魔方加密 反编译基于栈的指令,这篇文章使用本文分析的成果,从零开始编写了一个反编译器。

样本

文件样本由上一篇分析文章的 70# 层 提供。

魔方加密网站:https://www.mfenc.com/start.html

魔方加密文档:https://www.mfenc.com/document/start.md

下文称这个编译方法为 mfenc

静静地看着魔方加密吹牛逼

魔方加密的编译系统有这些特点:

  1. 自主开发
  2. 有完整的中间表示设计(中间指令和抽象语法树两种形式)
  3. 有完整的中间表示到目标 PHP 代码的映射规则
  4. 有与目标代码配套的虚拟机设计,包括虚拟机结构、与 PHP 环境的交互方法

魔方加密的程序不对外公开,随时保持更新,跟进最新的学术研究成果,在根本上杜绝了潜在攻击者的试探、分析等行为,保证了核心算法的安全,也保证了加密代码的安全。

说明

本文涉及大量汇编语言及反编译,需要有一定基础。而且学这个东西真的还不一定有用。

格式化代码

我用的是 nikic/php-parser 的 AST 分析器进行的代码格式化,因为我发现这个库对乱码变量名的支持很好。

<?php

use PhpParser\Error;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;

ini_set('xdebug.max_nesting_level', 10000);

require 'vendor/autoload.php';

$input_file = $argv[1];
$output_file = $argv[1] . '.formatted.php';

$code = file_get_contents($input_file);

// 解析代码
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}

// 格式化代码
$prettyPrinter = new PrettyPrinter\Standard;
$prettyCode = $prettyPrinter->prettyPrintFile($ast);

file_put_contents($output_file, $prettyCode);

01.jpg

分析代码

代码最前面是一堆拥有同样形式的函数,都是 7 个输入变量,都是以引用的方式传递参数。

function func1(&$arg0, &$eip, &$arg2, &$arg3, &$arg4, &$arg5, &$arg6);

后面是一个函数,包含一个静态变量,这个静态变量等于一个超长的字符串,一个由一堆字符串使用 . 点符号连接起来的超长的字符串。

function func0($arg0, $eip, $arg2 = null)

最后面是一句调用函数。

func0(array(), 0)

然后进行单步调试,跟踪一会就回发现,这特么不是一个虚拟机的形式吗!以下为了方便描述,我就使用 x86 指令集的某些描述方式了(只研究过一些 x86 指令集)。

初步分析结果

经过一段时间的调试,我们大概弄明白了最后一个函数类似于 call 指令,其他的每个函数的 7 个参数分别为:数据内存、指令指针、栈、栈指针、基址指针、报错等级栈、报错等级栈指针。

将变量名替换成有意义的名称之后的代码如下

function func1($memory, $eip, $stack, $esp, $ebp, $error_level_stack, $error_level_stack_pointer) {
    // 这里都没有 return 都是通过引用参数返回的
}

function func_call(array $args, $eip, $ret = null)
{
    static $memory;
    if (strlen($memory) == 0) {
        $memory = '......';
    }
    $stack = array();
    $error_level_stack = array(); // 用于保存 error_reporting level
    $esp = $error_level_stack_pointer = 0;
    $stack[++$esp] = $ret;
    foreach ($args as $item) {
        $stack[++$esp] = $item;
    }
    $stack[++$esp] = count($args);
    $stack[++$esp] = -1;
    $stack[++$esp] = 0;
    $ebp = $esp;
    while ($eip >= 0) {
        $func = 'func' . ($memory[$eip] ^  $memory[$eip + 1]) . ($memory[$eip] ^  $memory[$eip + 4]) . ($memory[$eip] ^  $memory[$eip + 5]);
        $eip += ord($memory[$eip] ^ $memory[$eip + 3]);
        $func($memory, $eip, $stack, $esp, $ebp, $error_level_stack, $error_level_stack_pointer);
    }
    if ($eip == -1) {
        return $stack[$esp];
    }
}

func_call(array(), 0);

修复局部变量名

经过观察发现,这个加密算法的函数中只有局部变量,所以我们可以轻松地进行变量名替换,而不会影响函数执行结果。

我这里依旧使用 PHP-Parser 先进行语法分析,然后再替换变量名,最后格式化输出。

在解析代码与格式化输出之间添加如下代码

use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

// 变量名替换
class VariableRenameNodeVisitor extends NodeVisitorAbstract
{
    protected $paramsMap = [];

    public function __construct(&$params_map)
    {
        $this->paramsMap = $params_map;
    }

    public function generateNewVariableName()
    {
        $values = array_values($this->paramsMap);
        $i = 0;
        while (in_array('v' . $i, $values)) {
            ++$i;
        }
        return 'v' . $i;
    }

    public function leaveNode(Node $node)
    {
        if ($node instanceof Node\Expr\Variable) {
            if (!isset($this->paramsMap[$node->name])) {
                $this->paramsMap[$node->name] = $this->generateNewVariableName();
            }
            $node->name = $this->paramsMap[$node->name];
        }
    }
}

class FunctionParamsRenameNodeVisitor extends NodeVisitorAbstract
{
    public function leaveNode(Node $node)
    {
        if ($node instanceof Node\Stmt\Function_) {
            $params_map = [];
            foreach ($node->params as $i => &$param) {
                $params_map[$param->name] = 'arg' . $i;
                $param->name = $params_map[$param->name];
            }
            $visitor = new VariableRenameNodeVisitor($params_map);
            $traverser = new NodeTraverser;
            $traverser->addVisitor($visitor);
            $node->stmts = $traverser->traverse($node->stmts);
        }
    }
}

$traverser = new NodeTraverser;
$traverser->addVisitor(new FunctionParamsRenameNodeVisitor);
$ast = $traverser->traverse($ast);

02.jpg

修复变量名之后之后我们继续调试。

调试的过程中,可以看出这套指令集中,数据与指令是混在一起的,并不是 .text.data 分开的,或者说使用了大量的立即数。

03.jpg
04.jpg

在所有的函数调用、evalincluderequire 处下断点,单步调试看看到底是什么逻辑。

然后单步跟踪一会就回发现每个函数的作用,所有的函数都是通过栈来进行数据交换的。

有的函数负责申请栈空间,有的负责清除栈空间,有的函数负责跳转 jejnz 之类的,有的负责函数调用,总之前面的 65 个函数可以称之为 mfenc 的指令集。

我们要破解这个文件必须把他的指令集中每一条指令都分析一下,然后对他的 VM 中间函数进行 Hook 操作,提取出关键 Opcode,然后根据 Opcode 对应的操作还原出原始代码,这个过程和 IDA 的还原代码很像,这个过程靠的是脑子和经验,但是最费的还是体力。

我以前没学过虚拟机的原理,破解这个的过程真的学到了很多。

提取原始 Opcode

先把 $memory 变量输出出来,免得原来的文件看起来费劲。

$memory 赋值语句之后插入 file_put_contents

$memory = '......';
file_put_contents('1.php.opcode.bin', $memory);

执行一次,之后改成

$memory = file_get_contents('1.php.opcode.bin');

函数重命名

为了消除程序乱码,我想了一个方法,就是把所有的函数名称改成 func1 之类的名字,然后动态地把乱码函数名代{过}{滤}理到我们替换之后的函数。

这样做之后有几个好处,我可以随意地修改程序,而不用担心编码错误。因为有代{过}{滤}理这一层,我可以随意 Hook 其中的步骤。

我又重新修改了我的 format.php,不过试了一下感觉效果并不是特别好,所以这个暂时就先放弃(不过之后肯定还是要把 Opcode 翻译成汇编语言的)。

function func51(&$memory, &$eip, &$stack, &$esp, &$ebp, &$error_level_stack, &$error_level_stack_pointer)
{
    eval("function " . $stack[$esp] . '(){return func65(func_get_args(),' . (int) ($memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++] . $memory[$eip++]) . ');}');
}
function func65(array $args, $eip, $ret = null)
{
    static $memory, $func_name_map;
    if (strlen($memory) == 0) {
        $memory = file_get_contents('1.php.opcode.bin');
        $func_name_map = include '2.php.formatted.php.func_name_map.php';
    }
    $stack = [];
    $error_level_stack = [];
    $esp = $error_level_stack_pointer = 0;
    $stack[++$esp] = $ret;
    foreach ($args as $item) {
        $stack[++$esp] = $item;
    }
    $stack[++$esp] = count($args);
    $stack[++$esp] = -1;
    $stack[++$esp] = 0;
    $ebp = $esp;
    while ($eip >= 0) {
        $func = base64_decode('zb+8') . ($memory[$eip] ^ $memory[$eip + 1]) . ($memory[$eip] ^ $memory[$eip + 2]) . ($memory[$eip] ^ $memory[$eip + 4]) . ($memory[$eip] ^ $memory[$eip + 5]);
        $eip += ord($memory[$eip] ^ $memory[$eip + 3]);
        if (isset($func_name_map[$func])) {
            $func = $func_name_map[$func];
        }
        $func($memory, $eip, $stack, $esp, $ebp, $error_level_stack, $error_level_stack_pointer);
    }
    if ($eip == -1) {
        return $stack[$esp];
    }
    exit;
}
include '2.php.formatted.php.func_map.php';

继续调试

05.jpg 06.jpg 07.jpg 08.jpg

上面的图中就是我在分析每一个函数的作用,把他们写成汇编语言,顺便分析函数调用的规则,方便我们写 Decompiler (是反编译器,而不是 Disassembler 反汇编器)

举个例子

00000060 + 006 >>> 00000066 - push (null)
00000066 + 006 >>> 00000089 - mov [esp], (string((int(2)[eip])[eip+2]     'GetUrlToDomain', 14
00000089 + 006 >>> 00000107 - def [esp], (int(12))[eip]     'GetUrlToDomain', 7667     ; function GetUrlToDomain(){$args=func_get_args();return call($args,7667);}
00000107 + 006 >>> 00000113 - sub esp, 1     5

这个汇编语言是我自己随便发明的语法,int(12) 表面后面的 [eip] 的整数值占 12 位数(十进制)。

最前面的数值是十进制,表示指令开始位置 +006 表示指令占 6 字节(这 6 字节包含指令调用的函数名和指令参数相对位置),根据指令循环的代码,+0 是异或加密的秘钥 +1, +2, +4, +5 是指令调用的函数名(以后就称之为指令名吧),+3 是本条指令的操作数位置或下一条指令的位置

在这个程序中,压栈会导致栈指针增大,出栈使用 sub 而不是 add,同理为局部变量分配空间是 add esp, ... 而不是使用 sub

我们应该把上面的代码反编译成什么呢?

function GetUrlToDomain(...$args) {
    // 内部的代码的指令从偏移 7667 处开始
}

再举个例子

00000848 + 006 >>> 00000854 - add esp, (int(2)) [eip]     4, '16'

这里把 esp 增加了 16,这意味着我们当前运行的这个代码块中就要有 16 个局部变量。

但是,我们得想办法用代码来实现上述功能。

反编译分析

我们要开始规规矩矩地分析了。

首先我们手工分析一下,然后根据我们手工分析的方法,用代码实现反编译。

片段 1

00000856 + 006 >>> 00000862 - push (null)
00000862 + 006 >>> 00000878 - mov [esp], (string((int(1)[eip])[eip+2]     'is_admin', 8
00000878 + 006 >>> 00000884 - call (0) [esp]     'is_admin'
00000884 + 006 >>> 00000890 - not [esp]     true
00000890 + 006 >>> 00000908 - jtrue [esp], ptr +(int(12))[eip]     false, 5997
00000908 + 006 >>> 00000914 - sub esp, 1     21

第一句是压栈,这个 mfenc 没有 x86 那种直接压入变量的指令,只能先压入一个 null,然后再向当前栈顶写入内容。

这是接下来的一段,第二句可以反编译成

$stack[$esp] = 'is_admin';

其实,更准确的说法应该是 $s1 = 'is_admin';,用 s 代表 symbol 就是符号的意思,这个符号肯定要被后文用到。

第三句是

$stack[$esp] = $stack[$esp]();

这时我们就要查找前面对寄存器写入的语句了,反编译成

$stack[$esp] = is_admin();

然后下一句是 not,而且在同一层栈中操作的,就是反编译成

$stack[$esp] = !is_admin();

jtrue 其实是我为了方便的,应该写成 jnz 才对,反编译应该就是

if (!is_admin()) {
    // 正常继续
    // 这里如果还有一个 jmp 就是标准的 if-else
}
// 5997 字节的指令后

最后一句 恢复栈平衡是一定要有的,由于这套指令集没有符号位,所以必须依赖栈作为判断依据,判断后无论是否跳转,肯定都要移动一下栈指针,把刚才判断的那个数值移到栈外。

片段 2

00000914 + 006 >>> 00000920 - push (null)
00000920 + 006 >>> 00000927 - link [esp], [ebp+(int(1))[eip]]     21, 5, NULL     ; ebp=4 [eip]=1
00000927 + 006 >>> 00000933 - push (null)
00000933 + 006 >>> 00000946 - mov [esp], (string((int(1)[eip])[eip+2]     'Grace', 5
00000946 + 006 >>> 00000952 - mov [esp-1], [esp]     'Grace'
00000952 + 006 >>> 00000958 - sub esp, 1     22
00000958 + 006 >>> 00000964 - unlink [esp], [esp]     'Grace'
00000964 + 006 >>> 00000970 - sub esp, 1     21

这里的 link 指令表示令 esp 执向 ebp+(int(1))[eip] 的引用,这里看 [eip] 的值,[eip]=1 那么后面那一整个操作数就相当于第 2 个局部变量 $v0。所以这段代码可以反编译成

$stack[$esp] =& $v0;

或者说

$v0 =& $stack[$esp];

然后这里就是提取一个字符串到栈中

$stack[$esp] = 'Grace';

然后这里有一个,赋值语句,要注意这里面包含被引用的变量,所以意思不一样。

$v0 = 'Grace';

上面代码有一个典型的结构,就是 push + link + push + 读取内容 + mov + sub + unlink + sub,这一套结构的用途就是给一个局部变量赋值。

片段 3

00000970 + 006 >>> 00000976 - push (null)
00000976 + 006 >>> 00000983 - link [esp], [ebp+(int(1))[eip]]     21, 6, NULL     ; ebp=4 [eip]=2
00000983 + 006 >>> 00000989 - push (null)
00000989 + 006 >>> 00001023 - mov [esp], (string((int(2)[eip])[eip+2]     'http://yun.api.suxing.me/', 25
00001023 + 006 >>> 00001029 - mov [esp-1], [esp]     'http://yun.api.suxing.me/'
00001029 + 006 >>> 00001035 - sub esp, 1     22
00001035 + 006 >>> 00001041 - unlink [esp], [esp]     'http://yun.api.suxing.me/'
00001041 + 006 >>> 00001047 - sub esp, 1     21
00001047 + 006 >>> 00001053 - push (null)
00001053 + 006 >>> 00001060 - link [esp], [ebp+(int(1))[eip]]     21, 7, NULL     ; ebp=4 [eip]=3
00001060 + 006 >>> 00001066 - push (null)
00001066 + 006 >>> 00001096 - mov [esp], (string((int(2)[eip])[eip+2]     'http://www.suxing.me/', 21
00001096 + 006 >>> 00001102 - mov [esp-1], [esp]     'http://www.suxing.me/'
00001102 + 006 >>> 00001108 - sub esp, 1     22
00001108 + 006 >>> 00001114 - unlink [esp], [esp]     'http://www.suxing.me/'
00001114 + 006 >>> 00001120 - sub esp, 1     21

熟练了吗?

$v1 = 'http://yun.api.suxing.me/';
$v2 = 'http://www.suxing.me/';

片段 4

00001120 + 006 >>> 00001126 - push (null)
00001126 + 006 >>> 00001150 - mov [esp], (string((int(2)[eip])[eip+2]     'function_exists', 15
00001150 + 006 >>> 00001156 - push (null)
00001156 + 006 >>> 00001173 - mov [esp], (string((int(1)[eip])[eip+2]     'curl_init', 9
00001173 + 006 >>> 00001179 - call (1) [esp-1], [esp]     'function_exists', 'curl_init'
00001179 + 006 >>> 00001185 - sub esp, 1     22
00001185 + 006 >>> 00001191 - not [esp]     true
00001191 + 006 >>> 00001197 - not [esp]     false
00001197 + 006 >>> 00001215 - jtrue [esp], ptr +(int(12))[eip]     true, 126
00001341 + 006 >>> 00001347 - sub esp, 1     21
if (function_exists('curl_init') != false) {
   // 正常执行
}
// 126 字节后

注意,这里有个很奇怪的东西,就是连续两次 not 指令,我分析的原因是 if (... != false) 的编译结果,我们可以认为是花指令,把它直接简化为 if (...)

call 指令是用栈中最深的做函数名,函数名上方的都做参数,返回值直接把函数名覆盖掉。call 后面要把栈多余的参数都移除。

jtrue 之后一定会有一个出栈的操作。

现在我们已经发现了函数调用和 if 语句的结构了,可以想想怎么反编译了。

自动化反编译

如果遇到 jtrue 则要新建一个 if 指令,if 指令有 3 部分 condstmtselse,其中 cond 由前面的指令提供,stmts 由跳转后的指令决定,else 内容由紧随其后的指令决定。

因为 stmts 是在 if 之后解析的,所以我们知道按照 if 来解析,但是我们再解析 cond 的时候,并不知道这个内容是被 if 使用的,所以也要像运行时一样,使用一个栈来存储未完成的表达式片段。

比如遇到读取 function_exists 时,我们就要进行下列内容(使用 php-parser

++$decompile_stack_pointer;
$decompile_stack[$decompile_stack_pointer] = new PhpParser\Node\Scalar\String_('function_exists');

同理,

++$decompile_stack_pointer;
$decompile_stack[$decompile_stack_pointer] = new PhpParser\Node\Scalar\String_('curl_init');

然后就是 call 指令了,

$decompile_stack[$decompile_stack_pointer - 1] = new \PhpParser\Node\Expr\FuncCall(
    new \PhpParser\Node\Name($decompile_stack[$decompile_stack_pointer - 1]->value),
    [
        new \PhpParser\Node\Arg($decompile_stack[$decompile_stack_pointer]),
    ]
);
--$decompile_stack_pointer;

两次 not 指令

$decompile_stack[$decompile_stack_pointer] = new \PhpParser\Node\Expr\BooleanNot($decompile_stack[$decompile_stack_pointer]);
$decompile_stack[$decompile_stack_pointer] = new \PhpParser\Node\Expr\BooleanNot($decompile_stack[$decompile_stack_pointer]);

if 指令

$decompile_stack[$decompile_stack_pointer] = new \PhpParser\Node\Stmt\If_($decompile_stack[$decompile_stack_pointer]);

由于我们不知道这个 if 指令到底走哪条路,我们就要开始 dfs 搜索了,按照两条路各走一遍,分别添加到 stmts 块和 else 块。

不过,想一想,这里会不会有什么问题?

如果这个跳转不是 if 而是 while 呢?跳转之后执行到某处又会跳转回来呢?

就算确定了就是是 if,我们又怎么知道 stmts 块和 else 块会合的位置呢?

想想 IDA Pro,我们是不是又代码片段的说法,我们通过 jmpjtrue 语句把代码分成片段,jtrue 一定是 stmts 段的开始,jmpstmts 段的结束。jtrue 的目标地址是 else 段的开始,jmp 的目标地址是 else 段的结束。

因为 php 中没有 goto 语句,所以我们可以大胆地使用上面的猜想。

比如下面这个标准的 if 嵌套

if ($cond1) {
    if ($cond2) {
        // stmt1
    } else {
        // stmt2
    }
} else {
    if ($cond3) {
        // stmt3
    } else {
        // stmt4
    }
}
jtrue $cond1 label1
    jtrue $cond3 label2
        // stmt4
        jmp label3
    label2:
        // stmt3
    label3:
label1:
    jtrue $cond2 label4
        // stmt2
        jmp label5
    label4:
        // stmt1
    label5:

如果不存在花指令的话,按上述分析应该很容易解析得到 if 语句

09.jpg 10.jpg 11.jpg

部分成果

功能代码已略去,仅保留验证部分

<?php
if (!is_admin()) {
} else {
    $v0 = 'Grace';
    $v1 = 'http://yun.api.suxing.me/';
    $v2 = 'http://www.suxing.me/';
    if (!!function_exists('curl_init')) {
    } else {
        wp_die('主机不支持curl,请联系主机服务商。');
    }
    if (!defined('WP_HOME')) {
    } else {
        if (!is_ssl()) {
            $v3 = str_replace('http://', '', defined('WP_HOME') ? constant('WP_HOME') : 'WP_HOME');
        } else {
            $v3 = str_replace('https://', '', defined('WP_HOME') ? constant('WP_HOME') : 'WP_HOME');
        }
    }
    if (!is_ssl()) {
        $v3 = str_replace('http://', '', home_url());
    } else {
        $v3 = str_replace('https://', '', home_url());
    }
    $v4 = explode('/', $v3);
    $v5 = $v4[0];
    $v6 = GetUrlToDomain($v5, $v1);
    if (!isset($_GET['do'])) {
    } else {
        if (!!!($_GET['do'] == 'activeapi')) {
        } else {
            $v7 = '{"copyright":"200"}';
            $v8 = json_decode($v7, (bool) 1);
            update_option('_nice_' . $v0 . '_' . $v6, $v8);
        }
        if (!isset($_GET['do'])) {
        } else {
            if (!!!($_GET['do'] == 'delelteapi')) {
            } else {
                update_option('_nice_' . $v0 . '_' . $v6, '');
            }
            $v9 = get_option('_nice_' . $v0 . '_' . $v6);
            if (!$v9) {
                $v10 = 400;
            } else {
                $v10 = (int) $v9['copyright'];
            }
            if ($v10 == 400) {
            } else {
                if (!$v9) {
                } else {
                    if (!(bool) (!!!($v10 == 200))) {
                    } else {
                        $v11 = mee_curl_get_contents($v1 . '?domain=' . $v6 . '&theme=' . $v0);
                        $v12 = json_decode($v11, (bool) 1);
                        update_option('_nice_' . $v0 . '_' . $v6, $v12);
                        $v13 = (int) $v12['copyright'];
                        $v13;
                        if ($v13 == 200) {
                        }
                    }
                }
                wp_die('您未获得' . $v0 . '主题的授权,请联系苏醒:<a href="https://www.suxing.me/i?a=qq">获取授权</a>', '授权提示');
            }
        }
        $v14 = get_option('_order_' . $v0 . '_' . $v6);
        if (!$v14) {
            $v15 = 400;
        } else {
            $v15 = (int) $v14['status'];
        }
        if ($v15 == 400) {
        } else {
            if (!$v14) {
            } else {
                if (!(bool) (!!!($v15 == 200))) {
                } else {
                    $v11 = mee_curl_get_contents($v2 . '?domain=' . $v6 . '&theme=' . $v0);
                    $v12 = json_decode($v11, (bool) 1);
                    update_option('_order_' . $v0 . '_' . $v6, $v12);
                    $v13 = (int) $v12['status'];
                    $v13;
                    if ($v13 == 200) {
                    }
                }
                wp_die('您未获得' . $v0 . '主题的服务授权,请联系苏醒:<a href="https://www.suxing.me/i?a=qq">续费服务</a>', '服务到期提示');
            }
        }
    }
}

可以看到许多“编译”的产物,比如连续三个“逻辑非”,比如说只有 else 没有 stmts 块。

破解这个东西之后,我真的觉得 IDA 实在太牛逼了,想做到反编译必须得已知大量的编译前后的对应方法。

总结

说实话,真的挺恶心,我需要把他的虚拟机的每一个 opcode 都看一遍,然后翻译成 php-parser 的 AST 构造代码。

这个虚拟机有 65 个 opcode,我看了两天时间,相当于把基本的自动反编译器的原理弄懂些了,写了一个基本的反编译器。

我真的没学过编译原理什么的,感觉研究完这个东西收获挺大的,有兴趣的同学也可以尝试一下反编译,还是有很多独特的技巧的,比如 dfs 搜索代码,或者如何找到代码会合的路径。

完全可以使用这个网站来加密 php,加密效果应该说是不错的,我给 90 分。不过这个加密的性能损失应该不小,凭感觉能在500%以上的性能损失,而且看样子好像没有关于 class 的 opcode,只能针对面向过程的 php 文件。

附录

反编译器还没完全写完,目前只能手动一段一段地输出代码,还不能直接全文反编译。

12.jpg

这里有另一个样本可以供大家研究

上个帖子的 92# 层

看样子同样是虚拟机加密方式

样本2.jpg

相关推荐




最后再说一句,你不应该把这个当成练习破解,而应该将其看做学习反编译原理。

免费评分

参与人数 44吾爱币 +49 热心值 +42 收起 理由
HOWMP + 1 + 1 用心讨论,共获提升!
code2018 + 1 用心讨论,共获提升!
ilikethis2 + 1 + 1 我很赞同!
GinkgoGO + 1 + 1 努力看懂
rockft + 1 + 1 鼓励转贴优秀软件安全工具和文档!
河边看戏的 + 1 + 1 用心讨论,共获提升!
18759069860 + 1 + 1 我很赞同!
ainimemeda + 1 + 1 热心回复!
sunshine_昊 + 1 + 1 用心讨论,共获提升!
antclt + 1 + 1 我很赞同!
?﹏從此沉默 + 1 + 1 我很赞同!
lyiwxt + 1 + 1 我很赞同!
五花小鱼 + 1 + 1 热心回复!
王娜娜 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
f4cku + 1 + 1 谢谢@Thanks!
绝美之城 + 1 + 1 热心回复!
jnez112358 + 1 + 1 谢谢@Thanks!
jane35622 + 1 + 1 最后的这个样本被解密网站一样识别为魔方加密.......
ykkcom + 1 + 1 我很赞同!
Baoofting + 1 + 1 谢谢@Thanks!
塞北的雪 + 1 + 1 这个文章我给100分~~~
oldman + 1 膜拜大佬!!
黑的思想 + 2 + 1 用心讨论,共获提升!
anqing_2018 + 1 + 1 谢谢@Thanks!
liphily + 1 我很赞同!
无情绝恋 + 2 + 1 谢谢@Thanks!
Ravey + 1 + 1 谢谢@Thanks!
珍惜幸福涙 + 1 + 1 真心牛逼
zy1234 + 1 + 1 热心回复!
wmsuper + 2 + 1 我很赞同!
qzr + 1 + 1 用心讨论,共获提升!
luogan129 + 1 + 1 谢谢@Thanks!
索马里的海贼 + 3 + 1 用心讨论,共获提升!
hellopojie + 1 + 1 谢谢@Thanks!
226tiger + 1 + 1 我很赞同!
lonehsigle + 1 + 1 无敌了~~
kk1212 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
joyc + 1 + 1 热心回复!
hellofuture + 1 + 1 热心回复!
Mrshenl + 1 + 1 热心回复!
pk8900 + 1 + 1 大神,出的教程就是经典。
邪恶博士 + 2 + 1 楼主辛苦了
飘零未忍 + 1 + 1 真绕~~~
65302666 + 2 + 1 大神多出点CE汇编方面的使用技巧

查看全部评分

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

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

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

发表于 2018-2-8 13:25 | 显示全部楼层
本帖最后由 jane35622 于 2018-2-8 23:23 编辑

大大您好,最后留下的123.txt那个我在别的php解密网站查了下,好像是魔方一代?我手上也有份魔方一代的源码,自己用自己写的工具弄了一下,包括打调试日志等balabala,大致弄清楚怎么回事了,可是怎么才能把这样一句句的代码转换回源码呢?
[Asm] 纯文本查看 复制代码
C:\myphp_www\PHPTutorial\php\php-5.6.27-nts\php.exe C:\Users\computer\PhpstormProjects\untitled\kvm.php
0x00000002        push $memory[1], null 
0x0000000E        push $memory[2], 'defined'
0x0000001C        push $memory[3], 'IN_DISCUZ'
0x00000052        run  $memory[$pointer-2]=$memory[$pointer-1]($memory[$pointer]);			//$memory[1]=defined('IN_DISCUZ');
0x00000054        pop  3 
0x00000056        pop  2 
0x0000007B        run  $memory[$pointer]=!$memory[$pointer]; 								//$memory[1]=!$memory[1];
0x000000A3        run  $memory[$pointer]=(bool)$memory[$pointer]; 
0x000000CB        run  if($memory[$pointer])$src_index=0x000001E6; 							//if(!defined('IN_DISCUZ'))$src_index=0x000001E6;
0x000000CD        pop  1 
0x000000CF        push $memory[1], null 
0x00000103        run  $memory[++$pointer]="defined"; 
0x00000143        run  $memory[++$pointer]="IN_ADMINCP"; 
0x00000179        run  $memory[$pointer-2]=$memory[$pointer-1]($memory[$pointer]);			//$memory[1]=defined('IN_ADMINCP');
0x0000017B        pop  3 
0x00000191        run  $src_index=0x00000197; 
0x00000199        pop  2 
0x000001BE        run  $memory[$pointer]=!$memory[$pointer]; 								//$memory[1]=!$memory[1];
0x000001E6        run  $memory[$pointer]=(bool)$memory[$pointer]; 
0x0000020E        run  if($memory[$pointer])$src_index=0x00000242; 							//if(!defined('IN_ADMINCP'))$src_index=0x00000242;
0x00000210        pop  1 
0x00000226        run  $src_index=0x000002A3; 
0x000002B9        push $memory[1], 'comiis_app_portal'
0x000002C7        push $memory[2], 'plugin_id'
0x000002C9        s2p  plugin_id															//$memory[2]=$plugin_id
0x000002ED        run  $memory[$pointer]=$memory[$pointer-1];								//$plugin_id='comiis_app_portal';
0x000002F1        pop  2 
0x000002F3        pop  1 
0x000002F5        push $memory[1], null 
0x00000309        push $memory[2], 'function_exists'
0x0000032D        push $memory[3], 'comiis_app_load_app_portal_data'
0x00000363        run  $memory[$pointer-2]=$memory[$pointer-1]($memory[$pointer]);			//$memory[1]=function_exists('comiis_app_load_app_portal_data');
0x00000365        pop  3 
0x00000367        pop  2 
0x0000038C        run  $memory[$pointer]=!$memory[$pointer]; 
0x000003A2        run  $src_index=0x000003A8; 
0x000003D0        run  if($memory[$pointer])$src_index=0x000003E8; 
0x000003EA        pop  1 
0x000003EC        push $memory[1], null 
0x000003FC        push $memory[2], 'file_exists'
0x000003FE        push $memory[3], null 
0x0000041D        run  $memory[$pointer]=DISCUZ_ROOT; 
0x00000471        run  $memory[++$pointer]="./source/plugin/"; 
0x000004A6        run  $memory[$pointer-1]=$memory[$pointer-1].$memory[$pointer]; 
0x000004A8        pop  4 
0x000004B6        push $memory[4], 'plugin_id'
0x000004B8        s2p  plugin_id 
0x000004D0        run  $src_index=0x000004D6; 
0x0000050B        run  $memory[$pointer-1]=$memory[$pointer-1].$memory[$pointer]; 
0x0000050D        pop  4 
0x00000529        push $memory[4], '/comiis_info/comiis.php'
0x0000055E        run  $memory[$pointer-1]=$memory[$pointer-1].$memory[$pointer]; 
0x00000560        pop  4 
0x00000596        run  $memory[$pointer-2]=$memory[$pointer-1]($memory[$pointer]); 
0x00000598        pop  3 
0x0000059A        pop  2 
0x000005C4        run  if($memory[$pointer])$src_index=0x000005DC; 
0x000005C6        pop  1 
0x000005DC        run  $src_index=0x00000746; 
0x00000761        run  $memory[++$pointer]=false; 
0x0000076F        run  $src_index=-1; 
 Process finished with exit code 0

魔方加密.zip (109.37 KB, 下载次数: 25)

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

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

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

发表于 2018-2-27 13:03 | 显示全部楼层

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

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

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

发表于 2018-2-8 21:55 | 显示全部楼层
youchao 发表于 2018-2-8 15:44
[mw_shl_code=php,true]

您好,能不能上传一下您的文件?我想拿来试试手哈~

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

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

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

发表于 2018-2-4 02:23 | 显示全部楼层
大神多出点CE汇编方面的使用技巧

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

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

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

发表于 2018-2-4 02:34 | 显示全部楼层
留个标记。改天研究研究

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

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

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

发表于 2018-2-4 05:31 | 显示全部楼层
谢大佬分享了

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

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

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

发表于 2018-2-4 06:27 | 显示全部楼层
厉害了~希望后续分享下自动化反编译的文章,几年前接触过php加密,从eval换echo,到$Global那种的都见过,$Global的就很烦,现在虚拟机都上了,也真是费心了。

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

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

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

发表于 2018-2-4 08:41 | 显示全部楼层
坐等大神出个编译器,已经收听,记得通知我

免费评分

参与人数 1热心值 +1 收起 理由
Ganlv + 1 你觉得我会公开出来吗

查看全部评分

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

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

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

发表于 2018-2-4 10:35 | 显示全部楼层
大神  分析的不错,学习了

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

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

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

发表于 2018-2-4 11:46 | 显示全部楼层
感谢楼主的分享!!!!!!!!

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

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

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

发表于 2018-2-4 12:55 | 显示全部楼层
谢大佬分享了,收藏了

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

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

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

发表于 2018-2-4 13:26 | 显示全部楼层
感谢大牛分享经验!!

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

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

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

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

本版积分规则


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

Mail To:Service@52PoJie.Cn

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

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

GMT+8, 2018-5-25 13:15

Powered by Discuz!

© 2001-2017 Comsenz Inc.

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