昨天在一个 PHP 的群里看到一个图片,图片如下:
看到这个图片,我觉得这应该是某个收费项目的源码,收费的项目为什么还要提供源码,这就是 PHP 的问题之一吧。
很多人也许想要修改这样的源码,但是无奈源码又是这样的让人看不懂。拿到这样的源码,我估计很多想要修改源码的一部分人就被卡住了。在这种情况下,我想说的是,作者既然这么做了,就是不希望被别人修改。如果真的觉得项目好的话,其实可以去付费的,毕竟软件是每个软件工程师的汗水。
虽然话是这么说,但是如果只是单纯的想要学习,也不产生什么利益的话,遇到这样的问题有什么办法呢?虽然我对 PHP 不怎么懂,但是我知道对于 PHP 这种源代码层面的处理想要还原问题不大(我自己的臆想,毕竟各种的处理方法可能很多,只是我不知罢了),关键在于还原一下值不值。当然了,PHP 的加密还存在引擎级别的,我觉得引擎级别还是有难度的。但是如果只是源码级别的处理,我觉得发挥想象其实每个人都是可以去尝试还原的,因为还原的可能性还是很大的。
这类代码我没怎么见过,针对上面那个图片,我没有拿到源文件,只有这个图片。针对这个图片,我给出一个处理的思路,和大家进行交流。
说说我的思路
说说如果是我处理的话,我处理的思路吧。
- 首先将代码格式化,用很多工具都可以进行格式化,比如 PHPStorm;
- 这样的代码格式化后显然是没有太大的用处的,格式化的目的在于要把整个源码规范一下,然后尝试把整个代码中的 goto 语句去掉;因为代码的执行是顺序的,也就是从文件的开头到结尾这么进行执行,如果能把 goto 去掉的话,你就得到了一份真正的执行顺序的代码,其实 goto 就是无条件的跳转,我们将离散的用 goto 连接的代码,变成线性的就可以了;
- 除了满屏的 goto 语句以外,剩下的就是一些看不懂的字符了,可能有人会说是加密了之类的,其实是看的不够仔细。仔细观察的话,其实只是被编码了。我们可以从代码中有明显特征的位置开始还原,什么是明显特征?看下图。
图中是一个 date() 函数,这个函数我们每个人都会用,它的参数是字符串。PHP 中用来限定字符串的符号分两种,分别是 单引号 和 双引号,在平时为了代码的运行速度,我们写代码通常会使用单引号,而字符串当中有转义字符时,我们就要去使用双引号。而这里 date() 函数中的字符串其实就是转义字符。这些看起来被加密的东西其实就是一些 ASCII 码,说白了就是考验大家的基础。“”开头的是 8 进制,“x”开头的是 16 进制,"131" 是大写字母“Y”,“55”是字符“-”,剩下的还要我说吗?还不会?找一份 ASCII 码表对着看看不行么?
有了第三步的基础,还原剩下的部分难吗?
尝试
我去网上找了类似的一个文件,然后自己尝试用代码去还原它的结构,也就是我上面思路的第二步。毕竟文件有点大,还是写代码还原靠谱。代码写了不到 200 行,还原差不多 20 多行的代码。可以说是有进展的,为什么没有全部还原呢?其实是有原因的,因为在格式化以后,我用代码进行处理的时候,没有逐个的去处理各种可能(因为这部分花时间比较多),我只是处理了部分的情况。有些格式化后的代码,和我想要的预期也不太相同,比如多行连续标签,标签后面接 goto 之类的情况,我没有去一一处理,因为我为的不是还原源码,而是验证我的思路。给出关键代码的结构,完整的源码就不提供了(具体的处理我删掉了),我自己都没有写完,而且也不算复杂。
代码语言:javascript复制<?php
class decode
{
private $f;
private $arr = [];
private $lineNo = 0;
public function openfile()
{
$this->f = fopen('./test.php', 'r');
}
public function closefile()
{
fclose($this->f);
}
public function de()
{
$this->openfile();
while (!feof($this->f)) {
$line = fgets($this->f);
$this->lineNo ;
$this->parse($line);
}
$this->closefile();
$this->output();
$this->outputcode();
}
public function delrn($str)
{
}
public function parse($line)
{
// 处理 goto 的
if (strpos($line, 'goto') !== false) {
}
// 处理标号后的代码
if (strpos($line, ':') !== false) {
// 标号后面是单行代码
if (strpos($line, ';') !== false) {
}
if (strpos($line, ':') !== false) {
}
if (strpos($line, 'goto') !== false) {
}
// 标号后面是多行代码
if (strpos($line, '{') !== false) {
$code = $this->delrn($line);
$i = 1; // 记录左括号{的数量
while ($line = fgets($this->f)) {
$this->lineNo ;
if (strpos($line, '{') !== false) {
}
if (strpos($line, '}') !== false) {
}
$code = $code . ' ' . $this->delrn($line);
}
}
// 标号后面是个function
if (strpos($line, 'function') !== false) {
$code = $line;
$i = 0; // 记录左括号{的数量
while ($line = fgets($this->f)) {
$this->lineNo ;
if (strpos($line, '{') !== false) {
}
if (strpos($line, '}') !== false) {
}
$code = $code . ' ' . $line;
}
}
return ;
}
}
// 中间文件
public function output()
{
$f = fopen('./output.php', 'w');
foreach ($this->arr as $k => $v) {
}
fclose($f);
}
// 最终文件
public function outputcode()
{
$f = fopen('./code.php', 'w');
while (true) {
}
fclose($f);
}
public function next($key, $arr)
{
$bret = false;
foreach($arr as $k => $v) {
if ($bret == true) {
return $k;
}
if ($k === $key) {
$bret = true;
}
}
return false;
}
}
$decode = new decode();
$decode->de();
总结
这种对代码的处理一般应该被称为“代码混淆”,而这种代码混淆的方式算是简单的。这种工具其实可以自己实现一个,按行读取每一行的 PHP 代码,然后给每行代码随机生成一个行号,然后用 goto 连接,最后进行乱序。然后可以把“字符串”处理成“转义字符”。当然了,其实还有很多可以处理的方法,只要把能想到的处理方法定义成规则,你的代码混淆工具处理后的 PHP 代码会比这个要复杂。
知道了混淆的思路,那么反混淆的话,其实也是这种思路,可以人肉进行处理,如果量大就不合适人肉了。量大就需要写工具去自动化完成了。
最后,我想再次和大家说,我们都是做软件开发的,请珍惜每一位软件工程师的汗水。盗取别人的成果,其实是在破坏这个行业,也是在违法。我们面对各种问题时,还是抱着学习和提高自身能力出发。