Como hacer un Key Generator para el Winzip 7.0 por Black Fenix

Otra vez estoy aquí, esta vez vamos a subir un poco el listón y vamos a hacer un generador de numeros de serie para el WinZip 7.0. que es lo que un verdadero cracker debe saber hacer.

necesita.gif (15203 bytes)


marcador.gif (1024 bytes)SoftIce para windows
marcador.gif (1024 bytes) W32Dasm
marcador.gif (1024 bytes) Turbo Pascal, para hacer el Key Generator (Opcional)

comenza.gif (16493 bytes)

Como siempre pondremos en marcha el SoftIce antes de ejecutar el programa luego ejecutamos el programa. Cada vez que lo ejecutamos aparece una ventana que nos comunica que el programa es una versión totalmente funcional pero no registrada, dandonos la opción de continuar, salir o introducir el numero de registro.
Esta última opción es la que seleccionaremos haciendo click sobre el boton 'Enter Registration Code'.Ahora nos aparece una ventana donde podemos introducirle los siguientes datos:

Name:
Registration #:

Esto es el nombre y el código de registros, actuaremos como es habitual en este tipo de casos.

marcador.gif (1024 bytes) Activamos el SoftIce (Ctrl+D) y ponemos un bpx getdlgitemtexta, salimos del SoftIce (Ctrl+D) e introducimos los datos por ejemplo

Name : Black Fenix
Registration #: 12345
pulsamos Ok y

marcador.gif (1024 bytes) Boom! estamos de nuevo en el SoftIce el bpx a surtido efecto, pulsamos F12 y aterrizamos en el código fuente del ejecutable del programa WinZip32.exe en estos momentos el programa está leyendo el nombre que le hubiesemos introducido, en mi caso 'Black Fenix' la dirección donde lo almacena está en EBX puedes
comprobarlo haciendo D EBX (dentro del SoftIce).

marcador.gif (1024 bytes) Pulsamos g para seguir con la ejecución del programa y de nuevo apareceremos en el SoftIce, esta vez el programa esta leyendo el numero que le hemos introducido, F12 y estaremos en el código del ejecutable. Ahora vamos a ir trazando paso a paso y vamos a ir viendo que es lo que hace el programa con el nombre y nuestro número no válido.
Esto es lo que podemos ver despues de pulsar F12.
|

:00408036FF150C844600Call dword ptr [0046840C]
:0040803C56push esi // En esta dirección está nuestro numero (malo)
:0040803DE857160200call 00429699 // llama a una función (descartada)
:0040804259pop ecx
:0040804356push esi // En esta dirección está nuestro numero (malo)
:00408044E879160200call 004296C2 // llama a una función (descartada)
:00408049803D28D9470000cmp byte ptr [0047D928], 00 // mira si el primer caracter del nombre es NULL
:0040805059pop ecx
:00408051745Fje 004080B2 // salta si es NULL
:00408053803D58D9470000cmp byte ptr [0047D958], 00 // mira si el primer caracter del número es NULL
:0040805A7456je 004080B2 // salta si es NULL
:0040805CE8EAFAFFFFcall 00407B4B // llama a una función que retorna un valor en EAX
:0040806185C0test eax, eax // esto es sospechoso
:00408063744Dje 004080B2 // salta si es 0 hmmm...
:0040806553push ebx

 

Un vistazo a este código nos permite afirmar con toda seguridad que si el numero o la cadena no se introdujeron (esto se comprueba con los cmp byte ptr [XXXXXXXXX], 0) el programa salta a una parte donde se nos comunica que los datos introducidos no son correctos. También sucede lo mismo si al regresar de la llamada a 407B4B EAX es igual a 0, esto es muuyyyy sospechoso, por lo que al llegar a esta función pulsaremos F8 para trazarla paso a paso.

marcador.gif (1024 bytes) Trazando poco a poco dentro de la función anterior iremos viendo como el programa realiza una serie de operaciones sobre el nombre, entre ellas supresión de espacios etc. Pero no será hasta llegar a la posición de código 407C0E donde el programa empuja a la pila el nombre y posteriormente nos aparecerá un misterioso numero.

