对多态技术感兴趣朋友,可能多是钟情于代码混淆这块,无意中发现了这篇文章,可能大侠们早已看过,但偶觉得写的比较有趣,他从另一个角度揭示了一种代码变换技术。于是做了一个简单的翻译(水平有限,大家凑合看喽),原文链接:
http://mirror.sweon.net/z0mbie/pgames.txt
    可肯能我们写多态时,更多的是考虑等效指令替换,指令乱序,随机指令生成,随机寄存器等这些因素,这篇文章倒是提了另外的思路,所以可以学习一下了,虽然不一定马上加入自己写的东东里,但留个思路日后琢磨吗,不废话了,下面就是.


Polymorphic Games
=================

part 1 -- conditional execution w/o jxx
---------------------------------------
让我们考虑一些 C to asm 的转换示例吧。
我们设置 a 是 eax ,b 是 ebx ,c 是 ecx, d 是 edx,“condition” 是一个结果或者是些二进制的对比值, 例如一个位,0 或者 1

例子 1
---------
首先,我们要知道下面的语句如何反汇编:

c = condition ? -1 : 0;

这个看起来像:
; CF <-- condition
sbb ecx, ecx
or
; ecx.high_bit <-- condition
sar ecx, 31


例子 2
---------

让我们看一个更真实的情况:

c = a < b ? -1 : 0;

或者从这个例子:

if (a < b) c = -1; else c = 0;

因此,我们必选这样:

cmp eax, ebx
sbb ecx, ecx

或者

mov ecx, eax
sub ecx, ebx
sar ecx, 31

例子 3
---------

一些更复杂点的代码, 例如 a=MIN(a,b) function:

if (a > b) a = b;

这相当于:

a = a > b ? b : a;

也相当于:

a = a + ((a > b ? -1 : 0) & (b - a))

也相当于:

a += ((b - a) < 0 ? -1 : 0) & (b - a)

其中(例子1和例子2)也可以用如下的方式来表达:

sub     ebx, eax
sbb     ecx, ecx
and     ecx, ebx
add     eax, ecx

例子 4
---------

绝对值, abs() function:

a = abs(a)
if (a < 0) a = -a;
a = a < 0 ? -a : a;

但是,有了NEG操作,它如同 NOT + INC (我考虑典型的 x86 asm)
我可以通过如下的方式做:

a = (a ^ (a<0?-1:0)) - (a<0?-1:0)

反汇编看起来是这样:

mov     edx, eax
sar     edx, 31
xor     eax, edx
sub     eax, edx

example 5
---------

一些C的代码:

if (a != 0)
  a = b;
else
  a = c;

a = a ? b : c;

a = ((a != 0 ? -1 : 0) & b) | ((a != 0 ? 0 : -1) & c);

因此,它看起来像这样:

cmp     eax, 1
sbb     eax, eax
and     ecx, eax
xor     eax, -1
and     eax, ebx
or      eax, ecx

正如你所看到的,一些基本的操作码都可以被重新编码,除了条件跳转语句,
即便我们有些条件语句,那其实也可以避免使用jxx 指令来达到目的.

例如:

if (c) a >>= b
可以被转成
a >>= (c ? b : 0),
这相当于例5 + shr
等等.

但是,如果我们想调用子程序呢? okey,这有解决的办法:

func_addr = condition ? real_func : fake_func;
func_addr(arg1, arg2, ...);

int fake_func() { return 0; }

但是,如果我想初始化一些变量呢?让我们先这样做:

var_addr = condition ? &real_var : &fake_var;
*var_addr = ...;

因此,一下情况:

if (condition)
{
  statement1;
  statement2;
  statementN;
}

你可以转换成:

if (condition) statement1;
if (condition) statement2;
if (condition) statementN;

每一行都可以单独编码,使用的技术,可以看上面的例子.
我们唯一不能改变,是直接跳转,和循环处理。

像 while() or for(;;).
然而,我们可以扩展到有条件的执行整个程序,
像这样所示:

while(1)
{
  if (c) line1;
  if (c) line2;
  if (c) c = ...;
  if (c) line3;
  if (c) line4;
}
然后,我们仅需要一个jmp跳转这样的每一个程序上.

正如你所看到的,可以有很多条件,
 if (c) if (d) if (e) lineN 
- 3个嵌套循环,

或者,我们可以写成一个状态机的形式,例如

  if (c==1) line1;
  if (c>=2&&c<=3) line2;
  if (c==4) line3;

等等,所有的程序操作都将可以编码成 w/o jmps   
对于一个程序,编写者尽量最少的使用jxx,理由如下:

