首页
社区
课程
招聘
[看雪读书月]2008看雪论坛读书月第二题的注册机
发表于: 2008-7-21 07:40 13383

[看雪读书月]2008看雪论坛读书月第二题的注册机

2008-7-21 07:40
13383
收藏
免费 7
支持
分享
最新回复 (29)
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
2
输入用户名和密码点regist按钮后,用户名和注册码将分别被保存到HKEY_CURRENT_USER\Software\MegaX键下的name和key中

重新运行CrackMe.net.exe后,开始读入上述注册表键值并校验用户名和注册码

首先注册码由4组字符串组成,每组5个字符,中间由"-"号分开(这里我们分别以AAAAA, BBBBB, CCCCC, DDDDD表示四组注册码,USERNAME表示用户名)
其次由固定字符串"CrackMe","MegaX",第一,第二组注册码以及用户名合成一个字符串
CrackMeAAAAABBBBBMegaXUSERNAMEMegaX,并计算这个字符串的md5
然后将计算出后的md5每个字节都转换为10进制数并合成一个字符串,然后取最前面的5个字符
然后在一个固定字符串“8x3p5BeabcdfghijklmnoqrstuvwyzACDEFGHIJKLMNOPQRSTUVWXYZ1246790”中依次查找上一步得到的字符串的每个字符,并将返回的偏移地址减去第三组注册码CCCCC中相对应的每个字节在该固定字符串中的偏移地址,如果为负数则加上该固定字符串的长度,最后在该固定字符串中查找上面计算出来的偏移地址的字符,并合成最后一组注册码
2008-7-21 07:59
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
3
忘记提到了,这道题还有一个特点是如果注册格式不正确则根本就不会继续校验下去,因此在注意到注册码格式之前很容易就跟飞了
2008-7-22 03:15
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
4
这里先说一声对不起哈,我的文字表达能力不太好,如果有文笔不通之处还请多多包涵了
   
【文章标题】: 《2008看雪论坛读书月第二题》分析随笔
【文章作者】: mstwugui
【软件名称】: 2008看雪论坛读书月第二题CrackMe02
【文件MD5】: 62c5e33505563dbd8a597dbaf5331f8e
【下载地址】: http://bbs.pediy.com/showthread.php?t=68795
【加壳方式】: 不知道具体是什么壳,但很明显被混淆保护了
【开发环境】: Microsoft Visual Studio .NET 2008
【使用工具】: OllyDbg, regshot, ApiMonitor
【操作平台】: XP SP3 + .NET Framework 2.0 SP1
【软件介绍】: 2008看雪论坛读书月第二题
   
刚看到第二题时有些意外也有些开心,正好没有分析过.NET程序这下有机会熟悉熟悉练习一下了,:)于是立刻下载下来运行一下


倒。。。电脑上安装好的.NET 1.1看来没用,再下个2.0 SP1


咣当。。。万事开头难,坚持。。。坚持。。。看了看论坛,似乎其他朋友也遇到了这样那样的问题,但也有成功运行的,看来是环境问题。
   
重启电脑。。。开机后立刻运行CrackMe.net.exe,哈,运气不错,正常启动了
   
可是。。。接下来我该从哪开始呢?
   
  。。。
   
嗯,还是先打开google搜索了一下.NET的反编译吧
   
Reflector…Dis#...Salamander…
   
看雪的题目果然不一般,想想也对,这道题肯定是混淆保护过的,如果从网上随便下个工具就能反编译出源代码那还玩个什么劲啊,算了吧,还是不浪费时间偷懒找反编译了,老老实实自己动手一定也可以丰衣足食,:)
   
于是习惯性的IDA了一眼。。。倒。。。。一堆问号不知所云。。。再换OD看看。。。哈,终于看到些认识的蝌蚪文了
   
jmp     dword ptr [<&mscoree._CorExeMain>]
   
很明显如果跟进去也只是系统调用里瞎转悠,既然不熟悉.NET也不知道该再哪下断点,还是暂且搁置一下先
   
再来运行一下CrackMe.net.exe,随手输入usernmae和key然后点regist
   
倒。。。又出错退出了,可是我也没有多干啥呀,和刚才刚启动好的时候也就多了几个进程,难道是内存不够?嗯,还是先把那几个新启动的进程都给关了再试试。汗。。。又正常启动了,可能是保护机制有BUG



