首页
社区
课程
招聘
阿里CTF2026-license
发表于: 2026-2-3 10:43 4350

阿里CTF2026-license

2026-2-3 10:43
4350

记录下第一次搞协议逆向,很遗憾比赛过程中卡在最后一步了

总体协议流程如下

直接分析license可以发现他和build_token建立了通信

运行过程中打印了license code: xxxx-uuid这样的字符串,但并没有在license文件里找到,猜测是从build_token发送elf回来加载到主elf内存里执行代码

直接上调试,ida里按照run.sh里设置--no-redirect -c 127.0.0.1:12345,本地跑./build_token -p 'r&FGW9RpqTc*aqof' -s -l 0.0.0.0:12345,从main函数return 0开始调试

刚开始可以看到retn后来到了新的区域

慢慢调试可以发现有一些脱壳的感觉,在解密一些数据代码,按照经验不停的跳过循环,最终可以发现来到一块非常大的函数

单步进入反编译后可以看到license、flag等字符串,说明这里才是真正的check逻辑

开头部分是在生成随机的UUID,慢慢调试直到要求输入,发现下图红框位置要求输入数据,分别要求4、12长度(注意回车符也算输入)

第三处要求输入长度的正是第一处输入的数值-12(不太可控,因为手动输入只能输可打印字符),因此调试到这里时我手动在第三处输入前把要求长度修改掉,从而得以输入可控长度的测试数据,我们这里成为数据流

数据包结构分析完后,下图中sub_403D10函数会返回CPU架构是否支持AVX并设置一个bool字节(随后很多if-else会根据这个值选择进入到的代码,这些分支代码逻辑是相同的,可以省略很多分析过程)。

我的CPU支持AVX,进入到下图红框中的数据初始化,AI分析可知是密钥扩展,但这里的密钥通过一定运算才能得到

所以直接来到最后最终轮密钥结果处,提取出来前32字节即可获得密钥为f6778d8728d8f17ce8c5c81f45c3d5fd869ca851b7575be540776f4f26c1140d

if-else出来后发现对16个0字节做了加密

这种模式符合AES-256-GCM模式,里面包含2部分,CTR+GHASH随后的调试里都可以看到特征,比如下图里检查了数据流最后16字节是否等于一组结果

可以写一个代码来加密数据,结果放入数据包中的数据流部分,从而调试通过这部分AES解密

解密完的数据发现检查了大小,要求不小于13字节

在往下调试的过程中发现报错Unknown frame descriptor,搜索字符串可以定位一堆报错case

搜索可知是Zstandard解码器报错,解码器在输入数据开头没有识别出合法的魔数(28 B5 2F FD,可以IDA搜索到多个比较),所以才报错

python有zstandard库可以直接帮我们压缩数据,把压缩完的数据传给AES-GCM再去构造数据包即可保证这里解压不在报错

这里非常明显,多处base64查表,而且上一步骤中解压的数据字符不是4的倍数会报错padding不对等等,确认这里在进行base64解码

所以只需要把我们对数据做一次base64编码再去zstd压缩、再去AES-GCM加密,最后构造包即可通过这部分

来到下图这里时会出现新的报错,结合AI分析以及代码里switch-case取字段的逻辑,可知是protobuf解析

sub_40B960里要求了wire必须等于2,一共四个字段enc_datapasswordsaltsha256_hash

所以这里要构造符合protobuf的数据结构

发现sha256加密

通过可控数据调试以及上一步骤中字段名,发现password和salt最先被读取并做了PBKDF2-HMAC-SHA256,iter正好是1000,生成了48字节

调试过程中到上图ROR8的位置时发现检查enc_data字段数据长度是否为20的倍数,不是就会跳转LABEL_80(ud2,也就是BUG())

当我修改完enc_data长度为20倍数后,到下图时发现解密完的数据最后检查了最后1字节是否小于20,如果大于20会报错invalid padding

到这里可以分析出是某种对称加密(分组大小为20字节)+PKCS#7填充

回过头分析对称加密算法,下图中调试经过unk_409DC0后会发现off_6EA318指向了一个256字节大小的sbox

值为

和标准AES不一样,但类似AES,搜索发现确实存在20字节分组的算法。调试发现unk_409DC0里面存在表生成的函数

交给AI分析可知生成了3张256字节表,用于方便后续AES计算,其中有log、exp、inv表。需要注意的是生成方式和标准不一样,标准xtime异或是0x1b,sub_41EE90里是异或了0x8d

最后出来后又在sub_41EBE0中生成了sbox(这里魔改了)

出来后qword_6EA498值为13,符合AES-160的NK+6(密钥是28字节224bit,NK=7)

此外在一轮轮调试数据还发现到mixcolumn时结果变了,检查发现数值和标准的不一样

到这里应该是全部的魔改点了。调试可以发现AES-160采用的是CBC模式,前面PBKDF2-HMAC-SHA256生成的48字节,前28字节作为密钥,后20字节作为iv。

最后还有一点,前面提到有padding所以应该是解密,但是上面的流程是按照加密算才和调试拿到的数据对应的,所以有可能是加密解密算法对调了下。

解密后会检查sha256加密后的值是否等于protobuf里解析出来的sha256_hash值(32字节)

前面步骤全部正确后会在最终check前再次进入一个很大的函数,整体AI分析了一遍可知是在做json数据解析,要求包含license_code和sign字段,license_code值等于最开始随机生成的UUID,而sign字段经过一定加密要求最后16字节等于UUID(去除-并转为字节)

