【原创】手脱花指令及IDA脚本编写

admin 2022年3月25日00:31:21评论156 views字数 5540阅读18分28秒阅读模式

作者坛账号:flatcc

综合许多篇帖子而成,大多参考以末尾参考文章形式给出,适用于初学者,感谢大家提出意见。可结合末尾源码参考学习。感觉有用的话感谢大佬们点个赞。

简介

作用

  • 欺骗反汇编器,让反汇编器无法正确反汇编出汇编代码,具体来说是破坏了反编译的分析,使得栈指针在反编译引擎中出现异常

  • 从而加大静态分析的难度,使得逆向分析人员难以识别代码的真正意图

原理

由于反编译器的工作原理一般是线性扫描算法或递归下降反汇编算法。

  • 线性扫描反汇编算法从程序的入口点开始反汇编,然后对整个代码进行扫描,反汇编扫描其过程中所遇到的每条指令。那么线性扫描算法的缺点也就显而易见了,由于其不定常的指令格式,在反汇编扫描过程中无法区分数据与代码,从而导致将代码段中嵌入的数据误解释为指令的操作码,以致最后得到错误的反汇编结果。

  • 递归下降算法,递归下降算法通过程序的控制流来确定反汇编的下一条指令,遇到非控制转移指令时顺序进行反汇编,而遇到控制转移指令时则从转移地址处开始进行反汇编。该算法的缺点在于难于准确确定间接转移的目的地址。

PATCH方法

先在IDA中开启字节码的显示,我这里设置显示的字节码是8。
【原创】手脱花指令及IDA脚本编写


以下图中的花指令为例,我们鼠标光标点到0x004560FF,然后按快捷键D,在下方为指令的地址0x00456100地址处,按快捷键C将其转换为指令。然后将0x004560FF处的0xE8Patch为0x90,也就是打补丁为nop指令。
【原创】手脱花指令及IDA脚本编写


然后保持副本即可。


实现案例

简单花指令-多层JMP嵌套

如下是单层的JMP形式:

 复制代码 隐藏代码
jmp LABEL1
  db junk_code;
LABEL1:

甚至如下的多层嵌套:

 复制代码 隐藏代码
//简单花指令-多层JMP嵌套
void example1()
{
    __asm {
        jmp LABEL1;
        _emit 68h;
    LABEL1:
        jmp LABEL2;
        _emit 0CDh;
        _emit 20h;
    LABEL2:
        jmp LABEL3;
        _emit 0E8h;
    LABEL3:
    }
    a = 99;
}

如下图,因为IDA使用的是递归下降算法进行反汇编,所以这种花指令可以被IDA轻松识别。
【原创】手脱花指令及IDA脚本编写


互补条件代替JMP跳转

类似如下形式,无论如何都会跳转到LABEL1处:

 复制代码 隐藏代码
  jz LABEL1
  jnz LABEL1
  db junk_code
LABEL1:

在如下代码中,先对eax进行xor之后,再进行test比较,zf标志位肯定为1,就肯定执行jz LABEL2;,也就是说中间0xC7永远不会执行。要记得:先压栈保存eax的值,最后再把eax的值pop出来。

 复制代码 隐藏代码
void example2_1()
{
    __asm {
        push eax;
        xor eax, eax;
        test eax, eax;
        jnz  LABEL1;
        jz LABEL2;
    LABEL1:
        _emit 0xC7;
    LABEL2:
        pop eax;
    }
    a = 21;
}

如下图,我们可以看到,IDA虽然识别栈帧错误,但是正确的程序流还是比较清晰的。
【原创】手脱花指令及IDA脚本编写


再如下,我们将中间填充代码改为0x21,混淆效果明显了一些,结果如下。
【原创】手脱花指令及IDA脚本编写


那么我们再来一个加强版,代码如下:

 复制代码 隐藏代码
void example2_3()
{
    __asm {
        xor eax, eax;
        test eax, eax;
        je LABEL1;
        jne LABEL2;
    LABEL2 :
        _emit 0x5e;
        and eax, ebx;
        _emit 0x50;
        xor eax, ebx;
        _emit 0x74;
        add eax, edx;
    LABEL1:
    }
    a = 23;
}

