// blog / 逆向笔记

花指令

花指令基础

花指令是一种反静态调试的最基础手段,其存在是干扰选手静态分析,但不会影响程序的运行。

实质就是一串垃圾指令,它与程序本身的功能无关,并不影响程序本身的逻辑运行。在软件保护中,花指令被作为一种手段来增加静态分析的难度。

IDA并不能正常识别花指令,导致可看可分析代码被破坏,因此需要我们自己去分析一下。

**花指令主要分为两类:

  • 可执行花指令
  • 不可执行花指令。

汇编基础

需要有一些x86汇编基础,推荐书籍王爽《汇编语言》** 常见的汇编指令:

mov eax, 1     eax赋值为1 
pop 1          将1从栈顶弹出 
pop ebp        将栈顶的值弹出赋给寄存器ebp 
push 1         将1压入栈中 
push ebp       将ebp的值压入栈中 
sub eax,1      eax的值减1 
add eax, 1     eax的值加1 
inc eax        eax的值加1 
dec eax        eax的值减1 
call [x]       调用地址为x的函数,call对应的硬编码为0xE8 
jmp x          跳转到x地址处,jmp对应的硬编码为0xE9 
nop            不做任何事情,对应的硬编码为0x90 
_emit          相当于db(byte类型,1字节)

常见混淆的字节码:

机器码汇编语言
9ACALL immed32
E8CALL immed16
E9JMP immed16
EBJMP immed8

常见花指令形式

1-单字节

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    _asm {
        jz start;    //jz 和 jnz 同时出现,导致永恒跳转
        jnz start;   
        _emit 0xE8;  //这个地方就是故意插入的花指令 CALL + 地址
    }                //程序正常运行会直接跳到start,但IDA线性扫描会误判下方是call指令,自动将后面的字节码识别为地址
start:
    printf("ok!");
    return 0;
}

2-永恒跳转

int main()
{
    _asm {
    xor eax, eax;   // eax ^ eax = 0 
    jz s;           // 必然成立跳转,一定会跳转
    _emit 0x11;     //填充垃圾指令 byte类型
    _emit 0x22;     //填充垃圾指令 byte类型
    _emit 0x33;     //填充垃圾指令 byte类型
    s:
    }
	printf("test \n");
}

3-更改ESP

int main()
{
    _asm {
        xor eax, eax;   
        jz s;           //必定跳转s
        add esp, 0x11; // IDA 会把此指令识别对函数堆栈进行操作,导致识别函数失败
    s:
    }
    printf("test \n");
}

4-jmp跳转+插入无效垃圾指令

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    _asm {
    
        jmp $+5     //$表示当前位置,+5即跳过5个字节码,到lable处了
        _emit 0x71
        _emit 2
        _emit 0xE9
        _emit 0xED
   }
 lable:
    printf("ok2");

    return 0;
}

5-永恒跳转嵌套(需要一层一层恢复)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    _asm {
        jz Label3;
        jnz Label3;
        _emit 0xE8;
    }
Label2:
    _asm {
        jz Label4;
        jnz Label4;
        _emit 0xE8;
    }


Label3:
    _asm {
        jz Label1;
        jnz Label1;
        _emit 0xE9;
    }
Label1:
    _asm {
        jz Label2;
        jnz Label2;
        _emit 0xE9;
    }
Label4:
    printf("ok2");

    return 0;
}

反混淆基础

方法一:手动恢复

适用条件:混淆少且类型单一 为了尽快解除题目,我们一般选择先手动去除混淆,迅速拿到flag 这里需要掌握IDA的基本快捷键:U、C、P

  1. U: 在IDA Pro中,按“U”重新定义汇编,转换为字节码格式
  2. C: 在IDA Pro中,按“C”,转换字节码为汇编形式
  3. P: 在IDA Pro中,按“P” 转换汇编语言为高级语言函数视图

方法二:IDA-Python脚本恢复

适用条件:混淆大量,手工基本不可去除

需要拿到混淆的字节码组成,利用脚本去除大量混淆。 需要根据实际地址和匹配模式来进行更改

import idc
start_add = 0x400800
end_add = 0x406e22
for i in range(start_add,end_add):
    if idc.get_wide_dword(i) == 0x08750a74: #小端序,匹配花指令
        for mm in range(9):
            ida_bytes.patch_byte(i+mm,0x90)

例子

re_hua1

IDA打开 435 双击main_0 444

看到标红,且无法生成伪代码。 jz,jnz永恒跳转 所以在E8(call)处,按“u”,然后右键用nop填充E8 293

再在nop处按“c”,再在main_0开头处按p即可恢复函数

// comments

评论

加载中…

正在读取评论…

// related

相关文章