通过观察报错定位到第一个是时间戳校验(license expired

上图中计算了时间戳差值并检查是否小于60s

时间戳校验后有一大串常量数据(1024字节)

继续往下走发现从上面数据中初始化了512字节,之后跳转到一个处理hex的逻辑

其中v204是单一字符,取自v196字符串,查看发现正是我们设置的sign值。下图每次读取两个字符拼接为一字节

之后是一个while循环,逻辑仍然是处理hex值,转为字节存储到了v206

又提取了固定的512字节(来自上面常量)

接着进入到一个函数,传递了sign和512字节,AI分析是大整数取模运算,符合RSA特性

通过调试拿到输入输出值测试发现是在计算

到这之后基本确认是RSA-4096签名生成的sign,比赛时卡这里是因为n、d端序提取错了,导致RSA签名完的数据一直对不上调试拿到的数据

所以总结来说这里sign经过n、d(私钥)解密后最后16字节要等于uuid,所以我们需要用n、e(公钥)加密uuid作为sign值构造json

到这里就是一步步往回逆向,具体流程如下

全流程代码如下

远程连接的exp如下

拿到远程flag

void __fastcall sub_40C1B0(__m128i *_RDI, const __m128i *a2, _OWORD *a3)
{
  _XMM0 = _mm_xor_si128(_mm_loadu_si128(a2), *_RDI);
  __asm
  {
    aesenc  xmm0, xmmword ptr [rdi+10h]
    aesenc  xmm0, xmmword ptr [rdi+20h]
    aesenc  xmm0, xmmword ptr [rdi+30h]
    aesenc  xmm0, xmmword ptr [rdi+40h]
    aesenc  xmm0, xmmword ptr [rdi+50h]
    aesenc  xmm0, xmmword ptr [rdi+60h]
    aesenc  xmm0, xmmword ptr [rdi+70h]
    aesenc  xmm0, xmmword ptr [rdi+80h]
    aesenc  xmm0, xmmword ptr [rdi+90h]
    aesenc  xmm0, xmmword ptr [rdi+0A0h]
    aesenc  xmm0, xmmword ptr [rdi+0B0h]
    aesenc  xmm0, xmmword ptr [rdi+0C0h]
    aesenc  xmm0, xmmword ptr [rdi+0D0h]
    aesenclast xmm0, xmmword ptr [rdi+0E0h]
  }
  *a3 = _XMM0;
}
void __fastcall sub_40C1B0(__m128i *_RDI, const __m128i *a2, _OWORD *a3)
{
  _XMM0 = _mm_xor_si128(_mm_loadu_si128(a2), *_RDI);
  __asm
  {
    aesenc  xmm0, xmmword ptr [rdi+10h]
    aesenc  xmm0, xmmword ptr [rdi+20h]
    aesenc  xmm0, xmmword ptr [rdi+30h]
    aesenc  xmm0, xmmword ptr [rdi+40h]
    aesenc  xmm0, xmmword ptr [rdi+50h]
    aesenc  xmm0, xmmword ptr [rdi+60h]
    aesenc  xmm0, xmmword ptr [rdi+70h]
    aesenc  xmm0, xmmword ptr [rdi+80h]
    aesenc  xmm0, xmmword ptr [rdi+90h]
    aesenc  xmm0, xmmword ptr [rdi+0A0h]
    aesenc  xmm0, xmmword ptr [rdi+0B0h]
    aesenc  xmm0, xmmword ptr [rdi+0C0h]
    aesenc  xmm0, xmmword ptr [rdi+0D0h]
    aesenclast xmm0, xmmword ptr [rdi+0E0h]
  }
  *a3 = _XMM0;
}
const char *__fastcall sub_7FFFF7D68AA6(unsigned __int64 a1)
{
  int v1; // r8d
  const char *result; // rax
 
  v1 = 0;
  if ( a1 > 0xFFFFFFFFFFFFFF88LL )
    v1 = -(int)a1;
  switch ( v1 )
  {
    case 0:
      result = "No error detected";
      break;
    case 1:
      result = "Error (generic)";
      break;
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 11:
    case 13:
    case 15:
    case 17:
    case 18:
    case 19:
    case 21:
    case 23:
LABEL_38:
      result = "Unspecified error code";
      break;
    case 10:
      result = "Unknown frame descriptor";
      break;
    case 12:
      result = "Version not supported";
      break;
    case 14:
      result = "Unsupported frame parameter";
      break;
    case 16:
      result = "Frame requires too much memory for decoding";
      break;
    case 20:
      result = "Data corruption detected";
      break;
    case 22:
      result = "Restored data doesn't match checksum";
      break;
    case 24:
      result = "Header of Literals' block doesn't respect format specification";
      break;
    default:
      switch ( v1 )
      {
        case 30:
          result = "Dictionary is corrupted";
          break;
        case 32:
          result = "Dictionary mismatch";
          break;
        case 34:
          result = "Cannot create Dictionary from provided samples";
          break;
        case 40:
          result = "Unsupported parameter";
          break;
        case 41:
          result = "Unsupported combination of parameters";
          break;
        case 42:
          result = "Parameter is out of bound";
          break;
        case 44:
          result = "tableLog requires too much memory : unsupported";
          break;
        case 46:
          result = "Unsupported max Symbol Value : too large";
          break;
        case 48:
          result = "Specified maxSymbolValue is too small";
          break;
        case 49:
          result = "This mode cannot generate an uncompressed block";
          break;
        case 50:
          result = "pledged buffer stability condition is not respected";
          break;
        case 60:
          result = "Operation not authorized at current processing stage";
          break;
        case 62:
          result = "Context should be init first";
          break;
        case 64:
          result = "Allocation error : not enough memory";
          break;
        case 66:
          result = "workSpace buffer is not large enough";
          break;
        case 70:
          result = "Destination buffer is too small";
          break;
        case 72:
          result = "Src size is incorrect";
          break;
        case 74:
          result = "Operation on NULL destination buffer";
          break;
        case 80:
          result = "Operation made no progress over multiple calls, due to output buffer being full";
          break;
        case 82:
          result = "Operation made no progress over multiple calls, due to input being empty";
          break;
        case 100:
          result = "Frame index is too large";
          break;
        case 102:
          result = "An I/O error occurred when reading/seeking";
          break;
        case 104:
          result = "Destination buffer is wrong";
          break;
        case 105:
          result = "Source buffer is wrong";
          break;
        case 106:
          result = "Block-level external sequence producer returned an error code";
          break;
        case 107:
          result = "External sequences are not valid";
          break;
        default:
          goto LABEL_38;
      }
      break;
  }
  return result;
}
const char *__fastcall sub_7FFFF7D68AA6(unsigned __int64 a1)
{
  int v1; // r8d
  const char *result; // rax
 
  v1 = 0;
  if ( a1 > 0xFFFFFFFFFFFFFF88LL )
    v1 = -(int)a1;
  switch ( v1 )
  {
    case 0:
      result = "No error detected";
      break;
    case 1:
      result = "Error (generic)";
      break;
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 11:
    case 13:
    case 15:
    case 17:
    case 18:
    case 19:
    case 21:
    case 23:
LABEL_38:
      result = "Unspecified error code";
      break;
    case 10:
      result = "Unknown frame descriptor";
      break;
    case 12:
      result = "Version not supported";
      break;
    case 14:
      result = "Unsupported frame parameter";
      break;
    case 16:
      result = "Frame requires too much memory for decoding";
      break;
    case 20:
      result = "Data corruption detected";
      break;
    case 22:
      result = "Restored data doesn't match checksum";
      break;
    case 24:
      result = "Header of Literals' block doesn't respect format specification";
      break;
    default:
      switch ( v1 )
      {
        case 30:
          result = "Dictionary is corrupted";
          break;
        case 32:
          result = "Dictionary mismatch";
          break;
        case 34:
          result = "Cannot create Dictionary from provided samples";
          break;
        case 40:
          result = "Unsupported parameter";
          break;
        case 41:
          result = "Unsupported combination of parameters";
          break;
        case 42:
          result = "Parameter is out of bound";
          break;
        case 44:
          result = "tableLog requires too much memory : unsupported";
          break;
        case 46:
          result = "Unsupported max Symbol Value : too large";
          break;
        case 48:
          result = "Specified maxSymbolValue is too small";
          break;
        case 49:
          result = "This mode cannot generate an uncompressed block";
          break;
        case 50:
          result = "pledged buffer stability condition is not respected";
          break;
        case 60:
          result = "Operation not authorized at current processing stage";
          break;
        case 62:
          result = "Context should be init first";
          break;
        case 64:
          result = "Allocation error : not enough memory";
          break;
        case 66:
          result = "workSpace buffer is not large enough";
          break;
        case 70:
          result = "Destination buffer is too small";
          break;
        case 72:
          result = "Src size is incorrect";
          break;
        case 74:
          result = "Operation on NULL destination buffer";
          break;
        case 80:
          result = "Operation made no progress over multiple calls, due to output buffer being full";
          break;
        case 82:
          result = "Operation made no progress over multiple calls, due to input being empty";
          break;
        case 100:
          result = "Frame index is too large";
          break;
        case 102:
          result = "An I/O error occurred when reading/seeking";
          break;
        case 104:
          result = "Destination buffer is wrong";
          break;
        case 105:
          result = "Source buffer is wrong";
          break;
        case 106:
          result = "Block-level external sequence producer returned an error code";
          break;
        case 107:
          result = "External sequences are not valid";
          break;
        default:
          goto LABEL_38;
      }
      break;
  }
  return result;
}
AES_SBOX = bytes.fromhex(
    "637C699066329A0E6441CBA99FFAD5AA6524F777371D83EB981A2A7DBD2502EEE"
    "5E7455029C4ECA7CCF05C4D1396A2099EFF5AA1C76FE9150C1BC5975614A5B620"
    "D62111700D7F4E4652354BA4C9011E310F2F17FCDB7430DE481C950653D36718F"
    "D2D1F7A8D8775B426E071A3825807D4BADAA8B5D99CCFF960D81200798904C2B8"
    "3C614276DF6CEA495462E8B3F50BF1287ED2CD23F28E80F836E3D722DDF34A2E5"
    "510C0B15943AC683FBB6DAFCAC638B973AEDCBC9DC3D14CFEA63B92E42B5BFB2C"
    "F6C1B25D8FEF78915F9472ED4088B7443427E16A0586C8938A7B8451E63D990A3"
    "3BF39038C086B3E8519CEB08BABA0E247BE4F5E9B57AD6E81163AD0F4"
)
AES_SBOX = bytes.fromhex(
    "637C699066329A0E6441CBA99FFAD5AA6524F777371D83EB981A2A7DBD2502EEE"
    "5E7455029C4ECA7CCF05C4D1396A2099EFF5AA1C76FE9150C1BC5975614A5B620"
    "D62111700D7F4E4652354BA4C9011E310F2F17FCDB7430DE481C950653D36718F"
    "D2D1F7A8D8775B426E071A3825807D4BADAA8B5D99CCFF960D81200798904C2B8"
    "3C614276DF6CEA495462E8B3F50BF1287ED2CD23F28E80F836E3D722DDF34A2E5"
    "510C0B15943AC683FBB6DAFCAC638B973AEDCBC9DC3D14CFEA63B92E42B5BFB2C"
    "F6C1B25D8FEF78915F9472ED4088B7443427E16A0586C8938A7B8451E63D990A3"
    "3BF39038C086B3E8519CEB08BABA0E247BE4F5E9B57AD6E81163AD0F4"
)
__int64 __fastcall sub_409DC0(volatile signed __int32 *a1, int a2, __int64 a3, __int64 a4)
{
  __int64 result; // rax
  int v5; // ett
  int v6; // ecx
  unsigned int v7; // ebp
  int v8; // ecx
  unsigned __int32 v9; // ebp
  bool v10; // al
  __int64 v11; // [rsp+86h] [rbp-58h] BYREF
  __int64 v12; // [rsp+9Eh] [rbp-40h]
  __int64 v13; // [rsp+A6h] [rbp-38h]
 
  v13 = a4;
  v12 = a3;
  result = *(unsigned int *)a1;
  if ( a2 )
  {
    do
    {
      while ( 1 )
      {
        v6 = result & 3;
        if ( (unsigned int)(v6 - 2) < 2 )
          break;
        if ( v6 != 1 )
          return result;
        v7 = result;
        if ( (result & 4) == 0 )
        {
          v7 = result | 4;
          result = (unsigned int)_InterlockedCompareExchange(a1, result | 4, 1);
          if ( (_DWORD)result != 1 )
            continue;
        }
        v11 = 0LL;
        while ( *a1 == v7
             && ((__int64 (__fastcall *)(__int64, volatile signed __int32 *, __int64, _QWORD, _QWORD, _QWORD, int))unk_4C70D6)(
                  202LL,
                  a1,
                  137LL,
                  v7,
                  0LL,
                  0LL,
                  -1) < 0
             && *(_DWORD *)((__int64 (*)(void))unk_4C4D28)() == 4 )
          ;
        result = *(unsigned int *)a1;
      }
      v5 = result;
      result = (unsigned int)_InterlockedCompareExchange(a1, result & 4 | 1, result);
    }
    while ( v5 != (_DWORD)result );
    v10 = v6 == 2;
  }
  else
  {
LABEL_15:
    while ( 2 )
    {
      v8 = result & 4;
      v9 = result;
      switch ( result & 3 )
      {
        case 0LL:
          return result;
        case 1LL:
          while ( 2 )
          {
            if ( v8 || (v9 |= 4u, result = (unsigned int)_InterlockedCompareExchange(a1, v9, 1), (_DWORD)result == 1) )
            {
              v11 = 0LL;
              while ( *a1 == v9
                   && ((__int64 (__fastcall *)(__int64, volatile signed __int32 *, __int64, _QWORD, _QWORD, _QWORD, int))unk_4C70D6)(
                        202LL,
                        a1,
                        137LL,
                        v9,
                        0LL,
                        0LL,
                        -1) < 0
                   && *(_DWORD *)((__int64 (*)(void))unk_4C4D28)() == 4 )
                ;
              v9 = *a1;
              result = *a1 & 3;
              v8 = *a1 & 4;
              switch ( *a1 & 3 )
              {
                case 0:
                  return result;
                case 1:
                  continue;
                case 2:
                  goto LABEL_23;
                case 3:
                  goto LABEL_14;
              }
            }
            goto LABEL_15;
          }
        case 2LL:
LABEL_23:
          BUG();
        case 3LL:
LABEL_14:
          result = (unsigned int)_InterlockedCompareExchange(a1, v8 | 1, v9);
          if ( v9 != (_DWORD)result )
            continue;
          v10 = 0;
          break;
      }
      break;
    }
  }
  LODWORD(v11) = 0;
  BYTE4(v11) = v10;
  (*(void (__fastcall **)(__int64, __int64 *))(v13 + 32))(v12, &v11);
  result = (unsigned int)_InterlockedExchange(a1, v11);
  if ( (result & 4) != 0 )
    return off_6E9E98(202LL, a1, 129LL, 0x7FFFFFFFLL);
  return result;
}
__int64 __fastcall sub_41EBE0(__int64 a1)
{
  __int64 i; // rbp
  _UNKNOWN **v3; // [rsp+0h] [rbp-148h] BYREF
  _UNKNOWN ***v4; // [rsp+8h] [rbp-140h] BYREF
  _OWORD v5[19]; // [rsp+10h] [rbp-138h] BYREF
 
  memset(v5, 0, 256);
  for ( i = 0LL; i != 256; ++i )
  {
    if ( dword_6EA310 )
    {
      v3 = &off_6EA010;
      v4 = &v3;
      sub_409DC0(&dword_6EA310, 1, (__int64)&v4, (__int64)&unk_6E95E8);
    }
    *((_BYTE *)v5 + i) = __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 3) ^ __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 1) ^ __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 4) ^ *((_BYTE *)&off_6EA010 + i + 512) ^ __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 2) ^ 0x63;
  }
  off_6E9ED8(a1, v5, 256LL);
  *(_QWORD *)(a1 + 256) = 10LL;
  *(_QWORD *)(a1 + 264) = 11LL;
  *(_QWORD *)(a1 + 272) = 12LL;
  *(_QWORD *)(a1 + 280) = 13LL;
  *(_QWORD *)(a1 + 288) = 14LL;
  *(_QWORD *)(a1 + 296) = 11LL;
  *(_QWORD *)(a1 + 304) = 11LL;
  *(_QWORD *)(a1 + 312) = 12LL;
  *(_QWORD *)(a1 + 320) = 13LL;
  *(_QWORD *)(a1 + 328) = 14LL;
  *(_QWORD *)(a1 + 336) = 12LL;
  *(_QWORD *)(a1 + 344) = 12LL;
  *(_QWORD *)(a1 + 352) = 12LL;
  *(_QWORD *)(a1 + 360) = 13LL;
  *(_QWORD *)(a1 + 368) = 14LL;
  *(_QWORD *)(a1 + 376) = 13LL;
  *(_QWORD *)(a1 + 384) = 13LL;
  *(_QWORD *)(a1 + 392) = 13LL;
  *(_QWORD *)(a1 + 400) = 13LL;
  *(_QWORD *)(a1 + 408) = 14LL;
  *(_QWORD *)(a1 + 416) = 14LL;
  *(_QWORD *)(a1 + 424) = 14LL;
  *(_QWORD *)(a1 + 432) = 14LL;
  *(_QWORD *)(a1 + 440) = 14LL;
  *(_QWORD *)(a1 + 448) = 14LL;
  *(_DWORD *)(a1 + 456) = 17170948;
  return a1;
}
__int64 __fastcall sub_41EE90(_QWORD **a1, __int64 a2)
{
  __int64 v2; // r14
  void (__fastcall **v3)(_BYTE *); // rbx
  char v5; // al
  __int64 v6; // rcx
  char v7; // si
  bool v8; // sf
  char v9; // al
  char v10; // si
  __int64 i; // rax
  _OWORD v12[15]; // [rsp+0h] [rbp-630h] BYREF
  __int128 v13; // [rsp+F0h] [rbp-540h]
  _OWORD v14[15]; // [rsp+100h] [rbp-530h] BYREF
  __int128 v15; // [rsp+1F0h] [rbp-440h]
  _OWORD v16[16]; // [rsp+200h] [rbp-430h] BYREF
  void (__fastcall **v17)(_BYTE *); // [rsp+308h] [rbp-328h]
  __int64 v18; // [rsp+310h] [rbp-320h]
  _BYTE v19[784]; // [rsp+320h] [rbp-310h] BYREF
 
  v3 = (void (__fastcall **)(_BYTE *))**a1;
  **a1 = 0LL;
  if ( !v3 )
    BUG();
  if ( *(_BYTE *)(a2 + 4) == 1 )
  {
    ((void (*)(void))unk_404120)();
    v18 = v2;
    v17 = v3;
    v15 = 0LL;
    memset(v14, 0, sizeof(v14));
    memset(v12, 0, sizeof(v12));
    v13 = 0LL;
    v5 = 1;
    v6 = 0LL;
    do
    {
      *((_BYTE *)v12 + v6) = v5;
      *((_BYTE *)v14 + (unsigned __int8)v5) = v6++;
      v7 = 2 * v5;
      v8 = v5 < 0;
      v9 = (2 * v5) ^ 0x8D;
      if ( !v8 )
        v9 = v7;
      v10 = 2 * v9;
      v8 = v9 < 0;
      v5 = (2 * v9) ^ 0x8D;
      if ( !v8 )
        v5 = v10;
    }
    while ( v6 != 255 );
    HIBYTE(v13) = v12[0];
    memset(v16, 0, sizeof(v16));
    for ( i = 3LL; i != 258; i += 3LL )
    {
      *((_BYTE *)&v15 + i + 14) = *((_BYTE *)v12 + (*((unsigned __int8 *)&v13 + i + 14) ^ 0xFFLL));
      *((_BYTE *)&v15 + i + 15) = *((_BYTE *)v12 + (*((unsigned __int8 *)&v13 + i + 15) ^ 0xFFLL));
      *((_BYTE *)v16 + i) = *((_BYTE *)v12 + (*((unsigned __int8 *)v14 + i) ^ 0xFFLL));
    }
    ((void (__fastcall *)(_QWORD **, _OWORD *, __int64))unk_4C9CBF)(a1, v14, 256LL);
    ((void (__fastcall *)(_QWORD **, _OWORD *, __int64))unk_4C9CBF)(a1 + 32, v12, 256LL);
    ((void (__fastcall *)(_QWORD **, _OWORD *, __int64))unk_4C9CBF)(a1 + 64, v16, 256LL);
    return (__int64)a1;
  }
  else
  {
    (*v3)(v19);
    return off_6E9ED8(v3, v19, 768LL);
  }
}
__int64 __fastcall sub_409DC0(volatile signed __int32 *a1, int a2, __int64 a3, __int64 a4)
{
  __int64 result; // rax
  int v5; // ett
  int v6; // ecx
  unsigned int v7; // ebp
  int v8; // ecx
  unsigned __int32 v9; // ebp
  bool v10; // al
  __int64 v11; // [rsp+86h] [rbp-58h] BYREF
  __int64 v12; // [rsp+9Eh] [rbp-40h]
  __int64 v13; // [rsp+A6h] [rbp-38h]
 
  v13 = a4;
  v12 = a3;
  result = *(unsigned int *)a1;
  if ( a2 )
  {
    do
    {
      while ( 1 )
      {
        v6 = result & 3;
        if ( (unsigned int)(v6 - 2) < 2 )
          break;
        if ( v6 != 1 )
          return result;
        v7 = result;
        if ( (result & 4) == 0 )
        {
          v7 = result | 4;
          result = (unsigned int)_InterlockedCompareExchange(a1, result | 4, 1);
          if ( (_DWORD)result != 1 )
            continue;
        }
        v11 = 0LL;
        while ( *a1 == v7
             && ((__int64 (__fastcall *)(__int64, volatile signed __int32 *, __int64, _QWORD, _QWORD, _QWORD, int))unk_4C70D6)(
                  202LL,
                  a1,
                  137LL,
                  v7,
                  0LL,
                  0LL,
                  -1) < 0
             && *(_DWORD *)((__int64 (*)(void))unk_4C4D28)() == 4 )
          ;
        result = *(unsigned int *)a1;
      }
      v5 = result;
      result = (unsigned int)_InterlockedCompareExchange(a1, result & 4 | 1, result);
    }
    while ( v5 != (_DWORD)result );
    v10 = v6 == 2;
  }
  else
  {
LABEL_15:
    while ( 2 )
    {
      v8 = result & 4;
      v9 = result;
      switch ( result & 3 )
      {
        case 0LL:
          return result;
        case 1LL:
          while ( 2 )
          {
            if ( v8 || (v9 |= 4u, result = (unsigned int)_InterlockedCompareExchange(a1, v9, 1), (_DWORD)result == 1) )
            {
              v11 = 0LL;
              while ( *a1 == v9
                   && ((__int64 (__fastcall *)(__int64, volatile signed __int32 *, __int64, _QWORD, _QWORD, _QWORD, int))unk_4C70D6)(
                        202LL,
                        a1,
                        137LL,
                        v9,
                        0LL,
                        0LL,
                        -1) < 0
                   && *(_DWORD *)((__int64 (*)(void))unk_4C4D28)() == 4 )
                ;
              v9 = *a1;
              result = *a1 & 3;
              v8 = *a1 & 4;
              switch ( *a1 & 3 )
              {
                case 0:
                  return result;
                case 1:
                  continue;
                case 2:
                  goto LABEL_23;
                case 3:
                  goto LABEL_14;
              }
            }
            goto LABEL_15;
          }
        case 2LL:
LABEL_23:
          BUG();
        case 3LL:
LABEL_14:
          result = (unsigned int)_InterlockedCompareExchange(a1, v8 | 1, v9);
          if ( v9 != (_DWORD)result )
            continue;
          v10 = 0;
          break;
      }
      break;
    }
  }
  LODWORD(v11) = 0;
  BYTE4(v11) = v10;
  (*(void (__fastcall **)(__int64, __int64 *))(v13 + 32))(v12, &v11);
  result = (unsigned int)_InterlockedExchange(a1, v11);
  if ( (result & 4) != 0 )
    return off_6E9E98(202LL, a1, 129LL, 0x7FFFFFFFLL);
  return result;
}
__int64 __fastcall sub_41EBE0(__int64 a1)
{
  __int64 i; // rbp
  _UNKNOWN **v3; // [rsp+0h] [rbp-148h] BYREF
  _UNKNOWN ***v4; // [rsp+8h] [rbp-140h] BYREF
  _OWORD v5[19]; // [rsp+10h] [rbp-138h] BYREF
 
  memset(v5, 0, 256);
  for ( i = 0LL; i != 256; ++i )
  {
    if ( dword_6EA310 )
    {
      v3 = &off_6EA010;
      v4 = &v3;
      sub_409DC0(&dword_6EA310, 1, (__int64)&v4, (__int64)&unk_6E95E8);
    }
    *((_BYTE *)v5 + i) = __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 3) ^ __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 1) ^ __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 4) ^ *((_BYTE *)&off_6EA010 + i + 512) ^ __ROL1__(*((_BYTE *)&off_6EA010 + i + 512), 2) ^ 0x63;
  }
  off_6E9ED8(a1, v5, 256LL);
  *(_QWORD *)(a1 + 256) = 10LL;
  *(_QWORD *)(a1 + 264) = 11LL;
  *(_QWORD *)(a1 + 272) = 12LL;
  *(_QWORD *)(a1 + 280) = 13LL;
  *(_QWORD *)(a1 + 288) = 14LL;
  *(_QWORD *)(a1 + 296) = 11LL;
  *(_QWORD *)(a1 + 304) = 11LL;
  *(_QWORD *)(a1 + 312) = 12LL;
  *(_QWORD *)(a1 + 320) = 13LL;
  *(_QWORD *)(a1 + 328) = 14LL;
  *(_QWORD *)(a1 + 336) = 12LL;
  *(_QWORD *)(a1 + 344) = 12LL;
  *(_QWORD *)(a1 + 352) = 12LL;
  *(_QWORD *)(a1 + 360) = 13LL;
  *(_QWORD *)(a1 + 368) = 14LL;
  *(_QWORD *)(a1 + 376) = 13LL;
  *(_QWORD *)(a1 + 384) = 13LL;
  *(_QWORD *)(a1 + 392) = 13LL;
  *(_QWORD *)(a1 + 400) = 13LL;
  *(_QWORD *)(a1 + 408) = 14LL;
  *(_QWORD *)(a1 + 416) = 14LL;
  *(_QWORD *)(a1 + 424) = 14LL;
  *(_QWORD *)(a1 + 432) = 14LL;
  *(_QWORD *)(a1 + 440) = 14LL;
  *(_QWORD *)(a1 + 448) = 14LL;
  *(_DWORD *)(a1 + 456) = 17170948;
  return a1;
}
__int64 __fastcall sub_41EE90(_QWORD **a1, __int64 a2)
{
  __int64 v2; // r14
  void (__fastcall **v3)(_BYTE *); // rbx
  char v5; // al
  __int64 v6; // rcx
  char v7; // si
  bool v8; // sf
  char v9; // al
  char v10; // si
  __int64 i; // rax
  _OWORD v12[15]; // [rsp+0h] [rbp-630h] BYREF
  __int128 v13; // [rsp+F0h] [rbp-540h]
  _OWORD v14[15]; // [rsp+100h] [rbp-530h] BYREF
  __int128 v15; // [rsp+1F0h] [rbp-440h]
  _OWORD v16[16]; // [rsp+200h] [rbp-430h] BYREF
  void (__fastcall **v17)(_BYTE *); // [rsp+308h] [rbp-328h]
  __int64 v18; // [rsp+310h] [rbp-320h]
  _BYTE v19[784]; // [rsp+320h] [rbp-310h] BYREF
 
  v3 = (void (__fastcall **)(_BYTE *))**a1;
  **a1 = 0LL;
  if ( !v3 )
    BUG();
  if ( *(_BYTE *)(a2 + 4) == 1 )
  {
    ((void (*)(void))unk_404120)();
    v18 = v2;
    v17 = v3;
    v15 = 0LL;
    memset(v14, 0, sizeof(v14));
    memset(v12, 0, sizeof(v12));
    v13 = 0LL;
    v5 = 1;
    v6 = 0LL;
    do
    {
      *((_BYTE *)v12 + v6) = v5;
      *((_BYTE *)v14 + (unsigned __int8)v5) = v6++;
      v7 = 2 * v5;
      v8 = v5 < 0;
      v9 = (2 * v5) ^ 0x8D;
      if ( !v8 )
        v9 = v7;
      v10 = 2 * v9;
      v8 = v9 < 0;
      v5 = (2 * v9) ^ 0x8D;
      if ( !v8 )
        v5 = v10;
    }
    while ( v6 != 255 );
    HIBYTE(v13) = v12[0];
    memset(v16, 0, sizeof(v16));
    for ( i = 3LL; i != 258; i += 3LL )
    {
      *((_BYTE *)&v15 + i + 14) = *((_BYTE *)v12 + (*((unsigned __int8 *)&v13 + i + 14) ^ 0xFFLL));
      *((_BYTE *)&v15 + i + 15) = *((_BYTE *)v12 + (*((unsigned __int8 *)&v13 + i + 15) ^ 0xFFLL));
      *((_BYTE *)v16 + i) = *((_BYTE *)v12 + (*((unsigned __int8 *)v14 + i) ^ 0xFFLL));
    }
    ((void (__fastcall *)(_QWORD **, _OWORD *, __int64))unk_4C9CBF)(a1, v14, 256LL);
    ((void (__fastcall *)(_QWORD **, _OWORD *, __int64))unk_4C9CBF)(a1 + 32, v12, 256LL);
    ((void (__fastcall *)(_QWORD **, _OWORD *, __int64))unk_4C9CBF)(a1 + 64, v16, 256LL);
    return (__int64)a1;
  }
  else
  {
    (*v3)(v19);
    return off_6E9ED8(v3, v19, 768LL);
  }
}
n = bytes.fromhex("B75815AF28D17CCE2ECA787C6004EC6F1A51AC14B5A7AF746C7B3DD1354517C3CF38B35BE17E64BAB76A47FEA92396DDA947CC268EE0DACC097911912AB64B4C572D7518003C8B24D9DF5E950D44FE9613805ABDC47EA1F693EB6B04D56A124522126770E9FC771B185EC44F308D57FAA6D3C67A585A5E1672F5F89FADB9B073E913166A99D5A3896BDC6430B1C4A5AECDA7FEFA15AEB41A37E76D61698FF36A2F1B2B0A0CBC1AA0AD068C8ECF9388558B0257335EADC4831397917CE1C9B5E2B033C6935F1B57A06BC830B3D03CC8C8D758A38CD8D85583435B3594A7599F1B692B9FBF0E98B388E6A96D20D6245EF5503F79693552987CCAEC2C86D481A45EEA1C573D33FA15109962E5C8D2A02A6923FB375C6B1F05FC4CDCCC17055AADBA17085FC8C22563DA4FFD05FEBD07A485B5B28C3203890FDABDDD6693C40FCECE05D4FEA9EE46F1447416B7FF4D914A6A5787917637977A3330659D8191CD102093F15ED4D2444E60A4950AE51EBF616721F8785F5656130CFABE2174DBB9F9E5121F8F10670EC0538465B283A02C187989E06C07E3BD3792C5E5E7C49752ADCCFA573DF668C90AB67C61CFC5E46D5CC387D51078D27ADBD8E3AE3CDA5C7A00E741B60E8DD1AD61D0F7633FC2E9F30A3BAC74897001B1AEB1B7C27A0C25A75620B8FD1374084F86186C56AF651FBEEE0DFEDA25FC110EFCD8BBB0C05222326BBF")
n = int.from_bytes(n, "little")
print(hex((1<<4096)%n-1))
n = bytes.fromhex("B75815AF28D17CCE2ECA787C6004EC6F1A51AC14B5A7AF746C7B3DD1354517C3CF38B35BE17E64BAB76A47FEA92396DDA947CC268EE0DACC097911912AB64B4C572D7518003C8B24D9DF5E950D44FE9613805ABDC47EA1F693EB6B04D56A124522126770E9FC771B185EC44F308D57FAA6D3C67A585A5E1672F5F89FADB9B073E913166A99D5A3896BDC6430B1C4A5AECDA7FEFA15AEB41A37E76D61698FF36A2F1B2B0A0CBC1AA0AD068C8ECF9388558B0257335EADC4831397917CE1C9B5E2B033C6935F1B57A06BC830B3D03CC8C8D758A38CD8D85583435B3594A7599F1B692B9FBF0E98B388E6A96D20D6245EF5503F79693552987CCAEC2C86D481A45EEA1C573D33FA15109962E5C8D2A02A6923FB375C6B1F05FC4CDCCC17055AADBA17085FC8C22563DA4FFD05FEBD07A485B5B28C3203890FDABDDD6693C40FCECE05D4FEA9EE46F1447416B7FF4D914A6A5787917637977A3330659D8191CD102093F15ED4D2444E60A4950AE51EBF616721F8785F5656130CFABE2174DBB9F9E5121F8F10670EC0538465B283A02C187989E06C07E3BD3792C5E5E7C49752ADCCFA573DF668C90AB67C61CFC5E46D5CC387D51078D27ADBD8E3AE3CDA5C7A00E741B60E8DD1AD61D0F7633FC2E9F30A3BAC74897001B1AEB1B7C27A0C25A75620B8FD1374084F86186C56AF651FBEEE0DFEDA25FC110EFCD8BBB0C05222326BBF")
n = int.from_bytes(n, "little")
print(hex((1<<4096)%n-1))
import hashlib
from base64 import b64encode
import zstandard as zstd
 
 
def zstd_compress(data):
    return zstd.compress(data)
 
 
