2020羊城杯easyre

发布时间 2023-06-19 08:55:43作者: Qsons

2020羊城杯easyre

  • 无壳64位程序
  • GCC编译

回显如下所示:

放IDA中继续分析:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  int v5; // eax
  char Str[48]; // [rsp+20h] [rbp-60h] BYREF
  char Str1[64]; // [rsp+50h] [rbp-30h] BYREF
  char v9[64]; // [rsp+90h] [rbp+10h] BYREF
  char v10[64]; // [rsp+D0h] [rbp+50h] BYREF
  char Str2[60]; // [rsp+110h] [rbp+90h] BYREF
  int v12; // [rsp+14Ch] [rbp+CCh] BYREF

  _main();
  strcpy(Str2, "EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG");
  puts("Hello, please input your flag and I will tell you whether it is right or not.");
  scanf("%38s", Str);
  if ( strlen(Str) != 38
    || (v3 = strlen(Str), (unsigned int)encode_one(Str, v3, v10, &v12))
    || (v4 = strlen(v10), (unsigned int)encode_two(v10, v4, v9, &v12))
    || (v5 = strlen(v9), (unsigned int)encode_three(v9, v5, Str1, &v12))
    || strcmp(Str1, Str2) )
  {
    printf("Something wrong. Keep going.");
    return 0;
  }
  else
  {
    puts("you are right!");
    return 0;
  }
}

程序逻辑如下所示:

  • 输入字符串到Str数组里
  • strlen(Str) != 38可得知flag长度必须为38
  • 字符串经过了3次加密分别是,encode_one、encode_two、encode_three
  • 经过3次加密后与Str2进行比较.

算法分析:

Encode_one

encode_one

__int64 __fastcall cmove_bits(unsigned __int8 a1, char a2, char a3)
{
  return (unsigned __int8)((int)(unsigned __int8)(a1 << a2) >> a3);
}

void __fastcall encode_one(char *a1, int a2, char *a3, int *a4)
{
  int v4; // esi
  int v5; // esi
  int v6; // esi
  int v7; // [rsp+34h] [rbp-1Ch]
  int v8; // [rsp+38h] [rbp-18h]
  int v10; // [rsp+48h] [rbp-8h]
  int i; // [rsp+4Ch] [rbp-4h]
  unsigned __int8 *v12; // [rsp+70h] [rbp+20h]

  v12 = (unsigned __int8 *)a1;
  if ( a1 && a2 )
  {
    v10 = 0;
    if ( a2 % 3 )
      v10 = 3 - a2 % 3;
    v8 = a2 + v10;
    v7 = 8 * (a2 + v10) / 6;
    for ( i = 0; i < v8; i += 3 )
    {
      *a3 = alphabet[(char)*v12 >> 2];
      if ( a2 + v10 - 3 == i && v10 )
      {
        if ( v10 == 1 )
        {
          v4 = (char)cmove_bits(*v12, 6u, 2u);
          a3[1] = alphabet[v4 + (char)cmove_bits(v12[1], 0, 4u)];
          a3[2] = alphabet[(char)cmove_bits(v12[1], 4u, 2u)];
          a3[3] = 61;
        }
        else if ( v10 == 2 )
        {
          a3[1] = alphabet[(char)cmove_bits(*v12, 6u, 2u)];
          a3[2] = 61;
          a3[3] = 61;
        }
      }
      else
      {
        v5 = (char)cmove_bits(*v12, 6u, 2u);
        a3[1] = alphabet[v5 + (char)cmove_bits(v12[1], 0, 4u)];
        v6 = (char)cmove_bits(v12[1], 4u, 2u);
        a3[2] = alphabet[v6 + (char)cmove_bits(v12[2], 0, 6u)];
        a3[3] = alphabet[v12[2] & 0x3F];
      }
      a3 += 4;
      v12 += 3;
    }
    if ( a4 )
      *a4 = v7;
  }
}

alphabet_table:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

这里的alphabet_table是一个标准的base64表,按理base64加密算法来说,base64最常见的操作是移位,这里的移位去哪了呢?在cmove_bits函数中,是存在着((a1 << a2) >> a3)这种移位.我们看一下传入的参数分别为v12[1],0,4u.

  • 也就是对v12[1]向左移动0位,向右移动4位。

这很明显是一个base64算法的特征,所以我们大致可以判断encode_one实际上是对于输入的明文进行第一层的Base64加密。

Encode_two

encode_two

void __fastcall encode_two(const char *a1, int a2, char *a3, int *a4)
{
  if ( a1 )
  {
    if ( a2 )
    {
      strncpy(a3, a1 + 26, 0xDui64);
      strncpy(a3 + 13, a1, 0xDui64);
      strncpy(a3 + 26, a1 + 39, 0xDui64);
      strncpy(a3 + 39, a1 + 13, 0xDui64);
    }
  }
}

encode_two传入的参数分别是a3,v5,v10,&v4

  • a3代表的是第一次经过Base64加密后的数据
  • v10代表是经过第二次加密后的数据

所以这里的encode_two函数,实际上就是一个对原数据进行了一个分组.

