URL:

Opção :




CRIANDO UM KEYGEN E ENTENDENDO SEU FUNCIONAMENTO

KeyGen (Key Generator) como o próprio nome já diz, é um gerador de chaves ou seriais para registrar programas de forma ilegal. Para qualquer nome digitado ele gera um serial válido que quando inserido no programa oficial é aceito como se tivesse sido obtido pelos meios legais.

Um pouco de lógica

Entender a lógica por trás da validação do Nome e Serial do CrackMe ajudará quando estivermos lidando com os códigos.

Vimos que antes das mensagens de Erro ou Sucesso o programa chama uma função denominada “compare” que na verdade é o Offset 00401A08.

004019CF CALL CrackMe.00401A08

E baseado no retorno dela exibe a mensagem adequada.

Com isso podemos imaginar que a função que se inicia no Offset 00401A08 deve ser a responsável por validar os dados digitados. Sem ainda entrarmos nos códigos podemos imaginar também algumas operações que a função de validação deve executar.

Primeiro ela deve obter os valores que foram digitados nos dois campos. Depois, se cada Nome possui um Serial diferente, ela deve deve pegar o que foi digitado no campo Nome, fazer uma série de operações que não sabemos quais são, pegar o resultado e compará-lo com o que foi digitado no campo Serial. Caso os dois sejam iguais o Serial digitado é válido.

Um algoritmo simplificado para isso seria assim:

Leia NomeDigitado;
Leia SerialDigitado:
Resultado = OperaçõesCom(NomeDigitado);
Se (Resultado == SerialDigitado)
Serial Válido;
Senão
Seria Inválido;

De um modo geral muitos programas fazem a validação dessa forma. O “X” da questão, ou a proteção do programa, está na dificuldade de descobrir quais operações são realizadas, podem ser muitos cálculos, recursividade, combinações, atribuições, etc.

Dependendo do grau de complexidade dessas operações o algoritmo parece funcionar muito bem. Pense então na depuração desse algoritmo, onde vamos executando linha a linha com a ajuda de um Debugger e vendo os resultados. Conseguiu enxergar uma falha aí? Que valor seria exibido na variável Resultado?

A variável Resultado seria na verdade um Serial válido para o Nome digitado. Mesmo digitando um Serial qualquer o programa geraria uma Serial válido para comparar com o que digitamos. E através da execução linha a linha do algoritmo seríamos capazes de visualizá-lo.

O processo de obter um Serial válido através da execução do programa em um Debugger é conhecido como “pescar serial” ou “serial fishing”.

Pescar o Serial no CrackMe

Um dos desafios do artigo anterior era conseguir pelo menos um Nome e Serial válido para o CrackMe. Isso é possível através do debug do programa no OllyDbg.

Abre o CrackMe no OllyDbg, coloque um Breakpoint (BP) no Offset 00401A08 clicando uma vez na linha e pressionando F2. Agora pressione F9 (Run) para iniciar o debug. Com isso o CrackMe irá executar e quando chegar na linha que colocamos o BP irá parar e permitir que executemos linha a linha.

Insira um Nome e Serial qualquer e clique em Registrar, eu inseri “crimes ciberneticos” e “1234”. O debug irá parar no nosso BP.


Agora vá pressionando F8 e acompanhando a execução, lembre da lógica explicada anteriormente. Quando chegar no Offset 00401A71 você vai ver (ASCII “nomedigitado”), nessa linha está lendo o Nome que digitamos e armazenando em algum lugar. O mesmo acontece com o Serial no endereço 00401A7C.

Seguindo pressionando F8 vemos que a execução entra em um loop, pode ser as operações que falamos anteriormente. Continue no F8 até sair do loop.

Quando chega no Offset 00401ACF é exibido um número, no meu caso (ASCII “28065600”). Eis aí o que estávamos procurando, o Serial.


Para confirmar se realmente é válido, executamos o CrackMe, inserimos os dados obtidos e vemos o resultado.


KeyGen

O mecanismo por trás de um keygen é o mesmo utilizado pelo programa original. Acabamos de ver que o próprio programa gera um Serial válido para um Nome que foi digitado. Então a função que faz isso está dentro do programa, precisamos localizá-la e copiá-la para o nosso keygen.