def xtime(a) -> int:
    a &= 0xFF
    return (((a << 1) ^ 0x8D) & 0xFF) if (a & 0x80) else ((a << 1) & 0xFF)
 
def gf_mul(a, b) -> int:
    a &= 0xFF
    b &= 0xFF
    res = 0
    for _ in range(8):
        if b & 1:
            res ^= a
        a = xtime(a)
        b >>= 1
    return res & 0xFF
 
# ---------------- S-box / inverse S-box (standard AES S-box) ----------------
 
AES_SBOX = bytes.fromhex(
    "637C699066329A0E6441CBA99FFAD5AA6524F777371D83EB981A2A7DBD2502EEE"
    "5E7455029C4ECA7CCF05C4D1396A2099EFF5AA1C76FE9150C1BC5975614A5B620"
    "D62111700D7F4E4652354BA4C9011E310F2F17FCDB7430DE481C950653D36718F"
    "D2D1F7A8D8775B426E071A3825807D4BADAA8B5D99CCFF960D81200798904C2B8"
    "3C614276DF6CEA495462E8B3F50BF1287ED2CD23F28E80F836E3D722DDF34A2E5"
    "510C0B15943AC683FBB6DAFCAC638B973AEDCBC9DC3D14CFEA63B92E42B5BFB2C"
    "F6C1B25D8FEF78915F9472ED4088B7443427E16A0586C8938A7B8451E63D990A3"
    "3BF39038C086B3E8519CEB08BABA0E247BE4F5E9B57AD6E81163AD0F4"
)
 
