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

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

htg 活跃值
2
2020-4-18 00:39
1902

一、准备工具及相关知识

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

二、软件验证思路

假设用户输入name、sn

2.1、获取用户名摘要:nameHash

nameHash  = MD5(SHA512(name+'EFBEEDDE'))
nameHash  = MD5(SHA512( nameHash  +'9E3779B9'))
【 EFBEEDDE  、 9E3779B9 实际运算需要转置】【摘要长度0x10】

2.2、获取序列号摘要:snHash

middleHash = AES( sn  )
snHash = RSA( middleHash )
【对称密钥的解密办法就在程序里,修改参数即可获取】
【RSA是256bit,必须进行暴力破解,大概20分钟以内】
【摘要长度0x10】

2.3、判断nameHash与snHash是否相等:相等则通过验证。

以上是软件验证思路的简要过程。里面涉及到细节会在下面两个章节中进行阐述。
那么逆向软件验证思路,即可构造出合法的用户名及序列号

三、分析过程

3.1、运行软件,实施观察:

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

使用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,双击进入
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,同时会进行返回值验证

 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 ,这个相对麻烦
首先观察两处疑点:
最右边的参数可以取值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;
}

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算法验证中:
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组合计算
然后在上面的结果后面再追加4个字节,进行hash组合计算
那么如何知道是hash组合了?我们接着深入到sub_4010B0
signed int __cdecl sub_4010B0(int a1, int a2, int a3)
{
  char v4; // [esp+0h] [ebp-40h]

  sub_4019B0(a1, a2, &v4);                      // SHA512
  sub_401560((int)&v4, 64, (void *)a3);         // MD5
  return 16;
}
有两个方法:sub_4019B0计算结果存到v4; sub_401560计算结果存到a3,最后会作为hash返回值给到上层调用方法
我们继续深入到这两个方法
sub_4019B0 :里面有特征值,到此就没必要在继续跟下去了,如果对加密不太了解,可以百度搜索特征值。
_BYTE *__cdecl sub_4019B0(int a1, int a2, void *a3)
{
  _BYTE *v3; // esi
  int v5; // [esp+4h] [ebp-D8h]
  int v6; // [esp+8h] [ebp-D4h]
  int v7; // [esp+Ch] [ebp-D0h]
  int v8; // [esp+10h] [ebp-CCh]
  int v9; // [esp+14h] [ebp-C8h]
  int v10; // [esp+18h] [ebp-C4h]
  int v11; // [esp+1Ch] [ebp-C0h]
  int v12; // [esp+20h] [ebp-BCh]
  int v13; // [esp+24h] [ebp-B8h]
  int v14; // [esp+28h] [ebp-B4h]
  int v15; // [esp+2Ch] [ebp-B0h]
  int v16; // [esp+30h] [ebp-ACh]
  int v17; // [esp+34h] [ebp-A8h]
  int v18; // [esp+38h] [ebp-A4h]
  int v19; // [esp+3Ch] [ebp-A0h]
  int v20; // [esp+40h] [ebp-9Ch]
  int v21; // [esp+44h] [ebp-98h]
  int v22; // [esp+48h] [ebp-94h]
  int v23; // [esp+4Ch] [ebp-90h]
  int v24; // [esp+50h] [ebp-8Ch]
  int v25; // [esp+D4h] [ebp-8h]
  int v26; // [esp+D8h] [ebp-4h]

  v3 = a3;
  if ( !a3 )
    v3 = &unk_41FB18;
  v21 = 0;
  v22 = 0;
  v23 = 0;
  v24 = 0;
  v25 = 0;
  v5 = 0xF3BCC908;
  v6 = 0x6A09E667;
  v7 = 0x84CAA73B;
  v8 = 0xBB67AE85;
  v9 = 0xFE94F82B;
  v10 = 0x3C6EF372;
  v11 = 0x5F1D36F1;
  v12 = 0xA54FF53A;
  v13 = 0xADE682D1;
  v14 = 1359893119;
  v15 = 0x2B3E6C1F;
  v16 = 0x9B05688C;
  v17 = 0xFB41BD6B;
  v18 = 0x1F83D9AB;
  v19 = 0x137E2179;
  v20 = 0x5BE0CD19;
  v26 = 64;
  sub_401890(&v5, (char *)a1, a2);
  sub_4015D0(v3, (int)&v5);
  sub_407E50(&v5, 0xD8u);
  return v3;
}
百度搜索:0xF3BCC908:即可判断是 SHA512

