【文章标题】: bxm的第6个CrackMe破解分析
【文章作者】: netwind
【作者QQ号】: 群:8601428
【下载地址】: http://bbs.pediy.com/attachment.php?s=&attachmentid=4612
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
od载入,查找-》所有参考文本串-》双击“good job!"
可以看到如下代码:
004016F0 . 83EC 60 sub esp, 60
004016F3 . 53 push ebx
004016F4 . 55 push ebp
004016F5 . BB 01000000 mov ebx, 1
004016FA . 57 push edi
004016FB . 8BE9 mov ebp, ecx
004016FD . 53 push ebx
004016FE . E8 B5780100 call 00418FB8
00401703 . 8D4424 0C lea eax, [esp+C]
00401707 . 6A 14 push 14 ; /Arg3 = 00000014
00401709 . 50 push eax ; |Arg2
0040170A . 68 E9030000 push 3E9 ; |Arg1 = 000003E9
0040170F . 8BCD mov ecx, ebp ; |
00401711 . E8 1F820100 call 00419935 ; \crackme_.00419935
00401716 . 83F8 03 cmp eax, 3 ; 用户名长度需大于3
00401719 . 0F8C A7000000 jl 004017C6
0040171F . 8D4C24 48 lea ecx, [esp+48]
00401723 . 6A 21 push 21 ; /Arg3 = 00000021
00401725 . 51 push ecx ; |Arg2
00401726 . 68 EA030000 push 3EA ; |Arg1 = 000003EA
0040172B . 8BCD mov ecx, ebp ; |
0040172D . E8 03820100 call 00419935 ; \crackme_.00419935
00401732 . 8D7C24 0C lea edi, [esp+C] ; 将用户名字符串地址给edi
00401736 . 83C9 FF or ecx, FFFFFFFF
00401739 . 33C0 xor eax, eax
0040173B . 33D2 xor edx, edx
0040173D . F2:AE repne scas byte ptr es:[edi>
0040173F . F7D1 not ecx
00401741 . 49 dec ecx
00401742 74 25 je short 00401769 ; 用户名串长度为0则跳(感觉多余代码,前面已经判断要大于3了)
00401744 . 56 push esi ; 保存esi
00401745 . BE 14000000 mov esi, 14
0040174A > 0FBE4414 10 movsx eax, byte ptr [esp+edx+10>; 从头开始取用户名字符asc码
0040174F . 0FAFC6 imul eax, esi ; 将取得的asc码乘以esi
00401752 . 03D8 add ebx, eax
00401754 . 42 inc edx
00401755 . 8D7C24 10 lea edi, [esp+10]
00401759 . 83C9 FF or ecx, FFFFFFFF
0040175C . 33C0 xor eax, eax
0040175E . 4E dec esi ; esi-1
0040175F . F2:AE repne scas byte ptr es:[edi>; 从头开始扫描edi指的字符串,同时ecx-1。
00401761 . F7D1 not ecx
00401763 . 49 dec ecx
00401764 . 3BD1 cmp edx, ecx ; ecx 实际上是edi指的字符串的长度。
00401766 .^ 72 E2 jb short 0040174A ; 因此这里循环次数为用户名的长度
00401768 . 5E pop esi
00401769 8B15 C0D04200 mov edx, [42D0C0] ; 这里获取通过GetStartupInfo(&si)运算得到的随机数
0040176F . 8D4C24 24 lea ecx, [esp+24]
00401773 . 6A 0A push 0A
00401775 . 03D3 add edx, ebx ; ebx为上面的那个循环计算得到的数
00401777 . 51 push ecx
00401778 . 52 push edx
00401779 . E8 687F0000 call 004096E6 ; 将edx对应的十进制数转为字符串
0040177E . 8D7C24 30 lea edi, [esp+30] ; 取得字符串地址
00401782 . 83C9 FF or ecx, FFFFFFFF
00401785 . 33C0 xor eax, eax
00401787 . 83C4 0C add esp, 0C
0040178A . 33D2 xor edx, edx
0040178C . F2:AE repne scas byte ptr es:[edi>
0040178E . F7D1 not ecx
00401790 . 49 dec ecx ; 执行后ecx 实际上是edi指的字符串的长度。
00401791 74 23 je short 004017B6 ; 若串长度为0提示正确信息(感觉这里的代码是多余的,它不可能为0)
00401793 > 0FBE4414 24 movsx eax, byte ptr [esp+ed>; 取所得串第一个字符
00401798 . 0FBE4C14 48 movsx ecx, byte ptr [esp+ed>; 取输入的注册码第一个字符
0040179D . 03C2 add eax, edx ; 将串第一个字符asc码加其序号(从0开始)
0040179F . 3BC1 cmp eax, ecx ; 再与注册码第一个字符比较
004017A1 75 23 jnz short 004017C6 ; 不相等,则错
004017A3 . 8D7C24 24 lea edi, [esp+24]
004017A7 . 83C9 FF or ecx, FFFFFFFF
004017AA . 33C0 xor eax, eax
004017AC . 42 inc edx
004017AD . F2:AE repne scas byte ptr es:[edi>
004017AF . F7D1 not ecx
004017B1 . 49 dec ecx
004017B2 . 3BD1 cmp edx, ecx
004017B4 .^ 72 DD jb short 00401793 ; 再对第二个字符比较,循环次数为所得串的长度
004017B6 > 6A 00 push 0 ; 两串匹配则提示正确消息
004017B8 . 6A 00 push 0
004017BA . 68 34B14200 push 0042B134 ; ASCII "Good job!"
004017BF . 8BCD mov ecx, ebp
004017C1 . E8 96700100 call 0041885C
004017C6 > 5F pop edi
004017C7 . 5D pop ebp
004017C8 . 5B pop ebx
004017C9 . 83C4 60 add esp, 60
004017CC . C3 retn
下面看看42D0C0里的随机数是怎么得到的:
重新用od载入,载od左下角内存窗口右键-转到-表达式 填42D0C0 然后转到这个地址;现在这个地址是四个0
将这四个0选中,右键-断点-内存写入。
然后运行程序断在:0040141E . 8915 C0D04200 mov [42D0C0], edx ; |
其附近有如下代码:
00401405 . 51 push ecx ; /pStartupinfo
00401406 . FF15 78224200 call [<&KERNEL32.GetStartu>; \GetStartupInfoA
0040140C . 8B5424 30 mov edx, [esp+30] ; startupinfo结构第9个参数
00401410 . 8B4424 24 mov eax, [esp+24] ; 第6个参数
00401414 . 8B5C24 20 mov ebx, [esp+20] ; 第5个参数
00401418 . 03D0 add edx, eax
0040141A . 03D3 add edx, ebx ; 将三个参数相加就得到随机数
0040141C . 6A 00 push 0 ; /Revert = FALSE
0040141E . 8915 C0D04200 mov [42D0C0], edx ; |
大致算法如下:
1、将用户名每一位乘以esi(esi=esi-1初始值esi=0x14)所得的和相加,再加1,得到一个数。
2、将此数加上随机数,得到另一个数。
3、将得到数的十进制每一位加上它对应的序号(比如123加序号后为135)得到的数的十进制,作为一个字符串就是注册码。
根据bxm提示,随机数是在用od载入时才产生,那么第二步则可省略,就剩下第一步和第三步。
算法c程序表示如下:
#include <stdio.h>
#include <string.h>
void main()
{
char name[]="netwind";
char temp[20];
long c=1,esi=0x14;
int i,l;
for(i=0;i<strlen(name);i++,esi--)
c=c+name[i]*esi;
i=0;
while(c) //将数字十进制转字符串。
{ temp[i++]=c%10+'0'; //这里得的是逆序的。
c/=10;
}
temp[i]=0;
l=strlen(temp);
for(i=0;i<strlen(temp);i++,l--) //每一位加上其正序时的序号。
printf("%c",temp[l-1]+=i); //将逆序串从最后一为开始输出,就是注册码。
printf("\n");
}
过程与汇编代码一样,只是汇编代码中间有一步把逆序串转正序,这里节省了这一步。
感想:
这个crackme很特别,很容易让人感觉写不出注册机.
程序应该加了异常处理吧,
od载入时,则会读取本进程STARTUPINFO 结构.
lpStartupInfo,参数结构
typedef struct _STARTUPINFO { // si
DWORD cb; //结构长度
LPTSTR lpReserved; //保留
LPTSTR lpDesktop; //保留
LPTSTR lpTitle; //如果为控制台进程则为显示的标题
DWORD dwX; //窗口位置...........
DWORD dwY; //窗口位置...........
DWORD dwXSize; //窗口大小
DWORD dwYSize; //窗口大小
DWORD dwXCountChars; //控制台窗口字符号宽度 .........
DWORD dwYCountChars; //控制台窗口字符号高度
DWORD dwFillAttribute; //控制台窗口填充模式
DWORD dwFlags; //创建标记
WORD wShowWindow; //窗口显示标记如同ShowWindow中的标记
WORD cbReserved2; //
LPBYTE lpReserved2; //
HANDLE hStdInput; //标准输入句柄
HANDLE hStdOutput; //标准输出句柄
HANDLE hStdError; //标准错误句柄
} STARTUPINFO, *LPSTARTUPINFO;
将读出的结构的第9,6,5个参数的值相加得到随机数,然后加上前面运算得到的数字,得到一个数,把该数十进制每一位加该位的序号(从0开始)得到一个数,把该数十进制表示的数转为字符串形式便是注册码.
如果单用od跟踪,而没考虑到异常的话,很难想象可以做出一个注册机.
因为:
netwind (2007-03-02 22:42:07)
问下:
GetStartupInfo(&si);获得当前进程的STARTUPINFO 结构
如何获得指定某个进程的STARTUPINFO 结构
大牛(2007-03-02 22:43:16)
那些信息都存放在那个进程的 PEB 里。
大牛 (2007-03-02 22:43:30)
PEB 不公开的。去找些资料来看
netwind (2007-03-02 22:44:06)
恩,谢谢了
没有其他的简单方法了吗?
大牛 (2007-03-02 22:44:17)
没
大牛 (2007-03-02 22:44:27)
或者说,我不知道
crackme的算法分析较容易,爆破点也很容易找到,对异常处理那块较感兴趣.
希望有人能贴出更好的破文,谢谢!
这个crackme应该公布 源代码 希望如此,bxm大牛辛苦了,多谢!
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2007年03月03日 12:51:45
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!