吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2693|回复: 9
收起左侧

[CrackMe] 无壳无花无反调无VM,只有简单算法的CM

[复制链接]
梦游枪手 发表于 2019-8-22 18:10
CM是什么?Crackme是什么?这是什么东西?楼主发的什么?
他们都是一些公开给别人尝试破解的小程序,制作 Crackme 的人可能是程序员,想测试一下自己的软件保护技术,也可能是一位 Cracker,想挑战一下其它 Cracker 的破解实力,也可能是一些正在学习破解的人,自己编一些小程序给自己破解,KeyGenMe是要求别人做出它的 keygen (序号产生器), ReverseMe 要求别人把它的算法做出逆向分析, UnpackMe 是要求别人把它成功脱壳,本版块禁止回复非技术无关水贴。

如题,也许应该叫ReverseMe,什么都没有只有几个简单算法,也是很常见的算法,就是用了比较别扭的方法实现了。只要知道是什么算法应该就秒了。不知道能活多久。由于没怎么限制输入字符串长度,应该有多解。
成功图

失败图

爆破应该是没有用的。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 3吾爱币 +7 热心值 +3 收起 理由
wgz001 + 1 + 1 谢谢@Thanks!
CrazyNut + 6 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
hayb + 1 热心回复!

查看全部评分

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

 楼主| 梦游枪手 发表于 2019-8-28 11:37
本帖最后由 梦游枪手 于 2019-8-28 20:57 编辑

真的没人玩了。。。惨,在这层公布flag和破解思路。后面来的想练习的同学可以先不看这层


公布flag,破解思路。
flag:W6bjWfJcrU

思路
题外话:其实我不喜欢CM作者自己写破解思路的,因为作者一开始就知道程序运行机制是怎样的,没有程序源码的cracker有些时候看到这种破解思路会蒙圈。
程序丢进IDA,找到main函数,粗略分析如下
[C] 纯文本查看 复制代码
int __cdecl sub_40217D(int argc, const char **argv, const char **envp)
{
  unsigned int len; // eax
  int v4; // eax
  int output; // [esp+1Ah] [ebp-20Eh]
  __int16 v7; // [esp+1Eh] [ebp-20Ah]
  int v8; // [esp+115h] [ebp-113h]
  int Str; // [esp+119h] [ebp-10Fh]
  _BYTE v10[3]; // [esp+11Dh] [ebp-10Bh]
  int v11; // [esp+214h] [ebp-14h]
  BOOL v12; // [esp+218h] [ebp-10h]
  int v13; // [esp+21Ch] [ebp-Ch]

  sub_402340();
  Str = 0;
  v11 = 0;
  memset(
    (void *)((unsigned int)v10 & 0xFFFFFFFC),
    0,
    4 * (((unsigned int)((char *)&Str - ((unsigned int)v10 & 0xFFFFFFFC) + 255) & 0xFFFFFFFC) >> 2));
  output = 0;
  v8 = 0;
  memset(
    (void *)((unsigned int)&v7 & 0xFFFFFFFC),
    0,
    4 * (((unsigned int)((char *)&output - ((unsigned int)&v7 & 0xFFFFFFFC) + 255) & 0xFFFFFFFC) >> 2));
  v13 = 0;
  initlinklist();                               // 初始化双向链表,成员为1,5,2,7,4,3,8,0,6
  scan((int)&Str);                              // scanf %s,但是%s被加密
  if ( isalnum((char *)&Str) )                  // 判断输入是否为字母和数字组合
  {
    len = strlen((const char *)&Str);
    v13 = base64_decode((int)&Str, len, &output);// base64解码,码表可以用byte_405020转换得到
                                                // 码表为dG/oHhf418OsS+IEUtgQzu6lvbWr2LmXqwVBFyn9MeRPcjZTCiK0NkYD5p73xJAa
    v12 = run((int)&output, v13);               // 解码后的结果放到run函数里面去跑
    if ( v12 )
    {
      v4 = issuccess();                         // 判断链表成员是否为想要的结果,判断条件是链表的下一个成员大于上一个成员,且最后一个成员为0即返回成功
                                                // 也就是说,链表的最终结果为1,2,3,4,5,6,7,8,0
      printsuccesorfailed(v4);
    }
    else
    {
      printsuccesorfailed(0);
    }
  }
  else
  {
    printsuccesorfailed(0);
  }
  return 0;
}

