Greedy Fly's Crackme

前些天在Crakmes.de拿到一个keygenme,作者说这是for newbies的,好吧,我比初学者还弱搞了一整天什么都没弄出来

今天思路有点点清晰了,先写点东西总结一下再说!
这是个Name/Serial型的KM(keygenme) 怎么定位确定按钮我就不讲了,直接贴确定按钮的事件代码吧

这是个Name/Serial型的KM(keygenme) 怎么定位确定按钮我就不讲了,直接贴确定按钮的事件代码吧
0040124D  call    00401461                                ;  释放一个名为Keygenme.dat的动态链接库文件
00401252  push    195                                     ; /ControlID = 195 (405.)
00401257  push    dword ptr [ebp+8]                       ; |hWnd
0040125A  call    <jmp.&user32.GetDlgItem>                ; \GetDlgItem:获取Name框句柄于eax
0040125F  mov     dword ptr [403190], eax
00401264  lea     eax, dword ptr [4032A8]
0040126A  push    eax                                     ; /lParam = 4032A8(Name存放地址)
0040126B  push    20                                      ; |wParam = 20
0040126D  push    0D                                      ; |Message = WM_GETTEXT
0040126F  push    dword ptr [403190]                      ; |hWnd = NULL
00401275  call    <jmp.&user32.SendMessageA>              ; \SendMessageA
0040127A  mov     edx, eax                                ;  edx=Len(Name)
0040127C  push    edx
0040127D  call    00401524
00401282  cmp     esi, 15D50
00401288  jnb     short 0040128F
0040128A  jmp     004013B0                                ;  长度不符合规格则退出程序
0040128F  cmp     eax, 0A
00401292  jbe     short 00401299                          ;  长度不能大于10,大于则退出程序
00401294  jmp     004013B0
00401299  push    0040302A                                ; /FileName = "keygenme.dat"
0040129E  call    <jmp.&kernel32.LoadLibraryA>            ; \LoadLibraryA 加载刚才释放的动态链接库
004012A3  or      eax, eax
004012A5  je      004013B0                                ;  加载不成功则退出程序
004012AB  mov     dword ptr [40319C], eax
004012B0  call    00401B8C                                ;  返回一片空区域00403300
004012B5  pop     edx
004012B6  push    edx                                     ; /Arg2
004012B7  push    004032A8                                ; |Arg1 = 004032A8
004012BC  call    00401BA8                                ; \Name的字符串处理函数,,下面的CALL也是(在403310创建用户拷贝)
004012C1  call    00401BF4
004012C6  mov     edi, eax
004012C8  push    20                                      ; /Length = 20 (32.)
004012CA  push    004032A8                                ; |Destination = KeygenMe.004032A8
004012CF  call    <jmp.&kernel32.RtlZeroMemory>           ; \RtlZeroMemory
004012D4  push    00403037                                ; /ProcNameOrOrdinal = "calc"
004012D9  push    dword ptr [40319C]                      ; |hModule = NULL
004012DF  call    <jmp.&kernel32.GetProcAddress>          ; \GetProcAddress
004012E4  mov     dword ptr [4031A0], eax
004012E9  push    edi                                     ;  name处理中制作出来的一个表403300
004012EA  call    dword ptr [4031A0]                      ;  DLL函数,在其中运算密码。
004012F0  push    004032A8
004012F5  push    esi
004012F6  call    00401620                                ;  也是计算函数要跟一下。
004012FB  push    dword ptr [40319C]                      ; /hLibModule = NULL
00401301  call    <jmp.&kernel32.FreeLibrary>             ; \FreeLibrary
00401306  push    28                                      ; /Count = 28 (40.)
00401308  push    004032C8                                ; |Buffer = KeygenMe.004032C8
0040130D  push    196                                     ; |ControlID = 196 (406.)
00401312  push    dword ptr [ebp+8]                       ; |hWnd
00401315  call    <jmp.&user32.GetDlgItemTextA>           ; \GetDlgItemTextA:得到密码。
0040131A  push    004032C8
0040131F  call    004010B4                                ;  密码运算
00401324  cmp     eax, 20
00401327  je      short 0040132E                          ;  密码至少要有32位
00401329  jmp     004013B0
0040132E  push    004032EA
00401333  push    004032C8
00401338  call    00401417
0040133D  lea     esi, dword ptr [4032A8]
00401343  lea     edi, dword ptr [4032EA]
00401349  mov     ecx, 10
0040134E  cld
0040134F  repe    cmps byte ptr es:[edi], byte ptr [esi]  ;  这里影响了标志位,但对JNZ没有影响
00401351  jnz     short 004013B0                          ;  call    00401417导致了jnz的跳转,所以上面的代码基本上没啥用
00401353  push    0040302A                                ; /FileName = "keygenme.dat"
00401358  call    <jmp.&kernel32.DeleteFileA>             ; \DeleteFileA
0040135D  push    66                                      ; /ControlID = 66 (102.)
0040135F  push    dword ptr [ebp+8]                       ; |hWnd
00401362  call    <jmp.&user32.GetDlgItem>                ; \GetDlgItem
00401367  push    1                                       ; /ShowState = SW_SHOWNORMAL
00401369  push    eax                                     ; |hWnd
0040136A  call    <jmp.&user32.ShowWindow>                ; \ShowWindow
0040136F  push    192                                     ; /ControlID = 192 (402.)
00401374  push    dword ptr [ebp+8]                       ; |hWnd
00401377  call    <jmp.&user32.GetDlgItem>                ; \GetDlgItem
0040137C  push    0                                       ; /Enable = FALSE
0040137E  push    eax                                     ; |hWnd
0040137F  call    <jmp.&user32.EnableWindow>              ; \EnableWindow
00401384  push    195                                     ; /ControlID = 195 (405.)
00401389  push    dword ptr [ebp+8]                       ; |hWnd
0040138C  call    <jmp.&user32.GetDlgItem>                ; \GetDlgItem
00401391  push    0                                       ; /Enable = FALSE
00401393  push    eax                                     ; |hWnd
00401394  call    <jmp.&user32.EnableWindow>              ; \EnableWindow
00401399  push    196                                     ; /ControlID = 196 (406.)
0040139E  push    dword ptr [ebp+8]                       ; |hWnd
004013A1  call    <jmp.&user32.GetDlgItem>                ; \GetDlgItem
004013A6  push    0                                       ; /Enable = FALSE
004013A8  push    eax                                     ; |hWnd
004013A9  call    <jmp.&user32.EnableWindow>              ; \EnableWindow
004013AE  jmp     short 004013CC
004013B0  push    194                                     ; /ControlID = 194 (404.)
004013B5  push    dword ptr [ebp+8]                       ; |hWnd
004013B8  call    <jmp.&user32.GetDlgItem>                ; \GetDlgItem
004013BD  push    0                                       ; /lParam = 0
004013BF  push    0                                       ; |wParam = 0
004013C1  push    0F5                                     ; |Message = BM_CLICK
004013C6  push    eax                                     ; |hWnd
004013C7  call    <jmp.&user32.SendMessageA>              ; \SendMessageA
004013CC  jmp     short 00401411
004013CE  cmp     dword ptr [ebp+10], 194
004013D5  jnz     short 00401411
004013D7  push    0040302A                                ; /FileName = "keygenme.dat"
004013DC  call    <jmp.&kernel32.DeleteFileA>             ; \DeleteFileA
004013E1  push    0                                       ; /lParam = 0
004013E3  push    0                                       ; |wParam = 0
004013E5  push    10                                      ; |Message = WM_CLOSE
004013E7  push    dword ptr [ebp+8]                       ; |hWnd
004013EA  call    <jmp.&user32.SendMessageA>              ; \SendMessageA
004013EF  jmp     short 00401411
004013F1  cmp     dword ptr [ebp+C], 10
004013F5  jnz     short 00401411
004013F7  push    dword ptr [403188]                      ; /hObject = 60050162
004013FD  call    <jmp.&gdi32.DeleteObject>               ; \DeleteObject
00401402  call    <jmp.&ole32.CoUninitialize>
00401407  push    0                                       ; /Result = 0
00401409  push    dword ptr [ebp+8]                       ; |hWnd
0040140C  call    <jmp.&user32.EndDialog>                 ; \EndDialog
00401411  xor     eax, eax
00401413  leave
00401414  retn    10
走了一遍程序,分析出来的大体过程:
根据用户名长度计算出一个值,如果这个值不在给定区间则挂
有两个函数共同处理Name,生成一个表403310
然后把表的地址传入函数calc处理(calc函数藏在一个名为keygenme.dat的dll文件里)
获取到密码后根据密码生成一个小表
然后是一个小call计算出某个值放进eax和20h比较,不符合则挂
然后又是一个对密码的Call,里面有一个循环,估计是在做与用户名匹配的计算吧  

