首页
社区
课程
招聘
[原创]看雪CTF.TSRC 2018 团队赛 第二题 WriteUp
发表于: 2018-12-4 10:30 3830

[原创]看雪CTF.TSRC 2018 团队赛 第二题 WriteUp

2018-12-4 10:30
3830

一、定位main函数

    1、从start函数一路可到sub_4EA710函数
int sub_4EA710()
{
  sub_48E5B5();
  return sub_4EA730();
}
    对于main函数入口定位,可以自己写一个VS2015或2017的程序对比一下就可以很快定位main函数。通过对比上面函数可以重命名如下:
int sub_4EA710()
{
  sub_48E5B5();
  return sub_4EA730();
}
    对于main函数入口定位,可以自己写一个VS2015或2017的程序对比一下就可以很快定位main函数。通过对比上面函数可以重命名如下:
int __usercall sub_4EA710@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
  j___security_init_cookie();
  return _tmainCRTStartup(a1, a2, a3);
}
     2、_tmainCRTStartup函数,对部分函数进行了重命名如下:
int __usercall sub_4EA710@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
  j___security_init_cookie();
  return _tmainCRTStartup(a1, a2, a3);
}
     2、_tmainCRTStartup函数,对部分函数进行了重命名如下:
signed int __usercall _tmainCRTStartup@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
  int v4; // [esp+28h] [ebp-2Ch]
  int *v5; // [esp+30h] [ebp-24h]
  _DWORD *v6; // [esp+34h] [ebp-20h]
  char v7; // [esp+3Ah] [ebp-1Ah]
  char v8; // [esp+3Bh] [ebp-19h]

  if ( !j___scrt_initialize_crt(1) )
    j___scrt_fastfail(a1, a2, a3, 7);
  v8 = 0;
  v7 = j___scrt_acquire_startup_lock();
  if ( dword_5F357C == 1 )
  {
    j___scrt_fastfail(a1, a2, a3, 7);
  }
  else if ( dword_5F357C )
  {
    v8 = 1;
  }
  else
  {
    dword_5F357C = 1;
    if ( j_initterm_e((int)&dword_5B1710, (int)&unk_5B1B34) )
      return 255;
    initterm((int)&unk_5B1000, (int)&unk_5B160C);
    dword_5F357C = 2;
  }
  _scrt_release_startup_lock(v7);
  v6 = (_DWORD *)sub_48C9BD();
  if ( *v6 && j___scrt_is_nonwritable_in_current_image((int)v6) )
    ((void (__thiscall *)(_DWORD, _DWORD, signed int, _DWORD))*v6)(*v6, 0, 2, 0);
  v5 = (int *)sub_48DB6A();
  if ( *v5 && j___scrt_is_nonwritable_in_current_image((int)v5) )
    register_thread_local_exe_atexit_callback(*v5);
  v4 = main();
  if ( !j___scrt_is_managed_app() )
    j_exit_checkSn(v4);
  if ( !v8 )
    j_cexit();
  j___scrt_uninitialize_crt(1, 0);
  return v4;
}
signed int __usercall _tmainCRTStartup@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
  int v4; // [esp+28h] [ebp-2Ch]
  int *v5; // [esp+30h] [ebp-24h]
  _DWORD *v6; // [esp+34h] [ebp-20h]
  char v7; // [esp+3Ah] [ebp-1Ah]
  char v8; // [esp+3Bh] [ebp-19h]

  if ( !j___scrt_initialize_crt(1) )
    j___scrt_fastfail(a1, a2, a3, 7);
  v8 = 0;
  v7 = j___scrt_acquire_startup_lock();
  if ( dword_5F357C == 1 )
  {
    j___scrt_fastfail(a1, a2, a3, 7);
  }
  else if ( dword_5F357C )
  {
    v8 = 1;
  }
  else
  {
    dword_5F357C = 1;
    if ( j_initterm_e((int)&dword_5B1710, (int)&unk_5B1B34) )
      return 255;
    initterm((int)&unk_5B1000, (int)&unk_5B160C);
    dword_5F357C = 2;
  }
  _scrt_release_startup_lock(v7);
  v6 = (_DWORD *)sub_48C9BD();
  if ( *v6 && j___scrt_is_nonwritable_in_current_image((int)v6) )
    ((void (__thiscall *)(_DWORD, _DWORD, signed int, _DWORD))*v6)(*v6, 0, 2, 0);
  v5 = (int *)sub_48DB6A();
  if ( *v5 && j___scrt_is_nonwritable_in_current_image((int)v5) )
    register_thread_local_exe_atexit_callback(*v5);
  v4 = main();
  if ( !j___scrt_is_managed_app() )
    j_exit_checkSn(v4);
  if ( !v8 )
    j_cexit();
  j___scrt_uninitialize_crt(1, 0);
  return v4;
}
      从main(sub_48E029)函数可以一路到keyInputAndCheck1(sub_4A19B0)。而此函数使用F5反编译会失败,是由于在如下2处代码堆栈没有平衡引起的
.text:004A1A27                 push    0
.text:004A1A29                 call    sub_48C274

.text:004A1A69                 push    0
.text:004A1A6B                 call    sub_48C274

     先临时将 push 0 指令改为 nop指令,就可以F5了。
.text:004A1A27                 push    0
.text:004A1A29                 call    sub_48C274

.text:004A1A69                 push    0
.text:004A1A6B                 call    sub_48C274

     先临时将 push 0 指令改为 nop指令,就可以F5了。
二、 keyInputAndCheck1函数
int __cdecl keyInputAndCheck1(int argc, const char **argv, const char **envp)
{
  int v3; // xmm0_4
  int v4; // edx
  int v5; // ecx
  int v7; // [esp+0h] [ebp-D8h]
  int v8; // [esp+0h] [ebp-D8h]
  int v9; // [esp+4h] [ebp-D4h]
  int len; // [esp+D0h] [ebp-8h]

  sub_48D7B4((int)&unk_5F6007);
  sub_48CD46(v3, (int)&dword_5F31E0, (int)"Please Input:");
  GetInputSn(v3, "%s", g_inputSn, 30, v7, v9);
  len = strlen(g_inputSn);
  if ( len <= 30 && len >= 10 )
  {
    strncpy(g_inputSn2, 30, (int)g_inputSn);
    if ( *(_BYTE *)(g_inputSn2 + 7) != 'A' )
    {
      printf(v3, (int)&g_inputErrString);
      exitProcess(v8);
    }
    inputKeyEor0x1F(v3, (char *)g_inputSn2);
  }
  else
  {
    printf(v3, (int)&g_inputErrString);
    exitProcess(v8);
  }
  return sub_48D935(v5, v4, 1, 0, v3);
}
   从汇编代码上看就比较明显了。
int __cdecl keyInputAndCheck1(int argc, const char **argv, const char **envp)
{
  int v3; // xmm0_4
  int v4; // edx
  int v5; // ecx
  int v7; // [esp+0h] [ebp-D8h]
  int v8; // [esp+0h] [ebp-D8h]
  int v9; // [esp+4h] [ebp-D4h]
  int len; // [esp+D0h] [ebp-8h]

  sub_48D7B4((int)&unk_5F6007);
  sub_48CD46(v3, (int)&dword_5F31E0, (int)"Please Input:");
  GetInputSn(v3, "%s", g_inputSn, 30, v7, v9);
  len = strlen(g_inputSn);
  if ( len <= 30 && len >= 10 )
  {
    strncpy(g_inputSn2, 30, (int)g_inputSn);
    if ( *(_BYTE *)(g_inputSn2 + 7) != 'A' )
    {
      printf(v3, (int)&g_inputErrString);
      exitProcess(v8);
    }
    inputKeyEor0x1F(v3, (char *)g_inputSn2);
  }
  else
  {
    printf(v3, (int)&g_inputErrString);
    exitProcess(v8);
  }
  return sub_48D935(v5, v4, 1, 0, v3);
}
   从汇编代码上看就比较明显了。
   1、调用GetInputSn函数获取输入的sn(g_inputSn),如果SN长度不足30字节,剩余用0XFE填充。
   2、sn长度在10-30之间,如果不是则输出"输入错误"退出。
   3、调用strncpy将g_inputSn拷贝到g_inputSn2。
   4、 判断g_inputSn2[7]是否等于字符'A',如果不等,则输出"输入错误"退出。
   5、调用SNEor0x1F函数。
