花指令

花指令是企图隐藏掉不想被逆向工程的代码块(或其它功能)的一种方法。

重点:构造永恒跳转,添加垃圾数据!

文本字符串

某字符串不想被逆向工程的时候,可能会试图隐藏掉该字符串,让IDA或者其他十六进制编辑器无法找到

下面介绍怎么去构造这样的字符串的实现方式:

mov byte ptr [ebx], ’h’
mov byte ptr [ebx+1], ’e’
mov byte ptr [ebx+2], ’l’
mov byte ptr [ebx+3], ’l’
mov byte ptr [ebx+4], ’o’
mov byte ptr [ebx+5], ’ ’
mov byte ptr [ebx+6], ’w’
mov byte ptr [ebx+7], ’o’
mov byte ptr [ebx+8], ’r’
mov byte ptr [ebx+9], ’l’
mov byte ptr [ebx+10], ’d’

当两个字符串进行比较的时候看起来是这样:

mov ebx, offset username
cmp byte ptr [ebx], ’j’
jnz fail
cmp byte ptr [ebx+1], ’o’
jnz fail
cmp byte ptr [ebx+2], ’h’
jnz fail
cmp byte ptr [ebx+3], ’n’
jnz fail
jz it_is_john

这种方法使得字符串不可能被分配到程序的代码段中。在某些场合可能会用到。

sprintf(buf, "%s%c%s%c%s", "hel",’l’,"o w",’o’,"rld");

代码看起来比较怪异,但是做为一个简单的防止逆向工程确实一个有用的方法。文本字符串也可能存在于加密的形式,那么所有字符串在使用前比较闲将字符串解密了。

可执行代码

在程序可执行的情况下,增加一些操作,以便让IDA分辨不出

插入垃圾

可执行代码花指令的意思是在真实的代码中插入一些垃圾代码,但是保证原有程序的执行正确。

例1:花指令

xor esi, 011223344h ; garbage
add esi, eax ; garbage
add eax, ebx
mov edx, eax ; garbage
shl edx, 4 ; garbage
mul ecx
xor esi, ecx ; garbage

这里的花指令使用原程序代码中没有使用的寄存器(ESI和EDX)。无论如何,增加花指令之后,原有的汇编代码变得更为枯涩难懂,从而达到不轻易被逆向工程的效果。

替换同等功能

mov op1, op2可以替换为 push op2/pop op1这两条指令。
jmp label可以替换为 push label/ret这两条指令,IDA将不会显示被引用的label。
call label可以替换为push label_after_call_instruction/push label/ref这三条指令。
push op可以替换为 sub esp, 4(或者8)/mov [esp], op这两条指令。

绝对执行或绝对不执行

如果开发人员肯定ESI寄存器始终为0:

    mov esi, 1
... ; some code not touching ESI
dec esi
... ; some code not touching ESI
cmp esi, 0
jz real_code
;fakeluggage
real_code:

逆向工程需要一段时间才能够执行到real_code。这也被称为opaque predicate。 另一个例子(同上,假设可以肯定ESI寄存器始终为0):

add eax, ebx ; real code
mul ecx ; real code
add eax, esi ; opaque predicate. XOR, AND or SHL, etc, can be here instead of ADD.

打乱执行流程

例:如下

begin: 
jmp ins1_label
ins2_label:
instruction 2
jmp ins3_label
ins3_label:
instruction 3
jmp exit
ins1_label:
instruction 1
jmp ins2_label
exit:

使用间接指针

dummy_data1 db 100h dup (0)
message1 db ’hello world’,0

dummy_data2 db 200h dup (0)
message2 db ’another message’,0

func proc
...
mov eax, offset dummy_data1 ; PE or ELF reloc here
add eax, 100h
push eax
call dump_string
...
mov eax, offset dummy_data2 ; PE or ELF reloc here
add eax, 200h
push eax
call dump_string
...
func endp

IDA仅会显示dummy_data1和dummy_data2的引用,但无法引导到文本字符串,全局变量甚至是函数的访问方式都可能使用这种方法以达到混淆代码的目地。

虚拟机/伪代码

程序员可能写一个PL或者ISA来解释程序(例如Visual Basic 5.0与之前的版本, .NET, Java machine)。这使得逆向工程不得不花费更多的时间去了解这些语言它们的所有ISP指令详细信息。更有甚者,他们可能需要编写其中某些语言的反汇编器。

