首先我们查看一下信息,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
2
3
4
5
6
7
8
int one_function( int a,int b);

int another_function( int a, int b)
{
if ( a == 0 || b == 0 )
return -1;
return one_function(a,b);
}

其中return one_function(a,b)这条语句,在某些新的编译器,可能会编译成这样的指令序列:

1
2
3
mov esp, ebp
pop ebp
jmp one_funcion

本段转自: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# coding:utf-8

key = 0x66, 0x6B, 0x63, 0x64, 0x7F, 0x61, 0x67, 0x64, 0x3B, 0x56, 0x6B, 0x61, 0x7B, 0x26, 0x3B, 0x50, 0x63, 0x5F, 0x4D, 0x5A, 0x71, 0x0C, 0x37, 0x66
fake_flag = ''


for i in range(24):
if i % 2 == 1:
fake_flag += chr(key[i] + i)
else:
fake_flag += chr(key[i] ^ i)


print(fake_flag)

# flag{fak3_alw35_sp_me!!}

结果是错的,还以为很简单,果然是小丑

可以看到前面第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
2
3
4
5
6
7
8
9
10
11

hahaha = 'hahahaha_do_you_find_me?'
v2 = [0x0E, 0x0D, 0x9, 0x6, 0x13, 0x5, 0x58, 0x56, 0x3E, 0x6,
0x0C, 0x3C, 0x1F, 0x57, 0x14, 0x6B, 0x57, 0x59, 0x0D]
true_flag = []


for i in range(19):
true_flag.append(chr(v2[i] ^ ord(hahaha[i])))

# flag{d07abccf8a410c

 可以看到只出了前一部分的flag。但是别忘了,encrypt()之后还有一个finally函数。我们查看:

 到这里,我没看懂这段代码的逻辑含义,但是它提示了:我把最后一部分藏起来,你不会成功的!!!

还记得前面用的异或加密算法吗,所以v3应该就是相同的方法继续加密后的五个密文字符,所以我们就需要知道他用什么key来加密的。前面用的是hahaha字符的前19个,这里我们继续用后面五个进行解密的话肯定不是flag。但是flag的最后一位肯定是},所以我们猜测是用某个数字和 ‘}’ 异或,可以得到 ‘:’ 这个字符。于是进一步猜测前面四个都是用相同的key加密的,于是我们解密:

1
2
3
4
5
6
7
8

v3 = [37, 116, 112, 38, 58]
key = ord('}') ^ 58

for i in range(5):
true_flag.append(chr(v3[i] ^ key))

print(''.join(true_flag))

得到flag:flag{d07abccf8a410cb37a}

所以回到前面的问题,为什么调用前面的两个函数,会出现栈失衡的问题?因为IDA在静态反汇编时,encrypt函数并没有解密,指令序列是乱的,IDA根据反汇编的结果进行栈的操作,自然在调用前后出现栈失衡的问题。 ​

2021-12-01
Contents

⬆︎TOP