首页
社区
课程
招聘
逆向lnn1123的KeyGenMe #1(晕,搞了五天啊)
发表于: 2006-6-3 21:59 6657

逆向lnn1123的KeyGenMe #1(晕,搞了五天啊)

2006-6-3 21:59
6657

前言:本来最初是想弄#2的,弄了两三天还是理不清头绪,
又下了#1回来,风格跟#2差不多,但运算简单些。

使用工具:
反编译及调试工具:OllyDbg,IDA,Resource Hacker
程序开发包:MASM32

KeyGenMe下载地址:http://www.crackmes.de/users/crackerlnn/keygenme_1/

第一步:还原界面代码

  首先用OllyDbg加载以查看是否有花指令。代码区域
还算清晰,并没有夹杂数据。然后用IDA加载分析。

  入口处代码如下:

============================
                public start
start                proc near
                push        NULL                ; lpModuleName
                call        GetModuleHandleA
                mov        hInstance, eax
                push        0                ; dwInitParam
                push        offset DialogFunc ; lpDialogFunc
                push        NULL                ; hWndParent
                push        DLGEX_MAIN        ; lpTemplateName
                push        hInstance        ; hInstance
                call        DialogBoxParamA
                push        eax                ; uExitCode
                call        ExitProcess
start                endp
============================

这是一个使用对话框界面的程序。对话框需要使用资源,
用Resource Hacker把它的资源提取出来辅助分析,根据
资源脚本中的ID号给代码中的常数增加一些符号定义,如
上面的DLGEX_MAIN等等。

  对话框的行为是在回调过程中描述的,下面来看这个
回调过程的代码:

============================
; Attributes: bp-based frame

; BOOL __stdcall DialogFunc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
DialogFunc        proc near                ; DATA XREF: start+Eo

hDlg                = dword        ptr  8
uMsg                = dword        ptr  0Ch
wParam                = dword        ptr  10h
lParam                = dword        ptr  14h

                push        ebp
                mov        ebp, esp
                cmp        [ebp+uMsg], WM_INITDIALOG
                jnz        loc_401CF2
                push        [ebp+hDlg]
                pop        hDlgMain
                push        ICO_MAIN        ; lpIconName
                push        hInstance        ; hInstance
                call        LoadIconA
                mov        hIcoMain, eax
                push        hIcoMain        ; lParam
                push        ICON_BIG        ; wParam
                push        WM_SETICON        ; Msg
                push        [ebp+hDlg]        ; hWnd
                call        SendMessageA
                push        EDT_SERIAL        ; nIDDlgItem
                push        [ebp+hDlg]        ; hDlg
                call        GetDlgItem
                mov        hTextSerial, eax
                push        EDT_NAME        ; nIDDlgItem
                push        [ebp+hDlg]        ; hDlg
                call        GetDlgItem
                mov        hTextName, eax
                xor        eax, eax
                push        0                ; nFileSystemNameSize
                push        NULL                ; lpFileSystemNameBuffer
                push        NULL                ; lpFileSystemFlags
                push        NULL                ; lpMaximumComponentLength
                push        offset VolumeSerialNumber ; lpVolumeSerialNumber
                push        0                ; nVolumeNameSize
                push        NULL                ; lpVolumeNameBuffer
                push        offset RootPathName ; "c:\\\\"
                call        GetVolumeInformationA
                mov        eax, VolumeSerialNumber
                xor        eax, 12345678h
                mov        dword_403091, eax
                jmp        loc_401EDC

loc_401CF2:                                ; CODE XREF: DialogFunc+Aj
                cmp        [ebp+uMsg], WM_COMMAND
                jnz        loc_401ECC
                mov        eax, [ebp+wParam]
                cmp        eax, ID_CHECK   ; 按下Check按钮
                jnz        loc_401EAC
                push        10h
                push        offset MD5_of_Name
                call        RtlZeroMemory
                push        28h                ; nMaxCount
                push        offset szNameString ; lpString
                push        hTextName        ; hWnd
                call        GetWindowTextA
                push        offset MD5_of_Name ; lpszOutput
                push        eax                ; uLenInput
                push        offset szNameString ; lpszInput
                call        MD5_Transform
                push        ebx
                push        esi
                push        edi
                push        ebp
                xor        edx, edx
                xor        eax, eax
                lea        esi, MD5_of_Name
                mov        ecx, 4

