解包一个 PAR 打包的 perl 程序源码

最近在流程debug时,和同事发现一个perl脚本有问题;然后发现这个perl脚本是别人打包好的,所以看不到源码。我就想把这个perl脚本给解包(不敢称为反编译,因为实际上也没做反编译)了。因为打包perl脚本这事,我以前也做过,所以我觉得应该可行。

如何用PAR来打包perl程序

首先,一般打包perl程序,现在应该是使用PAR::Packer打包perl程序,具体参数参考http://search.cpan.org/~rschupp/PAR-1.015/lib/PAR/Tutorial.pod。打包的命令如下:

pp -g -B -o xxx xxx.pl # -g 生成二进制程序, -B 将各种依赖项打包进去, -o 生成的文件名

解包PAR打包的perl程序

首先运行一次程序,然后在 /tmp/ 目录下,查看是否有 par- 开头的文件夹,PAR打包的程序有个特点,就是会将程序解压到 /tmp 目录下,然后再运行。

tmp目录下的文件
tmp目录下的文件

如果有这样子的目录,应该就是PAR打包的了。然后我们使用 unzip -d unarchive xxx,xxx是你的程序的名称,这样子我们就能得到解压的目录。(xxx.zip,语雀对上传文件的后缀有限制,大家自行解压,然后执行exe文件测试)

发现是Bleach加密过的源码

一般解压后我们就能得到脚本的原始.pl 文件,但是这次,我打开 xxx.pl 文件,却只看到一行很奇怪的代码,而文件的大小却很大。这里上传一个xxx.pl给大家玩一下😄️。

$_=<<'';y;\r\n;;d;y; \t;01;;$_=pack'b*',$_;$_=eval;$@&&die$@;$_

是的,整个文件,我wc了一下,足足有2000多行,但是打开来看,却只看到一行代码,这时,我就猜想,这里应该是使用了什么方法来加密的代码,将代码替换成不可见的字符。

于是我使用 cat -vet xxx.pl| less -SN 来查看该文件,发现这个文件只有这种类似与tab的字符,

$_=<<'';y;\r\n;;d;y; \t;01;;$_=pack'b*',$_;$_=eval;$@&&die$@;$_$
^I^I   ^I  ^I$
^I  ^I^I$
^I^I ^I  ^I ^I$

幸运的是,当我将 $_=<<'';y;\r\n;;d;y; \t;01;;$_=pack'b*',$_;$_=eval;$@&&die$@;$_$ 这串字符串拿去google之后,很快发现了A Beginner’s Guide to Compiling Perl Scripts这篇文章,原来这种源码是经过Bleach这种方法加密过的。

当然,这里我还在stackoverflow查到这种方法是如何生效的。

  • $_ = << ''; reads the rest of the file into the accumulator.
  • y;\r\n;;d; strips carriage returns and line feeds.
  • $_ = pack 'b*', $_; converts characters to bits in $_, LSB first.
  • $_ = eval; executes $_ as Perl code.
  • $@ && die $@; $_ handles exceptions and the return code gracefully.

解密Bleach加密的源码

明白是怎么加密之后,我就开始找如何解密的方法,一开始我找到一个unbleach.pl的代码,但是运行了之后,可能是这个代码太老了,并不能解码。

然后我看到How does this obfuscated Perl script work?bleach question,一下子就醒悟过来,上面的原理解释里不是说了吗,用 eval 来执行 $_里的perl代码,那将 eval 修改为 print,就变成了将代码都打印出来😄️。

原来那么简单!

最后我修改文件头一行为

$_=<<'';y;\r\n;;d;y; \t;01;;$_=pack'b*',$_;$_=print;$@&&die$@;$_

然后执行 perl xxx.pl | less -SN,终于看到源码了。

讨论

其实,我在几年前,也做过加密打包perl代码的工作,然后当时就发现,这个打包然并卵。首先会解压到 /tmp 目录下,其次 unzip 一下也能解包,当时我也有看到 PAR::Filter::BleachPAR::Filter::Bytecode 等加密方法,以为加上这个参数应该就可以,但现在发现这种方法还是不安全的。

另外,我看了A Beginner’s Guide to Compiling Perl Scripts里面加上 PAR::Filter::Crypto 的方法,我看到程序要解密加密后的代码,代码必须将密钥也写在代码里,所以应该也是然并卵的。

dev@virtualbox:~/Desktop/decompile$ more script/bestProgram.pl
 
use Filter::Crypto::Decrypt;
e6ad69e3dd1e9901ccf6ba701ff66dfb09ba6b2f6c3b872e33e1929e56298f9861777c6a21255012
c658fa873214cd41071f6915ef594ee5447ae02afc1e2d726333fb855148920518e827fbb0990053
73eddabe4e608e15fcc54008c659f218ac32d56ac8d05a78cfd446ae05cf6c19f7e3c1b30fab747f
c0b0017243f764b3d3db0ef081bbf245478bd5a6af4c361b22e488edf203d91d4018d930fe4d2c1b
8d4d103810eb0603

dev@virtualbox:~/Desktop/decompile$ ../bestProgram_Encrypted.exe 
$VAR1 = {
 'second' => 2,
 'first' => 1,
 'third' => 3
 };

最好的方法,还是将perl代码转换为c代码,甚至是c代码的模块的形式,增加反编译的难度。

参考

◀ 2020 年终总结阿里云PBS作业排队管理 ▶