首页
论坛
课程
招聘
[原创]看雪.安恒2020 KCTF春季赛 > 第七题 杯弓蛇影 wp
2020-4-30 20:37 3021

[原创]看雪.安恒2020 KCTF春季赛 > 第七题 杯弓蛇影 wp

2020-4-30 20:37
3021
由于程序是动态解码代码的,所以为了在IDA中分析,等解码完成DUMP出来,扔IDA,分析见注释:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int128 *v3; // esi
  int v4; // eax
  int v5; // eax
  __int64 v6; // rtt
  int v7; // ecx
  int result; // eax
  BYTE *v9; // eax
  int *v10; // esi
  signed int v11; // edi
  unsigned __int8 v12; // cl
  unsigned __int8 v13; // cl
  BYTE v14; // bl
  unsigned __int8 v15; // bl
  unsigned __int8 v16; // dl
  unsigned __int8 v17; // dl
  unsigned __int8 v18; // dl
  unsigned __int8 v19; // dl
  unsigned __int8 v20; // dl
  unsigned __int8 v21; // dl
  unsigned __int8 v22; // dl
  unsigned __int8 v23; // dl
  unsigned __int8 v24; // dl
  unsigned __int8 v25; // dl
  unsigned __int8 v26; // dl
  unsigned __int8 v27; // dl
  char *v28; // edi
  __int64 v29; // kr00_8
  int v30; // eax
  int v31; // ebx
  char *v32; // esi
  int v33; // eax
  char *v34; // ecx
  int v35; // ecx
  signed int v36; // eax
  int v37; // ST00_4
  int v38; // ST04_4
  int i; // eax
  bool v40; // zf
  int v41; // edx
  signed int v42; // esi
  int v43; // ecx
  signed int v44; // ecx
  unsigned __int8 v45; // [esp+10h] [ebp-2F68h]
  int v46; // [esp+10h] [ebp-2F68h]
  unsigned __int8 v47; // [esp+14h] [ebp-2F64h]
  unsigned __int8 v48; // [esp+18h] [ebp-2F60h]
  unsigned __int8 v49; // [esp+1Ch] [ebp-2F5Ch]
  unsigned __int8 v50; // [esp+20h] [ebp-2F58h]
  int v51; // [esp+28h] [ebp-2F50h]
  int v52; // [esp+2Ch] [ebp-2F4Ch]
  int v53; // [esp+3Ch] [ebp-2F3Ch]
  char sName[32]; // [esp+48h] [ebp-2F30h]
  char v55; // [esp+68h] [ebp-2F10h]
  int v56; // [esp+78h] [ebp-2F00h]
  LPVOID lpMem; // [esp+84h] [ebp-2EF4h]
  char v58; // [esp+88h] [ebp-2EF0h]
  char v59; // [esp+D0h] [ebp-2EA8h]
  int *v60; // [esp+D8h] [ebp-2EA0h]
  int v61; // [esp+DCh] [ebp-2E9Ch]
  BYTE *v62; // [esp+E0h] [ebp-2E98h]
  int v63; // [esp+E4h] [ebp-2E94h]
  BYTE *v64; // [esp+E8h] [ebp-2E90h]
  int v65; // [esp+ECh] [ebp-2E8Ch]
  int *v66; // [esp+F0h] [ebp-2E88h]
  int v67; // [esp+F4h] [ebp-2E84h]
  int *v68; // [esp+100h] [ebp-2E78h]
  int v69; // [esp+104h] [ebp-2E74h]
  __int128 v70; // [esp+120h] [ebp-2E58h]
  __int128 v71; // [esp+130h] [ebp-2E48h]
  __int64 v72; // [esp+140h] [ebp-2E38h]
  char v73; // [esp+148h] [ebp-2E30h]
  char sSN[1000]; // [esp+198h] [ebp-2DE0h]
  char v75; // [esp+580h] [ebp-29F8h]

  sName[0] = 0;
  *(_QWORD *)&sName[17] = 0i64;
  *(_OWORD *)&sName[1] = 0i64;
  *(_DWORD *)&sName[25] = 0;
  *(_WORD *)&sName[29] = 0;
  sName[31] = 0;
  memset(sSN, 0, 0x3E8u);
  sub_401070("Please input Username:");
  sub_4010B0("%s", sName, 32);
  sub_401070("Please input Serial:");
  sub_4010B0("%s");
  sub_4011A0(sName, v3);                        // 用sName初始化Hash表,共0xC80字节,前0x60放hn0x00_0x5f,后面的放到hn0x60_0xC7f
  v4 = strlen(sSN);
  v6 = v4;
  v5 = v4 / 24;
  v7 = v5;
  v52 = v5;
  if ( (unsigned int)(v6 % 24) )                // sSN长度为24的倍数
  {
    sub_401070("Sorry!the Serial is wrong!\n");
LABEL_3:
    sub_41B6BF("pause");
    result = 0;
  }
  else
  {
    if ( v5 > 0 )
    {
      v9 = (BYTE *)&sSN[1];
      v51 = v7;
      v10 = sn;                                 // 转换后的放BYTE数组sn中
      do
      {
        v11 = 3;
        do
        {
          v12 = *(v9 - 1);
          if ( v12 <= '9' )
            v13 = v12 - '0';
          else
            v13 = v12 - 55;
          v14 = *v9;
          if ( *v9 <= '9' )
            v15 = v14 - '0';
          else
            v15 = v14 - 55;
          v16 = v9[1];
          if ( v16 <= '9' )
            v17 = v16 - '0';
          else
            v17 = v16 - 55;
          v45 = v17;
          v18 = v9[2];
          if ( v18 <= '9' )
            v19 = v18 - '0';
          else
            v19 = v18 - 55;
          v47 = v19;
          v20 = v9[3];
          if ( v20 <= '9' )
            v21 = v20 - '0';
          else
            v21 = v20 - 55;
          v48 = v21;
          v22 = v9[4];
          if ( v22 <= '9' )
            v23 = v22 - '0';
          else
            v23 = v22 - 55;
          v49 = v23;
          v24 = v9[5];
          if ( v24 <= '9' )
            v25 = v24 - '0';
          else
            v25 = v24 - 55;
          v50 = v25;
          v26 = v9[6];
          if ( v26 <= '9' )
            v27 = v26 - '0';
          else
            v27 = v26 - 55;
          v9 += 8;
          *v10 = v27 | 16 * (v50 | 16 * (v49 | 16 * (v48 | 16 * (v47 | 16 * (v45 | 16 * (v15 | 16 * v13))))));// 8字节转一个DWORD
          ++v10;
          --v11;
        }
        while ( v11 );
        --v51;
      }
      while ( v51 );
      v7 = v52;
    }
    v28 = hn0x00_0x5f;
    v53 = v7 - 8;
    v29 = 4i64 * v7 - 32;
    v30 = 0;
    v46 = 0;
LABEL_35:
    v31 = *(_DWORD *)v28;
    v32 = &aVm[v30];
    v33 = strlen(&aVm[v30]);                    // 依次对三个常量串再分别加 hashName[0-12]中的4个字节计算256位hash值,对于相同的sName,这三个hash值是不变的,可认为是常量
                                                // aVM[3][256] = {"这杀软好多呀,好像是个VM","这机器里文件修改时间分布广","伊娃找到了理想植物"}
    v32[v33 + 3] = v31;
    v34 = &v32[v33];
    v34[2] = BYTE1(v31);
    v34[1] = BYTE2(v31);
    *v34 = HIBYTE(v31);
    v34[4] = 0;
    v35 = 0;
    v36 = 0;
    while ( v32[v36] )
    {
      if ( !v32[v36 + 1] )
      {
        ++v35;
        break;
      }
      if ( !v32[v36 + 2] )
      {
        v35 += 2;
        break;
      }
      if ( !v32[v36 + 3] )
      {
        v35 += 3;
        break;
      }
      if ( !v32[v36 + 4] )
      {
        v35 += 4;
        break;
      }
      v36 += 5;
      v35 += 5;
      if ( v36 >= 2000 )
        break;
    }
    hash256(hash1, (int)v32, v35);              // 计算hash,因为只同sName有关,可认为是常量,用KCTF为名,在这儿三次可从返回中取到常量数据
    sub_401810(&v75);                           // VM初始化
    v56 = 0;
    memset(&v58, 0, 0x48u);
    memset(&v59, 0, 0x50u);
    v70 = 0i64;
    v71 = 0i64;
    v72 = 0i64;
    memset(&v73, 0, 0x50u);
    sub_40F5E0(&v75, 0x29F1);
    v60 = sn;                                   // 将 SN 传到VM中
    v61 = (signed int)sn >> 31;
    v62 = hash1;                                // 将之前的hash常量传入到VM中(三次不同,共三个0x20字节)
    v63 = (signed int)hash1 >> 31;
    v64 = hn0x60_0xc7f;                         // 将hashName常量传入到VM中
    v65 = (signed int)hn0x60_0xc7f >> 31;
    v66 = out0x14;                              // 输出0x14字节缓冲区地址传入VM
    v67 = (signed int)out0x14 >> 31;
    v68 = out0x20;                              // 输出0x20字节缓冲区地址传入VM中
    v69 = (signed int)out0x20 >> 31;
    sub_40E2B0(&v55);                           // 运行VM对SN使用二个同sName有关的hash值常量解码,输出到上面提到的二个缓冲区
                                                // 用作者提供的正确注册码在这儿三次,可分别从二个输出缓冲区中得到正确的解密结果,只要这儿的输出同正确的一样(也就是预期解),后面的验证就能通过,当然也应该可以构造其它的结果来通过后面的验证
                                                // 算注册码时可以作为常量数据
    sub_401140(v37, v38, v53);                  // 对二个缓冲区返回的数据XOR解码
    hash256(hash2, (int)out2r, v29);            // XOR解码数据再计算hash值
    for ( i = strlen(out2r) - 1; i > 0; --i )
    {
      v40 = out2r[i] == 0x80u;                  // 串以0x80结束
                                                // 这儿不知道是不是有BUG,要是没有0x80好象只比较第一个字节??
                                                // 再则,要是0x80后再加无用的字节是不是也能通过?这样就可能多解??
      out2r[i] = 0;
      if ( v40 )
        break;
    }
    v41 = strlen(out2r);
    v42 = 0;
    v43 = 0;
    if ( v41 > 0 )
    {
      while ( out2r[v43] == ac1[v46][v43] )     // 三次xor后的解码串要分别同三个常量串相等
                                                // ac1[3][256]={"我是个任务管理器","找包含关键词的文件","赶紧带回去给船长"}
      {
        if ( ++v43 >= v41 )
          goto LABEL_55;
      }
      v42 = 1;
    }
LABEL_55:
    v44 = 0;
    while ( out0x20[v44] == hash2[v44] )        // 另一个0x20字节的返回缓冲区数据要同hash值相同
    {
      ++v44;
      if ( v44 >= 8 )
      {
        if ( v42 == 1 )
          break;
        if ( lpMem )
          j_j_j___free_base(lpMem);
        v28 += 4;                               // 准备下一轮,共三轮计算,都要满足上面的判断
        v30 = v46 * 256 + 256;
        ++v46;
        if ( (signed int)v28 >= 0x45AA5C )      // 三轮都通过,就验证成功
        {
          sub_401070("Congratulations! The Serial is Correct!\n");
          goto LABEL_3;
        }
        goto LABEL_35;
      }
    }
    sub_401070("Sorry!the Serial is wrong!\n");
    sub_41B6BF("pause");
    if ( lpMem )
      j_j_j___free_base(lpMem);
    result = 0;
  }
  return result;
}

