【BUUCTF-Reverse】findKey WP

运行

启动程序,弹出一个窗口,上面有菜单之外啥都没有。点进 Help/About 会出现一个关于弹窗。除此之外几乎啥信息都没有,就是在经过一通乱点之后有时会闪退。
菜单
弹窗

分析

无壳,直接拖入IDA中进行分析。

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  CHAR Buffer[100]; // [esp+4Ch] [ebp-84h] BYREF
  HACCEL hAccTable; // [esp+B0h] [ebp-20h]
  struct tagMSG Msg; // [esp+B4h] [ebp-1Ch] BYREF

  memset(&byte_428C54, 0, 0x100u);
  dword_428C50 = 0;
  LoadStringA(hInstance, 0x67u, WindowName, 100);
  LoadStringA(hInstance, 0x6Du, ClassName, 100);
  sub_401023(hInstance); // jmp sub_4011D0
  if ( !sub_40100F(hInstance, nShowCmd) )
    return 0;
  LoadStringA(::hInstance, 0x6Eu, Buffer, 100);
  hAccTable = LoadAcceleratorsA(hInstance, (LPCSTR)0x6D);
  while ( GetMessageA(&Msg, 0, 0, 0) )
  {
    if ( !TranslateAcceleratorA(Msg.hwnd, hAccTable, &Msg) )
    {
      TranslateMessage(&Msg);
      DispatchMessageA(&Msg);
    }
  }
  return Msg.wParam;
}

ATOM __cdecl sub_4011D0(HINSTANCE hInstance)
{
  WNDCLASSEXA v2; // [esp+4Ch] [ebp-30h] BYREF

  v2.cbSize = 48;
  v2.style = 3;
  v2.lpfnWndProc = (WNDPROC)&sub_401014;
  v2.cbClsExtra = 0;
  v2.cbWndExtra = 0;
  v2.hInstance = hInstance;
  v2.hIcon = LoadIconA(hInstance, (LPCSTR)0x6B);
  v2.hCursor = LoadCursorA(0, (LPCSTR)0x7F00);
  v2.hbrBackground = (HBRUSH)6;
  v2.lpszMenuName = (LPCSTR)109;
  v2.lpszClassName = ClassName;
  v2.hIconSm = LoadIconA(hInstance, (LPCSTR)0x6C);
  return RegisterClassExA(&v2);
}

似乎是个 Windows 程序。由于没怎么接触过于是又跑去简略学了一遍^^;

Windows API 文档

了解到 sub_401023 函数是定义窗口类的属性,在 sub_40100F 函数创建窗口,WinMain 的下方消息循环。

v2.lpfnWndProc = (WNDPROC)&sub_401014; 是窗口的回调函数,不断接受消息并进行处理,一般是主要的逻辑处理函数。所以直接点进 sub_401014 看看。

// attributes: thunk
void sub_401014()
{
  JUMPOUT(0x401640);
}

一个 JUMPOUT?不认识,可能是个跳转,所以继续点进 0x401640 看看。

WndProc跳转地址

看上去是正常汇编代码,但是按 P 不能创建函数,Output 里找到出问题的位置转到查看。

花指令

jmp 指令跳到了上面的一个不对劲的地址,而 jmp 上方连续 jzjnz 均跳到了下方同一个地址,大概相当于一个 jmp。也就是说下面的 jmp 并不会被执行,这大概就是花指令了,直接 nop 掉 jmp,重试 P 发现可以创建函数了,F5 查看。

