Sweettea 发表于 2020-7-5 11:15

C写的CM一枚,XP可跑

本帖最后由 Sweettea 于 2021-4-26 19:44 编辑

CM一枚

控制台程序
只有唯一正确解
输入:长度16以内的字符串
输出:成功输出true,失败输出false

无壳,
无暗桩,
无反调试,
有混淆,
有花指令

支持系统:XP - WIN10

破解要求:
爆破,追码,还原算法均可,看各位大佬玩得开心就好






==============以下2020年7月7日 00:54:17编辑======================

感谢大佬们的热心分析!
CM算法是个修改了的base64

此CM经过两次混淆:
第一次是基于C源代码的混淆,控制流平坦,无膨胀和花指令
第二次是编译链接阶段基于汇编代码的混淆,对代码进行了随机膨胀,加入了代码片段之间的随机跳转和花指令


贴上第二次膨胀混淆前的CM可执行文件.




关键函数功能:
sub_4010D0:
unsigned int __stdcall MyStrlen1(const char *string);
递归实现的strlen

sub_4012B0:
char __stdcall Dec1(char value);
单字节解密函数,用于解密base64表和要输出的"true"&"false"字符串

sub_4013A0:
void __stdcall KC_base64_encode(char *pIn, char *pOut);
修改的base64加密函数

sub_402010:
void *__stdcall DecMemcpy(void *dest, const void *src, unsigned int count);
调用了sub_4012B0的memcpy函数

sub_402240:
void __stdcall DoCmp(char *szInput, int (__cdecl *pfnputs)(const char *));
核心验证函数入口


@solly逆向找出的爆破点2:
00404520   85D2                      test edx,edx                      ; test edx,edx
在第二次膨胀混淆前的文件中对应的地址是:
loc_402683:
movzx   edx,
test    edx, edx
jz      short loc_402698




附上第二次混淆等价替换的模版文件:






solly 发表于 2020-7-9 09:10

解码结果:

solly 发表于 2020-7-6 02:05

本帖最后由 solly 于 2020-7-6 02:08 编辑

暴破位置,以下地址改成 xor edx, edx 即可:
00401A17      33D2               xor edx,edx
就是把变换后的输入码与"oPRofQ6uigRhpmwHcYQt"比较时,强行将每个字符比较结果变成相等。

小菜鸟一枚 发表于 2020-7-5 12:35

本帖最后由 小菜鸟一枚 于 2020-7-5 12:39 编辑

跑不出来,OD CTRL+A也不太好用,这就是混淆吗?一下子选中三行代码,请问楼主这是c/c加加写的吗,能不能请教一下这个混淆和花指令该怎么做,或者给个教程吧,谢谢!
int sub_401020()
{
__int128 v1; // @1
__int64 v2; // @1
int v3; // @1

v3 = 0;
v1 = 0i64;
_mm_storel_epi64((__m128i *)&v2, 0i64);
sub_401070((int)"Input:", 0);
sub_4010A0("%16s", &v1);
sub_40180B(&v1, sub_41A35B);
return 0;
}

signed int __cdecl sub_41A35B(const char *a1)
{
signed int result; // eax@2
int v2; // eax@3
signed int v3; // eax@4
signed int v4; // edx@4
_BYTE *v5; // esi@4
_BYTE *v6; // eax@6
int *v7; // @14
const char **v8; // @14
int *v9; // @14
unsigned int v10; // @14
int v11; // @3

if ( a1 )
{
    v2 = sub_41A1FC(1);
    v11 = v2;
    if ( (*(_DWORD *)(v2 + 12) >> 12) & 1 )
      goto LABEL_17;
    v3 = sub_42295B(v2);
    v4 = v3;
    v5 = &unk_4350F8;
    if ( v3 == -1 || v3 == -2 )
      v6 = &unk_4350F8;
    else
      v6 = (_BYTE *)(dword_435F18 + 48 * (v3 & 0x3F));
    if ( v6 )
      goto LABEL_18;
    if ( v4 != -1 && v4 != -2 )
      v5 = (_BYTE *)(dword_435F18 + 48 * (v4 & 0x3F));
    if ( v5 & 1 )
    {
LABEL_18:
      *(_DWORD *)sub_421F2E() = 22;
      sub_421E72();
      result = -1;
    }
    else
    {
LABEL_17:
      v10 = strlen(a1);
      v7 = &v11;
      v8 = &a1;
      v9 = (int *)&v10;
      result = sub_41A2D1(v11, &v7);
    }
}
else
{
    *(_DWORD *)sub_421F2E() = 22;
    sub_421E72();
    result = -1;
}
return result;
}

函数里面调函数,学习了,里面的那个函数有一次参数*a1,用OD跑了下参数也是输入的字符串,里面那个函数两个地方返回-1,应该是错误跳转,LANLE 17可能是正确的流程,然后跟丢了,没什么好的思路:'(weeqw

OD层层回溯,也只能看到在哪里把false入栈的
004274C0   .F64408 28 01test byte ptr ds:,0x1
004274C5   .75 15         jnz short CrackMe.004274DC
004274C7   .E8 62AAFFFF   call CrackMe.00421F2E
004274CC   .C700 09000000 mov dword ptr ds:,0x9
004274D2   .E8 44AAFFFF   call CrackMe.00421F1B
004274D7   .8320 00       and dword ptr ds:,0x0
004274DA   .EB 14         jmp short CrackMe.004274F0
004274DC   >FF75 10       push dword ptr ss:    ;这是字符串false\r\n长度,为7
004274DF   .FF75 0C       push dword ptr ss:    ;这就是字符串false\r\n