【原创】手脱花指令及IDA脚本编写


call&ret构造花指令

如下案例,代码中的esp存储的就是函数返回地址,对[esp]+8,就是函数的返回地址+8,正好盖过代码中的函数指令和垃圾数据。

 复制代码 隐藏代码
void example3()
{

    __asm {
        call LABEL9;
        _emit 0x83;
    LABEL9:
        add dword ptr ss : [esp], 8;
        ret;
        __emit 0xF3;
    }
    a = 3;
}

如下图所示,是上述花指令代码的效果,这里最好自己动态跟以下。
【原创】手脱花指令及IDA脚本编写


call 指令的直观理解:push 函数返回地址; jmp 立即数
ret 指令的直观理解:pop eip; add esp,4

利用函数返回确定值

有些函数返回值是确定的,比如我们自己写的函数,返回值可以是任意非零整数,就可以自己构造永恒跳转。

还有些API函数也是如此,比如在Win下HMODULE LoadLibraryA(LPCSTR lpLibFileName);函数,如果
我们故意传入一个不存在的模块名称,那么他就会返回一个确定的值NULL,此时就可以通过这个函数来构造永恒跳转。如下例子:

 复制代码 隐藏代码
void example4_1()
{

    LoadLibrary(L"./hhhh");//函数返回值存储于eax中
    __asm{
        cmp eax, 0;
        jc LABEL6_1;
        jnc LABEL6_2;
    LABEL6_1:
        _emit 0xE8;
    LABEL6_2:
    }
    a = 41;
}

混淆效果如下:
【原创】手脱花指令及IDA脚本编写


call和ret的组合

如下代码:

 复制代码 隐藏代码
void __declspec(naked)__cdecl example5(int* a)//裸函数,开辟和释放堆栈由我们自己写。
{//55 8b ec 83
    __asm
    {
        push ebp
        mov ebp, esp
        sub esp, 0x40
        push ebx
        push esi
        push edi
        mov eax, 0xCCCCCCCC
        mov ecx, 0x10
        lea edi, dword ptr ds : [ebp - 0x40]
        rep stos dword ptr es : [edi]
    }

    *a = 5;
    __asm
    {
         call LABEL9;
         _emit 0xE8;
         _emit 0x01;
         _emit 0x00;
         _emit 0x00;
         _emit 0x00;

     LABEL9:
         push eax;
         push ebx;
         lea  eax, dword ptr ds : [ebp - 0x0]; //将ebp的地址存放于eax
         add dword ptr ss : [eax - 0x50], 26;  //该地址存放的值正好是函数返回值,
                                              //不过该地址并不固定,根据调试所得。加26正好可以跳到下面的mov指令,该值也是调试计算所得
         pop eax;
         pop ebx;
         pop eax;
         jmp eax;
         __emit 0xE8;
         _emit 0x03;
         _emit 0x00;
         _emit 0x00;
         _emit 0x00;
         mov eax, dword ptr ss : [esp - 8];  //将原本的eax值返回eax寄存器

    }

    __asm
    {
        pop edi
        pop esi
        pop ebx
        mov esp, ebp
        pop ebp
        ret
    }
}

【原创】手脱花指令及IDA脚本编写


也就是说思路有很多种,按照自己喜欢的方式组合,只要不影响其他正常代码的运行就可以,如下也是比较好的两种思路

call嵌套的其他思路1

 复制代码 隐藏代码
    call LABEL1
    db 0E8h
LABEL2:
    jmp LABEL3
    db 0
    db 0
    db 0E8h
    db 0F6h
    db 0FFh
    db OFFh
    db OFFh
LABEL1:
    :call LABEL2
LABEL3:
    add esp,8

call嵌套的其他思路2

 复制代码 隐藏代码
    push eax
    call LABEL1
    db 29h
    db 5Ah
LABEL1:
    POP eax
    imul eax,3
    call LABEL2
    db 29h
    db5Ah
LABEL2:
    add esp,4
    pop eax

花指令原理另类利用

