首先我们查看一下信息,32位程序,无壳:
然后用IDA打开,提示// positive sp value has been detected, the output may be wrong!
出现了栈不平衡的问题,导致ida无法生成伪代码。那为什么会出现这种情况呢?
一般是程序代码有一些干扰代码,让IDA的反汇编分析出现错误。比如用push + n条指令 + retn来实际跳转,而IDA会以为retn是函数要结束,结果它分析后发现调用栈不平衡,因此就提示sp analysis failed.
还有一些比如编译器优化,因为ida是用retn指令来识别函数结束的,如果函数不是以这种方式结束,IDA就会分析为栈不平衡。也就是IDA找不到函数结束的位置。
例如以下代码:
1 | int one_function( int a,int b); |
其中return one_function(a,b)这条语句,在某些新的编译器,可能会编译成这样的指令序列:
1 | mov esp, ebp |
本段转自:https://blog.csdn.net/dj0379/article/details/8699219
所以我总结:IDA识别出栈不平衡,是由于IDA的伪代码生成引擎无法识别程序的汇编逻辑结构导致的。所以我们要手动对栈的值进行修正。
勾选显示栈指针的选项
可以看到这两个地方,都是调用了一个函数,结果栈指针都不一样了。至于为什么不一样,后面就知道了。
所以我们到这个函数的位置按下alt+k,修改新旧sp指针的差值,这里我们改为0.对于不同的函数调用,我们要改的值当然是不同的。例如:
1、_stdcall调用约定:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。
2、__cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用__cdecl的转换方式。
针对每一条指令执行完后,看看栈是否正常,如果不对,则通过ALT + K来修改.重点检查虚函数的调用, 如call [esi + n] , 这里不一定非得是esi,以及跳转前后的栈是否一致.另外还需要通过ALT + P 来确认下变量起始地址,清除个数与保存个数是否正常.
修改后可以看到两次函数调用前后的栈相等了。于是再次生成伪代码,IDA未出现错误:
发现逻辑很简单,先用wrong函数加密,然后omg函数校对
wrong() :
omg() :
可以看到加密后和&unk_4030C0地址的字符串校对,解密看一下:
1 | # coding:utf-8 |
结果是错的,还以为很简单,果然是小丑
可以看到前面第20行复制了一次flag,用在下面的encrypt函数里,才是真正的加密函数。
所以现在重点就是这个ebcrypt函数,我们点进去看,发现依然出现错误,并且一堆乱七八糟的东西:
所以我们观察到这层循环:
可以推测,出题者这里先给函数的代码段进行了加密,然后在运行的时候再用这层循环进行解密,相当于加了一层壳。
所以我们这里可以使用IDC脚本,或者动态调试查看到解密后正确的函数指令。我们用ollydbg打开,中文智能搜索定位到解密的位置:
可以看到这里,非常符合for循环优化后的指令序列,[ebp-0xC]就是i,jocker.00401500就是ecrypt()的地址。因此我们下断点到循环结束的地方,然后F9,再F7步入解密后的ecrypt函数内部:
可以看到正确的汇编指令。然后dump出来:
然后将新的可执行文件拖入IDA,可以看到ecrypt函数已经解出,函数名为start:
这里对前19个flag进行加密比较,&unk_403040就是flag前19个字节加密后的字符串,我们提取:
解密
1 |
|
可以看到只出了前一部分的flag。但是别忘了,encrypt()之后还有一个finally函数。我们查看:
到这里,我没看懂这段代码的逻辑含义,但是它提示了:我把最后一部分藏起来,你不会成功的!!!
还记得前面用的异或加密算法吗,所以v3应该就是相同的方法继续加密后的五个密文字符,所以我们就需要知道他用什么key来加密的。前面用的是hahaha字符的前19个,这里我们继续用后面五个进行解密的话肯定不是flag。但是flag的最后一位肯定是},所以我们猜测是用某个数字和 ‘}’ 异或,可以得到 ‘:’ 这个字符。于是进一步猜测前面四个都是用相同的key加密的,于是我们解密:
1 |
|
得到flag:flag{d07abccf8a410cb37a}
所以回到前面的问题,为什么调用前面的两个函数,会出现栈失衡的问题?因为IDA在静态反汇编时,encrypt函数并没有解密,指令序列是乱的,IDA根据反汇编的结果进行栈的操作,自然在调用前后出现栈失衡的问题。