首页
论坛
课程
招聘
雪    币: 328
活跃值: 活跃值 (29)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝

[讨论]换个角度看多态 - Polymorphic Games

2008-10-31 17:31 6597

[讨论]换个角度看多态 - Polymorphic Games

2008-10-31 17:31
6597
对多态技术感兴趣朋友,可能多是钟情于代码混淆这块,无意中发现了这篇文章,可能大侠们早已看过,但偶觉得写的比较有趣,他从另一个角度揭示了一种代码变换技术。于是做了一个简单的翻译(水平有限,大家凑合看喽),原文链接:
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的代码是不变的,检测算法修正所有导致检测失败的错误。

HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

最新回复 (9)
雪    币: 661
活跃值: 活跃值 (618)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
NONAME剑人 活跃值 3 2008-10-31 17:57
2
0
sofa..

一楼出租

===================
其实我觉得多态可以逐渐向虚拟机的方向发展,就向PCODE之类一样
雪    币: 217
活跃值: 活跃值 (11)
能力值: ( LV13,RANK:530 )
在线值:
发帖
回帖
粉丝
foxabu 活跃值 13 2008-10-31 19:10
3
0
ps, 用那个PHX Optimized Compiler 跑一下这些东西就没咯 就好比 
#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))))

这类宏用GCC,或者VC Release编译一下就浪费啦.

修改编译器产生垃圾代码使一个好办法,效果可以作出类似于那个什么 扭曲加密的效果,不过呢工业上不太适用.因为都知道C.C++ 编译器稳定性是一个很难控制的问题,即便最新版的VC也长出些BUG.所以呢,自己写编译器不现实的.当然你可以选择改写GCC,版权是另外一个问题.我随便YY两句。而且还是要从整体回到局部,找出VMP。 弄点宏进去。'Compile ' --> ' 收工'。


Polymorphic 早期的ASM病毒用的太多了。所以还是把位子占给fg..等待膜拜

我感觉z0mbie的这篇文章很科普啊。
雪    币: 750
活跃值: 活跃值 (17)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
海风月影 活跃值 17 2008-10-31 21:57
4
0
终于被爆出来了啊

钟情代码混淆直接学LTT的扭曲变换加密啊
雪    币: 504
活跃值: 活跃值 (19)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
笨笨雄 活跃值 14 2008-11-1 08:45
5
0
多态的精神在于为求目的不择手段,只要结果是一样的,手段可以随便发挥想象力

不过变形技术就不一样了~~~

写一个变形病毒的识别代码,估计跟脱壳的工作量没多少差别~~~

多态和变形的区别在于

多态只能变多,不能变少
变形能变多也能变少

所以多态里面会保留一份最原始的数据
变形多了一步将变形体重新简化抽象的步骤

多态只要找到那份原始数据,就可以直接定特征了
变形则需要把重新简化抽象的代码逆了用来处理变形体才能得到特征

当然现在的人把多态引擎放服务端,原始数据在病毒作者那里,杀软只能病毒作者放一份病毒出来就定一个特征.

从广义上理解,那些用原始方式作免杀的人也是一个多态引擎
雪    币: 5535
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-11-1 09:31
6
0
揭露一下,这篇文章其实是讲无分支代码的
雪    币: 328
活跃值: 活跃值 (29)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
neineit 活跃值 10 2008-11-1 10:40
7
0
恩,fg揭露的好准确啊,不过这个思路还是可以借鉴滴。
笨笨熊觉得Semi-metamorphism的思路如何,偶觉得不错,感染后效果上如同变形,编写上难度要小于变形,当然还有N多未知的情况,只有编写时才知道,有时间的朋友可以尝试。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
金身难破 活跃值 2008-11-2 00:13
8
0
学习借鉴经验............................
雪    币: 310
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
stalker 活跃值 8 2008-11-2 05:35
9
0
前些日子看Intel优化手册,当时也对消除分支产生了兴趣,并写了一段类似代码
雪    币: 235
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:460 )
在线值:
发帖
回帖
粉丝
火影 活跃值 11 2008-11-2 22:24
10
0
不懂哦
游客
登录 | 注册 方可回帖
返回