这是一道内部测试的ctf题目,源程序的md5是1902F635994FA663C3432553A67E5830

定位 主函数

这是一道windows sdk 写的 gui程序

尝试运行程序会得到如下运行结果

image-20200417205243788.png

在ida中对字符串 Wrong Code!Try Again!查找引用就可以找到 关键函数了

修复变量

  1. 分析主程序会发现这其实是一个标准的 c+sdk程序
  2. ida的分析基本正确
  3. 但是这里有一个全局变量识别出错了

这里是识别出错的地方

image-20200417205809962.png

  • ida认为这四个地址应该是 四个数组,但是仔细阅读源码可以发现这四个数组 在内存中应该是排在一起的
  • 这里对四个数组 重新定义:以byte_40313C为起点,定义类型为char [10]

修复之后就可以看到如下的关键代码

image-20200417210058388.png

分析主程序

  • 接下来就是主程序进行分析
  • 这里附上 分析后的代码
LRESULT __stdcall sub_401109(HWND hWndParent, UINT Msg, WPARAM wParam, LPARAM lParam)
{
  int v4; // ebx
  int v5; // eax
  char v6; // al
  char v7; // al
  int v8; // edx
  char v9; // cl
  int v10; // edx
  char v11; // al
  char v12; // cl
  __int16 v13; // ax
  int v14; // ecx
  CHAR ptr_pwd; // bl
  char v16; // dl
  char v17; // dl

  switch ( Msg )
  {
    case 2u:
      PostQuitMessage(0);
      break;
    case 1u:
      CreateWindowExA(0x200u, aEdit, 0, 0x50800080u, 15, 15, 255, 25, hWndParent, (HMENU)2, hInstance, 0);
      dword_403134 = SetDlgItemTextA(hWndParent, 2, String);
      CreateWindowExA(0x200u, aEdit, 0, 0x50800080u, 15, 50, 255, 25, hWndParent, (HMENU)4, hInstance, 0);
      dword_403134 = SetDlgItemTextA(hWndParent, 4, aEnterSerial);
      dword_403138 = (int)CreateWindowExA(
                            0x200u,
                            aButton,
                            aTry,
                            0x50800000u,
                            15,
                            85,
                            255,
                            25,
                            hWndParent,
                            (HMENU)3,
                            hInstance,
                            0);
      v4 = (unsigned int)(GetSystemMetrics(0) - 290) >> 1;
      v5 = GetSystemMetrics(1);
      SetWindowPos(hWndParent, 0, v4, (unsigned int)(v5 - 150) >> 1, 290, 150, 0x40u);
      break;
    case 0x111u:
      if ( (_WORD)wParam == 3 && !HIWORD(wParam) )
      {
        v6 = GetDlgItemTextA(hWndParent, 2, str_name, 40);// 获取用户名
        if ( v6 )                               // 用户名长度在[5,32]之间
        {
          if ( v6 > 32 )
          {
            MessageBoxA(0, aNameCanBeMax32, aSorry, 0);
          }
          else if ( v6 < 5 )
          {
            MessageBoxA(0, aNameMustBeMin5, aSorry, 0);
          }
          else
          {
            v7 = 5;
            v8 = 0;
            do
            {                                   // 前半段
              v9 = v7 + (str_name[v8] ^ 41);
              if ( v9 < 65 || v9 > 90 )
                v9 = v7 + 82;
              calc_name[v8] = v9;               // 根据用户名计算出的数据
                                                // 动态调试获取
                                                // 对calc_name 重定义为10字节
              calc_name[v8 + 1] = 0;
              LOBYTE(v8) = v8 + 1;
              --v7;
            }
            while ( v7 );
            v10 = 0;
            v11 = 5;
            do                                  // 后半段
            {
              v12 = v11 + (str_name[v10] ^ 0x27) + 1;
              if ( v12 < 65 || v12 > 90 )
                v12 = v11 + 77;
              calc_name[v10 + 5] = v12;
              calc_name[v10 + 6] = 0;
              LOBYTE(v10) = v10 + 1;
              --v11;
            }
            while ( v11 );
            v13 = GetDlgItemTextA(hWndParent, 4, str_pwd, 40);
            if ( v13 && v13 <= 10 && v13 >= 10 )// 密码长度为10
            {
              v14 = 0;
              while ( 1 )
              {
                ptr_pwd = str_pwd[v14];         // 迭代密码的指针
                if ( !ptr_pwd )
                  break;
                v16 = calc_name[v14] + 5;
                if ( v16 > 90 )
                  v16 = calc_name[v14] - 8;
                v17 = v16 ^ 0xC;
                if ( v17 < 65 )
                {
                  v17 = v14 + 75;
                }
                else if ( v17 > 90 )
                {
                  v17 = 75 - v14;
                }
                ++v14;
                if ( v17 != ptr_pwd )
                  goto LB_WRONG;                // nop掉 这里的 goto跳转,直接在内存中查看数据
              }
              MessageBoxA(0, aSerialIsCorrec, aGoodCracker, 0);// 正确的分支
            }
            else
            {
LB_WRONG:
              MessageBoxA(0, Text, Caption, 0);
            }
          }
        }
        else
        {
          MessageBoxA(0, aEnterName_0, aSorry, 0);
        }
      }
      break;
    default:
      return DefWindowProcA(hWndParent, Msg, wParam, lParam);
  }
  return 0;
}

