从SDK到SDK的过程
百度空间 | 百度首页 
 
文章列表
 
2009年09月29日 星期二 14:46

我们跟踪一下,重新用OD加载,运行,来到程序重启校验的部分

观察堆栈。。注册码的第一位怎么变成0了,难怪会失败的。看来程序修改的我们的注册码。
是在什么地方修改了呢?是在程序重启后,从注册表读入信息后修改的?还是在输入用户名和注册码校验完成,提示我们重启之后修改的呢?
一个个看,首先看是不是重启后修改的。我们下断RegQueryValueExW,观察堆栈

堆栈中显示,在读取 License 子键的值,这个子键就是我们存入注册信息的子键。那肯定就是在读注册信息了。看看读入的缓冲区
0013E330,记住这个地址。我们dd 0013E330,F8运行,没有看到什么注册信息,只是一些16进制的字节而已,说明注册信息肯定是被转换成
16进制存入了注册表。好的,F8继续走,注意观察寄存器和函数调用前压入堆栈的参数,有没有和00E13E330这个地址一样或是接近的。
来到了这里

这个函数的参数有一个是 0013E338 就是 0013E330后面的,运行完这个函数后,堆栈和寄存器中都出现了用户名。
看来这个函数是解码函数了,那么他肯定会有一个缓冲区用于解码后的字符串存放,edi函数调用完后就出现了注册名,说明edi就是缓冲区
dd edi确认一下

果然这个就是解码函数了。。。看到,解码出来的注册码第一位就是0了。。   是不是解码的时候改变了注册码呢?还记得当时输入用户名
和注册码 校验成功之后,就把注册信息存入注册表吗?他应该也是把用户名和注册码转换成16进制码然后存入注册表的。我们只需比较
当时存入的16进制码和现在读出来的16进制码是不是一样的就能确定是不是 解码的时候修改了注册码了。OK,然程序运行,输入注册码和用户名    XIAOBAOZI   3A10H-66D5B-EJC72-JML1I-ME9D5   来到存入注册信息的RegSetValueEx函数

看到缓冲区0B8B010,缓冲区大小为38(56d),也就是蓝色的这块区域。。OK,重启程序,来到读取注册信息的RegQueryValueEx函数

可以看到,2者是不一样的,这说明在校验成功写入后,程序又写入了一次注册表。我们从新输入用户名和注册码,下断RegSetValueEx,弹出
让我们重启程序的对话框,点确定,断下了,是校验成功后的写入的,这个我们已经知道了,然后我们关闭程序,在点了关闭后断在这里了

看到了,就是这里,观察堆栈,注册码第一位已经被修改了。同样,观察有没跳转可以跳过这个写入注册表的函数,发现没有,在函数头下断
看看是什么地方调用了这个函数

观察堆栈看到返回地址和压入的2个参数,一个是用户名,一个就是被修改过的注册码。Crrl + G 来到 返回地址0043E23B

可以看到,跳过的这些操作很可能就是修改注册码第一位为0,然后再次存入注册表。往上看

看来是通过 判断全局变量是否为0来决定是否修改注册码并再次写入注册表的。
重新加载,dd 00B883F0 ,对00B883F0下内存写入断点。第一断在这里

这个应该是初始化,讲该变量初始化为0,继续走,第二次断在了这里

就是这里了,重启校验的算法应该就在上面了。往上面找
004950C9 |> /33D2          /xor     edx, edx
004950CB |. |8A1439        |mov     dl, byte ptr ds:[ecx+edi]
004950CE |. |03EA          |add     ebp, edx                             ; 用户名每个字符累加到ebp
004950D0 |. |41            |inc     ecx
004950D1 |. |3BC8          |cmp     ecx, eax
004950D3 |.^\7C F4         \jl      short EditPlus.004950C9
004950D5 |> 8D4CED 0A     lea     ecx, dword ptr ss:[ebp+ebp*8+A]       ; ECX = ebp + ebp*8 +A
004950D9 |. B8 56555555   mov     eax, 55555556
004950DE |. F7E9          imul    ecx                                   ; 55555556*ecx
004950E0 |. 8BC2          mov     eax, edx                              ; 高位放到eax
004950E2 |. C1E8 1F       shr     eax, 1F
004950E5 |. 8D4C02 24     lea     ecx, dword ptr ds:[edx+eax+24]        ; ecx = edx +eax +24
004950E9 |. 81E1 0F000080 and     ecx, 8000000F
004950EF |. 79 05         jns     short EditPlus.004950F6
004950F1 |. 49            dec     ecx
004950F2 |. 83C9 F0       or      ecx, FFFFFFF0
004950F5 |. 41            inc     ecx
004950F6 |> 51            push    ecx
004950F7 |. 8D5424 18     lea     edx, dword ptr ss:[esp+18]
004950FB |. 68 B87C5300   push    EditPlus.00537CB8                     ; %1xcombo\find
00495100 |. 52            push    edx                                   ; 1
00495101 |. E8 DE520300   call    EditPlus.004CA3E4                     ; 转换成ASCII
00495106 |. 8B86 DC000000 mov     eax, dword ptr ds:[esi+DC]
0049510C |. 83C4 0C       add     esp, 0C
0049510F |. 8B00          mov     eax, dword ptr ds:[eax]
00495111 |. 8378 F8 05    cmp     dword ptr ds:[eax-8], 5
00495115 |. 7C 12         jl      short EditPlus.00495129               ; 注册码长度小于29则完蛋
00495117 |. 66:8B40 08    mov     ax, word ptr ds:[eax+8]
0049511B |. 0FBE4C24 14   movsx   ecx, byte ptr ss:[esp+14]
00495120 |. 25 FFFF0000   and     eax, 0FFFF
00495125 |. 3BC1          cmp     eax, ecx                              ; 注册码第5位和前面算出的ecx必须相等
00495127 |. 74 0C         je      short EditPlus.00495135               ; 不跳则修改注册码第一位为0
00495129 |> 8B96 E4000000 mov     edx, dword ptr ds:[esi+E4]
0049512F |. C702 01000000 mov     dword ptr ds:[edx], 1                 ; 修改注册表标识
00495135 |> 57            push    edi
00495136 |. E8 25810400   call    EditPlus.004DD260