Copiar como? Existem algumas formas de fazer isso. Uma forma é tentar entender o Assembly linha a linha e depois reescrever o código com a linguagem de programação predileta.

Outra forma é copiar o Assembly do jeito que está e colar dentro de nosso programa (Assembly Inline) com pequenas adaptações, isso nos poupa de tentar entender o que o código faz. Essa técnica é chamada de Ripping. Vejamos as duas.

Reescrevendo o Código

Essa solução foi fornecida pelo Gustavo, ele leu o artigo anterior e deixou um comentário com uma solução de keygen em Python. O código dele funciona perfeitamente, conseguiu entender o códigos em Assembly e em poucas linhas rescreveu toda a lógica em Python.

O código dele é esse:
import sys
acumula=0
c=0
while c<len(sys.argv[1]):
 acumula=acumula+ord(sys.argv[1][c])
 c=c+1
print (acumula*0x78)*0x78

No Python não é preciso compilar, é necessário apenas ter o interpretador instalado no computador, existem versões para Linux e Windows, mais informações no site oficial do projeto python.org.

Copie o código, salve em um arquivo com o nome de keygen.py por exemplo e depois execute a linha de comando:

python keygen.py nomedesejado

Como podemos ver abaixo, para o nome “crimes ciberneticos” gerou o mesmo Serial que pescamos anteriormente.

Assembly Inline - Ripping

Na técnica de Ripping precisamos localizar no Assembly do programa apenas as linhas relativas à geração do Serial. Depois copiamos o trecho e colamos em um programa que aceite códigos Assembly Inline. Nesse caso utilizarei a linguagem C.

Na instrução:

00401A71 MOV DWORD PTR SS:[EBP-30],EAX

Vimos que o Nome que digitamos é atribuído a um endereço na Pilha (Stack) que é o EBP-30. Então sabemos que todas as linhas que fizerem referência ao EBP-30 estará lidando com o Nome que digitamos.

No Offset 00401A7C também é lido o Serial que digitamos e atribuído a um endereço na Pilha, esse valor não tem utilidade para nosso keygen, o que importa para nós são as linhas após esse Offset.

O código que se segue é esse:

00401A7F | XOR EDX,EDX
00401A81 | MOV DWORD PTR SS:[EBP-38],EDX
00401A84 | XOR ECX,ECX
00401A86 | MOV DWORD PTR SS:[EBP-3C],ECX
00401A89 | JMP SHORT CrackMe.00401A9B

00401A8B | MOV EAX,DWORD PTR SS:[EBP-30]
00401A8E | MOV EDX,DWORD PTR SS:[EBP-3C]
00401A91 | MOVSX ECX,BYTE PTR DS:[EAX+EDX]
00401A95 | ADD DWORD PTR SS:[EBP-38],ECX
00401A98 | INC DWORD PTR SS:[EBP-3C]
00401A9B | MOV EAX,DWORD PTR SS:[EBP-30]
00401A9E | MOV EDX,DWORD PTR SS:[EBP-3C]
00401AA1 | CMP BYTE PTR DS:[EAX+EDX],0
00401AA5 | JNZ SHORT CrackMe.00401A8B

00401AA7 | IMUL ECX,DWORD PTR SS:[EBP-38],78
00401AAB | MOV DWORD PTR SS:[EBP-38],ECX
00401AAE | IMUL EAX,DWORD PTR SS:[EBP-38],78
00401AB2 | MOV DWORD PTR SS:[EBP-38],EAX

A vantagem do Ripping é que não precisamos entender o que todas essas linhas fazem para criarmos nosso keygen, basta copiarmos e fazermos algumas adaptações.

Além do endereço da Pilha EBP-30 que já sabemos se tratar do Nome digitado, vemos também no código os endereços EBP-38 e EBP-3C. No início do código vemos que os registradores EDX e ECX são zerados através do XOR e depois atribuídos a esses dois endereços da Pilha.

Isso quer dizer que possívelmente são duas variáveis que são zeradas no início da função. Sendo assim no nosso código Assembly Inline vamos substituir:

EBP-30 pela variável “Nome”.
EBP-38 pela variável “var38”.
EBP-3C pela variável “var3C”.

