• 标 题:一个PostScript(RoPS)注册机分析。初学者看。 (21千字)
  • 作 者:machoman[CCG]
  • 时 间:2001-7-8 19:37:29
  • 链 接:http://bbs.pediy.com

软件名称:Roger's PostScript(RoPS)
整理日期:2000.2.10最新版本:5.2c文件大小:282KB软件授权:共享软件使用平台:WinNT/2000/98Loadown address: http://www.newhua.com.cn/down/rops52c.exe              CrackingBy: machoman[CCG]  (China Cracking Group)
平台      :win2000/98
使用工具  :Softice 4.05 for NT /98,ida  4.04 或者 wdasm32 推荐汉化版
预定目标  :学习写简单的注册机。
难度等级  :入门级J
结果:
Name: [CCG]
Users: 1
Key:  4e802402618f

破解过程:
这个软件是一个读取PostScript格式的电子文档的软件,这种文档格式跟PDF文档格式一样都需要专门的软件才能打开,这个软件就是打开.ps后缀文档的一个小软件。它有30天的使用限制,如果过期会要求注册才能使用。我们的目标就是要知道其注册方法去找到注册码。以下的步骤就是达成这个目标所需要的步骤。你只要有耐心与兴趣跟我一起一步一步的做,就可以体会到破解的乐趣。当然我希望不是噩梦,要是也没办法,小弟破解水平不但低,而且表达能力更是不敢恭维,要是看不下去了千万要原谅小弟。只怪小弟才只读了正规的9年书^_^。我尽量力争写详细,只需要你具备一些很基础的汇编知识。
首先在注册框内输入名字比如我最先输入就是
  Name : [CCG]
  Users : 1
  Key  :31415926
这些输入是很重要的,你在用Softice 调试软件跟踪时需要时时参照这些值以观察其发生的变化,进而分析出注册算法正确的结果。当这里输入完成后你不要马上点OK,我想大家都可能没有个个摸大奖的手气。任意输入就搞定注册。这时就需要按住Ctrl+D调出强力调试工具,比如Softice 。你的画面切换到黑色的命令行界面,进来的目的就是要下断点让程序跑动起来后在注册前停下来,让我们能够看见其计算注册码的过程。断点的方式很多,对于windows的注册输入你一般可以使用
bpx  GetWindowTextA
bpx  GetDlgItemTextA
bpx  hmemcpy
bpx  LockMyTask
这些断点。
对于这个软件你可以使用
bpx GetDlgItemTextA下断就可以阻止住程序跑动的脚步。
完成这个后你再用Ctrl+D又回到了windows 界面中,现在你就可以拿出吃奶的力气按下OK键^_^。哈哈,发生了啥,程序被断下在softice 的黑头下了。你可以看见光标停止在User32!GetDlgItemTextA这里。我们现在是在user32.dll这个程序空间中的,只需按F12键就会
回到软件程序的空间见下面的星号部分。Mico$oft的windows操作系统就像其老板一样厚黑学学的好的很。脸皮是层一层的跟洋葱头一样。你要是在win98平台下用hmemcpy,lockmytask 这样的断点也可以断下,不过那样你把操作系统的脸皮扒的更干净,你就需要多用几下F12才能到下面的位置,不过你要小心别按过头哟,Cracker就是要胆大加心细的哟。你我要是跟老比尔一样脸厚说最少也是个小土老财了。我看我们也不要妄想了,没有发财的命。要不会干破解,还不早就去花天酒地泡妹妹去了。还受破解这个穷罪个啥哟,只想哪天兄弟们能够把破功提高到能跟[伪装者]大峡一样成为专业户就就好了,嘿嘿,我这辈子是不要想了。嘿嘿不废话了。到程序中来把光标停留在下面星号的位置,下边的汇编代码是用ida4.04 抓出来的,跟在Softice 下看见的情况有些微差别,比如外边传递过来的参数都用ebp+arg_**表示,而如果是内部变量就是ebp+var_***的方式表示。
PORT_1:
004361A9                push    [ebp+arg_8]
004361AC                push    [ebp+arg_4]
004361AF                push    [ebp+arg_0]
004361B2                push    dword ptr [ecx+1Ch]
004361B5                call    ds:GetDlgItemTextA  *****************;这里就是你F12回来的位置,程序在这里的作用是把你输入的用户名提供给判断程序
/*****打断解释一下:
这个函数在MSDN里是这样定义的
UINT GetDlgItemText( HWND hDlg, int nIDDlgItem, LPTSTR lpString, int nMaxCount);也就是说
其第三个参数lpString 也就是[ebp+arg_4]就是读出你对话框中的输入信息的,不你可以在这时在
Softice下记忆体显示命令
D ebp+0c(注:也就是ida 中004361ac push [ebp+arg_4])
0023:0012f27c A0 F2 12 00 13 00 00 00-8C F7 12 00 60 51 44 00
            ##########
然后在在我的机器上显示数据的第一行是上边所示,这个东西是间接寻址的,你只需要再用一下D 命令显示上边记忆体中#号的数据的位置就可以看见如下了,注意记忆体中的数据是高字节在后底字节在前的
          D 0012f2a0
0023:0012f2a0 5B 43 43 47 5D 00 50 00-D6 02 02 00 34 0E 81 00 [CCG].P…..4…
看看后边的字符显示不就是输入的注册名吗?这里的操作方式在Cracker程序时经常遇到。希望能学会多用D命令显示内存这样对你找到 有价值的东西                                                   
解释结束*******/
004361BB                jmp    short loc_4361CD
004361BD ; ---------------------------------------------------------------------------
004361BD
004361BD loc_4361BD:                            ; CODE XREF: sub_43619F+8 j
004361BD                push    [ebp+arg_8]
004361C0                mov    edx, [eax]
004361C2                mov    ecx, eax
004361C4                push    [ebp+arg_4]
004361C7                push    [ebp+arg_0]
004361CA                call    dword ptr [edx+78h]
004361CD
004361CD loc_4361CD:                            ; CODE XREF: sub_43619F+1C j
004361CD                pop    ebp
004361CE                retn    0Ch
你现在在程序中的一个子程序中,当用F10一步一步走过到4361ce或者用F12直接retrun时就会回到下面的主程序判断部分
你可以用 softice 命令
                  BD  *  清掉断点,然后用
                  F8    一步一步走。


