前些天在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,结果存放到第三排
将第一排和第三排的当前字符传给reg1和reg2
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以使得小循环能正确寻址到数据
然后jnz到1B25处。
做一下总结吧,现在看来呢,如果我们想写注册机的话就要把他里面的数组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就跳到成功提示信息了(或者直接退出程序)
时间问题就不写注册机了哈哈