解不开了

Sweettea 发表于 2020-7-5 13:05

本帖最后由 Sweettea 于 2020-7-5 13:08 编辑

小菜鸟一枚 发表于 2020-7-5 12:35
跑不出来,OD CTRL+A也不太好用,这就是混淆吗?一下子选中三行代码,请问楼主这是c/c加加写的吗,能不能请 ...
sub_41A35B是c库函数puts,

sub_40180B(&v1, sub_41A35B);
这是将puts函数指针从参数传递进sub_40180B里面调用

咋和c库函数刚上了呢{:1_924:}

小菜鸟一枚 发表于 2020-7-5 13:27

Sweettea 发表于 2020-7-5 13:05
sub_41A35B是c库函数puts,

sub_40180B(&v1, sub_41A35B);


00401029|.C745 FC 00000>mov ,0x0
00401030|.68 60E14200   push CrackMe.0042E160                  ;ASCII "Input:"
00401035|.0F1145 E4   movups dqword ptr ss:,xmm0
00401039|.660fd645 f4   movq qword ptr ss:,xmm0
0040103E|.E8 2D000000   call CrackMe.00401070
00401043|.8D45 E4       lea eax,
00401046|.50            push eax
00401047|.68 68E14200   push CrackMe.0042E168                  ;ASCII "%16s"
0040104C|.E8 4F000000   call CrackMe.004010A0
00401051|.83C4 0C       add esp,0xC
00401054|.8D45 E4       lea eax,
00401057|.68 5BA34100   push CrackMe.0041A35B
0040105C|.50            push eax
0040105D|.E8 A9070000   call CrackMe.0040180B



好吧,那我分析不出来了,我怎么调试都觉得0040104C 这个call是最大获取16个字符串,接下来只剩下sub_40180B这个call有问题,觉得只能分析它了

风扫春残雪 发表于 2020-7-5 14:45

花指令和各种跳转看得头疼。。。。感觉得写脚本去一遍,或者静态模拟真实指令的东西才能把sub_40180B搞清楚

印象比较深的大概有
1.
mov reg,addr
leareg,
jmp reg

2.
call offset
offset:
add/sub , idata
retn

好奇楼主是怎么写出来的……是用宏指令完成的吗?求分享,非常好奇
类似的东西在壳里见过。。。不过他们的实现方式基本都是自己调一个反汇编引擎,然后直接代码膨胀/等价混淆再塞进去这样

Sweettea 发表于 2020-7-5 16:36

风扫春残雪 发表于 2020-7-5 14:45
花指令和各种跳转看得头疼。。。。感觉得写脚本去一遍,或者静态模拟真实指令的东西才能把sub_40180B搞清楚 ...

提供下比较简单的思路:
vs编译选项里设置生成.asm文件,编译
对生成的.asm文件进行逐行解析,等价替换
把一个函数内的汇编代码分成n个小片段
每个代码片段之间随机加入跳转代码
每个代码片段随机打乱顺序
ml.exe编译.asm文件
link.exe链接obj

风扫春残雪 发表于 2020-7-5 19:31

头晕。。。试了很多种方法,最后发现还是得还原回来。。。还是老老实实从下到上分析吧

5#的乱序是最简单的几个,拼好了这些以后还要把一些辣鸡代码给收缩回来。感觉是个大工程……先把乱序拼回来了,吃饭去了
记录几个比较常见的模式(目测至少有十几种模式)

412D36   40834d : mov dword ptr ss:,edx
412D36   408351 : lea esp,dword ptr ss:
412D36   408473 : lea esp,dword ptr ss:
412D36   408477 : mov edx,dword ptr ss:


412D36   40847b : mov ecx,0x71F870
412D36   408480 : sub ecx,0x231ECD



412D36   4084dd : mov dword ptr ss:,ebx
412D36   4084e1 : sub esp,0x4
412D36   4082f9 : pop ebx



412D36   408305 : mov dword ptr ss:,edx
412D36   408309 : sub esp,0x4
412D36   4086b0 : pop edx

Sweettea 发表于 2020-7-5 19:40

风扫春残雪 发表于 2020-7-5 19:31
头晕。。。试了很多种方法,最后发现还是得还原回来。。。还是老老实实从下到上分析吧

5#的乱序是最简单 ...分析得不错。
代码经过膨胀,要还原回来的确是个大工程。膨胀方式大约有个二三十种。
调试观察找出规律,找到爆破点感觉差不多就OK了。要还原算法,个人感觉是有点难了。要追码的话,找到关键点后,密码学数学大佬应该可以推出正确注册码。

solly 发表于 2020-7-6 12:38

本帖最后由 solly 于 2020-7-6 17:59 编辑

暴破2:
00404520   85D2                      test edx,edx                      ; test edx,edx
00404522   0F84 69040000    je CrackMe.00404991

把 je 指令 NOP 掉,这里是字符串比较结果的检查,跳转则显示 false,不跳转则显示 true。
页: [1] 2
查看完整版本: C写的CM一枚,XP可跑