PORT_2:
/***************************************************************************/
004045E4                lea    eax, [ebp+var_28];用户名的地址
004045E7                push    13h
004045E9                mov    esi, 0D9h
004045EE                push    eax
004045EF                mov    edi, ecx
004045F1                push    esi
004045F2                call    sub_43619F;这里就是GetDlgItemText取name:[CCG]
;其地址放在[ebp_var_28]中
004045F7                lea    eax, [ebp+var_3C];你的光标回来后停留在这个位置
004045FA                push    13h
004045FC                push    eax
004045FD                push    0DAh
00404602                mov    ecx, edi
00404604                call    sub_43619F;再次GetDlgItemText取user:1放进
; [Ebp+var_3c]这个位置
00404609                push    1
0040460B                push    0
0040460D                push    0DCh
00404612                mov    ecx, edi
00404614                call    sub_43616D;取你的测试注册码
00404619                push    eax;测试注册码
0040461A                lea    eax, [ebp+var_14]
0040461D                push    offset aD      ; "%d"
00404622                push    eax
00404623                call    ds:wsprintfA;格式转换
00404629                lea    eax, [ebp+var_14];31415926 Key,用Sofeice D命令可见
0040462C                push    eax
0040462D                lea    eax, [ebp+var_3C];1 user
00404630                push    eax
00404631                lea    eax, [ebp+var_28];[CCG] Name
00404634                push    eax
00404635                call    sub_401940;注册码算法就在里边,嘿嘿,爆破手就
;在这里打住,要想吃鸡哪就需要跟进去
;Port3部分的汇编
0040463A                add    esp, 18h
0040463D                cmp    dword_44E168, 0;公德圆满的话,这里这个
;dword_44e168这个位置的数据为0,否则玩完
00404644                mov    dword_453670, eax
00404649                mov    [ebp+var_4], offset aThankYouForReg ; "Thank you for registering RoPS"
00404650                jz      short loc_40465B  ;
00404652                mov    [ebp+var_4], offset aSorryThatKeySe ; "Sorry, that key / serial number doesn't"...
00404659                jmp    short loc_404664
0040465B ; ---------------------------------------------------------------------------
0040465B
0040465B loc_40465B:                            ; CODE XREF: sub_4045DC+74 j
0040465B                push    1
0040465D                mov    ecx, edi
0040465F                call    sub_431C31
00404664
00404664 loc_404664:                            ; CODE XREF: sub_4045DC+7D j
00404664                mov    ecx, dword_45367C
0040466A                push    0
0040466C                push    offset aRegisterRops ; "Register RoPS"
00404671                push    [ebp+var_4]
00404674                call    sub_434BE4
00404679                mov    ecx, dword_45367C
0040467F                call    sub_402C27
00404684                lea    eax, [ebp+var_28]
00404687                push    13h
00404689                push    eax
0040468A                push    esi
0040468B                mov    ecx, edi
0040468D                call    sub_43619F
00404692                lea    eax, [ebp+var_28]
00404695                mov    esi, offset aRops_0 ; "rops"
0040469A                push    eax
0040469B                push    offset aReg    ; "reg"
004046A0                push    esi
004046A1                call    sub_40154F
004046A6                lea    eax, [ebp+var_3C]
004046A9                push    eax
004046AA                push    offset aKey    ; "key"
004046AF                push    esi
004046B0                call    sub_40154F
004046B5                lea    eax, [ebp+var_14]
004046B8                push    eax
004046B9                push    offset aUsers  ; "users"
004046BE                push    esi
004046BF                call    sub_40154F
004046C4                add    esp, 24h
004046C7                pop    edi
004046C8                pop    esi
004046C9                leave
004046CA                retn
004046CA sub_4045DC      endp
004046CA



