0%

逆向idea

2025-09-23 20:00By
Bao100
C语言逆向逆向技术
ida find

shitf+f12

image-20250923113543768

双击上图“.rdata:0000000000404019 0000000F C you are right!”跳转到

image-20250923113647083

上图ctrl+x或右键,跳转到引用,如下图

image-20250923113753410

伪代码分析

f5转伪代码,同时右键把str2等转字符串。

int __cdecl main(int argc, const char **argv, const char **envp) { __int64 Str2; // [rsp+20h] [rbp-60h] int v5; // [rsp+28h] [rbp-58h] __int16 v6; // [rsp+2Ch] [rbp-54h] char v7; // [rsp+2Eh] [rbp-52h] char Str1[1000]; // [rsp+410h] [rbp+390h] int v9; // [rsp+7F8h] [rbp+778h] int i; // [rsp+7FCh] [rbp+77Ch] _main(); Str2 = '3r_ys43{'; v5 = 'sr3v'; v6 = '}3'; v7 = 0; printf("please put your flag:"); scanf("%s", Str1); for ( i = 0; i <= 665; ++i ) { if ( Str1[i] == 101 ) Str1[i] = 51; } for ( i = 0; i <= 665; ++i ) { if ( Str1[i] == 97 ) Str1[i] = 52; } v9 = strcmp(Str1, (const char *)&Str2); if ( v9 ) printf("you are wrong,see again!"); else printf("you are right!"); system("pause"); return 0; }

我们来逐行分析这段IDA伪代码,并构造POC获取flag。

1. 代码分析:

int __cdecl main(int argc, const char **argv, const char **envp) { __int64 Str2; // [rsp+20h] [rbp-60h] int v5; // [rsp+28h] [rbp-58h] __int16 v6; // [rsp+2Ch] [rbp-54h] char v7; // [rsp+2Eh] [rbp-52h] char Str1[1000]; // [rsp+410h] [rbp+390h] int v9; // [rsp+7F8h] [rbp+778h] int i; // [rsp+7FCh] [rbp+77Ch] _main(); // 注意:这里是小端存储,实际字符串是反向的.x86架构是小端序,所以程序中的v5(int)和v6(short)在内存中是小端存储。 Str2 = '3r_ys43{'; v5 = 'sr3v'; v6 = '}3'; v7 = 0; //字符串结束标识 printf("please put your flag:"); scanf("%s", Str1); for ( i = 0; i <= 665; ++i ) { if ( Str1[i] == 101 ) // 101是字符'e'的ASCII Str1[i] = 51; // 51是字符'3'的ASCII } for ( i = 0; i <= 665; ++i ) { if ( Str1[i] == 97 ) // 97是字符'a'的ASCII Str1[i] = 52; // 52是字符'4'的ASCII } v9 = strcmp(Str1, (const char *)&Str2);//str2是字符串,到'\0'才结束。 if ( v9 ) printf("you are wrong,see again!"); else printf("you are right!"); system("pause"); return 0; }

2. 关键逻辑:

  • 程序首先初始化了一个字符串Str2(实际上是一个字节数组,包括Str2v5v6v7,它们共同组成一个完整的字符串)。
  • 然后读取用户输入的字符串到Str1
  • Str1进行两次遍历:
    • 第一次:将所有字符'e'替换为'3'。
    • 第二次:将所有字符'a'替换为'4'。
  • 最后将处理后的Str1Str2(即目标字符串)比较,如果相同则输出正确。

因此,flag是经过替换后的字符串(即目标字符串Str2),但我们需要知道原始输入(即替换前的字符串)才是真正的flag。

3. 目标字符串Str2的还原:

注意代码中:

所以我们需要将这些值转换为字节序列(小端)并拼接起来。

  • Str2__int64(8字节),值为'3r_ys43{'(注意:这是一个多字符常量,实际值是小端存储)。

    在C语言中,多字符常量(如'3r_ys43{')的实际数值取决于字节顺序(小端)。

    但这里我们更关心的是内存中的字节序列:实际上,Str2在内存中存储的字符串是"{34sy_r3"(因为小端,所以反向)。

    但注意:Str2本身是8字节,但字符串可能不止8字节(因为后面还有v5和v6)。

实际上,整个目标字符串是&Str2开始的连续内存,包括:

  • Str2(8字节)
  • v5(4字节)
  • v6(2字节)
  • v7(1字节,为0,即结束符)

所以我们需要将Str2v5v6的值转换为小端字节序列,然后拼接。

"{34sy_r3" + "v3rs" + "3}" + "\0"

即:"{34sy_r3v3rs3}"

4. 还原原始flag:

由于程序将输入中的'e'替换为'3'、'a'替换为'4',然后与目标字符串"{34sy_r3v3rs3}"比较。

所以,原始输入(即flag)应该是对目标字符串进行反向替换(即把'3'换回'e',把'4'换回'a')。

原始输入:flag{easy_reverse}


1. 为什么Str2v5v6v7共同构成一个完整的字符串?

在C语言中,局部变量在栈内存中的布局是连续的(除非有对齐要求)。代码中:

__int64 Str2; // 8字节,地址假设为addr int v5; // 4字节,地址为addr+8 __int16 v6; // 2字节,地址为addr+12 char v7; // 1字节,地址为addr+14

这些变量在内存中是相邻的(没有填充,因为类型大小匹配偏移:8+4+2+1=15,没有对齐问题)。

程序使用:

v9 = strcmp(Str1, (const char *)&Str2);

这里&Str2Str2的起始地址。strcmp会从该地址开始逐字节比较,直到遇到空字节(\0)为止。因此,它会读取:

  • Str2的8字节、
  • 然后v5的4字节(因为紧挨着)、
  • 然后v6的2字节、
  • 然后v7的1字节(正好是0,即字符串结束符)。

所以,这些变量共同构成了一个以&Str2为起始、以v7(0)结尾的连续字符串。

为什么需要拼接?

因为strcmp比较的是从&Str2开始的内存,直到遇到\0。所以我们必须将这些变量的小端字节序列拼接起来,才能得到完整的字符串。


2. 为什么需要将值转换为小端字节序列并拼接?

计算机存储多字节数据(如int、short)时有字节序问题:

  • 小端(Little-endian):低字节存储在低地址,高字节存储在高地址。
  • 大端(Big-endian):高字节存储在低地址,低字节存储在高地址。

x86架构是小端序,所以程序中的v5(int)和v6(short)在内存中是小端存储。


总结:

  • 变量在内存中连续布局,&Str2指向的字符串包括Str2v5v6v7
  • 由于小端存储,必须将数值转换为其字节序列(低字节在前)才能得到正确的字符串。
flag

flag{easy_reverse}

还没有人赞赏,快来当第一个赞赏的人吧!
  
© 著作权归作者所有
加载失败
广告
×
评论区
添加新评论