吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 22300|回复: 54
收起左侧

[CTF] 详细分析看雪2016CTF第一题Crack_ME用IDA+X64DBG追码

  [复制链接]
pk8900 发表于 2018-6-21 17:48
本帖最后由 pk8900 于 2018-6-25 23:17 编辑

前言


        很长时间没有发帖子了,这段时间一直在饿补,随着逆向学习深入,发现自己要学的东西太多了,许多东西过去都是只听说过,跟本没学习过,于是看网上的各种教程,并寻找用来练手的东西,发现看雪CTF里有不少CRACKME,于是拿来分析研究。
        这个CRACKME的作者叫[凉飕飕],CTF要求是追码,整个追码过程断续的进行2天时间,然后写的这个帖子,尽量还原原始分析步骤,如果有遗漏和不对的地方,欢迎大家请跟贴讨论。
       此贴并非技术贴,大牛可以一带而过,和我一样的新手也许可以从中学习到一些方法和经验。

下载地址


        CRACKME原帖及下载地址:https://ctf.pediy.com/game-fight-2.htm

crack简介


        PEid显示,Microsoft Visual C++ 8 *,图标是个MFC图标,估计是VC++的程序错不了。界面如下:
       Image 001.png
        输入任意注册码,点击OK,有提示信息框:something you lost!,并且每尝试一次,左边还有一个文本框递增次数。

使用工具


       IDA7.0,X64DBG,辅助工具:PEid,慧星小助手(查看控件ID,本例中:密码框为1002,次数框为1000)

摸清流程


       既然程序有提示字符串,那就从字符串下手。X64DBG载入,运行后程序自动退出,有反调试。
       打开IDA进行静态分析,查找字符串,找到结果如下:
      .text:00402423        sub_402120        push    offset Text     ; "something you lost!"
      转到sub_402120调用处,此处IDA代码如下:
      