深入到sub_401560
void *__cdecl sub_401560(int a1, int a2, void *a3)
{
  void *v3; // esi
  void *result; // eax
  int v5[23]; // [esp+4h] [ebp-5Ch]

  v3 = a3;
  if ( !a3 )
    v3 = &unk_41FAD8;
  result = (void *)sub_4080F0(v5);
  if ( result )
  {
    sub_407EB0(v5, (char *)a1, a2);
    sub_407FB0((int)v3, v5);
    sub_407E50(v5, 0x5Cu);
    result = v3;
  }
  return result;
}
初步看,没啥信息,我们再深入到sub_4080F0
特征值出现,百度0x67452301;,这个是 MD5的内容
signed int __cdecl sub_4080F0(_DWORD *a1)
{
  *a1 = 0x67452301;
  a1[1] = 0xEFCDAB89;
  a1[2] = 0x98BADCFE;
  a1[3] = 0x10325476;
  a1[4] = 0;
  a1[5] = 0;
  a1[22] = 0;
  return 1;
}
以上都是猜测,还需要验证,可以借助python来判断。
import hashlib
import binascii
#定义方法
def GetMD5(hex_string):
    m = hashlib.md5()
    byteArr = bytes().fromhex(hex_string)
    m.update(byteArr)
    md5 = m.hexdigest().upper()
    #print('md5:%s\n'%(md5))
    return md5
    
def GetSHA512(hex_string):
    sha512 = hashlib.sha512()
    byteArr = bytes().fromhex(hex_string)
    sha512.update(byteArr)
    sha512hash = sha512.hexdigest().upper()
    #print('sha512hash:%s\n'%(sha512hash))
    return sha512hash

#转换DWord并反转
def GetHexValueByHexString(hex_string):
    valueA = bytes().fromhex(hex_string)
    valueA = ''.join(['%02X'%i for i in valueA][::-1])
    return valueA

#执行:
print('第一次:')
name = 'B1AC71D22D82EBB1'
valueA = GetHexValueByHexString('EFBEEDDE')
nameBytes =''.join(['%02X'%(ord(i)) for i in name])
print("nameBytes:\n%s"%nameBytes)
hex_string = nameBytes +'DEEDBEEF'
print("hex_string:\n%s"%hex_string)
sha512hash = GetSHA512(hex_string)
print('sha512hash:\n%s\n'%(sha512hash))
md5hash = GetMD5(sha512hash)
print('md5hash:\n%s\n'%(md5hash))

print('*'*100)
print('第二次:')
valueA = GetHexValueByHexString('9E3779B9')
hex_string = md5hash + valueA
print("hex_string:\n%s"%hex_string)
sha512hash = GetSHA512(hex_string)
print('sha512hash:\n%s\n'%(sha512hash))
md5hash = GetMD5(sha512hash)
print('md5hash:\n%s\n'%(md5hash))
print('\n用户名MD5模拟计算完成')
计算结果如下:
第一次:
nameBytes:
42314143373144323244383245424231
hex_string:
42314143373144323244383245424231DEEDBEEF
sha512hash:
F42472903D3172F04D40333D610B08E5B7887EC45BDA8DAA6E38FC6C2CBDEDBD9C6CC5F29448D407C710140F1529958E2DE2670FC9422E6A91721E2718A2E99B

md5hash:
8E4890A34FA0383690081CB68564A973

****************************************************************************************************
第二次:
hex_string:
8E4890A34FA0383690081CB68564A973B979379E
sha512hash:
1774F7037032D7888EF0404BC05DD2EEE958580FC093D7E800E49CB9252B1D0B4CA54B9678D6C7A6B786F421D3F1985A39FC38364C82BDCC39741067B55DC315

