[2019红帽杯]easyRE
新手一枚,如有错误(不足)请指正,谢谢!!

<mark>个人博客:点击进入</mark>
题目链接:BUUCTF-re-[2019红帽杯]xx
题目下载:点击下载

IDA64位载入
shift+F12查看字符串

交叉引用找到关键代码

使用findcrypt插件找到加密算法为tea系列,进函数内部查看知道为xxtea

__int64 __fastcall sub_1400011A0(__int64 a1, __int64 a2)
{
  unsigned __int64 v2; // rbx
  signed __int64 v3; // rax
  __int128 *v4; // rax
  __int64 v5; // r11
  __int128 *v6; // r14
  int v7; // edi
  __int128 *v8; // rsi
  char v9; // r10
  int v10; // edx
  __int64 v11; // r8
  unsigned __int64 v12; // rcx
  signed __int64 v13; // rcx
  unsigned __int64 v14; // rax
  unsigned __int64 i; // rax
  _BYTE *v16; // rax
  size_t v17; // rsi
  _BYTE *v18; // rbx
  _BYTE *v19; // r9
  signed int v20; // er11
  char *v21; // r8
  signed __int64 v22; // rcx
  char v23; // al
  signed __int64 v24; // r9
  signed __int64 v25; // rdx
  __int64 v26; // rax
  size_t Size; // [rsp+20h] [rbp-48h]
  __int128 v29; // [rsp+28h] [rbp-40h]
  int v30; // [rsp+38h] [rbp-30h]
  int v31; // [rsp+3Ch] [rbp-2Ch]
  int user_input[4]; // [rsp+40h] [rbp-28h]
  int v33; // [rsp+50h] [rbp-18h]

  *(_OWORD *)user_input = 0i64;
  v33 = 0;
  sub_1400018C0(std::cin, a2, user_input);      // 读取用户输入
  v2 = -1i64;
  v3 = -1i64;
  do
    ++v3;
  while ( *((_BYTE *)user_input + v3) );        // 计算user_input的长度,即v3
  if ( v3 != 19 )                               // 输入的长度要等于19
  {
    sub_140001620(std::cout, "error\n");
    _exit((unsigned __int64)user_input);
  }
  v4 = (__int128 *)sub_140001E5C(5ui64);        // v4为动态分配出的一块长度为5的空间
  v5 = *(_QWORD *)&Code;                        // v5为内存中存储的数据
                                                // 为 "qwertyuiopasdfghjklzxcvbnm1234567890"
  v6 = v4;
  v7 = 0;
  v8 = v4;                                      // v6=v8=v4
  do
  {
    v9 = *((_BYTE *)v8 + (char *)user_input - (char *)v4);// v9 = user_input
    v10 = 0;
    *(_BYTE *)v8 = v9;
    v11 = 0i64;
    v12 = -1i64;
    do
      ++v12;
    while ( *(_BYTE *)(v5 + v12) );             // v12 计算内存中存储数据的长度
    if ( v12 )
    {
      do
      {
        if ( v9 == *(_BYTE *)(v5 + v11) )       // 输入的字符,是在内存存储的数据里中的一个元素
          break;
        ++v10;
        ++v11;
      }
      while ( v10 < v12 );
    }
    v13 = -1i64;
    do
      ++v13;
    while ( *(_BYTE *)(v5 + v13) );             // v12 计算内存中存储数据的长度
    if ( v10 == v13 )
      _exit(v5);
    v8 = (__int128 *)((char *)v8 + 1);
  }
  while ( (char *)v8 - (char *)v4 < 4 );        // 54~83行
                                                // 输入数据的前四个元素能在内存中存储的数据中找到对应
  *((_BYTE *)v4 + 4) = 0;                       // v4这块动态分配的内存空间中存放着 输入的前四个元素和一个0
  do
    ++v2;
  while ( *((_BYTE *)user_input + v2) );        // v2为输入字符串的长度
  v14 = 0i64;
  v29 = *v6;                                    // v6等于v4 85行
  while ( *((_BYTE *)&v29 + v14) )
  {
    if ( !*((_BYTE *)&v29 + v14 + 1) )
    {
      ++v14;
      break;
    }
    if ( !*((_BYTE *)&v29 + v14 + 2) )
    {
      v14 += 2i64;
      break;
    }
    if ( !*((_BYTE *)&v29 + v14 + 3) )
    {
      v14 += 3i64;
      break;
    }
    v14 += 4i64;
    if ( v14 >= 16 )
      break;
  }
  for ( i = v14 + 1; i < 16; ++i )
    *((_BYTE *)&v29 + i) = 0;                   // 将四个字符之后的置零
  v16 = sub_140001AB0((__int64)user_input, v2, (unsigned __int8 *)&v29, &Size);                                         //xxtea加密
  v17 = Size;                                   // 加密后的长度
  v18 = v16;
  v19 = sub_140001E5C(Size);                    // 动态分配一块加密后字符串长度给v19
  v20 = 1;
  *v19 = v18[2];
  v21 = v19 + 1;
  v19[1] = *v18;
  v19[2] = v18[3];
  v19[3] = v18[1];
  v19[4] = v18[6];
  v19[5] = v18[4];
  v19[6] = v18[7];
  v19[7] = v18[5];
  v19[8] = v18[10];
  v19[9] = v18[8];
  v19[10] = v18[11];
  v19[11] = v18[9];
  v19[12] = v18[14];
  v19[13] = v18[12];
  v19[14] = v18[15];
  v19[15] = v18[13];
  v19[16] = v18[18];
  v19[17] = v18[16];
  v19[18] = v18[19];
  v19[19] = v18[17];
  v19[20] = v18[22];
  v19[21] = v18[20];
  v19[22] = v18[23];                            // 排序
  for ( v19[23] = v18[21]; v20 < v17; ++v21 )   // v21=v19+1
  {
    v22 = 0i64;
    if ( v20 / 3 > 0 )
    {
      v23 = *v21;
      do
      {
        v23 ^= v19[v22++];
        *v21 = v23;
      }
      while ( v22 < v20 / 3 );
    }
    ++v20;
  }
  *(_QWORD *)&v29 = 0xC0953A7C6B40BCCEi64;
  v24 = v19 - (_BYTE *)&v29;
  *((_QWORD *)&v29 + 1) = 0x3502F79120209BEFi64;
  v25 = 0i64;
  v30 = 0xC8021823;
  v31 = 0xFA5656E7;
  do
  {
    if ( *((_BYTE *)&v29 + v25) != *((_BYTE *)&v29 + v25 + v24) )
      _exit(v7 * v7);
    ++v7;
    ++v25;
  }
  while ( v25 < 24 );
  v26 = sub_140001620(std::cout, "You win!");
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v26, sub_1400017F0);
  return 0i64;
}