使用用户名sName生成二个hash常量表
int __usercall sub_4011A0@<eax>(char *this@<ecx>, __int128 *a2@<esi>)
{
  char *v2; // edi
  int lenName; // eax
  signed int v4; // ebx
  BYTE *v5; // esi
  int v6; // eax
  int v7; // ecx
  int v8; // kr00_4
  int result; // eax
  BYTE v10[3200]; // [esp+10h] [ebp-C90h]
  int v11; // [esp+C94h] [ebp-Ch]
  int v12; // [esp+C98h] [ebp-8h]
  char *v13; // [esp+C9Ch] [ebp-4h]

  v2 = this;
  lenName = strlen(this);
  v4 = 0;
  v5 = v10;
  v13 = &v2[lenName];
  v11 = lenName + 1;
  v8 = lenName + 1;
  v6 = (unsigned __int64)(lenName + 1i64) >> 32;
  v7 = v8;
  v12 = v6;
  do
  {
    *v13 = v4;
    hash256(v5, (int)v2, __PAIR__(v6, v7));     // 计算256位hash值
    v6 = v12;
    ++v4;
    v7 = v11;
    v5 += 32;
  }
  while ( v4 < 100 );
  result = 0;
  *(_OWORD *)hn0x00_0x5f = *(_OWORD *)v10;      // 保存hash值前0x60字节到hn0x00_0x5f
  *(_OWORD *)&hn0x00_0x5f[16] = *(_OWORD *)&v10[16];
  *(_OWORD *)&hn0x00_0x5f[32] = *(_OWORD *)&v10[32];
  *(_OWORD *)&hn0x00_0x5f[48] = *(_OWORD *)&v10[48];
  *(_OWORD *)&hn0x00_0x5f[64] = *(_OWORD *)&v10[64];
  *(_OWORD *)&hn0x00_0x5f[80] = *(_OWORD *)&v10[80];
  do
  {
    *(_DWORD *)&hn0x60_0xc7f[result] = *(_DWORD *)&v10[result + 96];// 保存hash值前0x60字节到hn0x60_0xC7f, 后面应该只用到0x40字节
    *(_DWORD *)&hn0x60_0xc7f[result + 4] = *(_DWORD *)&v10[result + 100];
    *(_DWORD *)&hn0x60_0xc7f[result + 8] = *(_DWORD *)&v10[result + 104];
    *(_DWORD *)&hn0x60_0xc7f[result + 12] = *(_DWORD *)&v10[result + 108];
    *(_DWORD *)&hn0x60_0xc7f[result + 16] = *(_DWORD *)&v10[result + 112];
    *(_DWORD *)&hn0x60_0xc7f[result + 20] = *(_DWORD *)&v10[result + 116];
    *(_DWORD *)&hn0x60_0xc7f[result + 24] = *(_DWORD *)&v10[result + 120];
    *(_DWORD *)&hn0x60_0xc7f[result + 28] = *(_DWORD *)&v10[result + 124];
    result += 32;
  }
  while ( result < 3104 );
  return result;
}

