03. Entendendo algorÃtmos
Lembrete: Engenharia reversa é completametne legal desde que não envolva softwares comerciais ou que não viole direitos autorais. Como nesse caso nós estaremos utilizando um aplicativo que foi criado exclusivamente para esse tipde de estudo, não tem problema.
Como sempre, baixe o nosso alvo:
-Download: keygenme1.zip
PARTE 1
Antes de abrir o Olly, rode o programa ( tem até musiquinha ), chute um nome qualquer ( de referência nunca chute nomes com menos de 5 caracteres e mais que 16 ) e chute uma key qualquer. A não ser que você seja largo, mas largo mesmo, deve ter aparecido a mensagem "Hello, Mr. Badboy!". Erramos a key.
Ok, abra o Olly e abra o nosso alvo nele. Para encontrar o local onde é feita a verificação, vamos dar uma olhada nas funções que o programa utiliza das APIs do windows. Aperte ALT E. Na lista que apareceu, clique com o botão direito na primeira ( Name = keygenme ) e depois View Names. No tutorial anterior eu falei sobre algumas funções interessantes que devemos dar maior atenção. São as marcadas em vermelho:
Todas elas vão acabar nos levando ao algorÃtmo, mas uma delas em especial nos leva mais rápidamente a ele: lstrcmpA. Essa função faz uma comparação entre 2 strings, e é amplamente utilizada para comparar uma key verdadeira com a key que voce digitou. Vamos setar um breakpoit em todos os locais onde ela é chamada. Clique com o botão direito sobre a linha que contém a lstrcmpA e marque 'Set breakpoint onevery reference'.
Pode fechar essas janelas ( tanto a das funções quanto à das dlls utilizadas ) e voltar para a tela padrão do código. Rode o programa pelo Olly, digite novamente um nome e uma key e aperte 'Check'. Pimba, paramos no nosso breakpoint bem na chamada da função. Certo, estamos parados bem na hora em que ele vai comparar 2 strings.
Agora repare nos 2 argumentos que essa função lstrcmpA recebe ( as 2 linhas que antecedem essa chamada ). EBX contém 123456 ( que foi o serial que eu chutei ) e EAX contém uma outra string, num formato BEM tÃpico de key. Após isso ele chama a função lstrcmpA que vai comparar os 2 valores ( 123456 com "Von-FF..." ) e se a comparação for verdadeira, ele pula para 00401338 ( JE abaixo do CALL ). Se você for até o endereço 00401338, você vai ver que é o local onde ele exibe a mensagem de que a key está correta.
Experimente anotar o valor de EBX ( String1 ), e testá-la nesse KeyGenMe ( utilize o mesmo nome de antes, pois a key é gerada a partir do nome ).
BINGO! Você acaba de descobrir a key correta para o seu nome. :)
PARTE 2
Certo, encontramos a key verdadeira para o seu nome, mas que tal seguirmos adiante e descobrir como essa key é gerada?
Delete os breapoints atuais ( aperte ALT B e delete todos ). Volte a janela dos módulos ( ALT E ), selecione "View Names" no primeiro item da lista. Vamos setar um breakpoint em todos os locais onde ele chama a função GetDlgItemTextA ( pega um valor digitado na caixa de texto ). Caso não lembre como fazer isso, releia o inÃcio deste tutorial.
Depois de setar o breakpoints, clique em "Play", digite um nome e uma key e novamente clique em 'Check'. Certos, paramos no local onde ele adquire o texto de uma das textbox. Note que tem 2 chamadas dessa função, uma logo após a outra. Obviamente em uma vai pegar o nome e a outra vai pegar a key. Aperte F9 para continuar a execução do programa e novamente nós paramos na chamada da função ( agora para pegar o valor do segundo textbox )
004010E9 6A 28 PUSH 28
004010EB 68 F8DC4000 PUSH keygenme.0040DCF8 ; Armazena o nome em 0040DCF8
004010F0 68 EE030000 PUSH 3EE
004010F5 FF75 08 PUSH DWORD PTR SS:\[EBP+8\]
004010F8 E8 B3020000 CALL <JMP.&user32.GetDlgItemTextA> ; Chama a função para pegar o nome
004010FD 6A 28 PUSH 28
004010FF 68 F8DE4000 PUSH keygenme.0040DEF8 ; Armazena a key em 0040DEF8
00401104 68 EF030000 PUSH 3EF
00401109 FF75 08 PUSH DWORD PTR SS:\[EBP+8\]
0040110C E8 9F020000 CALL <JMP.&user32.GetDlgItemTextA> ; Chama a função para pegar a key
00401111 E8 F2000000 CALL keygenme.00401208
Após ter pego os 2 textos e armazenados nos determinados locais, ele realiza um pulo para 00401208 ( no endereço 00401111 ). O instindo nos diz que essa chamada provavelmente gera uma key a partir do nome que é comparada posteriormente ( como vimos agora pouco ). Vamos "entrar" nessa função para analisar o que ela faz. Selecione a linha 00401111 e aperte ENTER.
Devido ao comprimento do código, não vou explicar exatamente o que cada linha faz, vuo ressaltar as mais importantes. Logo de cara você percebe que existe uma certa "simetria" digamos assim. Você consegue ver que o algorÃtimo fica divido em 3 sequencias semelhantes. Caso não tenha reparado, a key correta é composta por um "Bon-" seguido de 3 sequências numéricas. Podemos então supor que cada um dos blocos de código ( marcados em vermelho ), são responsáveis por gerar cada parte da key.
-Analisando a primeira sequência ( 00401208 )
Vamos então iniciar a análise pelo endereço 00401208. Ele primeiramente pega o tamanho do nome e armazena em EAX. Depois joga o valor de EAX para o endereço [40DC86]. Nas linhas seguintes ele compara o tamanho do nome ( que está em [40DC86] ) com 4 e depois com 32 ( 50 em decimal ). Se for maior que 50 ou menor que 4 ele faz um salto para indicar que o nome está ou muito grande ou muito pequeno. Supondo que você tenha digitado um nome que tem um tamanho 4 <= nome >= 50, podemos continuar.
Em 00401231 começa o cálculo da primeira sequencia da key. Ele zera EAX, EBX e ECX ( XOR X, X zera o valor X ). Depois move para EDI o nome digitado e para EDX o tamanho do nome. Na próxima linha começa um "Loop", ou seja, uma sequência que se repete até que determinada condição seja alcançada. Veja a sequência
00401242 0FB60439 MOVZX EAX,BYTE PTR DS:\[ECX+EDI\] ; Move para EAX o byte indicado por ECX ( inicialmente zero )
00401246 83E8 19 SUB EAX,19 ; Subtrai 19 ( 25 em decimal ) de EAX ( EAX = EAX - 25 )
00401249 2BD8 SUB EBX,EAX ; Subtrai EAX de EBX ( EBX = EBX - EAX )
0040124B 41 INC ECX ; Incrementa ECX ( ECX = ECX + 1 )
0040124C 3BCA CMP ECX,EDX ; Compara ECX com EDX ( que contém o tamanho do nome )
0040124E 75 F2 JNZ SHORT keygenme.00401242 ; Caso a comparação seja falsa ( ECX diferente de EDX ), volte para o inÃcio do loop ( 1242 )
Resumindo o algorÃtmo, ele pega o valor ASCII de cada caractere, tira 25 e depois subtrai esse valor encontrado de EBX, algo como:
For i = 1 to Tamanho do nome
....Sequencia1 = Sequencia1 - ASC(Caractere(i)) - 25
Next
Após o cálculo da primeira sequência, ele chama uma função que formata o texto e armazena ele em determinado endereço. Nesse caso, ele vai mover o resultado ( que está em EBX ) para a posição de memória [0040E0F8]
-Analisando a primeira sequência ( 00401263 )
Novamente ele começa zerando EAX, EDX e ECX.
00401269 03C3 ADD EAX,EBX ; EAX = EBX ( ou seja, EAX vai ter o valor da sequência do alg. anterior, que foi armazenada em EBX )
0040126B 0FAFC3 IMUL EAX,EBX ; EAX = EAX \* EBX ( com os 2 registradores tem o mesmo valor, é a mesma coisa que elevar EAX ao quadrado )
0040126E 03C8 ADD ECX,EAX ; ECX = EAX ( ECX passa a ter o valor da multiplicação anterior )
00401270 2BD3 SUB EDX,EBX ; EDX = EDX - EBX ( EDX adquire o valor negativo de EBX ) - instrução INÚTIL
00401272 33D0 XOR EDX,EAX ; EDX recebe o valor da operação binária XOR entre EDX e EAX - instrução INÚTIL
00401274 0FAFD8 IMUL EBX,EAX ; EBX = EBX \* EAX ( EBX é multiplicado por EAX ( que anteriormente foi multiplicado por EBX )
Analisando essa última instrução com cuidado, você descobre que a sequência 2 nada mais é do que o valor gerado na primeira sequência, elevado ao cubo. Assim:
Sequencia2 = Sequencia1 * Sequencia1 * Sequencia1
O valor da sequência 2 é armazenado da mesma forma que na primeira sequencia, mas no endereço de memória [0040E1F8]
-Analisando a primeira sequência ( 0040128A )
Como sempre, ela começa zerando EAX, EBX, EDX, ECX
00401292 B8 F8E04000 MOV EAX,keygenme.0040E0F8 ; Move para EAX o NÚMERO 0040E0F8 ( não se iluda nesta parte )
00401297 03D8 ADD EBX,EAX ; Move para EBX o mesmo número
00401299 33CB XOR ECX,EBX ; Realiza um XOR entre ECX e EBX
0040129B 0FAFCB IMUL ECX,EBX ; Multiplica EBX por ECX
0040129E 2BC8 SUB ECX,EAX ; Subtrai EAX de EBX
Esta última sequência tem algo em particular. Repare que nenhuma das "váriaveis" é variável ( WTF?! ). Em nenhum momento ele usa algo que possa variar de acordo com o nome digitado. O valor de EAX na instrução 00401292 vai ser sempre 0040E0F8, pois o endereço da instrução não vai mudar nunca. Fazendo todos os cálculos necessários ( use a calculadora do Windows ), você vai chegar no valor '41720F48', que é então armazenado na posição [0040E2F8].
Sequencia3 = 41720F48
Já deciframos todo o algoritmo. Como ele monta a string completa ( junta tudo ) não nos interessa, mas caso queria entender, ele faz isso a partir da linha 004012C5. Lembre-se também que ele adiciona um "Bon-" no inicio da key. Agora o que você precisa fazer é escolher uma liguagem para programar o seu gerador. Eu vou fazer em VisualBASIC porque todo mundo entende.
-Codigo do gerador, em VB
Private Sub Gerar\_Click()
....Dim seq1 As Double, seq2 As Double
....Dim tamanho As Integer, i As Integer
....'pega o tamanho do nome
....tamanho = Len(txtNome.Text)
....If (tamanho < 4) Or (tamanho > 50) Then Exit Sub
....'sequencia 1
....For i = 1 To tamanho
........seq1 = seq1 - (Asc((Mid(txtNome.Text, i, 1))) - 25)
....Next
....'sequencia 2
....seq2 = seq1 ^ 3
....txtSerial.Text = "Bon-" & Hex(seq1) & "-" & Hex(seq2) & "-41720F48"
End Sub
É isso ae. Este é o fim do tutorial!
F3rGO!