加花方法

加花的方法:
1.去头加花,又分为直接去头.头前加花.头后加花。
2.加区加花.

修改方法

1.去除法
2.替换法
3.添加法
4.跳转法
5.移位法

特征码修改的几种方法.

常见的修改办法:

  1. 大小写替换法(可识别的字符.当然 函数表是除外的)
  2. 加1减1法
  3. 用 00 填充
  4. 跳转法(把特征码用 汇编语言跳到空白区域)
  5. 上下代码互换.
  6. 改相等含义的代码.

1.直接加花

记住入口点—找零区域—NOP填充—记住新入口点—编写花指令跳转回原入口点—保存文件— lordPE修改新入口点

2.去头加花

首先我们配置一个无壳的服务端,然后用OD载入后记下从入口点地址开始往下选择几处然后复制,再NOP掉,然后找到空白区域,写一些比如跳转的花指令代码后再把NOP掉的写上,再跳转回刚刚NOP掉的入口点下面的代码地址。

3.加多重花

首先我们配置一个无壳的服务端,然后用OD载入后记下入口点地址然后找到几处空白地址,当然这个大家可以找多处。我这里提供的是给大家一个思路,我就找了2处空白地址。然后我们首先记下第一处空白地址。这个我们记下是新的空白地址,然后第2处空白地址我们记下为跳转2 。最后我们跳到入口点。然后用lordPE修改入口点,至此一个加多重花的服务端就完成了。简单明了就是经过多处零区域的跳转。

4.加区加花

首先通过加区段工具给无壳的服务端加一个区段。区段名子随便填写,比如hacker,大小一般填写在50-200 就好了。然后我们用LORDPE打开首先记下入口点。然后选择区段记下新添加的HACKER区段的入口点。因为我们要写花指令,所以我们在入口点设置为HACKER区段的比入口点稍微大点的地址。然后我们在写花指令。通常找不到零区域的时候就可以加区段。
实际物理地址(OD载入的入口点)= 内存地址(0000F000)+镜象基址(RVA)

5.壳中加花

用加壳工具(如ASP ACK壳)给服务端先加一层壳,然后在加花指令。事先记好入口点

7.壳中加区加花

加壳—加区段—加花指令 综合了以上所有方法。

应用

(1) 简单加花
实现简单,但是原理比较简单,高手很容易就能去除,一般以消耗攻击者的耐心来达到目的。
(2)复杂加花
类似于加壳

  1. 记录程序的原入口点,
  2. 找到PE文件的空白区域,在空白区域内写入花指令(或者添加新节)
  3. 把入口点地址改为新入口地址
  4. 花指令执行完后跳转到原入口点地址
    在程序执行时,程序将从新的入口地址执行,即花指令先被执行,然后再执行程序原来的入口地址功能。
    增加了静态分析的难度,提高了代码的信息隐藏效果,该方法一般应用于病毒的免杀中。

例:

加花后,IDA与OD均反汇编错误
改进后程序:
void Function()
{
_asm xor eax,eax
_asm test eax,eax
_asm jz label1
_asm jnz label0
label0:
_asm __emit 0e8h
label1:
Input++;
Output++;
Input+=Output;
printf("函数结果:%d,%d",Input,Output);
}

隐藏API

逆向分析工作人员往往就是通过API在极短时间内获取了大量信息,从而使他们成功定位目标程序的关键代码段。所以隐藏对API 的调用可以有效地提高程序的抗分析能力。 例如一个简单的程序:

 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int       nCmdShow)
{
MessageBox(NULL,"test","box",0);
return 0;
}
反汇编代码如下:
.text:00401028 mov esi, esp
.text:0040102A push 0 ; uType
.text:0040102C push offset Caption ; "Test"
.text:00401031 push offset Text ; "Reverse Me"
.text:00401036 push 0 ; hWnd
.text:00401038 call ds:MessageBoxA(x,x,x,x)

目的是让反汇编的代码看不到 ds: MessageBoxA(x,x,x,x) 这样的提示。