原来在点了关闭程序后,他还进行了一次校验,不如过满足这次校验就把注册码第一位变成0,然后再次写入注册表,使得注册信息不对。
OK,现在 注册校验和重启校验都找出来了。。把上面的注册机 重启校验的那部分的 注释给去掉,重新编译就是真正的注册机了

跑了2个小时才跑出来一个正确的注册码
xiaobaozi
FEBEC-D9PP1-JO651-5IKB5-J1O0I


一点心得,献给和我一样的努力奋斗着的菜鸟们:找爆破点我想应该是破解的第一步吧,找到了爆破点,把程序校验的流程都搞透彻以后
再来分析程序的注册算法。加密解密中的第4章,逆向分析技术,这一章我感觉对我们初学者来说很重要,都是一些很基础的
知识,但是在破解中确实十分的有用,正如书中所说,破解知识逆向工程的一个小分支而已。再就是算法分析了,不要被一大
堆得汇编代码给吓住了,如果确定了这一段是算法的所在,就慢慢的分析。最后就是细致的观察,和大胆的猜测了,注意观察
堆栈,寄存器,信息窗口,通过这些就容易发现程序的关键点,在你没有思路头绪或是走到死胡同的时候,大胆的猜测可能会
让你柳暗花明。


更想请各位大神指点一下:如何才能提高注册机的运算速度,我跑了2个小时才跑出来。。。对这种不是明码比较的该如何写注册机呢?

 
2009年09月29日 星期二 14:26

OK,找到了爆破点,就来算法分析和注册机的编写吧
重启程序,输入序列号和用户名在第一个爆破点前的 校验函数下断 ,F7进去