那个计算hash的函数好象是Sha256变形,不过因为只是找出一个用户名KCTF的SN,而不是写一个通用注册机,所以没有再去分析,要算的数据直接调用一下它就能得到.

在调试时,进入VM后发现多次运算结果不一样,分析发现有调试检测来影响结果:
VM加法运算:
int __thiscall sub_40FED0(_DWORD *this)
{
  _DWORD *v1; // edi
  bool v2; // zf
  int v3; // eax
  HANDLE v4; // eax
  int v5; // ecx
  int v6; // edx
  int result; // eax
  BOOL v8; // eax
  int v9; // edx
  int v10; // esi
  int v11; // eax
  int v12; // ecx
  int v13; // eax
  unsigned __int8 v14; // cf
  BOOL pbDebuggerPresent; // [esp+8h] [ebp-4h]

  v1 = this;
  v2 = this[5] == *(unsigned __int8 *)(this[7] + this[18] + 1);
  this[5] = -1;
  if ( v2 )
  {
    v3 = this[6];
    this[6] = -1;
    if ( v3 == 1 )
    {
      v4 = GetCurrentProcess();
      CheckRemoteDebuggerPresent(v4, &pbDebuggerPresent);// 调试器判断, 检测到调试器时,后面的加法会多加一,造成计算错误
      v5 = v1[18] + v1[7];                      // 加法
      v6 = (int)&v1[2 * *(unsigned __int8 *)(v5 + 1)];
      result = pbDebuggerPresent + v1[2 * *(unsigned __int8 *)(v5 + 2) + 26];
      *(_QWORD *)(v6 + 104) += (unsigned int)result;
      *((_QWORD *)v1 + 9) += 3i64;
      return result;
    }
  }
  else
  {
    this[6] = -1;
  }
  v8 = sub_40E1F0();                            // 调试寄存器判断, 检测到使用调试寄存器时,后面的加法会多加一,造成计算错误
  v9 = v1[18] + v1[7];                          // 加法
  v10 = (int)&v1[2 * *(unsigned __int8 *)(v9 + 1)];
  *(_QWORD *)(v10 + 104) += *(_QWORD *)&v1[2 * *(unsigned __int8 *)(v9 + 2) + 26] + v8;
  v11 = *(unsigned __int8 *)(v1[7] + v1[18] + 1);
  v12 = v1[2 * v11 + 27];
  if ( !v1[2 * v11 + 26] && !v12 )
  {
    v13 = v1[25];
    v1[24] |= 2u;
    v1[25] = v13;
LABEL_8:
    v1[24] &= 0xFFFFFFFB;
    v14 = __CFADD__(v1[18], 3);
    v1[18] += 3;
    result = v1[25];
    v1[19] += v14;
    v1[25] = result;
    return result;
  }
  v1[24] &= 0xFFFFFFFD;
  result = v1[25];
  v1[25] = result;
  if ( v12 >= 0 )
    goto LABEL_8;
  v1[24] |= 4u;
  v14 = __CFADD__(v1[18], 3);
  v1[18] += 3;
  v1[25] = result;
  v1[19] += v14;
  return result;
}