int __usercall SNEor0x1F_0@<eax>(int a1@<xmm0>, char *inputKey)
{
  int v2; // edx
  int v3; // ecx
  unsigned int i; // [esp+D0h] [ebp-8h]

  sub_48D7B4((int)&unk_5F6007);
  inputKey[7] = 0x23;
  for ( i = 0; i < strlen(inputKey); ++i )
    inputKey[i] ^= 0x1Fu;
  return sub_48D935(v3, v2, 1, (int)inputKey, a1);
}
     SNEor0x1F函数将 g_inputSn2[7] = 0x23,然后按字节亦或0x1F。
int __usercall SNEor0x1F_0@<eax>(int a1@<xmm0>, char *inputKey)
{
  int v2; // edx
  int v3; // ecx
  unsigned int i; // [esp+D0h] [ebp-8h]

  sub_48D7B4((int)&unk_5F6007);
  inputKey[7] = 0x23;
  for ( i = 0; i < strlen(inputKey); ++i )
    inputKey[i] ^= 0x1Fu;
  return sub_48D935(v3, v2, 1, (int)inputKey, a1);
}
     SNEor0x1F函数将 g_inputSn2[7] = 0x23,然后按字节亦或0x1F。
三、查看对 g_inputSn2引用
        我们发现程序没有对 g_inputSn2 做更多的检查。可以看下还有谁对 g_inputSn2进行了访问,如下:
.data:005F3088 00 00 00 00                         g_inputSn2      dd 0                    ; DATA XREF: sub_495810+3E↑w
.data:005F3088                                                                             ; sub_49DC80:loc_49DCEC↑r
.data:005F3088                                                                             ; keyInputAndCheck1+87↑r
.data:005F3088                                                                             ; keyInputAndCheck1+9D↑r
.data:005F3088                                                                             ; keyInputAndCheck1:loc_4A1A70↑r
       可以看到函数sub_49DC80与sub_495810函数中有引用:
int __userpurge sub_49DC80@<eax>(int a1@<xmm0>, char *keyString)
{
  int v2; // edx
  int v3; // ecx
  unsigned int i; // [esp+E8h] [ebp-14h]

  sub_48D7B4((int)&unk_5F6007);
  if ( keyString )
  {
    for ( i = 0; i < strlen(keyString); ++i )
      keyString[i] ^= 0x1Cu;
    if ( !strcmp((int)keyString, g_inputSn2) )
    {
      outPut(a1, (int)&dword_5F31E0, 'o');
      outPut(a1, (int)&dword_5F31E0, 'k');
    }
  }
  return sub_48D935(v3, v2, 1, 0, a1);
}
     在 sub_49DC80设断点运行,程序可断下, 其中keyString参数为"invalid argument"。而程序逻辑就比较明显了:
     1、对 "invalid argument"进行按字节亦或0x1C,得到 “urj}pux<}n{iqyrh”
     2、调用strcmp与g_inputSn2比较,相等,则输出"ok"。
四、获得flag
     对字串“urj}pux<}n{iqyrh”按字节亦或0x1F,再将第7字串替换为“A”。可得flag。
     flag:jmubojgAbqdvnfmw
     虽然得到flag,但是执行到sub_ 49DC80路径并不可知,下面开始分析整个程序的执行流程。
五、通过调试可知校验函数 sub_49DC80的执行路径
     1、start->4EAA600->4EA710->_tmainCRTStartup(4EA730)->48E029->54A840->54A420->549F90->549EF0->54A1B0
      这里要注意函数: 54A840,其调用 54A420 函数
void __cdecl 54A840(UINT a1)
{
  sub_54A420(a1, 0, 0);
}

void __cdecl sub_54A420(UINT uExitCode, char checkFlag, int exitProcessFlag)
{
  _DWORD *v3; // ST08_4
  char value_2; // al
  char v5; // [esp+0h] [ebp-10h]
  char v6; // [esp+Fh] [ebp-1h]

  if ( !exitProcessFlag && checkPeFile() )
    sub_54A670(uExitCode);
  v6 = 0;
  v3 = sub_54A0D0(&v5, (int)&checkFlag, (int)&exitProcessFlag, (int)&v6);
  value_2 = j_return2();
  sub_549F90(value_2, (_DWORD **)v3);
  if ( v6 )
    j___scrt_uninitialize_crt(1, 1);
  if ( !exitProcessFlag )
    ExitProcess_0(uExitCode);
}
    函数sub_54A420的第二个参数 checkFlag  是否对 g_inputSn2进行进一步的校验,以及采用何种校验方式
    checkFlag  = 0 ---------->采用 全局变量5F4078中保护的校验函数
    checkFlag  = 1 ---------->采用 全局变量5F4088中保护的校验函数
    checkFlag  > 1 ---------->不进行校验,程序退出。

    当在函数   keyInputAndCheck1(4A19B0)中发现输入长度不符合要求时,其会调用如下:
    48C274->54A7B0->54A420,而函数 54A7B0如下:
void __cdecl sub_54A7B0(UINT a1)
{
  sub_54A420(a1, 2, 0);
}
    可见输入的checkFlag为2。实际上就是直接退出了。具体在 54A1B0 函数中可以看清楚
2、 54A1B0 函数
DWORD *__thiscall sub_54A1B0(_DWORD **this)
{
  _DWORD *result; // eax
  void (__thiscall *v2)(_DWORD, _DWORD, _DWORD, _DWORD); // ecx
  _DWORD **v3; // [esp+18h] [ebp-24h]

  v3 = this;
  result = (_DWORD *)(unsigned __int8)byte_5F3AE0;
  if ( !byte_5F3AE0 )
  {
    _InterlockedExchange((volatile signed __int32 *)&unk_5F3AD8, 1);
    if ( **this )
    {
      if ( **this == 1 )                        // chcekflag = 1 时
        sub_48B57C((unsigned int)&stru_5F4088);
    }
    else                                        // checkflag = 0时
    {
      nop((int)*this);
      if ( dword_5F3ADC != sub_48CFF8() )
      {
        v2 = (void (__thiscall *)(_DWORD, _DWORD, _DWORD, _DWORD))sub_48ACD5(dword_5F3ADC);
        v2(v2, 0, 0, 0);
      }
      sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    }
    if ( !**v3 )
      initterm((int)&unk_5B1C38, (int)&unk_5B1F4C);
    initterm((int)&unk_5B2050, (int)&unk_5B2154);
    result = v3[1];
    if ( !*result )
    {
      byte_5F3AE0 = 1;
      *(_BYTE *)v3[2] = 1;
    }
  }
  return result;
}
    1) 当checkFlag = 0 时:
       sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    2 ) 当 checkFlag = 1时:
      sub_48B57C((unsigned int)&stru_5F4088);
    3 ) 其他值函数直接退出。
    对于 checkFlag = 1 可能是作者另外的一种check函数,我们可以不管。无论 checkFlag 为0还是为1,区别只是调用 sub_48B57C参数不同。
    我们继续分析  sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    对于 g_funPtr :5F4078实际上是一个结构体,结构体的成员怎样分析出来我们后面在说。
    
3、checkFunInfo结构及加解密函数
typedef  struct checkFunInfo
{
    int *startAddr;
    int *endAddr;
    int *maxAddr;
}
     其中startAddr指向的是一个malloc的buf,这个buf中存储的是要执行的函数指针数组,而 48B57C函数其实就是执行 g_funPtr结构中包含的函数列表。但是这个结构的数据包括全局buf的起始地址,结束地址以及内容函数指针都是经过加密的,加密算法与本题题意吻合,实际上就是一个移位的算法:
     如果加密整数 data,加解密算法如下:
      __security_cookie ^(data ror (0x20 - __security_cookie % 0x20u )) --->加密算法
     __security_cookie ^(data ror ( __security_cookie % 0x20u ))           ---->解密算法
     实际上加密就是一个数循环移位 0x20-X, 解密就是循环移位X  这样的话一个数经过加密和解密后循环移位了0x20次,即为其本身。