流程为

  1. 先判断输入的字符串是否都在程序实现存储的数据Code中
  2. 然后取前四个字符作为xxtea的密钥,(不满位数右端补零)
  3. 然后对输入的字符串进行加密
  4. 之后对加密的字符串打乱顺序
  5. 之后异或操作
  6. 再与存储的数据进行比对

其中第6部存储的数据为
v29,v29+1,v30,v31在栈上存放的位置相连。小端序存放,需将其反过来写。

开始写脚本

data0 = "CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA"#为提取出来的v29,v29+1,v30,v31
data = []
for i in range(0,len(data0),2):
    data.append(int(data0[i]+data0[i+1],16))#每两位为整体,将16进制转换为10进制
print(data)
for i in range(len(data)-1,-1,-1):
    for j in range(i//3):
        data[i] ^= data[j]
        #进行的异或操作
print(data)
biao = [2,0,3,1,6,4,7,5,10,8,11,9,14,12,15,13,18,16,19,17,22,20,23,21]#置换表
shuju = [1]*24
for i in range(24):
    shuju[biao[i]] = data[i]#将其按照一定顺序置换
print(shuju)
print(len(shuju))

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "encode.h"
int main()
{
	int i,j,data[] = { 188, 165, 206, 64, 244, 178, 178, 231, 169, 18, 157, 18, 174, 16, 200, 91, 61, 215, 6, 29, 220, 112, 248, 220 };
	for (i = 1; i <= 6; i++)//导出加密的数据,将其转换为十六进制并用小端序来表示
	{
		printf("0x");
		for (j = i * 4 - 1; j > (i - 1) * 4 - 1; j--)
			printf("%x", data[j]);
		printf(",");
	}
	printf("\n0x");
	int key[] = { 'f','l','a','g' };//导出密钥,将其转换为十六进制并用小端序来表示
	for (i = 3; i >= 0; i--)
		printf("%x", key[i]);
	printf(",");
	for (i = 0; i < 3; i++)//密钥为4个32位的数,1个字符4bit,4个字符为32bit,还差3个32bit的数
		printf("0x0,");
}


解密得到十六进制字符串,这里用到了我自己写的头文件。头文件所用函数为

#define xxtea_DELTA 0x9e3779b9
#define xxtea_MX (((xxtea_z>>5^xxtea_y<<2) + (xxtea_y>>3^xxtea_z<<4)) ^ ((xxtea_sum^xxtea_y) + (xxtea_key[(xxtea_p&3)^xxtea_e] ^ xxtea_z)))
void xxtea(uint32_t* xxtea_origin, int xxtea_n, uint32_t const xxtea_key[4])
{
    uint32_t xxtea_y, xxtea_z, xxtea_sum;
    unsigned xxtea_p, xxtea_rounds, xxtea_e;
    if (xxtea_n > 1)            /* Coding Part */
    {
        xxtea_rounds = 6 + 52 / xxtea_n;
        xxtea_sum = 0;
        xxtea_z = xxtea_origin[xxtea_n - 1];
        do
        {
            xxtea_sum += xxtea_DELTA;
            xxtea_e = (xxtea_sum >> 2) & 3;
            for (xxtea_p = 0; xxtea_p < xxtea_n - 1; xxtea_p++)
            {
                xxtea_y = xxtea_origin[xxtea_p + 1];
                xxtea_z = xxtea_origin[xxtea_p] += xxtea_MX;
            }
            xxtea_y = xxtea_origin[0];
            xxtea_z = xxtea_origin[xxtea_n - 1] += xxtea_MX;
        } while (--xxtea_rounds);
    }
    else if (xxtea_n < -1)      /* Decoding Part */
    {
        xxtea_n = -xxtea_n;
        xxtea_rounds = 6 + 52 / xxtea_n;
        xxtea_sum = xxtea_rounds * xxtea_DELTA;
        xxtea_y = xxtea_origin[0];
        do
        {
            xxtea_e = (xxtea_sum >> 2) & 3;
            for (xxtea_p = xxtea_n - 1; xxtea_p > 0; xxtea_p--)
            {
                xxtea_z = xxtea_origin[xxtea_p - 1];
                xxtea_y = xxtea_origin[xxtea_p] -= xxtea_MX;
            }
            xxtea_z = xxtea_origin[xxtea_n - 1];
            xxtea_y = xxtea_origin[0] -= xxtea_MX;
            xxtea_sum -= xxtea_DELTA;
        } while (--xxtea_rounds);
    }
}

因为用户输入的为19位字符,也就是38位十六进制字符串,而上面生成了40位,所以要去掉最后两位,也就是去掉13。
最后将生成的十六进制数用小端序表示

得到flag

最后粘贴一下写的代码:

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "encode.h"
int main()
{
#if 0
	int i,j,data[] = { 188, 165, 206, 64, 244, 178, 178, 231, 169, 18, 157, 18, 174, 16, 200, 91, 61, 215, 6, 29, 220, 112, 248, 220 };
	for (i = 1; i <= 6; i++)//导出加密的数据,将其转换为十六进制并用小端序来表示
	{
		printf("0x");
		for (j = i * 4 - 1; j > (i - 1) * 4 - 1; j--)
			printf("%x", data[j]);
		printf(",");
	}
	printf("\n0x");
	int key[] = { 'f','l','a','g' };//导出密钥,将其转换为十六进制并用小端序来表示
	for (i = 3; i >= 0; i--)
		printf("%x", key[i]);
	printf(",");
	for (i = 0; i < 3; i++)//密钥为4个32位的数,1个字符4bit,4个字符为32bit,还差3个32bit的数
		printf("0x0,");

	uint32_t enc[6] = { (unsigned int)0x40cea5bc,(unsigned int)0xe7b2b2f4,(unsigned int)0x129d12a9,(unsigned int)0x5bc810ae,(unsigned int)0x1d06d73d,(unsigned int)0xdcf870dc };
	int n = -6;
	uint32_t const key[4] = { (unsigned int)0x67616c66,(unsigned int)0x0,(unsigned int)0x0,(unsigned int)0x0 };
	btea(enc, n, key);
	for (int i = 0; i < 6; i++)
		printf("%x", enc[i]);
#endif
	char string[] = "67616c665858437b646e615f742b2b5f7d6165";
	int i = 1,j=0;
	for (;i<6;i++)
	{
		for (j = 0; j < 8; j += 2)
		{
			if (j == 0 && i == 5)
				continue;
			printf("%c%c", string[i * 8 - j - 2], string[i * 8 - j - 1]);
		}
	}

}

flag为flag{CXX_and_++tea}