:00407C0E8D85C0FEFFFFlea eax, dword ptr [ebp+FFFFFEC0] // lee dirección vacia
:00407C1450push eax // la pasa como parametro
:00407C1557push edi // EDI apunta a nuestro numero
:00407C16E8AB000000call 00407CC6
:00407C1B59pop ecx
:00407C1CBE58D94700mov esi, 0047D958 // ESI apunta a nuestro numero
:00407C2159pop ecx
:00407C228D85C0FEFFFFlea eax, dword ptr [ebp+FFFFFEC0] // lee dirección del otro número misterioso
:00407C2856push esi // empuja nuestro numero a la pila
:00407C2950push eax // empuja el otro numero a la pila
:00407C2AE8D1FC0400call 00457900 // llama a una misteriosa función
:00407C2FF7D8neg eax // invierte EAX


Despues de llamar a la funcion 407cc6, el codigo siguiente obtiene un numero misterioso de la pila y lo pasa como argumento junto con nuestro otro numero a la función 457900 esta función retorna con un valor en EAX que resulta ser un 0 o un 1 segun la 'semejanza' de los dos numeros. En mi caso el numero misterioso resulta ser '252714DB' y da la casualidad que si lo introducimos como numero de serie junto con el nombre 'Black Fenix' el programa se registrará. ( Si introduces tu numero junto con tu nombre luego podras des-registrar el programa buscando el nombre y borrandolo del editor de registros de Windows, esta en la Carpeta WinZip y la clave es SN).
Ya has deducido que función es la que calcula el numero válido, claro es muy fácil la función es la 407CC6. Bueno pues ahora borramos todos los bpx con BC * y ponemos un bpx en 00407CC6.

marcador.gif (1024 bytes) Pulsaremos G y continuaremos con la ejecución del programa reciendo el mensaje de error correspondiente. Volvemos a pulsar en OK y esta vez estaremos en la función 407CC6. Pulsamos F8 para trazarla paso a paso.

