首页
社区
课程
招聘
[原创]新手逆向一个CM
发表于: 2018-8-28 17:43 3662

[原创]新手逆向一个CM

2018-8-28 17:43
3662

聊天群里发的一个CM,感觉很适合我的水平,每次都感觉就要弄出来了,就是做不对,来来回回磨了几天终于做出来了。现在总结起来,跟大家分享一下。

我的水平是什么名词都听过,但是OD的单步调试是哪个键都要百度查这种水平....

所以我就尽量写详细一点,按照时间顺序把我踩过的坑都写一下,避免大家再次上当受骗。

首先拿到这个CM我是丢进了win7 32位虚拟机里用OD跑。

先找到关键代码位置,我百度了可以通过字符串查找 消息断点 函数断点之类的,我都试了试,

最后是对messagebox下断点找到了代码区域。

00401103  /$  55            push ebp                                 ;  从这里开始验证注册码

00401104  |.  8BEC          mov ebp,esp

00401106  |.  83C4 FC       add esp,-4

00401109  |.  53            push ebx

0040110A  |.  56            push esi

0040110B  |.  57            push edi

0040110C  |.  FF75 08       push [arg.1]                             ; /s

0040110F  |.  FF15 20304000 call dword ptr ds:[<&msvcrt.strlen>]     ; \先计算注册码的长度,结果存储在eax里

00401115  |.  83C4 04       add esp,4

00401118  |.  83F8 2A       cmp eax,2A                               ;  把结果和2A比较,如果不对的话就弹出对话框think again.意思是注册码应该是42位的

0040111B  |.  74 17         je short give_a_t.00401134

0040111D  |.  6A 00         push 0                                   ; /Style = MB_OK|MB_APPLMODAL

0040111F  |.  6A 00         push 0                                   ; |Title = NULL

00401121  |.  68 00404000   push give_a_t.00404000                   ; |Text = "Think again!"

00401126  |.  6A 00         push 0                                   ; |hOwner = NULL

00401128  |.  E8 BB010000   call <jmp.&user32.MessageBoxA>           ; \MessageBoxA

0040112D  |.  5F            pop edi

0040112E  |.  5E            pop esi

0040112F  |.  5B            pop ebx

00401130  |.  C9            leave

00401131  |.  C2 0400       retn 4

00401134  |>  33FF          xor edi,edi

00401136  |.  8B75 08       mov esi,[arg.1]                          ;  注册码放进esi里

00401139  |.  AC            lods byte ptr ds:[esi]                   ;  取注册码第一位放到eax里,同时esi+1

0040113A  |.  EB 06         jmp short give_a_t.00401142

0040113C  |>  0FB6C0        /movzx eax,al

0040113F  |.  03F8          |add edi,eax

00401141  |.  AC            |lods byte ptr ds:[esi]

00401142  |>  0AC0           or al,al                                ;  判断eax是不是空

00401144  |.^ 75 F6         \jnz short give_a_t.0040113C             ;  上面这点代码整个意思就是循环累加43位的注册码,并把结果赋给edi

00401146  |.  333D 6C404000 xor edi,dword ptr ds:[40406C]            ;  40406c是个数据段的值,所以就是edi和[40406c]做异或运算

0040114C  |.  57            push edi                                 ; /edi 作为随机数种子

0040114D  |.  FF15 24304000 call dword ptr ds:[<&msvcrt.srand>]      ; \调用srand

00401153  |.  83C4 04       add esp,4

00401156  |.  33DB          xor ebx,ebx                              ;  ebx清零

00401158  |.  8B75 08       mov esi,[arg.1]                          ;  esi重新获得注册码

0040115B  |.  8D3D B4304000 lea edi,dword ptr ds:[4030B4]            ;  edi存放地址4030B4,这个地址的值是固定的

00401161  |.  E9 83000000   jmp give_a_t.004011E9