调试寄存器判断:
BOOL sub_40E1F0()
{
  HMODULE v0; // esi
  FARPROC GetTheadContext; // ebx
  BOOL result; // eax
  int (*GetCurrentThread)(void); // eax
  HANDLE hThread; // eax
  CONTEXT Context; // [esp+0h] [ebp-304h]
  CHAR sGetCurrentThread[16]; // [esp+2CCh] [ebp-38h]
  char v7; // [esp+2DCh] [ebp-28h]
  CHAR sGetTheadContext[16]; // [esp+2E0h] [ebp-24h]
  char v9; // [esp+2F0h] [ebp-14h]
  int v10; // [esp+2F4h] [ebp-10h]
  int v11; // [esp+2F8h] [ebp-Ch]
  int v12; // [esp+2FCh] [ebp-8h]
  char v13; // [esp+300h] [ebp-4h]

  *(_OWORD *)sGetTheadContext = *(_OWORD *)aGetthreadconte;
  v10 = 1852990795;
  v11 = 842230885;
  v12 = 1819042862;
  v13 = 0;
  v9 = 0;
  *(_OWORD *)sGetCurrentThread = *(_OWORD *)aGetCurrentThread;
  v7 = 0;
  v0 = (HMODULE)sub_40E060(&v10);
  GetTheadContext = GetProcAddress(v0, sGetTheadContext);
  result = 0;
  if ( GetTheadContext )
  {
    GetCurrentThread = (int (*)(void))GetProcAddress(v0, sGetCurrentThread);
    hThread = (HANDLE)GetCurrentThread();
    Context.ContextFlags = 0x10010;
    ((void (__stdcall *)(HANDLE, CONTEXT *))GetTheadContext)(hThread, &Context);// GetThreadContext取当前寄存器数据
    if ( Context.Dr0 || Context.Dr1 || Context.Dr2 || Context.Dr3 )// 判断硬件调试器,有值,即设置了硬件断点会造成计算错误
      result = 1;
  }
  return result;
}
VM返回数据XOR运算:
int __cdecl sub_401140(int a1, int a2, int a3)
{
  int result; // eax
  int v4; // edx

  for ( result = 0; result < a3; ++result )
  {
    v4 = out0x14[result] ^ out0x20[result & 7];
    out20x14[result] = v4;
    out2r[4 * result + 2] = BYTE1(v4);          // VM计算返回的的两个缓冲区数据XOR输出到our2r,后面要同常量串比较(0x80结尾),不过可以在0x80后加无用字节,就可能多解,(当然那个hash值也要变)
    out2r[4 * result + 3] = v4;
    out2r[4 * result + 1] = BYTE2(v4);
    out2r[4 * result] = HIBYTE(v4);
  }
  return result;
}

