本文根据 tmp.0ut 第五期中 genetix 的文章编译整理,非逐字翻译;文中观点均属于原作者。

原文:PERL Stuff

文章内容来自 tmpout.sh,本译文依原站 CC BY-NC-SA 4.0 许可作为改编内容发布。如有侵权请通知,我会下架文章。 全文字符数:3502

一个以代码为正文的页面

genetix 的这个页面没有解释性正文,主体是两份 Perl 脚本 1.pl2.pl。它们是围绕“自变形代码”的实验:同一套“引擎”能够产生不止一种文本形态。本文保留两份脚本的完整代码,并按代码本身的结构做中文说明;要核对准确字节与运行行为,应直接阅读原页,并只在隔离环境中实验。

1.pl:打乱关键字后“重写”源码

第一段脚本的思路是:把源码中出现的 Perl 关键字抽成一张表,随机洗牌后,再按洗牌结果重写一段模板源码,最终打印出来的代码里关键字顺序已经改变——也就是注释所说的“这是杀毒软件会看到的东西”。

#!/usr/bin/perl
@original_kw = qw(print if else for elsif open close local do while my push next);
@kw = @original_kw;

#headache
sub rndkw { $_ = ' 'x@_; $i = @_ ;s/ /$j = rand($i--);
@_[$i,$j] = @_[$j, $i]; ''/ge;@_} @kw = rndkw(@kw);$_=
join ' ' , 0..$#original_kw; s/(\d+)/$index{$1 } = [grep {
$kw[$_] eq $original_kw[$1]} 0..$#kw]->[0];''/ge;

#the virus would be here
my $code = <<"END_CODE";
\$kw[5] \$kw[10] \$f, '<', 't.tmp';
\$kw[10] \$metacode = \$kw[8] { \$kw[7] \$/; <\$f> };
\$kw[6] \$fh; \$kw[5] \$kw[10] \$fh, '<', \$0;
\$kw[10] \@data; \$kw[10] \$skipping = 0;
\$kw[9] (\$kw[10] \$line = <\$fh>) {
chomp \$line; \$kw[1] (\$line eq '#poly') {
\$kw[11] \@data, \$line;\$kw[11] \@data, \$metacode;  
\$skipping = 1;\$kw[12];}\$kw[1] (\$line eq '#end#') {
\$kw[11] \@data, \$line;\$skipping = 0;\$kw[12];
}\$kw[12] \$kw[1] \$skipping;\$kw[11] \@data, \$line;}
\$kw[6] \$fh; unlink "t.tmp";\$kw[0] join("\\n", \@data), "\\n";
END_CODE

#this is what av would see
$code =~ s/\$kw\[(\d+)\]/'\$kw[' . $index{$1} . ']'/ge;
print $code;

