首页
论坛
课程
招聘
[原创]KCTF2022春季赛第三题 解题过程
2022-5-16 00:33 3458

[原创]KCTF2022春季赛第三题 解题过程

2022-5-16 00:33
3458

1.初步流程分析

题目流程比较简洁,初步判断该exe定义了一个大的结构体进行操作,简单根据偏移量进行了结构体判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct context{
    int iterator1;   //0
    int unknow1;     //1
    unsigned int md5[5];       //2-6
    int unknow7;    //7
    int unknow8;    //8
    byte keys[16];  //9-12
    int unknow13;    //13
    int unknow14;    //14
    int unknow15;    //15
    int iterator2;   //16
    int unknow17;    //17
    unsigned int cipher[10];      //18-27
    int unknow28;    //28
    int unknow29;    //29
    char sn[4000];
    int a3;
    int a4;
};

重新对main进行解析,比较友好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  void *v3; // esp
  int result; // eax
  char v7; // [esp-10h] [ebp-1048h]
  context ctx; // [esp+0h] [ebp-1038h] BYREF
  CPPEH_RECORD ms_exc; // [esp+1020h] [ebp-18h]
 
  v3 = alloca(4128);
  memset(&ctx, 0xCCu, sizeof(ctx));
 
  ms_exc.registration.ScopeTable = (ms_exc.registration.ScopeTable ^ __security_cookie);
 
  memset(ctx.sn, 0, sizeof(ctx.sn));
 
  ctx.cipher[0] = 0;
  ctx.cipher[1] = 0;
  ctx.cipher[2] = 0;
  ctx.cipher[3] = 0;
  ctx.cipher[4] = 0;
  ctx.cipher[5] = 0;
  ctx.cipher[6] = 0;
  ctx.cipher[7] = 0;
  ctx.cipher[8] = 0;
  ctx.cipher[9] = 0;
 
  gets_s(ctx.sn, 4000u);
 
  if ( strlen(ctx.sn) == 32 )
  {
    for ( ctx.iterator2 = 0; ctx.iterator2 < 512; ++ctx.iterator2 )
    {
      ctx.unknow15 = 0;
      MEMORY[0] = ctx.iterator2;
      ms_exc.registration.TryLevel = 0xFFFFFFFE;
    }
 
    strcpy(ctx.keys, "Enj0y_1t_4_fuuuN");       // 16bytes
    _DX = 0x5F00;
    *(&ctx.unknow13 + 1) = 0;
    HIBYTE(ctx.unknow13) = 0;
 
    ctx.md5[0] = 0;
    ctx.md5[1] = 0;
    ctx.md5[2] = 0;
    ctx.md5[3] = 0;
    ctx.md5[4] = 0;
 
    for ( ctx.iterator1 = 0; ctx.iterator1 < 512; ++ctx.iterator1 )
    {
      __asm { insb }
      ms_exc.registration.TryLevel = -2;
      _DX = LOWORD(ctx.iterator1) + 1;
    }
 
    j_MD5_402DB0(ctx.keys, 0x10u, ctx.md5);
 
    j_aes_402070(ctx.md5, 0x10u, ctx.sn, ctx.cipher, 32);
 
    if ( !memcmp(ctx.cipher, byte_40B200, 0x20u) )
      printf_401037("OK\n", v7);
    else
      printf_401037("NO\n", v7);
    result = 0;
  }
  else
  {
    printf_401037("NO\n", v7);
    result = 0;
  }
  return result;
}

存在较多的反调试。

 

流程较为清晰:
1、keys = MD5(Enj0y_1t_4_fuuuN)
2、AES(keys , 16bytes , plain , cipher , 32bytes) == byte_40B200[0:20]

 

直接进行解密计算发现是乱码,判断算法进行了改变。

2.算法分析

2.1 MD5分析

直接使用题目中的MD5(Enj0y_1t_4_fuuuN),反正不会改变,看了别人的帖子才注意到这里修改了MD5的常数值。

1
2
3
4
5
int sub_402C60()
{
  MD5_Constants_40B298[42] = 0xD4AF3085;
  return 1;
}

2.2 AES分析

整个AES算法结构比较标准,在正常的128bit密钥流程中增加了对16字节输入输出的转置操作。

 