INV_SBOX = bytearray(256)
for i, v in enumerate(AES_SBOX):
    INV_SBOX[v] = i
INV_SBOX = bytes(INV_SBOX)
 
# ---------------- Rijndael-160 params ----------------
 
def rijndael_rounds(Nb, Nk) -> int:
    return max(Nb, Nk) + 6  # same as Rijndael
 
# ---------------- Key schedule (uses your xtime/rcon field) ----------------
 
def rot_word(w) -> int:
    return ((w << 8) & 0xFFFFFFFF) | ((w >> 24) & 0xFF)
 
def sub_word(w, sbox) -> int:
    return (
        (sbox[(w >> 24) & 0xFF] << 24) |
        (sbox[(w >> 16) & 0xFF] << 16) |
        (sbox[(w >>  8) & 0xFF] <<  8) |
        (sbox[(w >>  0) & 0xFF] <<  0)
    ) & 0xFFFFFFFF
 
def rcon(i) -> int:
    # i starts from 1, stored in MSB byte
    c = 1
    for _ in range(i - 1):
        c = xtime(c)
    return (c << 24) & 0xFFFFFFFF
 
def key_expansion(key, Nb, Nk, sbox):
    if len(key) != 4 * Nk:
        raise ValueError(f"key length must be {4*Nk} bytes for Nk={Nk}, got {len(key)}")
 
    Nr = rijndael_rounds(Nb, Nk)
    W_words = Nb * (Nr + 1)
 
    w = [0] * W_words
 
    # first Nk words (big-endian as in your code)
    for i in range(Nk):
        w[i] = int.from_bytes(key[4*i:4*i+4], "big")
 
    for i in range(Nk, W_words):
        temp = w[i - 1]
        if i % Nk == 0:
            temp = sub_word(rot_word(temp), sbox) ^ rcon(i // Nk)
        elif Nk > 6 and (i % Nk) == 4:
            temp = sub_word(temp, sbox)
        w[i] = (w[i - Nk] ^ temp) & 0xFFFFFFFF
 
    return w
 
# ---------------- State helpers (Nb=5 => 4x5, column-major) ----------------
 
def bytes_to_state(block, Nb):
    if len(block) != 4 * Nb:
        raise ValueError("bad block size")
    s = [[0]*Nb for _ in range(4)]
    for c in range(Nb):
        for r in range(4):
            s[r][c] = block[4*c + r]
    return s
 
def state_to_bytes(s, Nb):
    out = bytearray(4*Nb)
    for c in range(Nb):
        for r in range(4):
            out[4*c + r] = s[r][c] & 0xFF
    return bytes(out)
 
def add_round_key(s, round_w, round_idx, Nb) -> None:
    base = round_idx * Nb
    for c in range(Nb):
        w = round_w[base + c]
        s[0][c] ^= (w >> 24) & 0xFF
        s[1][c] ^= (w >> 16) & 0xFF
        s[2][c] ^= (w >>  8) & 0xFF
        s[3][c] ^= (w >>  0) & 0xFF
 
def sub_bytes(s, sbox, Nb) -> None:
    for r in range(4):
        for c in range(Nb):
            s[r][c] = sbox[s[r][c]]
 
# 你对齐出来的是“右移”版本
def shift_rows(s, Nb) -> None:
    for r in range(1, 4):
        k = r % Nb
        row = s[r]
        s[r] = row[-k:] + row[:-k]
 
def inv_shift_rows(s, Nb) -> None:
    for r in range(1, 4):
        k = r % Nb
        row = s[r]
        s[r] = row[k:] + row[:k]
 
# ---------------- MixColumns (your matrix) + auto inverse ----------------
 
MIX_MAT = [
    [4, 1, 6, 2],
    [2, 4, 1, 6],
    [6, 2, 4, 1],
    [1, 6, 2, 4],
]
 
def gf_mat_inv_4x4(mat):
    # Gauss-Jordan over GF(2^8) with xor add and gf_mul
    # Build [mat | I]
    A = [[mat[r][c] & 0xFF for c in range(4)] + [1 if c == r else 0 for c in range(4)] for r in range(4)]
 
    def gf_inv(x) -> int:
        # brute-force inverse in GF(2^8) for this field
        x &= 0xFF
        if x == 0:
            raise ZeroDivisionError("no inverse for 0")
        for y in range(1, 256):
            if gf_mul(x, y) == 1:
                return y
        raise ZeroDivisionError("no inverse found (should not happen)")
 
    for col in range(4):
        # find pivot
        pivot = None
        for r in range(col, 4):
            if A[r][col] != 0:
                pivot = r
                break
        if pivot is None:
            raise ValueError("matrix not invertible")
 
        if pivot != col:
            A[col], A[pivot] = A[pivot], A[col]
 
        inv_p = gf_inv(A[col][col])
        # scale pivot row
        for j in range(8):
            A[col][j] = gf_mul(A[col][j], inv_p)
 
        # eliminate other rows
        for r in range(4):
            if r == col:
                continue
            factor = A[r][col]
            if factor == 0:
                continue
            for j in range(8):
                A[r][j] ^= gf_mul(factor, A[col][j])
 
    inv = [[A[r][4 + c] & 0xFF for c in range(4)] for r in range(4)]
    return inv
 
INV_MIX_MAT = gf_mat_inv_4x4(MIX_MAT)
 
def mix_single_column(col):
    a0, a1, a2, a3 = [x & 0xFF for x in col]
    out = []
    for r in range(4):
        out.append(
            gf_mul(MIX_MAT[r][0], a0) ^
            gf_mul(MIX_MAT[r][1], a1) ^
            gf_mul(MIX_MAT[r][2], a2) ^
            gf_mul(MIX_MAT[r][3], a3)
        )
    return [x & 0xFF for x in out]
 
def inv_mix_single_column(col):
    a0, a1, a2, a3 = [x & 0xFF for x in col]
    out = []
    for r in range(4):
        out.append(
            gf_mul(INV_MIX_MAT[r][0], a0) ^
            gf_mul(INV_MIX_MAT[r][1], a1) ^
            gf_mul(INV_MIX_MAT[r][2], a2) ^
            gf_mul(INV_MIX_MAT[r][3], a3)
        )
    return [x & 0xFF for x in out]
 
def mix_columns(s, Nb) -> None:
    for c in range(Nb):
        col = [s[r][c] for r in range(4)]
        mc = mix_single_column(col)
        for r in range(4):
            s[r][c] = mc[r]
 
def inv_mix_columns(s, Nb) -> None:
    for c in range(Nb):
        col = [s[r][c] for r in range(4)]
        mc = inv_mix_single_column(col)
        for r in range(4):
            s[r][c] = mc[r]
 
# ---------------- Block encrypt/decrypt (20B) ----------------
 
def rijndael160_encrypt_block(block20, key28, Nb = 5, Nk = 7):
    Nr = rijndael_rounds(Nb, Nk)
    w = key_expansion(key28, Nb, Nk, AES_SBOX)
 
    s = bytes_to_state(block20, Nb)
    add_round_key(s, w, 0, Nb)
 
    for rnd in range(1, Nr):
        sub_bytes(s, AES_SBOX, Nb)
        shift_rows(s, Nb)
        mix_columns(s, Nb)
        add_round_key(s, w, rnd, Nb)
 
    sub_bytes(s, AES_SBOX, Nb)
    shift_rows(s, Nb)
    add_round_key(s, w, Nr, Nb)
 
    return state_to_bytes(s, Nb)
 
def rijndael160_decrypt_block(ct20, key28, Nb = 5, Nk = 7):
    Nr = rijndael_rounds(Nb, Nk)
    w = key_expansion(key28, Nb, Nk, AES_SBOX)
 
    s = bytes_to_state(ct20, Nb)
    add_round_key(s, w, Nr, Nb)
 
    for rnd in range(Nr - 1, 0, -1):
        inv_shift_rows(s, Nb)
        sub_bytes(s, INV_SBOX, Nb)
        add_round_key(s, w, rnd, Nb)
        inv_mix_columns(s, Nb)
 
    inv_shift_rows(s, Nb)
    sub_bytes(s, INV_SBOX, Nb)
    add_round_key(s, w, 0, Nb)
 
    return state_to_bytes(s, Nb)
 
# ---------------- PKCS#7 padding for 20-byte blocks ----------------
 
BLOCK_SIZE = 20
 
def pkcs7_pad(data, block_size = BLOCK_SIZE):
    pad_len = block_size - (len(data) % block_size)
    if pad_len == 0:
        pad_len = block_size
    return data + bytes([pad_len]) * pad_len
 
def pkcs7_unpad(padded, block_size = BLOCK_SIZE):
    if not padded or (len(padded) % block_size) != 0:
        raise ValueError("bad padded length")
    pad_len = padded[-1]
    if pad_len < 1 or pad_len > block_size:
        raise ValueError("bad padding")
    if padded[-pad_len:] != bytes([pad_len]) * pad_len:
        raise ValueError("bad padding bytes")
    return padded[:-pad_len]
 
# ---------------- High-level encrypt/decrypt with PBKDF2 key + post-xor mask ----------------
 
def derive_key_and_mask(password_b, salt, ITER):
    enc_data_48 = hashlib.pbkdf2_hmac("sha256", password_b, salt, ITER, dklen=48)
    key28 = enc_data_48[:28]
    mask20 = enc_data_48[28:]
    return key28, mask20
 
def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))
 
 
# ---------------- CBC 模式下的加密/解密逻辑 ----------------
 