最基本、最简单的方法:

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int       nCmdShow)
{
/********************************************************
1. 定义字符串
********************************************************/
TCHAR MsgBoxA[MAX_PATH]="MessageBoxA";

/********************************************************
2. 获取MessageBoxA的函数地址
********************************************************/
HMODULE hMod=LoadLibrary("user32.dll");
MYFUNC func=(MYFUNC)GetProcAddress(hMod,MsgBoxA);//获取MessageBoxA的函数地址。
func(0,"Reverse Me","Test",0); //调用MessageBoxA函数。
FreeLibrary(hMod);
return 0;
}
IDA中(V5.4)
.text:0040102E mov eax, dword ptr ds:aMessageboxa ; "MessageBoxA"
.text:00401033 mov dword ptr [ebp+ProcName], eax
.text:00401039 mov ecx, dword ptr ds:aMessageboxa+4
.text:0040103F mov [ebp+var_100], ecx
.text:00401045 mov edx, dword ptr ds:aMessageboxa+8
.text:0040104B mov [ebp+var_FC], edx
.text:00401051 mov ecx, 3Eh
.text:00401056 xor eax, eax
.text:00401058 lea edi, [ebp+var_F8]
.text:0040105E rep stosd
.text:00401060 mov esi, esp
.text:00401062 push offset LibFileName ; "user32.dll"
.text:00401067 call ds:__imp__LoadLibraryA@4 ; LoadLibraryA(x)
.text:0040106D cmp esi, esp
.text:0040106F call __chkesp
.text:00401074 mov [ebp+hLibModule], eax
.text:0040107A mov esi, esp
.text:0040107C lea eax, [ebp+ProcName]
.text:00401082 push eax ; lpProcName
.text:00401083 mov ecx, [ebp+hLibModule]
.text:00401089 push ecx ; hModule
.text:0040108A call ds:__imp__GetProcAddress@8 ; GetProcAddress(x,x)
.text:00401090 cmp esi, esp
.text:00401092 call __chkesp
.text:00401097 mov [ebp+var_10C], eax
.text:0040109D mov esi, esp
.text:0040109F push 0
.text:004010A1 push offset aTest ; "Test"
.text:004010A6 push offset aReverseMe ; "Reverse Me"
.text:004010AB push 0
.text:004010AD call [ebp+var_10C]
OD中:

进行简单加密处理 隐藏字符串”MessageBoxA”,”Reverse Me”,”Test”

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int       nCmdShow)
{
/********************************************************
1. 加密字符串
********************************************************/
char MsgBoxA[]={0x5c,0x74,0x62,0x62,0x70,0x76,0x74,0x53,0x7e,0x69,0x50,0x00};
//字符串"MessageBoxA"的加密形式。
char lpText[]={0x43,0x74,0x67,0x74,0x63,0x62,0x74,0x31,0x5C,0x74,0x00};
//字符串"Reverse Me"的加密形式。
char lpCaption[]={0x45,0x74,0x62,0x65,0x00};
//字符串"Test"的加密形式。

/********************************************************
2. 解密字符串
********************************************************/
for(int i=0;i<strlen(MsgBoxA);i++)
MsgBoxA[i]^=0x11; //解密字符串"MessageBoxA"
for(i=0;i<strlen(lpText);i++)
lpText[i]^=0x11; //解密字符串"Reverse Me"
for(i=0;i<strlen(lpCaption);i++)
lpCaption[i]^=0x11; //解密字符串"Test"

/********************************************************
3. 获取MessageBoxA的函数地址
********************************************************/
HMODULE hMod=LoadLibrary("user32.dll");
if(hMod)
{
MYFUNC func=(MYFUNC)GetProcAddress(hMod,MsgBoxA); //获取MessageBoxA()的函数地址。
func(0,lpText,lpCaption,0); //调用MessageBoxA函数。
FreeLibrary(hMod);
}
return 0;
}
IDA中:
.text:0040146E mov edx, [ebp+arg_8]
.text:00401471 push edx
.text:00401472 mov eax, [ebp+arg_4]
.text:00401475 push eax
.text:00401476 push offset s_SecondChanceA ; "Second Chance Assertion Failed: File %s"...
.text:0040147B lea ecx, [ebp+OutputString]
.text:00401481 push ecx
.text:00401482 call dword_4235D0
OD中:

3.将GetProcAddress也隐藏
类似上面的方法
4.简单的SMC

未完成 还会更新的

如果我记得的话

参考链接

re-for-beginners/花指令

看雪/反调试总结

花指令总结 大佬写的花指令总结,很顶、看的很爽