因此每次载入程序时都PATCH调检测,只要EAX为0就不影响结果,另外还有启动时检测,好象是检测启动时间,OD中设置启动时不暂停程序,启动时不设硬件断点就能通过,所以没有再去分析.

VM中计算时,用传入的2个前面用sName生成的hash常量表,对传入SN解码,经人肉分析出算法如下:
每次输入分别为32字节8个DWORD常量(仅同用户名有关) 计作 
DWORD keys1[8]
固定的hashName0x60(也仅同用户名有关) 计作 
DWORD keys2[16]
SN为DWORD sn[13]
输出二个缓冲区计作DWORD out0x14[5], out0x20[8]
算法还是比较简单,大致如下:

n5 = 0xfffffffb
for i in range(8):                #每轮13组,输出13个DWORD,这是前8个
    z = sn[i * 3]%n5
    y = sn[i * 3 + 1]%n5
    x = sn[i * 3 + 2]%n5
    b = keys1[i] % n5
    k = keys2[i]%n5
    a = b * b % n5
    d = (a * x % n5 + b * y % n5 + z) % n5
    f = (d ^ k) % n5
    if i < 5:
        out0x14[i] = f
    else:
        out0x20[i - 5] = f
for i in range(5):                #计算后5个
    z = sn[24 + i * 3]%n5
    y = sn[24 + i * 3 + 1]%n5
    x = sn[24 + i * 3 + 2]%n5
    b = keys1[i] % n5
    k = keys2[32 + i]%n5
    a = b * b % n5
    d = (a * x % n5 + b * y % n5 + z) % n5
    f = (d ^ k) % n5
    out0x20[i + 3] = f