PORT 3算法部分
00401940 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
00401940
00401940 ; Attributes: bp-based frame
00401940
00401940 sub_401940      proc near              ; CODE XREF: sub_40176C+130 p
00401940                                        ; sub_4045DC+59 p
00401940
00401940 var_44          = byte ptr -44h
00401940 var_30          = byte ptr -30h
00401940 var_1C          = byte ptr -1Ch
00401940 var_8          = dword ptr -8
00401940 var_4          = dword ptr -4
00401940 arg_0          = dword ptr  8
00401940 arg_4          = dword ptr  0Ch
00401940 arg_8          = dword ptr  10h
00401940
00401940                push    ebp
00401941                mov    ebp, esp
00401943                sub    esp, 44h
00401946                and    [ebp+var_8], 0
0040194A                push    ebx
0040194B                mov    ebx, [ebp+arg_0];判断第一个参数[CCG]
; Name的长度
0040194E                mov    [ebp+var_4], 0Ch
00401955                push    ebx
00401956                call    _strlen;很明显的运行库函数
0040195B                cmp    eax, 9;名字为长度为9吗?
0040195E                pop    ecx
0040195F                jnz    short loc_401976;长度不为9跳走,下去继续新判断
00401961                push    ebx;把名字做为参数
00401962                call    sub_401AA6;长度为9的话继续进这个地方去判断
00401967                cmp    eax, 3C9h;比较这个函数得到的结果为3C9h吗?
0040196C                pop    ecx
0040196D                jnz    short loc_401976;不是也跳走,下去继续新判断
0040196F                xor    eax, eax;设置标志,为合法。
00401971                jmp    loc_401AA3;饿嘿嘿,要是你名字刚好9个而且和为
;3c9的话,你就是合法了哟
00401976 ; ---------------------------------------------------------------------------
00401976
00401976 loc_401976:                            ; CODE XREF: sub_401940+1F j
00401976                                        ; sub_401940+2D j
00401976                push    ebx
00401977                call    _strlen
0040197C                cmp    eax, 10h;你的名字长度为10h吗?也就是16个字符
0040197F                pop    ecx
00401980                jnz    short loc_4019CF;不是的话,还是进入下边判断
00401982                push    ebx;再次压入用户名,我的是[CCG],当然不会走到这
;里,如果名字为16个字符的话,才会压入
00401983                call    sub_401AA6;名字计算求和
00401988                cmp    eax, 630h;结果恰好为630h吗?
0040198D                pop    ecx
0040198E                jnz    short loc_4019CF;如果不是,还是继续跳要判断注册码
;的
00401990                push    [ebp+arg_4];
00401993                lea    eax, [ebp+var_30]
00401996                push    eax
00401997                call    unknown_libname_1
0040199C                lea    eax, [ebp+var_30]
0040199F                push    2Dh
004019A1                push    eax
004019A2                call    _strchr
004019A7                add    esp, 10h
004019AA                test    eax, eax
004019AC                jnz    short loc_4019B6
004019AE                or      eax, 0FFFFFFFFh
004019B1                jmp    loc_401AA3
004019B6 ; ---------------------------------------------------------------------------
004019B6
004019B6 loc_4019B6:                            ; CODE XREF: sub_401940+6C j
004019B6                and    byte ptr [eax], 0
004019B9                inc    eax
004019BA                push    eax
004019BB                lea    eax, [ebp+var_44]
004019BE                push    eax
004019BF                call    unknown_libname_1
004019C4                pop    ecx
004019C5                lea    eax, [ebp+var_44]
004019C8                pop    ecx
004019C9                lea    ebx, [ebp+var_30]
004019CC                mov    [ebp+arg_4], eax
004019CF
004019CF loc_4019CF:                            ; CODE XREF: sub_401940+40 j
004019CF                                        ; sub_401940+4E j
004019CF                push    [ebp+arg_4];你用D这个地址就可以知道是输入的
;User :1这个部分
004019D2                call    __strlwr   
004019D7                cmp    byte ptr [ebx], 0
004019DA                pop    ecx
004019DB                jnz    short loc_4019E4
004019DD                and    byte ptr [ebx+1], 0
004019E1                mov    byte ptr [ebx], 20h
004019E4
004019E4 loc_4019E4:                            ; CODE XREF: sub_401940+9B j
004019E4                push    esi
004019E5                push    edi
004019E6                push    ebx
004019E7                call    _strlen;求字符的长度。
004019EC                pop    ecx
004019ED                mov    esi, eax;字符长度送esi
004019EF                push    11h
004019F1                mov    ecx, esi;字符长度送ecx
004019F3                pop    edi;edi=11h
004019F4                cmp    esi, edi;比较字符长度等于11h就是17个吗?
004019F6                jge    short loc_401A08;大于17个跳过去
//////////////////////////////////////////////////////////////////////////////////////////////////////////
004019F8
004019F8 loc_4019F8:                            ; CODE XREF: sub_401940+C6 j
004019F8                mov    eax, ecx;这里是个循环,被除数为eax初始为用户名
;我这里name,[CCG]字符长度ecx=5,
004019FA                cdq       
004019FB                idiv    esi      ;esi是11hó17
004019FD                mov    al, [edx+ebx];edx 中就是除法后的余数,作为一个变
;址寻址,在C中相当于一个指针,这
;里把这个以ebx为基地址的记忆体的
;数据取出,放在al中
00401A00                mov    [ecx+ebx], al;然后把结果放在,用户名字符后边
00401A03                inc    ecx       
00401A04                cmp    ecx, edi;已经附加到17个字符了吗?
00401A06                jl      short loc_4019F8;没有继续循环
//////////////////////////////////////////////////////////////////////////////////////////////////////////
/**********再次打断一下
在上边////////部分里的程序实际上是在你用户名不大于17个时,把你的用户名凑足17个,其方法是把你前边的用户名循环的加在后边凑足17个,比如我输入的[CCG]在这个循环结束时就会在记忆体中由最先的
  “[CCG]”凑成” [CCG] [CCG] [CCG] [C”这样的17个字符
  用C可以这样表示
int var,I;
char temp[18];
var=strlen(name);
for(var<17)
{
var=17/var+1;
for(I=0;I<var;I++)
strcat(temp,name);
}

***********打断结束/
00401A08
00401A08 loc_401A08:                            ; CODE XREF: sub_401940+B6 j
00401A08                push    [ebp+arg_8]
00401A0B                push    [ebp+arg_8];这下边就是高潮了,现在压的就是
;User 我的是1
00401A0E                call    _strlen;求长度
00401A13                pop    ecx
00401A14                mov    ecx, ebx;上边的17个字符串的位置送ec

00401A16                sub    ecx, eax;减去User长度
00401A18                add    ecx, edi;再加上11h这里就是把User从后边附加覆盖在
;字符串中
00401A1A                push    ecx
00401A1B                call    unknown_libname_1;
/********打断
程序经过这里后最终形成的输入字符串就是
” [CCG] [CCG] [CCG] [1”
把User 覆盖了最后一个字符,当你User有多个字符时,会向前覆盖,但是当你字符个数大于9时,只覆盖最后一个用0,表示。这些在Softice动态跟踪时可以很好的看清楚
结束******/
00401A20                or      byte ptr [ebx], 1
00401A23                pop    ecx
00401A24                pop    ecx

