花指令

这里写一下有关花指令的内容,自己的理解也很有限,只能以后再多学学再补充,现在只能把自己知道的写下来。

原理

首先反汇编有一个很关键的问题,就是怎么样区分数据和代码,由于x86和x64架构的每条指令是不等长的,区分数据和代码就变得很困难,反汇编的算法必须要对汇编指令长度和各种各样的跳转进行适当的处理,不然就会发生错误,导致反汇编失败,这也是花指令的作用。

目前主要的反汇编算法主要还是线性扫描*(Linear Sweep)和较高级的递归行进(Recursive Traversal)*,目前我比较常用的就是OD和IDA,它们的工作原理列在下面,此外还尝试了Ghidra,这个工具比较神奇,可以在有花指令的情况下抛掉很多无用的指令,反汇编出函数的大概,目前工作的原理还没有详细地了解,这里先提一下,了解之后再补充。

线性扫描本身的技术含量不是很高,反汇编工具将整个模块中的每一条指令都反汇编成汇编指令,每一个遇到的机器码都会当作汇编指令处理(不加判断),所以线性扫描根本不能把代码和数据区分开来,而一旦一条指令开始出错,整个反汇编都开始出错。

递归行进算法相较于线性扫描算法更灵活一些,它是按照代码可能的执行顺序来反汇编程序,每条可能的路径来扫描,解码出分支之后会记录这个地址,分别反汇编各个分支中的指令,这样按照路径进行搜寻,可以有效避免将数据识别成指令的问题。

工具 算法
OllyDbg Linear Sweep/Recursive Traversal(Ctrl+A)
IDA Pro Recursive Traversal
Ghidra

但是我们可以通过巧妙地构造代码和数据,插入一些“花指令”,以此干扰反汇编软件。因为前面提到的不同的机器指令包含的字节数不同地原因,所有的多字节指令只有正确识别第一个字节,也就是操作码,才能正确实现反汇编,否则就会识别成完全不同的另一条指令。

而迷惑递归行进算法还需要多动动脑子,一两个简单的垃圾数据不会使递归行进算法失效,但是,个人理解,在递归行进算法中,任意的控制转移指令,其后的地址都认为是有效的,因为递归行进算法中最重要的就是通过跳转来确定分支,因此,我们可以利用跳转来实现我们想要的“花指令”

常见花指令

  • jx & jnx

    比如说这样一段代码

1
2
3
4
5
6
text:00401065			jz short near ptr loc_40107C
text:00401067 jnz short near ptr loc_40107C
text:00401069
text:00401069 loc_401069:
text:00401069 db 36h ;junk code here
text:0040107C ……

​ 这里只是一个随便编写的例子,但是差不多所有的处理都是这个样子,两个互补指令代替了一个强制跳转,但是在IDA这里,没有成功跳转的分支也是默认有效的,所以会去解析junk code,后面的指令也就跟着出错了。

  • call+pop/(add esp)+return

    比如说这样的代码

    1
    2
    3
    4
    5
    6
    7
    text:0040103B		call loc_401042
    text:0040103B sub_401022 endp;sp-analysis failed
    text:0040103B
    text:00401040 cmp cl,dl
    text:00401042
    text:00401042 loc_401042:
    text:00401042 add esp,4

    call指令相当于jmp+push eip,所以用add esp,4指令可以消除入栈的eip的影响,此时call就相当于jmp,但是IDA还是会把0x401042当作新函数的首地址,所以当前函数的识别就会出错,IDA会找不到结束位置,因此没法继续反汇编。

  • jx

    这一类理解起来也蛮简单的,就是jmp到一个IDA无法解析的位置,比如说下面这段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .text:00000000004009F5 loc_4009F5:                             
    .text:00000000004009F5 jmp short near ptr loc_4009FB+1
    ;---------------------------------------------------------------------------
    ;从这里开始
    .text:00000000004009F7 xor eax, eax
    .text:00000000004009F9 jz short loc_4009F5
    .text:00000000004009FB
    .text:00000000004009FB loc_4009FB:
    .text:00000000004009FB call near ptr 0C59748h

    最下面这个0c59748的地址显然是无效的,这里肯定是出了问题,仔细观察一下代码,xor eax, eax这句得到的是0,所以下一个跳转是一定可以实现的,但是IDA判断不出来,他会对所有可能的分支进行解析,所以会从0x4009FB出开始解析新的命令,然后这个跳转并不存在,就会提示地址不存在,但是真正让反汇编进行不下去的是接下来的操作,另一条可能的路径,即正常的执行流程,进入到0x4009F5之后跳转到了0x4009FC的位置,但是这里已经解析过了,被一条假的命令占用了,IDA就会停止反汇编,然后报错。

  • call+一些巧妙的处理

    最近比赛的时候见到了一个很不错的花指令,可以学习一下,在call指令之后写入一些junk code,然后在调用的函数中对返回地址进行处理跳过junk code,详细的过程在这里

后记

写花指令的方式实在太多,也有很多很有创意的花指令,记录是记录不完的,但是只要掌握的原理,跟着程序的流程走,都不会有太大的难度,熟练了以后可以尝试使用脚本去除花指令。

攻防世界-crackme-wp(手动脱壳) hgame_wp

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×