需要找一份类似的代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int __cdecl aes_402070(void *Src, size_t KeySize, unsigned __int8 input[], unsigned __int8 uccipher[], int iLen)
{
  int j; // [esp+4h] [ebp-1C8h]
  unsigned int i; // [esp+8h] [ebp-1C4h]
  int state[6]; // [esp+10h] [ebp-1BCh] BYREF
  int key[11]; // [esp+28h] [ebp-1A4h] BYREF
  unsigned int *iKeyOffset; // [esp+54h] [ebp-178h]
  unsigned __int8 *__attribute__((__org_arrdim(0,0))) output; // [esp+58h] [ebp-174h]
  unsigned int KeyExtend[90]; // [esp+60h] [ebp-16Ch] BYREF
 
  output = uccipher;
  iKeyOffset = KeyExtend;
  key[6] = 0;
  key[7] = 0;
  key[8] = 0;
  key[9] = 0;
  key[0] = 0;
  key[1] = 0;
  key[2] = 0;
  key[3] = 0;
  state[0] = 0;
  state[1] = 0;
  state[2] = 0;
  state[3] = 0;
 
  if ( !Src || !input || !uccipher )
    return -1;
  if ( KeySize > 0x10 )
    return -1;
  if ( iLen % 0x10u )
    return -1;
 
  memcpy(key, Src, KeySize);
 
  KeyExtend_4010BE(key, 16, KeyExtend);
 
  for ( i = 0; i < iLen; i += 16 )
  {
    input_transpose_401145(state, input);
    AddRoundKey_401186(state, iKeyOffset);
 
    for ( j = 1; j < 10; ++j )
    {
      iKeyOffset += 4;
      ByteSub_401172(state);
      ShiftRow_40122B(state);
      MixColumn_40100F(state);
      AddRoundKey_401186(state, iKeyOffset);
    }
    ByteSub_401172(state);
    ShiftRow_40122B(state);
    AddRoundKey_401186(state, iKeyOffset + 4);
 
    output_transpose_40125D(state, output);
 
    output += 16;
    input += 16;
    iKeyOffset = KeyExtend;
  }
  return 0;
}

2.2.1 KeyExtend_4010BE

密钥扩展函数正常。

2.2.2 ByteSub_401172

字节替换函数中,查表方式没变,但是查看交叉引用时,发现sub_402330函数对其进行了修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl SubBytes_401A60(int a1[4][4])
{
  int j; // [esp+0h] [ebp-8h]
  int i; // [esp+4h] [ebp-4h]
 
  for ( i = 0; i < 4; ++i )
  {
    for ( j = 0; j < 4; ++j )
      *(&(*a1)[i] + j) = RijnDael_AES_LONG_40B000[*(&(*a1)[i] + j)];
  }
  return 0;
}
int sub_402330()
{
  RijnDael_AES_LONG_40B000[113] ^= RijnDael_AES_LONG_40B000[163];
  RijnDael_AES_LONG_40B000[163] ^= RijnDael_AES_LONG_40B000[113];
  RijnDael_AES_LONG_40B000[113] ^= RijnDael_AES_LONG_40B000[163];
  return 1;
}

原S盒中的:SBox[0x71] = 0xa3、SBox[0xa3]=0x0a,
这里修改为了:SBox[0x71] = 0x0a、SBox[0xa3]=0xa3

 

因此解密inv_sub_bytes的时候,RBox也应该进行相应的修改:
原本RBox[0xA3] = 0x71、RBox[0x0A] = 0xA3
修改为:RBox[0xA3] = 0xA3、RBox[0x0A] = 0x71

2.2.3 ShiftRow_40122B

反编译时提示401bef无法转换,直接nop掉反调试的指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl sub_401AE0(byte a1[4][4])
{
  int i; // [esp+10h] [ebp-38h]
  int v3[6]; // [esp+18h] [ebp-30h]
  CPPEH_RECORD ms_exc; // [esp+30h] [ebp-18h]
 
  v3[4] = 0xCCCCCCCC;
  v3[5] = 0xCCCCCCCC;
  v3[0] = 0;
  v3[1] = 0;
  v3[2] = 0;
  v3[3] = 0;
  for ( i = 0; i < 4; ++i )
  {
    v3[i] = _byteswap_ulong(*&(*a1)[4 * i]);
    v3[i] |= v3[i] >> (32 - 8 * i);
    ms_exc.registration.TryLevel = -2;
    (*a1)[4 * i] = HIBYTE(v3[i]);
    (*a1)[4 * i + 1] = BYTE2(v3[i]);
    (*a1)[4 * i + 2] = BYTE1(v3[i]);
    (*a1)[4 * i + 3] = v3[i];
  }
  return 0;
}

