ctf

看雪ctf第三赛季第一题解题过程

ctf题解

Posted by gxkyrftx on September 25, 2019

1 描述

防守方在发布CrackMe时,应向大众公开一组用户名和序列号,即 “ Name/Serial ” 。根据这组公开的用户名/序列号,得到密钥,然后再计算出本平台指定用户名“KCTF”得到的序列号即可成功。

判胜条件 1)若攻击方找出特定用户名(“KCTF”,不含引号)的序列号,经KCTF系统自动确认,将认定攻击方获胜;

新玩法,win32逆向

2 解题过程

首先通过运行程序,找到字符串

"【请输入您的用户名与序列号】\n"

然后找到了主函数,sub_8719D0

2.1 sub_8719D0

int sub_8719D0()
{
  unsigned int v0; // kr00_4
  signed int v1; // esi
  unsigned __int8 v2; // cl
  char v3; // dl
  signed int v4; // esi
  __int128 *v5; // edi
  int v6; // eax
  unsigned int v7; // ecx
  int v8; // eax
  unsigned int v9; // esi
  bool v10; // cf
  char *v11; // ecx
  unsigned int v12; // edx
  __int8 v13; // al
  char *v14; // ecx
  unsigned int v15; // edx
  char v16; // al
  __int128 *v17; // ecx
  _DWORD *v18; // edx
  unsigned int v19; // esi
  void *v20; // esi
  unsigned int v22; // [esp+8h] [ebp-E8h]
  int v23; // [esp+Ch] [ebp-E4h]
  int v24; // [esp+10h] [ebp-E0h]
  int v25; // [esp+14h] [ebp-DCh]
  int v26; // [esp+18h] [ebp-D8h]
  int v27; // [esp+1Ch] [ebp-D4h]
  __m128i v28; // [esp+60h] [ebp-90h]
  __int128 v29; // [esp+70h] [ebp-80h]
  __int128 sn; // [esp+80h] [ebp-70h]
  __int128 v31; // [esp+90h] [ebp-60h]
  char v32; // [esp+A0h] [ebp-50h]
  __int128 v33; // [esp+B0h] [ebp-40h]
  __int128 v34; // [esp+C0h] [ebp-30h]
  char v35; // [esp+D0h] [ebp-20h]

  printf((int)"【请输入您的用户名与序列号】\n");
  printf((int)&unk_88E748);
  scanf("%16s", &name, 17);                     // 输入用户名
  v0 = strlen((const char *)&name);
  sub_8729C0((__m128i *)((char *)&name + v0), 0, 16 - v0);// 取前4位,并将后面的所有值补0
  printf((int)&unk_88E760);
  v32 = 0;
  sn = 0i64;
  v31 = 0i64;
  v33 = 0i64;
  v34 = 0i64;
  v35 = 0;
  xmmword_8912F8 = 0i64;
  scanf("%33s", &sn, 33);
  v1 = 0;
  do
  {
    v2 = *((_BYTE *)&sn + 2 * v1);
    v3 = v2 - '7';
    if ( v2 <= '9' )
      v3 = *((_BYTE *)&sn + 2 * v1);
    *((_BYTE *)&xmmword_8912F8 + v1) = *((_BYTE *)&sn + 2 * v1 + 1)
                                     + 16 * v3
                                     - (*((_BYTE *)&sn + 2 * v1 + 1) > (unsigned __int8)'9' ? '7' : '0');
    ++v1;
  }
  while ( v1 < 16 );                            
  v4 = 0;
  v5 = &v33;
  do
  {
    sub_871990((int)v5, "%02X", *((unsigned __int8 *)&xmmword_8912F8 + v4++));
    v5 = (__int128 *)((char *)v5 + 2);
  }
  while ( v4 < 16 );                            // 加载到内存中
  v6 = strcmp((const char *)&sn, (const char *)&v33);// 比较16字节数据是否相同,相同返回0
  if ( v6 )
    v6 = -(v6 < 0) | 1;
  if ( v6 )
  {
    printf((int)" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");
    printf((int)&unk_88E770);                   // 请输入合法序列号
  }
  else
  {
    v22 = 0;
    v23 = 0;
    v24 = 0x67452301;
    v25 = 0xEFCDAB89;
    v28 = _mm_xor_si128((__m128i)name, (__m128i)xmmword_8912F8);// 异或结果为:其实我更喜欢孙坚
    v26 = 0x98BADCFE;
    v27 = 0x10325476;
    v29 = 0i64;
    sub_871000((int)&v28, &v22, 0x10u);         // md5
    v7 = (v22 >> 3) & 0x3F;                     // v22=0x80
    v8 = 'x' - v7;                              // v7=0x10
    v9 = '8' - v7;
    v10 = v7 < 0x38;
    v11 = (char *)&v22 + 1;
    if ( !v10 )
      v9 = v8;
    v12 = 0;
    do
    {
      v13 = *(v11 - 1);
      v11 += 4;
      v28.m128i_i8[v12] = v13;
      v28.m128i_i8[v12 + 1] = *(v11 - 4);
      v28.m128i_i8[v12 + 2] = *(v11 - 3);
      v28.m128i_i8[v12 + 3] = *(v11 - 2);
      v12 += 4;
    }
    while ( v12 < 8 );                          // 将v28第一个字节改为0x80,前几个置0,后8个不变
    sub_871000((int)&unk_8908C0, &v22, v9);
    sub_871000((int)&v28, &v22, 8u);
    v14 = (char *)&v24 + 1;
    v15 = 0;
    do
    {
      v16 = *(v14 - 1);
      v14 += 4;
      *((_BYTE *)&v29 + v15) = v16;
      *((_BYTE *)&v29 + v15 + 1) = *(v14 - 4);
      *((_BYTE *)&v29 + v15 + 2) = *(v14 - 3);
      *((_BYTE *)&v29 + v15 + 3) = *(v14 - 2);
      v15 += 4;
    }
    while ( v15 < 0x10 );                       // V14的值放到v29
    v17 = &v29;
    v18 = &unk_8908B0;
    v19 = 12;
    while ( *(_DWORD *)v17 == *v18 )            // 主要验证逻辑
    {
      v17 = (__int128 *)((char *)v17 + 4);
      ++v18;
      v10 = v19 < 4;
      v19 -= 4;
      if ( v10 )
      {
        v20 = &unk_88E700;                      // 正确提示
        goto LABEL_22;
      }
    }
    v20 = &unk_88E714;                          // 错误提示
LABEL_22:
    printf((int)" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");
    printf((int)v20);
  }
  sub_87507E("\npause\n");
  return 0;
}