看来是程序启动时校验username和key,  这种情况下通常都是将注册信息保存在注册表或是文件系统中。嗯,用regshot对文件系统和注册表做一个snapshot再regist然后比较前后区别。。。果然不出所料,注册信息存放在注册表中
   
  HKEY_CURRENT_USER\Software\MegaX\name: "masterwugui"
  HKEY_CURRENT_USER\Software\MegaX\key: "testkey"
   
嗯,回OD以前还需要确认一下具体是哪个API读取的注册信息,打开ApiMonitor设置监视RegQueryValueA, RegQueryValueW, RegQueryValueExA, RegQueryValueExW,然后重新启动CrackMe.net.exe,哈,注册表访问还不少,在接近最尾部的一处调用看到了是通过RegQueryValueExW读取的注册信息。
   
是时候回到OD了,开始调试CrackMe.net.exe,停在入口点后在ADVAPI32.RegQueryValueExW设一个断点,并设置条件为:
   
  ((([[esp+8]] == 0x0061006E) || ([[esp+8]] == 0x0065006B)) && ([esp+0x14] != 0))
   
为什么要设置这个条件?呵呵,RegQueryValueExW可不会只被调用一次,我们只需要在读取name和key的时候中断
   
按下F9。。。开跑。。。停下来了,先看一眼[esp+8],嗯,指向的是name,在[esp+0x14]设一个单字节硬件访问断点A,继续跑。。。
   
先是停在了ntdll.memmove里面,向缓冲写入刚刚从注册表里读取出来的用户名,继续跑。。。
   
接下来停在mscorwks里面,看代码很明显是计算字符串长度,继续跑。。。
   
  mov                  si, word ptr [eax]
  inc                    eax
  inc                    eax
  cmp                  si, bx
  jnz                    $-8
   
再接下来又停在了msvcr80.memcpy里面,对目标地址再设一个单字节硬件访问断点B,继续跑。。。
   
又停在mscorwks,不过这次是清空断点A的字符串,看来断点A指向的是个临时缓冲,删除了断点A,继续跑。。。
   
  add                  word ptr [edi+eax], 0
   
哈,回到了RegQueryValueExW,赶快看看[esp+8],嗯,现在指向的是key了,重复上面的步骤直到设置单字节硬件访问断点C
   
好了,现在有了两个单字节硬件访问断点B和C,分别指向name和key。有了这些基本信息后我们可以先F9简单看一下流程
   
连续在mscorlib里面同一处停下来两次,一次是因为name,一次是key,但没有什么特别的,系统调用,跳过。。。
   
接下来还是停在了mscorlib, 只不过这次似乎有些文章,请注意下图中加红框的部分,此时AX中存放了key指向的字符,而EDX指向的是一个字符’-’


   看来key中必须有字符’-’,而根据蓝色的这一句看来,应该需要两个或更多的’-’,不过我们暂时先忽略这个问题,继续F9。。。哈,没有再遇到其他的断点直接跑出来了
   
  很明显我们要想继续跟踪下去必须先修改key,如果key中没有’-‘字符就会直接注册校验失败。先把key改为1234-5678,然后再重新调试。
   
  嗯,现在又走近了一步,停在了mscorlib中,很明显这次是把key中‘-’字符之前的子字符串复制到目标地址,因此在目标地址下个单字节硬件访问断点D,继续F9。。。


   什么?又是畅通无阻直接跑出来了?看来key的格式还有文章,先重新调试回到上一步。但这次我们不要F9直接跑出来,重复用Ctrl+F9执行到返回查看一下究竟有什么文章。直到返回到这一层
  