def encrypt(data, password_b, salt, ITER):
    """
    CBC 模式加密 (逻辑互换版):
    1. PKCS7 Padding
    2. 使用 mask20 作为初始 IV
    3. 每个块:Plaintext XOR Previous_Ciphertext -> Decrypt_Core -> Ciphertext
    """
    key28, iv = derive_key_and_mask(password_b, salt, ITER)
 
    # 自动进行 PKCS7 填充
    data_p = pkcs7_pad(data, BLOCK_SIZE)
 
    out = bytearray()
    prev_block = iv  # 初始 IV
 
    for i in range(0, len(data_p), BLOCK_SIZE):
        plaintext_block = data_p[i:i + BLOCK_SIZE]
 
        # CBC 核心:明文先与前一个密文块异或
        mixed = xor_bytes(plaintext_block, prev_block)
 
        # 逻辑互换:调用原始的 decrypt 核心作为加密引擎
        ciphertext_block = rijndael160_decrypt_block(mixed, key28, Nb=5, Nk=7)
 
        out += ciphertext_block
        prev_block = ciphertext_block  # 更新 IV 为当前密文块
 
    return bytes(out)
 
 
def decrypt(ciphertext, password_b, salt, ITER):
    """
    CBC 模式解密 (逻辑互换版):
    1. 使用 mask20 作为初始 IV
    2. 每个块:Ciphertext -> Encrypt_Core -> XOR Previous_Ciphertext -> Plaintext
    3. 移除 Padding
    """
    if len(ciphertext) % BLOCK_SIZE != 0:
        raise ValueError("密文长度必须是 20 字节的倍数")
 
    key28, iv = derive_key_and_mask(password_b, salt, ITER)
 
    out = bytearray()
    prev_block = iv  # 初始 IV
 
    for i in range(0, len(ciphertext), BLOCK_SIZE):
        ciphertext_block = ciphertext[i:i + BLOCK_SIZE]
 
        # 逻辑互换:调用原始的 encrypt 核心作为解密引擎
        decrypted_core = rijndael160_encrypt_block(ciphertext_block, key28, Nb=5, Nk=7)
 
        # CBC 核心:解密后的数据与前一个密文块异或得到明文
        plaintext_block = xor_bytes(decrypted_core, prev_block)
 
        out += plaintext_block
        prev_block = ciphertext_block  # 更新 IV 为当前密文块(注意是解密前的密文)
 
    # 移除 PKCS7 填充
    return pkcs7_unpad(bytes(out), BLOCK_SIZE)
 
 