loc_401D4E:                                ; CODE XREF: DialogFunc+FCj
                or        edx, eax
                xor        eax, eax
                lodsb
                shl        edx, 8
                loop        loc_401D4E
                or        edx, eax
                mov        dword_40309D, edx ; 这里存放MD5_of_Name第一个双字的字节反序
                mov        ecx, 4
                xor        eax, eax
                xor        edx, edx

loc_401D69:                                ; CODE XREF: DialogFunc+115j
                or        edx, eax
                lodsb
                shl        edx, 8
                loop        loc_401D69
                or        edx, eax
                mov        dword_403095, edx ; 这里存放MD5_of_Name第二个双字的字节反序
                push        0Ch                ; nCount
                push        offset aChinacracker ; "ChinaCracker"
                call        sub_4019D8
                push        offset dword_403095
                push        offset dword_40309D
                call        sub_40197C
                nop
                nop
                nop
                mov        eax, 4
                push        28h                ; nMaxCount
                push        offset szSerialString ;        lpString
                push        hTextSerial        ; hWnd
                call        GetWindowTextA
                cmp        eax, 10h        ; 序列号长度必须为16
                jnz        loc_401E93

                mov        ecx, 8
                xor        eax, eax
                xor        edx, edx
                lea        esi, szSerialString
                lea        edi, unk_4031AC

loc_401DCC:                                ; CODE XREF: DialogFunc+1C0j
                movzx        eax, byte ptr [esi] ; 取得序列号所代表的16进制数值
                cmp        eax, 'a'
                jb        short loc_401DD9
                sub        eax, 57h
                jmp        short loc_401DEB

loc_401DD9:                                ; CODE XREF: DialogFunc+178j
                cmp        eax, 'A'
                jb        short loc_401DE3
                sub        eax, 37h
                jmp        short loc_401DEB

loc_401DE3:                                ; CODE XREF: DialogFunc+182j
                cmp        eax, '0'
                jb        short loc_401DEB
                sub        eax, 30h

loc_401DEB:                                ; CODE XREF: DialogFunc+17Dj
                                        ; DialogFunc+187j DialogFunc+18Cj
                shl        eax, 4
                inc        esi
                movzx        edx, byte ptr [esi]
                cmp        edx, 'a'
                jb        short loc_401DFC
                sub        edx, 57h
                jmp        short loc_401E0E

loc_401DFC:                                ; CODE XREF: DialogFunc+19Bj
                cmp        edx, 'A'
                jb        short loc_401E06
                sub        edx, 37h
                jmp        short loc_401E0E

loc_401E06:                                ; CODE XREF: DialogFunc+1A5j
                cmp        edx, '0'
                jb        short loc_401E0E
                sub        edx, 30h

loc_401E0E:                                ; CODE XREF: DialogFunc+1A0j
                                        ; DialogFunc+1AAj DialogFunc+1AFj
                add        eax, edx
                and        eax, 0FFh
                mov        [edi], al
                inc        edi
                inc        esi
                dec        ecx
                jnz        short loc_401DCC
                mov        ecx, 8
                lea        esi, dword_40435E
                lea        edi, unk_4031C0
                rep movsb
                push        offset aBcgFcgDfcg ; "[BCG][FCG][DFCG]"
                push        offset unk_4031AC ; lpszDest
                call        sub_401B84
                pop        ebp
                pop        edi
                pop        esi
                pop        ebx
                xor        eax, eax
                mov        ecx, 8
                lea        esi, unk_4031AC
                lea        edi, unk_4031C0
                repe cmpsb              ; 关键比较
                or        eax, ecx
                jz        short loc_401E74
                push        MB_ICONERROR        ; uType
                push        offset Caption        ; "KeyGen #1"
                push        offset aSerialError ; "Serial error"
                push        hDlgMain        ; hWnd
                call        MessageBoxA
                jmp        short loc_401EDC