4、 sub_48B57C函数
       该函数经过一些列调用最终会调用到 563A00
       48B57C->563D20->563500->563410->563A00
5、 563A00函数
signed int __thiscall sub_563A00(struct checkFunInfo **this)
{
  int v2; // ecx
  void (__thiscall *v3)(_DWORD); // ST0C_4
  int v4; // ecx
  int v5; // eax
  int endAddr1; // [esp+8h] [ebp-3Ch]
  int startAddr1; // [esp+Ch] [ebp-38h]
  int ___security_cookie; // [esp+14h] [ebp-30h]
  int *endAddr; // [esp+1Ch] [ebp-28h]
  unsigned int startAddr_1; // [esp+20h] [ebp-24h]
  int ***v11; // [esp+28h] [ebp-1Ch]
  unsigned int startAddr; // [esp+2Ch] [ebp-18h]
  int *curAddr; // [esp+30h] [ebp-14h]

  v11 = (int ***)this;
  if ( !(*this)->startAddr )
    return -1;
  startAddr = decodeData(*(*this)->startAddr);
  curAddr = (int *)decodeData((**v11)[1]);
  if ( !startAddr || startAddr == -1 )
    return 0;
  nop(v2);
  ___security_cookie = j___security_cookie_get_0();
  startAddr_1 = startAddr;
  endAddr = curAddr;
  while ( 1 )
  {
    do
      --curAddr;
    while ( (unsigned int)curAddr >= startAddr && *curAddr == ___security_cookie );
    if ( (unsigned int)curAddr < startAddr )
      break;
    v3 = (void (__thiscall *)(_DWORD))decodeData1(*curAddr);
    *curAddr = ___security_cookie;
    v3(v3);                                     // 执行对应的函数指针数组中的函数
    startAddr1 = decodeData(***v11);
    endAddr1 = decodeData((**v11)[1]);
    if ( startAddr1 != startAddr_1 || (int *)endAddr1 != endAddr )
    {
      startAddr_1 = startAddr1;
      startAddr = startAddr1;
      endAddr = (int *)endAddr1;
      curAddr = (int *)endAddr1;
    }
  }
  sub_48C567();
  if ( startAddr != -1 )
    sub_48F0C8(startAddr, 2);
  nop(v4);
  v5 = j___security_cookie_get();
  ***v11 = v5;
  (**v11)[1] = v5;
  (**v11)[2] = v5;
  return 0;
}
      在 g_funPtr结构对应的函数指针数组中包含函数5AFCB0,而函数经过一些列调用会最终调用 sub_49DC80执行最终的校验。其调用关系如下:
      5AFCB0->48C28D->49CEB0->48DACA->49DC80,在函数sub_49CEB0中存在内部key 'invalid argument'
int __usercall sub_49CEB0@<eax>(int a1@<xmm0>)
{
  int v1; // eax
  int v2; // edx
  int v4; // [esp+0h] [ebp-E8h]

  sub_48D7B4((int)&unk_5F6007);
  v1 = sub_48DACA(a1, (int)aInvalidArgumen_1);  // 'invalid argument'
  return sub_48D935(v4, v2, 1, v1, a1);
}
   6、 sub_49DC80校验函数调用路径
     从上面分析可知49DC80调用流程如下:
    start->4EAA600->4EA710->_tmainCRTStartup(4EA730)->48E029->54A840->54A420->549F90->549EF0->54A1B0->
    48B57C->563D20->563500->563410->563A00->5AFCB0->48C28D->49CEB0->48DACA->49DC80
   那么这里面的关键就是 g_funPtr结构的赋值在哪里实现的呢。下面就分析 g_funPtr结构的赋值。

六、 g_funPtr结构赋值
1、 g_funPtr结构初始化流程
      _tmainCRTStartup(4EA730)->48BD42->4E9DE0->48E696->564150
char sub_564150()
{
  return sub_48B7D9((int)&off_5D2378, (int)&unk_5D23F8);
}
    sub_48B7D9函数就是执行off_5D2378与off_5D23F8之间的函数
 10 3E 56 00                         off_5D2378      dd offset sub_563E10    ; DATA XREF: sub_564150+A↑o
.rdata:005D2378                                                                             ; sub_5641B0+A↑o
.rdata:005D237C 00 00 00 00                                         align 10h
.rdata:005D2380 C0 3E 56 00                                         dd offset sub_563EC0
.rdata:005D2384 00 00 00 00                                         align 8
.rdata:005D2388 FA A5 48 00                                         dd offset sub_48A5FA
.rdata:005D238C C5 DF 48 00                                         dd offset sub_48DFC5
.rdata:005D2390 A0 3E 56 00                                         dd offset sub_563EA0
.rdata:005D2394 B0 3E 56 00                                         dd offset sub_563EB0
.rdata:005D2398 01 C7 48 00                                         dd offset sub_48C701
.rdata:005D239C 6C C0 48 00                                         dd offset sub_48C06C
.rdata:005D23A0 1C F7 48 00                                         dd offset sub_48F71C
.rdata:005D23A4 6F BD 48 00                                         dd offset sub_48BD6F
.rdata:005D23A8 00 00 00 00                                         dd 0
.rdata:005D23AC 40 3F 56 00                                         dd offset sub_563F40
.rdata:005D23B0 0E FB 48 00                                         dd offset sub_48FB0E
.rdata:005D23B4 7B F7 48 00                                         dd offset sub_48F77B
.rdata:005D23B8 BB B7 48 00                                         dd offset sub_48B7BB
.rdata:005D23BC 14 BE 48 00                                         dd offset sub_48BE14
.rdata:005D23C0 56 E0 48 00                                         dd offset sub_48E056
.rdata:005D23C4 CB C0 48 00                                         dd offset sub_48C0CB
.rdata:005D23C8 14 C3 48 00                                         dd offset sub_48C314
.rdata:005D23CC 00 00 00 00                                         dd 0
.rdata:005D23D0 00 00 00 00                                         dd 0
.rdata:005D23D4 20 40 56 00                                         dd offset sub_564020
.rdata:005D23D8 00 00 00 00                                         dd 0
.rdata:005D23DC 90 3F 56 00                                         dd offset sub_563F90
.rdata:005D23E0 00 00 00 00                                         dd 0
.rdata:005D23E4 60 3F 56 00                                         dd offset sub_563F60
.rdata:005D23E8 70 3E 56 00                                         dd offset sub_563E70
.rdata:005D23EC 80 3E 56 00                                         dd offset sub_563E80
.rdata:005D23F0 30 3E 56 00                                         dd offset allCheckFunStruct_ini
.rdata:005D23F4 60 3E 56 00                                         dd offset sub_563E60
     上述函数指针数组中的最有一个函数563E30(allCheckFunStruct_ini)为初始化 g_funPtr
char allCheckFunStruct_ini()
{
  checkFunStruct_ini_0(&g_funPtr);
  checkFunStruct_ini_0(&stru_5F4088);
  return 1;
}
    其调用关系为:
    9A3E30->8CB9CD->9A3D50(checkFunStruct_ini)
int __cdecl checkFunStruct_ini(struct checkFunInfo *a1)
{
  int *__security_cookie; // eax

  if ( !a1 )
    return -1;
  if ( a1->startAddr == a1->maxAddr )
  {
    nop((int)a1);
    __security_cookie = (int *)j___security_cookie_get();
    a1->startAddr = __security_cookie;
    a1->endAddr = __security_cookie;
    a1->maxAddr = __security_cookie;
  }
  return 0;
}
   实际上就是将 0 赋给 g_funPtr,经加密后变为__security_cookie
   因此 g_funPtr 结构初始化调用流程为:
    _tmainCRTStartup(4EA730)->48BD42->4E9DE0->48E696->564150-> 48B7D9-> 9A3E30->8CB9CD->9A3D50(checkFunStruct_ini)