在这里继续单步F8连续几个跳转之后,直到如下代码



   这个跳转如果没有进入很快就会从这个函数中返回,所以这里肯定也是一道关卡。原来[eax+4]中存放的是key分割以后的数量,因此我们需要四组由’-‘分开的key。现在将key改为1234-5678-ABCD-EFGH后,重新调试
   
  在经过上一步数量校验之后,我们继续单步直到如下代码:
  


   此时,ecx指向的是分割后的字符串对象(注意这里指向的是个对象而不是字符串本身),而[B1659C]的作用是取该字符串的长度,因此key不仅仅需要3个’-‘分割,而且每组分割后的字符串长度必须为5。将key修改为01234-56789-ABCDE-FGHIJ,重新调试
   
  先F9连续跑一次试试,哈,看来没有更多的格式校验了,我们终于重新停在了指向name的断点B上。确定好格式没有问题之后,是时候该改变跟踪策略了。
   
  因为上一步校验每组密码字符串长度所处的代码在临时分配空间,所以无法直接在此处设置断点重新启动后自动中断下来。而且每次因为太多硬件断点停留也烦人,所以可以先删除所有其他的断点只保留指向name的断点B。当断点B在RegQueryValueExW取得name以后,下一次中断时这个分组字符串长度校验所处的内存空间已经正常分配,这时候就可以在cmp eax, 5这条指令上下一个断点,然后F9直接跑到这里。连续四次校验完四组密钥之后,一路F8(此时还会路过4次分割key,但我们暂时忽略),直到看到如下代码
  


   此时压入堆栈的eax和[ebp-dc]分别指向两个字符串对象”MegaX”和用户名”masterwugui”,而ecx和edx分别指向密钥中的第一”01234”和第二组字符串”56789”
   
  现在在下一个调用”call dword ptr [B165B0]”下个断点E,继续F9。。。
   
  我们会因为指向name的断点B中断两次,直接跳过,直到回到刚刚添加的断点E,这时候查看一下ecx和edx的值,原来前面那个调用是把那四个字符串合成起来,ecx因此指向了一个字符串对象”0123456789MegaXmasterwugui”,而edx指向的是第三组密钥字符串”ABCDE”
   
  呵呵,黎明越来越近了,很明显下一个调用”call dword ptr [B165B0]”非常关键,立刻F7单步进入。这里会连续两次动态解析加载代码,直接跳过
  


   在第二次从上述代码返回后,程序开始对之前传入的两个字符串对象”0123456789MegaXmasterwugui”以及”ABCDE”进行一连串的加密动作,这里我们暂时先忽略,继续运行到下图中红框处
  


   此时eax指向一个字符串对象”uVuyv”,也就是上面两个字符串计算出来的结果。因此对该地址下一个单字节硬件访问断点,F9继续跑
   
  第一次停留没有什么特别之处,直接跳过,但第二次停留时。。。
  


   字符串”uVuyv”被转换成了大写并在被保存到[edi+eax]中,继续在[edi+eax]下一个单字节硬件访问断点F,继续F9。。。
  


   看来是对上面计算出来的字符串”UVUYV”再次加密,此时红框里edx指向我们输入的第四组密钥字符串,而ecx指向的则是正确的第四组密钥字符串,终于得到了第一组有效的username和key
   
  Username: masterwugui
  Key: 01234-56789-ABCDE-RGHF3
   
  庆祝一下?别急别急,还有两个问题没有解决”RGHF3”和”UVUYV”分别是怎么计算出来的
   
  第一个问题比较容易,分析一下最后这一步很容易就得出了
  
    [FONT=新宋体]CString CalcFinalKey(CString sKey)[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        DWORD           i;[/FONT]
  [FONT=新宋体]        CString         sRet = [COLOR=#a31515]""[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sMegaX = [COLOR=#a31515]"MegaX"[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sFinalKeys = [COLOR=#a31515]"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[/COLOR];[/FONT]
  [FONT=新宋体]        BYTE            c;[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]for[/COLOR] (i=0; i<5; i++)[/FONT]
  [FONT=新宋体]        {[/FONT]
  [FONT=新宋体]                c = sKey.GetAt(i);[/FONT]
  [FONT=新宋体]                c += sMegaX.GetAt(i);[/FONT]
  [FONT=新宋体]                c += 0x23;[/FONT]
  
  [FONT=新宋体]                sRet += sFinalKeys.GetAt(c%0x24);[/FONT]
  [FONT=新宋体]        }[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]return[/COLOR] sRet;[/FONT]
  [FONT=新宋体]}[/FONT]
  
   问题是”uVuyv”是从哪里来的。。。现在我们如果回到前面去慢慢单步分析多半会头晕眼花,还是对”uVuyv”设置单字节硬件写入断点反着追吧
   
  接下来并没有多少难点也就不多费口舌了,在连续的设置单字节写入断点反追之后,发现”uVuyv”来自与一个字符串"1129720123519959835898227196100347012562" 的计算,而这个字符串则是由16个字节分别转为十进字数后合成的。
   
  又多了一个问题,这16个字节哪来的?同上面一样,继续反追之后发现最后对这16个字节进行写操作是ADVAPI32.CryptGetHashParam,看到这里是不是突然明白了?立刻在ADVAPI32.CryptCreateHash下一个断点重新调试,果然不出所料,在最后写入那关键的16个字节之前,CryptCreateHash用的参数是CALC_MD5
   
  现在大家应该都明白了,原来这16个字节就是之前用”0123456789MegaXmasterwugui”和第三组密钥字符串”ABCDE”合成的字符串"CrackMe0123456789MegaXmasterwuguiMegaX"计算出来的MD5,好了,现在可以完成最后的步骤,写注册机了。

    [COLOR=blue][FONT=新宋体]char[/FONT][/COLOR][FONT=新宋体] GetRandByte()[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        [COLOR=blue]char[/COLOR] c = rand()%36;[/FONT]
  
  [FONT=新宋体]        c += (c<10)?0x30:0x37;[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]return[/COLOR] c;[/FONT]
  [FONT=新宋体]}[/FONT]
  
  [FONT=新宋体]CString GenerateRandKey()[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        CString         sRandKey = [COLOR=#a31515]""[/COLOR];[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]for[/COLOR] ([COLOR=blue]int[/COLOR] i=0;i<5;i++)[/FONT]
  [FONT=新宋体]                sRandKey += GetRandByte();[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]return[/COLOR] sRandKey;[/FONT]
  [FONT=新宋体]}[/FONT]
  
  [FONT=新宋体]CString CalcFinalKey(CString sKey)[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        DWORD           i;[/FONT]
  [FONT=新宋体]        CString         sRet = [COLOR=#a31515]""[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sMegaX = [COLOR=#a31515]"MegaX"[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sFinalKeys = [COLOR=#a31515]"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[/COLOR];[/FONT]
  [FONT=新宋体]        BYTE            c;[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]for[/COLOR] (i=0; i<5; i++)[/FONT]
  [FONT=新宋体]        {[/FONT]
  [FONT=新宋体]                c = sKey.GetAt(i);[/FONT]
  [FONT=新宋体]                c += sMegaX.GetAt(i);[/FONT]
  [FONT=新宋体]                c += 0x23;[/FONT]
  
  [FONT=新宋体]                sRet += sFinalKeys.GetAt(c%0x24);[/FONT]
  [FONT=新宋体]        }[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]return[/COLOR] sRet;[/FONT]
  [FONT=新宋体]}[/FONT]
  
  
  [FONT=新宋体]CString CalcKey(CString sName, CString sPassword1, CString sPassword2, CString sPassword3)[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        CString         sTemp = [COLOR=#a31515]"CrackMe"[/COLOR] + sPassword1 + sPassword2 + [COLOR=#a31515]"MegaX"[/COLOR] + sName + [COLOR=#a31515]"MegaX"[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sTemp1 = [COLOR=#a31515]""[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sRet = [COLOR=#a31515]""[/COLOR];[/FONT]
  [FONT=新宋体]        CString         sKeys = [COLOR=#a31515]"8x3p5BeabcdfghijklmnoqrstuvwyzACDEFGHIJKLMNOPQRSTUVWXYZ1246790"[/COLOR];[/FONT]
  
  [FONT=新宋体]        HCRYPTPROV      hProv;[/FONT]
  [FONT=新宋体]        HCRYPTHASH      hHash;[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]if[/COLOR] (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0xF0000000))[/FONT]
  [FONT=新宋体]        {[/FONT]
  [FONT=新宋体]                [COLOR=blue]if[/COLOR] (CryptCreateHash(hProv, CALG_MD5, NULL, 0, &hHash))[/FONT]
  [FONT=新宋体]                {[/FONT]
  [FONT=新宋体]                        [COLOR=blue]if[/COLOR] (CryptHashData(hHash, ([COLOR=blue]const[/COLOR] BYTE*)sTemp.GetBuffer(), sTemp.GetLength(), 0))[/FONT]
  [FONT=新宋体]                        {[/FONT]
  [FONT=新宋体]                                DWORD   size = 16;[/FONT]
  [FONT=新宋体]                                BYTE    code[16];[/FONT]
  [FONT=新宋体]                                [COLOR=blue]int[/COLOR]             offset;[/FONT]
  
  [FONT=新宋体]                                [COLOR=blue]if[/COLOR] (CryptGetHashParam(hHash, HP_HASHVAL, (BYTE*)code, &size, 0))[/FONT]
  [FONT=新宋体]                                {[/FONT]
  [FONT=新宋体]                                        DWORD   i, j;[/FONT]
  
  [FONT=新宋体]                                        [COLOR=blue]for[/COLOR] (i=0; i<size; i++)[/FONT]
  [FONT=新宋体]                                        {[/FONT]
  [FONT=新宋体]                                                sTemp.Format([COLOR=#a31515]"%d"[/COLOR], code[i]);[/FONT]
  [FONT=新宋体]                                                sTemp1 += sTemp;[/FONT]
  [FONT=新宋体]                                        }[/FONT]
  
  [FONT=新宋体]                                        [COLOR=blue]for[/COLOR] (i=0; i< 5; i++)[/FONT]
  [FONT=新宋体]                                        {[/FONT]
  [FONT=新宋体]                                                j = sKeys.Find(sPassword3.GetAt(i));[/FONT]
  [FONT=新宋体]                                                offset = sKeys.Find(sTemp1.GetAt(i)) - j;[/FONT]
  [FONT=新宋体]                                                [COLOR=blue]if[/COLOR] (offset < 0)[/FONT]
  [FONT=新宋体]                                                        offset += sKeys.GetLength();[/FONT]
  [FONT=新宋体]                                                sRet += sKeys.GetAt(offset);[/FONT]
  [FONT=新宋体]                                        }[/FONT]
  [FONT=新宋体]                                }[/FONT]
  [FONT=新宋体]                        }[/FONT]
  [FONT=新宋体]                        CryptDestroyHash(hHash);[/FONT]
  [FONT=新宋体]                }[/FONT]
  [FONT=新宋体]                CryptReleaseContext(hProv,0);[/FONT]
  [FONT=新宋体]        }[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]if[/COLOR] (sRet.GetLength() == 0)[/FONT]
  [FONT=新宋体]        {
[/FONT]  [FONT=新宋体]                MessageBox(0, [COLOR=#a31515]"Failed to invoke cryptographic api!"[/COLOR], [COLOR=#a31515]"ERROR"[/COLOR], MB_ICONSTOP);[/FONT]
  [FONT=新宋体]                ExitProcess(0);[/FONT]
  [FONT=新宋体]        }[/FONT]
  
  [FONT=新宋体]        sRet.MakeUpper();[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]return[/COLOR] CalcFinalKey(sRet);[/FONT]
  [FONT=新宋体]}[/FONT]
  
  [COLOR=blue][FONT=新宋体]void[/FONT][/COLOR][FONT=新宋体] CpediykgDlg::OnEnChangeEdit1()[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        [COLOR=green]// TODO:  If this is a RICHEDIT control, the control will not[/COLOR][/FONT]
  [FONT=新宋体]        [COLOR=green]// send this notification unless you override the CDialog::OnInitDialog()[/COLOR][/FONT]
  [FONT=新宋体]        [COLOR=green]// function and call CRichEditCtrl().SetEventMask()[/COLOR][/FONT]
  [FONT=新宋体]        [COLOR=green]// with the ENM_CHANGE flag ORed into the mask.[/COLOR][/FONT]
  
  [FONT=新宋体]        [COLOR=green]// TODO:  Add your control notification handler code here[/COLOR][/FONT]
  [FONT=新宋体]        OnBnClickedGenerate();[/FONT]
  [FONT=新宋体]}[/FONT]
  
  [COLOR=blue][FONT=新宋体]void[/FONT][/COLOR][FONT=新宋体] CpediykgDlg::OnBnClickedGenerate()[/FONT]
  [FONT=新宋体]{[/FONT]
  [FONT=新宋体]        [COLOR=green]// TODO: Add your control notification handler code here[/COLOR][/FONT]
  [FONT=新宋体]        CString         sName;[/FONT]
  [FONT=新宋体]        [COLOR=blue]int[/COLOR]                     nNameLength;[/FONT]
  [FONT=新宋体]        CString         sPassword, sPassword1, sPassword2, sPassword3, sPassword4;[/FONT]
  
  
  [FONT=新宋体]        m_edtPassword.SetWindowText([COLOR=#a31515]""[/COLOR]);[/FONT]
  [FONT=新宋体]        m_edtName.GetWindowText(sName);[/FONT]
  [FONT=新宋体]        nNameLength = sName.GetLength();[/FONT]
  
  [FONT=新宋体]        [COLOR=blue]if[/COLOR] (nNameLength == 0)[/FONT]
  [FONT=新宋体]                [COLOR=blue]return[/COLOR];[/FONT]
  
  [FONT=新宋体]        srand(GetTickCount());[/FONT]
  [FONT=新宋体]        sPassword1 = GenerateRandKey();[/FONT]
  [FONT=新宋体]        sPassword2 = GenerateRandKey();[/FONT]
  [FONT=新宋体]        sPassword3 = GenerateRandKey();[/FONT]
  [FONT=新宋体]        sPassword4 = CalcKey(sName, sPassword1, sPassword2, sPassword3);[/FONT]
  
  [FONT=新宋体]        sPassword = sPassword1;[/FONT]
  [FONT=新宋体]        sPassword += [COLOR=#a31515]"-"[/COLOR];[/FONT]
  [FONT=新宋体]        sPassword += sPassword2;[/FONT]
  [FONT=新宋体]        sPassword += [COLOR=#a31515]"-"[/COLOR];[/FONT]
  [FONT=新宋体]        sPassword += sPassword3;[/FONT]
  [FONT=新宋体]        sPassword += [COLOR=#a31515]"-"[/COLOR];[/FONT]
  [FONT=新宋体]        sPassword += sPassword4;[/FONT]
  [FONT=新宋体]        m_edtPassword.SetWindowText(sPassword);[/FONT]
  [FONT=新宋体]}[/FONT]
   
  


注意,密钥中的前三组字符串其实也可以有小写字符的,但我写的注册机只用了大写字符和数字,纯粹是为了让密钥整齐一些看起来比较顺眼

最后再唠叨两句吧。技术日新月异,而且不会像赛跑有个理论极限,保不准明天又会出现一门新学问来,所以有时间的时候还是多练练手,有新东西的时候才会比较容易有感觉入手,而培养这个感觉基本上没有什么捷径,大多都是日积月累的经验堆出来的。

以这一题为例,第一步是需要明确入手点,然后以合适的方式切入,中间的过程虽然有些磕磕碰碰,但坚持下去并不断的总结经验,最后总会看到光明的。

破解注册算法其实只是逆向工程很小的一部分,而且最好不要太过依赖于或是期望某个强大的自动化工具或脚本,在逆向的过程中可以学习到很多技巧或机制,这些才是最值得我们学习的。

熟练的逆向=经验+毅力+信心+灵感+。。。呵呵,当然还需要一部分运气

好了,写到这里也该告个段落了,谢谢大家花这么多时间看到这里,也感谢一下MegaX和看雪论坛提供了这个练习机会
上传的附件:
2008-7-24 04:05
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
5
这一题应该还有很多不同的解法,我的办法肯定不是最正统的,或许可以先脱壳再常规反编译.NET程序,但因为不熟悉.NET环境所以一时我也只能这样了,希望熟悉的朋友不吝指教
2008-7-24 14:17
0
雪    币: 47147
活跃值: (20465)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
6
答题结束,从答案提交区移出。
更多事项参考:http://bbs.pediy.com/showthread.php?t=68795

由于本题只有mstwugui和沙金提交了答案,因此不需要投票即可决定奖项。
2008-7-27 20:05
0
雪    币: 97697
活跃值: (200839)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
7
support .
2008-7-27 20:35
0
雪    币: 5275
活跃值: (461)
能力值: (RANK:1170 )
在线值:
发帖
回帖
粉丝
8
呵呵,win32的算法和调试功底了得啊
2008-7-27 20:52
0
雪    币: 47147
活跃值: (20465)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
9
估计平时VM保护调试多了,碰到.Net这个大“VM”也得心应手。
2008-7-27 21:23
0
雪    币: 274
活跃值: (13)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
强,支持
2008-7-27 21:50
0
雪    币: 423
活跃值: (11)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
11
细节决定成败!

有许多地方要好好地学习。

文笔流畅,有分析,有思考,有功力,有分量!!!

对我的困惑的地方都有详细地解释,太感谢了,太牛了!!!

看雪可以改名了,就叫牛群论坛吧,我等只是牛毛的角色。
汗,九牛之一毛,就足以骄傲了。
2008-7-27 22:27
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
12
谢谢,不过很期待正统的.NET解法,一定可以学习到很多东西
2008-7-27 23:54
0
雪    币: 423
活跃值: (11)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
13
有些疑问,都看了三遍了,在文中没找到答案,望作者赐教:
1.jmp     dword ptr [<&mscoree._CorExeMain>]

这个位置不是每次调试都停在这,有不确定性。为什么?
是OD的设置问题?

2.当断点B在RegQueryValueExW取得name以后,下一次中断时这个分组字符串长度校验所处的内存空间已经正常分配,这时候就可以在cmp eax, 5这条指令上下一个断点,然后F9直接跑到这里。

这个我自己总结出来了。但是,一次调试不成功后,mscorwks里跟,有时不一定成功。有没有更有效率的方法:重新调试时,快速来到上一次的失败处?老是枯燥地重复,挺烦的。

3.这里会连续两次动态解析加载代码。

这里说的太简单了。也是我相当头痛的问题:动态解析加载代码,你这里怎么考虑的?

对于问题内容,你可以复制,然后在当前页查找,可以迅速确定在您破文中的位置。望指点一二,谢谢!!!
2008-7-27 23:58
0
雪    币: 136
活跃值: (20)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
14
内存空间已经正常分配,可以F2下断点。
如果还没有分配,但已经有可读权限时,可移动光标到地址,用F4跑到当前地址。(这照在本题比较好用)
2008-7-28 00:18
0
雪    币: 423
活跃值: (11)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
15
嗯,不错。好建议,真心感谢!明天一起验证一下。

看雪溜三秒,必有好大侠!
2008-7-28 00:22
0
雪    币: 440
活跃值: (61)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
16
[QUOTE=nba2005;487804]有些疑问,都看了三遍了,在文中没找到答案,望作者赐教:
1.jmp     dword ptr [<&mscoree._CorExeMain>]

这个位置不是每次调试都停在这,有不确定性。为什么?
是OD的设置问题?

2.当断点B在RegQueryValueEx...[/QUOTE]



本来看了破文后,自已心中也有太多的疑问.
但一时还不知该从何处提出来.......

这CM说实话从放题到前晚(现在过了零点了)断断续续跟了N次....
得到的关键就这三处
一、CM通过读取注册表来重启验证。
二、检测调试工具
三、代码采用边执行边解压

一直都无法定位到关键数据的位置上。。
注册表拦截断点,有时能断下有时断不下。。。。
断下的时候跟半天就跑飞了。。。。。
今天对比了一下,发觉差矩还是满大的。。。。。

刚才跟着nba2005兄的思路再对照mstwugui破文
解了许多疑问了.......

我也真心表示感谢
2008-7-28 00:48
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
17
[quote=nba2005;487804]有些疑问,都看了三遍了,在文中没找到答案,望作者赐教:
1.jmp     dword ptr [<&mscoree._CorExeMain>]

这个位置不是每次调试都停在这,有不确定性。为什么?
是OD的设置问题?

2.当断点B在RegQueryValueExW取得name以后,下一次中断时这个分组字符串长度校验所处的内存空间已经正常分配,这时候就可以在cmp eax, 5这条指令上下一个断点,然后F9直接跑到这里。

这个我自己总结出来了。但是,一次调试不成功后,mscorwks里跟,有时不一定成功。有没有更有效率的方法:重新调试时,快速来到上一次的失败处?老是枯燥地重复,挺烦的。

3.这里会连续两次动态解析加载代码。

这里说的太简单了。也是我相当头痛的问题:动态解析加载代码,你这里怎么考虑的?

对于问题内容,你可以复制,然后在当前页查找,可以迅速确定在您破文中的位置。望指点一二,谢谢!!![/quote]

第一个问题取决于你的OD设置,但是在同一台机器上不改设置的情况下应该停在同一个地方,不过停在哪其实也无所谓,因为这道题一直运行到取注册表键值之前都无所谓

第二个问题,内存环境是有可能变化的,但这道题目本身并没有对这个问题做文章,所以说内存变化完全是有windows自身内存分配导致的,因此通常情况下这个地址不会变,但如果发生变化,只需要退出OD重新加载一次,如此又会使用原先的内存地址,不过注意不要启动占用内存较大的程序,否则多半会影响到

对于第三个问题,那个函数会被频繁使用到动态解析加载代码,第一次我是F8直接过去,第二次再来到这里就明白了这个函数的作用,如果你比较一下就很容易发现调用那个函数以前即将执行代买所处的目标内存地址并没有被分配。此类问题分析多了可能会经常遇到,不过这个保护壳还算是仁慈了,因为执行完并没有释放这块内存,所以完全可以dump出来再静态分析。如果执行完就释放掉相应代码和数据所使用的内存会更耗时一些

如果要提高分析速度和效率,需要不断的平衡何时追或何时跑的问题,以这个函数为例,如果你要追进去弄明白估计也不会是一时三刻的事情,但如果留意一下,暂时先忽略然后回过头再看的时候很容易就半总结半猜测出来了,最后分析算法处也是类似,如果直接步入花的时间肯定会更多
2008-7-28 05:10
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
18
F4不是很安全,如果内存发生变化该地址甚至有可能不是合法的指令,我还是比较倾向于确定正确分配完成后再跑

对于这题来说如果内存没有发生变化这样确实比较快一些,对于动态内存我几乎不用F4只是出于安全习惯,因为经常会需要分析一些不安全的代码
2008-7-28 05:20
0
雪    币: 136
活跃值: (20)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
19
这个CM内存也会发生变化。有是经常连续四五次出内存和以前的不同。
我是这样解决的:在以下分别下了断点,写了注释
00F1991F    B9 1C010000     mov     ecx, 11C                         ; 读出name
00F19924    E8 97CAFFFF     call    00F163C0


00F1995A    B9 20010000     mov     ecx, 120                         ; 读出key
00F1995F    E8 5CCAFFFF     call    00F163C0


00F19A43    8B8F 60010000   mov     ecx, dword ptr [edi+160]         
00F19A49    8B97 58010000   mov     edx, dword ptr [edi+158]         ; user
00F19A4F    FFB7 5C010000   push    dword ptr [edi+15C]              ; key
00F19A55    FF15 90659A00   call    dword ptr [9A6590]                 ; 比较用户名


OD重新载入时,先F4到以上位置
如果内存和以上code符合,我就继续分析,不符合就重新载入。
2008-7-28 07:25
0
雪    币: 423
活跃值: (11)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
20
听君一席话,胜读十年书!!!

你的这番解释是经验之谈,在其他场合也适用,比如手工脱壳。
{
第二次再来到这里就明白了这个函数的作用,如果你比较一下就很容易发现调用那个函数以前即将执行代买所处的目标内存地址并没有被分配。此类问题分析多了可能会经常遇到
}

这个论述真是精辟!总结的透彻。

{
如果要提高分析速度和效率,需要不断的平衡何时追或何时跑的问题,以这个函数为例,如果你要追进去弄明白估计也不会是一时三刻的事情,但如果留意一下,暂时先忽略然后回过头再看的时候很容易就半总结半猜测出来了,最后分析算法处也是类似,如果直接步入花的时间肯定会更多
}
从理论上知道了大概的感觉。看来要掌握成下意识的行为,还是要多花时间练习、总结。

{
不过这个保护壳还算是仁慈了,因为执行完并没有释放这块内存,所以完全可以dump出来再静态分析。
}
这个要求的起点高了,暂时还达不到。以后就朝这方向努力。有方向和目标就好办了。

这个题目出得真是好啊,引来了逆向的第一猛大侠!

朝闻道,夕死可矣!

看雪来了个包青天
技术超群惊四方
谈笑间逆向大炮把蚊子灭
我等破解菜鸟俱汗颜

汗,新官这第一把火烧得太生猛,都不敢说自己懂破解了。
2008-7-28 07:33
0
雪    币: 423
活跃值: (11)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
21
这个解释似乎也合理,我今天晚上验证一下实际效果。将两位的经验体会体会。

这题我也是沙里淘金了。很难做出来的题目,却淘出了不少好金子。
2008-7-28 07:36
0
雪    币: 136
活跃值: (20)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
22
的确很猛,不得不佩服!
很多有疑问的地方,看了也明白不少。
2008-7-28 07:39
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
23
嗯,我是以name指针在RegQueryValueExW以后下一次访问来判断的
2008-7-28 07:43
0
雪    币: 136
活跃值: (20)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
24
其实,我取名 “沙金” 的本意是:吹尽黄沙始见金

不过,在逆向里,沙里淘金 好像比喻得还不错。
2008-7-28 07:44
0
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
25
瀑布汗一个。。。nba2005大侠的口才着实了得,太抬举了
2008-7-28 07:45
0
游客
登录 | 注册 方可回帖
返回
//