loc_401E74:                                ; CODE XREF: DialogFunc+1FFj
                push        MB_ICONINFORMATION ; uType
                push        offset Caption        ; "KeyGen #1"
                push        offset aWellDoneNowCod ; "Well Done! Now Code a        KeyGen"
                push        hDlgMain        ; hWnd
                call        MessageBoxA
                xor        eax, eax
                leave
                retn        10h

loc_401E93:                                ; CODE XREF: DialogFunc+157j
                push        MB_ICONERROR        ; uType
                push        offset Caption        ; "KeyGen #1"
                push        offset aLengthOfSerial ; "length of Serial error"
                push        hDlgMain        ; hWnd
                call        MessageBoxA
                jmp        short loc_401EDC

loc_401EAC:                                ; CODE XREF: DialogFunc+ADj
                cmp        eax, ID_ABOUT   ; 按下About按钮
                jnz        short loc_401EDC
                push        MB_ICONINFORMATION ; uType
                push        offset aAbout        ; "aBout"
                push        offset aKeygenme1ByLnn ; "KeyGenMe #1  By lnn1123\n\rCode With Win3"...
                push        hDlgMain        ; hWnd
                call        MessageBoxA
                jmp        short loc_401EDC

loc_401ECC:                                ; CODE XREF: DialogFunc+9Fj
                cmp        [ebp+uMsg], WM_CLOSE
                jnz        short loc_401EDC
                push        0                ; nResult
                push        [ebp+hDlg]        ; hDlg
                call        EndDialog

loc_401EDC:                                ; CODE XREF: DialogFunc+93j
                                        ; DialogFunc+218j DialogFunc+237j
                                        ; DialogFunc+250j DialogFunc+257j
                                        ; DialogFunc+270j DialogFunc+276j
                xor        eax, eax
                leave
                retn        10h
DialogFunc        endp
============================

对话框的初始化代码除了建立图标外,还取得两个输入框
的句柄。按下About按钮,显示一些关于程序的信息。按
下Check按钮,则调用GetWindowText到两个输入框中取文
本,用户名最多取40个字符,而序列号的长度必须为16,
否则显示“length of serial error”。取完文本后开始
检验注册码,检验失败显示“Serial error”,检验成功
则显示“Well Done! Now Code a KeyGen”。这些就是对
话框的基本行为。GetVolumeInformation函数调用返回的
结果在验证注册码过程中没有用到,可以当作冗余代码删
掉。剩下的部分就基本上可以照搬了。

第二步:注册码验证算法分析

  由关键比较部分的代码,注册码检验成功的标志是:
   qword_4031AC == qword_4031C0

(用qword_xxxxxx作为qword ptr [xxxxxx]的简写,下
同。)往上看不远处还可以总结出一个依赖关系:

   qword_4031C0 = qword_40435E

其他的依赖关系,静态分析就不太好找了。用OllyDbg跟
踪程序,并监视所关心的内存单元。可以得出如下结论:

  qword_4031AC在过程sub_401B84中被改写
  调用前:qword_4031AC = 序列号字节反序的16进制数值
  (如输入字符串"FEDCBA9876543210",则qword_4031AC = 1032547698BADCFE,
  在内存中存放为FE DC BA 98 76 54 32 10)

  qword_4031C0 = qword_40435E
  qword_40435E在过程sub_4019D8(调用于401D80)中被改写
  sub_4019D8运算过程完全使用程序自带数据,写入qword_40435E的值:0DDBAFB5BA2F07B6Ah
  qword_40435E在过程sub_40197C(调用于401D8F)被改写

  过程sub_40197C中:

  qword_40435E = qword_403316 xor qword_temp
  qword_temp以MD5_of_Name前两个双字的字节反序作为输入,经一些变换而得
  变换中读取内存区域:4032D6――403316,在sub_4019D8被写入数值(不依赖于输入)
  qword_403316在过程调用sub_4019D8中被写入数值(不依赖于输入):0E148481EF3EB9F33h