2、 g_funPtr结构赋值之函数指针BUF申请
   在函数_tmainCRTStartup中,会存在如下2个调用:
    if ( j_initterm_e((int)&dword_5B1710, (int)&unk_5B1B34) )
      return 255;
    initterm((int)&unk_5B1000, (int)&unk_5B160C);

    j_initterm_e 与initterm实际上就是执行初始化函数
   _tmainCRTStartup(4EA730) -8CA979(j_initterm_e)->9A4920->92A600->48D854->4EA250->48DAE8->4EA160->48A361->563D00->
     48E80D->563DD0->5634C0->563360->563710
signed int __thiscall sub_563710(struct bufInfo *this)
{
  int *mallocSaveCheckSnBuf; // eax
  int v3; // eax
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // eax
  int __security_cookie; // [esp+0h] [ebp-40h]
  char v9; // [esp+4h] [ebp-3Ch]
  _DWORD *v10; // [esp+8h] [ebp-38h]
  char v11; // [esp+Ch] [ebp-34h]
  _DWORD *v12; // [esp+10h] [ebp-30h]
  unsigned int funCnt2; // [esp+14h] [ebp-2Ch]
  unsigned int funCnt1; // [esp+18h] [ebp-28h]
  int *i; // [esp+1Ch] [ebp-24h]
  int *maxAddr; // [esp+20h] [ebp-20h]
  int startAddr; // [esp+24h] [ebp-1Ch]
  unsigned int funCnt; // [esp+28h] [ebp-18h]
  int *endAddr; // [esp+2Ch] [ebp-14h]
  int mallocSaveCheckSnBuf1; // [esp+30h] [ebp-10h]
  unsigned int mallocCnt; // [esp+34h] [ebp-Ch]
  struct bufInfo *v22; // [esp+38h] [ebp-8h]
  char v23; // [esp+3Fh] [ebp-1h]

  v22 = this;
  if ( !*this->strCheckFunInfo )
    return -1;
  startAddr = decodeData((int)(*v22->strCheckFunInfo)->startAddr);
  endAddr = (int *)decodeData((int)(*v22->strCheckFunInfo)->endAddr);
  maxAddr = (int *)decodeData((int)(*v22->strCheckFunInfo)->maxAddr);
  if ( endAddr == maxAddr )
  {
    funCnt = ((signed int)maxAddr - startAddr) >> 2;
    if ( funCnt <= 0x200 )
      funCnt1 = funCnt;
    else
      funCnt1 = 512;
    funCnt2 = funCnt1;
    mallocCnt = funCnt1 + funCnt;
    if ( !(funCnt1 + funCnt) )
      mallocCnt = 32;
    mallocSaveCheckSnBuf1 = 0;
    if ( mallocCnt >= funCnt )
    {
      mallocSaveCheckSnBuf = (int *)sub_48B18F( // malloc
                                      startAddr,
                                      mallocCnt,
                                      4,
                                      2,
                                      (int)"minkernel\\crts\\ucrt\\src\\appcrt\\startup\\onexit.cpp",
                                      'p');
      v12 = sub_48AFC8(&v11, (int)mallocSaveCheckSnBuf);
      mallocSaveCheckSnBuf1 = sub_48AA1E((int)v12);
      sub_48C4BD((int)&v11);
    }
    if ( !mallocSaveCheckSnBuf1 )
    {
      mallocCnt = funCnt + 4;
      v3 = sub_48B18F(startAddr, funCnt + 4, 4, 2, (int)"minkernel\\crts\\ucrt\\src\\appcrt\\startup\\onexit.cpp", 'w');
      v10 = sub_48AFC8(&v9, v3);
      mallocSaveCheckSnBuf1 = sub_48AA1E((int)v10);
      sub_48C4BD((int)&v9);
    }
    if ( !mallocSaveCheckSnBuf1 )
      return -1;
    startAddr = mallocSaveCheckSnBuf1;
    endAddr = (int *)(mallocSaveCheckSnBuf1 + 4 * funCnt);
    maxAddr = (int *)(mallocSaveCheckSnBuf1 + 4 * mallocCnt);
    v23 = nop(mallocSaveCheckSnBuf1 + 4 * mallocCnt);
    __security_cookie = j___security_cookie_get_0();
    for ( i = endAddr; i != maxAddr; ++i )
      *i = __security_cookie;
  }
  v4 = encodeData((int)*v22->checkFunPtr);
  *endAddr = v4;
  ++endAddr;
  v5 = j_EncodeData1(startAddr);
  (*v22->strCheckFunInfo)->startAddr = (int *)v5;
  v6 = j_EncodeData1((int)endAddr);
  (*v22->strCheckFunInfo)->endAddr = (int *)v6;
  v7 = j_EncodeData1((int)maxAddr);
  (*v22->strCheckFunInfo)->maxAddr = (int *)v7;
  return 0;
}
    函数是将一个函数插入到函数指针列表中,如果没有申请函数指针列表空间则先申请,首先申请的大小是32*4 ,申请完后就将相应的的函数指针加密存储。
   其中bufInfo结构如下:
  
typedef struct bufInfo
{
checkFunInfo **pcheckFunInfo;
int ** checkFunPtr;
}
   其中checkFunPtr 为加入到pCheckFunInfo的函数指针。

3、将  5AFCB0函数写入到 g_funPtr中。
   在函数中_tmainCRTStartup存在如下调用
   initterm((int)&unk_5B1000, (int)&unk_5B160C); 就是执行 5B1000与 5B160C之间的函数。而在 5B1000与5B160C之间存在如下:
.rdata:005B14F8 10 58 49 00                                         dd offset sub_495810
.rdata:005B14FC B0 57 49 00                                         dd offset sub_4957B0
  而函数sub_4957B0如下:
int __usercall sub_4957B0@<eax>(int a1@<xmm0>)
{
  int v1; // eax
  int v2; // edx
  int v3; // ecx

  sub_48D7B4((int)&unk_5F6007);
  v1 = sub_48D854((int)sub_5AFCB0);             // 将check函数sub_5AFCB0插入到函数指针数组中
  return sub_48D935(v3, v2, 1, v1, a1);
}
其调用函数48D854将check函数sub_5AFCB0插入到函数指针数组中。
七、总结
  1、初始化 g_funPtr;
  2、将真正的校验函数sub_4957B0加密后插入到 g_funPtr结构中;
  3、获取用户输入;
  4、判断输入长度是否为10与30之间;
  4、如果不是则调用sub_54A420函数,并将其参数checkflag设置为2,使其不执行校验函数,进程直接结束;
  5、如果输入的第7个字符不等于字符''A , 则调用sub_54A420函数,并将其参数checkflag设置为2,使其不执行校验函数,进程直接结束;
  6、将输入的第7个字符设置为0x23;
  7、调用 调用sub_54A420函数,并将其参数checkflag设置为0,经过一系列调用,最终会调用 g_funPtr中设置的函数 sub_5AFCB0
  8、函数 sub_5AFCB0经过一系列调用最终会调用校验函数 sub_49DC80,执行校验;
  9、此时就回到我们开头分析的位置了。


        我们发现程序没有对 g_inputSn2 做更多的检查。可以看下还有谁对 g_inputSn2进行了访问,如下:
.data:005F3088 00 00 00 00                         g_inputSn2      dd 0                    ; DATA XREF: sub_495810+3E↑w
.data:005F3088                                                                             ; sub_49DC80:loc_49DCEC↑r
.data:005F3088                                                                             ; keyInputAndCheck1+87↑r
.data:005F3088                                                                             ; keyInputAndCheck1+9D↑r
.data:005F3088                                                                             ; keyInputAndCheck1:loc_4A1A70↑r
       可以看到函数sub_49DC80与sub_495810函数中有引用:
int __userpurge sub_49DC80@<eax>(int a1@<xmm0>, char *keyString)
{
  int v2; // edx
  int v3; // ecx
  unsigned int i; // [esp+E8h] [ebp-14h]

  sub_48D7B4((int)&unk_5F6007);
  if ( keyString )
  {
    for ( i = 0; i < strlen(keyString); ++i )
      keyString[i] ^= 0x1Cu;
    if ( !strcmp((int)keyString, g_inputSn2) )
    {
      outPut(a1, (int)&dword_5F31E0, 'o');
      outPut(a1, (int)&dword_5F31E0, 'k');
    }
  }
  return sub_48D935(v3, v2, 1, 0, a1);
}
     在 sub_49DC80设断点运行,程序可断下, 其中keyString参数为"invalid argument"。而程序逻辑就比较明显了:
     1、对 "invalid argument"进行按字节亦或0x1C,得到 “urj}pux<}n{iqyrh”
     2、调用strcmp与g_inputSn2比较,相等,则输出"ok"。
四、获得flag
     对字串“urj}pux<}n{iqyrh”按字节亦或0x1F,再将第7字串替换为“A”。可得flag。
     flag:jmubojgAbqdvnfmw
.data:005F3088 00 00 00 00                         g_inputSn2      dd 0                    ; DATA XREF: sub_495810+3E↑w
.data:005F3088                                                                             ; sub_49DC80:loc_49DCEC↑r
.data:005F3088                                                                             ; keyInputAndCheck1+87↑r
.data:005F3088                                                                             ; keyInputAndCheck1+9D↑r
.data:005F3088                                                                             ; keyInputAndCheck1:loc_4A1A70↑r
       可以看到函数sub_49DC80与sub_495810函数中有引用:
int __userpurge sub_49DC80@<eax>(int a1@<xmm0>, char *keyString)
{
  int v2; // edx
  int v3; // ecx
  unsigned int i; // [esp+E8h] [ebp-14h]

  sub_48D7B4((int)&unk_5F6007);
  if ( keyString )
  {
    for ( i = 0; i < strlen(keyString); ++i )
      keyString[i] ^= 0x1Cu;
    if ( !strcmp((int)keyString, g_inputSn2) )
    {
      outPut(a1, (int)&dword_5F31E0, 'o');
      outPut(a1, (int)&dword_5F31E0, 'k');
    }
  }
  return sub_48D935(v3, v2, 1, 0, a1);
}
     在 sub_49DC80设断点运行,程序可断下, 其中keyString参数为"invalid argument"。而程序逻辑就比较明显了:
     1、对 "invalid argument"进行按字节亦或0x1C,得到 “urj}pux<}n{iqyrh”
     2、调用strcmp与g_inputSn2比较,相等,则输出"ok"。
四、获得flag
     对字串“urj}pux<}n{iqyrh”按字节亦或0x1F,再将第7字串替换为“A”。可得flag。
int __userpurge sub_49DC80@<eax>(int a1@<xmm0>, char *keyString)
{
  int v2; // edx
  int v3; // ecx
  unsigned int i; // [esp+E8h] [ebp-14h]

  sub_48D7B4((int)&unk_5F6007);
  if ( keyString )
  {
    for ( i = 0; i < strlen(keyString); ++i )
      keyString[i] ^= 0x1Cu;
    if ( !strcmp((int)keyString, g_inputSn2) )
    {
      outPut(a1, (int)&dword_5F31E0, 'o');
      outPut(a1, (int)&dword_5F31E0, 'k');
    }
  }
  return sub_48D935(v3, v2, 1, 0, a1);
}
     在 sub_49DC80设断点运行,程序可断下, 其中keyString参数为"invalid argument"。而程序逻辑就比较明显了:
     1、对 "invalid argument"进行按字节亦或0x1C,得到 “urj}pux<}n{iqyrh”
     2、调用strcmp与g_inputSn2比较,相等,则输出"ok"。
四、获得flag
     对字串“urj}pux<}n{iqyrh”按字节亦或0x1F,再将第7字串替换为“A”。可得flag。
     flag:jmubojgAbqdvnfmw
     虽然得到flag,但是执行到sub_ 49DC80路径并不可知,下面开始分析整个程序的执行流程。
五、通过调试可知校验函数 sub_49DC80的执行路径
     1、start->4EAA600->4EA710->_tmainCRTStartup(4EA730)->48E029->54A840->54A420->549F90->549EF0->54A1B0
      这里要注意函数: 54A840,其调用 54A420 函数
void __cdecl 54A840(UINT a1)
{
  sub_54A420(a1, 0, 0);
}

void __cdecl sub_54A420(UINT uExitCode, char checkFlag, int exitProcessFlag)
{
  _DWORD *v3; // ST08_4
  char value_2; // al
  char v5; // [esp+0h] [ebp-10h]
  char v6; // [esp+Fh] [ebp-1h]

  if ( !exitProcessFlag && checkPeFile() )
    sub_54A670(uExitCode);
  v6 = 0;
  v3 = sub_54A0D0(&v5, (int)&checkFlag, (int)&exitProcessFlag, (int)&v6);
  value_2 = j_return2();
  sub_549F90(value_2, (_DWORD **)v3);
  if ( v6 )
    j___scrt_uninitialize_crt(1, 1);
  if ( !exitProcessFlag )
    ExitProcess_0(uExitCode);
}
    函数sub_54A420的第二个参数 checkFlag  是否对 g_inputSn2进行进一步的校验,以及采用何种校验方式
void __cdecl 54A840(UINT a1)
{
  sub_54A420(a1, 0, 0);
}

void __cdecl sub_54A420(UINT uExitCode, char checkFlag, int exitProcessFlag)
{
  _DWORD *v3; // ST08_4
  char value_2; // al
  char v5; // [esp+0h] [ebp-10h]
  char v6; // [esp+Fh] [ebp-1h]

  if ( !exitProcessFlag && checkPeFile() )
    sub_54A670(uExitCode);
  v6 = 0;
  v3 = sub_54A0D0(&v5, (int)&checkFlag, (int)&exitProcessFlag, (int)&v6);
  value_2 = j_return2();
  sub_549F90(value_2, (_DWORD **)v3);
  if ( v6 )
    j___scrt_uninitialize_crt(1, 1);
  if ( !exitProcessFlag )
    ExitProcess_0(uExitCode);
}
    函数sub_54A420的第二个参数 checkFlag  是否对 g_inputSn2进行进一步的校验,以及采用何种校验方式
    checkFlag  = 0 ---------->采用 全局变量5F4078中保护的校验函数
    checkFlag  = 1 ---------->采用 全局变量5F4088中保护的校验函数
    checkFlag  > 1 ---------->不进行校验,程序退出。

    当在函数   keyInputAndCheck1(4A19B0)中发现输入长度不符合要求时,其会调用如下:
    48C274->54A7B0->54A420,而函数 54A7B0如下:
void __cdecl sub_54A7B0(UINT a1)
{
  sub_54A420(a1, 2, 0);
}
    可见输入的checkFlag为2。实际上就是直接退出了。具体在 54A1B0 函数中可以看清楚
void __cdecl sub_54A7B0(UINT a1)
{
  sub_54A420(a1, 2, 0);
}
    可见输入的checkFlag为2。实际上就是直接退出了。具体在 54A1B0 函数中可以看清楚
2、 54A1B0 函数
DWORD *__thiscall sub_54A1B0(_DWORD **this)
{
  _DWORD *result; // eax
  void (__thiscall *v2)(_DWORD, _DWORD, _DWORD, _DWORD); // ecx
  _DWORD **v3; // [esp+18h] [ebp-24h]

  v3 = this;
  result = (_DWORD *)(unsigned __int8)byte_5F3AE0;
  if ( !byte_5F3AE0 )
  {
    _InterlockedExchange((volatile signed __int32 *)&unk_5F3AD8, 1);
    if ( **this )
    {
      if ( **this == 1 )                        // chcekflag = 1 时
        sub_48B57C((unsigned int)&stru_5F4088);
    }
    else                                        // checkflag = 0时
    {
      nop((int)*this);
      if ( dword_5F3ADC != sub_48CFF8() )
      {
        v2 = (void (__thiscall *)(_DWORD, _DWORD, _DWORD, _DWORD))sub_48ACD5(dword_5F3ADC);
        v2(v2, 0, 0, 0);
      }
      sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    }
    if ( !**v3 )
      initterm((int)&unk_5B1C38, (int)&unk_5B1F4C);
    initterm((int)&unk_5B2050, (int)&unk_5B2154);
    result = v3[1];
    if ( !*result )
    {
      byte_5F3AE0 = 1;
      *(_BYTE *)v3[2] = 1;
    }
  }
  return result;
}
    1) 当checkFlag = 0 时:
DWORD *__thiscall sub_54A1B0(_DWORD **this)
{
  _DWORD *result; // eax
  void (__thiscall *v2)(_DWORD, _DWORD, _DWORD, _DWORD); // ecx
  _DWORD **v3; // [esp+18h] [ebp-24h]

  v3 = this;
  result = (_DWORD *)(unsigned __int8)byte_5F3AE0;
  if ( !byte_5F3AE0 )
  {
    _InterlockedExchange((volatile signed __int32 *)&unk_5F3AD8, 1);
    if ( **this )
    {
      if ( **this == 1 )                        // chcekflag = 1 时
        sub_48B57C((unsigned int)&stru_5F4088);
    }
    else                                        // checkflag = 0时
    {
      nop((int)*this);
      if ( dword_5F3ADC != sub_48CFF8() )
      {
        v2 = (void (__thiscall *)(_DWORD, _DWORD, _DWORD, _DWORD))sub_48ACD5(dword_5F3ADC);
        v2(v2, 0, 0, 0);
      }
      sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    }
    if ( !**v3 )
      initterm((int)&unk_5B1C38, (int)&unk_5B1F4C);
    initterm((int)&unk_5B2050, (int)&unk_5B2154);
    result = v3[1];
    if ( !*result )
    {
      byte_5F3AE0 = 1;
      *(_BYTE *)v3[2] = 1;
    }
  }
  return result;
}
    1) 当checkFlag = 0 时:
       sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    2 ) 当 checkFlag = 1时:
      sub_48B57C((unsigned int)&stru_5F4088);
    3 ) 其他值函数直接退出。
    对于 checkFlag = 1 可能是作者另外的一种check函数,我们可以不管。无论 checkFlag 为0还是为1,区别只是调用 sub_48B57C参数不同。
    我们继续分析  sub_48B57C((unsigned int)&g_funPtr);      // 5F4078
    对于 g_funPtr :5F4078实际上是一个结构体,结构体的成员怎样分析出来我们后面在说。
    
3、checkFunInfo结构及加解密函数
typedef  struct checkFunInfo
{
    int *startAddr;
    int *endAddr;
    int *maxAddr;
}
     其中startAddr指向的是一个malloc的buf,这个buf中存储的是要执行的函数指针数组,而 48B57C函数其实就是执行 g_funPtr结构中包含的函数列表。但是这个结构的数据包括全局buf的起始地址,结束地址以及内容函数指针都是经过加密的,加密算法与本题题意吻合,实际上就是一个移位的算法:
     如果加密整数 data,加解密算法如下:
      __security_cookie ^(data ror (0x20 - __security_cookie % 0x20u )) --->加密算法
     __security_cookie ^(data ror ( __security_cookie % 0x20u ))           ---->解密算法
     实际上加密就是一个数循环移位 0x20-X, 解密就是循环移位X  这样的话一个数经过加密和解密后循环移位了0x20次,即为其本身。
4、 sub_48B57C函数
       该函数经过一些列调用最终会调用到 563A00
       48B57C->563D20->563500->563410->563A00
5、 563A00函数
signed int __thiscall sub_563A00(struct checkFunInfo **this)
{
  int v2; // ecx
  void (__thiscall *v3)(_DWORD); // ST0C_4
  int v4; // ecx
  int v5; // eax
  int endAddr1; // [esp+8h] [ebp-3Ch]
  int startAddr1; // [esp+Ch] [ebp-38h]
  int ___security_cookie; // [esp+14h] [ebp-30h]
  int *endAddr; // [esp+1Ch] [ebp-28h]
  unsigned int startAddr_1; // [esp+20h] [ebp-24h]
  int ***v11; // [esp+28h] [ebp-1Ch]
  unsigned int startAddr; // [esp+2Ch] [ebp-18h]
  int *curAddr; // [esp+30h] [ebp-14h]

  v11 = (int ***)this;
  if ( !(*this)->startAddr )
    return -1;
  startAddr = decodeData(*(*this)->startAddr);
  curAddr = (int *)decodeData((**v11)[1]);
  if ( !startAddr || startAddr == -1 )
    return 0;
  nop(v2);
  ___security_cookie = j___security_cookie_get_0();
  startAddr_1 = startAddr;
  endAddr = curAddr;
  while ( 1 )
  {
    do
      --curAddr;
    while ( (unsigned int)curAddr >= startAddr && *curAddr == ___security_cookie );
    if ( (unsigned int)curAddr < startAddr )
      break;
    v3 = (void (__thiscall *)(_DWORD))decodeData1(*curAddr);
    *curAddr = ___security_cookie;
    v3(v3);                                     // 执行对应的函数指针数组中的函数
    startAddr1 = decodeData(***v11);
    endAddr1 = decodeData((**v11)[1]);
    if ( startAddr1 != startAddr_1 || (int *)endAddr1 != endAddr )
    {
      startAddr_1 = startAddr1;
      startAddr = startAddr1;
      endAddr = (int *)endAddr1;
      curAddr = (int *)endAddr1;
    }
  }
  sub_48C567();
  if ( startAddr != -1 )
    sub_48F0C8(startAddr, 2);
  nop(v4);
  v5 = j___security_cookie_get();
  ***v11 = v5;
  (**v11)[1] = v5;
  (**v11)[2] = v5;
  return 0;
}
      在 g_funPtr结构对应的函数指针数组中包含函数5AFCB0,而函数经过一些列调用会最终调用 sub_49DC80执行最终的校验。其调用关系如下:
      5AFCB0->48C28D->49CEB0->48DACA->49DC80,在函数sub_49CEB0中存在内部key 'invalid argument'
int __usercall sub_49CEB0@<eax>(int a1@<xmm0>)
{
  int v1; // eax
  int v2; // edx
  int v4; // [esp+0h] [ebp-E8h]

  sub_48D7B4((int)&unk_5F6007);
  v1 = sub_48DACA(a1, (int)aInvalidArgumen_1);  // 'invalid argument'
  return sub_48D935(v4, v2, 1, v1, a1);
}
   6、 sub_49DC80校验函数调用路径
     从上面分析可知49DC80调用流程如下:
    start->4EAA600->4EA710->_tmainCRTStartup(4EA730)->48E029->54A840->54A420->549F90->549EF0->54A1B0->
    48B57C->563D20->563500->563410->563A00->5AFCB0->48C28D->49CEB0->48DACA->49DC80
   那么这里面的关键就是 g_funPtr结构的赋值在哪里实现的呢。下面就分析 g_funPtr结构的赋值。

六、 g_funPtr结构赋值
1、 g_funPtr结构初始化流程
      _tmainCRTStartup(4EA730)->48BD42->4E9DE0->48E696->564150
char sub_564150()
{
  return sub_48B7D9((int)&off_5D2378, (int)&unk_5D23F8);
}
    sub_48B7D9函数就是执行off_5D2378与off_5D23F8之间的函数
 10 3E 56 00                         off_5D2378      dd offset sub_563E10    ; DATA XREF: sub_564150+A↑o