///////////////////////////////////////////////////////////////////////(最关键处)
00401A25                mov    eax, 1F4h ;这里就是最后的循环判断,要循环500次 
;1F4hó500嘛,够多把
00401A2A
00401A2A loc_401A2A:                            ; CODE XREF: sub_401940+10F j
00401A2A                mov    ecx, [ebp+var_8] ;最初为0
00401A2D                mov    edx, [ebp+var_4];最初为0xc
00401A30                mov    dl, [edx+ebx];变址把字符[temp+0xc]取出就是把17个
;字符中的第13个字符取出
00401A33                lea    esi, [ecx+ebx];跟第一个相加
00401A36                add    [esi], dl;跟第一个相加
00401A38                test    ecx, ecx;比较ecx,为零吗?
00401A3A                jnz    short loc_401A3F
00401A3C                mov    [ebp+var_8], edi;为零,把17个字符指向变元,让下
;次循环时,改变指针位置
00401A3F
00401A3F loc_401A3F:                            ; CODE XREF: sub_401940+FA j
00401A3F                cmp    [ebp+var_4], 0;相加的字符位置为偏移0吗?
00401A43                jnz    short loc_401A48;不是就跳
00401A45                mov    [ebp+var_4], edi;为零的话,指向最后一个字符edi=11h
00401A48
00401A48 loc_401A48:                            ; CODE XREF: sub_401940+103 j
00401A48                dec    [ebp+var_8];被加的变元指针位置减一
00401A4B                dec    [ebp+var_4];加数指针也减一
00401A4E                dec    eax;循环变元减1,
00401A4F                jnz    short loc_401A2A还没有到500次循环,继续
///////////////////////////////////////////////////////////////////////
/********分析一下
  程序在这/////////部分就是算法,它是这样处理的,在一个500次的循环中把字符串按一个规律两两相加,
” [CCG] [CCG] [CCG] [1”

用C表示就是
int var1=0xc;//第一次加数字符的相对位置
int var2=1;
for( int I=0;I<500;I++)
{var2--;//被加数的相对位置,循环一次减1
temp[var2]=temp[var2]+temp[var1];//做加法
if(var2==0)
    var2=0x11;//如果被加数为第一个字符,修改下次循环其指向最后一个字符
if (var1==0)
var1=0x11;//如果加数的位置为第一个字符,改变指针到最后一个字符
var1-- //循环减一
}
500次循环做完,得到的结果就是最后的注册码,就是取在数组中的前6个Byte对应的数字
*********/
00401A51                mov    esi, offset a0123456789abcd ; "0123456789abcdef"
00401A56                lea    edi, [ebp+var_1C]
00401A59                movsd
00401A5A                movsd
00401A5B                movsd
00401A5C                and    dword_44E168, 0
00401A63                movsd
00401A64                movsb
00401A65                mov    esi, [ebp+arg_4]
00401A68                xor    edi, edi
00401A6A
00401A6A loc_401A6A:                            ; CODE XREF: sub_401940+15A j
00401A6A                movzx  eax, byte ptr [edi+ebx]
00401A6E                mov    dl, [esi]
00401A70                mov    ecx, eax
00401A72                shr    ecx, 4
00401A75                cmp    dl, [ebp+ecx+var_1C]
00401A79                jnz    short loc_401A8B ;right can’t jump
00401A7B                mov    cl, [esi+1]
00401A7E                and    eax, 0Fh
00401A81                cmp    cl, [ebp+eax+var_1C];比较你的Key跟计算结果相等吗?
00401A85                jnz    short loc_401A8B;在这里正确都不跳,
;不然就置错误标志
00401A87                xor    eax, eax;标志0是正确
00401A89                jmp    short loc_401A8E; must to there
00401A8B ; ---------------------------------------------------------------------------
00401A8B
00401A8B loc_401A8B:                            ; CODE XREF: sub_401940+139 j
00401A8B                                        ; sub_401940+145 j
00401A8B                push    1; 要是在这里就错了
00401A8D                pop    eax
00401A8E
00401A8E loc_401A8E:                            ; CODE XREF: sub_401940+149 j
00401A8E                or      dword_44E168, eax
00401A94                inc    edi
00401A95                inc    esi
00401A96                inc    esi
00401A97                cmp    edi, 6;比较前6个byte的信息。
00401A9A                jl      short loc_401A6A;
00401A9C                mov    eax, dword_453670
00401AA1                pop    edi
00401AA2                pop    esi
00401AA3
00401AA3 loc_401AA3:                            ; CODE XREF: sub_401940+31 j
00401AA3                                        ; sub_401940+71 j
00401AA3                pop    ebx
00401AA4                leave
00401AA5                retn
00401AA5 sub_401940      endp
00401AA5
程序将这些信息写入注册表的HLM\Software\Centipede\Rops\rops中,每次启动时判断。注册机付在后边。