今天我要先把calc函数之前的东西先分析出来! 
004012BC  call    00401BA8                               
004012C1  call    00401BF4
先从这两个call开始分析:
00401BA8  push    ebp
00401BA9  mov     ebp, esp
00401BAB  push    esi
00401BAC  push    edi
00401BAD  push    ebx
00401BAE  mov     ebx, dword ptr [ebp+C]                  ;  长度
00401BB1  mov     esi, dword ptr [ebp+8]                  ;  name
00401BB4  jmp     short 00401BE7
00401BB6  /mov     eax, dword ptr [403344]
00401BBB  |mov     edx, 10
00401BC0  |sub     edx, eax
00401BC2  |cmp     ebx, edx
00401BC4  |jnb     short 00401BC8                         ;  edx不可以大于ebx
00401BC6  |mov     edx, ebx
00401BC8  |lea     edi, dword ptr [eax+403310]
00401BCE  |mov     ecx, edx
00401BD0  |rep     movs byte ptr es:[edi], byte ptr [esi]
00401BD2  |sub     ebx, edx
00401BD4  |add     eax, edx
00401BD6  |cmp     eax, 10
00401BD9  |jnz     short 00401BE2
00401BDB  |call    00401A70
00401BE0  |xor     eax, eax
00401BE2  |mov     dword ptr [403344], eax
00401BE7   and     ebx, ebx
00401BE9  \jnz     short 00401BB6
00401BEB  pop     ebx
00401BEC  pop     edi
00401BED  pop     esi
00401BEE  pop     ebp
00401BEF  retn    8

 代码很长(因为里面还有一个很长的call),不过不用怕!先得出特殊结论,在总结一般性质!!