def encode_varint(x: int) -> bytes:
    if x < 0:
        raise ValueError("varint encoder expects non-negative int")
    out = bytearray()
    while True:
        b = x & 0x7F
        x >>= 7
        if x:
            out.append(b | 0x80)
        else:
            out.append(b)
            break
    return bytes(out)
 
def encode_key(field_number: int, wire_type: int) -> bytes:
    return encode_varint((field_number << 3) | wire_type)
 
def field_length_delimited(field_number: int, data) -> bytes:
    if isinstance(data, str):
        data = data.encode("utf-8")
    return encode_key(field_number, 2) + encode_varint(len(data)) + data
 
 
MASK128 = (1 << 128) - 1
R = 0xE1000000000000000000000000000000  # GCM reduction constant (SP800-38D)
 
# ---------------- AES-ECB 单块 ----------------
def aes_ecb_encrypt_block(key: bytes, block16: bytes) -> bytes:
    if len(block16) != 16:
        raise ValueError("block16 must be 16 bytes")
    if len(key) not in (16, 24, 32):
        raise ValueError("key must be 16/24/32 bytes")
 
    from Crypto.Cipher import AES  # type: ignore
    return AES.new(key, AES.MODE_ECB).encrypt(block16)
 
 
# ---------------- GCM: inc32 ----------------
def inc32(counter16: bytes) -> bytes:
    """只对最后32-bit(big-endian)+1,前12字节不变。"""
    if len(counter16) != 16:
        raise ValueError("counter16 must be 16 bytes")
    prefix = counter16[:12]
    c = int.from_bytes(counter16[12:], "big")
    c = (c + 1) & 0xFFFFFFFF
    return prefix + c.to_bytes(4, "big")
 
 