00495440 /$ 81EC 34020000 sub     esp, 234                         ; 校验注册码和用户函数
00495446 |. 53            push    ebx
00495447 |. 8B9C24 3C0200>mov     ebx, dword ptr ss:[esp+23C]
0049544E |. 56            push    esi
0049544F |. 57            push    edi
00495450 |. 53            push    ebx
00495451 |. E8 DB3D0300   call    EditPlus.004C9231                ; 取用户名长度
00495456 |. 8BF0          mov     esi, eax
00495458 |. 83C4 04       add     esp, 4
0049545B |. 85F6          test    esi, esi                         ; 用户名是否为空
0049545D |. 75 0A         jnz     short EditPlus.00495469
0049545F |. 5F            pop     edi
00495460 |. 5E            pop     esi
00495461 |. 5B            pop     ebx
00495462 |. 81C4 34020000 add     esp, 234
00495468 |. C3            retn
00495469 |> 8BBC24 480200>mov     edi, dword ptr ss:[esp+248]
00495470 |. 55            push    ebp
00495471 |. 57            push    edi
00495472 |. E8 BA3D0300   call    EditPlus.004C9231                ; 去注册码长度
00495477 |. 83C4 04       add     esp, 4
0049547A |. 8BE8          mov     ebp, eax
0049547C |. 8D4424 50     lea     eax, dword ptr ss:[esp+50]
00495480 |. 6A 00         push    0                                ; /pDefaultCharUsed = NULL
00495482 |. 6A 00         push    0                                ; |pDefaultChar = NULL
00495484 |. 68 F3010000   push    1F3                              ; |MultiByteCount = 1F3 (499.)
00495489 |. 50            push    eax                              ; |MultiByteStr
0049548A |. 56            push    esi                              ; |WideCharCount
0049548B |. 53            push    ebx                              ; |WideCharStr
0049548C |. 8B1D 78945000 mov     ebx, dword ptr ds:[<&KERNEL32.Wi>; |kernel32.WideCharToMultiByte
00495492 |. 6A 00         push    0                                ; |Options = 0
00495494 |. 6A 00         push    0                                ; |CodePage = CP_ACP
00495496 |. FFD3          call    ebx                              ; \WideCharToMultiByte
00495498 |. 6A 00         push    0                                ; /pDefaultCharUsed = NULL
0049549A |. 6A 00         push    0                                ; |pDefaultChar = NULL
0049549C |. 8D4C24 24     lea     ecx, dword ptr ss:[esp+24]       ; |
004954A0 |. 6A 31         push    31                               ; |MultiByteCount = 31 (49.)
004954A2 |. 51            push    ecx                              ; |MultiByteStr
004954A3 |. 55            push    ebp                              ; |WideCharCount
004954A4 |. 8BF0          mov     esi, eax                         ; |
004954A6 |. 57            push    edi                              ; |WideCharStr
004954A7 |. 6A 00         push    0                                ; |Options = 0
004954A9 |. 6A 00         push    0                                ; |CodePage = CP_ACP
004954AB |. C64434 70 00 mov     byte ptr ss:[esp+esi+70], 0      ; |
004954B0 |. FFD3          call    ebx                              ; \WideCharToMultiByte
004954B2 |. 8BF8          mov     edi, eax
004954B4 |. C6443C 1C 00 mov     byte ptr ss:[esp+edi+1C], 0
004954B9 |. E8 A2F6FFFF   call    EditPlus.00494B60
004954BE |. 8D5424 50     lea     edx, dword ptr ss:[esp+50]
004954C2 |. 56            push    esi
004954C3 |. 52            push    edx
004954C4 |. 6A 00         push    0
004954C6 |. E8 E5F6FFFF   call    EditPlus.00494BB0                ; 计算用户名 F7进去
004954CB |. 83C4 0C       add     esp, 0C                          ; 计算结果通过eax返回
004954CE |. 25 FFFF0000   and     eax, 0FFFF                       ; 取返回eax的低字
004954D3 |. 50            push    eax                              ; 计算结果入栈
004954D4 |. 8D4424 14     lea     eax, dword ptr ss:[esp+14]       ; 用于存放格式化后的字符串
004954D8 |. 68 A4B25300   push    EditPlus.0053B2A4                ; %02x
004954DD |. 50            push    eax
004954DE |. E8 014F0300   call    EditPlus.004CA3E4                ; 将计算结果转换成ASCII码
004954E3 |. 8A4C24 2A     mov     cl, byte ptr ss:[esp+2A]
004954E7 |. 8A4424 1C     mov     al, byte ptr ss:[esp+1C]
004954EB |. 83C4 0C       add     esp, 0C
004954EE |. 3AC8          cmp     cl, al                           ; 注册码第3个字符和算出来的第一个字符比较
004954F0 |. 5D            pop     ebp
004954F1      74 0C         je      short EditPlus.004954FF          ; 必须跳
004954F3 |. 5F            pop     edi
004954F4 |. 5E            pop     esi
004954F5 |. 33C0          xor     eax, eax
004954F7 |. 5B            pop     ebx
004954F8 |. 81C4 34020000 add     esp, 234
004954FE |. C3            retn
004954FF |> 8A5424 1B     mov     dl, byte ptr ss:[esp+1B]
00495503 |. 8A4424 0D     mov     al, byte ptr ss:[esp+D]
00495507 |. 3AD0          cmp     dl, al                           ; 注册码第4个字节和计算出来的第二个字节比较
00495509      74 0C         je      short EditPlus.00495517          ; 必须跳
0049550B |. 5F            pop     edi
0049550C |. 5E            pop     esi
0049550D |. 33C0          xor     eax, eax
0049550F |. 5B            pop     ebx
00495510 |. 81C4 34020000 add     esp, 234
00495516 |. C3            retn
00495517 |> 83C7 FE       add     edi, -2                          ; 去掉注册码前2位,计算注册码
0049551A |. 8D4424 1A     lea     eax, dword ptr ss:[esp+1A]
0049551E |. 57            push    edi
0049551F |. 50            push    eax
00495520 |. 6A 00         push    0
00495522 |. E8 89F6FFFF   call    EditPlus.00494BB0                ; 计算注册码,F7进去
00495527 |. 83C4 0C       add     esp, 0C                          ; 计算结果通过eax返回
0049552A |. 25 FFFF0000   and     eax, 0FFFF
0049552F |. 8D4C24 0C     lea     ecx, dword ptr ss:[esp+C]
00495533 |. 50            push    eax                              ; 计算结果入栈
00495534 |. 68 A4B25300   push    EditPlus.0053B2A4                ; %02x
00495539 |. 51            push    ecx                              ; 用于存放的缓冲区
0049553A |. E8 A54E0300   call    EditPlus.004CA3E4                ; 计算结果转换为ASCII码
0049553F |. 8A5424 24     mov     dl, byte ptr ss:[esp+24]
00495543 |. 8A4424 18     mov     al, byte ptr ss:[esp+18]
00495547 |. 83C4 0C       add     esp, 0C
0049554A |. 3AD0          cmp     dl, al                           ; 注册码第1位和算出来的第1位比较
0049554C      74 0C         je      short EditPlus.0049555A          ; 必须跳
0049554E |. 5F            pop     edi
0049554F |. 5E            pop     esi
00495550 |. 33C0          xor     eax, eax
00495552 |. 5B            pop     ebx
00495553 |. 81C4 34020000 add     esp, 234
00495559 |. C3            retn
0049555A |> 8A4C24 19     mov     cl, byte ptr ss:[esp+19]
0049555E |. 8A5424 0D     mov     dl, byte ptr ss:[esp+D]
00495562 |. 33C0          xor     eax, eax
00495564 |. 3ACA          cmp     cl, dl                           ; 注册码第2位和算出来的第2位比较
00495566 |. 5F            pop     edi
00495567 |. 5E            pop     esi
00495568      0F94C0        sete    al                               ; 条件必须为真
0049556B      5B            pop     ebx
0049556C      81C4 34020000 add     esp, 234
00495572 \. C3            retn


Call   00494BB0   F7进去后
00494BB0 /$ 8B4C24 08     mov     ecx, dword ptr ss:[esp+8]        ; 传入的字符串入ecx
00494BB4 |. 8B4424 0C     mov     eax, dword ptr ss:[esp+C]        ; 长度入eax
00494BB8 |. 56            push    esi
00494BB9 |. 8D3401        lea     esi, dword ptr ds:[ecx+eax]      ; esi指向字符串最后一个字符
00494BBC |. 3BCE          cmp     ecx, esi                         ; 字符串是否为空
00494BBE |. 73 2A         jnb     short EditPlus.00494BEA
00494BC0 |. 8B4424 08     mov     eax, dword ptr ss:[esp+8]        ; eax = 0
00494BC4 |. 53            push    ebx                              ; eax用于存放结果
00494BC5 |> 8BD0          /mov     edx, eax                        ; 取结果到edx
00494BC7 |. 33DB          |xor     ebx, ebx
00494BC9 |. 8A19          |mov     bl, byte ptr ds:[ecx]           ; 取字符串一个字符
00494BCB |. 81E2 FF000000 |and     edx, 0FF                        ; 取上次结果的低字节
00494BD1 |. 33D3          |xor     edx, ebx                        ; 和取的字符字符异或
00494BD3 |. 33DB          |xor     ebx, ebx                        ; ebx清零
00494BD5 |. 8ADC          |mov     bl, ah                          ; 取上次结果高位
00494BD7 |. 66:8B0455 A80>|mov     ax, word ptr ds:[edx*2+570DA8] ; 查表取值
00494BDF |. 66:33C3       |xor     ax, bx                          ; 查的的值和上次结果的高位异或
00494BE2 |. 41            |inc     ecx                             ; 指向下一个字符
00494BE3 |. 3BCE          |cmp     ecx, esi
00494BE5 |.^ 72 DE         \jb      short EditPlus.00494BC5
00494BE7 |. 5B            pop     ebx
00494BE8 |. 5E            pop     esi
00494BE9 |. C3            retn

OK,根据算法,写出我们的注册机

#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
using namespace std;

WORD Fun(char String[]); //计算中间结果
bool Check(char Name[],char Sn[]); //校验用户名和注册码
bool ReSetCheck(char Name[],char Sn[]); //重启校验

WORD Table[] = {   //表
0x0000 ,0xC1C0 ,0x81C1 ,0x4001 ,0x01C3 ,0xC003 ,0x8002 ,0x41C2,
0x01C6 ,0xC006 ,0x8007 ,0x41C7 ,0x0005 ,0xC1C5 ,0x81C4 ,0x4004 ,
0x01CC ,0xC00C ,0x800D ,0x41CD ,0x000F ,0xC1CF ,0x81CE ,0x400E ,
0x000A ,0xC1CA ,0x81CB ,0x400B ,0x01C9 ,0xC009 ,0x8008 ,0x41C8 ,
0x01D8 ,0xC018 ,0x8019 ,0x41D9 ,0x001B ,0xC1DB ,0x81DA ,0x401A ,
0x001E ,0xC1DE ,0x81DF ,0x401F ,0x01DD ,0xC01D ,0x801C ,0x41DC ,
0x0014 ,0xC1D4 ,0x81D5 ,0x4015 ,0x01D7 ,0xC017 ,0x8016 ,0x41D6 ,
0x01D2 ,0xC012 ,0x8013 ,0x41D3 ,0x0011 ,0xC1D1 ,0x81D0 ,0x4010 ,
0x01F0 ,0xC030 ,0x8031 ,0x41F1 ,0x0033 ,0xC1F3 ,0x81F2 ,0x4032 ,
0x0036 ,0xC1F6 ,0x81F7 ,0x4037 ,0x01F5 ,0xC035 ,0x8034 ,0x41F4 ,
0x003C ,0xC1FC ,0x81FD ,0x403D ,0x01FF ,0xC03F ,0x803E ,0x41FE ,
0x01FA ,0xC03A ,0x803B ,0x41FB ,0x0039 ,0xC1F9 ,0x81F8 ,0x4038 ,
0x0028 ,0xC1E8 ,0x81E9 ,0x4029 ,0x01EB ,0xC02B ,0x802A ,0x41EA ,
0x01EE ,0xC02E ,0x802F ,0x41EF ,0x002D ,0xC1ED ,0x81EC ,0x402C ,
0x01E4 ,0xC024 ,0x8025 ,0x41E5 ,0x0027 ,0xC1E7 ,0x81E6 ,0x4026 ,
0x0022 ,0xC1E2 ,0x81E3 ,0x4023 ,0x01E1 ,0xC021 ,0x8020 ,0x41E0 ,
0x01A0 ,0xC060 ,0x8061 ,0x41A1 ,0x0063 ,0xC1A3 ,0x81A2 ,0x4062 ,
0x0066 ,0xC1A6 ,0x81A7 ,0x4067 ,0x01A5 ,0xC065 ,0x8064 ,0x41A4 ,
0x006C ,0xC1AC ,0x81AD ,0x406D ,0x01AF ,0xC06F ,0x806E ,0x41AE ,
0x01AA ,0xC06A ,0x806B ,0x41AB ,0x0069 ,0xC1A9 ,0x81A8 ,0x4068 ,
0x0078 ,0xC1B8 ,0x81B9 ,0x4079 ,0x01BB ,0xC07B ,0x807A ,0x41BA ,
0x01BE ,0xC07E ,0x807F ,0x41BF ,0x007D ,0xC1BD ,0x81BC ,0x407C ,
0x01B4 ,0xC074 ,0x8075 ,0x41B5 ,0x0077 ,0xC1B7 ,0x81B6 ,0x4076 ,
0x0072 ,0xC1B2 ,0x81B3 ,0x4073 ,0x01B1 ,0xC071 ,0x8070 ,0x41B0 ,
0x0050 ,0xC190 ,0x8191 ,0x4051 ,0x0193 ,0xC053 ,0x8052 ,0x4192 ,
0x0196 ,0xC056 ,0x8057 ,0x4197 ,0x0055 ,0xC195 ,0x8194 ,0x4054 ,
0x019C ,0xC05C ,0x805D ,0x419D ,0x005F ,0xC19F ,0x819E ,0x405E ,
0x005A ,0xC19A ,0x819B ,0x405B ,0x0199 ,0xC059 ,0x8058 ,0x4198 ,
0x0188 ,0xC048 ,0x8049 ,0x4189 ,0x004B ,0xC18B ,0x818A ,0x404A ,
0x004E ,0xC18E ,0x818F ,0x404F ,0x018D ,0xC04D ,0x804C ,0x418C ,
0x0044 ,0xC184 ,0x8185 ,0x4045 ,0x0187 ,0xC047 ,0x8046 ,0x4186 ,
0x0182 ,0xC042 ,0x8043 ,0x4183 ,0x0041 ,0xC181 ,0x8180 ,0x4040 ,
0xFAC1 ,0x0000
};
void main()
{
char Name[20];
char Sn[30];
cout<<"输入用户名"<<endl;
cin>>Name;

srand(time(NULL));   //随即种子初始化

while (1)    
{
for (int kk = 0;kk != 29;kk++)
   while (1)
   {
    Sn[kk] = 48 + rand()%33; //随即枚举字符
    if (!(Sn[kk]>=58 && Sn[kk]<=64))
     break;
   }
Sn[5]=Sn[11]=Sn[17]=Sn[23]='-';//为了使序列号好看,固定了这几位,其实不需要
Sn[29] = '\0';   //字符串结尾符
cout<<Sn<<endl; //显示当前枚举的序列号
Check(Name,Sn); //校验当前的序列号
}
}


WORD Fun(char String[]) //F7进去的那个计算函数
{
WORD result = 0;
WORD temp = 0;
WORD temp2 = 0;
WORD temp3 = 0;
WORD ch;
for (int i = 0;i != lstrlen(String);i++)
{
ch = String[i];
temp = result&0xFF;
temp = ch^temp;
temp2 = result >> 8;
result = Table[(int)temp];
temp3 = Table[(int)temp];
temp3 = temp3 << 8;
result = result >> 8;
result = result | temp3;
result = result^temp2;
}
return result;
}

bool Check(char Name[],char Sn[]) //校验用户名和序列号函数
{
char MidRes[10];
for (int i = 0;i != sizeof(Sn)/sizeof(char);i++)   //序列号小写转大写
Sn[i]>=97 && Sn[i]<=122 ? Sn[i] = Sn[i] - 32 : Sn[i];
WORD res = Fun(Name);
wsprintf(MidRes,"%x",res);
for (i = 0;i != sizeof(MidRes)/sizeof(char);i++)    //中间结果小写转大写
MidRes[i]>=97 && MidRes[i]<=122 ? MidRes[i] -= 32 : MidRes[i];
// printf("%04x",res);
if (MidRes[0] != Sn[2])
{
// cout<<"错误的序列号"<<endl;
return false;
}
if (MidRes[1] != Sn[3])
{
// cout<<"错误的序列号"<<endl;
return false;
}
char *p = Sn;
res = Fun(p+2);
wsprintf(MidRes,"%x",res);
for (i = 0;i != sizeof(MidRes)/sizeof(char);i++)    //中间结果小写转大写
MidRes[i]>=97 && MidRes[i]<=122 ? MidRes[i] -= 32 : MidRes[i];
// printf("%04x",res);
if (MidRes[0] != Sn[0])
{
// cout<<"错误的序列号"<<endl;
return false;
}
if (MidRes[1] != Sn[1])
{
// cout<<"错误的序列号"<<endl;
return false;
}
/* if (!ReSetCheck(Name,Sn))            //先不用看这个重启校验的函数
{
cout<<"重启校验失败"<<endl;
return false;
}             */

cout<<"正确的序列号"<<endl;
getchar();
getchar();
return true;
}
bool ReSetCheck(char Name[],char Sn[])      //重启校验的函数
{
DWORD Sum = 0x1;
for (int i = 0;i != lstrlen(Name);i++)
Sum = Sum + (BYTE)Name[i];
Sum = Sum + Sum*8 +0xA;
__asm
{
pushad;
mov ecx,Sum;   //累加结果放ecx
mov eax,55555556h;
imul ecx;    //结果和55555556相乘
mov eax,edx;   //高位放eax
shr eax,1Fh;
xor ecx,ecx;
add ecx,edx;   //ecx = eax + edx + 24
add ecx,eax;
add ecx,24h;
and ecx,8000000Fh;
jns over;
dec ecx;
or ecx,4294967280d;    //or ecx,FFFFFFF0h,这样写居然不行?
inc ecx;
over: mov Sum,ecx;
popad;
}

char Res[10];
wsprintf(Res,"%x",Sum);
for (i = 0;i != sizeof(Res)/sizeof(char);i++)    //结果小写转大写
Res[i]>=97 && Res[i]<=122 ? Res[i] -= 32 : Res[i];
if (Res[0] == Sn[4])
return true;
return false;
}

程序通过枚举序列号来和指定的用户名进行 校验检测,如果注册码符合了条件则停止枚举
先不要看那个重启校验的函数,根据这个注册机,输入用户名XIAOBAOZI   (大写的),跑了半个小时,出来一个注册码
3A10H-66D5B-EJC72-JML1I-ME9D5
使用这个注册码,弹出让我们重启程序使注册码生效的消息框,说明这个注册码满足了校验函数的要求。重启程序,结果却还是弹出无效的注册码的消息框。。。
为什么呢?很奇怪,既然他重启校验也是使用的那个校验函数,为什么会不对呢?

 
2009年09月29日 星期二 14:23

首先输入注册码

发现有错误提示,bp MessageBoxA,发现断不下来,用bp MessageBoxW,成功断下
alt + F9 返回到程序领空(按一下确定就可以到了程序领空)

往上面看,看有没的跳转可以跳过这个MessageBox的,结果没有发现可以跳过这个MessageBox的跳转,说明这个函数就是提示错误的,只要这个函数以调用就会弹出错误的对话框,这样的话,我们单步走到ret,看看是什么地方调用了这个函数

发现有一个je跳转可以跳过这个函数,可能是爆破点,于是在函数头下断,运行到je的时候修改标识寄存器的z标志为1,直接跳过错误提示的函数,结果在call   004E4F92这个函数后还是提示错误,仔细观察错误提示函数和004E4F92这2个函数,发现使用了相同的参数,都是[ebp+C]和[ebp+8],在数据段中查看这2个参数

发现[ebp-10]中存放的就是提示错误的字符串,说明这个je不是爆破点,这整个函数也是一个错误提示函数,调用则提示错误,使用上面的思路,运行到ret,看看是什么地方调用了这个函数

发现有一个jnz跳转可以跳过这个函数,那这个是不是爆破点呢?老办法,在jnz上下端,然后修改标志寄存器Z为0,跳过后在运行,结果弹出这样的对话框

然后点帮助菜单,点关于选项,弹出这样的信息

说明这个jnz就是爆破点了,上面的call 00495440就是校验函数
他说要重新启动程序使注册码生效,我们重新启动一下程序。

结果弹出这样的信息

说明他是有重启验证的,上面的jnz爆破点只是校验输入时候的用户名和注册码的,还有一个重启校验的点要找。相同的方法,下断bp MessageBoxW

往上面找,看有没可以跳过这个消息框的跳转,一直找到函数头都没有找到,说明这个函数一旦执行就提示注册码无效了。直接运行到ret,看看那个地方调用了这个函数

再次发现上面有个je跳可以跳过这个无效注册码提示的函数,还是上面的老方法,下断,然后修改标识寄存器Z实现跳转,结果发现还是提示的无效的注册码,说明,这整个函数也是一调用就提示了无效的注册码,一层一层的跟出来

看来是一个switch语句,当switch(6D)的时候就执行了弹出无效注册码消息框的函数,来到函数头,看看

发现,eax是通过[esp+10]传过来的,[esp+10]显然是一个形参值,于是看堆栈中的返回地址,Ctrl+G,输入返回地址,直接来到调用这个包含switch语句的函数

发现又是一个switch,往上面翻,居然看到switch WM_COMMAND这些了,难道这已经到消息循环了,貌似走远了,看来这条路行不通。。。。怎么办呢?

我们知道重启校验的话一般都是通过注册表或KeyFile来检测的,于是我们从头来,来到我们先找到的爆破点,前面那个call就是关键call了,在call那下断点

进去这个计算用户名和注册码的call,没有发现有保存信息到KeyFile或注册表的API,说明保存信息的功能在下面实现,跳过爆破点,看call 00494CC0 这个函数,前面压入栈的2个参数,一个是用户名,一个是注册码,很有可能就是保存信息的函数,我们F7进去,F8一直走

可以看到,有注册表操作的函数哦,原来把信息写到了
Software\ES-Computing\EditPlus3\Install"下的license子键。

好了,重新加载程序,下断RegQueryKeyEx,观察堆栈,看到

时,F8一直走,来到

看到了&nbsp;SetTimer函数,还记得在跟踪&nbsp;无效的注册码的消息框的时候,有个KillTimer么?会不会有点关系呢?再看看call 00495440,这个就是在输入注册码和用户名之后校验的函数,说明程序启动从注册表读取了信息之后就调用了这个函数来校验从注册表读入的信息。这个函数后面又有一个jnz,会不会就是重启校验的爆破点呢?我们运行到jnz的时候,修改标识寄存器Z,使得跳转实现,然后继续运行,结果没有弹出错误的对话框,点帮助菜单,选择关于,显示

说明这就是重启校验的爆破点了再仔细看一下,无论是输入注册码和用户名那的校验,还是重启之后的校验,都是通过这个函数来实现的,只要函数返回值不为0就能完美爆破了。。


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

 
2009年07月04日 星期六 22:02

WIndows为每个进程分配了4GB的虚拟地址空间,让每个进程都认为自己拥有4GB的内存空间,4GB怎么来的? 32位 CPU可以取地址的空间为2的32次方,就是4GB(正如16位CPU有20根寻址线所有拥有2的20次方的寻址空间一样)

当我们在Windows中双击一个应用程序图标后,系统为该应用程序创建一个进程,Windows使得每个进程都拥有2GB的地址空间,这2GB地址空间用于程序存放代码,数据,堆栈,自由存储区(堆),另外2GB用于共享系统使用



前面的这些地址并不是物理内存中的地址,而是该进程空间中的虚拟地址
虚拟空间只是Windows为该进程分配的一个虚拟的地址空间,只有当其和物理内存相关联后才有意义

内存的分页
每个物理地址对应一个虚拟地址?1GB那页表该有多长,所以将内存分页管理,4K为一页,即4K就是一个最小单位。虚拟地址到物理地址的映射见图,中间的那个就是页表了。

如何映射?
进程被创建时会建立一个 虚拟内从到物理内存的映射表--------页表,根据页表可以将虚拟内存和物理内存关联起来

-----------------------------------------页表如何工作,怎么将虚拟地址关联物理地址----------------------------------

虚拟内存是什么?
就是把磁盘拿来当内存用,这是以前买电脑时的想法。
所以就一直都想不明白一个问题:要真是这样,那内存分个什么1GB,2GB,4GB,大家都买个1M的内存条,然后把自己磁盘拿来当内存用多好,比2GB,4GB不知道要大多少。
其实这个说法有一点擦边球的味道,虚拟内存是一些系统页文件,存放在磁盘上,每个系统页文件大小也为4K,物理内存也被分页,每个页大小也为4K,这样虚拟页文件和物理内存页就可以对应,实际上虚拟内存就是用于物理内存的临时存放的磁盘空间。

页文件就是内存页,物理内存中每页叫物理页,磁盘上的页文件叫虚拟页,物理页+虚拟页就是系统所以使用的页文件的总和。还有映像页文件和映射页文件,映像页文件就是拿程序本身当页文件使用(而不是用系统的页文件),映射页文件就是使用磁盘上的文件(非系统页文件)来当页文件使用(这主要用于读取文件)。

虚拟地址页的状态:
空闲:该区域没有被所使用,也没有被预定,没有和物理内存管理
私有:该区域虽然没有被使用,但是已经被申请(预定了),别人无法使用它。同样也没和物理内存关联
提交:该区域已经和物理内存管理,可以使用了

虚拟内存和物理内存的管理(Windows内存管理的核心)
Windows是多任务的系统,在每个进程创建时,系统为每个进程也创建了一个页表,用于虚拟地址到物理地址的转换。比如现在程序在执行进程A,用户切换到了另外一个进程B,则系统会将进程A在内存中的数据存放到页文件中,并更新进程A的页表(使虚拟地址和页文件形成映射)。然后读取进程B的页表,根据页表判断进程B的数据是在内存中还是在页文件中(通过页文件的类型来判断),如果在内存中就直接读取,如果在页面文件中,就将页面文件内容读入物理内存,然后更新页表(使虚拟地址和物理内存形成映射)。这样一看,虚拟内存实际上就是冒牌的物理内存了吧。

程序的执行
一个PE文件有数据区,代码区,堆栈区(由系统分配,用于管理局部变量),使用OD载入一个程序就可以知道这些都是以二进制的形式保存在文件中。
程序刚运行的时候,系统不直接将整个程序载入到物理内存中,也不将其载入到页文件中,而是以程序文件本身作为页文件形成映射(虚拟地址到页文件的映射),建立页表,然后随着程序的执行通过页表来将其虚拟地址转换成物理地址(将页文件读入内存),然后在读取内存中的指令或数据。当进程被切换时,将内存内容保存到页文件,更新页表,如此往复,实现多任务操作。

可以知道,程序的代码段,数据段,堆栈区(系统分配)这些虚拟地址区域已经是映射状态,即有相应的物理内存与之对应。系统为每个进程提供了2G的自己的虚拟地址空间,剩下的虚拟地址空间干什么用?
剩下的虚拟地址空间就是给程序运行时动态分配内存使用。C++中 new的功能就是动态分配地址空间:

申请内存的最小单位是区域,每个区域为CPU粒度大小,即64K,每次申请的内存都必须是64K的整数倍,C++ new功能申请一个区域,保留该区域,然后提交需要的页,其他的保留。

char *address=new char[1024];   //分配1K的内存
这条语句首先申请一个区域的地址空间,表示这个区域已经被预定了,这就是上述区域状态中的私有状态,虽然预定了,但是还没有和物理内存关联起来,所以程序也无法使用该内存,然后程序将这1K的内存提交,就是映射到了内存当中,区域的状态就变成了映射状态,这样程序就可以使用这1K的内存了,而剩下的页仍然为保留状态。那当进程被切换时,这1K的进程存放在哪呢?程序本身的页文件已经被 代码,全局数据,堆栈这些所使用了,所以系统会为自由存储区分配的内存分配新的页文件来做虚拟内存。

局部变量的定义是由系统分配的,它将局部变量分配到堆栈区,因为堆栈区已经映射了,所以不用在映射,故不用使用新的页文件了。堆栈区的大小为1M左右,如果分配的局部变量超过1M会产生堆栈溢出。

可以看到,系统的单个页文件大小为4K,程序自己的虚拟空间地址从00010000到7FFEFFFF差不多是2G
动态分配一个500M的内存后,物理内存,页文件,可用的虚拟地址空间都减少了500M

查询内存状态使用VirtualQuery(Address[n],&membaseinf,sizeof(MEMORY_BASIC_INFORMATION))
定义3个变量
char Stack[20*1024];          //存在堆栈中,堆栈在程序启动时已经被映像到内存中了
char* Dynamic=new char[64*1024];       //动态分配一个70K的内存
char* Dynamic2=new char[1024];          //动态分配一个1K的内存

地址所在页面基地址:查询的地址所在的页面的起始地址
页面所在区域的基地址:页面所在区域的起始地址
区域保护属性:分配区域时要设置区域的读写属性
从页面基地址开始拥有相同属性(空闲,保留,提交)的所有页的字节数:可以看到这些都是4096的整数倍,因为一个页4096,该大小一般都和申请的内存空间大小相当,因为这些内存都被提交了。

申请一个内存空间的过程
首先申请一个虚拟地址空间区域,然后提交申请的内存空间大小的页(将其和页文件关联)。其他的地址空间保留。

第一条指令分配了一个字符数组的局部变量,该变量分配在堆栈中,由系统分配,所以其区域为程序的静态存储区,即在程序启动时候这个区域的所有虚拟地址就和程序文件本身映像了,所以局部变量的区域基地址都是一样的,那为什么它的页面文件类型是页文件呢?不应该是exe映像么?因为现在文件在内存中,所有是物理页,就是页文件。

第二条指令动态分配了一块大小为1K的内存区域,这块内存分配在自由存储区,它所在的区域是在堆中申请的一个区域,第三条指令在堆中分配了一个70K左右的内存,因为他们是在堆中分配的,所以这2个变量的区域基地址是不一样的。

分配的区域有多大?
第三条指令分配了一个70K左右的内存,它会向系统申请多大的区域呢?由区域大小为64K的整数倍知该区域至少为128K,查询这70K之后的虚拟地址的状态

可以看到该地址所在的区域和Dynamic是一样的,它的基地址为594000,在那70K之后,这之后的区域的状态为保留,说明系统保留了剩下的区域,这剩下的区域有966656,就是966K左右的大小,那整个区域的大小就是(0x14000)81920+966656。

 
2009年06月29日 星期一 19:57

第一个,GDI实现时钟程序。

Clock窗口创建时定义一个计时器,该计时器过程函数中使用InvalidateRect函数,每隔1秒将Clock的整个客户区变成无效区域,从而促使Clock窗口激发WM_PAINT消息,在WM_PAINT消息中绘制整个时钟画面
绘制过程:1绘制60个小刻度点表示分和秒。
                  2绘制12个小刻度点表示时。
                  3绘制时分秒针

注意事项:
1在WM_PAINT消息中要以BeginPaint和EndPaint函数来开头和结尾,InvalidateRect(hWnd,NULL,TRUE)第三个才参TRUE表示使用背景色将该区域清除。如果该参数为FALSE,则不会用背景色清除该区域的内容,从而造成画面重叠,(当然你可以再WM_PAINT中首先用背景色填充一次客户区),如果WM_PAINT消息中没有BeginPaint和EndPaint,要使用ValidRect来使该区域变得有效,不然会无止尽的触发WM_PAINT,但是这样虽然可以骗过WINDOWS,但是不能清除客户区,造成画面叠加。

 
     


©2009 Baidu