.rdata:005D2378                                                                             ; sub_5641B0+A↑o
.rdata:005D237C 00 00 00 00                                         align 10h
.rdata:005D2380 C0 3E 56 00                                         dd offset sub_563EC0
.rdata:005D2384 00 00 00 00                                         align 8
.rdata:005D2388 FA A5 48 00                                         dd offset sub_48A5FA
.rdata:005D238C C5 DF 48 00                                         dd offset sub_48DFC5
.rdata:005D2390 A0 3E 56 00                                         dd offset sub_563EA0
.rdata:005D2394 B0 3E 56 00                                         dd offset sub_563EB0
.rdata:005D2398 01 C7 48 00                                         dd offset sub_48C701
.rdata:005D239C 6C C0 48 00                                         dd offset sub_48C06C
.rdata:005D23A0 1C F7 48 00                                         dd offset sub_48F71C
.rdata:005D23A4 6F BD 48 00                                         dd offset sub_48BD6F
.rdata:005D23A8 00 00 00 00                                         dd 0
.rdata:005D23AC 40 3F 56 00                                         dd offset sub_563F40
.rdata:005D23B0 0E FB 48 00                                         dd offset sub_48FB0E
.rdata:005D23B4 7B F7 48 00                                         dd offset sub_48F77B
.rdata:005D23B8 BB B7 48 00                                         dd offset sub_48B7BB
.rdata:005D23BC 14 BE 48 00                                         dd offset sub_48BE14
.rdata:005D23C0 56 E0 48 00                                         dd offset sub_48E056
.rdata:005D23C4 CB C0 48 00                                         dd offset sub_48C0CB
.rdata:005D23C8 14 C3 48 00                                         dd offset sub_48C314
.rdata:005D23CC 00 00 00 00                                         dd 0
.rdata:005D23D0 00 00 00 00                                         dd 0
.rdata:005D23D4 20 40 56 00                                         dd offset sub_564020
.rdata:005D23D8 00 00 00 00                                         dd 0
.rdata:005D23DC 90 3F 56 00                                         dd offset sub_563F90
.rdata:005D23E0 00 00 00 00                                         dd 0
.rdata:005D23E4 60 3F 56 00                                         dd offset sub_563F60
.rdata:005D23E8 70 3E 56 00                                         dd offset sub_563E70
.rdata:005D23EC 80 3E 56 00                                         dd offset sub_563E80
.rdata:005D23F0 30 3E 56 00                                         dd offset allCheckFunStruct_ini
.rdata:005D23F4 60 3E 56 00                                         dd offset sub_563E60
     上述函数指针数组中的最有一个函数563E30(allCheckFunStruct_ini)为初始化 g_funPtr
char allCheckFunStruct_ini()
{
  checkFunStruct_ini_0(&g_funPtr);
  checkFunStruct_ini_0(&stru_5F4088);
  return 1;
}
    其调用关系为:
    9A3E30->8CB9CD->9A3D50(checkFunStruct_ini)
int __cdecl checkFunStruct_ini(struct checkFunInfo *a1)
{
  int *__security_cookie; // eax

  if ( !a1 )
    return -1;
  if ( a1->startAddr == a1->maxAddr )
  {
    nop((int)a1);
    __security_cookie = (int *)j___security_cookie_get();
    a1->startAddr = __security_cookie;
    a1->endAddr = __security_cookie;
    a1->maxAddr = __security_cookie;
  }
  return 0;
}
   实际上就是将 0 赋给 g_funPtr,经加密后变为__security_cookie
   因此 g_funPtr 结构初始化调用流程为:
    _tmainCRTStartup(4EA730)->48BD42->4E9DE0->48E696->564150-> 48B7D9-> 9A3E30->8CB9CD->9A3D50(checkFunStruct_ini)
2、 g_funPtr结构赋值之函数指针BUF申请
   在函数_tmainCRTStartup中,会存在如下2个调用:
    if ( j_initterm_e((int)&dword_5B1710, (int)&unk_5B1B34) )
      return 255;
    initterm((int)&unk_5B1000, (int)&unk_5B160C);

    j_initterm_e 与initterm实际上就是执行初始化函数
   _tmainCRTStartup(4EA730) -8CA979(j_initterm_e)->9A4920->92A600->48D854->4EA250->48DAE8->4EA160->48A361->563D00->
     48E80D->563DD0->5634C0->563360->563710
signed int __thiscall sub_563710(struct bufInfo *this)
{
  int *mallocSaveCheckSnBuf; // eax
  int v3; // eax
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // eax
  int __security_cookie; // [esp+0h] [ebp-40h]
  char v9; // [esp+4h] [ebp-3Ch]
  _DWORD *v10; // [esp+8h] [ebp-38h]
  char v11; // [esp+Ch] [ebp-34h]
  _DWORD *v12; // [esp+10h] [ebp-30h]
  unsigned int funCnt2; // [esp+14h] [ebp-2Ch]
  unsigned int funCnt1; // [esp+18h] [ebp-28h]
  int *i; // [esp+1Ch] [ebp-24h]
  int *maxAddr; // [esp+20h] [ebp-20h]
  int startAddr; // [esp+24h] [ebp-1Ch]
  unsigned int funCnt; // [esp+28h] [ebp-18h]
  int *endAddr; // [esp+2Ch] [ebp-14h]
  int mallocSaveCheckSnBuf1; // [esp+30h] [ebp-10h]
  unsigned int mallocCnt; // [esp+34h] [ebp-Ch]
  struct bufInfo *v22; // [esp+38h] [ebp-8h]
  char v23; // [esp+3Fh] [ebp-1h]

  v22 = this;
  if ( !*this->strCheckFunInfo )
    return -1;
  startAddr = decodeData((int)(*v22->strCheckFunInfo)->startAddr);
  endAddr = (int *)decodeData((int)(*v22->strCheckFunInfo)->endAddr);
  maxAddr = (int *)decodeData((int)(*v22->strCheckFunInfo)->maxAddr);
  if ( endAddr == maxAddr )
  {
    funCnt = ((signed int)maxAddr - startAddr) >> 2;
    if ( funCnt <= 0x200 )
      funCnt1 = funCnt;
    else
      funCnt1 = 512;
    funCnt2 = funCnt1;
    mallocCnt = funCnt1 + funCnt;
    if ( !(funCnt1 + funCnt) )
      mallocCnt = 32;
    mallocSaveCheckSnBuf1 = 0;
    if ( mallocCnt >= funCnt )
    {
      mallocSaveCheckSnBuf = (int *)sub_48B18F( // malloc
                                      startAddr,
                                      mallocCnt,
                                      4,
                                      2,
                                      (int)"minkernel\\crts\\ucrt\\src\\appcrt\\startup\\onexit.cpp",
                                      'p');
      v12 = sub_48AFC8(&v11, (int)mallocSaveCheckSnBuf);
      mallocSaveCheckSnBuf1 = sub_48AA1E((int)v12);
      sub_48C4BD((int)&v11);
    }
    if ( !mallocSaveCheckSnBuf1 )
    {
      mallocCnt = funCnt + 4;
      v3 = sub_48B18F(startAddr, funCnt + 4, 4, 2, (int)"minkernel\\crts\\ucrt\\src\\appcrt\\startup\\onexit.cpp", 'w');
      v10 = sub_48AFC8(&v9, v3);
      mallocSaveCheckSnBuf1 = sub_48AA1E((int)v10);
      sub_48C4BD((int)&v9);
    }
    if ( !mallocSaveCheckSnBuf1 )
      return -1;
    startAddr = mallocSaveCheckSnBuf1;
    endAddr = (int *)(mallocSaveCheckSnBuf1 + 4 * funCnt);
    maxAddr = (int *)(mallocSaveCheckSnBuf1 + 4 * mallocCnt);
    v23 = nop(mallocSaveCheckSnBuf1 + 4 * mallocCnt);
    __security_cookie = j___security_cookie_get_0();
    for ( i = endAddr; i != maxAddr; ++i )
      *i = __security_cookie;
  }
  v4 = encodeData((int)*v22->checkFunPtr);
  *endAddr = v4;
  ++endAddr;
  v5 = j_EncodeData1(startAddr);
  (*v22->strCheckFunInfo)->startAddr = (int *)v5;
  v6 = j_EncodeData1((int)endAddr);
  (*v22->strCheckFunInfo)->endAddr = (int *)v6;
  v7 = j_EncodeData1((int)maxAddr);
  (*v22->strCheckFunInfo)->maxAddr = (int *)v7;
  return 0;
}
    函数是将一个函数插入到函数指针列表中,如果没有申请函数指针列表空间则先申请,首先申请的大小是32*4 ,申请完后就将相应的的函数指针加密存储。
   其中bufInfo结构如下:
  
typedef struct bufInfo
{
checkFunInfo **pcheckFunInfo;
int ** checkFunPtr;
}
   其中checkFunPtr 为加入到pCheckFunInfo的函数指针。

3、将  5AFCB0函数写入到 g_funPtr中。
   在函数中_tmainCRTStartup存在如下调用
   initterm((int)&unk_5B1000, (int)&unk_5B160C); 就是执行 5B1000与 5B160C之间的函数。而在 5B1000与5B160C之间存在如下:
.rdata:005B14F8 10 58 49 00                                         dd offset sub_495810
.rdata:005B14FC B0 57 49 00                                         dd offset sub_4957B0
  而函数sub_4957B0如下:
int __usercall sub_4957B0@<eax>(int a1@<xmm0>)
{
  int v1; // eax
  int v2; // edx
  int v3; // ecx

  sub_48D7B4((int)&unk_5F6007);
  v1 = sub_48D854((int)sub_5AFCB0);             // 将check函数sub_5AFCB0插入到函数指针数组中
  return sub_48D935(v3, v2, 1, v1, a1);
}
其调用函数48D854将check函数sub_5AFCB0插入到函数指针数组中。
typedef  struct checkFunInfo
{
    int *startAddr;
    int *endAddr;
    int *maxAddr;
}
     其中startAddr指向的是一个malloc的buf,这个buf中存储的是要执行的函数指针数组,而 48B57C函数其实就是执行 g_funPtr结构中包含的函数列表。但是这个结构的数据包括全局buf的起始地址,结束地址以及内容函数指针都是经过加密的,加密算法与本题题意吻合,实际上就是一个移位的算法:
     如果加密整数 data,加解密算法如下:
      __security_cookie ^(data ror (0x20 - __security_cookie % 0x20u )) --->加密算法
     __security_cookie ^(data ror ( __security_cookie % 0x20u ))           ---->解密算法
     实际上加密就是一个数循环移位 0x20-X, 解密就是循环移位X  这样的话一个数经过加密和解密后循环移位了0x20次,即为其本身。
4、 sub_48B57C函数
       该函数经过一些列调用最终会调用到 563A00
       48B57C->563D20->563500->563410->563A00
5、 563A00函数
signed int __thiscall sub_563A00(struct checkFunInfo **this)
{
  int v2; // ecx
  void (__thiscall *v3)(_DWORD); // ST0C_4
  int v4; // ecx
  int v5; // eax
  int endAddr1; // [esp+8h] [ebp-3Ch]
  int startAddr1; // [esp+Ch] [ebp-38h]
  int ___security_cookie; // [esp+14h] [ebp-30h]
  int *endAddr; // [esp+1Ch] [ebp-28h]
  unsigned int startAddr_1; // [esp+20h] [ebp-24h]
  int ***v11; // [esp+28h] [ebp-1Ch]
  unsigned int startAddr; // [esp+2Ch] [ebp-18h]
  int *curAddr; // [esp+30h] [ebp-14h]

  v11 = (int ***)this;
  if ( !(*this)->startAddr )
    return -1;
  startAddr = decodeData(*(*this)->startAddr);
  curAddr = (int *)decodeData((**v11)[1]);
  if ( !startAddr || startAddr == -1 )
    return 0;
  nop(v2);
  ___security_cookie = j___security_cookie_get_0();
  startAddr_1 = startAddr;
  endAddr = curAddr;
  while ( 1 )
  {
    do
      --curAddr;
    while ( (unsigned int)curAddr >= startAddr && *curAddr == ___security_cookie );
    if ( (unsigned int)curAddr < startAddr )
      break;
    v3 = (void (__thiscall *)(_DWORD))decodeData1(*curAddr);
    *curAddr = ___security_cookie;
    v3(v3);                                     // 执行对应的函数指针数组中的函数
    startAddr1 = decodeData(***v11);
    endAddr1 = decodeData((**v11)[1]);
    if ( startAddr1 != startAddr_1 || (int *)endAddr1 != endAddr )
    {
      startAddr_1 = startAddr1;
      startAddr = startAddr1;
      endAddr = (int *)endAddr1;
      curAddr = (int *)endAddr1;
    }
  }
  sub_48C567();
  if ( startAddr != -1 )
    sub_48F0C8(startAddr, 2);
  nop(v4);
  v5 = j___security_cookie_get();
  ***v11 = v5;
  (**v11)[1] = v5;
  (**v11)[2] = v5;
  return 0;
}
      在 g_funPtr结构对应的函数指针数组中包含函数5AFCB0,而函数经过一些列调用会最终调用 sub_49DC80执行最终的校验。其调用关系如下:
signed int __thiscall sub_563A00(struct checkFunInfo **this)
{
  int v2; // ecx
  void (__thiscall *v3)(_DWORD); // ST0C_4
  int v4; // ecx
  int v5; // eax
  int endAddr1; // [esp+8h] [ebp-3Ch]
  int startAddr1; // [esp+Ch] [ebp-38h]
  int ___security_cookie; // [esp+14h] [ebp-30h]
  int *endAddr; // [esp+1Ch] [ebp-28h]
  unsigned int startAddr_1; // [esp+20h] [ebp-24h]
  int ***v11; // [esp+28h] [ebp-1Ch]
  unsigned int startAddr; // [esp+2Ch] [ebp-18h]
  int *curAddr; // [esp+30h] [ebp-14h]

  v11 = (int ***)this;
  if ( !(*this)->startAddr )
    return -1;
  startAddr = decodeData(*(*this)->startAddr);
  curAddr = (int *)decodeData((**v11)[1]);
  if ( !startAddr || startAddr == -1 )
    return 0;
  nop(v2);
  ___security_cookie = j___security_cookie_get_0();
  startAddr_1 = startAddr;
  endAddr = curAddr;
  while ( 1 )
  {
    do
      --curAddr;
    while ( (unsigned int)curAddr >= startAddr && *curAddr == ___security_cookie );
    if ( (unsigned int)curAddr < startAddr )
      break;
    v3 = (void (__thiscall *)(_DWORD))decodeData1(*curAddr);
    *curAddr = ___security_cookie;
    v3(v3);                                     // 执行对应的函数指针数组中的函数
    startAddr1 = decodeData(***v11);
    endAddr1 = decodeData((**v11)[1]);
    if ( startAddr1 != startAddr_1 || (int *)endAddr1 != endAddr )
    {
      startAddr_1 = startAddr1;
      startAddr = startAddr1;
      endAddr = (int *)endAddr1;
      curAddr = (int *)endAddr1;
    }
  }
  sub_48C567();
  if ( startAddr != -1 )
    sub_48F0C8(startAddr, 2);
  nop(v4);
  v5 = j___security_cookie_get();
  ***v11 = v5;
  (**v11)[1] = v5;
  (**v11)[2] = v5;
  return 0;
}
      在 g_funPtr结构对应的函数指针数组中包含函数5AFCB0,而函数经过一些列调用会最终调用 sub_49DC80执行最终的校验。其调用关系如下:
      5AFCB0->48C28D->49CEB0->48DACA->49DC80,在函数sub_49CEB0中存在内部key 'invalid argument'
int __usercall sub_49CEB0@<eax>(int a1@<xmm0>)
{
  int v1; // eax
  int v2; // edx
  int v4; // [esp+0h] [ebp-E8h]

  sub_48D7B4((int)&unk_5F6007);
  v1 = sub_48DACA(a1, (int)aInvalidArgumen_1);  // 'invalid argument'
  return sub_48D935(v4, v2, 1, v1, a1);
}
   6、 sub_49DC80校验函数调用路径
     从上面分析可知49DC80调用流程如下:
int __usercall sub_49CEB0@<eax>(int a1@<xmm0>)
{
  int v1; // eax
  int v2; // edx
  int v4; // [esp+0h] [ebp-E8h]

  sub_48D7B4((int)&unk_5F6007);
  v1 = sub_48DACA(a1, (int)aInvalidArgumen_1);  // 'invalid argument'
  return sub_48D935(v4, v2, 1, v1, a1);
}
   6、 sub_49DC80校验函数调用路径

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

最后于 2018-12-5 09:10 被ODPan编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 5568
活跃值: (3208)
能力值: ( LV12,RANK:407 )
在线值:
发帖
回帖
粉丝
2
到了第6步就止步了
后面三个步骤就迈不过去了。
2018-12-5 17:18
0
雪    币: 9934
活跃值: (2554)
能力值: ( LV6,RANK:87 )
在线值:
发帖
回帖
粉丝
3
感谢楼主的分析
2018-12-5 17:38
0
游客
登录 | 注册 方可回帖
返回
//