# ---------------- GF(2^128) 乘法 (GCM) ----------------
def gf128_mul_gcm(x: int, y: int) -> int:
    """
    标准 GHASH 乘法:按 big-endian bit 顺序。
    """
    z = 0
    v = x
    for i in range(128):
        if (y >> (127 - i)) & 1:
            z ^= v
        if v & 1:
            v = (v >> 1) ^ R
        else:
            v >>= 1
    return z & MASK128
 
 
def ghash(H: bytes, aad: bytes, c: bytes) -> bytes:
    """
    GHASH_H(A, C),最后包含 len(A)||len(C) (bit) 的长度块。
    """
    if len(H) != 16:
        raise ValueError("H must be 16 bytes")
    H_int = int.from_bytes(H, "big")
    X = 0
 
    def iter_blocks(data: bytes):
        for i in range(0, len(data), 16):
            blk = data[i:i+16]
            if len(blk) < 16:
                blk = blk + b"\x00" * (16 - len(blk))
            yield blk
 
    for blk in iter_blocks(aad):
        X = gf128_mul_gcm(X ^ int.from_bytes(blk, "big"), H_int)
 
    for blk in iter_blocks(c):
        X = gf128_mul_gcm(X ^ int.from_bytes(blk, "big"), H_int)
 
    len_block = (len(aad) * 8).to_bytes(8, "big") + (len(c) * 8).to_bytes(8, "big")
    X = gf128_mul_gcm(X ^ int.from_bytes(len_block, "big"), H_int)
 
    return X.to_bytes(16, "big")
 
 
# ---------------- GCM: CTR 加密/解密 ----------------
def gcm_ctr_crypt(key: bytes, J0: bytes, data: bytes) -> bytes:
    """
    GCM 的加解密(CTR):从 counter=inc32(J0) 开始。
    """
    if len(J0) != 16:
        raise ValueError("J0 must be 16 bytes")
 
    out = bytearray()
    counter = inc32(J0)
    for off in range(0, len(data), 16):
        block = data[off:off+16]
        ks = aes_ecb_encrypt_block(key, counter)
        out.extend(bytes(b ^ k for b, k in zip(block, ks[:len(block)])))
        counter = inc32(counter)
    return bytes(out)
 
 
# ---------------- GCM: 由 nonce(12) + plaintext 计算 ciphertext 和 tag ----------------
def gcm_encrypt_and_tag(key: bytes, nonce12: bytes, plaintext: bytes, aad: bytes = b""):
    """
    返回 (ciphertext, tag16)
    - J0 = nonce || 0x00000001  (nonce长度=12时标准写法)
    - H  = AES_K(0^128)
    - C  = CTR(J0, P)
    - S  = AES_K(J0)
    - tag = S XOR GHASH(H, AAD, C)
    """
    if len(nonce12) != 12:
        raise ValueError("nonce must be 12 bytes")
 
    J0 = nonce12 + b"\x00\x00\x00\x01"
    H = aes_ecb_encrypt_block(key, b"\x00" * 16)
 
    ciphertext = gcm_ctr_crypt(key, J0, plaintext)
 
    S = aes_ecb_encrypt_block(key, J0)
    g = ghash(H, aad, ciphertext)
    tag = bytes(x ^ y for x, y in zip(S, g))
    return ciphertext, tag
 
 
# ---------------- 拼包:4字节长度 + 12字节nonce + (ciphertext||tag) ----------------
def build_packet(nonce12: bytes, ciphertext: bytes, tag16: bytes) -> bytes:
    if len(nonce12) != 12:
        raise ValueError("nonce must be 12 bytes")
    if len(tag16) != 16:
        raise ValueError("tag must be 16 bytes")
 
    body = nonce12 + ciphertext + tag16
    length = len(body)  # = 12 + len(ciphertext) + 16
    return length.to_bytes(4, "little") + body
 
 
