什么是花枝令

flower是花指令,花指令是什么?其实就是插入垃圾指令,我好不容易写出的程序怎能让你这么轻易的逆出来。所以我们要通过修改汇编去破除花指令。

我们不妨想一下哪里能插入垃圾指令。你看jmp跳转以及call指令等等地方都是不二之选。
在了解花指令之前,我们先要学习ida的反汇编机制。
1 线性扫描算法,线性一条流水线上的,想想c语言的线性顺序,类比一下这就是从头到尾扫描,都解释为指令,这就使其如果遇到数据也可能错误反汇编,在反汇编代码中插入数据,反汇编就会失败。
2 递归下降算法,其根据可能执行的逻辑进行反汇编,一般是在遇到分支跳转和call指令时发挥作用,jmp指令其就不会看jmp与跳转到的地址中间的汇编代码,而例如jz等条件跳转其就会分析两条路径(跳转与不跳转)。

在介绍内容前,先看一个简单的加花的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<windows.h>
int main(int arg, char * argv[])
{
int a = MessageBoxW(NULL, L"喵了个喵", L"卡拉比丘", MB_OK);
int b;
__asm__("NOP");
//AT&T 语法
__asm__("jmp label1\n\t"
"NOP\n\t"
"label1:"
);
//Intel语法
__asm__(
".intel_syntax nonprefix"
"jmp 1f\n" //数字标签f表示forward地址增大,b表示behind地址减小
".byte 0x12"
"1:"
);
b= 1;
return 0;
}
/*__asm__ volatile (
"汇编指令\n\t"
"汇编指令\n\t"
: 输出操作数列表 //可选
: 输入操作数列表 //可选
: 被破坏的寄存器列表 //可选
);*/


一下汇编为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

.text:0000000140001440 push rbp
.text:0000000140001441 mov rbp, rsp
.text:0000000140001444 sub rsp, 30h
.text:0000000140001448 mov [rbp+arg], ecx
.text:000000014000144B mov [rbp+argv], rdx
.text:000000014000144F call __main
.text:0000000140001454 lea rdx, Caption
.text:000000014000145B lea rax, Text
.text:0000000140001462 mov r9d, 0 ; uType
.text:0000000140001468 mov r8, rdx ; lpCaption
.text:000000014000146B mov rdx, rax ; lpText
.text:000000014000146E mov ecx, 0 ; hWnd
.text:0000000140001473 mov rax, cs:__imp_MessageBoxW
.text:000000014000147A call rax ; __imp_MessageBoxW
.text:000000014000147C mov [rbp+a], eax
.text:000000014000147F nop
.text:0000000140001480 jmp short label1
.text:0000000140001480 ; ---------------------------------------------------------------------------
.text:0000000140001482 db 90h
.text:0000000140001483 ; ---------------------------------------------------------------------------
.text:0000000140001483
.text:0000000140001483 label1: ; CODE XREF: main+40↑j
.text:0000000140001483 mov [rbp+b], 1
.text:000000014000148A mov eax, 0
.text:000000014000148F add rsp, 30h
.text:0000000140001493 pop rbp
.text:0000000140001494 retn
.text:0000000140001494 main endp
.text:0000000140001494

这就是在跳转前与后添加垃圾数据,通用的手法

讲一点前置的栈小知识

栈(stack)是自顶而下的,也就是栈顶是地址,所以压栈(push)是减去一个值,出栈是加上一个值

其遵守的是后进先出的原则

修花指令是基于栈上操作的

以下为栈图

1
2
3
4
5
6
7
8
9
10
11
12
内存高地址
┌─────────────┐
│ 0x7FFF F000 │ 数据1 ← 栈底(最先入栈)
├─────────────┤
│ 0x7FFF EFFC │ 数据2 (第2个入栈)
├─────────────┤
│ 0x7FFF EFF8 │ 数据3 (第3个入栈)
├─────────────┤
│ 0x7FFF EFF4 │ 数据4 (第4个入栈)
├─────────────┤
│ 0x7FFF EFF0 │ 数据5 ← 栈顶(最后入栈)
└─────────────┘

汇编

特此声明x86汇编有很多,下面仅是部分

mov eax, immidiate(将立即数存入eax寄存器)

1
mov eax, 2

push eax 将eax(32位)寄存器压栈

pop eax(将栈顶存的出栈存入eax)

1.无关花枝令

add eax,ebx(eax = eax + ebx)

sub eax, ebx (eax = eax - ebx)

mul+操作数()

nop (类似于python中的pass,空指令)

2.不定长度的指令

jmp + 标签(跳转到标签处)

1
2
3
4
    jmp skip        ; 跳转到 skip 标签处
mov eax, 2 ; 这行不会执行,被跳过
skip:
mov ebx, 3 ; 从这里继续执行

3.分支指令

xor eax,eax(eax = eax ^eax,异或是相同为0,不同为1,所以结果为0)

cmp 操作数1, 操作数2(仅影响标志位,不影响操作数,将ZF标志位相等置为1,不等置为0)