代码要点:

  • @original_kw 列出 12 个 Perl 关键字(print if else for elsif open close local do while my push next)。
  • rndkw 用 Perl 的替换与随机索引交换来洗牌:把参数列表铺成等长空格串,每匹配一个空格就随机选一个剩余位置做交换,最终返回打乱后的数组。
  • %index 记录“原关键字在打乱后的数组里的新位置”:遍历 0..$#original_kw,对每个序号找出 @kw 中与 $original_kw[$n] 相等的元素下标。
  • heredoc 模板 END_CODE 是一个用 $kw[序号] 占位的“病毒位”(#the virus would be here):占位符展开后会是读取自身 $0、把 #poly#end# 标记之间的内容连同 $metacode 收集进 @data、写回并删除临时文件 t.tmp 之类的代码——具体语义取决于最终关键字如何填入。
  • 最后一行把模板里每个 $kw[n] 替换成 $kw[新位置] 并打印,于是每次运行输出的“源码”关键字排列都不同。

2.pl:让“引擎”自身也变异

第二段脚本更进一步:它不仅改变数据,还改变构造引擎的代码本身。注释说得很直白:“下面的代码会以两种方式变化——引擎自身被变异。”

use List::Util qw(shuffle);

my @arr = qw!shuffle split = @ } or ; , { local shift die print do map join o s . ) ( p m 0 / \\ # ^!; #could add more
#below is the code which changes in 2 different ways. the "engine" itself is mutated
my @x = (
[ 1,  '$code', "$arr[2]", "$arr[13]", "$arr[8]" ],
[ 2,  "$arr[9]", "$arr[20]", "$arr[3]", 'ARGV', "$arr[7]", '$/', "$arr[19]" ],
[ 3,  "$arr[2]", "$arr[10]", '||$0;<>', "$arr[4]", "$arr[6]" ],
[ 4,  '$code=~/my \@arr = qw!(.*?)!;/s ', "$arr[5]", ' ', "$arr[11]", "$arr[6]" ],
[ 5,  "$arr[3]", "$arr[16]", "$arr[2]", "$arr[1]", q{' '}, "$arr[7]", '$1', "$arr[6]" ],
[ 6,  "$arr[3]", "$arr[17]", "$arr[2]", "$arr[0]", "$arr[3]", "$arr[16]", "$arr[6]" ],
[ 7,  "$arr[3]", "$arr[21]", "$arr[8]", "$arr[3]", "$arr[17]", "$arr[4]" ],
[ 8,  "$arr[2]", "$arr[23]", '..$#s', "$arr[6]" ],
[ 9,  "$arr[3]", "$arr[22]", "$arr[2]", "$arr[14]", ' $p{$o[$_]}' ],
[ 10, "$arr[7]", "$arr[23]", '..$#o', "$arr[6]" ],
[ 11, q{$code=~s/my \@arr = qw![^!]*!;/'my @arr = qw!'.} ],
[ 12, "$arr[15]", q{(' ',@s).'!;'/e;} ],
[ 13, '$code=~s/\$arr\[(\d+)\]/"\$arr[$m[$1]]"/ge', "$arr[6]" ],
[ 14, q{$code=~/(.*?my \@x = \(\n)(.*?)(\n\);.*)/s or die;$pre=$1;$mid=$2;$post=$3;} ],
[ 15, q{@l=split/\n/,$mid;} ],
[ 16, q{@l=shuffle @l;} ],
[ 17, q{$code=$pre.join("\n",@l).$post;} ],
[ 18, "$arr[12]", ' $code', "$arr[6]" ],
);

my ($code, $x, $pre, $mid, $post, @l, @s, @o, %p, @m);

$x = join '', map join('', @{$_}[1..$#$_]), sort { $a->[0] <=> $b->[0] } @x; #re-order the shuffled array before using eval ofc.

eval $x;
die $@ if $@; 

代码要点:

  • @arr 是“符号表”:里面混着 Perl 操作符、关键字与标点(shuffle split = @ } or ; , { local shift die print do map join o s . ) ( p m 0 / \ # ^),注释说“还可以再加”。这些符号会被当作代码片段的拼装材料。
  • @x 是一张二维表:每行第一个数字是拼接顺序,其余字段是要拼进最终程序的代码片段。许多片段通过 $arr[n] 间接引用符号表,因此只要符号表内容变化,拼出的程序就会变化。
  • 某些行是自改写指令:例如第 4、11–13 行用正则去匹配并替换源码里的 my @arr = qw!(.*?)!;$arr[\d+];第 14–17 行把 my @x = (...) 的中段抽出来、按行洗牌后再放回去。也就是说,“数据表”和“引擎代码”都能被重写。
  • 最终 $x@x 各行按序号排序后拼接成一段程序,再用 eval 执行:这就是注释说的“在 eval 之前先把打乱的数组重新排序”。

编译说明

原页几乎没有叙述性文字,可翻译的内容主要就是这两段代码与其中的注释,因此本文以“保留代码全文 + 中文说明”的形式呈现,不扩写也不改写原程序。脚本本身是演示 Perl 的语法密度、字符串替换与自引用能力如何服务于代码高尔夫和自变形实验;要在真实系统上核对行为,请直接阅读原页并在隔离环境中运行。


上一页:一个 440 字节的变形 ELF-64 病毒

下一页:有 XLAT 就够了:数据驱动的解码实验