所以每轮输出f = (((a * x) % n5 + (b * y) % n5 + z) % n5) ^ k

以上分析可以看出b,k是常量(同sName有关),输出f在sName固定时,也是定值(当然,由于前面对主程序的分析,估计也可以构造出非预期解,不过要满足不少条件),要算出SN就要算出每轮的x,y,z,也就是解多个带模运算的三元一次方程组:
((a * x1) % n5 + (b * y1) % n5 + z) % n5 = d
((a * x2) % n5 + (b * y2) % n5 + z) % n5 = d
((a * x3) % n5 + (b * y3) % n5 + z) % n5 = d

在解方程这儿卡了很久,本来发现用matlab可以解方程的,不过对模运算没用,解出来是分数,不知道怎么办了,后来发现对分数结果可以用逆元的方式将分母换到分子问题才得到解决:
之前是手动在matlab中求解,后来才发现其实可以用sympy直接求解的:

import sympy
#整理后的常量表,用户名为KCTF时
#每行三个数据,分别是
#1.正常输出结果 f
#2.方程y的系数b(三句话加一个hashName的DOWRD的hash值) (x的系数a = b * b % 0xfffffffb, z的系数为1)
#3.对输出f的异或值(hashName0x60) k(可同f合并为d), d = (f ^ k) % 0xfffffffb
#4.这几个数据均是在主程序在调用sub_40E2B0的VM算法处取得:
#    f是利用作者提供的正确注册码,在VM运算的二个缓冲区返回的共13个DWORD,三次不一样,计算时要跳过后面的验证才能到下一轮,要不只能取第一次的数据
#    b,k 是用 KCTF 作用户名,在进入VM时的二个输入常量,这个是不变的只需要取一次,不过要是想得到非预期解,需要再多处理一下
datas = [    
    (
        (0xc5071efb, 0x835904e1, 0xd9c3e463),
        (0x9f2a34fa, 0xccffa00f, 0xd9c3e463),
        (0xc56852e3, 0xdf77dfa8, 0xd9c3e463),
    ),
    (
        (0xc32e91a4, 0xc834944b, 0x11c9af78),
        (0xbb602863, 0x490c46ed, 0x11c9af78),
        (0xf14cd90a, 0xae0ed8d8, 0x11c9af78),
    ),
    (
        (0xa85fcca4, 0x027a19e0, 0x6485bf9e),
        (0xbd146c18, 0xf78be5a1, 0x6485bf9e),
        (0x73d09543, 0x064da354, 0x6485bf9e),
    ),
    (
        (0x16c8d3e3, 0xfeb308a3, 0xff4bd05d),
        (0xf9eeda20, 0x81a56274, 0xff4bd05d),
        (0x3048ab81, 0x4c8b95cc, 0xff4bd05d),
    ),
    (
        (0x902a3513, 0x621ff195, 0x65769726),
        (0x97741627, 0x165deb5c, 0x65769726),
        (0x80becf48, 0xf934ca39, 0x65769726),
    ),
    (
        (0x0bd5d43c, 0x4b705c5c, 0xf5c38988),
        (0x4af88406, 0xf6f46796, 0xf5c38988),
        (0x7da7ef17, 0xc4e9de04, 0xf5c38988),
    ),
    (
        (0x7bd8596a, 0x3eb5d9d0, 0xbf3a2423),
        (0x01cc91bb, 0x44de5146, 0xbf3a2423),
        (0x45b462d2, 0x18ee2793, 0xbf3a2423),
    ),
    (
        (0x66ae7578, 0x9a5c73f4, 0x4b718cc0),
        (0x01e8d8d2, 0x00e4984c, 0x4b718cc0),
        (0xbb752dbb, 0x945ac9c2, 0x4b718cc0),
    ),
    (
        (0xd6251514, 0x835904e1, 0xc70d8f49),
        (0x4c2a14e4, 0xccffa00f, 0xc70d8f49),
        (0x84e41825, 0xdf77dfa8, 0xc70d8f49),
    ),
    (
        (0x102a3513, 0xc834944b, 0xdfc73315),
        (0x2b8a9627, 0x490c46ed, 0xdfc73315),
        (0x00becf48, 0xae0ed8d8, 0xdfc73315),
    ),
    (
        (0x6808be95, 0x027a19e0, 0x74470070),
        (0x3ec631a2, 0xf78be5a1, 0x74470070),
        (0x3304780d, 0x064da354, 0x74470070),
    ),
    (
        (0x00dba539, 0xfeb308a3, 0x94b89f71),
        (0x9e7c236e, 0x81a56274, 0x94b89f71),
        (0x65b228ac, 0x4c8b95cc, 0x94b89f71),
    ),
    (
        (0x18e6b83f, 0x621ff195, 0x0e60f6b1),
        (0xeddcd152, 0x165deb5c, 0x0e60f6b1),
        (0x539c7e00, 0xf934ca39, 0x0e60f6b1),
    ),
]

n5=0xfffffffb

def EX_GCD(a,b,arr):
    if b == 0:
        arr[0] = 1
        arr[1] = 0
        return a
    g = EX_GCD(b, a % b, arr)
    t = arr[0]
    arr[0] = arr[1]
    arr[1] = t - int(a / b) * arr[1]
    return g
def ModReverse(a,n):
    arr = [0,1,]
    gcd = EX_GCD(a,n,arr)
    if gcd == 1:
        return (arr[0] % n + n) % n
    else:
        return -1

#分数及负数模0xfffffffb转化
def toNum(v):
    q = v.q
    p = v.p
    if q != 1:
        q = ModReverse(q, n5)
    if p < 0:
        p += (-p // n5 + 1) * n5
    return (q * p) % n5

def outsn(d):
    s = ''
    for i in range(4):
        c = d & 0xff
        d >>= 8
        s = "%02X" % c + s
    return s

sSn = ''
for dat in datas:
    f1, b1, k1 = dat[0]
    f2, b2, k2 = dat[1]
    f3, b3, k3 = dat[2]
    b1 %= n5
    b2 %= n5
    b3 %= n5
    a1 = b1*b1 % n5
    a2 = b2*b2 % n5
    a3 = b3*b3 % n5
    c1,c2,c3 = 1,1,1
    d1 = (f1 ^ k1) % n5
    d2 = (f2 ^ k2) % n5
    d3 = (f3 ^ k3) % n5
    x = sympy.Symbol('x')
    y = sympy.Symbol('y')
    z = sympy.Symbol('z')
    v = sympy.solve([a1*x + b1*y + c1*z - d1, a2*x + b2*y + c2*z - d2, a3*x + b3*y + c3*z - d3], [x,y,z])
    sn0 = toNum(v[z])
    sn1 = toNum(v[y])
    sn2 = toNum(v[x])
    sSn += outsn(sn0)
    sSn += outsn(sn1)
    sSn += outsn(sn2)
print(sSn)

最后得到预期解:
sName = KCTF
sSn = E84DE727B4C7223FC4C8B34F9D4E4221225DD4A26A95E624E5EB45269DE64C0A9ED50B44BBA723F9878E4B4DD8841263453DC5157B401D6B5AF4F1401193FAA1433ADA48145A358ABCE1B8439C7F5D390C31987E39BB056E1A21B92B8DE2358ECEC1FF6C206CF8C17C46C89144BA8DA0EC4834389FFA54B32C8D3174E97AC3C2024783D0DFDC2BC9524B9C81B40F78F2E184A49B2292B4D79A58EF0B

PS:记得这个版本是多解修复版,不过从前面的分析应该还是多解的吧?

《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!

最后于 2020-4-30 20:59 被AloneWolf编辑 ,原因: 偶没设字体呀, 怎么会有不同大小的字体?
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回