返回
顶部

references:

上回我们说到CrackHead的注册机编写,但是由于触及到了我的知识盲区img,所以就暂时放在那里没写

现在我知道了如何下内存断点,那么问题就迎刃而解了

我们知道0x40339C这个地址决定了ESI的值,但是我们并不知道这块地址中的值是怎么来的,通过在这个地址下内存断点,可以定位到更改该内存内容的代码

1652062405363

可以看到更改内存的代码位于ntdll内,这些都是系统代码,我们一路返回即可,最后定位到CrackHead的代码,在此下断点即可,此时内存断点就可以清除了,当然你不清除也没啥影响

我们只需要把这一部分代码搞明白就行了:

1652062405363

从倒数第三行可以看到,ESI的值被写入了0x40339C中,而EDI又是通过下面这一段循环代码计算出来的

0040143E  |. 33FF           XOR EDI,EDI
00401440  |> 8BC1           MOV EAX,ECX
00401442  |. 8B1E           MOV EBX,DWORD PTR DS:[ESI]
00401444  |. F7E3           MUL EBX
00401446  |. 03F8           ADD EDI,EAX
00401448  |. 49             DEC ECX
00401449  |. 83F9 00        CMP ECX,0
0040144C  |.^75 F2          JNZ SHORT CrackHea.00401440

首先EDI被清空,然后进行循环,循环次数为ECX的值,EAX被初始化为ECX的值,在这段循环代码中,我们只需要知道ESI、ECX、EBX的值即可计算出EDI的值

00401431  |. 8D35 9C334000  LEA ESI,DWORD PTR DS:[40339C]
00401437  |. 0FB60D EC33400>MOVZX ECX,BYTE PTR DS:[4033EC]

从这两行代码我们可以知道ESI的值为x040339C,那么EBX的值就是内存地址为0x40339C处的内容,ECX的值为内存地址0x4033EC处的内容

这上面有一条指令可能大家之前没有见过,就是MOVZX(move with zero-extend),该指令跟MOV差不多,就是高位补0而已

比方说你把一个16位的值通过MOVZX指令放到EAX(32bit)中,那么MOVZX会自动将EAX的高16bit置0

再往上看

0040140C  /$ 60             PUSHAD
0040140D  |. 6A 00          PUSH 0                                   ; /RootPathName = NULL
0040140F  |. E8 B4000000    CALL <JMP.&KERNEL32.GetDriveTypeA>       ; \GetDriveTypeA
00401414  |. A2 EC334000    MOV BYTE PTR DS:[4033EC],AL
00401419  |. 6A 00          PUSH 0                                   ; /pFileSystemNameSize = NULL
0040141B  |. 6A 00          PUSH 0                                   ; |pFileSystemNameBuffer = NULL
0040141D  |. 6A 00          PUSH 0                                   ; |pFileSystemFlags = NULL
0040141F  |. 6A 00          PUSH 0                                   ; |pMaxFilenameLength = NULL
00401421  |. 6A 00          PUSH 0                                   ; |pVolumeSerialNumber = NULL
00401423  |. 6A 0B          PUSH 0B                                  ; |MaxVolumeNameSize = B (11.)
00401425  |. 68 9C334000    PUSH CrackHea.0040339C                   ; |VolumeNameBuffer = CrackHea.0040339C
0040142A  |. 6A 00          PUSH 0                                   ; |RootPathName = NULL
0040142C  |. E8 A3000000    CALL <JMP.&KERNEL32.GetVolumeInformation>; \GetVolumeInformationA

第一行的PUSHAD指令意为push all general-purpose register,就是把所有通用寄存器压栈

这一段代码其实就是两个系统函数的调用

下面这行代码将GetDriveTypeA的返回值放到了地址为0x4033EC的内存中

00401414  |. A2 EC334000    MOV BYTE PTR DS:[4033EC],AL

因为该函数的返回值最大值为6,所以AL(8bit)就足够存放返回值了

然后是GetVolumeInformationA

00401425  |. 68 9C334000    PUSH CrackHea.0040339C                   ; |VolumeNameBuffer = CrackHea.0040339C

这个是该函数的第二个参数,根据文档,该参数为传出参数,存放的是卷标的名称,之前之所以测了好几台机器都是一样的注册码,就是因为那几台机器都没有卷标

卷标就是这个东西,是用户自己设置的,默认是没有的

1652062405363

那么现在我们现在就已经获取到了所有需要的变量的值了,写出对应的C代码计算出序列号就完事了

编写思路如下:

ECX的初始值,0x4033EC指针的值我们已经拿到了,就是GetDriveType的返回值

EBX的初始值,0x40339C指针的值,这个值我们也有,就是GetVolumeInformation的传出参数VolumeName的值

EAX = ECX

EBX = VolumeName前四个字节(由于小端序的原因,需要进行翻转(高位在高址))

EAX=EAX*EBX EDX存放高32位,EAX存放低32bit 本次计算与EDX无关,我们只需要低32bit的值就行了

EDI=EDI+EAX EDI初始值为0

ECX用于循环计数

我直接用codeblocks编译的

#include   <windows.h>
#include   <stdio.h>

int main(int argc, char **argv) {
    char   volume_name[256]={0};

    GetVolumeInformation( NULL,volume_name,11,NULL,NULL,NULL,NULL,NULL);

    printf("[+] volume name: %s\n", volume_name);

    char _24_31[3]={0};
    char _16_23[3]={0};
    char _8_15[3]={0};
    char _0_7[3]={0};

    sprintf(_24_31, "%02X", (unsigned char)volume_name[0]);
    sprintf(_16_23, "%02X", (unsigned char)volume_name[1]);
    sprintf(_8_15, "%02X", (unsigned char)volume_name[2]);
    sprintf(_0_7, "%02X", (unsigned char)volume_name[3]);

    char* hex_string = (char*)malloc(8+1);
    strcpy(hex_string, _0_7);
    strcat(hex_string, _8_15);
    strcat(hex_string, _16_23);
    strcat(hex_string, _24_31);
    unsigned int ecx = GetDriveType(NULL);
    printf("[+] drive type: %u\n", ecx);
    unsigned int ebx = (unsigned int)strtoul(hex_string, NULL, 16);
    printf("[+] ebx initial value: 0x%x\n", ebx);
    free(hex_string);
    printf("[+] ecx initial value: %u\n", ecx);
    unsigned int eax = 0;
    unsigned int edi = 0;

    int i = 0;
    for(i=0; i<ecx; i++) {
        eax = ecx-i;
        eax *= ebx;
        edi += eax;
    }

    printf("[+] edi value: 0x%x\n",edi);
    const unsigned int const_value = (unsigned int)strtoul("797A7553", NULL, 16);
    printf("[+] calculated serial number: %u\n", const_value^edi);

    return   0;
}