(转贴请保持完整)
  AllRight reserved :[CCG]
//VC 控制台下注册机
嘿嘿,小弟太菜。注册机中好多BVG,谢谢大哥指出。首先,数组变量的
分配因该在主程序外边,在里边定义会 在strcat(temp,name)时当名字
过长时,覆盖数据为啥会出现这样的情况小弟原因不明,希望哪个大哥指教;
第二,字符结尾要用0x00结束。不然会在后边显示一些怪字符.
第三,这个注册机不完美,还有些路没有去走,嘿嘿,哪个大哥把他补全。
只希望初学者去走做,对于高手这个东西也太简单了。大家一起学习。去走走看。
小弟献丑了。
include<windows.h>
  char name[100],user[10],temp[18],covert[13]; //BUG修改定义成全局变量
  int i,var,var1,var2;
  BYTE lena,lenb;

main()
{   


 
  printf("[CCG] KeyGen\n");
  printf("please input your name:\n");
  scanf("%s",name);
    getchar();
  printf("please input user number:\n");
  scanf("%s",user);
    getchar();
    var=strlen(name);
    memset(temp,0x00,18);
    if(var<17)
    {
        var=17/var+1;
      for(i=0;i<var;i++)
      strcat(temp,name);
    }
    else
      strncpy(temp,name,17);
 
    temp[17]=0x00;
    var=strlen(user);
    if(var>10)
        temp[16]=0x30;
    else
      memcpy(&temp[17-var],user,var);
//    printf("%s\n",temp);
    var1=0xc;
    var2=1;
    for(i=0;i<500;i++)
    {
     var2--;
    temp[var2]=temp[var2]+temp[var1];
     if(var2==0)
         var2=0x11;
     
      if(var1==0)
         var1=0x11;
        var1--;
    }
for(i=0;i<6;i++)
{
lena=(unsigned char)temp[i]/0x10;
lenb=(unsigned char)temp[i]&0xf;
  if(lena<10)
  covert[2*i]=lena+0x30;
  else
  covert[2*i]=lena+0x57;
  if(lenb<10)
  covert[2*i+1]=lenb+0x30;
  else
  covert[2*i+1]=lenb+0x57;
   
}
  covert[12]=0x00; //BUG最后加上结尾不然会显示错误
  printf(" key is:%s",covert);
  }