def make_packet_from_nonce_plain(key: bytes, nonce12: bytes, plaintext: bytes, aad: bytes = b""):
    ciphertext, tag16 = gcm_encrypt_and_tag(key, nonce12, plaintext, aad=aad)
    pkt = build_packet(nonce12, ciphertext, tag16)
    return pkt
 
 
n = int.from_bytes(bytes.fromhex("BF6B322252C0B0BBD8FC0E11FC25DAFE0DEEBE1F65AF566C18864F087413FDB82056A7250C7AC2B7B1AEB101708974AC3B0AF3E9C23F63F7D061ADD18D0EB641E7007A5CDA3CAEE3D8DB7AD27810D587C35C6DE4C5CF617CB60AC968F63D57FACCAD5297C4E7E5C59237BDE3076CE08979182CA083B2658453C00E67108F1F12E5F9B9DB7421BEFA0C1356565F78F8216761BF1EE50A95A4604E44D2D45EF1932010CD91819D6530337A9737769187576A4A914DFFB7167444F146EEA9FED405CECE0FC49366DDBDDA0F8903328CB2B585A407BDFE05FD4FDA6325C2C85F0817BAAD5A0517CCDC4CFC051F6B5C37FB23692AA0D2C8E562991015FA333D571CEA5EA481D4862CECCA7C98523569793F50F55E24D6206DA9E688B3980EBF9F2B691B9F59A794355B438355D8D88CA358D7C8C83CD0B330C86BA0571B5F93C633B0E2B5C9E17C91971383C4AD5E3357028B558893CF8E8C06ADA01ABC0C0A2B1B2F6AF38F69616DE7371AB4AE15FAFEA7CDAEA5C4B13064DC6B89A3D5996A1613E973B0B9AD9FF8F572165E5A587AC6D3A6FA578D304FC45E181B77FCE97067122245126AD5046BEB93F6A17EC4BD5A801396FE440D955EDFD9248B3C0018752D574C4BB62A91117909CCDAE08E26CC47A9DD9623A9FE476AB7BA647EE15BB338CFC3174535D13D7B6C74AFA7B514AC511A6FEC04607C78CA2ECE7CD128AF1558B7"), "big")
d = int.from_bytes(bytes.fromhex("32C892BD6E6CF6B66F83B78BE7F4771C0DC0382A8644B54DEA57BFA20381C63F523D0B0D16397F6D52B380FC5BC9EBED41A0CF434628A131FED3DB548BF2CA41C3B269C4369600E42C0556997E07214F6A721C29A49D3744E9DB04C25709C14CA57E9A39EFA082621F3FB09E09BB45FAD2E8A9F64FDA457A8CE9982899C90EBA69CF0E12FDC572304E81D6D7056F478D3D2B3E9448B9BD27A5F13DEB1D32AF2E944440F58888A46EDC497AD2D91F14E4092C0D4EBF37E8BA220C4D00469377D6AE9E16AAD55C6619D73F65DF364B03A28AF910A0C442FC8871ECF9F8AA4624147F8F3C21BBC5BAF0A5B00A3CE67367AA665D4BDB8036F3289E8EE6192FFDEB8A800A60196CF0C73E75EA5B397057451ADFD512AEE8F1E2C04F959B74270DD7A2F434A3C378C50734570CFFF1880C3D0879A4378AB2E06E2A51C4021205F8CC8D5A24878DB2457984F0BDA467D617CBC92AE65D154C1C4A98740007F4DB588DEBABD1579EA1E5162E3A7B716FB2ABC99274A8E252C2DF32AD674C85219B55213BF4C7621E5E7D583F04F69A68391F1F93FC472425900C9B51DECBDE9B2F742753AF51B9F1C146119D628D379A427D92C8F860D4D662DAFB0E9AF766B0FE580E3325E90F364F8C747095D4A259803139C3937B58EF20E601E8B78B70C2093A4D7335847EF18E6CFD8694BD6928350D66F3809B573C73A00889411C0B97C1204F6986"), "big")
e = 65537
 
 
def gen_packet(license_code):
    password_b = b"mypassword"
    salt = bytes.fromhex("1020304050")
    ITER = 1000
    TARGET_HEX = "".join(license_code.split("-"))
    calculated_sig = pow(int.from_bytes(bytes.fromhex(TARGET_HEX), "big"), e, n)
    raw_data = b'{"license_code":"'+license_code.encode()+b'","sign":"'+calculated_sig.to_bytes((calculated_sig.bit_length() + 7) // 8, 'big').hex().encode()+b'"}'
    ct = encrypt(raw_data, password_b, salt, ITER)
    protobuf_msg = b"".join([
        field_length_delimited(1, ct),  # enc_data = 20 bytes (派生)
        field_length_delimited(2, password_b),  # password
        field_length_delimited(3, salt),  # salt
        field_length_delimited(4, hashlib.sha256(raw_data).digest()),  # sha256_hash
    ])
    b64 = b64encode(protobuf_msg)
    buffer = zstd_compress(b64)
    key = bytes.fromhex("f6778d8728d8f17ce8c5c81f45c3d5fd869ca851b7575be540776f4f26c1140d")
    nonce12 = b"b" * 12
    packet = make_packet_from_nonce_plain(key, nonce12, buffer, aad=b"")
    return packet
 
 
if __name__ == "__main__":
    packet = gen_packet("612e2577-5b06-4bc3-b84b-b3c126b43662")
    print(int.from_bytes(packet[:4], byteorder="little"))
    print(packet[16:].hex())
import hashlib
from base64 import b64encode
import zstandard as zstd
 
 
def zstd_compress(data):
    return zstd.compress(data)
 
 
def xtime(a) -> int:
    a &= 0xFF
    return (((a << 1) ^ 0x8D) & 0xFF) if (a & 0x80) else ((a << 1) & 0xFF)
 
def gf_mul(a, b) -> int:
    a &= 0xFF
    b &= 0xFF
    res = 0
    for _ in range(8):
        if b & 1:
            res ^= a
        a = xtime(a)
        b >>= 1
    return res & 0xFF
 
# ---------------- S-box / inverse S-box (standard AES S-box) ----------------
 
AES_SBOX = bytes.fromhex(
    "637C699066329A0E6441CBA99FFAD5AA6524F777371D83EB981A2A7DBD2502EEE"
    "5E7455029C4ECA7CCF05C4D1396A2099EFF5AA1C76FE9150C1BC5975614A5B620"
    "D62111700D7F4E4652354BA4C9011E310F2F17FCDB7430DE481C950653D36718F"
    "D2D1F7A8D8775B426E071A3825807D4BADAA8B5D99CCFF960D81200798904C2B8"
    "3C614276DF6CEA495462E8B3F50BF1287ED2CD23F28E80F836E3D722DDF34A2E5"
    "510C0B15943AC683FBB6DAFCAC638B973AEDCBC9DC3D14CFEA63B92E42B5BFB2C"
    "F6C1B25D8FEF78915F9472ED4088B7443427E16A0586C8938A7B8451E63D990A3"
    "3BF39038C086B3E8519CEB08BABA0E247BE4F5E9B57AD6E81163AD0F4"
)
 
INV_SBOX = bytearray(256)
for i, v in enumerate(AES_SBOX):
    INV_SBOX[v] = i
INV_SBOX = bytes(INV_SBOX)
 
# ---------------- Rijndael-160 params ----------------
 
def rijndael_rounds(Nb, Nk) -> int:
    return max(Nb, Nk) + 6  # same as Rijndael
 
# ---------------- Key schedule (uses your xtime/rcon field) ----------------
 
def rot_word(w) -> int:
    return ((w << 8) & 0xFFFFFFFF) | ((w >> 24) & 0xFF)
 
def sub_word(w, sbox) -> int:
    return (
        (sbox[(w >> 24) & 0xFF] << 24) |
        (sbox[(w >> 16) & 0xFF] << 16) |
        (sbox[(w >>  8) & 0xFF] <<  8) |
        (sbox[(w >>  0) & 0xFF] <<  0)

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 6
支持
分享
最新回复 (2)
雪    币: 7792
活跃值: (3671)
能力值: (RANK:166 )
在线值:
发帖
回帖
粉丝
2
真的厉害,套了这么多层这个题确实难
2026-2-4 23:17
0
雪    币: 570
活跃值: (1286)
能力值: ( LV7,RANK:111 )
在线值:
发帖
回帖
粉丝
3
moshuiD 真的厉害,套了这么多层这个题确实难
其实这题被大佬们骂得很惨,套壳+rust做吐了
2026-2-5 09:27
0
游客
登录 | 注册 方可回帖
返回