md5hash:
B8D7725F7715A6A547A2BF0E8373705E
使用IDA进行调试【我用的是IDA64,不清楚IDA为啥不行】,经过验证,此时得到的用户名摘要的确是与python计算一致

附加密算法特征值:
引用地址:https://blog.csdn.net/Memroy/article/details/88077167
MD5
*v1 = 0x67452301;
v1[1] = 0xEFCDAB89;
v1[2] = 0x98BADCFE;
v1[3] = 0x10325476;

SHA1
*v1 = 0x67452301;
v1[1] = 0xEFCDAB89;
v1[2] = 0x98BADCFE;
v1[3] = 0x10325476;
v1[4] = 0xC3D2E1F0;

SHA256
*v1 = 0x6A09E667;
v1[1] = 0xBB67AE85;
v1[2] = 0x3C6EF372;
v1[3] = 0xA54FF53A;
v1[4] = 0x510E527F;
v1[5] = 0x9B05688C;
v1[6] = 0x1F83D9AB;
v1[7] = 0x5BE0CD19;

SHA224
*v1 = 0xC1059ED8;
v1[1] = 0x367CD507;
v1[2] = 0x3070DD17;
v1[3] = 0xF70E5939;
v1[4] = 0xFFC00B31;
v1[5] = 0x68581511;
v1[6] = 0x64F98FA7;
v1[7] = 0xBEFA4FA4;

SHA512
*a1 = 0xF3BCC908;
a1[1] = 0x6A09E667;
a1[2] = 0x84CAA73B;
a1[3] = 0xBB67AE85;
a1[4] = 0xFE94F82B;
a1[5] = 0x3C6EF372;
a1[6] = 0x5F1D36F1;
a1[7] = 0xA54FF53A;
a1[8] = 0xADE682D1;
a1[9] = 0x510E527F;
a1[10] = 0x2B3E6C1F;
a1[11] = 0x9B05688C;
a1[12] = 0xFB41BD6B;
a1[13] = 0x1F83D9AB;
a1[14] = 0x137E2179;
a1[15] = 0x5BE0CD19;

SHA384
*a1 = 0xC1059ED8;
a1[1] = 0xCBBB9D5D;
a1[2] = 0x367CD507;
a1[3] = 0x629A292A;
a1[4] = 0x3070DD17;
a1[5] = 0x9159015A;
a1[6] = 0xF70E5939;
a1[7] = 0x152FECD8;
a1[8] = 0xFFC00B31;
a1[9] = 0x67332667;
a1[10] = 0x68581511;
a1[11] = 0x8EB44A87;
a1[12] = 0x64F98FA7;
a1[13] = 0xDB0C2E0D;
a1[14] = 0xBEFA4FA4;
a1[15] = 0x47B5481D;

RC4
有2个256次数的循环

AES
秘钥不需要单独初始化
并且里面有很多复杂的xor
一般情况下秘钥都是16字节(可能)
如果是CBC的话则还会拥有一个16字节的IV

DES
秘钥需要单独初始化
秘钥长度固定8字节
每8字节分段加密

RSA
秘钥需要单独初始化
秘钥长度一般很长, 存储格式一般为base64文本

ECC
秘钥需要单独初始化
秘钥的首字节一般为0x04或0x03, 并且长度是这种17或31这种没有4字节对齐的数据

(3)、两者相等,即可通过
if ( !memcmp(&nameHash, &snHash, 0x10u) ) // 比较16个字节
        sub_411A90(aCongratulation);            // 正确

四、解决办法

因为尚未实现对对称密钥的python代码化,以及利用大整数计算工具计算大数运算比python要快,python应该有相应模块,我就不找了,我将解密分为五部分来完成,由3个工具辅助实现

4.1、构造用户名“KCTF”对应的两次双重hash值:nameHash

import hashlib
import binascii
#定义方法
def GetMD5(hex_string):
    m = hashlib.md5()
    byteArr = bytes().fromhex(hex_string)
    m.update(byteArr)
    md5 = m.hexdigest().upper()
    #print('md5:%s\n'%(md5))
    return md5
    
