【CTF reverse】新160个CrackMe之085-easycrackme-IDA静态分析+x64dbg动态分析

405 阅读6分钟

PETools查看概况

打开PETools,32位程序,Line number和Local Symbols已移除,Byte of machine word are reversed (high),入口点Section: [CODE], EP: 0x0000384C,因此没有壳。

IDA静态分析

载入IDA,把sub_40423C改成WndProc(随便百度一下,对照一个标准的mfc demo可以得知),sub_404024改成solve1sub_403F60改成about_windowsub_402ED0改成extract_string(注:这是静态分析阶段的错误印象,之后会揭晓其含义)。

// 只有solve1是有意思的
LRESULT __stdcall WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
  LRESULT v4; // ebx@1

  v4 = 0;
  switch ( Msg )
  {
    case 1u:
      ::lParam = (LPARAM)LoadBitmapA(hInstance, "software");
      break;
    case 2u:
      DeleteObject(::wParam);
      PostQuitMessage(0);
      break;
    case 0xFu:
      hdc = BeginPaint(hWnd, &Paint);
      MoveToEx(hdc, 20, 55, 0);
      LineTo(hdc, 400, 55);
      MoveToEx(hdc, 10, 214, 0);
      LineTo(hdc, 400, 214);
      MoveToEx(hdc, 10, 216, 0);
      LineTo(hdc, 400, 216);
      SelectObject(hdc, h);
      break;
    case 0x111u:
      if ( lParam == dword_406538 )
        solve1();
      if ( lParam == dword_40653C )
        about_window();
      if ( lParam == dword_406540 )
        SendMessageA(hWnd, 2u, 0, 0);
      break;
    default:
      v4 = DefWindowProcA(hWnd, Msg, wParam, lParam);
      break;
  }
  return v4;
}
// 这么长的代码,怎么可能静态查看呢?
int solve1()
{
  int v0; // ebx@1
  char *v1; // eax@3
  int v2; // ebx@3
  char *v3; // eax@5
  int v4; // eax@5
  int v5; // esi@7
  int v6; // ebx@7
  signed int v7; // eax@8
  int v8; // ebx@10
  int v9; // esi@10
  char *v10; // ST08_4@12
  char *v11; // eax@12
  unsigned int v13; // [sp-18h] [bp-20h]@1
  _UNKNOWN *v14; // [sp-14h] [bp-1Ch]@1
  int *v15; // [sp-10h] [bp-18h]@1
  signed __int32 v16; // [sp+0h] [bp-8h]@1
  int v17; // [sp+4h] [bp-4h]@1
  int savedregs; // [sp+8h] [bp+0h]@1

  v17 = 0;
  v16 = 0;
  v15 = &savedregs;
  v14 = &loc_4041BD;
  v13 = __readfsdword(0);
  __writefsdword(0, (unsigned int)&v13);
  sub_402DE8((int)&v17);
  sub_402DE8((int)&v16);
  v0 = GetWindowTextLengthA(dword_406534);
  if ( v0 > 0 )
  {
    do
    {
      sub_402ED8((int)&v16, (int)dword_4041D4);
      --v0;
    }
    while ( v0 );
  }
  v1 = get_402FA5_str((char *)v16);
  GetWindowTextA(dword_406534, v1, 50);
  v2 = GetWindowTextLengthA(dword_406530);
  if ( v2 > 0 )
  {
    do
    {
      sub_402ED8((int)&v17, (int)dword_4041D4);
      --v2;
    }
    while ( v2 );
  }
  v3 = get_402FA5_str((char *)v17);
  GetWindowTextA(dword_406530, v3, 50);
  // v4 = strlen(v17) = strlen(Name)
  v4 = extract_string(v17);
  // 求v8变量的关键循环
  if ( v4 > 0 )
  {
    v5 = 0;
    v6 = v4;
    if ( v4 > 0 )
    {
      v7 = 1;
      do
      {
        v5 += (v7 - 1) * *(_BYTE *)(v17 + v7 - 1);
        ++v7;
        --v6;
      }
      while ( v6 );
    }
    v8 = dword_4050B8[v5 & 0xF];
    sub_402F1C(v16, dword_404204);
    // 对Register进行一些处理,返回值送给v9
    v9 = sub_403FA0(v16);
    sub_402E80((volatile signed __int32 *)&v17, (signed __int32)"Error");
    sub_402E80(&v16, (signed __int32)"Invalid");
    if ( v8 == v9 )
    {
      sub_402E80((volatile signed __int32 *)&v17, (signed __int32)dword_404220);
      sub_402E80(&v16, (signed __int32)dword_40422C);
    }
  }
  else
  {
    sub_402E80(&v16, (signed __int32)dword_4041E0);// 这里实际上就是Name为空,弹出“Your Name?”的
    sub_402E80((volatile signed __int32 *)&v17, (signed __int32)"Error");
  }
  v10 = get_402FA5_str((char *)v17);
  v11 = get_402FA5_str((char *)v16);
  MessageBoxA_0(hWnd, v11, v10, 0);
  __writefsdword(0, v13);
  v15 = (int *)&loc_4041C4;
  return sub_402E0C((int)&v16, 2);
}

动静结合

solve1实在过于难懂,只能上x64dbg了。

1.jpg

左上角几个蓝色按钮,只用到从左到右第3个“运行“按钮,第5、6个”步进“和”步过“按钮。步进就是要进入函数调用,步过则不进入函数。