基本上函数做的操作我都注释了,不懂的之后再问吧。
梳理下流程,输入字符串先base64解码,然后将解码结果作为run函数的参数并执行run函数,只要run函数将链表成员从1,5,2,7,4,3,8,0,6转为1,2,3,4,5,6,7,8,0即成功。
细看下run函数
[C] 纯文本查看 复制代码
BOOL __cdecl run(int a1, int a2)
{
  int op; // eax
  int i; // [esp+18h] [ebp-10h]
  signed int v5; // [esp+1Ch] [ebp-Ch]

  v5 = 0;
  for ( i = 0; i < a2; ++i )
  {
    op = *(unsigned __int8 *)(i + a1) - i - 99; // 解密输入,并作为switch case的参数
    if ( op == 4 )
    {
      if ( !move(linklisthead, 4) )
        v5 = 1;                                 // move失败则flag无效
    }
    else if ( op > 4 )
    {
      if ( op == 6 )
      {
        if ( !move(linklisthead, 6) )
          v5 = 1;
      }
      else
      {
        if ( op != 8 )
        {
LABEL_17:
          v5 = 1;
          goto LABEL_18;
        }
        if ( !move(linklisthead, 8) )
          v5 = 1;
      }
    }
    else
    {
      if ( op != 2 )
        goto LABEL_17;
      if ( !move(linklisthead, 2) )
        v5 = 1;
    }
LABEL_18:
    if ( v5 )
      return v5 == 0;
  }
  return v5 == 0;
}

该函数里可以看出,解密输入后,输入的字符串里只有{2,4,6,8},函数成功条件为move函数不能返回0。在已经知道base64的码表以及和输入字符串结构的情况,可以自行生成一串字符,OD跟踪一下观察链表顺序的变化,就知道move函数是干啥的了。这里就直接公布吧。
run函数实际上是个推箱子游戏,初始化的链表实际上是初始化了这样一个布局的推箱子。
1,5,2
7,4,3
8,0,6
0表示空地
输入的字符串解密后,2表示0向上移动,4表示向下移动,6表示向左移动,8表示向右移动。
只要将布局还原为
1,2,3
4,5,6
7,8,0
就算成功了。
为什么要用链表而不是数组?我觉得链表在内存中的分布是分散的,而在程序中可以依靠指针将它们串连在一起,而且IDA的伪代码在不知道结构体的定义时,表现并不是很好(但是只要知道了结构体的定义,效果就不一样了),这样可以迷惑破解者。在这个CM里面,要得到链表的结果,必须一次次地跟踪每个链表成员的前节点和后节点(也可以写脚本输出),增加了还原时的繁琐程度。
回到正题,还原上面的推箱子游戏的步骤为{6,2,8,2,8,4,4},再按照run函数解密的方法加密回去,得到字符串ifmholm,为了降低难度选择了加密成明文字符串。
再用上面的码表base64编码后,得到结果W6bjWfJcrU==,去掉等号就是flag了。因为main函数的isalnum已经判断了输入字符串不能有符号,而且base64字符串的等号就是填充对齐的作用,去掉也不会有影响。
至于主楼说到的多解,应该也很容易理解,因为CM没有对输入长度做很强的限制,所以可以在输入里面多加几个步骤,只要布局最后是上面那样子就行。
综上所述,我觉得这个CM的难度其实算低的,够不到中等难度的门槛。用到的算法都是常见的算法,只是做了一点改动,只要被猜出来是什么算法,CM就不攻自破了。虽然CM的链表写的是双向链表,但是基本上只使用后节点,前节点在这个小游戏几乎用不上。推箱子游戏用链表的方式重写也只是想避免被猜出来。不过玩这个CM的人太少了,并不能知道实际效果怎么样。
写完发现这字数快比得上之前发的帖子的字数了。

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
610100 + 1 + 1 膜拜
wgz001 + 1 + 1 学习了,666