def GetSHA512(hex_string):
    sha512 = hashlib.sha512()
    byteArr = bytes().fromhex(hex_string)
    sha512.update(byteArr)
    sha512hash = sha512.hexdigest().upper()
    #print('sha512hash:%s\n'%(sha512hash))
    return sha512hash

def GetHash(hex_string):
    sha512hash = GetSHA512(hex_string)
    md5hash = GetMD5(sha512hash)
    return md5hash

def GetHexValueByHexString(hex_string):
    valueA = bytes().fromhex(hex_string)
    valueA = ''.join(['%02X'%i for i in valueA][::-1])
    return valueA
    
#第一阶段:根据用户名生成Hash
#name = 'B1AC71D22D82EBB1'
name = 'KCTF'
print('第一阶段:根据用户名生成Hash.....\n')
print('用户名:%s'%name)
print('第一次:')
valueA = GetHexValueByHexString('EFBEEDDE')
nameBytes =''.join(['%02X'%(ord(i)) for i in name])
nameHash = GetHash(nameBytes + valueA)
print('nameHash:%s'%(nameHash))

#print('*'*100)
print('第二次:')
valueA = GetHexValueByHexString('9E3779B9')
nameHash = GetHash(nameHash + valueA)
print('nameHash:%s\n'%(nameHash))
print('根据用户名生成Hash完成\n\n')
输出结果为:14AF58AD4D76D59D8D2171FFB4CA2231

4.2、构造合法序列号对应的snHash

snHash = 00025D343CED2E5A3CD5FE94CEA1570014AF58AD4D76D59D8D2171FFB4CA2231
【 snHash  = 00+02+ 5D343CED2E5A3CD5FE94CEA157 +00+nameHash】

4.3、snHash实施RSA解密为DecyptBigInteger

使用 BigInt.exe进行大数计算

输出结果为12A1758C9A9AACE82BDFA5ED5190DEBC0CAF522F2785E92E6CACBC6B0E3220B3

4.4、 DecyptBigInteger实施对称解密为AesDecyptBigInteger

运行程序,用户名输入KCTF,序列号输入为 12A1758C9A9AACE82BDFA5ED5190DEBC0CAF522F2785E92E6CACBC6B0E3220B3
在运行到.text:004013C8处,将代码改为 push 1,或者在执行之后,修改堆栈里的对应数据为1
.text:004013C8                 push    0
.text:004013CA                 push    80h
.text:004013CF                 lea     edx, [esp+7Ch+returnValueA]
.text:004013D3                 push    offset unk_4190D0
.text:004013D8                 push    edx
.text:004013D9                 push    eax
.text:004013DA                 lea     eax, [esp+88h+intSN]
.text:004013DE                 push    eax
.text:004013DF                 call    sub_4010F0
.text:004013E4                 lea     ecx, [esp+8Ch+returnValueB]
.text:004013E8                 lea     edx, [esp+8Ch+returnValueA]
.text:004013EC                 push    ecx
.text:004013ED                 push    20h
.text:004013EF                 push    edx
.text:004013F0                 call    sub_401210
.text:004013F5                 mov     al, [esp+98h+returnValueB]
执行完.text:004013DF                 call    sub_4010F0 之后,查看 0x19FDCC处【有的电脑不一样,这个可以通过记住之前压入堆栈中的edx值来跟踪】
获取值为:6ED8BC1F04D0C360567FB579398265FEEC8B48DC4B804904FEB1AB538C823270

4.5、 AesDecyptBigInteger输出为16进制字符串,即为合法序列号

name:KCTF
sn: 6ED8BC1F04D0C360567FB579398265FEEC8B48DC4B804904FEB1AB538C823270


