Flower Directive and Anti-Confusion
1. Flower instruction
The flower instruction is a basic method of anti-debugging. Its presence interferes with the static analysis of the player, but does not affect the operation of the program. It is essentially a string of garbage instructions that has nothing to do with the function of the program itself and does not affect the logic of the program itself. In software protection, the flower instruction is used as a means to increase the difficulty of static analysis. ida does not recognize the flower instruction properly, resulting in the destruction of viewable and analyzable code, so we need to analyze it ourselves. Flower instructions are mainly divided into two categories: executable flower instructions and non-executable flower instructions.
- Executable flower instructions: flower instructions are executed while the program is running normally, but do not affect the normal operation of the program.
- Non-executable flower instructions: flower instructions are not executed when the program is running normally.
Commonly obfuscated bytecode:
machine code | assembly language |
---|---|
9A | CALL immed32 |
E8 | CALL immed16 |
E9 | JMP immed16 |
EB | JMP immed8 |
2. Common flower instruction analysis
(1) Single byte
#include <>
int main()
{
__asm {
jz start; //jz and jnz at the same time, resulting in an eternal jump.
jnz start;
_emit 0xE8; //this is where the flower instruction CALL + address was intentionally inserted
}
start.
printf("ok!");
return 0; }
}
Example: [NSSRound#3 Team]jump_by_jump
The main function won't compile. Look down and see the flower instruction.
To remove the flower command, press the D shortcut. Hard-code the call first, E8.
Put the cursor on db 0E8 again, change E8 to nop(90), press C again and hit yes to fix the hardcoding to code.
Then, repair downward one by one, placing the cursor over the column and pressing C until there are no more columns in color.
Place the cursor at the beginning of the function and press the P key to create a function and tab to pseudo-code.
(2) Eternal Jump
int main()
{
__asm {
xor eax, eax; // eax ^ eax = 0
jz s; // must set up a jump, will definitely jump
_emit 0x11; // fill garbage instruction byte type
_emit 0x22; // fill garbage instruction byte type
_emit 0x33; // fill garbage instruction byte type
s.
}
printf("test \n");
}
(3) Changing the ESP
int main()
{
__asm {
xor eax, eax.
jz s.
add esp, 0x11; // IDA will recognize this instruction as an operation on the function stack, resulting in a failure to identify the function
s.
}
printf("test \n"); }
}
(4) jmp jumps to insert invalid garbage instructions
#define _CRT_SECURE_NO_WARNINGS
#include <>
int main()
{
_asm {
jmp $+5
_emit 0x71
_emit 2
_emit 0xE9
_emit 0xED
}
lable:
printf("ok2");
return 0;
}
(5) Nested Eternal Jump
#define _CRT_SECURE_NO_WARNINGS
#include <>
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;
}
3. Anti-Confusion
Method 1: Manual recovery
Applicable conditions: little confusion and single type
In order to unlock the topic as quickly as possible, we generally choose to remove the obfuscation manually first to get the flag quickly
Here you need to master the basic IDA shortcuts: U, C, P
- U: In IDA Pro, press "U" to redefine assembly to bytecode format.
- C: Press "C" in IDA Pro to convert bytecode to assembly form.
- P: In IDA Pro, press "P" to convert assembly language to high-level language function view.
Method 2: IDA-Python Script Recovery
Applicable conditions: confusing large quantities, basically unremovable by hand
Need to get the obfuscated bytecode composition and utilize scripts to remove a lot of the obfuscation.
Example: [GFCTF 2021] wordy
A large number of flower commands with machine code EBFF appear, which would be costly to nop manually, so here's the idapython
import idc
import ida_bytes
start_add=0x1144
end_add=0x3100
for address in range(start_add, end_add):
new_byte = ida_bytes.get_byte(address)
next = ida_bytes.get_byte(address + 1)
nnext = ida_bytes.get_byte(address + 2)
if new_byte == 0xeb and next == 0xff and nnext == 0xc0:
ida_bytes.patch_byte(address, 0x90)
First the loop iterates over the data from the0x1144
until (a time)0x3100
at the address of the current byte. At each address, check whether the current byte is the0xeb
If the next byte is0xff
If the next byte is0xc0
. If the match is to0xeb 0xff 0xc0
This byte sequence then takes the byte at the current address0xeb
modify to0x90
。0x90
In assembly language, this is the NOP instruction, which means "no operation", i.e., this instruction has no effect on program execution.