这里为了条理清晰起见,直接把后来总结的变量依赖关系
一次性列出。而实际过程则不是这么简单。事实上,当发
现qword_40435E在sub_4019D8中被改写时,我就迫不及待
地跟进去。这个过程中首先是复制一个长达4KB的数据块,
然后一大堆移位,异或,调用这个过程,调用那个过程,
大量的全局变量操作……弄了整整一天,也没弄清它在做
什么。偶然之间,发现怎么不见输入数据,诸如用户名之
类的东西参与运算?整个过程中就只有程序自带的数据在
做运算。猛然之间恍然大悟,既然如此,运算出来的结果
就不会依赖于输入,是一个常数,也就是说,我们根本不
用关心里面究竟做了什么运算,只要看它最后返回的数值
是什么就行了!为了确认起见,尝试输入不同的用户名及
序列号,步过过程sub_4019D8,再查看qword_40435E的内
容,果然每次都是一样!

  过程sub_40197C中最后一次改写qword_40435E,这次
过程调用带了两个参数,分别是MD5_of_Name的前两个双
字的字节反序。既然涉及到用户名,这次就不能不管具体
运算了。跟进去一看:

============================
sub_40197C        proc near                ; CODE XREF: sub_4019D8+A2p
                                        ; sub_4019D8+DAp DialogFunc+135p

counter                = dword        ptr -4
arg_0                = dword        ptr  8
arg_4                = dword        ptr  0Ch

                push        ebp
                mov        ebp, esp
                add        esp, 0FFFFFFFCh
                push        ebx
                push        edi
                push        esi
                push        edx
                push        ecx
                mov        eax, [ebp+arg_0]
                mov        ecx, [ebp+arg_4]
                mov        eax, [eax]
                mov        esi, [ecx]
                mov        edi, offset dword_4032D6
                mov        [ebp+counter], 10h
                mov        ebx, edi

loc_40199F:                                ; CODE XREF: sub_40197C+3Bj
                xor        eax, [ebx]
                mov        edx, eax
                push        eax
                call        sub_401910
                mov        ecx, [ebp+counter]
                xor        eax, esi
                add        ebx, 4
                dec        ecx
                mov        esi, edx
                mov        [ebp+counter], ecx
                jnz        short loc_40199F
                mov        ecx, [edi+40h]        ; 403316
                mov        edx, [edi+44h]        ; 40331A
                xor        ecx, eax
                xor        edx, esi
                mov        dword_404362, edx
                mov        dword_40435E, ecx
                pop        ecx
                pop        edx
                pop        esi
                pop        edi
                pop        ebx
                leave
                retn        8
sub_40197C        endp
============================

这里在对作为参数输入的两个双字进行了一大堆运算以
后,才从qword_403316中取出数值与其异或后写入到
qword_40435E中,而qword_40435E原来的值竟然没有用到
就被覆盖了。qword_403316的数值也是在sub_4019D8中写
入的,同样道理,这个值也是个常数。其中涉及到的
sub_401910过程的代码如下:

============================
sub_401910        proc near                ; CODE XREF: sub_40197C+28p

arg_0                = dword        ptr  8

                push        ebp
                mov        ebp, esp
                push        ebx
                push        edi
                push        esi
                push        edx
                push        ecx
                mov        ecx, [ebp+arg_0]
                mov        al, cl
                and        eax, 0FFh
                shr        ecx, 8
                mov        edx, eax
                mov        al, cl
                mov        edi, offset dword_4032D6
                and        eax, 0FFh
                shr        ecx, 8
                mov        esi, eax
                mov        eax, ecx
                shr        eax, 8
                and        eax, 0FFh
                and        ecx, 0FFh
                and        esi, 0FFFFh
                and        edx, 0FFFFh        ; 至此:edx,esi,ecx,eax
                                        ; 分别含有arg_0的第1,2,3,4
                                        ; 字节的零扩展
                mov        eax, [edi+eax*4+48h]
                mov        ebx, [edi+ecx*4+448h]
                mov        ecx, [edi+esi*4+848h]
                add        eax, ebx
                xor        eax, ecx
                mov        ecx, [edi+edx*4+0C48h]
                add        eax, ecx
                pop        ecx
                pop        edx
                pop        esi
                pop        edi
                pop        ebx
                leave
                retn        4
