内容纲要

花指令的产生

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

2.

        jz label1
        jnz label1
        db 0EBh
        db2
label1:
        jmp label2
        db 81h
label2:

3.

        push eax
        call label1
        db 29h
        db 5Ah
label1:
        POP eax
        imul eax,3
        call label2
        db 29h
        db 5Ah
label2:
        add esp,4
        pop eax

4.

        jmp label1
        db 68h
label1: 
        jmp label2
        db 0CDh,20h
label2:
        jmp label3
        db 0E8h
label3:

_asm

__asm {
    _emit 075h    #jmp $+4
    _emit 2h
    _emit 0E9h
    _emit 0EDh
}

例子

#include <stdio.h>
#include <stdint.h>
#define JUNKCODE __asm{
    __asm jmp junk1 
    __asm __emit 0x12 
    __asm junk2: 
    __asm ret 
    __asm __emit 0x34 
    __asm junk1: 
    __asm call junk2  
}

void encrypt(uint32_t* v, uint32_t* k) {
    uint32_t v0 = v[0], v1 = v[1], sum = 0, i;           /* set up */
    uint32_t delta = 0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];   /* cache key */
    for (i = 0; i < 32; i++) {                       /* basic cycle start */
        JUNKCODE        ////////////2
        sum += delta;
        v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    }                                              /* end cycle */
    v[0] = v0; v[1] = v1;
}
int main() {
    int a = 1;
    uint32_t flag[] = { 1234,5678 };
    uint32_t key[] = { 9,9,9,9 };
    __asm {         ///////////////////1
        _emit 075h
        _emit 2h
        _emit 0E9h
        _emit 0EDh
    }
    encrypt(flag, key);
    printf("%d,%d", flag[0], flag[1]);
    return 0;
}
//设置了两个花指令,一个在main函数(1),一个加在了encrypt函数(2)

idapython脚本

def nop(addr, endaddr):
    while addr < endaddr:
        PatchByte(addr, 0x90)
        addr += 1

def undefine(addr, endaddr):
    while addr < endaddr:
        MakeUnkn(addr, 0)
        addr += 1

def dejunkcode(addr, endaddr):
    while addr < endaddr:
        MakeCode(addr)
        # 匹配模板
        if GetMnem(addr) == 'jmp' and GetOperandValue(addr, 0) == addr + 5 and Byte(addr+2) == 0x12:
            next = addr + 10
            nop(addr, next)
            addr = next
            continue
        addr += ItemSize(addr)

dejunkcode(0x00411820, 0x00411957)
undefine(0x00411820, 0x00411957)
MakeFunction(0x00411820, -1)

函数用法解析

MakeCode(ea)            #分析代码区,相当于ida快捷键C
ItemSize(ea)            #获取指令或数据长度
GetMnem(ea)             #得到addr地址的操作码
GetOperandValue(ea,n)   #返回指令的操作数的被解析过的值
PatchByte(ea, value)    #修改程序字节
Byte(ea)                #将地址解释为Byte
MakeUnkn(ea,0)          #MakeCode的反过程,相当于ida快捷键U
MakeFunction(ea,end)    #将有begin到end的指令转换成一个函数。如果end被指定为BADADDR(-1),IDA会尝试通过定位函数的返回指令,来自动确定该函数的结束地址

idapython详细函数用法

特殊花指令

push    0x26
xor dword ptr ss:[esp], 0x34
相当于:
push    0x12

可能出现的花指令

因为是ring0指令,所以出现的话一般是花指令

IN  (INS, INSB, INSW, INSD)
OUT (OUTS, OUTSB, OUTSW, OUTSD)
IRET
IRETD
ARPL
ICEBP / INT 1
CLI
STI
HLT

ring3指令,虽有效但少见,分为便利指令,不太可能的数学指令和远指针指令

便利指令

ENTER    
#现代编译器倾向于避免使用ENTER和LEAVE,因此大多数程序员也不会使用它们。
LEAVE
LOOP (LOOPE/LOOPZ, LOOPNE/LOOPNZ)  
#编译器通常选择不使用这些,而是使用JMP和条件跳转指令来制作自己的循环。
PUSHA
POPA

不太可能的数学指令

#浮点指令
F*  
#浮点指令通常以字母“F”开始。虽然有些程序使用浮点数学,但大部分程序都不使用浮点数学。
WAIT/FWAIT
SAHF
LAHF    
#SAHF和LAHF指令将AH寄存器的内容复制到标志寄存器EFLAGS中。这是一种编程行为,不会从高级语言转换下来,因此编译器通常不会输出这些指令。

#ASCII调整指令
AAA
AAS
AAM
AAD
#“AA”系列指令涉及以二进制编码的十进制形式处理数据。这是一个比较早的编码方式,基本很难再现代计算中遇到。可能经常在反汇编花指令中遇到这些指令,因为它们是单字节指令。
SBB     #SBB指令不少于九个操作码,占可能范围的3.5%。它们不是单字节指令,但是有很多形式和很多操作码
XLAT
#单字节指令
CLC
STC
CLD
STD
#这四个指令清除并设置进位和目标标志。单字节指令

远指针指令

LDS
LSS
LES
LFS
LGS
#置远指针的指令仍然占用两个单字节操作码和两个字节操作码范围中的三个值

其他

AX,BX,CX,DX,SP,BP等,而不是EAX,EBX,ECX,EDX,ESP和EBP
LOCK
BOUND
WAIT
段选择器
FS
GS
SS
ES

去花方法

idapython
idapython函数用法
idc(调用自己封装好的api)
idc函数用法

复现

几次做题,比较常见的:0xEB(jmp),0x0FF

不知道为什么vscode写不了加花的,最后用vs2019写了加花的程序

#include<stdio.h>
#include<windows.h>
int a = 1;
int main()
{
    printf("hello!");
    __asm {
        push eax;
        xor eax, eax;
        test eax, eax;
        jnz  LABEL1;
        jz LABEL2;
    LABEL1:
        _emit 0xE8;
    LABEL2:
        mov byte ptr[a], 0;
        call LABEL3;
        _emit 0xFF;
    LABEL3:
        add dword ptr ss : [esp] , 8;
        ret;
        __emit 0x11;
        mov byte ptr[a], 2;
        pop eax;
    }
    printf("world");
    return 0;
}

image-20210520005014623

image-20210520011847850

这里经测试,无论如何,都无法堆栈平衡,只能整段patch

image-20210520012006905

学习链接:
识别和避免反汇编中遇到的花指令
花指令的产生
解花指令的一些方法(1)
解花指令的一些方法(2)
汇编指令对应码