[C++] 纯文本查看 复制代码
        BeginPaint(hWnd, &Paint);        EndPaint(hWnd, &Paint);
        return 0;
    }
    return DefWindowProcW(hWnd, Msg, wParam, lParam);
  }
  if ( (unsigned __int16)wParam > 0x40Au )
  {
    switch ( (unsigned __int16)wParam )
    {
      case 0x40Bu:                              // 注册成功条件:wParam==0x40B
        *(_OWORD *)v20 = xmmword_41DB98;
        v23 = 0;
        v21 = xmmword_41DBA8;
        v22 = xmmword_41DBB8;
        memset(&v24, 0, 0x96u);
        MessageBoxW(hWnd, v20, L"Successed", 0);// 成功位置
        return 0;
      case 0x40Cu:
        sub_402970(lParam, &v19);
        v9 = operator new[](2 * (v19 + 1));
        sub_402870(v9, lParam);
        sub_4029B0(v9);
        sub_402A00(v9);
        memset(v20, 0, 0xC8u);
        sub_402870(v20, v9);
        j_j_j___free_base(v9);
        v10 = 0;
        if...
        if ( sub_402810(lParam, v20) )
          SendMessageW(hWnd, 0x111u, 0x40Au, 0);
        return 0;
      case 0x40Fu:
        MessageBoxW(0, L"something you lost!", L"Failed", 0);// 失败处
        return 0;
    }
    return DefWindowProcW(hWnd, 0x111u, wParam, lParam);
  }
  if ( (unsigned __int16)wParam == 0x40A )
  {
    *(_OWORD *)Text = xmmword_41DB60;
    v26 = xmmword_41DB70;
    v27 = xmmword_41DB80;
    v28 = qword_41DB90;
    memset(&v29, 0, 0x2Cu);
    MessageBoxW(hWnd, Text, L"Failed", 0);      // 失败处

在失败对话框上方,有明显"Successed"成功字样,分析得出成功的条件就是:wParam==0x40B,再往上可以看到:
[C++] 纯文本查看 复制代码
 switch ( Msg )
    {
      case 0x113u:
v13 = operator new(0x14u);
        v40 = 0;
        *(_OWORD *)v39 = 0i64;
        v14 = GetDlgItem(hWnd, 1000);
        GetDlgItemTextW(v14, 1000, v39, 10);
        v19 = -858993460;
        sub_4027C0(v39, (const char *)L"%d", &v19);
        *v13 = v19;
        v13[1] = hWnd;
        v13[3] = GetDlgItem(hWnd, 1002);
        v13[2] = GetDlgItem(hWnd, 1000);
        v13[4] = v13;
“.............................”
while ( *((_WORD *)&v32 + v15) );
        if ( OpenEventW(0x1F0003u, 0, L"Cracker") )
          CreateThread(0, 0, StartAddress, v13, 0, 0);
        return 0;
“.............................”
return DefWindowProcW(hWnd, Msg, wParam, lParam);

case 0x113u,百度查询,0x113为时间事件,也就是程序里有一个时钟事件,用做检测,事件中包含"Cracker",“Failed”等可疑字串,未发现成功字串,只有一个可疑的CreateThread(0, 0, StartAddress, v13, 0, 0);函数,创建新线程。
(此期间尝试跟踪此时间事件,结果一点进展都没有,关于程序反调试,我采用的附加的方法轻松绕过,反调试部分放在最后分析。)
绕了半天的弯路后,又回到这里,决定从成功注册的条件:wParam==0x40B入手,IDA查的常量:0x40B(IDA操作为查找立即数),果然找到了关键位置,sub_401870,右键对函数重命名为:check_right_401870(命名方便在函数列表中查找,保留地址是方便X64DBG中进行调试定位),在这个子程序中调用了 return PostMessageW((HWND)struct_count_list->win_handle, 0x111u, 0x40Bu, 0);,这样wParam就被赋值0x40B,就可以出现成功的提示了。(此期间尝试在X64DBG中对基址+1870地址下断,根本断不下来,说明验证过程中没有调用这里)
继续IDA中静态分析,查找check_right_401870函数调用关系,(IDA操作:鼠标定位于check_right_401870函数中,右键“邻近浏览器”),在邻近浏览器图表视图中通过双击父结点,及隐藏非重要节点,得出如下流程图:
Image 002.png
成功只有一个路线,IDA的这个功能直的很有用,如果想爆破实现的话,那就可以按图索骥了。
3.

逐段分析


这个过程是漫长的,也可以说是经验积累的过程,大牛通常能一眼看穿的代码,而新手就要推敲半天,我想这就是大牛和新手之间最大的差别吧~~~。
3.1

msgcheck_2120


此函数代码很长,下面仅贴上关键部分(IDA F5 伪代码):
[C++] 纯文本查看 复制代码
if ( (unsigned __int16)wParam == 0x3EB )    // 点击了OK按钮
    {
      sub_402790(String, 10, (const char *)L"%d", ++dword_420318);
      v4 = GetDlgItem(hWnd, 1000);
      SetWindowTextW(v4, String);
      v5 = (struct_count_array *)operator new(0x14u);
      v40 = 0;
      v6 = v5;
      *(_OWORD *)v39 = '\0';
      v7 = GetDlgItem(hWnd, 1000);
      GetWindowTextW(v7, v39, 10);
      vsscanf_27C0((int)v39, (const char *)L"%d", &v19);
      *(_DWORD *)&v6->Now_count = v19;
      v6->win_handle = (int)hWnd;
      v6->passwd_handle = (int)GetDlgItem(hWnd, 1002);
      v6->count_handle = (int)GetDlgItem(hWnd, 1000);
      v6->field_10 = (int)v6;
      CreateThread(0, 0, (LPTHREAD_START_ROUTINE)check_01_4020E0, v6, 0, 0);// 调用检查函数
      return 0;
    }

msgcheck_402120是MSG处理函数,这里处理所有返回的事件,上面代码为OK按钮事件,用CreateThread函数创建一个新线程,处理按钮事件,参数V6,即上面的V5,通过X64DBG中查看,估计是一个结构体,于是在IDA中自定义了一个结构体:如下
[Asm] 纯文本查看 复制代码
struct_count_array struc ; (sizeof=0x14, mappedto_101)
00000000 Now_count       dw ?
00000002                 db ? ; undefined
00000003                 db ? ; undefined
00000004 win_handle      dd ?
00000008 count_handle    dd ?
0000000C passwd_handle   dd ?
00000010 field_10        dd ?
00000014 struct_count_array ends

在IDA中将变量V5指定为结构体指针,这样代码会更加清晰,结构体中:Now_count=当前尝试次数 win_handle=主窗体句柄  count_handle=显示次数文本框句柄  passwd_handle=密码框句柄 field_10=当前结构体指针,通过这结构体可以轻松访问密码框和次数框的文本内容。
结构体大小是20,从v5 = (struct_count_array *)operator new(0x14u);这句中就可以确定。
3.2

check_01_20E0


代码比较少,调用了check_02_401CB0,参数共2个,第一个是我们自定义的那个结构体数据,第二个参数是0
[C++] 纯文本查看 复制代码
int __stdcall check_01_4020E0(struct_count_array *lpMem)
{
  check_02_401CB0((int)lpMem, 0);
  sub_402CE4(lpMem);
  return 0;
}

3.3

check_02_401CB0


伪代码如下:
[C++] 纯文本查看 复制代码
void __fastcall check_02_401CB0(int a1, int a2)
{
  struct_count_array *canshu_1; // edi
  int v3; // ecx
  unsigned int len; // esi
  WCHAR *v5; // eax
  _WORD *next_passwd; // ebx
  clock_t v7; // [esp+4h] [ebp-D0h]
  WCHAR String; // [esp+8h] [ebp-CCh]

  canshu_1 = (struct_count_array *)a1;
  if ( a1 )
  {
    if ( a2 )
    {
      v7 = clock();
      memset(&String, 0, 200u);
      GetWindowTextW((HWND)canshu_1->passwd_handle, &String, 200);
      len = 0;
      v5 = &String;
      if ( String )
      {
        do
        {
          ++v5;
          ++len;
        }
        while ( *v5 );
      }
      next_passwd = operator new[](2 * (len + 1));
      if ( clock() - v7 > 2 )
        exit(0);
      sub_402870((int)next_passwd, &String);    // 修改了PASSWORD
      if ( len >= 7 )
      {
        if ( len <= 7 )
        {
          check_03_401A60((int)canshu_1, next_passwd);// len>=7 并且<=7 ,所以密码长度只能为7位
          return;
        }
        SendMessageW((HWND)canshu_1->win_handle, 0x40Du, 0, 0);
      }
      else
      {
        SendMessageW((HWND)canshu_1->win_handle, 0x40Eu, 0, 0);
      }
      j_j_j___free_base(next_passwd);
      return;
    }
    if ( checkhave_b_401C00((HWND *)a1)         // 检查是否含有‘b’
      && (memset(&String, 0, 0xC8u),
          GetWindowTextW((HWND)canshu_1->passwd_handle, &String, 100),
          checkhave_402A50(v3, (__int16 *)&String, 'p')) )// 检查是否含有 p
    {
      check_02_401CB0((int)canshu_1, 1);
    }
    else
    {
      SendMessageW((HWND)canshu_1->win_handle, 0x111u, 0x40Fu, 0);
    }
  }
}

这个函数很有意思,参数a2=0时,检测密码中是否含有字母‘p’和‘b’,如果有则调用自身,传a2参数为1,当a2=1时,检查密码长度是否为7位,通过这两个条件即可进入下一验证环节。
3.4

check_03_1A60


这个函数是一个加密函数,check_03_401A60(struct_count_array *a1, _WORD *a2),a2取我们输入的密码,在这个过程中被加密,加密过程也比较简单,只是做了一下异或加密。前半部分分析了半天貌似没有实际做用,估计是作者加的干扰代码。
函数最后调用:return check_right_401870(next_canshu_a1, next_passwd);,将加密后的密码传入下一验证步骤。
3.5

check_right_1870


伪代码如下:
[C++] 纯文本查看 复制代码
BOOL __fastcall check_right_401870(struct_count_array *a1, _WORD *a2)
{
  struct_count_array *struct_count_list; // ebx
  _WORD *password; // edi
  char *char_N; // ecx
  signed int v5; // eax
  signed int v6; // eax
  __int16 *v7; // ecx
  unsigned int count; // edx
  __int16 *i; // eax
  unsigned int v10; // ecx
  unsigned int v11; // eax
  unsigned int len_pw; // ecx
  _WORD *v13; // eax
  unsigned int v14; // eax
  unsigned int len_pw1; // edx
  _WORD *j; // eax
  unsigned int v17; // ecx
  unsigned int v18; // eax
  int n; // esi
  __int16 enc_s; // cx
  __int64 *v21; // ebx
  __int16 *_pswd; // eax
  __int16 v23; // dx
  __int16 *v24; // ecx
  int v25; // eax
  int v26; // ecx
  __int64 *v27; // eax
  _WORD *v28; // esi
  unsigned int v29; // ecx
  struct_count_array *tmp_arg_1; // [esp+Ch] [ebp-54h]
  __int16 enc_list[28]; // [esp+10h] [ebp-50h]
  char v33; // [esp+48h] [ebp-18h]
  __int64 v34; // [esp+50h] [ebp-10h]
  __int16 v35; // [esp+58h] [ebp-8h]

  struct_count_list = a1;
  password = a2;
  tmp_arg_1 = a1;
  memset(enc_list, 0, 54u);
  char_N = &v33;
  v5 = '0';
  do
  {
    *(_WORD *)char_N = v5;
    char_N += 2;
    ++v5;
  }
  while ( v5 <= '9' );                          // 填充数字 0-9
  v6 = 'a';
  v7 = enc_list;
  do
  {
    *v7 = v6;
    ++v7;
    ++v6;
  }
  while ( v6 <= 'z' );                          // 填充小写字母 a-z
  count = 0;
  for ( i = enc_list; *i; ++count )             // 计算个数
    ++i;
  v10 = 0;
  if ( count )
  {
    do
    {
      v11 = (unsigned __int16)enc_list[v10];
      if ( v11 >= 'a' && v11 <= 'z' )
        enc_list[v10] = v11 - 32;
      ++v10;
    }
    while ( v10 < count );                      // 小字字母转为大写字母
  }
  len_pw = 0;
  v13 = password;
  if ( password )
  {
    if ( *password )
    {
      do
      {
        ++v13;
        ++len_pw;
      }
      while ( *v13 );                           // count_pw = Password长度
    }
    v14 = 0;
    if ( len_pw )
    {
      do
      {
        if ( v14 >= 2 )
        {
          if ( v14 >= 4 )
            password[v14] ^= 'B';
          else
            password[v14] ^= 'P';
        }
        else
        {
          password[v14] ^= 0xFu;
        }
        ++v14;
      }
      while ( v14 < len_pw );
    }                                           // 还原已经加密过的PASSWORD
    len_pw1 = 0;
    for ( j = password; *j; ++len_pw1 )
      ++j;
    v17 = 0;
    if ( len_pw1 )
    {
      do
      {
        v18 = (unsigned __int16)password[v17];
        if ( v18 >= 'a' && v18 <= 'z' )
          password[v17] = v18 - 32;
        ++v17;
      }
      while ( v17 < len_pw1 );                  // password小写转大写
    }
  }
  n = 0;
  v34 = '\0';
  v35 = 0;
  if ( *password )
  {
    enc_s = enc_list[0];
    v21 = &v34;
    _pswd = password;
    do
    {
      if ( enc_s )
      {
        v23 = *_pswd;
        v24 = enc_list;
        v25 = 0;
        while ( v23 != *v24 )
        {
          v24 = &enc_list[++v25];
          if ( !enc_list[v25] )
            goto LABEL_37;
        }
        *(_WORD *)v21 = enc_list[v25];
        v21 = (__int64 *)((char *)v21 + 2);
LABEL_37:
        enc_s = enc_list[0];
      }
      _pswd = &password[++n];
    }
    while ( password[n] );
    struct_count_list = tmp_arg_1;
  }                                             // 取出第一个字母的位置 V34
  v26 = 0;
  v27 = &v34;
  if ( (_WORD)v34 )
  {
    do
    {
      v27 = (__int64 *)((char *)v27 + 2);
      ++v26;
    }
    while ( *(_WORD *)v27 );
    if ( v26 == 2 )
    {
      LODWORD(v34) = '5\01';
      HIDWORD(v34) = &unk_420050;               // “15PB”
      v28 = password + 2;
      v35 = 0;
      v29 = 0;
      while ( *((_WORD *)&v34 + v29) == *v28 )
      {
        ++v29;
        ++v28;
        if ( v29 >= 4 )
        {
          if ( !sub_401740(struct_count_list, password) )
            return PostMessageW((HWND)struct_count_list->win_handle, 0x111u, 0x40Au, 0);// --40B--成功--
          return PostMessageW((HWND)struct_count_list->win_handle, 0x111u, 0x40Bu, 0);
        }
      }
    }
  }
  return PostMessageW((HWND)struct_count_list->win_handle, 0x111u, 0x40Au, 0);
}

这一函数很长,实现的功能也很复杂,函数首先构建一个密码表“123456789AB......Z”,实际用到的只有前9个数字字符,然后还原上一步聚中加密的密码,并转为大写,然后对比了我们输入密码的3至6位是否为“15PB”,这样我们就可以确定我们的密码第3-6位正确的应该是:“15pb”
函数另外调用了sub_401740(struct_count_list, password)来进行验证,IDA伪代码如下:
[C++] 纯文本查看 复制代码
signed int __fastcall sub_401740(_DWORD *a1, _WORD *a2)
{
  _WORD *the_password; // ebx
  struct_count_array *v3; // edx
  __int128 *v4; // ecx
  signed int char_1; // eax
  int len; // esi
  _WORD *v7; // eax
  char *new_string; // edi
  unsigned __int16 *last_passwd; // eax
  int v10; // esi
  __int128 *v11; // ecx
  __int16 v12; // di
  __int16 v13; // ax
  __int128 *v14; // ecx
  signed int v15; // edx
  unsigned __int16 *v17; // [esp+4h] [ebp-50h]
  struct_count_array *v18; // [esp+8h] [ebp-4Ch]
  char *v19; // [esp+Ch] [ebp-48h]
  __int128 charlist_1to9; // [esp+30h] [ebp-24h]
  __int64 v21; // [esp+40h] [ebp-14h]
  int v22; // [esp+48h] [ebp-Ch]
  __int16 v23; // [esp+4Ch] [ebp-8h]

  the_password = a2;
  v22 = 0;
  v3 = (struct_count_array *)a1;
  v23 = 0;
  v18 = (struct_count_array *)a1;
  charlist_1to9 = '\0';
  v4 = &charlist_1to9;
  char_1 = '1';
  v21 = '\0';
  do
  {
    *(_WORD *)v4 = char_1;
    v4 = (__int128 *)((char *)v4 + 2);
    ++char_1;
  }                                             // charlist_1to9="123456789"
  while ( char_1 <= '9' );
  len = 0;
  v7 = the_password;
  if ( the_password && *the_password )
  {
    do
    {
      ++v7;
      ++len;
    }
    while ( *v7 );                              // 计算密码长度
  }
  new_string = cat_4028D0((char *)&charlist_1to9, &the_password[*(_DWORD *)&v3->Now_count]);
  last_passwd = &the_password[len - 1];
  v19 = new_string;
  v10 = 0;
  v17 = last_passwd;
  if ( (_WORD)charlist_1to9 )
  {
    v11 = &charlist_1to9;
    v12 = *last_passwd & 1;
    while ( 1 )
    {
      v13 = v12 + (*(_WORD *)v11 >> 2);
      if ( v13 == '2' )
        break;
      if ( v13 != 'd' )
      {
        v11 = (__int128 *)((char *)&charlist_1to9 + 2 * ++v10);
        if ( *(_WORD *)v11 )
          continue;
      }
      new_string = v19;
      goto LABEL_12;
    }
  }
  else
  {
LABEL_12:
    v14 = &charlist_1to9;
    v15 = '1';
    while ( *(_WORD *)v14 == *(_WORD *)((char *)v14 + (char *)the_password - (char *)&charlist_1to9) )// 第一位 是 1   the_password 是原始密码
    {
      v15 += 6;
      v14 = (__int128 *)((char *)v14 + 2);
      if ( v15 > '9' )
      {
        if ( (unsigned __int16)*the_password + *((unsigned __int16 *)new_string + 9) == 'c'// 第二位即:*((unsigned __int16 *)new_string + 9,所以第

二位是2
          && *v17 == *(_DWORD *)&v18->Now_count + *((unsigned __int16 *)new_string + 6) )// 最后一位:次数+字串里的7,即8
        {
          return 1;
        }
        return 0;
      }
    }
  }
  return 0;
}

在sub_401740中,验证第一位是否为“1”,函数将密码连接成“123456789”+从调用次数开始截取到最后的密码部分,比如第一次点OK时密码是“ABCDEFG”,连接后即为“123456789BCDEFG”,验证第一位+第二位是否等于‘c’,所以第二位为‘2’,通过取“123456789BCDEFG”中第(7+验证次数)位与我们输入密码的最后一位比较,来验证最后一位,所以第一次验证时最后一位应该是“8”,但第二次验证则无法验证成功第二位的2,因为2被截去了,所以只能在第一次按OK时输入“1215pb8”才能通过验证。
至此整个分析过程完成,并成功找到密码。
截个图:
Image 003.png
4.

反调试分析


关于反调试:程序启动有反调试:2处:
1、比较父进程名称,是否为exeplorer.exe
0109152C | 74 04                        | je crack_.1091532                              |
0109152E | 84 D2                        | test dl, dl                                    |
01091530 | 74 1A                        | je crack_.109154C                              | 跳去结束程序
01091532 | 57                           | push edi                                       |
01091533 | FF 15 60 80 0A 01            | call dword ptr ds:[<&CloseHandle>]             |
01091539 | 8B 4D FC                     | mov ecx, dword ptr ss:[ebp-0x4]                |
0109153C | 33 C0                        | xor eax, eax                                   |
0109153E | 5F                           | pop edi                                        |
0109153F | 5E                           | pop esi                                        |
01091540 | 33 CD                        | xor ecx, ebp                                   |
01091542 | 5B                           | pop ebx                                        |
01091543 | E8 7D 17 00 00               | call crack_.1092CC5                            |
01091548 | 8B E5                        | mov esp, ebp                                   |
0109154A | 5D                           | pop ebp                                        |
0109154B | C3                           | ret                                            |
0109154C | 6A 00                        | push 0x0                                       |
0109154E | E8 5E 36 00 00               | call <crack_.sub_1094BB1>                      |
2、通过以下代码,比较运行中延时时间,大于2毫秒,则认为被调试,程序退出,这块没明白具体原理,下断结果3毫秒,比判断多了1毫秒,不知道是怎么产生的,猜测是程序被调试响应速度慢。
[Asm] 纯文本查看 复制代码
01091575 | 57                           | push edi                                       | 获取时间
01091576 | E8 63 33 00 00               | call <crack_.sub_10948DE>                      |
0109157B | 8B 35 84 81 0A 01            | mov esi, dword ptr ds:[<&LoadStringW>]         |
01091581 | 8B F8                        | mov edi, eax                                   |
01091583 | 6A 64                        | push 0x64                                      |
01091585 | 68 20 04 0B 01               | push crack_.10B0420                            | 10B0420:L"Crack_Me_4"
0109158A | 6A 67                        | push 0x67                                      |
0109158C | 53                           | push ebx                                       |
0109158D | FF D6                        | call esi                                       |
0109158F | 6A 64                        | push 0x64                                      |
01091591 | 68 58 03 0B 01               | push crack_.10B0358                            | 10B0358:L"CRACK_ME_4"
01091596 | 6A 6D                        | push 0x6D                                      |
01091598 | 53                           | push ebx                                       |
01091599 | FF D6                        | call esi                                       |
0109159B | 8B CB                        | mov ecx, ebx                                   |
0109159D | E8 FE 00 00 00               | call <crack_.sub_10916A0>                      |
010915A2 | E8 37 33 00 00               | call <crack_.sub_10948DE>                      |
010915A7 | 2B C7                        | sub eax, edi                                   |
010915A9 | 6A 00                        | push 0x0                                       |
010915AB | 83 F8 02                     | cmp eax, 0x2                                   | 比较时间
010915AE | 0F 8F DA 00 00 00            | jg crack_.109168E                              |

中间也有几处反调试,皆为第2种方法,通过时间差来判断。

附上Crackme程序: Crack_Me.exe.rar (124.37 KB, 下载次数: 33)
IDA数据文件: Crack_Me.idb.rar (456.83 KB, 下载次数: 21)

免费评分

参与人数 18吾爱币 +20 热心值 +17 收起 理由
xiaobaixiaobai + 1 + 1 谢谢@Thanks!
ourshiningdays + 1 + 1 用心讨论,共获提升!
jimmy2752 + 1 + 1 我很赞同!
lmdme + 1 + 1 我很赞同!
liphily + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
郭雨帅哥 + 1 + 1 谢谢@Thanks!
pass101 + 1 + 1 用心讨论,共获提升!
xyuetao + 1 + 1 谢谢@Thanks!
zzzlucas + 1 + 1 谢谢@Thanks!
无痕软件 + 3 + 1 兄弟进展神速啊。
plasd + 1 + 1 谢谢@Thanks!
vLove0 + 1 + 1 用心讨论,共获提升!
hjm666 + 1 + 1 用心讨论,共获提升!
sysksy + 1 + 1 谢谢@Thanks!
liyuaism + 1 + 1 我很赞同!
独行风云 + 1 + 1 用心讨论,共获提升!
测试中…… + 1 + 1 用心讨论,共获提升!
lanlana + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

cyhcuichao 发表于 2018-6-22 22:31
学习了楼主
 楼主| pk8900 发表于 2018-6-22 10:31
hjm666 发表于 2018-6-22 09:25
刚开始学的时候有注意到看雪的这个CM,后来死在了反调试上,现在一直就没有遇到过反调试CM,想问附加程序后 ...

想找反调试,就不能进行附加,载入后查找API调用,进程检测的几个API函数是必须要跟的,比如:<kernel32.CreateToolhelp32Snapshot>快照函数,<kernel32.Process32FirstW>和<kernel32.Process32NextW>遍历函数,<kernel32.ExitProcess>和<kernel32.TerminateProcess>结束进程函数,在这个CM中引用了时间较验,可下断<kernel32.GetSystemTimeAsFileTime>,关于反调试方面太多,大多通过下断<kernel32.ExitProcess>后,进行回溯。本例中最快方法是IDA中查找MFC的Exit调用。
头像被屏蔽
yahanwangluo 发表于 2018-6-21 19:50
其实从技术角度出发来看,这个东西应该这样分析,我不会!
测试中…… 发表于 2018-6-21 21:14
学习下,比较忙都没时间搞这些东西......
chenjingyes 发表于 2018-6-22 00:52
谢谢楼主分享   
iteamo 发表于 2018-6-22 08:40
越学习发现不会的越多  怎么办
gutfreund 发表于 2018-6-22 09:13 来自手机
学习下,学海无涯
hjm666 发表于 2018-6-22 09:25
刚开始学的时候有注意到看雪的这个CM,后来死在了反调试上,现在一直就没有遇到过反调试CM,想问附加程序后,是怎么找到检测OD在运行的关键点,并过检测的啊??
连长233 发表于 2018-6-22 10:06 来自手机
谢谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 00:29

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表