:00407CC655push ebp
:00407CC78BECmov ebp, esp
:00407CC951push ecx
:00407CCA8B4D08mov ecx, dword ptr [ebp+08] // Coge puntero al nombre
:00407CCD8365FC00and dword ptr [ebp-04], 00000000
:00407CD153push ebx
:00407CD256push esi
:00407CD38A11mov dl, byte ptr [ecx] // lee primer caracter
:00407CD557push edi // prepara registros
:00407CD633C0xor eax, eax
:00407CD88BF1mov esi, ecx // copia puntero al nombre
:00407CDA33FFxor edi, edi // EDI es el contador de caracteres
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407CF1(U)
// Esta parte calcula la segunda parte // del código válido y lo deja en [ebp-4]
:00407CDC84D2test dl, dl // comprueba que no sea un caracter nulo
:00407CDE7413je 00407CF3 // si es nulo, fin de cadena y de calculo
:00407CE0660FB6D2movzx dx, dl // extiende el caracter a DX
:00407CE48BDFmov ebx, edi // copia contador de caracteres
:00407CE60FAFDAimul ebx, edx // multiplica contador x caracter actual
:00407CE9015DFCadd dword ptr [ebp-04], ebx // lo suma a la segunda parte del codigo finbal
:00407CEC8A5601mov dl, byte ptr [esi+01] // coge siguiente caracter
:00407CEF47inc edi // incrementa contador
:00407CF046inc esi // y puntero
:00407CF1EBE9jmp 00407CDC // pasa al siguiente caracter // Aquí se inicia el cálculo de la primera parte // del código válido
:00407CF3C705ECD3470001000000mov dword ptr [0047D3EC], 00000001
:00407CFD8BF1mov esi, ecx // copia puntero al nombre
:00407CFF8A09mov cl, byte ptr [ecx] // coge el primer caracter
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407D1C(U)
:00407D0184C9test cl, cl // comprueba si es 0
:00407D037419je 00407D1E // salta a 407D1E si lo es
:00407D05660FB6C9movzx cx, cl // extiende cl sin signo a CX (CX=caracter actual)
:00407D096821100000push 00001021 // empuja 1021h = 4219
:00407D0E51push ecx // y el caracter actual
:00407D0F50push eax // y eax (la primera vez EAX = 0 )
:00407D10E82A000000call 00407D3F // llama a una función (ver más abajo) // que realiza un cálculo con el caracter actual
:00407D158A4E01mov cl, byte ptr [esi+01] // coge el siguiente caracter de la cadena
:00407D1883C40Cadd esp, 0000000C // ajusta puntero de pila
:00407D1B46inc esi // incrementa el puntero de la cadena
:00407D1CEBE3jmp 00407D01 // salta hacia atras 407D01
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407D03(C)
:00407D1E0FB74DFCmovzx ecx, word ptr [ebp-04] // coge 4 ultimos digitos del código
:00407D2283C063add eax, 00000063 // añade 63 a los 4 primeros digitos del código
:00407D2551push ecx // empuja a la pila
:00407D260FB7C0movzx eax, ax // extiende los 4 primeors digitos del codigo a EAX
:00407D2950push eax // empuja EAX
* Possible StringData Ref from Data Obj //"%04X%04X"
:00407D2A6884F44600push 0046F484 // esta función se encarga de unirlos
:00407D2FFF750Cpush [ebp+0C] // y salvarlos como una cadena de caracteres
:00407D32E869E20400call 00455FA0 // de longitud máxima 8 caracteres y en formato hexadecimal
:00407D3783C410add esp, 00000010 // si la primera parte del código tiene más de cuatro
:00407D3A5Fpop edi // caracteres, estos prevaleceran sobre la segunda
:00407D3B5Epop esi // parte del código de la cual se suprimiran los
:00407D3C5Bpop ebx // caracteres necesarios por la izquierda para dar
:00407D3DC9leave // cabida a los de la primera parte del código
:00407D3EC3ret
* Referenced by a CALL at Addresses: |:00407D10 , :00407DFB
:00407D3F55push ebp // guarda estado de la pila
:00407D408BECmov ebp, esp
:00407D428B4508mov eax, dword ptr [ebp+08] // eax = coge código actual
:00407D4556push esi
:00407D4633C9xor ecx, ecx
:00407D486A08push 00000008 // empuja 8 a la pila
:00407D4A8A6D0Cmov ch, byte ptr [ebp+0C] // lee caracter pasado en CH
:00407D4D5Apop edx // inicia un contador con 8 pasos
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407D65(C)
:00407D4E8BF1mov esi, ecx // copia caracter a esi
:00407D5033F0xor esi, eax // XOR con código actual
:00407D5266F7C60080test si, 8000 // comprueba si esá por debajo de 8000h
:00407D577407je 00407D60 // si salta
:00407D5903C0add eax, eax // no, duplica el codigo actual
:00407D5B334510xor eax, dword ptr [ebp+10] // hace un xor con 1021h (ver argumentos antes del call)
:00407D5EEB02jmp 00407D62 // salta hacia abajo
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407D57(C)
:00407D60D1E0shl eax, 1 // desplaza un bit a la izquierda el codigo actual
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407D5E(U)
:00407D62D1E1shl ecx, 1 // desplaza un bit a la izquierda el caracter actual
:00407D644Adec edx // decrementa contador
:00407D6575E7jne 00407D4E // sigue si no es 0
:00407D675Epop esi
:00407D685Dpop ebp
:00407D69C3ret

Bueno, es una rutina bastante larga pero con los comentarios que he puesto creo que podras entenderla sin problemas. Pero todo no queda aquí ya que si seguimos la ejecución del programa podremos comprobrar que se vuelve a generar otro numero sospechoso, a partir de una cadena que equivale a nuestro nombre pero
esta vez el nombre entero está en minusculas. Esto quiere decir que el programa tiene dos numeros de registro válidos por nombre, uno para el nombre tal y como se introdujo y otro para el nombre en minusculas. Para nuestra decepción el programa no usa la misma rutina de cálculo pero si que es muy similar a la que usa para calcular el primer código, aquí tienes un volcado de la rutina. Sólo he marcado lo que la hace diferente, el resto se comporta igual que la anterior.