00401166  |>  FF15 28304000 /call dword ptr ds:[<&msvcrt.rand>]      ; [rand

0040116C  |.  0FB60C33      |movzx ecx,byte ptr ds:[ebx+esi]         ;  循环取出注册码放到ecx里

00401170  |.  F7E1          |mul ecx                                 ;  随机数*注册码里的一位(循环取),结果存在eax里

00401172  |.  B9 2166C9FA   |mov ecx,FAC96621

00401177  |.  50            |push eax                                ;  保存eax

00401178  |.  33D2          |xor edx,edx

0040117A  |.  F7F1          |div ecx                                 ;  eax/ecx  ,余数放在edx中,商放在eax中

0040117C  |.  58            |pop eax

0040117D  |.  52            |push edx                                ;  保存了第一次的余数

0040117E  |.  F7E0          |mul eax                                 ;  eax=eax*eax

00401180  |.  F7F1          |div ecx

00401182  |.  8BC2          |mov eax,edx                             ;  eax=余数

00401184  |.  F7E2          |mul edx                                 ;  eax=余数*余数

00401186  |.  F7F1          |div ecx                                 ;  这里ecx就是固定的值,一直没变

00401188  |.  8BC2          |mov eax,edx                             ;  再除一遍,余数赋给eax

0040118A  |.  F7E2          |mul edx                                 ;  eax=余数*余数

0040118C  |.  F7F1          |div ecx                                 ;  再除一遍

0040118E  |.  8BC2          |mov eax,edx

00401190  |.  F7E2          |mul edx                                 ;  eax=余数*余数

00401192  |.  F7F1          |div ecx                                 ;  后面重复这个过程

00401194  |.  8BC2          |mov eax,edx

00401196  |.  F7E2          |mul edx

00401198  |.  F7F1          |div ecx

0040119A  |.  8BC2          |mov eax,edx

0040119C  |.  F7E2          |mul edx

0040119E  |.  F7F1          |div ecx

004011A0  |.  8BC2          |mov eax,edx

004011A2  |.  F7E2          |mul edx

004011A4  |.  F7F1          |div ecx

004011A6  |.  8BC2          |mov eax,edx

004011A8  |.  F7E2          |mul edx

004011AA  |.  F7F1          |div ecx

004011AC  |.  8BC2          |mov eax,edx

004011AE  |.  F7E2          |mul edx

004011B0  |.  F7F1          |div ecx

004011B2  |.  8BC2          |mov eax,edx

004011B4  |.  F7E2          |mul edx

004011B6  |.  F7F1          |div ecx

004011B8  |.  8BC2          |mov eax,edx

004011BA  |.  F7E2          |mul edx

004011BC  |.  F7F1          |div ecx

004011BE  |.  8BC2          |mov eax,edx

004011C0  |.  F7E2          |mul edx

004011C2  |.  F7F1          |div ecx

004011C4  |.  8BC2          |mov eax,edx

004011C6  |.  F7E2          |mul edx

004011C8  |.  F7F1          |div ecx

004011CA  |.  8BC2          |mov eax,edx

004011CC  |.  F7E2          |mul edx

004011CE  |.  F7F1          |div ecx

004011D0  |.  8BC2          |mov eax,edx

004011D2  |.  F7E2          |mul edx

004011D4  |.  F7F1          |div ecx

004011D6  |.  8BC2          |mov eax,edx

004011D8  |.  F7E2          |mul edx

004011DA  |.  F7F1          |div ecx

004011DC  |.  8BC2          |mov eax,edx

004011DE  |.  5A            |pop edx                                 ;  edx=第一次的余数

004011DF  |.  F7E2          |mul edx                                 ;  乘上最后一次的余数

004011E1  |.  F7F1          |div ecx                                 ;  再求一次余数

004011E3  |.  3B149F        |cmp edx,dword ptr ds:[edi+ebx*4]        ;  最后和4030B4这里的数据作比较,如果不等就比较是不是验证到了

004011E6  |.  75 0A         |jnz short give_a_t.004011F2             ;  最后一位,是的话就成功,不是的话就失败。

004011E8  |.  43            |inc ebx                                 ;  如果验证的数据相等的话,就继续验证下一个数据。

004011E9  |>  83FB 2A        cmp ebx,2A

004011EC  |.^ 0F85 74FFFFFF \jnz give_a_t.00401166                   ;  验证42个数据

004011F2  |>  83FB 2A       cmp ebx,2A

004011F5  |.  73 12         jnb short give_a_t.00401209

004011F7  |.  6A 00         push 0                                   ; /Style = MB_OK|MB_APPLMODAL

004011F9  |.  6A 00         push 0                                   ; |Title = NULL

004011FB  |.  68 0D404000   push give_a_t.0040400D                   ; |Text = "Incorrect!"

00401200  |.  6A 00         push 0                                   ; |hOwner = NULL

00401202  |.  E8 E1000000   call <jmp.&user32.MessageBoxA>           ; \MessageBoxA

00401207  |.  EB 13         jmp short give_a_t.0040121C

00401209  |>  6A 00         push 0                                   ; /Style = MB_OK|MB_APPLMODAL

0040120B  |.  68 21404000   push give_a_t.00404021                   ; |Title = "Congrats"

00401210  |.  68 18404000   push give_a_t.00404018                   ; |Text = "Correct!"

00401215  |.  6A 00         push 0                                   ; |hOwner = NULL

00401217  |.  E8 CC000000   call <jmp.&user32.MessageBoxA>           ; \MessageBoxA


这里我还傻乎乎的写满了注释,其实用IDA F5一下比这可清晰明了多了。


这里的逻辑基本就看明白了,

///****************************************/

先判断注册码是不是42位

然后用42位注册码求和,与40406c这个地址的数做异或运算,作为随机数的种子

For(42)

{

注册码的每一位*随机数;

求余数

求平方

求余数

秋平方

...

最后和4030b4地址的数比较一下看看对不对

}

//////*************************************************/

这里要特别说明0x40406c的值,因为我每次打开OD载入这个程序都是0x3133335d,所以我以为这是一个固定的值。至于他怎么来的我也没关心。

然后我天真的以为这就完了呢。当时想的找个用户名,然后按这个逻辑算一遍,得到注册码,美滋滋。

后来转念一想,这程序也没有输入用户名的地方啊,只让输入注册码,而且计算完的东西已经写好固定了,就在0x4030B4 这个地方。合着我要把这个结果逆回去啊~~~

这个一直是求余数,对原来的数肯定有损失,相当于多对一的关系,肯定是不可逆的,那就只能是爆破了。

然后就是想想怎么爆破,穷举42位的asc码是不可能,算到明年也算不出来。

那么我只能先去吃顿饭,吃完饭之后果然想出来办法了。

可以先把v6求出来,从0~0xffffffff一个一个试,这个估计用不了多长时间

求出v6之后只要满足   注册码*rand=v6 就可以了。

注册码是不可能知道的,那么只能从随机值来考虑了,看看随机值是不是可以确定。

因为C语言的随机是一种伪随机,只要种子是确定的,那么随机值就是确定 。

而种子是42位asc码的和,因为asc码最大128,那么42位的和也不会超过6000,这个数还是很小的那就肯定可以爆破了。

第一步,求出v6

第二步,求出正确的随机数(其实就是爆破出42位注册码的和)

第三步,相除得到注册码


然后我就把爆破代码写好了,可以看得出,跟IDA里的很像。没错,我就是无脑复制粘贴的。

只要把最后的比较改成一个switch去比较42个就好了。

这里我把结果写入文件了。


这是我得到的部分数据。

接下来就是再写一个爆破注册码的和的程序,也很简单。


验算部分的代码我给弄丢了,我就不贴了,就是循环6000遍,算出来注册码

然后去比较注册码的和,一样了就说明找对了。

然而,这最关键,最激动人心的一步,竟然没有得到结果!!

之后我做了很多无用的尝试,比如说我觉得可能是因为除法不准的问题,所以我只去确定所有的注册码都在1~128这个范围就行了,暗喜一下,结果还是没有结果。

-----------------------------------华丽的分割线---------------------------------------------------------

注册码*rand=v6

注册码是我要求的,不用考虑。

如果有问题,那要么是rand 的问题,要么是v6的问题。

V6可是我无脑粘贴IDA得到的,不可能有问题,而且经过那么复杂的运算还能正好得到一个值跟他对应,所以v6肯定没问题,一定是rand的问题。

Rand是通过注册码的和 跟40406c的值异或的种子得来的。注册码的和肯定是在0~6000不可能错,那么一定就是40406c这个值错了。

之后下班回家了继续干,由于家里没装虚拟机,我就直接在本机上调试了,环境是win10 64位。

按照之前相同的方法下断点,结果断点没断下来,程序竟然直接退出了!!!

我仅有的一点知识面告诉我,这是反调试!但是我对反调试的认识仅限于这个词而已。我一个OD都不知道单步调试是哪个快捷键的人,哪会搞什么反调试~~

但是我就在想啊,既然刚载入的时候回断下来,之后下断点又运行不到,那么一定会有一个地运行完了就退出吧,那么我就要找到这个地方,看看到底是怎么做到的让我不能下断点。

然后我就从程序一开始一路高歌F7+F8,结果没一会儿程序就退了,也没运行多少代码,经过反复几次测试,找到了运行完就退出的位置


就是这个函数,运行完就退了,难怪下断点都断不下来。然后我又百度了这个函数看看是什么作用。果然是一个反调试函数,会除去所有附加的调试器。像我这么粗暴的人,看见这种直接就nop掉了。之后果然是能下断点了,开开心心继续调试。

之后更诡异的事情发生了,等运行到验证注册码的地方,我看了下40406c的值,竟然是0!

有了刚才这个反调试的经验,我很自然的想到应该还有其他的反调试,他虽然没有直接让程序退出,但是可能根本有没有被调试,更改了程序流程。

然后我又开始从头开始调试,nop掉ntsetinformationthread,继续f8。插句话,这里的代码很奇怪,就跟对不齐一样,每次运行到那里才能显示出正常代码,好在这里的代码少,我一句一句跟也能看,要是代码多了话真就看不懂了,还请大佬指教一下怎么解决这个问题。

之后遇到了


想都不想,肯定有问题,直接百度一下。这果然也是一个反调试函数,如果有调试器的话,会给第三个参数就是40406c赋值-1


这里果然是跟我想的差不多,比较了一下这个值,然后多了一个赋值


后面把edi的值赋给了[404036],应该是一个回调函数的地址。

之后会运行到[404036]。

往404036的值 下断点(不是404036是它的值)继续跟。

然后又遇到了这个


想都不想直接nop啊!

......

后来反反复复看了好多遍我才发现这个好像是个坑,因为他有两个参数是40406c,最终的值不是有没有被调试,而是返回值的字节数,也就是4.

所以这个虽然是一个反调试函数,但是nop掉的话就错了!这个是我反反复复很多遍才发现的~~

如果把这个函数nop掉了,最终40406c的值就是3133335d,正确的应该是31333359.

其实在这一段代码里还有几处跟前面很像,比如给回调函数赋值那些,但是这一段代码确确实实没有能赋值的地方,也确实不该赋值,我反反复复看了好多遍才确定。

最终确定了40406c的值,就是0x31333359。

/*/*/**********************************************************///

接着返回验证代码,把生成随机数时,异或的值改成31333359,满怀欣喜的等一个结果!

然而,并没有结果!

我只能继续看上面的代码分析40406c的值,前面是直接把分析全写了,实际是来回看了好遍,最后的状态就是,他就是31333359,要不就是3133335d,要都不是我就真不会了!

我把可能的值都试过了,还是算不出来结果。

/***********************************************/

之后我又想,是不是生成的随机数不对?于是我打开的vs调试,看了下生成的随机数,看起来是有点问题。虽然单看每一个42位随机都挺好,但是这6000个一起看,第一个生成的随机数,明显有一种稳定增大的迹象!


我甚至都觉得根据第一个随机值都能推算出他的种子...

然后我就把目标放在了随机值我就觉得他不对,在网上查了半天,也没有结果,但是我得到了一个很重要的信息。那就是随机数的值最大只能是0x7fff

我的注册码最大128,随机值最大0x7fff,相乘最大也不到16进制的八位那么大啊,而我得到的那些数据全都是8位的,那就说明一个问题,我一开始计算的那些数据,肯定不对!

/*************************************************************/

这下可真难住我了,从IDA直接复制的代码怎么会有错呢,我订正了好几遍,就是没错,但算出来的肯定不对,最后实在没有办法了,干脆我直接用汇编代码写跟od里一模一样的代码看看算出来到底对不对。


大概这样子,基本是从OD复制的...

这是最后得到的结果


这样才对嘛,全都是6位一下的数字,才有可能算的对啊。

看来IDA确实出错了,第一次用IDA体验极差。

最后到原来的程序验证一下,成功得到注册码!


总结一下:

最主要的IDA坑我这一下着实浪费我很长时间

但是要不是坑我这一下,我也不会对其他地方看的那么细心认真,也不会有那么多收获

虽然每一次的努力不一定能让我得到结果

但是真的感觉到每次我都向结果前进了一点

在这个过程中感受到很多乐趣

最终得到了结果体验还是极好的

希望以后能和大家多多交流共同进步


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (4)
雪    币: 5144
活跃值: (9652)
能力值: ( LV9,RANK:181 )
在线值:
发帖
回帖
粉丝
2
过程挺详细的,顶一下。
2018-8-28 18:30
0
雪    币: 15170
活跃值: (16832)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
3
学习学习
2018-8-29 08:53
0
雪    币: 1535
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
顶一下
2018-8-29 10:34
0
雪    币: 259
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习你的这种研究精神。
2018-8-29 11:56
0
游客
登录 | 注册 方可回帖
返回
//