该题目对行变换函数进行了修改。直接分析16字节输入,魔改的变换如下:
0 1 2 3 -> 0 1 2 3
4 5 6 7 -> 7 4 5 6
8 9 10 11 -> 10 11 8 9
12 13 14 15 -> 13 14 15 12

 

在进行inv_shift_rows直接对如上变换方式进行逆变换即可。

2.2.4 MixColumn_40100F

列混淆函数中,计算时取的是state矩阵的转置,在编写Inv_MixColumn时也在取计算结果时,取state矩阵的转置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int __cdecl sub_401E50(byte state[4][4])
{
  char v1; // bl
  char v2; // bl
  char v3; // bl
  int j_; // [esp+Ch] [ebp-40h]
  int i_; // [esp+10h] [ebp-3Ch]
  int j; // [esp+14h] [ebp-38h]
  int i; // [esp+18h] [ebp-34h]
  byte a[4][4]; // [esp+20h] [ebp-2Ch] BYREF
  byte state_[4][4]; // [esp+38h] [ebp-14h] BYREF
 
  *&a[0][0] = 0x1010302;                        // 2 3 1 1
  *&a[1][0] = 0x1030201;
  *&a[2][0] = 0x3020101;
  *&a[3][0] = 0x2010103;
 
  for ( i = 0; i < 4; ++i )
  {
    for ( j = 0; j < 4; ++j )
      state_[i][j] = (*state)[4 * i + j];
  }
  for ( i_ = 0; i_ < 4; ++i_ )
  {
    for ( j_ = 0; j_ < 4; ++j_ )
    {
      v1 = XTIME_401118(a[i_][0], state_[0][j_]);
      v2 = XTIME_401118(a[i_][1], state_[1][j_]) ^ v1;
      v3 = XTIME_401118(a[i_][2], state_[2][j_]) ^ v2;
      (*state)[4 * i_ + j_] = XTIME_401118(a[i_][3], state_[3][j_]) ^ v3;
    }
  }
  return 0;
}

2.2.5 AddRoundKey_401186

加轮密钥中,也对要异或的16字节密钥进行了转置操作ExtendKey[i][j] = u32Extendkey[j] >> (8 * (3 - i))。解密时直接调用该函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl sub_401970(byte state[4][4], unsigned int u32Extendkey[4])
{
  int j; // [esp+0h] [ebp-20h]
  int i; // [esp+4h] [ebp-1Ch]
  byte ExtendKey[4][4]; // [esp+Ch] [ebp-14h] BYREF
  int v6; // [esp+1Ch] [ebp-4h]
 
  *&ExtendKey[0][0] = 0xCCCCCCCC;
  *&ExtendKey[1][0] = 0xCCCCCCCC;
  *&ExtendKey[2][0] = 0xCCCCCCCC;
  *&ExtendKey[3][0] = 0xCCCCCCCC;
  v6 = 0xCCCCCCCC;
 
  for ( i = 0; i < 4; ++i )
  {
    for ( j = 0; j < 4; ++j )
    {
      ExtendKey[i][j] = u32Extendkey[j] >> (8 * (3 - i));
      (*state)[4 * i + j] ^= ExtendKey[i][j];
    }
  }
  return 0;
}

3. 解密

根据对AES加密函数相应的修改,编写对应的解密函数。直接对byte_40B200开始的32字节进行解密,发现不对,直接获取内存值发现有修改,查看交叉引用,发现sub_4023D0对校验值进行修改。求解得到flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void aes_decrypt(uint8_t *in, uint8_t *out, uint8_t *w) {
 
    uint8_t state[4*Nb];
    uint8_t r, i, j;
 
    for (i = 0; i < 4; i++) {
        for (j = 0; j < Nb; j++) {
            state[Nb*i+j] = in[j+4*i];  //transposition
        }
    }
 
    add_round_key(state, w, Nr);
 
    for (r = Nr-1; r >= 1; r--) {
        inv_shift_rows(state);
        inv_sub_bytes(state);
        add_round_key(state, w, r);
        inv_mix_columns(state);
    }
 
    inv_shift_rows(state);
    inv_sub_bytes(state);
    add_round_key(state, w, 0);
 
    for (i = 0; i < 4; i++) {
        for (j = 0; j < Nb; j++) {
            out[i+4*j] = state[Nb*j+i];  //transposition
        }
    }
}
int sub_4023D0()
{
  byte_40B200[16] = 0xF4;
  byte_40B200[17] = 0xF2;
  return 1;
}

看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2022-5-16 20:50 被kanxue编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回