花指令 什么是花指令 花指令就是指在程序中完全冗余,不影响程序功能但对逆向工程产生干扰的指令。
花指令没有固定格式,泛指用于干扰逆向工作的无用指令。
花指令常见类型
一个常见的函数头是这样的:
push ebp mov ebp esp sub esp,0x100
插入一些相互抵消的操作,使得上段代码成这样:
push ebp pushfd add esp,0xd nop sub esp,0xd popfd mov ebp esp sub esp,0x100
有特殊的指令,其字节码是这样的: E8 FF ···· 其中E8指令为call, E8 FF的意思是跳转到本指令的第二个字节,也就是说,E8 FF的下一条指令为 FF ····,这种情况下E8这个字节码完全没有作用,但是却扰乱了静态分析,将这个字节码称之为脏字节。
也会存在很多其他的脏字节,其中大多是E8(call)这个字节。 例如这一段代码:
push ebp jz addr2 jnz addr2 db 0xE8 add3: sub esp,0x100 ··· add2: mov ebp,esp jmp addr3
其中 第二行与第三行这一对相反条件的跳转,之后往往会有花指令
用复杂的指令替换简单的指令,往往涉及到栈指针的改变,导致IDA无法正常分析。 这种类型的花指令需要仔细分析每一条指令导致的改变,尤其是栈指针的改变,从而判断出哪一部分是完全无用的部分。
这一类型的花指令经常涉及到call/ret指令的替换,通过改变ESP/EBP,来实现上述两个指令的更换。
这一部分非常推荐的是刚刚过去的VNCTF2023中的confuse_re题目。
在此只粘贴本题目中的花指令部分。
.text:00000000004045D0 .text:00000000004045D0 var_8 = qword ptr -8 .text:00000000004045D0 .text:00000000004045D0 ; __unwind { .text:00000000004045D0 endbr64 .text:00000000004045D4 push rbp .text:00000000004045D5 mov rbp, rsp .text:00000000004045D8 sub rsp, 10h .text:00000000004045DC mov [rbp+var_8], rdi .text:00000000004045E0 push rbp .text:00000000004045E1 call $+5 .text:00000000004045E6 .text:00000000004045E6 loc_4045E6: ; DATA XREF: sub_4045D0+17↓o .text:00000000004045E6 pop rbp .text:00000000004045E7 add rbp, (offset loc_4045EE - offset loc_4045E6) .text:00000000004045EB push rbp .text:00000000004045EC retn .text:00000000004045EC sub_4045D0 endp ; sp-analysis failed .text:00000000004045EC .text:00000000004045EC ; --------------------------------------------------------------------------- .text:00000000004045ED db 0E8h .text:00000000004045EE ; --------------------------------------------------------------------------- .text:00000000004045EE .text:00000000004045EE loc_4045EE: ; DATA XREF: sub_4045D0+17↑o .text:00000000004045EE pop rbp .text:00000000004045EF mov rax, [rbp-8] .text:00000000004045F3 mov rdi, rax .text:00000000004045F6 call sub_403940 .text:00000000004045FB nop .text:00000000004045FC leave .text:00000000004045FD retn .text:00000000004045FD ; } // starts at 4045D0 .text:00000000004045FE
这一花指令从0x4045E0开始,一直到0x4045EE,所实现的功能仅仅只是跳转,或者说无任何功能,全部nop即可。
花指令的手动patch
首先是必备的IDA基本操作,D、C、P快捷键,以及编辑函数范围操作。
本部分的例题是NSSCTF平台上的 jump_by_jump。 (作者非常幸运能够在学习花指令的时候看到一篇简单详细的题解,此题目中的Tigamm的题解是本部分的资料来源)
.text:00411888 jz short near ptr loc_41188C+1 .text:0041188A jnz short near ptr loc_41188C+1 .text:0041188C .text:0041188C loc_41188C: ; CODE XREF: .text:00411888↑j .text:0041188C ; .text:0041188A↑j .text:0041188C call near ptr 41BC4932h .text:00411891 add [ecx+0D8BE045h], cl .text:00411897 xor al, 7Bh
可以看到前两条指令使得下一条指令应该是从loc_41188C+1开始的,但是IDA已经从41188C开始分析了,因此我们要将41818C的字节给nop掉。
由于作者的IDA没有keypatch插件,因此作者在此介绍通过修改16进制窗口的字节码来实现patch的方法,如果读者有patch插件,可以进行patch。
首先在41188C处按下D键,将其转化为字节码。.text:00411888 jz short near ptr unk_41188D .text:0041188A jnz short near ptr unk_41188D .text:0041188A ; --------------------------------------------------------------------------- .text:0041188C db 0E8h .text:0041188D unk_41188D db 0A1h ; CODE XREF: .text:00411888↑j .text:0041188D ; .text:0041188A↑j .text:0041188E db 30h ; 0 OFF32 SEGDEF [_rdata,417B30] .text:0041188F db 7Bh ; { .text:00411890 db 41h ; A .text:00411891 ; --------------------------------------------------------------------------- .text:00411891 add [ecx+0D8BE045h], cl .text:00411897 xor al, 7Bh
在16进制窗口中右键Edit,将E8修改为90,回到反汇编窗口。.text:00411888 jz short near ptr unk_41188D .text:0041188A jnz short near ptr unk_41188D .text:0041188A ; --------------------------------------------------------------------------- .text:0041188C db 90h .text:0041188D unk_41188D db 0A1h ; CODE XREF: .text:00411888↑j .text:0041188D ; .text:0041188A↑j .text:0041188E db 30h ; 0 OFF32 SEGDEF [_rdata,417B30] .text:0041188F db 7Bh ; { .text:00411890 db 41h ; A .text:00411891 ; --------------------------------------------------------------------------- .text:00411891 add [ecx+0D8BE045h], cl .text:00411897 xor al, 7Bh
在4118C处按下C键,然后依次在之后的代码也按下C键,知道左边的地址栏中没有黄色的地址为止。
.text:00411888 jz short loc_41188D .text:0041188A jnz short loc_41188D .text:0041188C nop .text:0041188D .text:0041188D loc_41188D: ; CODE XREF: .text:00411888↑j .text:0041188D ; .text:0041188A↑j .text:0041188D mov eax, ds:dword_417B30 .text:00411892 mov [ebp-20h], eax .text:00411895 mov ecx, ds:dword_417B34 .text:0041189B mov [ebp-1Ch], ecx .text:0041189E mov edx, ds:dword_417B38 .text:004118A4 mov [ebp-18h], edx .text:004118A7 mov eax, ds:dword_417B3C .text:004118AC mov [ebp-14h], eax .text:004118AF mov ecx, ds:dword_417B40 .text:004118B5 mov [ebp-10h], ecx .text:004118B8 mov dx, ds:word_417B44 .text:004118BF mov [ebp-0Ch], dx .text:004118C3 mov dword ptr [ebp-2Ch], 0 .text:004118CA jmp short loc_4118D5
最后在函数头位置按下P键,重新定义函数。如此,花指令已经被手动修改成功了,再按下F5按键,即可得到真正的主函数伪代码。
int __cdecl main_0 (int argc, const char **argv, const char **envp) { int i; char v5[28 ]; strcpy (v5, "NSSCTF{Jump_b9_jump!}" ); for ( i = 0 ; i < 21 ; ++i ) v5[i] = (v5[i] + v5[(i * i + 123 ) % 21 ]) % 128 ; sub_4110CD ("%s" , (char )v5); return 0 ; }
使用IDApython进行花指令的patch 本部分的例题是NSSCTF平台上的wordy。
.text:0000000000001135 ; int __fastcall main(int, char **, char **) .text:0000000000001135 main: ; DATA XREF: start+1D↑o .text:0000000000001135 ; __unwind { .text:0000000000001135 push rbp .text:0000000000001136 mov rbp, rsp .text:0000000000001139 sub rsp, 10h .text:000000000000113D mov dword ptr [rbp-4], 0 .text:0000000000001144 .text:0000000000001144 loc_1144: ; CODE XREF: .text:loc_1144↑j .text:0000000000001144 jmp short near ptr loc_1144+1 .text:0000000000001144 ; --------------------------------------------------------------------------- .text:0000000000001146 dw 0BFC0h .text:0000000000001148 dq 0FFFEDFE800000068h, 65BFC0FFEBFFh, 0FFEBFFFFFED2E800h .text:0000000000001148 dq 0C5E80000006CBFC0h, 6CBFC0FFEBFFFFFEh, 0FFFFFEB8E8000000h .text:0000000000001148 dq 6FBFC0FFEBh, 0C0FFEBFFFFFEABE8h, 0FE9EE800000020BFh .text:0000000000001148 dq 77BFC0FFEBFFFFh, 0EBFFFFFE91E80000h, 0E80000006FBFC0FFh .text:0000000000001148 dq 0BFC0FFEBFFFFFE84h, 0FFFE77E800000072h, 6CBFC0FFEBFFh .text:0000000000001148 dq 0FFEBFFFFFE6AE800h, 5DE800000064BFC0h, 21BFC0FFEBFFFFFEh .text:0000000000001148 dq 0FFFFFE50E8000000h, 0ABFC0FFEBh, 0C0FFEBFFFFFE43E8h .text:0000000000001148 dq 0FE36E800000054BFh, 68BFC0FFEBFFFFh, 0EBFFFFFE29E80000h .text:0000000000001148 dq 0E800000065BFC0FFh, 0BFC0FFEBFFFFFE1Ch, 0FFFE0FE800000072h .text:0000000000001148 dq 65BFC0FFEBFFh, 0FFEBFFFFFE02E800h, 0F5E800000020BFC0h .text:0000000000001148 dq 61BFC0FFEBFFFFFDh, 0FFFFFDE8E8000000h, 72BFC0FFEBh .text:0000000000001148 dq 0C0FFEBFFFFFDDBE8h, 0FDCEE800000065BFh, 20BFC0FFEBFFFFh .text:0000000000001148 dq 0EBFFFFFDC1E80000h, 0E80000006DBFC0FFh, 0BFC0FFEBFFFFFDB4h
可以看到从jmp命令开始,IDA已经无法自动分析了。 可以看到,这里的JMP指令与前文中调到的E8 FF的call指令有相似之处。 因此我们只需要将EB改成90即可。 但是之后的汇编中同样有相同的花指令,而且非常多,我们可以使用IDApython来对其进行修改。
附上代码
startaddr = 0x001144 endaddr = 0x3100 for i in range (startaddr,endaddr): if get_wide_byte(i) == 0xEB : if get_wide_byte(i+1 ) == 0xFF : patch_byte(i,0x90 ) print ("[+] Addr {} is patched" .format (hex (i)))
关键的就是两个函数,一个是get_wide_byte()函数,一个是patch_byte()函数。
当然IDA编写脚本时有很多函数。
http://www.s0rry.cn/archives/python-ni-xiang-jiao-ben-bian-xie
可以参考上面这个链接进行学习。