【文章标题】: 蛇年捏软柿子之 - ASA KeygenMe#2
【文章作者】: 返璞归真
【作者邮箱】: fobnn@vip.qq.com
【软件名称】: ASA KeygenMe#2
【下载地址】: KeygenMe#2.rar
【软件大小】: 2.2KB
【加壳方式】: none
【编写语言】: VC
【操作平台】: WINALL
【软件介绍】: 一个简单的KeyGenMe
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
拿到Keygenme 首先跑下。
初看可以得出几点。
1.有机器码参与注册码的计算。(不同机器的机器码应该不同)
2.用户名有最少长度限制。
如图。
下面OD载入。
可以看出是没有壳的。一个简单的对话框程序。程序逻辑十分清晰,故直接静态分析即可。
00401000 > 6A 00 push 0x0 ;OEP处
00401002 E8 91030000 call <jmp.&kernel32.GetModuleHandleA>
00401007 A3 C0304000 mov dword ptr ds:[0x4030C0],eax
0040100C E8 B1030000 call <jmp.&comctl32.InitCommonControls>
00401011 6A 00 push 0x0
00401013 68 2D104000 push KeygenMe.0040102D
;DialogBoxParamA的第倒数第二个参数是指向窗体过程函数的指针,我们直接去看窗体过程即可。
(这里要理解为什么是第二个push 简单讲就是参数压栈顺序是从右向左的)
00401018 6A 00 push 0x0
0040101A 6A 65 push 0x65
0040101C FF35 C0304000 push dword ptr ds:[0x4030C0]
00401022 E8 35030000 call <jmp.&user32.DialogBoxParamA>
;创建窗体进入窗体循环等等。。
00401027 50 push eax
00401028 E8 65030000 call <jmp.&kernel32.ExitProcess>
;结束了。。
--------------------------------------------窗体过程分析------------------------------------------
0040102D 55 push ebp
0040102E 8BEC mov ebp,esp
00401030 817D 0C 1001000>cmp dword ptr ss:[ebp+0xC],0x110
;ebp+0xC正好指向的是WPARAM 是否是 WM_INITDIALOG
00401037 75 3D jnz short KeygenMe.00401076
;是WM_INITDIALOG到这里处理了
00401039 68 DB070000 push 0x7DB
0040103E FF35 C0304000 push dword ptr ds:[0x4030C0]
00401044 E8 2B030000 call <jmp.&user32.LoadIconA>
;这里LoadIcon
00401049 50 push eax
0040104A 6A 01 push 0x1
0040104C 68 80000000 push 0x80
00401051 FF75 08 push dword ptr ss:[ebp+0x8]
00401054 E8 27030000 call <jmp.&user32.SendMessageA>
;这里SetIcon
00401059 68 E9030000 push 0x3E9
0040105E FF75 08 push dword ptr ss:[ebp+0x8]
00401061 E8 02030000 call <jmp.&user32.GetDlgItem>
00401066 50 push eax
00401067 E8 20030000 call <jmp.&user32.SetFocus>
0040106C FF75 08 push dword ptr ss:[ebp+0x8]
0040106F E8 5D000000 call KeygenMe.004010D1
;关键CALL猜想应该是获取机器码然后放到机器码的那个控件里
00401074 EB 55 jmp short KeygenMe.004010CB
;到此处理WM_INITDIALOG结束
00401076 817D 0C 1101000>cmp dword ptr ss:[ebp+0xC],0x111
;是否是WM_COMMAND
0040107D 75 3C jnz short KeygenMe.004010BB
;这里开始处理WM_COMMAND
0040107F 817D 10 EB03000>cmp dword ptr ss:[ebp+0x10],0x3EB
;ebp+0x10正好是 LOWORD(wParam) 至于为什么,大家模拟下参数压入的顺序即可。
00401086 75 1E jnz short KeygenMe.004010A6
;控件ID正好是0x3EB,猜想应该是对注册码进行校验(check)按钮的事件
00401088 FF75 08 push dword ptr ss:[ebp+0x8]
0040108B E8 DB010000 call KeygenMe.0040126B
00401090 85C0 test eax,eax
00401092 74 10 je short KeygenMe.004010A4
00401094 FF75 08 push dword ptr ss:[ebp+0x8]
00401097 E8 B9000000 call KeygenMe.00401155
0040109C FF75 08 push dword ptr ss:[ebp+0x8]
0040109F E8 2F020000 call KeygenMe.004012D3
004010A4 EB 25 jmp short KeygenMe.004010CB
;处理控件ID为0x3EB的WM_COMMAND消息结束
004010A6 817D 10 EC03000>cmp dword ptr ss:[ebp+0x10],0x3EC
004010AD 75 1C jnz short KeygenMe.004010CB
;控件ID正好是0x3EC
004010AF 6A 00 push 0x0
004010B1 FF75 08 push dword ptr ss:[ebp+0x8]
004010B4 E8 A9020000 call <jmp.&user32.EndDialog>
004010B9 EB 10 jmp short KeygenMe.004010CB
;处理控件ID为0x3EC的WM_COMMAND消息结束
004010BB 837D 0C 10 cmp dword ptr ss:[ebp+0xC],0x10
;是否是WM_CLOSE消息?
004010BF 75 0A jnz short KeygenMe.004010CB
;WM_CLOSE开始处理
004010C1 6A 00 push 0x0
004010C3 FF75 08 push dword ptr ss:[ebp+0x8]
004010C6 E8 97020000 call <jmp.&user32.EndDialog>
;WM_CLOSE结束
004010CB 33C0 xor eax,eax
004010CD C9 leave
004010CE C2 1000 retn 0x10
通过分析窗体过程可以得出KeyGenME的Close按钮ID是0x3EC,Check按钮的ID是0x3EB
我们下面主要分析0x3EB这个分支的处理即可。
程序的流程如下。
创建一个对话框。
处理WM_INITDIALOG消息时计算出机器码并且显示到界面上。
消息处理逻辑里相应了界面上2个按钮的点击消息。
==========================================================================================================
机器码计算的分析
回到上面处理机器码的分布
00401059 68 E9030000 push 0x3E9
0040105E FF75 08 push dword ptr ss:[ebp+0x8]
00401061 E8 02030000 call <jmp.&user32.GetDlgItem>
00401066 50 push eax
00401067 E8 20030000 call <jmp.&user32.SetFocus>
0040106C FF75 08 push dword ptr ss:[ebp+0x8]
0040106F E8 5D000000 call KeygenMe.004010D1
;可以很清晰的看出首先获取父窗体HWND 然后调用004010D1这个call,来获取并且显示机器码,具体大家可以参考API原型
;004010D1这个call很明显就一个参数我们命名为 void GetHardWareCode( HWND hWindow )
下面我们一起来到004010D1这个函数体内
004010D1 55 push ebp
004010D2 8BEC mov ebp,esp
004010D4 83C4 F8 add esp,-0x8
004010D7 68 00020000 push 0x200
004010DC 68 0C314000 push KeygenMe.0040310C
004010E1 E8 C4020000 call <jmp.&kernel32.RtlZeroMemory>
004010E6 6A 20 push 0x20
004010E8 68 EC304000 push KeygenMe.004030EC
004010ED E8 B8020000 call <jmp.&kernel32.RtlZeroMemory>
;上面清空了程序中的两个变量,这2个变量在程序中多次被使用到。0040310C长度为0x200字节,004030EC长度为0x20字节
004010F2 C705 00304000 0>mov dword ptr ds:[0x403000],0x200
004010FC 68 EC304000 push KeygenMe.004030EC
00401101 E8 9E020000 call <jmp.&kernel32.GlobalMemoryStatus>
00401106 A1 F4304000 mov eax,dword ptr ds:[0x4030F4]
0040110B 8945 F8 mov dword ptr ss:[ebp-0x8],eax
;GlobalMemoryStatus被调用004030EC其实是MEMORYSTATUS结构的第一个字节,具体结构如下
typedef struct _MEMORYSTATUS {
DWORD dwLength;
DWORD dwMemoryLoad;
SIZE_T dwTotalPhys;
SIZE_T dwAvailPhys;
SIZE_T dwTotalPageFile;
SIZE_T dwAvailPageFile;
SIZE_T dwTotalVirtual;
SIZE_T dwAvailVirtual;
} MEMORYSTATUS, *LPMEMORYSTATUS;
mov eax,dword ptr ds:[0x4030F4] 看到获取的是0x4030F4这个地址的内容而这个结构开始的地址是004030EC
0x4030F4 - 0x4030EC = 8个字节 正好等于两个DWORD类型 那么其实正好指向MEMORYSTATUS.dwTotalPhys字段
看来MEMORYSTATUS.dwTotalPhys是有用的。
0040110E 68 C8304000 push KeygenMe.004030C8
00401113 E8 86020000 call <jmp.&kernel32.GetSystemInfo>
;GetSystemInfo大家也可以参考下原型004030C8是SYSTEM_INFO结构体的第一个字节
typedef struct _SYSTEM_INFO {
union {
DWORD dwOemId; // Obsolete field...do not use
struct {
WORD wProcessorArchitecture;
WORD wReserved;
};
};
DWORD dwPageSize;
LPVOID lpMinimumApplicationAddress;
LPVOID lpMaximumApplicationAddress;
DWORD_PTR dwActiveProcessorMask;
DWORD dwNumberOfProcessors;
DWORD dwProcessorType;
DWORD dwAllocationGranularity;
WORD wProcessorLevel;
WORD wProcessorRevision;
} SYSTEM_INFO, *LPSYSTEM_INFO;
下面又获取了GetSystemInfo中的dwAllocationGranularity 和 dwProcessorType字段,
具体推理和之前一样。
00401118 8B3D E4304000 mov edi,dword ptr ds:[0x4030E4] ;dwProcessorType;
0040111E A1 E0304000 mov eax,dword ptr ds:[0x4030E0] ;dwNumberOfProcessors;
00401123 99 cdq
00401124 F7EF imul edi ; dwProcessorType * ;dwNumberOfProcessors
00401126 8B7D F8 mov edi,dword ptr ss:[ebp-0x8]
00401129 33C7 xor eax,edi
;(dwAllocationGranularity * dwProcessorType) ^ dwTotalPhys
0040112B 57 push edi
0040112C 50 push eax
0040112D 68 8C304000 push KeygenMe.0040308C ; ASCII "%X%X"
00401132 68 0C314000 push KeygenMe.0040310C
00401137 E8 14020000 call <jmp.&user32.wsprintfA>
;格式化一下注册码格式前8位为dwTotalPhys的16进制,后八位为(dwAllocationGranularity * dwProcessorType) ^ dwTotalPhys的16进制
0040113C 83C4 10 add esp,0x10
0040113F 68 0C314000 push KeygenMe.0040310C
00401144 68 EA030000 push 0x3EA
00401149 FF75 08 push dword ptr ss:[ebp+0x8]
0040114C E8 35020000 call <jmp.&user32.SetDlgItemTextA>
;注册码显示到Edit控件上
00401151 C9 leave
00401152 C2 0400 retn 0x4
;机器码获取流程就很清晰了。
------------------------------------------------注册验证部分--------------------------------------------------
00401088 FF75 08 push dword ptr ss:[ebp+0x8]
0040108B E8 DB010000 call KeygenMe.0040126B
00401090 85C0 test eax,eax
00401092 74 10 je short KeygenMe.004010A4
;用户名是否合法?
00401094 FF75 08 push dword ptr ss:[ebp+0x8]
00401097 E8 B9000000 call KeygenMe.00401155
0040109C FF75 08 push dword ptr ss:[ebp+0x8]
0040109F E8 2F020000 call KeygenMe.004012D3
004010A4 EB 25 jmp short KeygenMe.004010CB
call 0040126B分析
bool GetUserName(HWND hWindow );
我已经把代码分块了,很清晰再一次用到了KeygenMe.0040310C,用来存放输入的用户名,并且用户名长度要大于5,否则会提示The length of name is short!
0040126B 55 push ebp
0040126C 8BEC mov ebp,esp
0040126E 68 00020000 push 0x200
00401273 68 0C314000 push KeygenMe.0040310C
00401278 E8 2D010000 call <jmp.&kernel32.RtlZeroMemory>
;变量清空
0040127D 68 00020000 push 0x200
00401282 68 0C314000 push KeygenMe.0040310C
00401287 68 E9030000 push 0x3E9
0040128C FF75 08 push dword ptr ss:[ebp+0x8]
0040128F E8 DA000000 call <jmp.&user32.GetDlgItemTextA>
;获取用户名
00401294 68 0C314000 push KeygenMe.0040310C
00401299 E8 1E010000 call <jmp.&kernel32.lstrlenA>
;获取长度
0040129E 83F8 05 cmp eax,0x5
004012A1 7D 2C jge short KeygenMe.004012CF
;长度小于5则提示错误
004012A3 6A 30 push 0x30
004012A5 68 B1304000 push KeygenMe.004030B1 ; ASCII "Info"
004012AA 68 94304000 push KeygenMe.00403094 ; ASCII "The length of name is short!"
004012AF FF75 08 push dword ptr ss:[ebp+0x8]
004012B2 E8 C3000000 call <jmp.&user32.MessageBoxA>
004012B7 68 E9030000 push 0x3E9
004012BC FF75 08 push dword ptr ss:[ebp+0x8]
004012BF E8 A4000000 call <jmp.&user32.GetDlgItem>
004012C4 50 push eax
004012C5 E8 C2000000 call <jmp.&user32.SetFocus>
004012CA B8 00000000 mov eax,0x0
004012CF C9 leave
004012D0 C2 0400 retn 0x4
call 00401155分析
此处应该为算法核心
00401155 55 push ebp
00401156 8BEC mov ebp,esp
00401158 83C4 F4 add esp,-0xC
0040115B 68 00020000 push 0x200
00401160 68 0C334000 push KeygenMe.0040330C
00401165 E8 40020000 call <jmp.&kernel32.RtlZeroMemory>
0040116A 68 00020000 push 0x200
0040116F 68 0C314000 push KeygenMe.0040310C
00401174 E8 31020000 call <jmp.&kernel32.RtlZeroMemory>
;用到两个变量 0040330C,0040310C 长度均为200
00401179 68 00020000 push 0x200
0040117E 68 0C314000 push KeygenMe.0040310C
00401183 68 EA030000 push 0x3EA
00401188 FF75 08 push dword ptr ss:[ebp+0x8]
0040118B E8 DE010000 call <jmp.&user32.GetDlgItemTextA>
;0040310C存的是机器码,这里机器码是直接从EDIT中GetText获取的,让Patch HARDWARE ID变得很简单,本文只讨论本程序的算法故不展开说了
00401190 68 0C314000 push KeygenMe.0040310C
00401195 E8 22020000 call <jmp.&kernel32.lstrlenA>
0040119A 8945 FC mov dword ptr ss:[ebp-0x4],eax
;长度保存下来做为后用,其实长度应该是16位固定的
0040119D 33C9 xor ecx,ecx
0040119F 33D2 xor edx,edx
004011A1 BE 0C314000 mov esi,KeygenMe.0040310C
004011A6 EB 04 jmp short KeygenMe.004011AC
004011A8 031431 add edx,dword ptr ds:[ecx+esi]
004011AB 41 inc ecx
004011AC 3B4D FC cmp ecx,dword ptr ss:[ebp-0x4]
004011AF ^ 75 F7 jnz short KeygenMe.004011A8
004011B1 8955 F8 mov dword ptr ss:[ebp-0x8],edx
;对机器码循环累加
for(int i = 0 ; i < strlen(hardwarecode) ; ++i )
{
dwCode += *(DWORD*)hardwarecode[i];
}
004011B4 68 00020000 push 0x200
004011B9 68 0C314000 push KeygenMe.0040310C
004011BE E8 E7010000 call <jmp.&kernel32.RtlZeroMemory>
004011C3 68 00020000 push 0x200
004011C8 68 0C314000 push KeygenMe.0040310C
004011CD 68 E9030000 push 0x3E9
004011D2 FF75 08 push dword ptr ss:[ebp+0x8]
004011D5 E8 94010000 call <jmp.&user32.GetDlgItemTextA>
;又获取了用户名
004011DA 68 0C314000 push KeygenMe.0040310C
004011DF E8 D8010000 call <jmp.&kernel32.lstrlenA>
004011E4 8945 FC mov dword ptr ss:[ebp-0x4],eax
004011E7 33C9 xor ecx,ecx
004011E9 33D2 xor edx,edx
004011EB BE 0C314000 mov esi,KeygenMe.0040310C
004011F0 EB 2A jmp short KeygenMe.0040121C
004011F2 031431 add edx,dword ptr ds:[ecx+esi] ;累加这个值最后还有用
004011F5 60 pushad
004011F6 51 push ecx
004011F7 81E2 FF0F0000 and edx,0xFFF
004011FD 8BC2 mov eax,edx
004011FF B9 28000000 mov ecx,0x28
00401204 99 cdq
00401205 F7F9 idiv ecx
00401207 59 pop ecx
00401208 33C0 xor eax,eax
0040120A BE 04304000 mov esi,KeygenMe.00403004 ; ASCII "rwuyticgjxcmncvbmvbergjsghkfhjdsb457dg45"
0040120F 8A0432 mov al,byte ptr ds:[edx+esi] ;查出来
00401212 BE 0C334000 mov esi,KeygenMe.0040330C
00401217 880431 mov byte ptr ds:[ecx+esi],al ;写如临时变量
0040121A 61 popad
0040121B 41 inc ecx
0040121C 3B4D FC cmp ecx,dword ptr ss:[ebp-0x4]
0040121F ^ 75 D1 jnz short KeygenMe.004011F2
00401221 8955 F4 mov dword ptr ss:[ebp-0xC],edx
;根据对用户名每一位进行查表,这里类似与查表加密手段,其实是一个while语句
;查询的结果依次存入.0040330C这个变量中
;用C语言描述如下
DWORD dwTemp = 0;
char* szTemp = 0040330C;
for (int i = 0 ; i < strlen( szUsername) ; ++i )
{
dwTemp += *(DWORD*)&szUsername[ i ];
szTemp[ i ] = target[ ( dwTemp & 0xFFF) % 0x28) ]; target是"rwuyticgjxcmncvbmvbergjsghkfhjdsb457dg45"
}
;查表结束了
00401224 68 00020000 push 0x200
00401229 68 0C314000 push KeygenMe.0040310C
0040122E E8 77010000 call <jmp.&kernel32.RtlZeroMemory>
00401233 8B45 F8 mov eax,dword ptr ss:[ebp-0x8] ;机器码的累加值
00401236 8B55 F4 mov edx,dword ptr ss:[ebp-0xC] ;用户名的累加值
00401239 33D0 xor edx,eax
;这里对两个累加的值进行抑或运算做为注册码的前半部分
0040123B 52 push edx
0040123C 68 91304000 push KeygenMe.00403091 ; ASCII "%X"
00401241 68 0C314000 push KeygenMe.0040310C
00401246 E8 05010000 call <jmp.&user32.wsprintfA>
0040124B 83C4 0C add esp,0xC
;格式化成16进制
0040124E 68 0C334000 push KeygenMe.0040330C
00401253 68 0C314000 push KeygenMe.0040310C
00401258 E8 53010000 call <jmp.&kernel32.lstrcatA>
;注册码进行连接,查表的结果做为后半部分
0040125D 68 0C314000 push KeygenMe.0040310C
00401262 E8 EF000000 call <jmp.&user32.CharUpperA>
;注册码转换成大写
00401267 C9 leave
00401268 C2 0400 retn 0x4
这里为止算法已经很清晰了,相对来说比较简单,我等小菜也只能捏捏软柿子。
------------------------------------------------------------------
注册验证部分,采用的是明码比较,大家自己看下吧。
004012D3 55 push ebp
004012D4 8BEC mov ebp,esp
004012D6 68 00020000 push 0x200
004012DB 68 0C334000 push KeygenMe.0040330C
004012E0 E8 C5000000 call <jmp.&kernel32.RtlZeroMemory>
004012E5 68 00020000 push 0x200
004012EA 68 0C334000 push KeygenMe.0040330C
004012EF 68 F2030000 push 0x3F2
004012F4 FF75 08 push dword ptr ss:[ebp+0x8]
004012F7 E8 72000000 call <jmp.&user32.GetDlgItemTextA>
004012FC 68 0C334000 push KeygenMe.0040330C ;用户输入的
00401301 68 0C314000 push KeygenMe.0040310C ;实现计算出来的正确明码
00401306 E8 AB000000 call <jmp.&kernel32.lstrcmpA>
0040130B 85C0 test eax,eax
0040130D 74 29 je short KeygenMe.00401338 ;爆破点
0040130F 6A 30 push 0x30
00401311 68 B6304000 push KeygenMe.004030B6 ; ASCII "Info"
00401316 68 62304000 push KeygenMe.00403062 ; ASCII "The serial is not correct.Try again! :("
0040131B FF75 08 push dword ptr ss:[ebp+0x8]
0040131E E8 57000000 call <jmp.&user32.MessageBoxA>
00401323 68 F2030000 push 0x3F2
00401328 FF75 08 push dword ptr ss:[ebp+0x8]
0040132B E8 38000000 call <jmp.&user32.GetDlgItem>
00401330 50 push eax
00401331 E8 56000000 call <jmp.&user32.SetFocus>
00401336 EB 14 jmp short KeygenMe.0040134C
00401338 6A 40 push 0x40
0040133A 68 BB304000 push KeygenMe.004030BB ; ASCII "Info"
0040133F 68 2D304000 push KeygenMe.0040302D ; ASCII "Good! The serial is correct.Now make a keygen. :)"
00401344 FF75 08 push dword ptr ss:[ebp+0x8]
00401347 E8 2E000000 call <jmp.&user32.MessageBoxA>
0040134C C9 leave
0040134D C2 0400 retn 0x4
--------------------------------------------------------------
--------------------------------------------------------------------------------
【经验总结】
相对来说这个KeyGenMe比较简单,一遍看下来可以完全将作者原来的代码逆向出来了。有兴趣的可以用SDK来写一个一模一样的。
附上注册机的C语言描述
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
char sztarget[]="rwuyticgjxcmncvbmvbergjsghkfhjdsb457dg45";
int _tmain(int argc, _TCHAR* argv[])
{
char szTemp[ 200 ];
char szBuffer[ 200 ];
MEMORYSTATUS tempx;
GlobalMemoryStatus( &tempx );
SYSTEM_INFO tempxx;
GetSystemInfo( &tempxx );
sprintf( szTemp,"%x%x",(tempxx.dwAllocationGranularity * tempxx.dwProcessorType) ^ tempx.dwTotalPhys, tempx.dwTotalPhys );
puts(szTemp );
memset( szBuffer, 0, sizeof( szBuffer ));
memset( szTemp, 0 , sizeof( szTemp ));
scanf("%s", szBuffer );
DWORD len = 0;
DWORD* pDS = (DWORD*)szBuffer;
for (int i = 0 ; i < strlen( szBuffer ); ++i )
{
pDS = (DWORD*)&szBuffer[ i ];
len+= *pDS;
}
memset( szBuffer, 0, sizeof( szBuffer ));
scanf("%s", szBuffer );
DWORD temp = 0;
for (int i = 0 ; i < strlen( szBuffer ) ; ++i )
{
temp += *(DWORD*)&szBuffer[ i ];
int t = temp & 0xFFF;
szTemp[ i ] = sztarget[ (t% 0x28) ];
}
szTemp[ strlen( szBuffer) ] = 0;
sprintf(szBuffer,"%x%s", temp ^ len, szTemp );
CharUpper( szBuffer );
puts(szBuffer);
return 0;
}
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2013年02月17日 22:01:13
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)