je local_3(当ZF为1,既是上面指令相等时跳转)

6.堆栈不平衡(常见于call + ret)

call指令,相当于push+jmp,先将返回地址压栈之后跳转

ret指令,相当于pop+jmp,先将返回地址出栈之后跳转

常见花指令

1 插入无关指令

例如源代码

1
2
add eax, ebx
mul ecx //无符号乘法:EDX:EAX = EAX × ECX(高32位存edx,低32位存eax)

可以插入与程序无关的指令

1
2
3
4
5

add eax, ebx
mul ecx
xor esi,ecx
add eax, 999(狼尊大人)

2.不定长度的指令

1
2
3
4
5
6
text:0000000140001480 EB 01                       jmp     short label1
.text:0000000140001480 ; ---------------------------------------------------------------------------
.text:0000000140001482 90 db 90h
.text:0000000140001483 ; ---------------------------------------------------------------------------
.text:0000000140001483
.text:0000000140001483 label1: ; CODE XREF: main+40↑j

EB 00 00 00 00 00就相当于jmp下一条指令(当前地址(:0000000140001480)+2),而EB 01相当于(01 = 目标地址-当前地址-该指令的长度)

jmp 为基址+指令长度+偏移量

1
2
3
0000 E9 01 00 00 00 jmp 0006
0005 0xE9 ...
0006 0x50 push eax0007

由于线性扫描算法可能将0xE9误认为jmp,nop掉就行了

3.分支指令

由于递归下降算法,je、jne等跳转要接着往下面跳转,这样就可以在跳转指令与target地址间插入junkcode,这类解法是将无关数据nop掉

1
2
3
4
5
6
7
xor eax,eax(自身异或一定为0)
cmp eax , 0
je local_3
junk code(导致反汇编失败)
local_3:


4.间接跳转

地址 | 指令

1
2
3
4
5
6
7
04009A9 call $+5 ; use '$' to get current address.'call' here is used as 'push'
04009AE pop rax ; pop current address from stack to
rax04009AF add rax,10
04009B1 jmp rax ; jump to 04009B8
04009B3 ;---
04009B5 db 0EBh,0EBh,0EBh
04009B8 ;---

call $+5中的$表示当前地址,call会将下一个指令的地址压栈,pop rax将地址出栈存入rax

add rax,10加法指令相当于rax(04009B8) = rax(04009AE)+10,jmp rax

由于汇编时是静态的没有运行,无法解析出rax存的值,导致db 0EBh,0EBh,0EBh被错误认为为指令

解决办法从04009B8到04009B5全nop掉

5.指令复用

1
EB FF jmp short near ptr loc_4009E0+1 ; 

这里的0xFF相当于-1(补码,有符号正数)

解决办法就是将eb nop掉

6.堆栈不平衡(常见于call + ret)

1
2
3
4
5
6
xor eax,eax
jz s
add esp,0x11 ; computer does not exec this code!
ret
s:
; normal code

call相当于push + jmp,ret相当于pop+jmp

该段代码相当于jmp s,正常来说两个分支栈都要平衡但是

add 0x11,esp ; computer does not exec this code!
ret分支显然栈不平衡,会报错

上面不平衡的代码未执行,接着这个执行了的

1
2
3
4
5
6
7
call next
next:
movl continue,esp
ret
continue:
;normal code

这相当于jmp continue:但是movl continue,esp修改了栈顶指针的指向的栈位置,ret是将continue弹出栈,但是

next仍存在栈中,这就导致栈不平衡报错

改正方法是将

call next
next:
movl continue,esp
ret

这段代码nop掉

Haruhikage

悴んだ心 ふるえる眼差し

世界で 僕は ひとりぼっちだった

散ることしか知らない春は

毎年 冷たくあしらう

暗がりの中 一方通行に

ただ ただ 言葉を書き殴って

期待するだけ むなしいと分かっていても

救いを求め続けた

(せつなくて いとおしい)

今ならば 分かる気がする

(しあわせで くるおしい)

あの日泣けなかった僕を

光は やさしく連れ立つよ

雲間をぬって きらりきらり

心満たしては 溢れ

いつしか頬を きらりきらり

熱く 熱く濡らしてゆく

君の手は どうしてこんなにも温かいの?

ねぇお願い

どうかこのまま 離さないでいて

縁を結んでは ほどきほどかれ

誰しもがそれを喜び 悲しみながら

愛を数えてゆく

鼓動を確かめるように

(うれしくて さびしくて)

今だから 分かる気がした

(たいせつで こわくって)

あの日泣けなかった僕を

光は やさしく抱きしめた

照らされた世界 咲き誇る大切な人

あたたかさを知った春は

僕のため 君のための 涙を流すよ

あぁ なんて眩しいんだろう

あぁ なんて美しいんだろう…

雲間をぬって きらりきらり

心満たしては 溢れ

いつしか頬を きらりきらり

熱く 熱く濡らしてゆく

君の手は どうしてこんなにも温かいの?

ねぇお願い

どうかこのまま 離さないでいて

ずっと ずっと 離さないでいて [10