LRESULT __stdcall WndProc(HWND hWndParent, UINT Msg, WPARAM wParam, LPARAM lParam)
{
  int v5; // eax
  size_t v6; // eax
  DWORD v7; // eax
  int v8; // eax
  int v9; // eax
  CHAR v10[256]; // [esp+54h] [ebp-3F8h] BYREF
  char v11[7]; // [esp+154h] [ebp-2F8h] BYREF
  __int16 v12; // [esp+15Bh] [ebp-2F1h]
  char v13; // [esp+15Dh] [ebp-2EFh]
  char Str[253]; // [esp+160h] [ebp-2ECh] BYREF
  __int16 v15; // [esp+25Dh] [ebp-1EFh]
  char v16; // [esp+25Fh] [ebp-1EDh]
  CHAR v17[256]; // [esp+260h] [ebp-1ECh] BYREF
  CHAR String[4]; // [esp+360h] [ebp-ECh] BYREF
  int v19; // [esp+364h] [ebp-E8h]
  __int16 v20; // [esp+368h] [ebp-E4h]
  CHAR Text[32]; // [esp+36Ch] [ebp-E0h] BYREF
  struct tagRECT Rect; // [esp+38Ch] [ebp-C0h] BYREF
  CHAR Buffer[100]; // [esp+39Ch] [ebp-B0h] BYREF
  HDC hdc; // [esp+400h] [ebp-4Ch]
  struct tagPAINTSTRUCT Paint; // [esp+404h] [ebp-48h] BYREF
  int v26; // [esp+444h] [ebp-8h]
  int v27; // [esp+448h] [ebp-4h]

  LoadStringA(hInstance, 0x6Au, Buffer, 100);
  if ( Msg > 0x111 )
  {
    if ( Msg == 0x205 )
    {
      if ( strlen((const char *)String1) > 6 )
        ExitProcess(0);
      if ( strlen((const char *)String1) )
      {
        memset(v17, 0, sizeof(v17));
        v6 = strlen((const char *)String1);
        memcpy(v17, String1, v6);
        v7 = strlen((const char *)String1);
        sub_40101E(String1, v7, (LPSTR)String1);
        strcpy(Str, "0kk`d1a`55k222k2a776jbfgd`06cjjb");
        memset(&Str[33], 0, 0xDCu);
        v15 = 0;
        v16 = 0;
        strcpy(v11, "SS");
        *(_DWORD *)&v11[3] = 0;
        v12 = 0;
        v13 = 0;
        v8 = strlen(Str);
        sub_401005(v11, (int)Str, v8);
        if ( _strcmpi((const char *)String1, Str) )
        {
          SetWindowTextA(hWndParent, "flag{}");
          MessageBoxA(hWndParent, "Are you kidding me?", "^_^", 0);
          ExitProcess(0);
        }
        memcpy(v10, &unk_423030, 0x32u);
        v9 = strlen(v10);
        sub_401005(v17, (int)v10, v9);
        MessageBoxA(hWndParent, v10, 0, 0x32u);
      }
      ++dword_428D54;
    }
    else
    {
      if ( Msg != 0x208 )
        return DefWindowProcA(hWndParent, Msg, wParam, lParam);
      if ( dword_428D54 == 16 )
      {
        strcpy(String, "ctf");
        v19 = 0;
        v20 = 0;
        SetWindowTextA(hWndParent, String);
        strcpy(Text, "Are you kidding me?");
        MessageBoxA(hWndParent, Text, Buffer, 0);
      }
      ++dword_428D54;
    }
  }
  else
  {
    switch ( Msg )
    {
      case 0x111u:
        v27 = (unsigned __int16)wParam;
        v26 = HIWORD(wParam);
        if ( (unsigned __int16)wParam == 104 )
        {
          DialogBoxParamA(hInstance, (LPCSTR)0x67, hWndParent, (DLGPROC)DialogFunc, 0);
        }
        else
        {
          if ( (unsigned __int16)wParam != 105 )
            return DefWindowProcA(hWndParent, Msg, wParam, lParam);
          DestroyWindow(hWndParent);
        }
        break;
      case 2u:
        PostQuitMessage(0);
        break;
      case 0xFu:
        hdc = BeginPaint(hWndParent, &Paint);
        GetClientRect(hWndParent, &Rect);
        v5 = strlen(Buffer);
        DrawTextA(hdc, Buffer, v5, &Rect, 1u);
        EndPaint(hWndParent, &Paint);
        break;
      default:
        return DefWindowProcA(hWndParent, Msg, wParam, lParam);
    }
  }
  return 0;
}

确实是窗口回调函数的样子。

通过直接翻阅 winuser.h 得知上方 0x2020x2050x208 分别对应鼠标左、中、右键的抬起消息,下方0x111 是菜单调用消息。== 104!= 105 没看懂,但是根据后续代码可以推测是菜单栏中的退出和关于界面弹窗按钮。

分析关于弹窗

我先看了下方。DialogFunc 从名字就能看出来是个函数,点进去发现是关于界面的消息回调函数。

int __stdcall sub_401B20(HWND hDlg, int a2, unsigned __int16 a3, int a4)
{
  switch ( a2 )
  {
    case 0x110:
      return 1;
    case 0x111:
      if ( a3 != 1 && a3 != 2 )
        goto LABEL_6;
      EndDialog(hDlg, a3);
      return 1;
    case 0x202:
LABEL_6:
      String1[dword_428C50++] = '1';
      return 0;
    case 0x205:
      String1[dword_428C50++] = '3';
      return 0;
    case 0x208:
      if ( dword_428D58 < 3 )
        ++dword_428D5C;
      String1[dword_428C50++] = '2';
      return 0;
    default:
      return 0;
  }
}