我们把a3代表1,a3+13代表2,a3+26代表3,a3+39代表4,可以得知以下结论

  • 1 - 3
  • 2 - 1
  • 3 - 4
  • 4 - 2
  • 左边代表原数据的位置,右边代表分组过后的数据所在位置.

Encode_three

Encode_three代码如下:

__int64 __fastcall encode_three(const char *a1, int a2, char *a3, int *a4)
{
  char v5; // [rsp+Fh] [rbp-11h]
  int i; // [rsp+14h] [rbp-Ch]
  const char *v8; // [rsp+30h] [rbp+10h]

  v8 = a1;
  if ( !a1 || !a2 )
    return 0xFFFFFFFFi64;
  for ( i = 0; i < a2; ++i )
  {
    v5 = *v8;
    if ( *v8 <= 64 || v5 > 90 )
    {
      if ( v5 <= 96 || v5 > 122 )
      {
        if ( v5 <= 47 || v5 > 57 )
          *a3 = v5;
        else
          *a3 = (v5 - 48 + 3) % 10 + 48;
      }
      else
      {
        *a3 = (v5 - 97 + 3) % 26 + 97;
      }
    }
    else
    {
      *a3 = (v5 - 65 + 3) % 26 + 65;
    }
    ++a3;
    ++v8;
  }
  return 0i64;
}

可以看出这个函数是一个自定义逻辑算法。

对于在ASCII码在65-90之间的字符,进行以下运算

*a3 = (v5 - 65 + 3) % 26 + 65;

对于在ASCII码在97-122之间的字符,进行以下运算

*a3 = (v5 - 97 + 3) % 26 + 97;

对于在ASCII码在48-57之间的字符,进行以下运算

*a3 = (v5 - 48 + 3) % 10 + 48;

这里如果对于恺撒加密有涉及的人,是比较熟悉的。恺撒加密的算法,我在另外一篇博客提到过,

恺撒加密实际上,是一种替换加密的技术,是基于明文在字母表上的偏移的一种加密技术。要注意的是,字母表不一定要为26个字母,只是根据密码设计的需求而已。而恺撒加密中最常见的加密方法就是同余了,即:

$En(x) = (x + n) mod 26$

明文+偏移量要控制在26之内,因为字母表是26个。

所以这里的encode_three函数,实际上是对65-90,97-122,48-57的字符做一个偏移量为3的恺撒加密。

同时在main函数里面,最后的Str1经过三次加密后,必须要和Str2相等,所以Str2相当于最后的密文,Str1相当于我们要求的明文,即flag.

Str2:"EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG"

解密流程:

既然我们已经知道了程序的整个加密逻辑,如下所示:

  1. encode_one(Base64标准加密)
  2. encode_two(切片分组)
  3. encode_three(恺撒加密,key=3)

decode_one

所以我们要先写一个恺撒解密的Python脚本,脚本如下:

def decrypt_caesar(str,key=3):#根据key的不同可以改变
    test = ""
    for i in str:
        if ord(i) >= 65 and ord(i) <= 90:
            test += chr(65 + ((ord(i) - 65 -3) % 26))#这个是从A到Z
        elif ord(i) >= 97 and ord(i) <= 122:
            test += chr(97 + ((ord(i) - 97 -3) % 26))#这个是从a到z一共有26个字母,要控制在表里面
        elif ord(i) >= 48 and ord(i) <= 57:#这里可以根据题目中的字母表进行改变
            test += chr(48 + ((ord(i) - 48 - 3) % 10))
        else:
            test += i
    return test
b = "EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG"
a = decrypt_caesar(b)
print(a)

#BjYjM2Mjk4NzMR1dIVHs2NzJjY0MTEzM2VhMn0=zQ3NzhhMzhlOD

decode_two

我们知道在encode_two里面,分组所对应的逻辑如下所示:

1(分组前的排序) - 3(分组后的排序)
2 - 1
3 - 4
4 - 2
strncpy(a3, a1 + 26, 0xDui64);
strncpy(a3 + 13, a1, 0xDui64);
strncpy(a3 + 26, a1 + 39, 0xDui64);
strncpy(a3 + 39, a1 + 13, 0xDui64);

所以对应的脚本如下所示:

a = "BjYjM2Mjk4NzMR1dIVHs2NzJjY0MTEzM2VhMn0=zQ3NzhhMzhlOD"

for i in range(0,len(a),13):
    print(a[i:i+13])
"""
BjYjM2Mjk4NzM #3
R1dIVHs2NzJjY #1
0MTEzM2VhMn0= #4
zQ3NzhhMzhlOD #2
"""


我们再手动拼接一下即可."R1dIVHs2NzJjYzQ3NzhhMzhlODBjYjM2Mjk4NzM0MTEzM2VhMn0="

decode_three

直接上Base64解密脚本即可:

import base64
a = "R1dIVHs2NzJjYzQ3NzhhMzhlODBjYjM2Mjk4NzM0MTEzM2VhMn0="
b = base64.b64decode(a)
print(b)
#b'GWHT{672cc4778a38e80cb362987341133ea2}'