首页
论坛
课程
招聘
雪    币: 5520
活跃值: 活跃值 (54)
能力值: ( LV15,RANK:1194 )
在线值:
发帖
回帖
粉丝

[原创]看雪.京东 2018CTF 第十二题 破解之道 Writeup

2018-7-11 23:09 2310

[原创]看雪.京东 2018CTF 第十二题 破解之道 Writeup

2018-7-11 23:09
2310
其实此CM本来还是比较简单的,不过由于64位程序很少看,程序中还有不少垃圾代码,看得眼花:(
下面从IDA F5的代码中列出主要验证部分,就能看清验证流程,从而得到找到SN的方法.
设输入的SN为snInput, snLen 为SN长度:

//串的HASH值计算函数
signed __int64 __fastcall getHash(char *a1)
{
  char v1; // dl
  signed __int64 result; // rax
  signed __int64 v3; // rax

  v1 = *a1;
  for ( result = 0xCBF29CE484222325i64; *a1; result = 0x100000001B3i64 * v3 )
  {
    ++a1;
    v3 = result ^ v1;
    v1 = *a1;
  }
  return result;
}

//关键代码调用
  if ( a1 == 2 )
  {
    strncpy_s(snInput, 260i64, *(const char **)(a2 + 8), 260i64); //复制输入的SN
    sub_140002E00(&retaddr); //调用验证函数
    result = 0i64;
  }

//以下是sub_140002E00验证函数的主要验证片段.
  snLen = -1i64;
  do
    ++snLen;
  while ( snInput[snLen] ); //计算输入SN的长度,设为snLen

//垃圾代码略

  if ( (snLen & 0xFFF) + (v533 << 12) == 30 )
  { //要到这儿才能通过,也就是SN的长度要为30个字符
    v8 = 0;
    v9 = 0;
    if ( snLen )
    {
      v10 = snInput;
      do
      {
        v188 = 0;
        v11 = *v10;
        LOBYTE(v188) = v11;
        v12 = (char *)&v188;
        v13 = 0xCBF29CE484222325i64;
        if ( v11 )
        {
          do
          {
            v13 = 0x100000001B3i64 * (v13 ^ v11);
            v11 = *++v12;
          }
          while ( *v12 );
        }
        v14 = v8 + 1;
        if ( v13 != 0xAF63B44C8601A894i64 )
          v14 = v8;
        v8 = v14;
        ++v9;
        ++v10;
      }
      while ( v9 < snLen );
      v1 = 32i64;
      if ( v14 >= 3 )
      { //这儿要大于3才行,不过只要别的条件满足,这儿也就满足了
.......
 }

//垃圾代码略

//这儿开始验证SN前9位,由于是一个字符一个字符的验证,所以由getHash函数很易暴出为:KXCTF2018
  *(_WORD *)sTmp = 0;
  sTmp[0] = snInput[0];
  if ( getHash(sTmp) == 0xAF64064C860233EAi64 )
  {
    sTmp[0] = snInput[1];
    if ( getHash(sTmp) == 0xAF64154C86024D67i64 )
    {
      sTmp[0] = snInput[2];
      if ( getHash(sTmp) == 0xAF63FE4C86022652i64 )
      {
        sTmp[0] = snInput[3];
        if ( getHash(sTmp) == 0xAF64094C86023903i64 )
        {
          sTmp[0] = snInput[4];
          if ( getHash(sTmp) == 0xAF63FB4C86022139i64 )
          {
            sTmp[0] = snInput[5];
            if ( getHash(sTmp) == 0xAF63AF4C8601A015i64 )
            {
              sTmp[0] = snInput[6];
              if ( getHash(sTmp) == 0xAF63AD4C86019CAFi64 )
              {
                sTmp[0] = snInput[7];
                if ( getHash(sTmp) == 0xAF63AC4C86019AFCi64 )
                {
                  sTmp[0] = snInput[8];
                  if ( getHash(sTmp) == 0xAF63B54C8601AA47i64 )
                  {
                    v42 = 0i64; //要到这儿才能通过,也就是snInput前9个字符计算出来的HASH值要依次等于以上的9个值
                    v37 = 0;
                    v43 = 0x50700i64;
                  }
                  else
                  {
                    v42 = 0x100000000i64;
                    v43 = 0x393B00i64;
                  }
                }
                else
                {
                  v42 = 0x100000000i64;
                  v43 = 0x393B00i64;
                }
              }
              else
              {
                v42 = 0x100000000i64;
                v43 = 3750656i64;
              }
            }
            else
            {
              v42 = 0x100000000i64;
              v43 = 3750656i64;
            }
          }
          else
          {
            v42 = 0x100000000i64;
            v43 = 3750656i64;
          }
        }
        else
        {
          v42 = 0x100000000i64;
          v43 = 3750656i64;
        }
      }
      else
      {
        v42 = 0x100000000i64;
        v43 = 0x393B00i64;
      }
    }
    else
    {
      v42 = 0x100000000i64;
      v43 = 0x393B00i64;
    }
  }
  else
  {
    v42 = 0x100000000i64;
    v43 = 0x393B00i64;
  }

//垃圾代码略

//对整个SN的HASH值校验,
  if ( getHash(snInput) == 0x4F8075587499C0FFi64 )
  { //SN的HASH值必须等于以上值
    LODWORD(v46) = 31;
    LODWORD(v47) = 0;
  }

//垃圾代码略


//由SN中的三个9分离出szDllName文件,szProcName
  memset(szDllName, 0, 260ui64);
  p1 = (char *)strstr(snInput, "9"); //分离第一部分,DLL文件名
  v81 = p1;
  szDllName[0] = p1[1]; //Dll文件名长为5个
  szDllName[1] = p1[2];
  szDllName[2] = p1[3];
  szDllName[3] = p1[4];
  szDllName[4] = p1[5];
  strcat_s(szDllName, 260ui64, ".DLL"); //加上".DLL"
  p2 = (char *)strstr(v81 + 1, "9"); //分离第二部分,PROC名
  *strstr(p2 + 1, "9") = 0; //第三个9写串结束'\0'
  memset(&szProcName, 0, 260ui64);
  strncpy_s(&szProcName, 260i64, p2 + 1, 260i64);


//垃圾代码略

//由szDllName,载入DLL模块
//hMod = LoadLibrary(szDllName)
  v108 = (HMODULE)((__int64 (__fastcall *)(char *))((char *)v95
                                                  + *(unsigned int *)((char *)&v95[*(unsigned __int16 *)((char *)v95 + 2 * v99 + (unsigned int)v96[9])]
                                                                    + (unsigned int)v96[7])))(szDllName);
  hMod = v108;


//垃圾代码略

//由分离出来的szProcName得到API地址proc
//下句相当于:proc = GetProcAddress(hMod, szProcName)
  proc = (__int64 (__fastcall *)(_QWORD, signed __int64))((__int64 (__fastcall *)(__int64, char *))((char *)v110
                                                                                                  + *(unsigned int *)((char *)&v110[*(unsigned __int16 *)((char *)v110 + 2 * v112 + (unsigned int)v111[9])] + (unsigned int)v111[7])))(
                                                           v108,
                                                           &szProcName);
  if ( proc ) //szDllName.szProcName API函数必须存在
  {
    v118 = -1i64;
    do
      ++v118;
    while ( snInput[v118] ); //v118是SN的长度,也就是30
    apiResult = proc(0i64, v118); //调用API函数,返回apiResult,API函数参数可以从无到二个参数(因为是fastcall不用平衡栈),参数1为0,参数2为30
  }


//垃圾代码略

  if ( apiResult >= 0 ) //API返回一定要>=0
  {
LABEL_118:
    *(_DWORD *)szSuccess = -dword_140038070; //解密成功提示
    *(_DWORD *)&szSuccess[4] = ((unsigned int)dword_140038074 >> 16) + ((unsigned __int16)dword_140038074 << 16);
    *(_DWORD *)&szSuccess[8] = ((unsigned int)dword_140038078 >> 16) + ((unsigned __int16)dword_140038078 << 16);
    *(_DWORD *)&szSuccess[12] = ((unsigned int)dword_14003807C >> 16) + ((unsigned __int16)dword_14003807C << 16);
    *(_DWORD *)&szSuccess[16] = dword_140038080 - 5;
    sub_140001A40((__int64)&v506, (__int64)szSuccess, 19i64); //显示成功提示
    v182 = (__int64 *)&v506;
    if ( v508 >= 0x10 )
      v182 = v506;
    v183 = sub_140001340(&qword_140039930, (__int64)v182, v507);
    sub_1400018B0(v183);
    sub_140001FD0(&v506);
    v230 = -293063854;
    v180 = -1773405844;
    v181 = -827325493;
  }

由以片段可以看出验证主要条件:
1.SN长度为30
2.SN前9个要为"KXCTF2018"
3.SN整个HASH为0x4F8075587499C0FFi64
4.SN从10位开始到结束有三个9,将这部分分成两段,前一段长5个字符,为DLL文件的名字,后一段为API名字
5.API调用参数为0-2个,如有参数,参数1=0,参数2=30
6.API调用返回值只能是>=0
7.DLL文件应该是WINDOWS系统自带的,而且每个版本都应该有,要不就会注册不成功.

由以上分析的验证条件,可以得到破解方案:
1.前9个字符用程序里的getHash函数暴出:
unsigned __int64 tabHash[] = {
0xAF64064C860233EA, 0xAF64154C86024D67, 0xAF63FE4C86022652, 0xAF64094C86023903
, 0xAF63FB4C86022139, 0xAF63AF4C8601A015, 0xAF63AD4C86019CAF, 0xAF63AC4C86019AFC, 0xAF63B54C8601AA47
};
CString sn;
char snSet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int i, j;
for (i = 0; i < 9; i++) {
for (j = 0; j < sizeof(snSet) - 1; j++) {
sn += snSet[j];
if (getHash(sn + i) == tabHash[i]) {
break;
}
}
}
        OutputDebugString(sn);

2.API函数参数和返回值先不考虑,相信HASH相同的没有吧?DLL由于考虑WINDOWS自带的,且名字长度为5,猜是NTDLL,当然如果不是就比较麻烦,要遍历WINDOWS\SYSTEM32下DLL文件,还要遍历每个DLL的导出函数名.
用PE的工具(如PEExplorer)得到NTDLL的所有导出函数名,再对所有的函数名加上NTDLL(这个可能是大写也可能是小写或大小写都有)和三个9跑HASH值,即"KXCTF20189NTDLL9函数名9",整个SN长30,当然,中间还可以加入干扰字符,但由于比赛规则所限暴力破解时间,所以作者才没有加?
LPCSTR tabApiName[]={
"RtlDispatchAPC",
"RtlActivateActivationContextUnsafeFast",
"RtlDeactivateActivationContextUnsafeFast",
"RtlInterlockedPushListSList",
"RtlUlongByteSwap",
"RtlUlonglongByteSwap",
"RtlUshortByteSwap",
"A_SHAFinal",
"A_SHAInit",
"A_SHAUpdate",
"AlpcAdjustCompletionListConcurrencyCount",
"AlpcFreeCompletionListMessage",
"AlpcGetCompletionListLastMessageInformation",
"AlpcGetCompletionListMessageAttributes",
"AlpcGetHeaderSize",
"AlpcGetMessageAttribute",
"AlpcGetMessageFromCompletionList",
"AlpcGetOutstandingCompletionListMessageCount",
"AlpcInitializeMessageAttribute",
"AlpcMaxAllowedMessageLength",
"AlpcRegisterCompletionList",
"AlpcRegisterCompletionListWorkerThread",
"AlpcRundownCompletionList",
"AlpcUnregisterCompletionList",
"AlpcUnregisterCompletionListWorkerThread",
"ApiSetQueryApiSetPresence",
"CsrAllocateCaptureBuffer",
"CsrAllocateMessagePointer",
"CsrCaptureMessageBuffer",
"CsrCaptureMessageMultiUnicodeStringsInPlace",
"CsrCaptureMessageString",
"CsrCaptureTimeout",
"CsrClientCallServer",
"CsrClientConnectToServer",
"CsrFreeCaptureBuffer",
"CsrGetProcessId",
"CsrIdentifyAlertableThread",
"CsrSetPriorityClass",
"CsrVerifyRegion",
"DbgBreakPoint",
"DbgPrint",
"DbgPrintEx",
"DbgPrintReturnControlC",
"DbgPrompt",
"DbgQueryDebugFilterState",
"DbgSetDebugFilterState",
"DbgUiConnectToDbg",
"DbgUiContinue",
"DbgUiConvertStateChangeStructure",
"DbgUiConvertStateChangeStructureEx",
"DbgUiDebugActiveProcess",
"DbgUiGetThreadDebugObject",

//太多了,略

"wcsrchr",
"wcsspn",
"wcsstr",
"wcstok_s",
"wcstol",
"wcstombs",
"wcstoul",
};
for(int i =0;i< sizeof(tabApiName)/sizeof(tabApiName[0]);i++){
LPCSTR apiName = tabApiName[i];
CString sn;
sn.Format("KXCTF20189NTDLL9%s9", apiName);
if(sn.GetLength() == 30){
if(getHash(sn) == 0x4F8075587499C0FFI64){
OutputDebugString(sn);
break;
}
}
}
最终SN为:
KXCTF20189NTDLL9DbgUiContinue9



HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

最新回复 (1)
雪    币: 1769
活跃值: 活跃值 (25)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
蒼V嵐 活跃值 3 2018-8-1 10:51
2
0
请教下是如何识别出strcat_s这些函数的,我用ucrtbase做了sig但还是没有识别出来,谢谢
游客
登录 | 注册 方可回帖
返回