程序逻辑

算法逻辑

  1. 程序首先 利用算法1/算法2分别计算出name的前半段calc_name以及后半段calc_name

    注意这里 是用两个算法分别计算
  2. 接下载再利用calc_name计算出真正的 密码
  3. 这里要注意的是:真正的密码是以字节为单位进行计算,并且每计算出一个字节就会立即比对,因此无法直接在内存中获取到完整的密钥
  4. 这里选择 按照程序逻辑写出对应的注册机来解题

    题目要求 用户名必须为 dcnctf,所以注册机中直接写死了

calc_name

这里有一个关键的数据calc_name,一共有两种方案:

  1. 由于程序需要的是指定 用户名 对应的密码,因此可以采用动态调试
  2. 直接 利用 源程序中 关于calc_name相关的代码计算

动态调试

直接在关键位置00401316下断电,利用x64 断点即可

获取cala_name.png

算法还原

阅读主程序中相关的代码,还原出 计算calc_name的相关代码

//计算中间数据
void get_cala_name()
{
    // 前半段
    char v7 = 5;
    int v8 = 0;
    do
    {
      char v9 = v7 + (str_name[v8] ^ 41);
      if ( v9 < 65 || v9 > 90 )
        v9 = v7 + 82;
      calc_name[v8] = v9;
      calc_name[v8 + 1] = 0;
      v8= v8 + 1;
      --v7;
    }
    while ( v7 );
    
    
    //后半段
        int v10 = 0;
        int v11 = 5;
        do                                  // 后半段
        {
            char v12 = v11 + (str_name[v10] ^ 0x27) + 1;
            if ( v12 < 65 || v12 > 90 )
            v12 = v11 + 77;
            calc_name[v10 + 5] = v12;
            calc_name[v10 + 6] = 0;
            v10 = v10 + 1;
            --v11;
        }
        while ( v11 );
}

注册机

##include <stdio.h>
##include <stdlib.h>



// 动态调式获取 calc_name
// 题目要求用户名为 "dcnctf"
char str_name[]="dcnctf";
// 调试出来的数据是 RNJLSIIMGU
// 这里长度给多点
char calc_name[16]={0};


//计算中间数据
void get_cala_name()
{
    // 前半段
    char v7 = 5;
    int v8 = 0;
    do
    {
      char v9 = v7 + (str_name[v8] ^ 41);
      if ( v9 < 65 || v9 > 90 )
        v9 = v7 + 82;
      calc_name[v8] = v9;
      calc_name[v8 + 1] = 0;
      v8= v8 + 1;
      --v7;
    }
    while ( v7 );
    
    
    //后半段
        int v10 = 0;
        int v11 = 5;
        do                                  // 后半段
        {
            char v12 = v11 + (str_name[v10] ^ 0x27) + 1;
            if ( v12 < 65 || v12 > 90 )
            v12 = v11 + 77;
            calc_name[v10 + 5] = v12;
            calc_name[v10 + 6] = 0;
            v10 = v10 + 1;
            --v11;
        }
        while ( v11 );
}


void get_pwd()
{
    // 这是从 程序中抠出来的代码
    int v14=0;
     while ( v14<10 )
    {
        char v16 = calc_name[v14] + 5;
        if ( v16 > 90 )
          v16 = calc_name[v14] - 8;
        char v17 = v16 ^ 0xC;
        if ( v17 < 65 )
        {
          v17 = v14 + 75;
        }
        else if ( v17 > 90 )
        {
          v17 = 75 - v14;
        }
        ++v14;
        printf("%c",v17);

    }
}

int main ()
{

    get_cala_name();
    printf("calc_name: %s \r\n",calc_name);
    
    get_pwd();
    return 0;
}

flag

账号: dcnctf

密码: KJCHTBBDSV

Last modification:April 17, 2020
如果觉得我的文章对你有用,请随意赞赏