Essas são as primeiras adaptações que faremos no código.

No programa em C não teremos endereços de Offset, retiramos todos, mas isso causa problema com os Jumps no Assembly que usam os Offsets para desviar a execução do código. Para resolvermos isso inserimos rótulos nos locais originais onde o programa “pula” com o Jumps. A compreensão disso ficará mais clara logo abaixo quando eu aprensentar o código final.

Um último detalhe é que o C só entende valores hexadecimais com a máscara 0x999, então no código Assembly onde está o valor 78 substituiremos por 0x78.

Agora colocando em prática tudo o que foi dito, nosso keygen em C com código Assembly Inline ficará assim:
int gerarSerial(char* nome)
{
        int var3c;
        int var38;
        int serial = 0;
        asm
        {
                XOR EDX,EDX
                MOV DWORD PTR SS:[var38],EDX
                XOR ECX,ECX
                MOV DWORD PTR SS:[var3c],ECX
                JMP SHORT Jump1
        Loop1:
                MOV EAX,DWORD PTR SS:[nome]
                MOV EDX,DWORD PTR SS:[var3c]
                MOVSX ECX,BYTE PTR DS:[EAX+EDX]
                ADD DWORD PTR SS:[var38],ECX
                INC DWORD PTR SS:[var3c]
        Jump1:
                MOV EAX,DWORD PTR SS:[nome]
                MOV EDX,DWORD PTR SS:[var3c]
                CMP BYTE PTR DS:[EAX+EDX],0
                JNZ SHORT Loop1
                IMUL ECX,DWORD PTR SS:[var38],0x78
                MOV DWORD PTR SS:[var38],ECX
                IMUL EAX,DWORD PTR SS:[var38],0x78
                MOV DWORD PTR SS:[serial],EAX
        }
        return serial;
}

int main(int argc, char* argv[])
{
        printf("KeyGen CrackMe - crimesciberneticos.com\n\n");
        if(argc>1){
                printf("Nome: %s\n", argv[1]);
                printf("Serial: %u\n", gerarSerial(argv[1]));
        }
        else{
                printf("Use: keygen.exe <Nome>\n");
        }
        return 0;
}

Esse código foi compilado com êxito no Borland C++ Builder. Creio que também funcionará nos compiladores da Microsoft e em outros que utilizam o Assembly na sintaxe Intel.

Infelizmente o GCC e seus derivados como o Bloodshed Dev-C++ utilizam a sintaxe AT&T, então nesses compiladores o código não funcionará, seria necessário convertê-lo para a sintaxe AT&T. Um exemplo de como isso pode ser feito pode ser encontrado aqui: GCC-Inline-Assembly-HOWTO.

Como podemos ver o código Assembly é tratado dentro do C como se fosse uma função normal, inclusive pode-se combinar as variáveis das duas linguagens.

O keygen já compilado pode ser baixado aqui. Para utilizá-lo execute a linha de comando:

keygen.exe nomedesejado

Conclusão

Vimos nesse artigo mais uma técnica de cracking. Muitos mais do que aprender a crackear um programa esses conhecimentos podem ser úteis para protegê-lo. Ainda, o conhecimento de Assembly e engenharia reversa pode ser aproveitado em muitas áreas da segurança da informação.

Não são todos os softwares que possuem essa lógica de validação de Serial, mas muitos utilizam algo parecido.

O código original da função “compare” em C++ é esse:
bool TForm1::compare(){
   String edit = Edit1->Text;
   String edit2= Edit2->Text;
   char* n = edit.c_str();
   char* n2= edit2.c_str();

   int s = 0;
   for(int i=0;n[i]!=0;i++){
        s+=n[i];
   }
   s = s * 5 * 4 * 3 * 2;
   s = s * 5 * 4 * 3 * 2;

   char saida[100];
   sprintf(saida, "%d", s);
   if(strcmp(saida,n2)==0)
        return true;
   else
        return false;
}


Dúvidas, sugestões? Deixe um comentário. :)

Compartilhar usando :

DEIXE SEU COMENTARIO :

Comentarios - Mundo Hacker | Facebook-copyright(™ © ®)