查看全部评分

玖公子 发表于 2019-8-23 19:57
我别的工具都不会用,只会OD!
因此,看到这个程序,那就OD打开程序,停在这
[Asm] 纯文本查看 复制代码
00402510   .  56            push esi                                 ;  kernel32.Sleep

00402511   .  53            push ebx

00402512   .  83EC 14       sub esp,0x14

00402515   .  833D 18404000>cmp dword ptr ds:[0x404018],0x2

0040251C   .  8B4424 24     mov eax,dword ptr ss:[esp+0x24]

00402520   .  74 0A         je short crackme.0040252C

00402522   .  C705 18404000>mov dword ptr ds:[0x404018],0x2

0040252C   >  83F8 02       cmp eax,0x2

0040252F   .  74 12         je short crackme.00402543

00402531   .  83F8 01       cmp eax,0x1

00402534   .  74 3A         je short crackme.00402570

00402536   >  83C4 14       add esp,0x14

00402539   .  B8 01000000   mov eax,0x1

0040253E   .  5B            pop ebx                                  ;  crackme.00401214

0040253F   .  5E            pop esi                                  ;  crackme.00401214

00402540   .  C2 0C00       retn 0xC


我们F8单步到向下走,看到了
[Asm] 纯文本查看 复制代码
00401386   .  E8 F20D0000   call crackme.0040217D

0040138B   .  8B0D 08604000 mov ecx,dword ptr ds:[0x406008]

00401391   .  A3 0C604000   mov dword ptr ds:[0x40600C],eax

00401396   .  85C9          test ecx,ecx                             ;  msvcrt.77C04E29

00401398   .  0F84 CE000000 je crackme.0040146C

0040139E   .  8B15 04604000 mov edx,dword ptr ds:[0x406004]

004013A4   .  85D2          test edx,edx

004013A6   .  75 0A         jnz short crackme.004013B2