sub_401910        endp
============================

这里涉及一个1024个双字的表,具体就不列出来了。最后
书写代码的时候把这段代码提取出来嵌入源码中就行了。

  至于qword_4031AC,它是在过程sub_401B84中被改写
的,查看一下这个过程的代码:

============================
; int __stdcall        sub_401B84(int lpszDest,int lpParam1)
sub_401B84        proc near                ; CODE XREF: DialogFunc+1DFp

lpszDest        = dword        ptr  8
lpParam1        = dword        ptr  0Ch

                push        ebp
                mov        ebp, esp
                pusha
                mov        esi, [ebp+lpParam1]
                mov        eax, [esi]
                mov        ebx, [esi+4]
                mov        ecx, [esi+8]
                mov        edx, [esi+0Ch]
                mov        dword_4053C0, eax
                mov        dword_4053C4, ebx
                mov        dword_4053C8, ecx
                mov        dword_4053CC, edx
                push        ebp
                mov        ebx, [ebp+lpszDest]
                mov        edx, 0C6EF3720h
                mov        esi, [ebx]
                mov        edi, [ebx+4]
                mov        ebp, 20h

loc_401BC0:                                ; CODE XREF: sub_401B84+83j
                mov        eax, esi
                mov        ebx, esi
                mov        ecx, esi
                shl        eax, 4
                add        eax, dword_4053C8
                shr        ebx, 5
                add        ebx, dword_4053CC
                add        ecx, edx
                xor        ecx, eax
                xor        ecx, ebx
                sub        edi, ecx
                mov        eax, edi
                mov        ebx, eax
                mov        ecx, eax
                shl        eax, 4
                add        eax, dword_4053C0
                shr        ebx, 5
                add        ebx, dword_4053C4
                add        ecx, edx
                xor        ecx, eax
                xor        ecx, ebx
                sub        esi, ecx
                sub        edx, 9E3779B9h
                dec        ebp
                jnz        short loc_401BC0
                mov        dword_4053C0, ebp
                mov        dword_4053C4, ebp
                mov        dword_4053C8, ebp
                mov        dword_4053CC, ebp
                pop        ebp
                mov        ebx, [ebp+lpszDest]
                mov        [ebx], esi
                mov        [ebx+4], edi
                popa
                leave
                retn        8
sub_401B84        endp
============================

这个过程中又有一个冗长的计算过程。如果这里不去管
这个算法的细节,我们得到的注册成功方程式将会是:

  sub_40197C(前2双字(MD5(用户名))) == sub_401B84(序列号)

(注:从过程sub_401000中的67452310,0EFCDAB89这些
常数容易识别出它是MD5算法,故重命名为
MD5_Transform。可用MD5计算器验证之)MD5算法是不可
逆的,无法从序列号出发获得对应的用户名,那就只能
从用户名来计算序列号。所以对于sub_401B84这个过程
必须求出它的逆变换来。注意到过程中有一条指令:
sub edx, 9E3779B9h,再将代码页面往上翻页,看到:

============================
JunkProc        proc near

arg_0                = dword        ptr  8
arg_4                = dword        ptr  0Ch

                push        ebp
                mov        ebp, esp
                pusha
                mov        esi, [ebp+arg_4]
                mov        eax, [esi]
                mov        ebx, [esi+4]
                mov        ecx, [esi+8]
                mov        edx, [esi+0Ch]
                mov        dword_4053C0, eax
                mov        dword_4053C4, ebx
                mov        dword_4053C8, ecx
                mov        dword_4053CC, edx
                push        ebp
                mov        ebx, [ebp+arg_0]
                xor        edx, edx
                mov        esi, [ebx]
                mov        edi, [ebx+4]
                mov        ebp, 20h

