190319 逆向-花指令

以前也接触过简单的花指令,基本上就是jz/jnz式的固定跳转
前几天的某比赛中出现了一个相对而言比较复杂的花指令,参考pizza的笔记开始一阵学习XD

前言

花指令指的是没有卵用,会干扰代码阅读甚至反编译,却不影响程序功能的代码。
广义上来说OLLVM、VMP一类的代码改变型混淆也属于花指令,本文所指的是指会干扰反汇编、影响机器码解析但不影响正常机器码的字节。

原理

产生花指令的根本原因是x86指令集由不定长指令构成
当通过跳转使执行流命中到另一条指令的中间时就会造成静态反汇编的解析错误

现代反汇编器有两种思路:

  1. 线性扫描
    从开头到结尾依次读取机器码并进行反汇编
  2. 递归下降
    从程序入口向后反汇编,遇到条件跳转则分别从分支的地方继续反汇编,无条件跳转则尝试从目的指令继续反汇编

线性扫描显然很容易制造花指令,只要在跳转和目标之间插入长度较长的指令开头(例如E8,后接4个字节)即可使之后的所有指令解析错误。OllyDbg和windbg就是使用的线性扫描法

而递归下降则可以避开这种简单的花指令,跳过中间的脏字节。但对于一些针对性的花指令–例如jz+jnz+脏字节,则会由于上下文无关的算法而对脏字节进行解析

除此以外还有把call当jmp使用的手段,由于在编译器中call只会用来作为子程序/函数跳转的指令,因此IDA往往会将call的地址视作一个函数的起始地址,进而破坏整个函数的完整性

分类

jx+jnx

最常见的一种花指令
190319 逆向-花指令_第1张图片
如前所述,jnz后是“可能的分支”,会破坏后续指令的解析

call+pop/add esp/add [esp] + retn

190319 逆向-花指令_第2张图片
call指令可以理解为jmp + push ip
因此如果通过add esp,4来降低栈顶即可去除push ip的影响,从而使call等价于jmp
但IDA会认为这是函数的分界,从而导致函数的范围识别错误

stx/jx

在这里插入图片描述

CLC [Clear Carry Flag] 清除EFlags寄存器的Carry标志位
jnb [jump if cf=0] 如果Carry标志位为0则跳转
因此结合起来也相当于jmp,而单纯的IDA无法联系上下文也认为后一个字节是可能的分支

去花

通过IDA脚本或者编程操作二进制都可
由于花指令有固定的格式,因此对其使用串进行匹配并将其NOP掉即可
例如

#include 


static matchBytes(StartAddr, Match) 
{ 
auto Len, i, PatSub, SrcSub; 
Len = strlen(Match);

while (i < Len) 
{ 
   PatSub = substr(Match, i, i+1); 
   SrcSub = form("%02X", Byte(StartAddr)); 
   SrcSub = substr(SrcSub, i % 2, (i % 2) + 1); 
   
   if (PatSub != "?" && PatSub != SrcSub) 
   { 
    return 0; 
   } 
   
   if (i % 2 == 1) 
   { 
    StartAddr++; 
   } 
   i++; 
}

return 1; 
}


static main() 
{ 
   auto Addr, Start, End, Condition, junk_len, i;

Start = 0x740; 
End = 0x7f3;
Condition = "740A7508E810000000EB04E8";
junk_len = 12;

for (Addr = Start; Addr < End; Addr++) 
{ 
   if (matchBytes(Addr, Condition)) 
   { 
    for (i = 0; i < junk_len; i++) 
    {
     PatchByte(Addr, 0x90); 
     MakeCode(Addr); 
     Addr++; 
    } 
   } 
   
}

AnalyzeArea(Start, End); 
Message("Clear Fake-Jmp Opcode Ok "); 
} 

另外根据实际花指令而言,可能还需要依照花指令后的长度来控制NOP的范围,这里就不放脚本了 实现起来应该不难~

你可能感兴趣的:(CTF,CrackMe,逆向和保护)