当我们理解了花指令的原理后,我们可以在将花指令中的垃圾数据替换为一些特定的特征码,可以对应的$“定位功能”$,尤其在SMC自解码这个反调试技术中可以运用。例如:

 复制代码 隐藏代码
asm
{
  Jz Label
  Jnz Label
  _emit 'h'
  _emit 'E'
  _emit 'l'
  _emit 'L'
  _emit 'e'
  _emit 'w'
  _emit 'o'
  _emit 'R'
  _emit 'l'
  _emit 'D'
Label:
}

将这串特征码hElLowoRlD嵌入到代码中,那我们只需要在当前进程中搜索hElLowoRlD字符串,就可以定位到当前代码位置,然后对下面的代码进行SMC自解密。

小结

构造永恒跳转,添加垃圾数据

综合题目案例

IDC脚本去花

我们以题目[MSLRH].exe为例子,分析一段花指令,使用IDA中的快捷键DC来手动过花指令。手动分析清楚其流程后,我们可以写一个脚本,来批量匹配花指令的模式NOP掉影响静态分析的代码。
【原创】手脱花指令及IDA脚本编写


写好的IDC脚本如下:

 复制代码 隐藏代码
//文件名:test.idc
#include <idc.idc>
static main()
{
    auto x,FBin,ProcRange;
    FBin = "E8 0A 00 00 00 E8 EB 0C 00 00 E8 F6 FF FF FF";
    //目标 = "E8 0A tel:00 00 00 90 EB 0C tel:90 90 90 90 90 90 90";
    //花指令1的特征码
    for (x = FindBinary(MinEA(),0x03,FBin);x != BADADDR;x = FindBinary(x,0x03,FBin))
    {
            x=x+5; //返回的x是第一个E8的地址,
                  //加上5是第二个E8的地址
            PatchByte (x,0x90);//nop掉
            x = x + 3; //00
            PatchByte (x,0x90);
            x++;  //00 E8
            PatchWord (x,0x9090);
            x =x + 2 ; //F6 FF FF FF
            PatchDword(x,0x90909090);
    }
}

使用IDAPython去花

 复制代码 隐藏代码
import idautils
import idc

def my_nop(addr, endaddr):
    while addr < endaddr:
        patch_byte(addr, 0x90)
        addr += 1

pattern = "E8 0A 00 00 00 E8 EB 0C 00 00 E8 F6 FF FF FF"
cur_addr = 0x456000
end_addr = 0x467894

while cur_addr<end_addr:
    cur_addr = idc.find_binary(cur_addr,SEARCH_DOWN,pattern)
    print("patch address: " + str(cur_addr)) # 打印提示信息
    if cur_addr == idc.BADADDR:
        break
    else:
        my_nop(cur_addr+5,cur_addr+6)
        my_nop(cur_addr+8,cur_addr+14)
    cur_addr = idc.next_head(cur_addr)

附带源码:
【原创】手脱花指令及IDA脚本编写 JunkCode.7z (5.11 KB, 论坛原文下载)


参考

  • 反汇编基础:https://tinytracer.com/archives/反汇编基础/

  • 自动获取驱动程序IO控制码初级版:https://bbs.pediy.com/thread-153965.htm

  • 花指令总结:https://www.anquanke.com/post/id/236490

  • 反编译系列教程(上):http://drops.xmd5.com/static/drops/papers-13686.html

  • _emit伪指令:https://docs.microsoft.com/zh-cn/cpp/assembler/inline/emit-pseudoinstruction?view=msvc-160

  • IDAPython官方文档:https://hex-rays.com/products/ida/support/idapython_docs/

  • IDAPython脚本示例:https://www.cnblogs.com/shenshuoyaoyouguang/p/13841078.html

  • IDAPython动态调试开发技巧:https://www.bbsmax.com/A/x9J27Lgg56/


--

www.52pojie.cn


--

pojie_52

本文始发于微信公众号(吾爱破解论坛):【原创】手脱花指令及IDA脚本编写

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月25日00:31:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【原创】手脱花指令及IDA脚本编写https://cn-sec.com/archives/542854.html

发表评论

匿名网友 填写信息