翻阅 Windows API 得知与窗口回调一样,a2uMsg 消息,那么 0x2020x2050x208 就很熟悉了,都是鼠标抬起消息。

观察到鼠标抬起消息的响应是给 String1 的末尾加上 '1''2''3'String1 在上面的 WndProc 函数似乎也有出现,可以推测是在关于窗口界面分别使用鼠标的左中右键来输入字符 '1''2''3'

分析加解密

回到 WndProc 函数的上方,可以看到 if ( Msg == 0x205 ) 表示右键时触发。

String1 长度大于 6 则直接退出程序,说明输入的长度不超过 6。

sub_40101E(String1, v7, (LPSTR)String1); 似乎对 String1 做了什么操作,跟进去。

int __cdecl sub_4013A0(BYTE *pbData, DWORD dwDataLen, LPSTR lpString1)
{
  DWORD i; // [esp+4Ch] [ebp-24h]
  CHAR String2[4]; // [esp+50h] [ebp-20h] BYREF
  BYTE v6[16]; // [esp+54h] [ebp-1Ch] BYREF
  DWORD pdwDataLen; // [esp+64h] [ebp-Ch] BYREF
  HCRYPTHASH phHash; // [esp+68h] [ebp-8h] BYREF
  HCRYPTPROV phProv; // [esp+6Ch] [ebp-4h] BYREF

  if ( !CryptAcquireContextA(&phProv, 0, 0, 1u, 0xF0000000) )
    return 0;
  if ( CryptCreateHash(phProv, 0x8003u, 0, 0, &phHash) )
  {
    if ( CryptHashData(phHash, pbData, dwDataLen, 0) )
    {
      CryptGetHashParam(phHash, 2u, v6, &pdwDataLen, 0);
      *lpString1 = 0;
      for ( i = 0; i < pdwDataLen; ++i )
      {
        wsprintfA(String2, "%02X", v6[i]);
        lstrcatA(lpString1, String2);
      }
      CryptDestroyHash(phHash);
      CryptReleaseContext(phProv, 0);
      return 1;
    }
    else
    {
      CryptDestroyHash(phHash);
      CryptReleaseContext(phProv, 0);
      return 0;
    }
  }
  else
  {
    CryptReleaseContext(phProv, 0);
    return 0;
  }
}

眼花缭乱全是不认识的函数,但是根据 Crypt、Hash 等关键词可以推测是加解密或哈希函数。

查询 Windows API 得知,CryptCreateHash 函数是创建哈希对象,第二项表示了使用的哈希算法,0x8003 可以查到就是 MD5 哈希算法。也就是把输入的结果用 MD5 哈希处理后输出为一串十六进制数字的字符串赋值回去。

再回到 WndProcStr 给的赋值 "0kk`d1a`55k222k2a776jbfgd`06cjjb" 一眼很重要的样子。下方 sub_401005(v11, (int)Str, v8); 函数跟进去,发现是个简单的异或加密。

unsigned int __cdecl sub_401590(LPCSTR lpString, int a2, unsigned int a3)
{
  unsigned int result; // eax
  unsigned int i; // [esp+4Ch] [ebp-Ch]
  unsigned int v5; // [esp+54h] [ebp-4h]

  v5 = lstrlenA(lpString);
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= a3 )
      break;
    *(_BYTE *)(i + a2) ^= lpString[i % v5];
  }
  return result;
}

密钥是 v11 也就是 "SS",写个 py 小程序解。

key_str = "SS"
key = [ord(x) for x in key_str]

ciphertext_str = "0kk`d1a`55k222k2a776jbfgd`06cjjb"
ciphertext = [ord(x) for x in ciphertext_str]

for i in range(len(ciphertext)):
    ciphertext[i] ^= key[i % len(key)]

cipherhash = ''.join([chr(x) for x in ciphertext])
print(cipherhash)

结果是 c8837b23ff8aaa8a2dde915473ce0991,显然是个十六进制数,和上面的 MD5 哈希值对上了。

6 位只由 123 构成的字符串组合可能数总量很低,甚至应该可以直接写脚本循环爆破,但是我还是懒了=_=

MD5

这么简单感觉瞎按都能按出来了……

结果

重新打开程序,点进关于弹窗界面,按照左中右右中左的顺序点击鼠标,关闭弹窗界面,再在主窗口右键,直接得到 flag。

flag

结语

网上搜到的 WP 似乎都是得到 123321 后直接用这个密钥异或解密数据得到 flag,好像没怎么看到分析别的函数,于是写了此 WP。

第一次写 WP 写的不怎么好见谅QwQ

posted @ 2026-01-14 20:27  BearBrine  阅读(4)  评论(0)    收藏  举报