为了方便,先假设一下
403344 =table1
403310 =table2

edx先减去table1的值再跟ebx比较
如果ebx<edx的话就使得edx=ebx
然后table2+eax=edi,后面应该会有数据存到edi指向内存
ecx=edx作为循环记数
然后将用户名拷贝一份到edi指向内存
刚才edx是大于ebx的,那么当ebx大于edx的时候这里会得出
ebx比edx大多少(否则为0)
然后比较edx+eax是否等于16,如果不等于把当前eax(eax+edx)放入table1
如果等于则执行函数401A70,清空eax。这两种情况之后都要退出本函数,因为
ebx早就为0了

在这里似乎又是一次长度检测,如果不符合某条件的用户名会被进行不同的处理。 
我们输入了8个字符的用户名,不需要进行CALL,那么就先忽略这个Call吧
所以呢这个函数目前的作用呢只有两个,就是
1 把32A8处的用户名数据传送到3310
2 把403344的值置为8(本例的用户长度)
 
  00401BF2  mov     edi, edi
00401BF4  push    esi
00401BF5  push    edi
00401BF6  mov     eax, dword ptr [403344]
00401BFB  mov     edx, 10
00401C00  sub     edx, eax
00401C02  lea     edi, dword ptr [eax+403310]             ;  用长度+name的副本
00401C08  and     edx, 0FF
00401C0E  mov     eax, edx                                ;  填充内容
00401C10  mov     ecx, edx                                ;  次数
00401C12  rep     stos byte ptr es:[edi]
00401C14  call    00401A70                                ;  这个干什么的??
00401C19  mov     esi, 00403330
00401C1E  mov     edi, 00403310
00401C23  mov     ecx, 10
00401C28  rep     movs byte ptr es:[edi], byte ptr [esi]
00401C2A  call    00401A70                                ;  这个干什么的??
00401C2F  mov     eax, 00403300                           ;  返回这个表
00401C34  pop     edi
00401C35  pop     esi
00401C36  retn
在这个函数里面,他将我们的用户名和用户名长度等数据与他自身提供的数据进行异或等运算,最后将结果放在一个表里面,先写到这里具体深入的分析现在再做! 

过了N久…………
 
好吧,已经完成了对这两个函数的分析了,如下:

这个函数的作用其实就是对某一处内存拷贝来拷贝去,异或来异或去而已。

将第二排数据每四个与第一排数据的每四个Xor,结果存放到第三排

 

 

将第一排和第三排的当前字符传给reg1reg2

edx=edx xor reg1

edx寻址数组403080把寻址数据放到reg3

reg2=reg2 xor reg3

reg2被放到第三行的第一个字符

edx=reg2

这个循环有n个以上的操作,一共循环18h/4

--------------------------------------------------------------------

现在重头设过字符串指针

403300开始取字节与以eax寻址的数组403080中取的字节Xor

然后再放回当前位置。

这个操作也有很多次

循环30h/4

其实这里只是一个小循环,在小循环外面还有这样的操作

eax=eax+edx

eax=eax & ffh

edx++

ebp复原为-30以使得小循环能正确寻址到数据

然后jnz1B25处。

 

做一下总结吧,现在看来呢,如果我们想写注册机的话就要把他里面的数组403080中的数据弄下来

然后写一个跟他差不多的算法来运算出最后得出的一个表。

对了有点东西忘记了,现在先从这个有很多Xor操作的函数里面出去吧。

现在发现程序又很奇怪地把3330那排数据拷贝到了3310处然后再对这个表进行了很多Xor操作(还是用的同一个CALL

然后就把最后得出的一个表4033300返回到eax里面。

再退出到外面的一层函数我们发现403300这个表被传到了一个从GetProcAdress得来的函数处

这个函数叫做calc,是个动态链接库的导出函数,只不过作者把dll文件做成资源放在exe里面,运行的时候

才释放出来,加载然后获取其中函数calc

今天的任务结束了,终于迈出了解决难题的第一步!

 
--------------------------------------------------------------------
刚刚分析出后面的总体过程了,先写点什么吧
首先把之前算出的403300这个表传入函数calc
在calc里面又有几个函数计算出另一个表,表地址放在esi
calc函数之后将esi处的数据拷贝至004032A8处。
接下来获取密码,然后有一个函数对密码长度作检测
长度检测过关之后就传入了一两个表,一个全部为零,另一个是我们填写的密码
在这个函数里面因为影响了标志位Z,所以影响了后来的jnz跳转至错误或正确提示信息
现在开始深入分析每个函数:

进入到calc函数中:
10005AC0  push    ebp
10005AC1  mov     ebp, esp
10005AC3  push    10005C24                         ; ASCII "47B842023ABE85D739C51EEB4357DE61"
10005AC8  push    10
10005ACA  push    dword ptr [ebp+8]                ; 403300
10005ACD  call    10005B40                         ; calc the arg
10005AD2  mov     esi, 10005C3C                    ; get ret
10005AD7  mov     byte ptr [10005C2C], 0
10005ADE  mov     edi, 10005C24                    ; ASCII "47B842023ABE85D739C51EEB4357DE61"
10005AE3  push    10
10005AE5  push    10001000
10005AEA  call    10002250
10005AEF  push    10005C64
10005AF4  push    esi
10005AF5  call    100022BC
10005AFA  push    10
10005AFC  push    10001000
10005B01  call    10002250
10005B06  push    10005C84
10005B0B  push    edi
10005B0C  call    100022BC
10005B11  push    10005C84
10005B16  push    10005C64
10005B1B  call    10005BA0
10005B20  sub     esi, esi
10005B22  lea     esi, dword ptr [10005C64]
10005B28  push    40
10005B2A  push    10005C24                         ; ASCII "47B842023ABE85D739C51EEB4357DE61"
10005B2F  call    10005B38                         ; jmp 到 ntdll.RtlZeroMemory
10005B34  leave
10005B35  retn    4
调用了很多call,不过也只好慢慢地来分析了:
10005AC0  push    ebp
10005AC1  mov     ebp, esp
10005AC3  push    10005C24                         
10005AC8  push    10
10005ACA  push    dword ptr [ebp+8]                ; 403300
10005ACD  call    10005B40                         ; calc the arg
第一个call传入了三个参数,第一个是一块空表10005c24(顺着push的顺序来说,免得混淆)
第二个是常数10,进到里面之后我们会知道这是一个循环变量
第三个是我们熟悉的403300,就是之前分析出来的两个函数对Name的运算结果
现在进入这个CALL看看:
10005B40  push    ebp
10005B41  mov     ebp, esp
10005B43  push    edi
10005B44  push    esi
10005B45  push    ebx
10005B46  mov     ebx, dword ptr [ebp+C]           ; 10
10005B49  mov     edi, dword ptr [ebp+10]          ; 1005c24
10005B4C  test    ebx, ebx
10005B4E  mov     esi, dword ptr [ebp+8]           ; 403300
10005B51  je      short 10005B89                   ; ebx=0就跑路
10005B53  movzx   eax, byte ptr [esi]
10005B56  mov     ecx, eax
10005B58  add     edi, 2
10005B5B  shr     ecx, 4
10005B5E  and     eax, 0F
10005B61  and     ecx, 0F
10005B64  cmp     eax, 0A
10005B67  sbb     edx, edx
10005B69  adc     eax, 0
10005B6C  lea     eax, dword ptr [eax+edx*8+37]
10005B70  cmp     ecx, 0A
10005B73  sbb     edx, edx
10005B75  adc     ecx, 0
10005B78  shl     eax, 8
10005B7B  lea     ecx, dword ptr [ecx+edx*8+37]
10005B7F  or      eax, ecx
10005B81  inc     esi
10005B82  mov     word ptr [edi-2], ax
10005B86  dec     ebx
10005B87  jnz     short 10005B53
10005B89  mov     eax, edi
10005B8B  mov     byte ptr [edi], 0
10005B8E  sub     eax, dword ptr [ebp+10]          ; 生成表的长度放在eax
10005B91  pop     ebx
10005B92  pop     esi
10005B93  pop     edi
10005B94  pop     ebp
10005B95  retn    0C
我分析出来的东西:
 每次从esi所指内存挪一个字节到eax和ecx,然后edi+=2
先将ecx右移四位
然后eax和ecx都 & fh
后面的操作分为两部分
第一部分
cmp eax,0a
如果上面换算出来的eax>a则 CF=0,导致下面的edx=0和adc操作将+1
                     否则 CF=1, 导致下面的edx=-1和adc操作将无影响
edx=0/-1
eax=eax+1/0
eax=edx*8+eax+37

cmp ecx,0a
如果上面换算出来的ecx>a则 CF=0,导致下面的edx=0和adc操作将+1
                     否则 CF=1, 导致下面的edx=-1和adc操作将无影响
edx=0/-1
ecx=ecx+1/0
ecx=edx*8+eax+37
---------------------
然后将eax左移8位之后or eax,ecx
esi指向下个字符
ax放到新表内
ebx--(循环变量)
最后把生成表的长度放在eax
------------------------------------------------------------------------------------------------
我看了看这个keygenme的calc函数的后面的代码,吓了我一大跳!要逆向的东西真是超多!所以我决定还是先把calc函数放一放,写注册机的时候直接把dll载入然后用他的calc函数就好了
------------------------------------------------------------------------------------------------ 
接下来继续!
之前看错了以为40131F那里也是什么有意义的函数,原来是个获取密码长度的函数(密码长度应为32位)
再看401338处的函数,这个函数将我们输入的密码做一下换算弄出一个表来,过程如下:
每次从password中拿一个字节到edx,如果小于40h则ebx=-1 大于则ebx=0
edx=edx-37h
ebx=ebx & 7
指针指向下个字符
ebx+edx大于0则结束函数
小于0则:
eax=ebx
eax=eax<<4
[empty]=al
然后又跟40142D那里一样………如果最终ebx为正数则退出
为负数则:
ebx=ebx & fh
eax=eax+ebx
[empty]=al
一直这样循环下去直到填满empty这个表
-------------------------------------------------
接下来的代码:
0040133D  lea     esi, dword ptr [4032A8]
00401343  lea     edi, dword ptr [4032EA]
00401349  mov     ecx, 10
0040134E  cld
0040134F  repe    cmps byte ptr es:[edi], byte ptr [esi]   ;  两个表比较,下面的jnz根据标志位跳转到成功提示
00401351  jnz     short 004013B0
在这段代码中将calc中计算出来的表和刚才从我们的密码中换算出来的表做比较(repe cmps意思是相等则继续比较,不等则置ZF为0)
如果ZF条件满足jnz就跳到成功提示信息了(或者直接退出程序) 
时间问题就不写注册机了哈哈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值