:00407D6A55push ebp
:00407D6B8BECmov ebp, esp
:00407D6D81ECCC000000sub esp, 000000CC
:00407D7353push ebx
:00407D7456push esi
:00407D7557push edi
:00407D768D8534FFFFFFlea eax, dword ptr [ebp+FFFFFF34] // Parte de este código es para
:00407D7CFF7508push [ebp+08] // comprobar que
:00407D7F33DBxor ebx, ebx // el primer código
:00407D81895DFCmov dword ptr [ebp-04], ebx // se generó correctamente
:00407D8433FFxor edi, edi //
:00407D8650push eax //
:00407D87E894E00400call 00455E20 //
:00407D8C59pop ecx //
:00407D8D8D8534FFFFFFlea eax, dword ptr [ebp+FFFFFF34] //
:00407D9359pop ecx //
:00407D9450push eax //
:00407D95E836D60500call 004653D0 //
:00407D9A80BD34FFFFFF00cmp byte ptr [ebp+FFFFFF34], 00 //
:00407DA159pop ecx //
:00407DA28DB534FFFFFFlea esi, dword ptr [ebp+FFFFFF34] //
:00407DA8741Fje 00407DC9 //
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407DC7(C)
:00407DAA0FB606movzx eax, byte ptr [esi]
:00407DAD50push eax // +
:00407DAEE85DF80400call 00457610 // + Esta es una de la diferencia respecto a la otra // + Esta función comprueba la validez del caracter actual // + retornando un valor <> de 0 si es válido // + rutina
:00407DB385C0test eax, eax // + si retorna 0
:00407DB559pop ecx // + el caracter no es válido y no se tiene
:00407DB6740Bje 00407DC3 // + en cuenta para el cálculo
:00407DB8660FB606movzx ax, byte ptr [esi]
:00407DBC0FAFC3imul eax, ebx
:00407DBF0145FCadd dword ptr [ebp-04], eax
:00407DC243inc ebx
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407DB6(C)
:00407DC346inc esi
:00407DC4803E00cmp byte ptr [esi], 00
:00407DC775E1jne 00407DAA
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407DA8(C)
:00407DC980BD34FFFFFF00cmp byte ptr [ebp+FFFFFF34], 00
:00407DD0C705ECD3470001000000mov dword ptr [0047D3EC], 00000001
:00407DDA8DB534FFFFFFlea esi, dword ptr [ebp+FFFFFF34]
:00407DE07429je 00407E0B
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407E09(C)
:00407DE20FB606movzx eax, byte ptr [esi]
:00407DE550push eax // +
:00407DE6E825F80400call 00457610 // + Esta es otra de las diferencias respecto a
:00407DEB85C0test eax, eax // + a la otra rutina se comporta igual que
:00407DED59pop ecx // + el call anterior (si caracter no es valido
:00407DEE7415je 00407E05 // + no lo procesa)
:00407DF0660FB606movzx ax, byte ptr [esi] // +
:00407DF46821100000push 00001021
:00407DF950push eax
:00407DFA57push edi
:00407DFBE83FFFFFFFcall 00407D3F
:00407E0083C40Cadd esp, 0000000C
:00407E038BF8mov edi, eax
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407DEE(C)
:00407E0546inc esi
:00407E06803E00cmp byte ptr [esi], 00
:00407E0975D7jne 00407DE2
* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407DE0(C)
:00407E0B0FB745FCmovzx eax, word ptr [ebp-04]
:00407E0F8B750Cmov esi, dword ptr [ebp+0C]
:00407E1283C763add edi, 00000063
:00407E1550push eax
:00407E160FB7C7movzx eax, di
:00407E1950push eax
* Possible StringData Ref from Data Obj //"%04u%04u"
:00407E1A6890F44600push 0046F490
:00407E1F56push esi
:00407E20E87BE10400call 00455FA0
:00407E2583C410add esp, 00000010
:00407E2880660800and byte ptr [esi+08], 00
:00407E2C5Fpop edi
:00407E2D5Epop esi
:00407E2E5Bpop ebx
:00407E2FC9leave
:00407E30C3ret
// Este es el volcado de la rutina llamada en los puntos marcados con + de la rutina anterior:
:00457610833DBC63470001cmp dword ptr [004763BC], 00000001 // comprueba algo ??
:004576177E13jle 0045762C // este salto no se realiza si se esta // introduciendo un numero de registro
:004576198B442404mov eax, dword ptr [esp+04]
:0045761D6803010000push 00000103 // Pasa 103h a como primer argumento
:0045762250push eax // Este argumento es el código del caracter // actual
:00457623E8F82D0000call 0045A420 // Llama a la función
:0045762883C408add esp, 00000008
:0045762BC3ret

