题目
初步分析
使用 Exeinfo PE 软件对其进行分析:
初步分析得出信息:此为 C++编写的 32 位可执行文件,类型为控制台应用程序,使用 MSVC 编译器编译,并且没有加壳。
执行该程序,界面如下:
推测基本逻辑为判断所输入字符串经过一定转换后是否与 flag 相同。
逆向开始
使用 IDA Pro 32 位程序对此程序进行分析,按下 F5 得到其 C 的伪代码:
通过简单分析验证了之前的推测。为了方便,对其中已明确用途的变量和函数重新指定类型和名称:
如此一来,整个程序的流程便已清晰明了:
- 首先对输入的字符串(即 Str1)长度进行判断,若长度不等于 29,直接退出程序。由此我们推测,flag 可能是 29 位的字符串。
- 然后将 char*类型的指针变量指向 Str1,即 Str1 的第一个字符。计算出 Str2 和 Str1 的地址偏移量 offset。定义计数变量 i 的值为 29。
- 暂且跳过循环的部分。从程序结尾部分看出,程序将 Str1 与 Str2 进行了比较,若结果相同便会输出 「Congratulations」。这里的 29 再次印证了之前的推测。
容易想到,flag 极有可能藏在 Str2 中,但 Str2 显然并未在此处定义。为了找出 Str2,我们使用 IDA 的 「跳转至交叉引用」 功能。右击 Str2:
点击 「Jump to xref…」,IDA 将会为我们显示出所有引用了该变量的位置。
可以看出,Str2 在 sub_401000 这个函数中出现了两次,点击 「OK」 以跳转至该函数:
由之前的分析,显然该 result 变量存储的应该就是伪装后的 flag。flag 由 29 个字符组成,而这里的 result 数组有 7 个元素,4×7=28,加上最后赋值操作的元素刚好是 29。为了验证推测,我们将其数据以 16 进制显示:
以字节计数,刚好为 29 个字节。
似乎这就是我们需要的结果。然而将其转换后却只能得到一堆毫无意义的字符,经验证也并不是我们寻找的 flag,推测这段数据被程序进行了某种加密。
回到 main 函数的循环部分:
分析第一次循环得出,该循环将 Str1 中字符逐个与其下一个字符进行异或操作,并重新赋值给当前字符,完成了对 Str1 的变换。
下面这条语句看起来这似乎并没有涉及到 Str2 的操作,其实不然。我们知道,在 C 中表达式 p[x] 与 (p+x) 的返回值相同,那么 character[offset-1] 的返回值与(character+offset-1) 的返回值显然也相同。在循环体的第一条语句中,character 已经自增 1,那么此处一定存在真值表达式:(character+offset-1)==(Str1+offset)==*Str2。
也就是说,这里操作的对象实际上是 Str2。这条语句将 Str2 中的当前字符重新赋值为 ROL1 函数的返回值。那么 ROL1 是个什么函数?分析汇编:
得出结论,这里的 ROL1 实际上就是汇编中的 rol 循环左移指令。在 IDA 安装目录下的 plugins 文件夹内,我们可以找到文件名为 defs.h 的头文件,其中就有对 ROL1 函数的声明和定义:
// rotate left
template<class T> T __ROL__(T value, int count)
{
const uint nbits = sizeof(T) * 8;
if ( count > 0 )
{
count %= nbits;
T high = value >> (nbits - count);
if ( T(-1) < 0 ) // signed value
high &= ~((T(-1) << count));
value <<= count;
value |= high;
}
else
{
count = -count % nbits;
T low = value << (nbits - count);
value >>= count;
value |= low;
}
return value;
}
inline uint8 __ROL1__(uint8 value, int count) { return __ROL__((uint8)value, count); }
inline uint16 __ROL2__(uint16 value, int count) { return __ROL__((uint16)value, count); }
inline uint32 __ROL4__(uint32 value, int count) { return __ROL__((uint32)value, count); }
inline uint64 __ROL8__(uint64 value, int count) { return __ROL__((uint64)value, count); }
inline uint8 __ROR1__(uint8 value, int count) { return __ROL__((uint8)value, -count); }
inline uint16 __ROR2__(uint16 value, int count) { return __ROL__((uint16)value, -count); }
inline uint32 __ROR4__(uint32 value, int count) { return __ROL__((uint32)value, -count); }
inline uint64 __ROR8__(uint64 value, int count) { return __ROL__((uint64)value, -count); }
至此,我们已经完成了对该程序的所有分析过程。下面我们只需复现出程序源码,稍作修改便可得到 flag。
代码复现
显然,我们只需注释掉判断长度的代码,并在最后将 Str2 打印出来即可。
复现并稍作修改后的代码如下:
#include <cstdio>
#include <cstring>
#include ../defs.h
char *Str2;
int main()
{
int result[8u]; // eax
result[0] = 0x7171D110;
result[1] = 0x20204721;
result[2] = 0x7392B420;
result[3] = 0x12367431;
result[4] = 0x36C0A361;
result[5] = 0x2340A1F6;
result[6] = 0x22E785A0;
*((short *)result + 14) = 0xD7;
Str2 = (char *)result;
char Str1[256u]; // ebx
char *character; // edx
int offset; // esi
int i; // edi
char nextCharacter; // al
printf(Please Input Your Flag:\n);
scanf(%s, Str1);
// if (strlen(input) != 29)
// return -1;
character = Str1;
offset = Str2 - Str1;
i = 29;
do
{
nextCharacter = *++character;
*(character - 1) ^= nextCharacter;
character[offset - 1] = __ROL1__(character[offset - 1], 4);
--i;
} while (i);
if (!strncmp(Str1, Str2, 29u))
printf(Congratulations);
//start
for (int i = 28; i > 0; i--)
Str2[i - 1] ^= Str2[i];
printf(Str2);
return 0;
}
运行结果:
EOF