004013A8   .  E8 0F1F0000   call <jmp.&msvcrt._cexit>                ; [msvcrt._cexit

004013AD   .  A1 0C604000   mov eax,dword ptr ds:[0x40600C]

004013B2   >  8D65 F0       lea esp,dword ptr ss:[ebp-0x10]

004013B5   .  59            pop ecx                                  ;  msvcrt.77C04E29

004013B6   .  5B            pop ebx

004013B7   .  5E            pop esi

004013B8   .  5F            pop edi

004013B9   .  5D            pop ebp

004013BA   .  8D61 FC       lea esp,dword ptr ds:[ecx-0x4]

004013BD   .  C3            retn


下面是一个exit,百度一下,就是退出程序的意思!
00401386这里如果F8,程序就跑起来了,我们F7跟进去,继续F8单步走
[Asm] 纯文本查看 复制代码
00402201  |.  E8 41FFFFFF   call crackme.00402147

00402206  |.  8D8424 190100>lea eax,dword ptr ss:[esp+0x119]

0040220D  |.  890424        mov dword ptr ss:[esp],eax


00402201在这里为我们回车进去能看到一个scanf函数,获取输入的数据
这里我们F8,程序跑飞,我们随便输入一个字符串:jiugongzi

程序此时还没跑飞,说明我们的思路是对的,我们继续F8向下走,来到了
[Asm] 纯文本查看 复制代码
00402298  |.  E8 7BFBFFFF   call crackme.00401E18

0040229D  |>  B8 00000000   mov eax,0x0

004022A2  |.  8D65 F8       lea esp,[local.2]

004022A5  |.  5B            pop ebx

004022A6  |.  5F            pop edi

004022A7  |.  5D            pop ebp

004022A8  \.  C3            retn


00402298这里如果我们F8,就会发现程序窗口已经出现错误信息(wrong flag!)了
因此,这个call我们要F7进去,看看它到底干了什么!
接着我们继续F8向下走,来到了
[Asm] 纯文本查看 复制代码
00401E5C  |.  E8 23FEFFFF   call crackme.00401C84

00401E61  |.  8945 F4       mov [local.3],eax

00401E64  |>  8B45 F4       mov eax,[local.3]                        ; ||

00401E67  |.  890424        mov dword ptr ss:[esp],eax               ; ||

00401E6A  |.  E8 F5130000   call <jmp.&msvcrt.puts>                  ; |\puts

00401E6F  |.  8B45 F4       mov eax,[local.3]                        ; |

00401E72  |.  890424        mov dword ptr ss:[esp],eax               ; |

00401E75  |.  E8 0A140000   call <jmp.&msvcrt.free>                  ; \free

00401E7A  |.  90            nop

00401E7B  |.  C9            leave

00401E7C  \.  C3            retn


我们发现下面那个retn就返回了,而返回后程序就直接输出错误信息了,puts这个函数是写入
字符串,free是释放内存。那么这个puts写入的内容肯定就是上面那个call里面干的!
所以00401E5C这个call我们F7进去
[Asm] 纯文本查看 复制代码
00401C84  /$  55            push ebp

00401C85  |.  89E5          mov ebp,esp

00401C87  |.  53            push ebx

00401C88  |.  83EC 34       sub esp,0x34

00401C8B  |.  C745 DB 00000>mov dword ptr ss:[ebp-0x25],0x0          ; ||

00401C92  |.  C745 DF 00000>mov dword ptr ss:[ebp-0x21],0x0          ; ||

00401C99  |.  C645 E3 00    mov byte ptr ss:[ebp-0x1D],0x0           ; ||

00401C9D  |.  8B45 0C       mov eax,[arg.2]                          ; ||

00401CA0  |.  890424        mov dword ptr ss:[esp],eax               ; ||

00401CA3  |.  E8 CC150000   call <jmp.&msvcrt.malloc>                ; |\malloc

00401CA8  |.  8945 E4       mov [local.7],eax                        ; |

00401CAB  |.  8B45 0C       mov eax,[arg.2]                          ; |

00401CAE  |.  894424 08     mov dword ptr ss:[esp+0x8],eax           ; |

00401CB2  |.  8B45 08       mov eax,[arg.1]                          ; |crackme.00405000

00401CB5  |.  894424 04     mov dword ptr ss:[esp+0x4],eax           ; |

00401CB9  |.  8B45 E4       mov eax,[local.7]                        ; |

00401CBC  |.  890424        mov dword ptr ss:[esp],eax               ; |

00401CBF  |.  E8 A8150000   call <jmp.&msvcrt.memcpy>                ; \memcpy

00401CC4  |.  C745 F4 00000>mov [local.3],0x0

00401CCB  |.  EB 27         jmp short crackme.00401CF4

00401CCD  |>  A1 E0634000   /mov eax,dword ptr ds:[0x4063E0]         ;  88?

00401CD2  |.  8B55 F4       |mov edx,[local.3]

00401CD5  |.  895424 04     |mov dword ptr ss:[esp+0x4],edx

00401CD9  |.  890424        |mov dword ptr ss:[esp],eax

00401CDC  |.  E8 7DF8FFFF   |call crackme.0040155E

00401CE1  |.  8B00          |mov eax,dword ptr ds:[eax]

00401CE3  |.  8D50 61       |lea edx,dword ptr ds:[eax+0x61]

00401CE6  |.  8D4D DB       |lea ecx,dword ptr ss:[ebp-0x25]

00401CE9  |.  8B45 F4       |mov eax,[local.3]

00401CEC  |.  01C8          |add eax,ecx

00401CEE  |.  8810          |mov byte ptr ds:[eax],dl

00401CF0  |.  8345 F4 01    |add [local.3],0x1

00401CF4  |>  837D F4 08     cmp [local.3],0x8

00401CF8  |.^ 7E D3         \jle short crackme.00401CCD

00401CFA  |.  837D 10 00    cmp [arg.3],0x0

00401CFE  |.  74 3B         je short crackme.00401D3B

00401D00  |.  C745 F0 00000>mov [local.4],0x0

00401D07  |.  EB 28         jmp short crackme.00401D31

00401D09  |>  8B55 F0       /mov edx,[local.4]

00401D0C  |.  8B45 E4       |mov eax,[local.7]

00401D0F  |.  01D0          |add eax,edx

00401D11  |.  0FB618        |movzx ebx,byte ptr ds:[eax]

00401D14  |.  8D55 DB       |lea edx,dword ptr ss:[ebp-0x25]

00401D17  |.  8B45 F0       |mov eax,[local.4]

00401D1A  |.  01D0          |add eax,edx

00401D1C  |.  0FB608        |movzx ecx,byte ptr ds:[eax]

00401D1F  |.  8B55 F0       |mov edx,[local.4]

00401D22  |.  8B45 E4       |mov eax,[local.7]

00401D25  |.  01D0          |add eax,edx

00401D27  |.  31CB          |xor ebx,ecx

00401D29  |.  89DA          |mov edx,ebx

00401D2B  |.  8810          |mov byte ptr ds:[eax],dl

00401D2D  |.  8345 F0 01    |add [local.4],0x1

00401D31  |>  8B45 F0        mov eax,[local.4]

00401D34  |.  3B45 0C       |cmp eax,[arg.2]

00401D37  |.^ 7C D0         \jl short crackme.00401D09

00401D39  |.  EB 4C         jmp short crackme.00401D87

00401D3B  |>  C745 EC 00000>mov [local.5],0x0

00401D42  |.  EB 3B         jmp short crackme.00401D7F

00401D44  |>  C745 E8 00000>/mov [local.6],0x0

00401D4B  |.  EB 28         |jmp short crackme.00401D75

00401D4D  |>  8B55 EC       |/mov edx,[local.5]

00401D50  |.  8B45 E4       ||mov eax,[local.7]

00401D53  |.  01D0          ||add eax,edx

00401D55  |.  0FB618        ||movzx ebx,byte ptr ds:[eax]

00401D58  |.  8D55 DB       ||lea edx,dword ptr ss:[ebp-0x25]

00401D5B  |.  8B45 E8       ||mov eax,[local.6]

00401D5E  |.  01D0          ||add eax,edx

00401D60  |.  0FB608        ||movzx ecx,byte ptr ds:[eax]

00401D63  |.  8B55 EC       ||mov edx,[local.5]

00401D66  |.  8B45 E4       ||mov eax,[local.7]

00401D69  |.  01D0          ||add eax,edx

00401D6B  |.  31CB          ||xor ebx,ecx

00401D6D  |.  89DA          ||mov edx,ebx

00401D6F  |.  8810          ||mov byte ptr ds:[eax],dl

00401D71  |.  8345 E8 01    ||add [local.6],0x1

00401D75  |>  837D E8 08    | cmp [local.6],0x8

00401D79  |.^ 7E D2         |\jle short crackme.00401D4D

00401D7B  |.  8345 EC 01    |add [local.5],0x1

00401D7F  |>  8B45 EC        mov eax,[local.5]

00401D82  |.  3B45 0C       |cmp eax,[arg.2]

00401D85  |.^ 7C BD         \jl short crackme.00401D44

00401D87  |>  8B45 E4       mov eax,[local.7]

00401D8A  |.  83C4 34       add esp,0x34

00401D8D  |.  5B            pop ebx                                  ;  003F3838

00401D8E  |.  5D            pop ebp                                  ;  003F3838

00401D8F  \.  C3            retn


这里面到底是干了啥能让程序输出错误信息(wrong flag!)
仔细观察堆栈窗口却只发现了
堆栈地址=0022FCA3, (ASCII "bfchediag 9?")
edx=00000000  
猜测bfchediag这是正确的字符串?也不对啊!

实在是想不出来在哪做的比较!
只会用OD,强行装X分析一波,望各位看客不要见笑。(虽然啥也没分析出来)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x
weikun444 发表于 2019-8-22 21:15
无奈不懂算法


int sub_40217D()
{
  unsigned int v0; // eax
  int v1; // eax
  int v3; // [esp+1Ah] [ebp-20Eh]
  __int16 v4; // [esp+1Eh] [ebp-20Ah]
  int v5; // [esp+115h] [ebp-113h]
  int Str; // [esp+119h] [ebp-10Fh]
  _BYTE v7[3]; // [esp+11Dh] [ebp-10Bh]
  int v8; // [esp+214h] [ebp-14h]
  int v9; // [esp+218h] [ebp-10h]
  int v10; // [esp+21Ch] [ebp-Ch]

  sub_402340();
  Str = 0;
  v8 = 0;
  memset(
    (void *)((unsigned int)v7 & 0xFFFFFFFC),
    0,
    4 * (((unsigned int)((char *)&Str - ((unsigned int)v7 & 0xFFFFFFFC) + 255) & 0xFFFFFFFC) >> 2));
  v3 = 0;
  v5 = 0;
  memset(
    (void *)((unsigned int)&v4 & 0xFFFFFFFC),
    0,
    4 * (((unsigned int)((char *)&v3 - ((unsigned int)&v4 & 0xFFFFFFFC) + 255) & 0xFFFFFFFC) >> 2));
  v10 = 0;
  sub_401B66();
  sub_402147(&Str);
  if ( sub_402097((char *)&Str) )
  {
    v0 = strlen((const char *)&Str);
    v10 = sub_401F8D((int)&Str, v0, &v3);
    v9 = sub_401E7D(&v3, v10);
    if ( v9 )
    {
      v1 = sub_401D90();
      sub_401E18(v1);  //好像是显示成功或失败
    }
    else
    {
      sub_401E18(0);  //这里是失败
    }
  }
  else
  {
    sub_401E18(0);  //这里也是失败
  }
  return 0;
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 1吾爱币 +3 热心值 +1 收起 理由
liphily + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

 楼主| 梦游枪手 发表于 2019-8-22 22:12

只F5这里是不够的,深入分析才能找到答案,好好加油吧。
 楼主| 梦游枪手 发表于 2019-8-24 11:04
玖公子 发表于 2019-8-23 19:57
我别的工具都不会用,只会OD!
因此,看到这个程序,那就OD打开程序,停在这
[mw_shl_code=asm,true]0040 ...

你进去00401E5C算是对了一点,在这里flag正确就会输出“awesome!”,强行爆破的话会因为flag不正确输出乱码,所以真正的比较不是在这里,比较处会对结果做判断,当然是一个写得很别扭的判断,至于我比较的是什么,请加油。
 楼主| 梦游枪手 发表于 2019-8-24 11:12
不过算法类的CM玩的人是真的少,有没有大佬来玩一下
wgz001 发表于 2019-8-29 12:50
base64我也没看出来,更找不到main函数
 楼主| 梦游枪手 发表于 2019-8-29 13:38
wgz001 发表于 2019-8-29 12:50
base64我也没看出来,更找不到main函数

main函数的话只要找到scanf,回溯一下堆栈就行。base64可以看那个函数输出结果的长度,能看出标准的“4转3”(base64的4个字符转为明文的3个字符),只要有这种特征的基本可以定为base64,剩下的就是找到码表了。

免费评分

参与人数 1热心值 +1 收起 理由
wgz001 + 1 谢谢@Thanks!

查看全部评分

wgz001 发表于 2019-8-31 14:54
建议感兴趣的同学玩一下,应该可以学到不少东西
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止回复与主题无关非技术内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-4-26 17:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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