1  难以理解其逻辑
2. 更不容易理解其指令的排列顺序
ok, 可以想象,用相同的C的源代码,来产生不同的一些不同的独特的程序.
你可以写一些模板,通过脚本来处理这些模板到C的转换。
在这些模板中,你可以替换一些像下面的宏的一些定义,这些都是可以随机选择的.


#define H0(x)       (((signed)(x)) >> (sizeof((signed)(x))*8-1))
#define H1(a,b)     H0((a)-(b))

#define MIN1(a,b)   ((a)+(H1(b,a) & ((b)-(a))))
#define MIN2(a,b)   ((a)-(H1(b,a) & ((a)-(b))))
#define MIN3(a,b)   ((b)-(H1(a,b) & ((b)-(a))))
#define MIN4(a,b)   ((b)+(H1(a,b) & ((a)-(b))))
//#define MIN5(a,b)   ((a)<(b)?(a):(b))
//#define MIN6(a,b)   ((a)>(b)?(b):(a))
//#define MIN7(a,b)   ((b)>(a)?(a):(b))
//#define MIN8(a,b)   ((b)<(a)?(b):(a))

#define MAX1(a,b)   ((a)+(H1(a,b) & ((b)-(a))))
#define MAX2(a,b)   ((a)-(H1(a,b) & ((a)-(b))))
#define MAX3(a,b)   ((b)-(H1(b,a) & ((b)-(a))))
#define MAX4(a,b)   ((b)+(H1(b,a) & ((a)-(b))))
//#define MAX5(a,b)   ((a)<(b)?(b):(a))
//#define MAX6(a,b)   ((a)>(b)?(a):(b))
//#define MAX7(a,b)   ((b)>(a)?(b):(a))
//#define MAX8(a,b)   ((b)<(a)?(a):(b))

#define ABS1(a)     (((a)^H0(a))-H0(a))
//#define ABS2(a)     ((a)>0?(a):-(a))
//#define ABS3(a)     ((a)>=0?(a):-(a))
//#define ABS4(a)     ((a)<0?-(a):(a))
//#define ABS5(a)     ((a)<=0?-(a):(a))

所有的这些宏可以产生不同的代码,生成w/o jmps 类型的代码.


part 2 -- generating code
-------------------------
在某些情况下,复杂的多态解密器是可以被 set-of-instructions 技术检测出来的.

这一技术可以被定义为以下方面:

if (一些代码已经包含了一些已知的指令)
{
    return true
  or
    仿真这些指令.
}
因此,分析算法时当遇到非定义的指令,那将直接returns false.
可能对抗这种算法检测的方法是,用mistfall-like decryptor 技术注入到
程序代码中(入口模糊),使多态部分的解码器分多个小的代码片断,这些部分都是
间接的联系(例如,分散不同的代码节位置,并利用预先设计的程序进行这些空间的计算)

当你再产生代码时,就可以用下面的方法来改变他们了:

例子 6
---------

; at this step we know result of (r1 ? r2)
          cmp     r1, r2
          jxx     l2
l1:
; never executed really, but can be emulated
          {random part of random .code segment}
l2:
; can be either executed or emulated
          {part of our code}


例子 7
---------

; at this step we know that (r1 % (N+1)) == 2
          and     r1, N
          <jmp|call> dword ptr [offset table1 + r1 * 4]
          ...
table1:   dd l0
          dd l1
          dd l2
          ...
          dd lN
l0:
          ; never executed really, but can be emulated
          {random part of random .code segment}
l1:
          ; never executed really, but can be emulated
          {random part of random .code segment}
l2:
          ; can be either executed or emulated
          {part of our code}
lN:
          ...

正如您所看到的,例6/7所构造的方法将允许您产生这样的代码.

1 总体的指令效果是非常接近标志的指令(ps:猜想他的大意应该是说,构造的指令在效果上同真实的、人为写的程序很接近,也就是很
好的迷惑了扫描器)

2. 充分和正确的仿真是需要一个真正确定的指令指标(ps:应该就是指对set-of-instructions定义的那些指令)

给出的定理:如果一些算法检测将要分析每个有条件指令的跳转,如果仅选择一个条件的指令流(其中只以一些指令是用来定义的)进行分析,那么这将导致误报。

然而,在这里要应该指出:统计法或更先进的
检测算法被用来使用时,是所有其它的简单方法都失效时才会这样做。

那么唯一的办法是写在near-to-undetectable的代码是不变的,检测算法修正所有导致检测失败的错误。