反静态反汇编技术的原理其实非常简单,主要弄清除反汇编程序的原理,就可以创作出多个ANTI-反汇编的方法。由于这个技术没有固定的技术形势,如何反怎么样反全靠你自己的想象力了。这里就介绍一下原理方面和几个常见的技术。

反汇编软件的原理:
反汇编软件的原理非常简单,读取可执行文件后从PE头中取出代码节的入口点文件偏移。按照X86体系结构的编码规则进行解码。然后将代码的助记符进行输出。类似IDA这样先进的反汇编软件还加入了函数头分析,跳转分析,局部变量分析等功能。其实,最基本的原理都是大学编译原理课程中所提到的词法分析和语法分析的相关知识。这里插入一个题外话,至少我感觉的是任何先进的软件,其中到最后用到的核心技术都是编译原理相关的技术。都出不了- 分析- 模拟 - 解释,这些东西。

随机加密原理:
随机加密原理最初是在多态病毒中使用的技巧,它的反反汇编语言也非常简单,就是将自身的代码进行加密,这样反汇编器反汇编后出现的是一堆乱码。这样的随机加密可以有两层随机一个是算法的随机,一种是算子的随机,算法的随机在专题下面的多态加解密中进行专门的探讨,算子的随机相对简单,只要取一个随机值就好了。而这样加密形式也有一个固定的形势存在。
如一下形式:
--- 解密头 ---
--- 被加密体 ---
--- 解密算子 ---

这样代码的功能部分被保护,保护不到的是解密头。解密头启动后如果节被设置了可写标志则直接执行,如果没有,可以使用VirusProtect这个函数修改内存的属性进行可写的设置。当然也可以开辟一个堆空间将密体写入并解密运行。如果认为一开始在没有任何重定位的情况下不想暴露太多的获取API的函数和功能,也可以将代码写入栈中,这样可以进行两个分段的解密情况,一加密的为代码体本身,另一个就是保护程序自身的功能代码。例如:解密代码和ANTI-DEBUG代码。在栈中有个很方便的事情就是天生支持可读可写可执行,分配空间方便等特点,释放轻松等特点,大有百试不爽的感觉。
此结构类似如下(可以自行发挥想象构建复杂的结构):
--- 解密头 ---
--- 被加密体 ---
--- 功能被加密体 ---
--- 加密体解密算子 ---
--- 功能体解密算子 ---
解密头首先开辟栈,将功能被加密体写入栈后解密,再次跳入执行。功能体解密真正的功能代码。当然可以在其中加入ANTI-DEBUG代码与设定异常等。

加密这种方法简单实用。如果有想些类似程序的朋友不妨常用,而且它反汇编反的很彻底,一个简单的XOR就能让IDA对主体分析失效。

不规则的函数头:
让我们先看一个标准的函数头
push ebp
mov esp, ebp
sub esp, StackSize
代码...
mov esp, ebp
pop ebp
ret

IDA在识别函数的时候也是通过识别这些指令的opcode码来识别函数的。通常在整过编译器编译过后采用[ebp+偏移]来引用参数,而使用[ebp-偏移]来引用局部变量的。
当然写壳这类的东西用汇编是最适合的,如果用高级语言当然一定也要嵌入汇编,总之因为汇编的灵活,我们就来构建自己特殊的函数形势,让IDA分析不到。其他反汇编程序全部忽略。(至今没发现比IDA还健壮的。)

这里给出一些思路吧。因为这方面也没有什么固定的东西,说白了一切看发挥,例如sub esp, StackSize,就可以使用以下函数替换,当然还有清除堆栈的指令 mov ebp, esp; pop ebp也可以替换

代码:
    ;; ----- 构建栈空间-----
    MakeStackLive:
    ;; arg is in eax       
        mov ecx, 04h
        idiv ecx
        movzx eax, ax
        xor ecx, ecx
    MakeStackLiveLoop:
        call MakeStackLiveInsertOne
        MakeStackLiveInsertOne:
        inc ecx
        dec eax
        jnz MakeStackLiveLoop
        mov eax, 04h
        imul ecx
        mov eax, dword ptr [esp+eax]
        push eax
        retn 0
        
    ;; ----- 清除栈空间 -----
    ClearStackLive:
    ;; arg is in eax
        mov ecx, 04h
        idiv ecx
        mov ebx, dword ptr [esp]
        movzx ecx, ax
        pop eax
        ClearStackLiveLoop:
        pop eax
        loop ClearStackLiveLoop
        push ebx
        retn 0  
