这是一道关于异或运算(xor)的题

拿到这道题我直接用IDA打开了,没有进行查壳什么的,基础题嘛,应该没有

打开直接找到主函数,按F5反编译,以下是这道题的核心内容

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+2Ch] [rbp-124h]
  char __b[264]; // [rsp+40h] [rbp-110h] BYREF

  memset(__b, 0, 0x100u);
  printf("Input your flag:\n");
  get_line(__b, 0x100u);
  if ( strlen(__b) != 33 )
    goto LABEL_7;
  for ( i = 1; i < 33; ++i )
    __b[i] ^= __b[i - 1];
  if ( !strncmp(__b, global, 0x21u) )
    printf("Success");
  else
LABEL_7:
    printf("Failed");
  return 0;
}

其核心点就是将你输入进_b数组且长度不超过33的字符串经过异或运算的结果与程序内预定义的 global数组比较,相同则成功。

那这里我们就要弄懂他是怎么进行异或运算的

理解异或运算(XOR)

异或是一种逻辑运算,符号是 ^。它的规则很简单:两个位相同结果为 0,不同结果为 1

例如:

  • 0 ^ 0 = 0

  • 0 ^ 1 = 1

  • 1 ^ 0 = 1

  • 1 ^ 1 = 0

对于字节(8个位),我们可以对每个位分别进行异或。例如:

  • 二进制 0110 (6) ^ 0011 (3) = 0101 (5)

异或有几个重要性质:

  1. 任何数和自己异或等于 0a ^ a = 0

  2. 任何数和 0 异或等于自身a ^ 0 = a

  3. 交换律a ^ b = b ^ a

  4. 结合律(a ^ b) ^ c = a ^ (b ^ c)

  5. 自反性:如果 a ^ b = c,那么 c ^ b = a 且 c ^ a = b。这是最关键的性质,也是我们解密的依据。

核心

在整段代码中异或运算的核心代码是

__b[i] ^= __b[i - 1];

它等价于 __b[i] = __b[i] ^ __b[i-1]。意思是:把 __b[i] 和 __b[i-1] 进行异或运算,结果再存回 __b[i]

for ( i = 1; i < 33; ++i )

而他前面的循环意思就是我输入的字符串存储在 __b 中,下标从 0 开始。循环变量 i 从 1 到 32,每次处理一个元素 __b[i]。实际上他一次性就执行完了

那么这两个结合起来的运算流程就是

  • __b[0] 

  • __b[1] = F[1] ^ F[0]

  • __b[2] = F[2] ^ F[1] ^ F[0]

  • __b[3] = F[3] ^ F[2] ^ F[1] ^ F[0]

  • __b[4] = F[4] ^ F[3] ^ F[2] ^ F[1] ^ F[0]

注意:第一个数据下标0也就是__b[0] 是无法执行异或运算的,只有从下标1开始才会与前面一个两两相邻执行异或运算。

这就是在整个异或运算的流程,也就是说在正常的代码运行当中,我们输入的flag会经过这样的运算得出的结果与global数组比较,如果我们输入的flag正确,那么程序将会输出Success,如果不对则输出Failed。

综上所诉,我们的flag其实就是global数组,只不过他是经过异或运算变成这样的

了解了这些就好办多了,我们要做的就是将global数组提取出来,然后反向计算得到flag

我们在主函数上找到global数组双击他进入数据段

可以看到这并不是我们想要的数据,这说明 global 本身并不是直接存放数据的数组,而是一个指针dq 表示 8 字节的地址),它指向另一个地方,那里才是真正的数据。这个指针指向的标号是 aFKWOXZUPFVMDGH,我们需要再双击 aFKWOXXZUPFVMDGH(或者按 Enter 键),就能跳转到真正的数据定义处。

这三行是连续的,它们共同构成了完整的 33 字节数据。第一行标号 aFKWOXZUPFVMDGH 指向数据开头,后面两行没有新标号,只是接着显示剩余部分,所以我们应该把这三行所有的字节按顺序合并在一起。

并把它们换算成面向计算机语言的16进制

接下来我们来逐行解析,并列出每个字节的数值(十六进制):

第一行:db 'f',0Ah

  • 'f' = 0x66

  • 0Ah = 0x0A

第二行:db 'k',0Ch,'w&O.@',11h,'x',0Dh,'Z;U',11h,'p',19h,'F',1Fh,'v"M#D',0Eh,'g'

仔细拆分:

  • 'k' → 0x6B

  • 0Ch → 0x0C

  • 连续字符 'w&O.@' → 依次为:'w'=0x77, '&'=0x26, 'O'=0x4F, '.'=0x2E, '@'=0x40

  • 11h → 0x11

  • 'x' → 0x78

  • 0Dh → 0x0D

  • 连续字符 'Z;U' → 'Z'=0x5A, ';'=0x3B, 'U'=0x55

  • 11h → 0x11

  • 'p' → 0x70

  • 19h → 0x19

  • 'F' → 0x46

  • 1Fh → 0x1F

  • 连续字符 'v"M#D' → 'v'=0x76, '"'=0x22, 'M'=0x4D, '#'=0x23, 'D'=0x44

  • 0Eh → 0x0E

  • 'g' → 0x67

第三行:db 6,'h',0Fh,'G2O',0

  • 6 → 0x06

  • 'h' → 0x68

  • 0Fh → 0x0F

  • 连续字符 'G2O' → 'G'=0x47, '2'=0x32, 'O'=0x4F

  • 最后的 0 是字符串结束符(NULL),不计入我们要取的 33 个字节。

完整的合并内容如下:

0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11,
0x78, 0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F,
0x76, 0x22, 0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F,
0x47, 0x32, 0x4F

最终payload:

最后我们用编程语言写出反向计算的代码将合并内容带入进行反向计算,去获取最终的flag。

以下为python示例:

data = [0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11,
        0x78, 0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F,
        0x76, 0x22, 0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F,
        0x47, 0x32, 0x4F]
flag = [data[0]] + [data[i] ^ data[i-1] for i in range(1, 33)]
print(''.join(chr(c) for c in flag))

这里定义了一个表data,其实就是上面讲的数组_b。在flag那一行中因为下标0不参与计算,所以就取data[0],后面就是加上参与异或计算的结果,然后遍历 从1到32依次处理、计算,原理与上面的C语言相同。

print这一行是将 flag 列表中的每个整数 c 转换成对应的字符(chr(c)),然后用 ''.join() 把这些字符连接成一个完整的字符串,最后打印出来。

得到最终flag:

flag{QianQiuWanDai_YiTongJiangHu}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