loc_401B15:                                ; CODE XREF: JunkProc+80j
                add        edx, 9E3779B9h
                mov        eax, edi
                mov        ecx, eax
                mov        ebx, edi
                shl        eax, 4
                shr        ebx, 5
                add        eax, dword_4053C0
                add        ebx, dword_4053C4
                add        ecx, edx
                xor        ecx, eax
                xor        ecx, ebx
                add        esi, ecx
                mov        eax, esi
                mov        ebx, esi
                mov        ecx, esi
                shl        eax, 4
                shr        ebx, 5
                add        eax, dword_4053C8
                add        ebx, dword_4053CC
                add        ecx, edx
                xor        ecx, eax
                xor        ecx, ebx
                add        edi, ecx
                dec        ebp
                jnz        short loc_401B15
                mov        dword_4053C0, ebp
                mov        dword_4053C4, ebp
                mov        dword_4053C8, ebp
                mov        dword_4053CC, ebp
                pop        ebp
                mov        ebx, [ebp+arg_0]
                mov        [ebx], esi
                mov        [ebx+4], edi
                popa
                leave
                retn        8
JunkProc        endp
============================

这个过程本来没有被程序调用到,IDA也就没有分析,所
以起名叫JunkProc。这里却有一条add edx, 9E3779B9h
指令,会不会这个过程就是sub_401B84的逆变换?为了
验证这一猜测,转到OllyDbg中,在主程序中调用
sub_401B84之前,先记下qword_4031AC的值。单步调用
完以后,立即把call语句修改为call 00401ADC,也就是
上面这个JunkProc的地址,然后重置EIP到push第一个参
数的语句上,以同样的参数调用JunkProc。果然,
qword_4031AC的值被还原了!

  这一来,计算注册码的方程式应该是:

  JunkProc(sub_40197C(前2双字(MD5(用户名)))) == 序列号

根据模块化程序设计的思想把算法重写一遍,尽量去掉
不必要的全局变量(从程序反汇编的代码来看,其中使
用了不少全局变量,这也是造成程序难以跟踪的原因之
一):

  验证注册码:(参数:用户名指针,序列号指针)
   局部变量:MD5值缓冲区
        注册码的16进制数值
   
   将用户名计算出MD5值存入缓冲区……
   将MD5值前2个双字作字节反序……
   代入sub_40197C中变换……
   获得注册码所示的16进制数值(逆序)
   代入sub_401B84中变换……
   比较二者的变换结果是否相等:
   返回:非0(不相等)0(相等)

第三步:KeyGen

  由上面给出的方程式,只须把验证注册码的过程稍
作修改,即可用来生成注册码。也就是说在sub_40197C
变换完后,不是转而处理输入码,而是直接把变换结果
再代入到那个JunkProc中处理,最后设法把内存数值转
换为字符串,就能得到正确的注册码了。

  依照这种想法,可以用原来的界面稍作修改(如把
Check按钮的标题改为Generate等)作为KeyGen界面,
然后在源代码中加入条件汇编,依照一定条件确定编译
原程序代码还是注册机代码,具体实现过程从略。

上传文件内容:

KeyGenMe.asm 还原的源程序(包含注册机条件汇编)
reversed.rc   原程序资源脚本
KeyGen.rc     注册机资源脚本
Icon_1.ico  图标


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (5)
雪    币: 222
活跃值: (40)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
2
过程sub_4019D8是blowfish初始化密钥
JunkProc是TEA加密函数
2006-6-3 22:56
0
雪    币: 370
活跃值: (78)
能力值: ( LV9,RANK:970 )
在线值:
发帖
回帖
粉丝
3
耐心不错,值得嘉奖。
2006-6-3 23:14
0
雪    币: 234
活跃值: (370)
能力值: ( LV9,RANK:530 )
在线值:
发帖
回帖
粉丝
4
呵呵,asm写的逆向相对容易一些 支持~
2006-6-4 17:14
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习一下~~ 呵呵
2006-6-5 16:03
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
最初由 冲天剑 发布
前言:本来最初是想弄#2的,弄了两三天还是理不清头绪,
又下了#1回来,风格跟#2差不多,但运算简单些。


使用工具:
........

非常详细,新手学习。。
2006-6-8 00:17
0
游客
登录 | 注册 方可回帖
返回
//