很简单的代码,eax为堆栈的大小。调用函数后除以4得到以4对齐的个数。然后使用call指令递增堆栈长度。最后返回。清除代码反之使用pop指令递减堆栈长度。
使用:
push ebp
mov ebp, esp
mov eax, StackSize
call MakeStackLive

代码...

mov eax, StackSize
call ClearStackLive
ret

另外对于类似push ebp 这样的指令也可以进行替换,发挥一下想象3.push ebp 可以用什么指令进行替换push eax; mov eax, ebp; mov [esp], eax 同样也是将ebp写入到堆栈中。至于其他的指令替换。大家自己想吧。如果做的有规范点就是构建自己的一套标准。包括找寻局部变量与获取参数。

分段解密反dump:
dump有两个思路一个就是分段的解密。大体的思路和反反汇编的原理一样,不同的是它的算子多了。也是分段的进行加解密的。其实,如果思路再灵活点,在这方面可以准备多一些的加解密算法随机的填写和应用。
另一个是,当每次执行完后,就再次将此函数或者功能块进行加密。在使用时解密。并且准备几个这样的加解密函数。通过这样看似很笨拙的手段,可以很有效的防止实时的dump。

改变执行流程指令:
很多壳代码都有这样的功能,实时的改变代码运行流程。例如将jz变成jnz
代码:
lea edi, JmpLabel
;; 将jz变为jnz则跳转
inc byte ptr [edi]
test eax, eax
JmpLable:
jz Label1

代码...

Label1
jz的opcode为74而jnz的opcode为75。通过opcode进行改变流程的最用。
我们不妨思路再宽阔一些。单个指令流程能进行改变。那么代码块的位置也能进行替换。当然如果要保证代码可以正常运行,在编写代码的时候要经过精心设计后的。尽量使用局部变量,跳转不要使用长跳。跳出当前代码段,这样可以使你不用去编写重定位代码。也更容易实现些。
例如:
代码:
;; 使用串指令进行传输, 如果你觉的这样太容易被人看出来,就用相同的复杂的指令进行替
;; 换。尽量使用麻烦的代码,插入花指令也可以。
lea edi, Func1
lea esi, Func2
mov ecx, Func2Size
cld
rep movsb
代码...
jmp Func1
代码...
Func1:
...
Func2:
...
Func1可以是使用过的代码,也可以是非常长的一段花指令,当然也可以是废弃的指令。

利用异常处理:
WINDWOS提供了一套很完善的异常保护机制。至今我还没看到IDA有可以分析这东西的功能。呵呵关键是语法分析集合太庞大了,只限于人们的想象。估计IDA的设计师们没办法才放弃的。
让我们看一段在破解中常遇到的异常代码。由于专题后门有专门讲解WIN异常处理的,所以这里暂且不去讨论异常处理的原理了。
代码:
;; 安装异常处理
assume fs : nothing
push offset ExpHandle
push fs:[0]
mov     fs:[0],esp
xor ecx, ecx
;; 造成内存访问错误
push dword ptr [ecx]

这里代码将不会执行...

ExpHandle:
异常处理代码...
;; 卸载异常处理
pop fs:[0]
add esp, 04h
其实这样也很明显,可以在安装处理程序之中加些花指令,卸载异常也可以进行一些指令变换。等。

最后的话:
这节没有给出完整的代码,个人认为没有太大的必要。反静态反汇编。其实就是和反汇编软件的词法分析系统做对抗。用数学语言描绘就是,保证集合A(反汇编的分析关键字集合)与集合B(我们编写代码的特征)互为补集。还有觉的没有必要的地方就是,这种东西全靠思路。希望这篇文章对正在学习与之相关的朋友有些启发,我的建议是,看过就看了。忘记此文在上面给出的代码。自己去想。这些和系统底层的知识联系不大,都是最基本的编码过程。只要花些心思就能写的出。你在编写程序的时候难受一些,不方便一些。破解者就会付出比你更加多的精力和时间去破解你的程序。

...