Este es el volcado de la rutina llamada en la rutina anterior. Esta rutina retorna un valor segun el caracter pasado por la anterior, se usa para comprobar si los caracteres del nombre introducido van a ser procesados. Si retorna 0, el caracter no será tenido en cuenta cuando se realiza el cálculo del segundo código.

 

:0045A42051push ecx
:0045A4218B4C2408mov ecx, dword ptr [esp+08]
:0045A42556push esi
:0045A4268D4101lea eax, dword ptr [ecx+01]
:0045A4293D00010000cmp eax, 00000100
:0045A42E7715ja 0045A445 // este salto nunca se realiza cuando se esta introduciendo un código de registros
:0045A4308B15B0614700mov edx, dword ptr [004761B0] // Carga puntero a un tabla
:0045A43633C0xor eax, eax // borra EAX
:0045A438668B044Amov ax, word ptr [edx+2*ecx] // usa el código del caracter pasado como índice a una tabla
:0045A43C8B4C2410mov ecx, dword ptr [esp+10] // lee 103h en ecx
:0045A44023C1and eax, ecx // Realiza un AND con 103h sobre
:0045A4425Epop esi // el valor de la tabla
:0045A44359pop ecx
:0045A444C3ret // resultado en eax, este resultado es comprobado por las rutina marcadas con +

El problema reside en la tabla, tenemos que copiarla para poder reproducir el algoritmo. Su contenido lo podemos encontrar a partir de la dirección 4761B0, son exactamente 256 valores uno por cada código de caracter.

marcador.gif (1024 bytes) Bueno, ahora examina las dos rutinas y crea el programilla en Pascal o C que genere los dos numeros. Yo lo he hecho en Pascal, aquí tienes el código fuente. Pero recuerda copiarlo no te aportará saber y tampoco pretenderas que te lo den todo hecho eh??.

Vuelve a examinar este documento e intenta hacerlo por ti mismo si aún tienes dudas. Si necesitas desensamblar el WinZip puedes usar el W32Dasm para examinar mejor las rutinas aquí expuestas.

keygen.gif (14559 bytes)

Tambien encontrarás el programa compilado en el mismo lugar que este documento (wzipkg.exe).

Program winzip7_KeyGenerator;

Uses Crt,strings;

{ Esta es la tabla que el WinZip usa para calcular el segundo código }

Const Table:array [0..255] of byte = (
$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,
$20,$00,$28,$00,$28,$00,$28,$00,$28,$00,$28,$00,$20,$00,$20,$00,
$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,
$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,
$48,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,
$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,
$84,$00,$84,$00,$84,$00,$84,$00,$84,$00,$84,$00,$84,$00,$84,$00,
$84,$00,$84,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,
$10,$00,$81,$00,$81,$00,$81,$00,$81,$00,$81,$00,$81,$00,$01,$00,
$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,
$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,$01,$00,
$01,$00,$01,$00,$01,$00,$10,$00,$10,$00,$10,$00,$10,$00,$10,$00,
$10,$00,$82,$00,$82,$00,$82,$00,$82,$00,$82,$00,$82,$00,$02,$00,
$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,
$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,$02,$00,
$02,$00,$02,$00,$02,$00,$10,$00,$10,$00,$10,$00,$10,$00,$20,$00);

Var Name,Name2:string[80];
charb:byte;

Function ComputeCode2(Cadena:string):longint;
Var j:byte;
code,chard:longint;
Begin
    code:=0;
    for j:=1 to length(Cadena) do
    Begin
        chard:=ord(cadena[j]);
        code:=code+(chard*(j-1));
    End;
ComputeCode2:=(code shl 16) shr 16;
End;

Function ComputeCode2b(Cadena:string):longint;
Var j,k:byte;
code,chard:longint;
Begin
    code:=0;
    k:=0;
    for j:=1 to length(Cadena) do
    Begin
        chard:=ord(cadena[j]);
        if boolean(Table[chard*2] and $103) then
        Begin
            code:=code+(chard*k);
            inc(k);
        End;
    End;
    ComputeCode2b:=(code shl 16) shr 16;