sub_402E80((volatile signed __int32 *)&v17, (signed __int32)"Error");对应的汇编代码CODE:0040414E mov edx, offset aError_0 ; "Error"处打断点,在程序界面随便输入些什么,然后点击x64dbg左上角的运行按钮。结合IDA的c伪代码和x64dbg动态调试得,变量v17就是Namev16就是Register#

前面说过extract_string我看不懂,但用x64dbg,打断点+“步过”,验证ABCDEabcdefgh等数据可知,extract_string就是strlen

同样是在动态调试过程中才得知,dword_404220就是OKdword_40422C就是Thanks a lot。因此我们只需要关注v8v9变量怎么算出来的。

v8就是这段代码,十分简单易懂的含义(记住变量v17就是Name)。

    v5 = 0;
    v6 = v4;
    if ( v4 > 0 )
    {
      v7 = 1;
      do
      {
        v5 += (v7 - 1) * *(_BYTE *)(v17 + v7 - 1);
        ++v7;
        --v6;
      }
      while ( v6 );
    }
    v8 = dword_4050B8[v5 & 0xF];
/*
edx = strlen(Name)
CODE:0040410C                 lea     ebx, [edx+edx]
CODE:0040410F                 add     ebx, 63h
CODE:00404112                 and     ebx, 0FFFFh
CODE:00404118                 and     esi, 0FFFFh
CODE:0040411E                 shl     ebx, 10h
CODE:00404121                 add     esi, ebx
CODE:00404123                 mov     ebx, esi
CODE:00404125                 mov     esi, ebx
*/

一开始我以为ida分析有错误,因为漏了下面那段汇编。但实际上没有错误,只不过是因为去除那段代码不影响结果,所以ida帮我们优化了。

v9则是下面的函数(把extract_string改成strlen):

int __usercall sub_403FA0@<eax>(signed __int32 a1@<eax>)
{
  int v1; // ecx@1
  int v2; // edx@1
  signed int v3; // eax@1
  int v4; // ebx@3
  unsigned int v6; // [sp-Ch] [bp-18h]@1
  _UNKNOWN *v7; // [sp-8h] [bp-14h]@1
  int *v8; // [sp-4h] [bp-10h]@1
  int v9; // [sp+4h] [bp-8h]@1
  signed __int32 v10; // [sp+8h] [bp-4h]@1
  int savedregs; // [sp+Ch] [bp+0h]@1

  v9 = 0;
  v10 = a1;
  sub_402F90();
  v8 = &savedregs;
  v7 = &loc_404016;
  v6 = __readfsdword(0);
  __writefsdword(0, (unsigned int)&v6);
  sub_402E80((volatile signed __int32 *)&v9, v10);
  v1 = strlen(v9) + 1;
  v2 = 0;
  v3 = 1;
  do
    v2 = *(_BYTE *)(v9 + v3++ - 1) + 10 * v2 - 48;
  while ( v1 != v3 );
  v4 = v2;
  __writefsdword(0, v6);
  v8 = (int *)&loc_40401D;
  sub_402E0C((int)&v9, 2);
  return v4;
}

这里涉及的函数有点多,不可能一个个看的,还是需要x64dbg:在v9 = sub_403FA0(v16)对应的汇编代码CODE:00404144 call sub_403FA0打断点,到断点处x64dbg点击一下“步过”按钮,然后查看v9(即esi寄存器)的值。

我们的结论很简单:只有以下代码有用:

  v1 = strlen(v9) + 1;  
  v2 = 0;
  v3 = 1;
  do
    v2 = *(_BYTE *)(v9 + v3++ - 1) + 10 * v2 - 48;
  while ( v1 != v3 );
  v4 = v2;

它就是把Register#从字符串转为int,并且无法正确处理带负号的情形。所以,我们只需要在IDA中shift+E导出dword_4050B8数组,即可开始编写注册机。

因为它不能处理负号,所以我们需要看一看Name对应的注册码转32位有符号数以后是不是负的,负的要丢弃。下面给出的几个Name都是合法的。

def calc(a):
    ans = 0
    for i in range(3,-1,-1):
        ans = ans << 8 | a[i]
    return ans

tbl = [
    0x26, 0x11, 0x79, 0x19, 0x07, 0x10, 0x79, 0x19, 0x79, 0x19, 
    0x26, 0x11, 0x79, 0x19, 0x07, 0x10, 0x78, 0x56, 0x34, 0x12, 
    0xF0, 0xDE, 0xBC, 0x9A, 0x34, 0x34, 0x12, 0x12, 0x78, 0x78, 
    0x78, 0x78, 0xC6, 0xCC, 0xC6, 0xCC, 0x00, 0xCC, 0x00, 0xCC, 
    0xFF, 0xEF, 0xEF, 0xFF, 0x55, 0x55, 0xCC, 0xDD, 0x89, 0x87, 
    0x67, 0x67, 0xCC, 0xCB, 0xCE, 0xCE, 0xAB, 0x99, 0x88, 0x77, 
    0x66, 0x77, 0x33, 0x44
]
names = [
    "iloveacm","ctfer","HANS","WSW","wsw",
    "1234567890"
]
for name in names:
    ans = 0
    for i in range(len(name)):
        ans += i * ord(name[i])
    ans &= 0xf
    ans <<= 2
    print(name,[hex(v) for v in tbl[ans:ans+4]],calc(tbl[ans:ans+4]))

资源下载

  1. exe文件可以在这个视频的简介给出的链接找到。
  2. IDA6.6可以在jsj.aust.edu.cn的ctf toolkit找到。