前言
本来是单开坑来写怎么除去花指令的,写着写着内容就加了很多其他涉及反调试的了,刚好花指令也算re里代码混淆内容,那干脆全部一起写了吧,努力写完。。。
栈指针平衡
在这就不具体说明栈了,知道入栈出栈,栈顶指针和栈底,先进后出就ok
在做题的时候,静态分析谁不想F5大法啊,然后施法失败
无法查看伪代码,做题时就很费时间,od不耐用啊,如果不解决这个问题,然后就只能分析汇编代码,对于逻辑简单的题目来说还可以理解,对于复杂点的题目就只能望洋兴叹。
因此现在立刻马上就解决这个问题!并熟悉其原理!好好的“羞辱”一下出题人!
题目来源:2018年九月份安恒杯月赛之NewDriver
在打开题目的时候ida就已经提示了是在哪个地方出现了栈指针不平衡的地方
(啊原来这个红色的提示就是栈指针不平衡啊,之前在手脱花指令的时候经常出现)
先让ida显示栈指针
是个逆向手都知道,栈帧开始将其函数的返回地址esp压进栈,用ebp来保存该值,压栈和出栈过程栈指针都会随之变化,那么在清理栈的最后,将其返回地址重新赋给esp,此时esp和ebp的值都是一样的都是返回地址,最后,栈指针会回到初始状态。
但看下图,进行pop指令后的栈指针和栈帧开始的栈指针并不相同
(注意:每条语句前的栈指针是这条语句未执行时的栈指针。
原栈指针:
这就引起了栈指针不平衡,所以需要手动调节栈指针让其平衡
在ida中使用快捷键AIT+K来进行栈指针的修改
因为要修改最后两句的栈指针,所以要在这两个指令前修改
那么应该改成多少呢:0x21E-0x4=0x21A
成功反编译:
为啥会出现栈指针失衡问题?
IDA有栈跟踪的功能,它在函数内部遇到ret(retn)指令时会做判断:栈指针的值在函数的开头/结尾是否一致,如果不一致就会在函数的结尾标注"sp-analysisfailed"。
一般编程中,不同的函数调用约定(如stdcall&_cdcel call)可能会出现这种情况
另外,为了实现代码保护而加入代码混淆(特指用push/push+ret实现函数调用)技术也会出现这种情况。
想要保护自己的代码可以加入花指令混淆或者垃圾代码,除了花指令外,给软件加壳也是一种混淆代码的方式
花指令
接上:花指令又是怎样影响栈指针的呢?
这部分后续再写,环境还没配置好。。。
另外,好听点保护代码,难听点就是我们打题的时候,花指令就会被用来干扰ida和od等软件对程序的静态分析使这些软件无法正常反汇编出原始代码。
一些比较常见的去花总结如下:
jmp
单层jmp和多层jmp
例如:
jmp LABEL1
db junk_code;
LABEL1:
....
甚至是多层嵌套的jmp
//简单花指令-多层JMP嵌套
void example1()
{
__asm {
jmp LABEL1;
_emit 68h;
LABEL1:
jmp LABEL2;
_emit 0CDh;
_emit 20h;
LABEL2:
jmp LABEL3;
_emit 0E8h;
LABEL3:
}
a = 99;
}
互补条件代替JMP跳转: jzjnz
利用jz和jnz的互补条件跳转指令来代替jmp。
如下,无论如何都会跳转到LABEL1处
jz LABEL1
jnz LABEL1
db junk_code
LABEL1:
永真条件跳转:jnzjz
通过设置永真或者永假的,导致程序一定会执行,由于ida反汇编会优先反汇编接下去的部分(false分支)。也可以调用某些函数会返回确定值,来达到构造永真或永假条件。
比如:先对eax进行自身xor后test,ZF标志位肯定为1,那么无论如何都不会跳转到LABEL1
void example2_1()
{
__asm {
push eax;
xor eax, eax;
test eax, eax;
jnz LABEL1;
jz LABEL2;
LABEL1:
_emit 0xC7;
LABEL2:
pop eax;//恢复ebx寄存器
}
a = 21;
}
示例1:简单jmp
可以骗过dbg,但是放在ida中就很容易看出来,无效跳转
题目来源:[HDCTF2019]Maze
简单的检查程序,32位带壳,直接去壳
放ida,可以明显看到核心代码就是在这,但是直接反汇编没有反应
可以看到其中一个跳转指令是跳转到下一条指令,相当于是没跳转,废话指令,而call指令调用了一个不是地址的地址,可以简单猜测存在花指令,导致ida编译失败
将其跳转指令nop掉,重启程序
发现已经可以正常反汇编咯
示例2:永真条件跳转
题目来源:来自NSSCTF平台
[HZNUCTF 2023 final]虽然他送了我玫瑰花
基本流程:无壳32位,丢ida查找字符串,做了两次脱花也做了一下比较,后面看了看wp发现还是差点到位呜呜还是要理解原理和多加练习
来到该字符串处反编译失败
来到该函数的开头,逐行看看,明显的花指令,直接nop即可,通过push ebx 构造永真条件进行跳转,直接nop掉即可
nop掉之后将整个函数按P键使其被识别为一个函数
反编译成功:
这里记录一下前两次脱花自己出现的问题:
1)第一次脱没有将pop ebx给nop掉,该花指令通过push ebx等一系列指令进行永真条件的构造,让其无法步进后面的代码,而这些都是在堆栈中进行的,太久了都忘了压栈出栈的操作是一连套的了
2)脱花连后面的push和jmp都直接nop掉了,wwwwwwwwwwwwwhhhhaaatttt是有必要的
示例3:互补条件跳转
题目来源:某春秋平台CTF逆向题-Random
无壳32位
找到主函数反编译
来看这个ida没有识别出来的函数到底是什么
显然易见,互补条件jzjnz跳转
在地址0x40146A处的跳转无论如何都会跳转到0x40146F处,使得IDA未能识别,call指令调用了一个不存在的地址(另外的数据和跳转地址混合在了一起)因此IDA没有将该部分正确识别为正确的跳转地址。
解决方法:按D键将该部分转换为数据,然后在正确的跳转地址处按C键,使其正常识别出函数,此外,由于0x40146A~0x40146E都是人工添加的无用代码(其中xor test指令nop掉对程序也没什么影响顺手nop掉了,感觉就是影响ZF标志位 方便给下面的互补条件的jzjnz提供条件),我们可以直接将该部分数据全部转为空指令nop
成功反编译:
感觉永真条件构造和互补条件跳转其实差不多,反正都会跳
call&ret
这里利用call和ret,在函数中修改返回地址,达到跳过thunkcode到正常流程的目的。可以干扰ida的正常识别(这个让我想起了之前在学PE文件的时候写shellcode,手改程序入口执行shellcode后再跳转到真正的程序入口,异曲同工
__asm{
call label1
_emit junkcode
label1:
add dword ptr ss:[esp],8//具体增加多少根据调试来
ret
_emit junkcode
}
call指令:将下一条指令地址压入栈,再跳转执行
ret指令:将保存的地址取出,跳转执行
环境还配好加上还没找到相适配的题,先搁置