End;

Function ComputeCode1(keygen: word; charb: byte; code:longint):longint;
Var j:word;
charw1,charw2:longint;
Begin
    charw1:=charb shl 8;
    For j:=1 to 8 do
    Begin
        charw2:=charw1;
        charw2:=((charw2 XOR code) shl 16) shr 16;
        if charw2<=$8000 then
        Begin
            code:=code shl 1;
        End
        Else
        Begin
            code:=code+code;
            code:=(code XOR keygen);
        End;
        charw1:=charw1 shl 1;
    End;
ComputeCode1:=code;
End;
{ Las 3 rutinas siguientes son de Trevor Carlsen }
Function Byte2Hex(numb : Byte): String; { Byte a hex String }
Const HexChars : Array[0..15] of Char = '0123456789ABCDEF';
begin
    Byte2Hex[0] := #2; Byte2Hex[1] := HexChars[numb shr 4];
    Byte2Hex[2] := HexChars[numb and 15];
end; { Byte2Hex }

Function Numb2Hex(numb: Word): String; { Word a hex String.}
begin
    Numb2Hex := Byte2Hex(hi(numb))+Byte2Hex(lo(numb));
end; { Numb2Hex }

Function Long2Hex(L: LongInt): String; { LongInt a hex String }
begin
    Long2Hex := Numb2Hex(L shr 16) + Numb2Hex(L);
end; { Long2Hex }

FUNCTION LowerCase( s: STRING ): STRING; ASSEMBLER;
ASM
    PUSH DS
    CLD
    LDS SI, s
    XOR AX, AX
    LODSB
    XCHG AX, CX
    LES DI, @Result
    MOV BYTE PTR ES:[DI], CL
    INC DI
    JCXZ @@3

    @@1: LODSB
    CMP AL, 'A'
    JB @@2
    CMP AL, 'Z'
    JA @@2
    OR AL, $20

    @@2: STOSB
    LOOP @@1

    @@3: POP DS
    END;

Var i:byte;
codigo1a,codigo2a,codigo1b,codigo2b:longint;
c1:string;
c2:string[4];
codigo2:string[8];
Begin
    textcolor(7);
    Writeln('WinZip 7.0 Key Generator by Black Fenix (c) 1999:');
    Writeln('-------------------------------------------------');
    Write('Enter your name: ');
    Readln(Name);
    Name2:=Name;
    Name2:=LowerCase(Name2);
    codigo1a:=0;
    codigo2a:=0;
    codigo1b:=0;
    codigo2b:=0;
    for i:=1 to length(Name) do
    Begin
        charb:=ord(Name[i]);
        if charb<>0 then
        Begin
            codigo1a:=ComputeCode1($1021,charb,codigo1a);
        End Else break;
        charb:=ord(Name2[i]);
        if boolean(Table[word(charb*2)] and $103) then
        Begin
            codigo1b:=ComputeCode1($1021,charb,codigo1b);
        End
    End;
    codigo1a:=((codigo1a+$63) shl 16) shr 16;
    codigo1b:=((codigo1b+$63) shl 16) shr 16;
    codigo2a:=ComputeCode2(Name);
    codigo2b:=ComputeCode2b(Name2);
    { convierte a cadena la primera parte del segundo codigo }
    str(codigo1b,c1);
    { convierte a cadena la segunda parte del segundo código }
    { con un longitud máxima de 4 caracteres }
    str(codigo2b:4,c2);
    { concatena los dos código, Nota: nunca sobrepasaran los 8 caracteres ya que -> codigo2[8]:string }
    codigo2:=c1+c2;
    write('Valid registration numbers are: ');
    textcolor(15);
    writeln(Numb2Hex(codigo1a),Numb2Hex(codigo2a),' or ',codigo2);
    textcolor(7);
End.


You are inside Reversed Minds pages.

por Mr. Silver / WKT!
La información aquí vertida es exclusivamente para uso educacional, no puedo hacerme responsable del uso que se haga de esta, por lo que atiendo a la honradez de cada uno :), recuerda que debes comprar el software que utilices :)