2020-04-19 11:15  综合 丿feng 大佬的WP( https://bbs.pediy.com/thread-258934.htm ),现在给出完整的 python代码。
gmpy2的安装库,需要到 https://github.com/aleaxit/gmpy/releases/tag/gmpy2-2.1.0a1 里面下载安装,提前安装wheel
python36版本:
pip install wheel
pip install gmpy2-2.1.0a1-cp36-cp36m-win_amd64.whl
import hashlib
import binascii
from binascii import hexlify
#算法部分库
from Crypto.Cipher import AES
from Crypto.Util.number import bytes_to_long, long_to_bytes
import gmpy2

#定义方法
def GetMD5(hex_string):
    m = hashlib.md5()
    byteArr = bytes().fromhex(hex_string)
    m.update(byteArr)
    md5 = m.hexdigest().upper()
    #print('md5:%s\n'%(md5))
    return md5
    
def GetSHA512(hex_string):
    sha512 = hashlib.sha512()
    byteArr = bytes().fromhex(hex_string)
    sha512.update(byteArr)
    sha512hash = sha512.hexdigest().upper()
    #print('sha512hash:%s\n'%(sha512hash))
    return sha512hash

def GetHash(hex_string):
    sha512hash = GetSHA512(hex_string)
    md5hash = GetMD5(sha512hash)
    return md5hash

def GetHexValueByHexString(hex_string):
    valueA = bytes().fromhex(hex_string)
    valueA = ''.join(['%02X'%i for i in valueA][::-1])
    return valueA
    
#第一阶段:根据用户名生成Hash
#name = 'B1AC71D22D82EBB1'
name = 'KCTF'
print('第一阶段:根据用户名生成Hash.....\n')
print('用户名:%s'%name)
print('第一次:')
valueA = GetHexValueByHexString('EFBEEDDE')
nameBytes =''.join(['%02X'%(ord(i)) for i in name])
nameHash = GetHash(nameBytes + valueA)
print('nameHash:%s'%(nameHash))

#print('*'*100)
print('第二次:')
valueA = GetHexValueByHexString('9E3779B9')
nameHash = GetHash(nameHash + valueA)
print('nameHash:%s\n'%(nameHash))
print('根据用户名生成Hash完成\nnameHash:%s'%nameHash)
#*************************************************************************************
#*************************************************************************************
print('*'*100)
print('*'*100)
#第二阶段:根据Hash,解密为0x20大整数
print('\n第二阶段:根据Hash,解密为0x20大整数.....RSA算法')
#nameHask扩充到0x20长度
nameHashEx = '00'+'02'+'00'*13 + '00' + nameHash
nameHashEx = '00'+'02'+'5D343CED2E5A3CD5FE94CEA157'+ '00' + nameHash
print('nameHashEx:\t%s'%(nameHashEx))
print('RSA解密')
nameHashExBytes = bytes().fromhex(nameHashEx)
c = bytes_to_long(nameHashExBytes)

n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39
#d = 0x390A684CB713378FFD5CCE8C4000B5D6A2BB9F29B63D395E6BE6E9DD941527BD
e = 0x10001
p = 0x979BE0C9EECE7426C9FD28C2D6E7772B
q = 0xB22831D15714EB91CD83340B4837182B
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = gmpy2.powmod(c, d, n)
#mInteger = nameHashInteger  ** d % n
nameHashInteger = m 
print('nameHashInteger:%02X'%(nameHashInteger))
print('第二阶段:根据Hash,解密为0x20大整数.....RSA算法完成\n')
#*************************************************************************************
#*************************************************************************************
print('*'*100)
print('*'*100)
#第三阶段:反算序列号:AES解密
print('\n第三阶段:反算序列号:AES解密')
aesKey ='480B62C3ACD6C8A36B18D9E906CD90D2'#在文件里作为常量加载
print('aesKey:\t%s'%(aesKey))
print('AES解密中')
aesKeyBytes = bytes().fromhex(aesKey)
aes = AES.new(aesKeyBytes, AES.MODE_ECB)
snBytes = aes.encrypt(long_to_bytes(nameHashInteger))
sn = hexlify(snBytes).decode().upper()
print('sn:\t\t%s'%sn)
print('第三阶段:反算序列号:AES完成')


看雪论坛2020激励机制:能力值、活跃值和雪币体系!会员积分、权限和会员发帖、回帖活跃程度关联!

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