这两天有点发烧,被这个疫情搞得人心惶惶的。我们这里是小镇平常过年的时候人来人往的,今年就显得格外的冷清。这是老天帮让在家学习啊,破解完这个crackme明天就去接着看我的加密解密,算了算没几天就开学了,我的寒假计划还没完成呢!加油加油!
工具:
- 按钮事件地址转换器E2A
- Ollydbg
- PEID
先运行一下程序,点击Help按钮显示帮助信息:发现其要求和crackme6的要求一样都是把按钮隐藏从而把RingZero的logo显示出来
随便输入用户名和注册码后点击Register按钮,程序没有任何反应。
点击Cancella按钮后,注册码被清零
接着我们用PEID查看其基本信息,得到其是Delphi编写的32位程序,无壳
下面我们打开OD正式开始破解程序
- 打开OD并运行程序,随便输入用户名和序列号
然后我们需要了解各个按钮处做了什么处理,用E2A查看各个按钮事件的地址
发现有三个可疑的按钮事件,因为Again按钮现在被隐藏了,所以我们先对剩余两个按钮事件中的任意一个分析,那我们分析Registerz按钮吧。
根据E2A中的地址在OD中搜索,并再此地址处下断点,然后点击Registerz按钮程序会停在此地址处
然后我们F8单步向下执行同样注意:(判断跳转指令),因为有可能这就是成功与不成功的判断处。我们向下执行遇见了第一个判断跳转指令
此处按照我们刚才随便输入的用户名和序列号来讲其是会跳转的,因为我们刚才输入的肯定不对,那是不是不跳转其就说明我们输入的正确呢。正当我准备显式改变eip让其不跳转时(逆其道而为之),我发下在跳转指令下存在一句注释(根据我的经验这应该是弹出一个消息框显示此信息)。此信息的意思为:请在代码编辑器里输入一个有效的长整数,那我意思是如果不跳转就说明我们输入的是无效的长整数。那说明我们刚才随便输入的序列号为有效的,那我们就不要显示改变eip了,继续F8向下执行看还有没有判断跳转指令。
刚F8两下又遇见一处跳转指令,是否这处跳转是判断序列号是否正确的关键跳转呢?
程序这里是不跳转的,因为我们输入的是错误的序列号,那是不是跳转了就说明我们序列号正确了呢,正当我准备改变eip时,又看到如果我们强制跳转其也会弹出一个消息框,内容意思为:代码得大于0。显然我们刚刚输入的111111是大于0的说明我们输入的合法。这样我们就不需要强制跳转继续往下执行看还有没有判断跳转指令?
刚F8几下我们有遇见了判断跳转指令,
此处按照输入的错误的序列号来讲其是需要跳转的,那是不是不跳转就说明我们的序列号正确呢,我们显式改变eip试一下(即让他不跳转,逆其道而为之)。
强制改变完eip后我们运行一下程序,发现Register按钮消失,同时Again按钮出现
既然again按钮出现了,我们不妨就来分析一下again按钮。
同样打开E2A查看again按钮事件地址,并在OD中搜索并下断点。点击Again按钮后程序将停在此地址处
然后我们在F8单步向下执行(同样注意判断跳转指令),遇见第一个判断跳转指令,我们发现和Register按钮处的处理逻辑相同(同样有一句相同的提示信息)同样我们这里跟着程序继续F8,不需要显式改变eip
当我们F8向下又遇见一处判断跳转指令,同样和Register按钮处的处理逻辑一样,我们依然是F8跟着程序的逻辑走(不要强制改变eip),
接着我们F8继续向下执行又碰见一处判断跳转指令,和Register按钮处的情况一样,所以我们这里(逆其道而为之),
强制改变完eip后我们运行程序,发现Again按钮消失,RingZero的logo显示出来了(成功,哈哈爆破成功)。
接下来我们开始分析算法,由前面的爆破过程我们知道要先分析Register按钮,
打开OD重新运行程序,为了方便我们还输入和爆破时一样的用户名和注册码。
点击Register按钮后我们程序停在此按钮事件处理地址处接着我们F8来到刚才我们强制改变eip的判断跳转指令前。我们需要分析如何才能让程序自主实现不跳转(而不需要我们强制改变eip),
此跳转处的代码为
00442FB9 E8 EAF9FFFF call aLoNg3x_.004429A8
00442FBE 84C0 test al,al
00442FC0 74 30 je XaLoNg3x_.00442FF2
所以要想不跳转,需要让al不为0,而关键就在aLoNg3x_.004429A8函数
我们F7单步进入此关键函数分析。
004429A8 /$ 55 push ebp
004429A9 |. 8BEC mov ebp,esp
004429AB |. 83C4 F4 add esp,-0xC
004429AE |. 53 push ebx
004429AF |. 56 push esi
004429B0 |. 57 push edi
004429B1 |. 894D F8 mov [local.2],ecx ; 局部变量1:用户名
004429B4 |. 8955 FC mov [local.1],edx ; 局部变量2:未知?
004429B7 |. 8BF8 mov edi,eax
004429B9 |. 8B45 F8 mov eax,[local.2]
004429BC |. E8 2712FCFF call aLoNg3x_.00403BE8
004429C1 |. 33C0 xor eax,eax
004429C3 |. 55 push ebp
004429C4 |. 68 7A2A4400 push aLoNg3x_.00442A7A
004429C9 |. 64:FF30 push dword ptr fs:[eax]
004429CC |. 64:8920 mov dword ptr fs:[eax],esp
004429CF |. 8B45 F8 mov eax,[local.2]
004429D2 |. E8 5D10FCFF call aLoNg3x_.00403A34 ; 求用户名长度
004429D7 |. 83F8 04 cmp eax,0x4
004429DA |. 0F8E 82000000 jle aLoNg3x_.00442A62
004429E0 |. 33DB xor ebx,ebx
004429E2 |. 8B45 F8 mov eax,[local.2]
004429E5 |. E8 4A10FCFF call aLoNg3x_.00403A34 ; 求用户名长度
004429EA |. 85C0 test eax,eax
004429EC |. 7E 38 jle XaLoNg3x_.00442A26
004429EE |. 8945 F4 mov [local.3],eax ; 局部变量3:用户名长度
004429F1 |. BE 01000000 mov esi,0x1
004429F6 |> 8B45 F8 /mov eax,[local.2] ; 此为双循环
004429F9 |. E8 3610FCFF |call aLoNg3x_.00403A34
004429FE |. 83F8 01 |cmp eax,0x1
00442A01 |. 7C 1D |jl XaLoNg3x_.00442A20
00442A03 |> 8B55 F8 |/mov edx,[local.2]
00442A06 |. 0FB65432 FF ||movzx edx,byte ptr ds:[edx+esi-0x1]
00442A0B |. 8B4D F8 ||mov ecx,[local.2]
00442A0E |. 0FB64C01 FF ||movzx ecx,byte ptr ds:[ecx+eax-0x1]
00442A13 |. 0FAFD1 ||imul edx,ecx ; esi指针所指的字符 * eax指针所指字符
00442A16 |. 0FAFD7 ||imul edx,edi ; 然后在与edx相乘
00442A19 |. 03DA ||add ebx,edx ; 所得的结果累加到ebx中
00442A1B |. 48 ||dec eax ; 内层循环每循环一次改变一次eax指针(eax指针分别指向用户名的每一个字符)
00442A1C |. 85C0 ||test eax,eax
00442A1E |.^ 75 E3 |\jnz XaLoNg3x_.00442A03
00442A20 |> 46 |inc esi ; 外层循环每循环一次改变一次esi指针(esi也是指向用户名的每一个字符)
00442A21 |. FF4D F4 |dec [local.3]
00442A24 |.^ 75 D0 \jnz XaLoNg3x_.004429F6
00442A26 |> \8BC3 mov eax,ebx ;把累加和ebx除以0xA2C2A,得到的余数在传给ebx
00442A28 |. 99 cdq
00442A29 |. 33C2 xor eax,edx
00442A2B |. 2BC2 sub eax,edx
00442A2D |. B9 2A2C0A00 mov ecx,0xA2C2A
00442A32 |. 99 cdq
00442A33 |. F7F9 idiv ecx
00442A35 |. 8BDA mov ebx,edx
到此为止其算法就是:
char 用户名[6];
for(int i = 0; i <= 5; i++)
for(int x = 0; x <=5; x++)
{
ebx += (用户名[i] * 用户名[x] * edi);
}
ebx = ebx % 0xA2C2A
我们接着向下分析
00442A37 |. 8B45 FC mov eax,[local.1] ; 把局部变量1传给eax
00442A3A |. B9 59000000 mov ecx,0x59
00442A3F |. 99 cdq
00442A40 |. F7F9 idiv ecx ; ecx = 局部变量1 / 0x59
00442A42 |. 8BC8 mov ecx,eax
00442A44 |. 8B45 FC mov eax,[local.1]
00442A47 |. BE 50000000 mov esi,0x50
00442A4C |. 99 cdq
00442A4D |. F7FE idiv esi ; ecx = ecx + (局部变量1 % 0x50)
00442A4F |. 03CA add ecx,edx
00442A51 |. 41 inc ecx
00442A52 |. 894D FC mov [local.1],ecx ; 局部变量1 = ecx
00442A55 |. 3B5D FC cmp ebx,[local.1] ; if(ebx == 局部变量1) 则不跳转,否则跳转
00442A58 |. 75 04 jnz XaLoNg3x_.00442A5E
00442A5A |. B3 01 mov bl,0x1
00442A5C |. EB 06 jmp XaLoNg3x_.00442A64
00442A5E |> 33DB xor ebx,ebx
00442A60 |. EB 02 jmp XaLoNg3x_.00442A64
00442A62 |> 33DB xor ebx,ebx
00442A64 |> 33C0 xor eax,eax
00442A66 |. 5A pop edx
00442A67 |. 59 pop ecx
00442A68 |. 59 pop ecx
00442A69 |. 64:8910 mov dword ptr fs:[eax],edx
00442A6C |. 68 812A4400 push aLoNg3x_.00442A81
00442A71 |> 8D45 F8 lea eax,[local.2]
00442A74 |. E8 3F0DFCFF call aLoNg3x_.004037B8
00442A79 \. C3 retn
00442A7A .^ E9 F907FCFF jmp aLoNg3x_.00403278
00442A7F .^ EB F0 jmp XaLoNg3x_.00442A71
00442A81 . 8BC3 mov eax,ebx
00442A83 . 5F pop edi
00442A84 . 5E pop esi
00442A85 . 5B pop ebx
00442A86 . 8BE5 mov esp,ebp
00442A88 . 5D pop ebp
00442A89 . C3 retn
此算法为:
ecx = 局部变量1 / 0x59
ecx = ecx + (局部变量1 % 0x50)
局部变量1 = ecx
if(ebx == 局部变量1)
eax = 1;
else
eax = 0;
因为只有当eax不为0时才能让关键函数跳转处实现不跳转,从而让Register按钮消失,让Again按钮出现。
综上所述此关键函数算法总结为:
char 用户名[6];
for(int i = 0; i <= 5; i++)
for(int x = 0; x <=5; x++)
{
ebx += (用户名[i] * 用户名[x] * edi);
}
ebx = ebx % 0xA2C2A
ecx = 局部变量1 / 0x59
ecx = ecx + (局部变量1 % 0x50)
局部变量1 = ecx
if(ebx == 局部变量1)
eax = 1;
else
eax = 0;
只要eax最后等于1,就可以让关键函数处的跳转不跳转,从而让Register按钮消失,让Again按钮出现。
然而 edi 与 局部变量1我们都不知道是如何得到的,
我们需要分析edi和局部变量1是如何得到的,我们先分析局部变量1是怎么得到的。在关键函数的开始部分我们发现局部变量1是由edx赋值得到的,所以我们需要知道edx的值是怎么得到的,继续往上分析发现到达关键函数头部,说明edx应该是以参数的形式传给关键函数的,所以我们需要到关键函数外部的上方分析edx是怎么产生的。(运行程序,然后点击Register按钮)F8向下运行来到关键函数附近,
我们由代码可得edx是esi赋值的,而上方会测试esi的值总而判断所输入的序列号是否大于0(因为小于0其会跳转,并弹出消息框提示:输入的序列号应大于0)难道esi是序列号的十进制形式,半信半疑我们就试试:从堆栈中我们看到esi的值为0001b207,用十进制表示正好为111111(即我们输入的序列号),所以我们得知局部变量1为我们输入的序列号所对应的十进制数值。
(由经验可得其上方肯定有函数来将序列号转化为对应的十进制值,既然我们已经知道了局部变量1的含义,我们就不往上面分析了)。
下面我们就来分析一下edi是如何产生的,
接着我们在进入关键函数中分析edi是怎么得到的,在关键函数头部我们看到 edi的值是由eax赋值得到的,所以我们要知道eax值是怎么得到的。在往上分析发现到达关键函数头部,所以eax的值也是也是以参数的形式传给关键函数的,看来还得往关键函数外上方分析eax是如何得到的。(运行程序,点击Register按钮),F8向下执行来到关键函数上方。
可以看到eax的值是由0x445830地址处的值赋值得到的,所以我们需要在数据窗口跟踪0x445830地址处的值是怎么产生的。
在数据窗口搜索0x445830,
现在其值为0,所以其会把0赋给eax,eax在赋给edi
我们由关键函数的算法
char 用户名[6];
for(int i = 0; i <= 5; i++)
for(int x = 0; x <=5; x++)
{
ebx += (用户名[i] * 用户名[x] * edi );
}
ebx = ebx % 0xA2C2A
ecx = 局部变量1 / 0x59
ecx = ecx + (局部变量1 % 0x50)
局部变量1 = ecx
if(ebx == 局部变量1)
eax = 1;
else
eax = 0;
如果edi为0则ebx最后也为0(因为其每次都有edi相乘),那么这处算法就一点意思都没有,以为无论用户名是什么,其计算的ebx都为0.
那么会不会使作者估计这样迷惑我们的呢,我们就看有没有局部变量1经过处理后能等于0,然而没有局部变量1经过处理后能为0,所以edi不能为0,也就是地址0x445830处的值需要被改变不能为0,不然此处的算法无意义。
所以我们往关键函数上分析,并在数据窗口中国追踪0x445830地址处的值。(运行程序,点击Register),F8单步运行看0x445830地址处的值是否改变。
然而直到运行到把关键函数处,0x445830地址处的值都还是0一直没有改变。
而且从函数头部到关键函数处只在发现一处能改变0x445830地址处数据的指令,但是这条指令并没有运行,因为这条指令是被判断注册码是否有效的判断指令给跳过去了(心想应该不是这条指令改变得0x445830,应该还有其他指令改变0x445830地址处的数据),
诶!不是还有一个Cancella按钮吗,是不是这个按钮事件处理处改变的0x445830呢?我们就来分析分析此按钮事件处理处。
用E2A查看Cancella按钮处理事件的地址后,在OD中搜索并下断点,运行程序,并点击Cancella按钮,程序会停在此地址处。
接着我们F8单步执行程序,并在数据窗口中查看0x00445830地址处的值看其是否变化,(注意因为我们由一开始执行程序时知道,如果输入的序列号不对,点击Cancella按钮会使序列号清空的,所以我们要注意一些跳转指令),
当我们F8向下碰到第一条判断跳转指令时,我们看到被跳转的代码块有一处提示信息,“Great!!!”,是不是说执行这块代码我们就成功了,就可以改变0x445830地址处的值了呢我们知道如果按着程序的流程跳过这段代码,最后我们输入的错误的序列号会被清零,所以我们不妨就逆其道而为之看看是不是如这个提示信息所说我们执行了它就会成功,我们强制改变eip(让其不跳转)
然后我们F8向下执行看会不会改变00445830内存处的值,F8几下后如我们预料的那样弹出了"Great!!!"的消息框
哈哈,是不是快成功了,继续往下F8并观察00445830内存处的数据
不一会又弹出一个消息框:信息为LAMER!!!,本人英语不好查了一下,本以为是一些褒义词结果竟然是:傻瓜!!!,(难道作者在告诉我,我错了,我掉进了他的陷阱里),
而此时00445830地址处的数据也没有发生变化
不急在往下F8看看,结果知道执行结束程序暂停此地址数据都没有发生变化,程序框也没有任何变化,唯一不同的是因为我们强制改变了eip,所以我们输入的错误的序列号并没有清零!
诶!我果然掉进了作者的陷阱了吗,怪不得他给我弹出个消息框是:傻瓜
,但是在弹出GREAT消息框和弹出傻瓜消息框中间也没有跳转指令啊,只要我们强制改变了eip,就会弹出GREAT和傻瓜消息框。
诶!会不会是这样的,虽然我们输入错误的注册码点击Cancella按钮会使注册码清零,但是其也会改变00445830地址的值,我们只需要在其改变完00445830地址处的值后再把序列号输入在点击Register按钮即可。说干就干我们在点击Cancella按钮,程序停在按钮事件处理的开头
然后我们F8向下执行,并观察00445830地址处的数据看其是否发生变化。我们在遇见判断跳转时不需要强制改变eip,就按程序自己的逻辑走。但是事与愿违,并没有像我们想的那样:00445830地址处的数据没有改变,但我们输入的错误的注册码确实被清零了!
看来不对,百思不得其解!
还是输入我们刚刚输入的序列号111111,点击Register按钮来到关键函数上看看吧!
我们又看到了那就唯一能明显能改变0x445830地址处数据的指令,但是执行此代码块需要输入无效的注册码,作者不会这莫狗吧!(难道是先输入无效的注册码改变00445830地址处的数据后,在输入正确的序列号),不管怎样我们先试一试再说。
我们运行程序,然后改变序列号:我们需要输入无效的序列号让其执行上图的代码块,我们也不知道无效的序列号的规范,那就随便输入一堆序列号把,然后点击Register按钮,
然后我们F8往下执行看看是不是无效的序列号,从而让其执行能改变00445830地址处数据的代码块,
我们输入还真是无效的序列号,弹出消息框提醒我们输入的序列号无效。然后我们继续向下F8,执行那句给00445830地址处的数据赋值的语句,看其能不能使此地址处的数据发生变化,果然执行完mov dword ptr ds:[0x445830],eax
00445838地址处的数据不再是0了,关键函数处的算法有意义了(也就是有机会满足条件了),
我们先来试试到底能不能如我们所想的那样。既然00445838处的数据已经不为0了,那我我们继续运行程序,然后输入刚才那个有效的序列号111111,然后点击Register按钮,我们进入关键函数。
我们发现此时eax已经不为0,其值等于0x00445830地址处的数据,继续向下F8
执行完相关算法后ebx不再为0,这样其就有机会等于 变换后的局部变量1,现在我们看这个00445830中的数据是怎么产生的,因为此数据是在输入无效序列号后进入相关代码块产生的,所以我们还要知道什么样的序列号是无效的,这都要从Reginster按钮事件处理的第一个跳转开始分析(因为实在那判断序列号是否有效的),我们运行程序,随便输入序列号11111a,然后点击Reginster按钮程序停到函数头部。
我们F8到判断序列号是否有效的关键函数后,F7进入函数分析算法:看什么样的序列号为无效的。
00402958 /$ 53 push ebx
00402959 |. 56 push esi
0040295A |. 57 push edi
0040295B |. 89C6 mov esi,eax
0040295D |. 50 push eax
0040295E |. 85C0 test eax,eax
00402960 |. 74 73 je XaLoNg3x_.004029D5
00402962 |. 31C0 xor eax,eax
00402964 |. 31DB xor ebx,ebx
00402966 |. BF CCCCCC0C mov edi,0xCCCCCCC
0040296B |> 8A1E /mov bl,byte ptr ds:[esi] ; Case 20 (' ') of switch 0040296E
0040296D |. 46 |inc esi
0040296E |. 80FB 20 |cmp bl,0x20 ; Switch (cases 20..78)
00402971 |.^ 74 F8 \je XaLoNg3x_.0040296B ; 得到输入的注册码里第一个不是空格的字符
00402973 |. B5 00 mov ch,0x0
00402975 |. 80FB 2D cmp bl,0x2D
00402978 |. 74 69 je XaLoNg3x_.004029E3
0040297A |. 80FB 2B cmp bl,0x2B
0040297D |. 74 66 je XaLoNg3x_.004029E5
0040297F |. 80FB 24 cmp bl,0x24
00402982 |. 74 66 je XaLoNg3x_.004029EA
00402984 |. 80FB 78 cmp bl,0x78
00402987 |. 74 61 je XaLoNg3x_.004029EA
00402989 |. 80FB 58 cmp bl,0x58
0040298C |. 74 5C je XaLoNg3x_.004029EA
0040298E |. 80FB 30 cmp bl,0x30
00402991 |. 75 13 jnz XaLoNg3x_.004029A6
00402993 |. 8A1E mov bl,byte ptr ds:[esi] ; Case 30 ('0') of switch 0040296E
00402995 |. 46 inc esi
00402996 |. 80FB 78 cmp bl,0x78
00402999 |. 74 4F je XaLoNg3x_.004029EA
0040299B |. 80FB 58 cmp bl,0x58
0040299E |. 74 4A je XaLoNg3x_.004029EA
004029A0 |. 84DB test bl,bl ; 如果没有第二个字符则跳转
004029A2 |. 74 20 je XaLoNg3x_.004029C4
004029A4 |. EB 04 jmp XaLoNg3x_.004029AA
004029A6 |> 84DB test bl,bl ; Default case of switch 0040296E
004029A8 |. 74 34 je XaLoNg3x_.004029DE ; 如果一个字符也没就跳转
004029AA |> 80EB 30 /sub bl,0x30
004029AD |. 80FB 09 |cmp bl,0x9
004029B0 |. 77 2C |ja XaLoNg3x_.004029DE
004029B2 |. 39F8 |cmp eax,edi ; 转换得到的数最大不能超过214748364
004029B4 |. 77 28 |ja XaLoNg3x_.004029DE
004029B6 |. 8D0480 |lea eax,dword ptr ds:[eax+eax*4]
004029B9 |. 01C0 |add eax,eax ; eax = eax*10
004029BB |. 01D8 |add eax,ebx
004029BD |. 8A1E |mov bl,byte ptr ds:[esi]
004029BF |. 46 |inc esi
004029C0 |. 84DB |test bl,bl
004029C2 |.^ 75 E6 \jnz XaLoNg3x_.004029AA
004029C4 |> FECD dec ch
004029C6 |. 74 10 je XaLoNg3x_.004029D8
004029C8 |. 85C0 test eax,eax
004029CA |. 7C 12 jl XaLoNg3x_.004029DE
004029CC |> 59 pop ecx
004029CD |. 31F6 xor esi,esi
004029CF |> 8932 mov dword ptr ds:[edx],esi
004029D1 |. 5F pop edi
004029D2 |. 5E pop esi
004029D3 |. 5B pop ebx
004029D4 |. C3 retn
004029D5 |> 46 inc esi
004029D6 |. EB 06 jmp XaLoNg3x_.004029DE
004029D8 |> F7D8 neg eax
004029DA |.^ 7E F0 jle XaLoNg3x_.004029CC
004029DC |.^ 78 EE js XaLoNg3x_.004029CC
004029DE |> 5B pop ebx ; Default case of switch 004029FE
004029DF |. 29DE sub esi,ebx
004029E1 |.^ EB EC jmp XaLoNg3x_.004029CF
004029E3 |> FEC5 inc ch ; Case 2D ('-') of switch 0040296E
004029E5 |> 8A1E mov bl,byte ptr ds:[esi] ; Case 2B ('+') of switch 0040296E
004029E7 |. 46 inc esi
004029E8 |.^ EB BC jmp XaLoNg3x_.004029A6
004029EA |> BF FFFFFF0F mov edi,0xFFFFFFF ; Cases 24 ('$'),58 ('X'),78 ('x') of switch 0040296E
004029EF |. 8A1E mov bl,byte ptr ds:[esi]
004029F1 |. 46 inc esi
004029F2 |. 84DB test bl,bl ; 如果是十六进制形式:则转换为其对应的十进制值
004029F4 |.^ 74 DF je XaLoNg3x_.004029D5
004029F6 |> 80FB 61 /cmp bl,0x61
004029F9 |. 72 03 |jb XaLoNg3x_.004029FE
004029FB |. 80EB 20 |sub bl,0x20
004029FE |> 80EB 30 |sub bl,0x30 ; Switch (cases 30..46)
00402A01 |. 80FB 09 |cmp bl,0x9
00402A04 |. 76 0B |jbe XaLoNg3x_.00402A11
00402A06 |. 80EB 11 |sub bl,0x11
00402A09 |. 80FB 05 |cmp bl,0x5
00402A0C |.^ 77 D0 |ja XaLoNg3x_.004029DE
00402A0E |. 80C3 0A |add bl,0xA ; Cases 41 ('A'),42 ('B'),43 ('C'),44 ('D'),45 ('E'),46 ('F') of switch 004029FE
00402A11 |> 39F8 |cmp eax,edi ; Cases 30 ('0'),31 ('1'),32 ('2'),33 ('3'),34 ('4'),35 ('5'),36 ('6'),37 ('7'),38 ('8'),39 ('9') of switch 004029FE
00402A13 |.^ 77 C9 |ja XaLoNg3x_.004029DE
00402A15 |. C1E0 04 |shl eax,0x4
00402A18 |. 01D8 |add eax,ebx
00402A1A |. 8A1E |mov bl,byte ptr ds:[esi]
00402A1C |. 46 |inc esi
00402A1D |. 84DB |test bl,bl
00402A1F |.^ 75 D5 \jnz XaLoNg3x_.004029F6
00402A21 \.^ EB A9 jmp XaLoNg3x_.004029CC
分析其算法可得有效的序列号符合以下几个条件
- 输入的序列号前面可以有多个空格
- 从第一个不为空格的字符开始,如果是以0-9的数字开头则序列号后不能有不是0-9的其他字符(意思是其就为一串由0-9组成的字符串),然后会将其转化为对应的十进制值存到eax中(我们上面分析的时候说过一定会有一个把序列号转化为其对应十进制值的函数的,猜想没错),把0019f730处的数据变为0表示此序列号为有效的
- 从第一个不为空格的字符开始,如果是以0x,0X,0$开头的序列号那么其后面所跟的字符只能是0-9,a-f(即把其看成是一个十六进制数值),然后会将其转化为对应的十进制值存到eax中,把0019f730处的数据变为0表示此序列号为有效的。
然后我们知道有效序列号满足这些条件也就知道无效序列号满足什么条件了。而我们刚才输入的是11111a其为无效的序列号正好我们看看当输入无效序列号后是怎么改变00445830地址处的数据的,我们F8单步走出此函数。
因为我们刚才输入的序列号为11111a,这不是正确的序列号所以不会吧0019F370处的数据变为0,又因为0019f370处的数据其实就是此函数局部变量1,当cmp [local.1] , 0x0
来判断序列号是否有效时,因为局部变量不为0所以其判断序列号无效,程序不跳转执行下面的代码块。
接着我们找到改变00445830地址处数据地址的指令
然后找到改变00445830地址数据的关键函数F8执行到它,再F7进入分析数据产生的算法,
00442A8C /$ 55 push ebp
00442A8D |. 8BEC mov ebp,esp
00442A8F |. 51 push ecx
00442A90 |. 53 push ebx
00442A91 |. 56 push esi
00442A92 |. 57 push edi
00442A93 |. 8945 FC mov [local.1],eax
00442A96 |. 8B45 FC mov eax,[local.1]
00442A99 |. E8 4A11FCFF call aLoNg3x_.00403BE8
00442A9E |. 33C0 xor eax,eax
00442AA0 |. 55 push ebp
00442AA1 |. 68 212B4400 push aLoNg3x_.00442B21
00442AA6 |. 64:FF30 push dword ptr fs:[eax]
00442AA9 |. 64:8920 mov dword ptr fs:[eax],esp
00442AAC |. 8B45 FC mov eax,[local.1]
00442AAF |. E8 800FFCFF call aLoNg3x_.00403A34 ; 计算所输入注册码的长度
00442AB4 |. 83F8 05 cmp eax,0x5 ; 无效序列号长度大于5
00442AB7 |. 7E 3D jle XaLoNg3x_.00442AF6
00442AB9 |. BE 7B030000 mov esi,0x37B ; esi = 0x37b
00442ABE |. 8B45 FC mov eax,[local.1]
00442AC1 |. E8 6E0FFCFF call aLoNg3x_.00403A34
00442AC6 |. 8BD8 mov ebx,eax
00442AC8 |. 4B dec ebx
00442AC9 |. 85DB test ebx,ebx
00442ACB |. 7E 2B jle XaLoNg3x_.00442AF8
00442ACD |. B9 01000000 mov ecx,0x1
00442AD2 |> 8B45 FC /mov eax,[local.1] ; 此循环将我们输入的无效的序列号 用两个指针来指向第一个字符和第二个字符
00442AD5 |. 0FB60408 |movzx eax,byte ptr ds:[eax+ecx] ; esi +=( (第一个指针所指字符 % 0x11 + 1) * 第二个指针所指字符)
00442AD9 |. BF 11000000 |mov edi,0x11 ; 第一个指针后移,第二个指针也后移,直到第二个指针指向序列号最后一个字符
00442ADE |. 33D2 |xor edx,edx
00442AE0 |. F7F7 |div edi
00442AE2 |. 42 |inc edx
00442AE3 |. 8B45 FC |mov eax,[local.1]
00442AE6 |. 0FB64408 FF |movzx eax,byte ptr ds:[eax+ecx-0x1]
00442AEB |. 0FAFD0 |imul edx,eax
00442AEE |. 03F2 |add esi,edx
00442AF0 |. 41 |inc ecx
00442AF1 |. 4B |dec ebx
00442AF2 |.^ 75 DE \jnz XaLoNg3x_.00442AD2
00442AF4 |. EB 02 jmp XaLoNg3x_.00442AF8
00442AF6 |> 33F6 xor esi,esi
00442AF8 |> 8BC6 mov eax,esi
00442AFA |. B9 48710000 mov ecx,0x7148
00442AFF |. 99 cdq
00442B00 |. F7F9 idiv ecx ; eax = esi % 0x7148
00442B02 |. 8BC2 mov eax,edx
00442B04 |. 99 cdq
00442B05 |. 33C2 xor eax,edx
00442B07 |. 2BC2 sub eax,edx
00442B09 |. 8BD8 mov ebx,eax
00442B0B |. 33C0 xor eax,eax
00442B0D |. 5A pop edx
00442B0E |. 59 pop ecx
00442B0F |. 59 pop ecx
00442B10 |. 64:8910 mov dword ptr fs:[eax],edx
00442B13 |. 68 282B4400 push aLoNg3x_.00442B28
00442B18 |> 8D45 FC lea eax,[local.1]
00442B1B |. E8 980CFCFF call aLoNg3x_.004037B8
00442B20 \. C3 retn
00442B21 .^ E9 5207FCFF jmp aLoNg3x_.00403278
00442B26 .^ EB F0 jmp XaLoNg3x_.00442B18
00442B28 . 8BC3 mov eax,ebx
00442B2A . 5F pop edi
00442B2B . 5E pop esi
00442B2C . 5B pop ebx
00442B2D . 59 pop ecx
00442B2E . 5D pop ebp
00442B2F . C3 retn
分析后算法为:
char 无效序列号[len];
esi = 0x37b;
if(len > 5)
{
for(int i = 0; i <= len-2; i++)
{
esi += 无效序列号[i] * 无效序列号[i+1];
}
eax = esi % 0x7148;
}
else
eax = 0;
而eax的值又会赋给0x00445830,我们知道了0x00445830的值是怎么产生的了,
现在我们可以总结一下如何让Register按钮消失,同时让让Again按钮出现。
1.先输入一个无效的序列号点击Reginster按钮(序列号长度大于5)
2.再输入一个有效的序列号点击Reginster按钮。
无效序列号只要是一个无效的,且长度大于5就可以。有效序列号的算法为:
char 无效序列号[len];
esi = 0x37b;
for(int i = 0; i <= len-2; i++)
{
esi += 无效序列号[i] * 无效序列号[i+1];
}
eax = esi % 0x7148;
char 用户名[6];
for(int i = 0; i <= 5; i++)
for(int x = 0; x <=5; x++)
{
ebx += (用户名[i] * 用户名[x] * eax );
}
ebx = ebx % 0xA2C2A
int a,b,c
a = 0;
while(1)
{
b = a/0x59;
c = a%0x50;
b = b+c+1;
if(b == ebx)
{
cout<<"序列号的十进制形式为:"<<a;
break;
}
else
a++;
}
有了有效序列号的十进制形式后可以根据有效序列号的规则,将其改为十六进制的形式填写也可以。
破解这个程序给我最大的感触就是在破解程序的时候有时候要敢想,开阔思路,你会有意想不到的收获!不管怎样这个程序还是很不错的,估计我的思路效率比较低。还是得多练习啊,明天就去接着看加密与解密,加油加油!
喜欢足球的cxy 发布了20 篇原创文章 · 获赞 5 · 访问量 495 私信 关注