##

2.2 程序的主要逻辑

  1. 输入用户名,长度不够16字节,用0x00补齐
  2. 输入序列号,转化为16进制,然后存放到内存
  3. 然后输入序列号与转化为十六进制后再编码,进行比较,也就是一个自检,输出的结果放在v6中。根据v6的值判断输入的序列号是不是合法
  4. 然后存入几个幻数,幻数一看就是md5,然后可以点进去看一些位运算特征,进一步确定(不是关键)
  5. md5算法的输入参数是:幻数+异或结果v28,也就是用户名(name)异或序列号(xmmword_8912F8)的结果。
  6. 然后就是关键的比较v17 == *v18,也就是判断由异或结果计算的哈希,是不是和原来的哈希:DA E5 23 10 06 71 95 71 4B A2 CE E2 33 2B B8 66,相等.

3 破解思想

根据规则,给定用户名和序列号正确的异或结果是:C6 E4 CA B5 CE D2 B8 FC CF B2 BB B6 CB EF BC E1‘其实我更喜欢孙坚’。

需要构造的用户名是:KCTF,所以两者异或,即可解题,脚本如下:

sn=[0xC6,0xE4,0xCA,0xB5,0xCE,0xD2,0xB8,0xFC,0xCF,0xB2,0xBB,0xB6,0xCB,0xEF,0xBC,0xE1]
name=[0x4b,0x43,0x54,0x46]
flag=[]
for i in range(4):
	flag.append(sn[i]^name[i])
for i in range(4,len(sn)):
	flag.append(sn[i]^0x00)
print flag
flag1=''
for i in flag:
	flag1+=chr(i).encode('hex')
print flag1.upper()

本文访问量: