首页
社区
课程
招聘
[原创] 看雪.安恒2020 KCTF春季赛 第二题:子鼠开天 by 心学
发表于: 2020-4-18 00:39 5053

[原创] 看雪.安恒2020 KCTF春季赛 第二题:子鼠开天 by 心学

htg 活跃值
4
2020-4-18 00:39
5053

发现有字符串“Enter your name:”

(1)、处理sn生成Hash,同时会进行返回值验证

1、工具:IDA、Python、RSATool2v17.exe、BigInt.exe
2、密码学知识:RSA、AES、MD5、SHA512

二、软件验证思路

2、密码学知识:RSA、AES、MD5、SHA512

二、软件验证思路

假设用户输入name、sn
nameHash  = MD5(SHA512(name+'EFBEEDDE'))
nameHash  = MD5(SHA512( nameHash  +'9E3779B9'))
【 EFBEEDDE  、 9E3779B9 实际运算需要转置】【摘要长度0x10】
middleHash = AES( sn  )
snHash = RSA( middleHash )
【对称密钥的解密办法就在程序里,修改参数即可获取】
【RSA是256bit,必须进行暴力破解,大概20分钟以内】
【摘要长度0x10】
以上是软件验证思路的简要过程。里面涉及到细节会在下面两个章节中进行阐述。
那么逆向软件验证思路,即可构造出合法的用户名及序列号
使用IDA加载软件,View->Open subviews->Strings,或者Shift+F12,打开String视图
Ctrl+F搜索上述发现的字符串,双击定位变量处,按X键跳转到代码处,按F5键,即可生成伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  time_t v3; // eax
  unsigned int lenName; // kr04_4
  int result; // eax
  char Name; // [esp+8h] [ebp-12Ch]
  char v7; // [esp+9h] [ebp-12Bh]
  __int16 v8; // [esp+69h] [ebp-CBh]
  char v9; // [esp+6Bh] [ebp-C9h]
  char Sn; // [esp+6Ch] [ebp-C8h]
  char v11; // [esp+6Dh] [ebp-C7h]
  __int16 v12; // [esp+131h] [ebp-3h]
  char v13; // [esp+133h] [ebp-1h]

  Name = 0;
  Sn = 0;
  memset(&v7, 0, 0x60u);
  v8 = 0;
  v9 = 0;
  memset(&v11, 0, 0xC4u);
  v12 = 0;
  v13 = 0;
  v3 = time(0);
  sub_411AD8(v3);
  sub_411A90(aEnterYourName);
  scanf(aS, &Name);
  lenName = strlen(&Name) + 1;
  if ( lenName - 1 < 3 || lenName - 1 > 0x14 )  // 用户名长度在3-20之间
  {
    sub_411A90(aBadName);
    result = -1;
  }
  else
  {
    sub_411A90(aEnterYourSn);
    scanf(aS, &Sn);
    if ( strlen(&Sn) == 64 )                    // 序列号是64位
    {
      sub_401380((int)&Name, lenName - 1, (int)&Sn, 0x40);// 关键验证代码
      result = 0;
    }
    else
    {
      sub_411A90(aBadSn);
      result = -1;
    }
  }
  return result;
}
从中找到关键方法:sub_401380,双击进入
int __cdecl main(int argc, const char **argv, const char **envp)
{
  time_t v3; // eax
  unsigned int lenName; // kr04_4
  int result; // eax
  char Name; // [esp+8h] [ebp-12Ch]
  char v7; // [esp+9h] [ebp-12Bh]
  __int16 v8; // [esp+69h] [ebp-CBh]
  char v9; // [esp+6Bh] [ebp-C9h]
  char Sn; // [esp+6Ch] [ebp-C8h]
  char v11; // [esp+6Dh] [ebp-C7h]
  __int16 v12; // [esp+131h] [ebp-3h]
  char v13; // [esp+133h] [ebp-1h]

  Name = 0;
  Sn = 0;
  memset(&v7, 0, 0x60u);
  v8 = 0;
  v9 = 0;
  memset(&v11, 0, 0xC4u);
  v12 = 0;
  v13 = 0;
  v3 = time(0);
  sub_411AD8(v3);
  sub_411A90(aEnterYourName);
  scanf(aS, &Name);
  lenName = strlen(&Name) + 1;
  if ( lenName - 1 < 3 || lenName - 1 > 0x14 )  // 用户名长度在3-20之间
  {
    sub_411A90(aBadName);
    result = -1;
  }
  else
  {
    sub_411A90(aEnterYourSn);
    scanf(aS, &Sn);
    if ( strlen(&Sn) == 64 )                    // 序列号是64位
    {
      sub_401380((int)&Name, lenName - 1, (int)&Sn, 0x40);// 关键验证代码
      result = 0;
    }
    else
    {
      sub_411A90(aBadSn);
      result = -1;
    }
  }
  return result;
}
从中找到关键方法:sub_401380,双击进入
void __cdecl sub_401380(int name, unsigned int lenName, int sn, int num64)
{
  char nameHash; // [esp+4h] [ebp-70h]
  char returnValueB; // [esp+14h] [ebp-60h]
  char v6; // [esp+15h] [ebp-5Fh]
  char v7; // [esp+23h] [ebp-51h]
  char snHash; // [esp+24h] [ebp-50h]
  char intSN; // [esp+34h] [ebp-40h]
  char returnValueA; // [esp+54h] [ebp-20h]

  if ( lenName >= 3 && lenName <= 0x14 && num64 == 64 )
  {
    if ( myAtoi((char *)sn, 64, &intSN) != 0x20 // 字符串转16进制整数:即'123456'会转为0x123456
      || (sub_4010F0(
            (int)&intSN,
            0x20,
            (int)&returnValueA,
            (int)&unk_4190D0,
            0x80,
            0),                                 // 序列号:对称加密方法
          sub_401210(
            (int)&returnValueA,
            32,
            (int)&returnValueB),                // 序列号:RSA加密方法
          returnValueB)
      || v6 != 2
      || v7 )
    {
      sub_411A90(aBadSn);                       // 错误
    }
    else
    {
      sub_401190((const void *)name, lenName, (int)&nameHash);// 用户名:生成摘要
      if ( !memcmp(&nameHash, &snHash, 0x10u) ) // 比较16个字节
        sub_411A90(aCongratulation);            // 正确
    }
  }
}

3.2、验证分为三个步骤:

(1)、处理sn生成Hash,同时会进行返回值验证

void __cdecl sub_401380(int name, unsigned int lenName, int sn, int num64)
{
  char nameHash; // [esp+4h] [ebp-70h]
  char returnValueB; // [esp+14h] [ebp-60h]
  char v6; // [esp+15h] [ebp-5Fh]
  char v7; // [esp+23h] [ebp-51h]
  char snHash; // [esp+24h] [ebp-50h]
  char intSN; // [esp+34h] [ebp-40h]
  char returnValueA; // [esp+54h] [ebp-20h]

  if ( lenName >= 3 && lenName <= 0x14 && num64 == 64 )
  {
    if ( myAtoi((char *)sn, 64, &intSN) != 0x20 // 字符串转16进制整数:即'123456'会转为0x123456
      || (sub_4010F0(
            (int)&intSN,
            0x20,
            (int)&returnValueA,
            (int)&unk_4190D0,
            0x80,
            0),                                 // 序列号:对称加密方法
          sub_401210(
            (int)&returnValueA,
            32,
            (int)&returnValueB),                // 序列号:RSA加密方法
          returnValueB)
      || v6 != 2
      || v7 )
    {
      sub_411A90(aBadSn);                       // 错误
    }
    else
    {
      sub_401190((const void *)name, lenName, (int)&nameHash);// 用户名:生成摘要
      if ( !memcmp(&nameHash, &snHash, 0x10u) ) // 比较16个字节
        sub_411A90(aCongratulation);            // 正确
    }
  }
}
 myAtoi:将sn字符串转成对应的十六进制大整数,输出为256位整数A
sub_4010F0:实施对称加密,输出为256位整数B
sub_401210:实施RSA加密,输出为256位整数C,其中后128位,即0x10个字节为snHash值
                       但是对C有三个判断语句:
                               第0个字节等于00:对应于 returnValueB
                               第1个字节等于02:对应于 v6 != 2
                               第F个字节等于00:对应于 v7
                       C的结构如:0002【13个字节,随机】00 + snHash
群里大佬提到的多解,估计问题出在这儿,作者应该锁定13个字节,或者再加一道判断语句,基本上就能限定解的唯一性。

sn--->Hash:如果破解了RSA,基本上就能进行逆操作。Hash--->sn
以下是该步骤的相关代码,以及判断的思路、验证操作

myAtoi: 相对简单,可以在调试时进行验证
int __cdecl myAtoi(char *sn, int num64, char *a3)
{
  char *v3; // ebp
  int v4; // esi
  int result; // eax
  char *v6; // edi
  signed int i; // edx
  unsigned __int8 v8; // cl
  char v9; // cl

  if ( !sn )
    return 0;
  if ( num64 % 2 )
    return 0;
  v3 = a3;
  if ( !a3 )
    return 0;
  v4 = 0;
  result = num64 / 2;
  if ( num64 / 2 > 0 )
  {
    v6 = sn;
    do
    {
      LOWORD(sn) = *(_WORD *)v6;
      i = 0;
      do
      {
        v8 = *((_BYTE *)&sn + i);
        if ( v8 < 0x30u || v8 > 0x39u )         // 非数字
        {
          if ( v8 < 0x61u || v8 > 0x66u )       // 非小写字母
          {
            if ( v8 < 0x41u || v8 > 0x46u )     // 非大写字母
              return 0;
            v9 = v8 - 0x37;                     // 大写字母:转16进制
          }
          else
          {
            v9 = v8 - 0x57;                     // 小写字母:转16进制
          }
        }
        else
        {
          v9 = v8 - 0x30;                       // 数字转16进制
        }
        *((_BYTE *)&sn + i++) = v9;
      }
      while ( i < 2 );                          // 一次处理两个字符
      v6 += 2;
      v3[v4++] = BYTE1(sn) & 0xF | 16 * (_BYTE)sn;
    }
    while ( v4 < result );
  }
  return result;
}

sub_4010F0:实施对称加密,输出为256位整数B ,这个相对麻烦
int __cdecl myAtoi(char *sn, int num64, char *a3)
{
  char *v3; // ebp
  int v4; // esi
  int result; // eax
  char *v6; // edi
  signed int i; // edx
  unsigned __int8 v8; // cl
  char v9; // cl

  if ( !sn )
    return 0;
  if ( num64 % 2 )
    return 0;
  v3 = a3;
  if ( !a3 )
    return 0;
  v4 = 0;
  result = num64 / 2;
  if ( num64 / 2 > 0 )
  {
    v6 = sn;
    do
    {
      LOWORD(sn) = *(_WORD *)v6;
      i = 0;
      do
      {
        v8 = *((_BYTE *)&sn + i);
        if ( v8 < 0x30u || v8 > 0x39u )         // 非数字
        {
          if ( v8 < 0x61u || v8 > 0x66u )       // 非小写字母
          {
            if ( v8 < 0x41u || v8 > 0x46u )     // 非大写字母
              return 0;
            v9 = v8 - 0x37;                     // 大写字母:转16进制
          }
          else
          {
            v9 = v8 - 0x57;                     // 小写字母:转16进制
          }
        }
        else
        {
          v9 = v8 - 0x30;                       // 数字转16进制
        }
        *((_BYTE *)&sn + i++) = v9;
      }
      while ( i < 2 );                          // 一次处理两个字符
      v6 += 2;
      v3[v4++] = BYTE1(sn) & 0xF | 16 * (_BYTE)sn;
    }
    while ( v4 < result );
  }
  return result;
}

sub_4010F0:实施对称加密,输出为256位整数B ,这个相对麻烦
首先观察两处疑点:
最右边的参数可以取值0或1,从而进入不同的处理方法内,在sub_404FB0里也会进行判断,极度怀疑是加密函数和解密函数
0x80h在AES解密环节中很多,有时候也会出现在hash运算中。
验证操作:
先记录经过sub_4010F0处理后:intSN和returnValue的数值是多少,他们都是0x20字节长,等长,初步验证是对称加密
然后将 returnValue作为用户验证码输入,在 sub_4010F0处暂停,修改其调用函数处的参数值 int0:0改为1
执行完成之后,观察返回值,经过对比,验证了对称加密,也可以再次实施逆运算。
int __cdecl sub_4010F0(int intSN, int int20h, int returnValue, int constValue, int int80h, int int0)
{
  int intSNCopy; // esi
  int v7; // ebx
  char middleValue; // [esp+4h] [ebp-F4h]

  if ( int0 == 1 )
    sub_404FE0((_DWORD *)constValue, int80h, (unsigned int *)&middleValue);
  else
    sub_4053A0(constValue, int80h, (int)&middleValue);// 执行
  if ( int20h / 16 <= 0 )
    return int20h;
  intSNCopy = intSN;
  v7 = int20h / 16;
  do
  {
    sub_404FB0(intSNCopy, returnValue - intSN + intSNCopy, (int)&middleValue, int0);
    intSNCopy += 16;
    --v7;
  }
  while ( v7 );
  return int20h;
}

int __cdecl sub_4010F0(int intSN, int int20h, int returnValue, int constValue, int int80h, int int0)
{
  int intSNCopy; // esi
  int v7; // ebx
  char middleValue; // [esp+4h] [ebp-F4h]

  if ( int0 == 1 )
    sub_404FE0((_DWORD *)constValue, int80h, (unsigned int *)&middleValue);
  else
    sub_4053A0(constValue, int80h, (int)&middleValue);// 执行
  if ( int20h / 16 <= 0 )
    return int20h;
  intSNCopy = intSN;
  v7 = int20h / 16;
  do
  {
    sub_404FB0(intSNCopy, returnValue - intSN + intSNCopy, (int)&middleValue, int0);
    intSNCopy += 16;
    --v7;
  }
  while ( v7 );
  return int20h;
}

sub_401210:实施RSA加密,输出为256位整数C,其中后128位,即0x10个字节为snHash值 ;这个处理起来比较麻烦
进入函数之内:
里面有:  sub_4068D0((int)v5, 0x10001);0x10001这是明显的RSA中的e值,很多RSA中的e都使用0x10001,极度怀疑这是一个RSA算法
那么如何来判断,那个是n了?既然是RSA算法,那么里面肯定有一个n,这个不可能由用户来带入,恰好V10是0x20字节长度,基本确定是一个RSA256算法,RSA256算法的加密强度太低,可以通过因式分解获取pq、进而得出ψ(n),再算出d,那么就可以做出解密算法来。
signed int __cdecl sub_401210(int a1, int int20h, int a3)
{
  bignum_st *v4; // esi
  bignum_st *v5; // edi
  bignum_st *v6; // ebx
  bignum_st *v7; // ebp
  signed int v8; // eax
  _DWORD *lpMem; // [esp+0h] [ebp-24h]
  char v10[32]; // [esp+4h] [ebp-20h]
  _BYTE *int20ha; // [esp+2Ch] [ebp+8h]

  v10[0] = 0x69;
  v10[8] = 0x39;
  v10[31] = 0x39;
  v10[1] = 0x82u;
  v10[2] = 0x30;
  v10[3] = 0x28;
  v10[4] = 0x57;
  v10[5] = 0x74;
  v10[6] = 0x65;
  v10[7] = 0xABu;
  v10[9] = 0x91u;
  v10[10] = 0xDFu;
  v10[11] = 4;
  v10[12] = 0x51;
  v10[13] = 0x46;
  v10[14] = 0xF9u;
  v10[15] = 0x1D;
  v10[16] = 0x55;
  v10[17] = 0x6D;
  v10[18] = 0xEEu;
  v10[19] = 0x88u;
  v10[20] = 0x70;
  v10[21] = 0x84u;
  v10[22] = 0x5D;
  v10[23] = 0x8Eu;
  v10[24] = 0xE1u;
  v10[25] = 0xCDu;
  v10[26] = 0x3C;
  v10[27] = 0xF7u;
  v10[28] = 0x7E;
  v10[29] = 0x4A;
  v10[30] = 0xC;
  if ( int20h != 0x20 )
    return 0;
  lpMem = sub_406170();
  v4 = sub_406650();                            // 初始化
  v5 = sub_406650();
  v6 = sub_406650();
  v7 = sub_406650();
  sub_406930((unsigned __int8 *)a1, 0x20, (LPVOID *)v7);// a1转换成大整数v7
  sub_4068D0((int)v5, 0x10001);                 // 0x10001转换成大整数v5
  sub_406930((unsigned __int8 *)v10, 0x20, (LPVOID *)v4);// v10转换成大整数v4
  sub_406D30(v6, v7, v5, v4, (int)lpMem);       // v6存放结果:这是RSA加解密函数
  v8 = 0;
  int20ha = (_BYTE *)(v6->d + 0x1F);
  do
    *(_BYTE *)(++v8 + a3 - 1) = *int20ha--;
  while ( v8 < 0x20 );                          // 将计算的结果倒序存放
  sub_4065F0(v4);                               // 复原
  sub_4065F0(v5);
  sub_4065F0(v6);
  sub_4065F0(v7);
  sub_4061D0(lpMem);
  return 0x20;
}
RSA算法验证中:
signed int __cdecl sub_401210(int a1, int int20h, int a3)
{
  bignum_st *v4; // esi
  bignum_st *v5; // edi
  bignum_st *v6; // ebx
  bignum_st *v7; // ebp
  signed int v8; // eax
  _DWORD *lpMem; // [esp+0h] [ebp-24h]
  char v10[32]; // [esp+4h] [ebp-20h]
  _BYTE *int20ha; // [esp+2Ch] [ebp+8h]

  v10[0] = 0x69;
  v10[8] = 0x39;
  v10[31] = 0x39;
  v10[1] = 0x82u;
  v10[2] = 0x30;
  v10[3] = 0x28;
  v10[4] = 0x57;
  v10[5] = 0x74;
  v10[6] = 0x65;
  v10[7] = 0xABu;
  v10[9] = 0x91u;
  v10[10] = 0xDFu;
  v10[11] = 4;
  v10[12] = 0x51;
  v10[13] = 0x46;
  v10[14] = 0xF9u;
  v10[15] = 0x1D;
  v10[16] = 0x55;
  v10[17] = 0x6D;
  v10[18] = 0xEEu;
  v10[19] = 0x88u;
  v10[20] = 0x70;
  v10[21] = 0x84u;
  v10[22] = 0x5D;
  v10[23] = 0x8Eu;
  v10[24] = 0xE1u;
  v10[25] = 0xCDu;
  v10[26] = 0x3C;
  v10[27] = 0xF7u;
  v10[28] = 0x7E;
  v10[29] = 0x4A;
  v10[30] = 0xC;
  if ( int20h != 0x20 )
    return 0;
  lpMem = sub_406170();
  v4 = sub_406650();                            // 初始化
  v5 = sub_406650();
  v6 = sub_406650();
  v7 = sub_406650();
  sub_406930((unsigned __int8 *)a1, 0x20, (LPVOID *)v7);// a1转换成大整数v7
  sub_4068D0((int)v5, 0x10001);                 // 0x10001转换成大整数v5
  sub_406930((unsigned __int8 *)v10, 0x20, (LPVOID *)v4);// v10转换成大整数v4
  sub_406D30(v6, v7, v5, v4, (int)lpMem);       // v6存放结果:这是RSA加解密函数
  v8 = 0;
  int20ha = (_BYTE *)(v6->d + 0x1F);
  do
    *(_BYTE *)(++v8 + a3 - 1) = *int20ha--;
  while ( v8 < 0x20 );                          // 将计算的结果倒序存放
  sub_4065F0(v4);                               // 复原
  sub_4065F0(v5);
  sub_4065F0(v6);
  sub_4065F0(v7);
  sub_4061D0(lpMem);
  return 0x20;
}
RSA算法验证中:
m = 0x13981882F9F8B823387C6EF9D5009E9F01675D5A3719A44696336E639FCD2778
e = 0x10001
n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39
c = m ** e % n
print('%02X'%c)
计算结果与调试结果一直:25D343CED2E5A3CD5FE94CEA15700B8D7725F7715A6A547A2BF0E8373705E
m = 0x13981882F9F8B823387C6EF9D5009E9F01675D5A3719A44696336E639FCD2778
e = 0x10001
n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39
c = m ** e % n
print('%02X'%c)
计算结果与调试结果一直:25D343CED2E5A3CD5FE94CEA15700B8D7725F7715A6A547A2BF0E8373705E
还需要补充000,即为:00025D343CED2E5A3CD5FE94CEA15700B8D7725F7715A6A547A2BF0E8373705E
也可以通过看雪中的Big Integer Calculator v1.13 带入计算,感谢readyu大神


RSA算法破解中:使用RSATool2v17.exe
对n进行因式分解
n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39
大约20分钟以内,分解结果:
 p = 0x979BE0C9EECE7426C9FD28C2D6E7772B
q = 0xB22831D15714EB91CD83340B4837182B
  计算d
d = 0x390A684CB713378FFD5CCE8C4000B5D6A2BB9F29B63D395E6BE6E9DD941527BD
【有大佬提d到 http://www.factordb.com/,经过测试,不成功。  https://www.wolframalpha.com/  也不成功,老老实实分解中】

(2)、处理name生成Hash
signed int __cdecl sub_401190(const void *name, unsigned int lenName, int returnMD5)
{
  char v4; // [esp+8h] [ebp-DCh]
  int v5; // [esp+18h] [ebp-CCh]
  char v6; // [esp+1Ch] [ebp-C8h]

  qmemcpy(&v6, name, lenName);
  *(int *)((char *)&v5 + lenName + 4) = dword_41D038;// v5在v6前面4个字节,v5+4相当于定位到v6,再加上lenName,也就是在用户字符串后面。实际上在用户字符串后面加上4个字节
  sub_4010B0((int)&v6, lenName + 4, (int)&v4);  // 结果存在v4里面,实际上是 SHA512+MD5计算摘要
  v5 = dword_41D030;                            // v5在v4的后面,也就是在v4后面追加了4个字节
  sub_4010B0((int)&v4, 20, returnMD5);
  return 16;
}
首先在用户名后面追加4个字节,进行hash组合计算
signed int __cdecl sub_401190(const void *name, unsigned int lenName, int returnMD5)
{
  char v4; // [esp+8h] [ebp-DCh]
  int v5; // [esp+18h] [ebp-CCh]
  char v6; // [esp+1Ch] [ebp-C8h]

  qmemcpy(&v6, name, lenName);
  *(int *)((char *)&v5 + lenName + 4) = dword_41D038;// v5在v6前面4个字节,v5+4相当于定位到v6,再加上lenName,也就是在用户字符串后面。实际上在用户字符串后面加上4个字节
  sub_4010B0((int)&v6, lenName + 4, (int)&v4);  // 结果存在v4里面,实际上是 SHA512+MD5计算摘要
  v5 = dword_41D030;                            // v5在v4的后面,也就是在v4后面追加了4个字节
  sub_4010B0((int)&v4, 20, returnMD5);
  return 16;
}
首先在用户名后面追加4个字节,进行hash组合计算
然后在上面的结果后面再追加4个字节,进行hash组合计算
那么如何知道是hash组合了?我们接着深入到sub_4010B0

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-4-19 11:19 被htg编辑 ,原因: 晚上python最终代码
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 2150
活跃值: (1173)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
        我想请问一下, sub_4010B0和 sub_4019B0是怎么F5反编译的,我堆栈平衡也调了 还是显示无法反编译,麻烦了您了
上传的附件:
2020-4-22 14:10
0
游客
登录 | 注册 方可回帖
返回
//