Oficina

kiTo KGNmeAGAiN (C-1)

Seg

28

Ago

2006


10:20

  • Imprimir
(7 votos, média 5.00 de 5) 


Para resolver este keygen-me é preciso muita lógica e algum conhecimento de criptografia.

O programa

O programa foi escrito por kiTo usando a linguagem Assembly para a plataforma Windows. Publicado no site crackmes.de em 06.08.06.

Objetivo do desafio

Criar um keygen que forneça um serial correto para cada nome de usuário.

Analisando o programa

kiTo KGNmeAGAiN
kiTo KGNmeAGAiN em execução

O programa apresenta duas caixas de texto (uma para o nome do usuário e outra para o serial), uma área de texto desabilitada com 24 caracteres (no meu caso são NTE0MDA4MjhOVEUwTURBNE==) e um botão identificado com [Check]. Deixando tudo como está e clicando no botão - nada acontece. Informando um nome de usuário (pra variar, usei "teste") e um serial qualquer (como sempre, usei "aaaaa") - também nada acontece. A primeira conclusão é de que não há mensagens de erro. Hmmm, sem strings para dar apoio a coisa fica um pouco mais complicada. A esperança é que o autor tenha colocado uma mensagem para informar que o serial está correto.

Planejando o ataque

Como não temos muitas informações de como funciona o programa ou de como ele foi planejado, só nos resta tentar encontrar strings que nos posicionem no código e nos forneçam alguns endereços para breakpoints. Através destes pontos de parada poderemos encontrar a rotina de cálculo do serial. Como foi dito que o programa foi escrito em Assembly, é de supor que o código seja do tipo "sequinho" e limpo.

As ferramentas

  1. PEiD - a ferramenta que dificilmente eu dispenso. Ainda mais em casos como este, em que as informações são escassas. Se ainda não tiver esta ferramenta, vá para dowloads > informatica > utilitarios.
  2. Plugin Krypto ANALyzer para o PEiD - também está em downloads > informática > utilitários (só descobri a necessidade de usar este plugin depois que resolvi fuçar o executável com alguns deles smile )
  3. OllyDbg - meu debugger favorito para seguir a execução passo a passo.
  4. MD5-Checker (também depois que descobri que havia criptografia na jogada). Procure em downloads > criptologia > criptografia. Existem outros programas, mas este é o mais simples.
  5. x3chun's Base64 Enconde & Decode - um dos raros executáveis para Windows para Base64. Uma ferramenta muito prática (novamente depois que descobri quais algoritmos criptográficos estavam sendo aplicados). Disponível em downloads > criptologia > criptografia.

PILOTANDO O RATO

A primeira providência é explorar um pouco mais o executável. Rode o PEiD, carregue o kiToKGNmeAGAiN.exe e vamos lá:

  • Logo de cara, a área de texto que deve indicar a linguagem de programação utilizada diz Nothing found *. Ué!? Será que não é Assembly ou será que é um Assembly que não foi reconhecido? Então vamos checar o trabalho do compilador dando uma olhada nas seções.
  • Clique no botão [>] à direita de EP Section - devem aparecer as seções .text, .rdata, .data e .rsrc. Ao que tudo indica, a compilação é padrão...
  • Clique no botão [->] inferior direito, escolha Plugins e Krypto ANALyzer. Tchan, tchan, tchan, tchan! Dê só uma olhada nisto!
BASE64 table :: 00001260 :: 00403060
	Referenced at 004018E6
	Referenced at 004018EC
	Referenced at 004018F8
	Referenced at 004018FE
MD5 :: 0000060D :: 0040120D
	The reference is above.

Viiixi, tem criptografia na jogada! Antes de continuar é bom ter certeza saber alguma coisa sobre Base64 e o hash MD5. Se quiser dar uma recordada nos assuntos ou saber do que se trata, procure pelos artigos Base64 e MD5 e alegre-se - ambos são interativos smile

Mas tem mais. Olhando o código com o desassemblador do PEid (botão [>] ao lado de First Bytes), o código é meio esquisito. Além disso, clicando o botão [Strings], a grande maioria delas parece entulho. É... parece que vamos ter que abrir mão de strings e apelar para chamadas à API do Windows sad


Debugando com o OllyDbg

Rode o OllyDbg e carregue o executável. Digite F9 para executar o programa, forneça o nome do usuário, o serial e clique no botão [Check]. Nada acontece, mas o OllyDbg não trava. Bom, muito bom! Sinal de que o programa não possui código anti-debugger.

Agora vamos procurar um ponto de apoio para colocar um breakpoint. Para isto, clique com o botão direito do mouse no painel de código do OllyDbg e dê mais uma conferida nas strings escolhendo os itens de menu Search for e All referenced text strings. A coisa parece um pouco melhor do que quando procuramos strings com o PEid. O OllyDbg mostra o seguinte:

Text strings referenced in kiToKGNm:.text
Address    Disassembly                               Text string
00401040   PUSH kiToKGNm.0040323E                    ASCII "NTE0MDA4MjhOVEUwTURBNE=="
00401072   PUSH kiToKGNm.0040304B                    ASCII "kiTo - KgnMe AGAiN!"
004010B8   PUSH kiToKGNm.00403000                    ASCII "%X"
004010BD   PUSH kiToKGNm.004031A8                    ASCII "51400828"
004010CA   PUSH kiToKGNm.004031DA                    ASCII "NTE0MDA4Mjg="
004010D1   PUSH kiToKGNm.004031A8                    ASCII "51400828"
004010DB   PUSH kiToKGNm.004031A8                    ASCII "51400828"
004010E0   PUSH kiToKGNm.0040320C                    ASCII "51400828NTE0MDA4Mjg="
004010EA   PUSH kiToKGNm.004031DA                    ASCII "NTE0MDA4Mjg="
004010EF   PUSH kiToKGNm.0040320C                    ASCII "51400828NTE0MDA4Mjg="
004010F9   PUSH kiToKGNm.0040323E                    ASCII "NTE0MDA4MjhOVEUwTURBNE=="
00401100   PUSH kiToKGNm.0040320C                    ASCII "51400828NTE0MDA4Mjg="
00401117   PUSH kiToKGNm.00403270                    ASCII "teste"
0040112F   PUSH kiToKGNm.00403270                    ASCII "teste"
00401148   PUSH kiToKGNm.0040323E                    ASCII "NTE0MDA4MjhOVEUwTURBNE=="
0040118E   PUSH kiToKGNm.00403000                    ASCII "%X"
004011D6   MOV EDI,kiToKGNm.004033B0                 (Initial CPU selection)

Dê só uma olhada na porção de strings que terminam com = ou ==. Lembra alguma coisa? Tudo indica que seja material codificado com Base64. Só por curiosidade, dispare seu x3chun's Base64 Encoder & Decoder e decodifique algumas delas. A NTE0MDA4MjhOVEUwTURBNE==, por exemplo, é decodificada em 51400828NTE0MDA4@ e a 51400828NTE0MDA4Mjg= em ç^4Óͼ51400828. Não ajudou muito, mas dá para desconfiar que alguma coisa serviu de base para estes cálculos. De alguma forma este código deve ter importância porque um deles, NTE0MDA4MjhOVEUwTURBNE==, é o que aparece na área de texto do programa (o seu, provavelmente, é diferente smile ).

Passo seguinte será procurar um gancho em alguma função da API. Como existem duas caixas de texto nas quais informamos nome e serial, para pegar estes valores geralmente se usa a função GetDlgItemTextA que fica na DLL user32 do sistema Windows. Para ver quais chamadas o executável faz, clique com o botão direito do mouse na área de código do OllyDbg, escolha Search for e All intermodular calls. Na janela apresentada encontramos vários tipos de chamada e, como esperado, duas chamadas à função GetDlgItemTextA. Dê um duplo clique sobre a primeira para deslocar o código para o endereço correspondente. Role o código para baixo até encontrar o início da rotina e coloque um breakpoint no início do controle de comprimento do nome do usuário (pode ter no máximo 50 caracteres):

0040110B  /$ 55             PUSH EBP
0040110C  |. 8BEC           MOV EBP,ESP
0040110E  |. C605 D4324000 >MOV BYTE PTR DS:[4032D4],0
00401115  |. 6A 32          PUSH 32                        ; /Count = 32 (50.)
00401117  |. 68 70324000    PUSH kiToKGNm.00403270         ; |Buffer = kiToKGNm.00403270
0040111C  |. 68 E9030000    PUSH 3E9                       ; |ControlID = 3E9 (1001.)
00401121  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]      ; |hWnd
00401124  |. E8 E3080000    CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA

Use a tecla F9 para rodar o programa e bingo! A execução pára no breakpoint. Agora é só ir no passo a passo e tentar entender o que as instruções estão fazendo.


Debugando passo a passo

A rotina que nos interessa é mostrada a seguir:

00401115  |. 6A 32          PUSH 32                        ; /Count = 32 (50.)
00401117  |. 68 70324000    PUSH kiToKGNm.00403270         ; |Buffer = kiToKGNm.00403270
0040111C  |. 68 E9030000    PUSH 3E9                       ; |ControlID = 3E9 (1001.)
00401121  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]      ; |hWnd
00401124  |. E8 E3080000    CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401129  |. 68 A2324000    PUSH kiToKGNm.004032A2
0040112E  |. 50             PUSH EAX
0040112F  |. 68 70324000    PUSH kiToKGNm.00403270
00401134  |. E8 67070000    CALL kiToKGNm.004018A0
00401139  |. 68 A2324000    PUSH kiToKGNm.004032A2         ; /StringToAdd = ""
0040113E  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; |ConcatString = ""
00401143  |. E8 A0080000    CALL <JMP.&kernel32.lstrcatA>  ; \lstrcatA
00401148  |. 68 3E324000    PUSH kiToKGNm.0040323E         ; /StringToAdd = "NTE0MDA4MjhOVEUwTURBNE=="
0040114D  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; |ConcatString = ""
00401152  |. E8 91080000    CALL <JMP.&kernel32.lstrcatA>  ; \lstrcatA
00401157  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; /String = ""
0040115C  |. E8 93080000    CALL <JMP.&kernel32.lstrlenA>  ; \lstrlenA
00401161  |. A3 A0314000    MOV DWORD PTR DS:[4031A0],EAX
00401166  |. E8 3D060000    CALL kiToKGNm.004017A8
0040116B  |. FF35 A0314000  PUSH DWORD PTR DS:[4031A0]
00401171  |. 68 D4324000    PUSH kiToKGNm.004032D4
00401176  |. E8 6D060000    CALL kiToKGNm.004017E8
0040117B  |. E8 C8060000    CALL kiToKGNm.00401848
00401180  |. 33DB           XOR EBX,EBX
00401182  |. 0318           ADD EBX,DWORD PTR DS:[EAX]
00401184  |. 0358 04        ADD EBX,DWORD PTR DS:[EAX+4]
00401187  |. 0358 08        ADD EBX,DWORD PTR DS:[EAX+8]
0040118A  |. 0358 0C        ADD EBX,DWORD PTR DS:[EAX+C]
0040118D  |. 53             PUSH EBX                       ; /<%X>
0040118E  |. 68 00304000    PUSH kiToKGNm.00403000         ; |Format = "%X"
00401193  |. 68 38334000    PUSH kiToKGNm.00403338         ; |s = kiToKGNm.00403338
00401198  |. E8 5D080000    CALL <JMP.&user32.wsprintfA>  ; \wsprintfA
0040119D  |. 83C4 0C        ADD ESP,0C
004011A0  |. C605 3C334000 >MOV BYTE PTR DS:[40333C],2D
004011A7  |. 6A 32          PUSH 32                        ; /Count = 32 (50.)
004011A9  |. 68 6A334000    PUSH kiToKGNm.0040336A         ; |Buffer = kiToKGNm.0040336A
004011AE  |. 68 EA030000    PUSH 3EA                       ; |ControlID = 3EA (1002.)
004011B3  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]      ; |hWnd
004011B6  |. E8 51080000    CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
004011BB  |. 68 6A334000    PUSH kiToKGNm.0040336A         ; /String2 = ""
004011C0  |. 68 38334000    PUSH kiToKGNm.00403338         ; |String1 = ""
004011C5  |. E8 24080000    CALL <JMP.&kernel32.lstrcmpA> ; \lstrcmpA
004011CA  |. C9             LEAVE
004011CB  \. C2 0400        RETN 4

Acompanhe o passo a passo:

  1. F7 - Põe o valor 32h (50 decimal) na pilha (o painel da pilha é o inferior direito).
  2. F7 - Põe na pilha o endereço do buffer.
  3. F7 - Põe na pilha o ID de controle 3E9.
  4. F7 - Põe na pilha o manipulador da janela do executável.
  5. F8 - Tudo o que foi feito nos passos anteriores foi para preparar uma chamada à função GetDlgItemTextA. Use F8 para executar a função sem sair da linha de execução na qual nos encontramos. Observe que logo depois de retornar, a linha 0040112F mostra uma string com o nome do usuário,
    0040112F  |. 68 70324000    PUSH kiToKGNm.00403270
       mostra
    0040112F  |. 68 70324000    PUSH kiToKGNm.00403270         ;  ASCII "teste"
      
    que a pilha foi realinhada com a retirada dos valores previamente inseridos e que o registrador EAX mostra o comprimento do nome.
  6. F7 - Põe na pilha o endereço 004032A2.
  7. F7 - Põe na pilha o valor de EAX (comprimento do nome).
  8. F7 - Põe na pilha o nome do usuário (teste).
  9. F8 - Chama uma subrotina em 004018A0. Na volta da subrotina a pilha está novamente alinhada e agora a linha
  10. 00401139  |. 68 A2324000    PUSH kiToKGNm.004032A2                   ; /StringToAdd = ""
       mostra
    00401139  |. 68 A2324000    PUSH kiToKGNm.004032A2                   ; /StringToAdd = "dGVzdGU="
      

Pausa para meditação: depois desta chamada, a string que deve ser adicionada é dGVzdGU=. Novamente está com jeito de codificação Base64 e meu palpite é que o nome do usuário foi codificado. Mais uma vez, rode o Base64 Enconder & Decoder e confira. UAU! É isto mesmo! Base64 de "teste" é "dGVzdGU=". Neste caso, a rotina que faz a codificação deve estar localizada no endereço 004018A0. Vamos guardar este endereço e explorá-lo mais tarde.


Vou repetir aqui o trecho que será analisado passo a passo para que vocês acompanhem o que está acontecendo comparando os fatos com as instruções em Assembly. Além disso, neste ponto de execução, algumas strings foram atualizadas:

...
00401139  |. 68 A2324000    PUSH kiToKGNm.004032A2         ; /StringToAdd = "dGVzdGU="
0040113E  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; |ConcatString = ""
00401143  |. E8 A0080000    CALL <JMP.&kernel32.lstrcatA>  ; \lstrcatA
00401148  |. 68 3E324000    PUSH kiToKGNm.0040323E         ; /StringToAdd = "NTE0MDA4MjhOVEUwTURBNE=="
0040114D  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; |ConcatString = ""
00401152  |. E8 91080000    CALL <JMP.&kernel32.lstrcatA>  ; \lstrcatA
00401157  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; /String = ""
0040115C  |. E8 93080000    CALL <JMP.&kernel32.lstrlenA>  ; \lstrlenA
00401161  |. A3 A0314000    MOV DWORD PTR DS:[4031A0],EAX
00401166  |. E8 3D060000    CALL kiToKGNm.004017A8
0040116B  |. FF35 A0314000  PUSH DWORD PTR DS:[4031A0]
00401171  |. 68 D4324000    PUSH kiToKGNm.004032D4         ; ASCII "NTE0MDA4MjhOVEUwTURBNE=="
00401176  |. E8 6D060000    CALL kiToKGNm.004017E8
0040117B  |. E8 C8060000    CALL kiToKGNm.00401848
...
  1. F7 - Põe o nome em Base64 (dGVzdGU=) na pilha.
  2. F7 - Pôe uma string vazia na pilha.
  3. F8 - Chama a subrotina de concatenação de strings do kernel32 que coloca "" + "dGVzdGU=" no endereço 004032D4.
  4. F7 - Põe NTE0MDA4MjhOVEUwTURBNE== na pilha (de onde será que veio este Base64?)
  5. F7 - Põe a string concatenada dGVzdGU= na pilha.
  6. F8 - Chama a subrotina de concatenação de strings que coloca "dGVzdGU=" + "NTE0MDA4MjhOVEUwTURBNE==" no endereço 004032D4.
  7. F7 - Põe a string concatenada dGVzdGU=NTE0MDA4MjhOVEUwTURBNE== na pilha.
  8. F8 - Chama a rotina do kernel32 que determina o comprimento de strings, o que retorna o valor 20h (32 decimal) no registrador EAX.
  9. F7 - Coloca o valor de EAX (20h) na posição de memória 4031A0.
  10. F8 - Chama uma rotina em 004017A8 :confused:
  11. F7 - Põe o valor da posição de memória 4031A0 (20h) na pilha.
  12. F7 - Põe a string NTE0MDA4MjhOVEUwTURBNE== na pilha.
  13. F8 - Chamada para 004017E8 :confused:
  14. F8 - Chamada para 00401848 :confused:

:confused: Quando começam a surgir muitas dúvidas, pode-se fazer duas coisas: simplesmente continuar para ver no que vai dar ou explorar melhor onde estamos enroscando. Optei por simplesmente continuar para ver se é possível ciscar o serial correto para o usuário "teste". Depois, podemos voltar para esclarecer as dúvidas.

...
00401180  |. 33DB           XOR EBX,EBX
00401182  |. 0318           ADD EBX,DWORD PTR DS:[EAX]
00401184  |. 0358 04        ADD EBX,DWORD PTR DS:[EAX+4]
00401187  |. 0358 08        ADD EBX,DWORD PTR DS:[EAX+8]
0040118A  |. 0358 0C        ADD EBX,DWORD PTR DS:[EAX+C]
0040118D  |. 53             PUSH EBX                       ; /<%X>
0040118E  |. 68 00304000    PUSH kiToKGNm.00403000         ; |Format = "%X"
00401193  |. 68 38334000    PUSH kiToKGNm.00403338         ; |s = kiToKGNm.00403338
00401198  |. E8 5D080000    CALL <JMP.&user32.wsprintfA>  ; \wsprintfA
0040119D  |. 83C4 0C        ADD ESP,0C
004011A0  |. C605 3C334000 >MOV BYTE PTR DS:[40333C],2D
...
  1. F7 - Zera o registrador EBX.
  2. F7 - Soma EBX com valor que está na posição de memória indicada em EAX. O endereço é 004033F0 (marque este endereço, pois é daí que o programa está buscando os dados).
  3. F7 - Soma EBX com o valor que está na posição de memória indicada em EAX+4. Epa, parece que está sendo feita uma soma de valores de 32 bits (dwords).
  4. F7 - Soma EBX com o valor que está na posição de memória indicada em EAX+8.
  5. F7 - Soma EBX com o valor que está na posição de memória indicada em EAX+C. Para mim, o resultado de todas estas somas foi EBX = 6D6809C2 (o seu deve ser outro).
  6. F7 - Põe o valor 6D6809C2 na pilha.

  7. F7 - Põe o formato %X na pilha.

  8. F7 - Põe o endereço 403338 na pilha (localize este endereço no painel da memória, o inferior esquerdo, e observe o resultado depois do próximo passo).

  9. F8 - Chama a função da DLL user32 que transforma valores hexadecimais em strings. A string 6D6809C2 é colocada no endereço de memória que está sendo monitorado.
  10. F7 - Adiciona 0Ch (12 decimal) ao ponteiro da pilha.
  11. F7 - Coloca o valor ASCII 2D, que corresponde ao caracter "-", na quinta posição da string "6D6809C2" transformando-a em 6D68-9C2. Será este o serial procurado?

O serial

Só falta mais um tiquinho e chegaremos lá. Já temos uma string que cheira a serial, mas precisamos encontrar a rotina que compara o serial calculado com o fornecido. Veja a continuação do código e observe que a linha 4011C0 já mostra String1 = "6D68-9C2":

...
004011A7  |. 6A 32          PUSH 32                        ; /Count = 32 (50.)
004011A9  |. 68 6A334000    PUSH kiToKGNm.0040336A         ; |Buffer = kiToKGNm.0040336A
004011AE  |. 68 EA030000    PUSH 3EA                       ; |ControlID = 3EA (1002.)
004011B3  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]      ; |hWnd
004011B6  |. E8 51080000    CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
004011BB  |. 68 6A334000    PUSH kiToKGNm.0040336A         ; /String2 = ""
004011C0  |. 68 38334000    PUSH kiToKGNm.00403338         ; |String1 = "6D68-9C2"
004011C5  |. E8 24080000    CALL <JMP.&kernel32.lstrcmpA> ; \lstrcmpA
004011CA  |. C9             LEAVE
004011CB  \. C2 0400        RETN 4
  1. F7 - Põe 32h na pilha.
  2. F7 - Põe um endereço para um buffer na pilha.
  3. F7 - Põe o manipulador da janela na pilha.
  4. F8 - Chama a função GetDlgItemTextA para pegar o texto da segunda caixa de texto (onde colocamos o serial bichado "aaaaa"). Assim que retornamos desta função, a linha com String2 = "" é transformada em String2 = "aaaaa".
  5. F7 - Põe "aaaaa" na pilha.
  6. F7 - Põe "6D68-9C2" na pilha.
  7. F8 - Com as duas strings como parâmetros, fazemos a chamada para a função que compara strings do kernel32.

Sabe o que acontece? O óbvio: as strings não são idênticas e já sabemos que, se o serial fornecido não estiver correto, o programa simplesmente não reage. Para provar definitivamente que o serial calculado é o certo, faça o executável rodar novamente com F9, troque o serial "aaaaa" pelo que acabamos de descobrir e clique no botão [Check]. A execução deve parar no breakpoint (ou breakpoints, se você colocou mais alguns). Use F9 para fazer o programa deslanchar e, tá em casa. Recebemos uma mensagem de... sei lá o que... escrita em sueco!

O keygen

Podemos usar o próprio programa como keygen, mesmo sem entender muito bem como o serial foi calculado. Eu sei que é covardia, mas nada impede de fazermos duas pequenas alterações no executável original para transformá-lo numa ferramenta geradora de seriais dele mesmo. Veja como é fácil:

  • Em primeiro lugar, temos uma rotina que cria uma caixa de mensagem quando o serial fornecido for o certo - nada impede que a usemos para informar o serial calculado.
  • Já descobrimos o endereço onde o serial calculado é armazenado na memória (403338).

Pois bem, reinicie o programa com Ctrl+F2 e a execução vai parar novamente na rotina de cálculo do serial. Role o código um pouco para cima e ponha um breakpoint na última linha desta rotina, ou seja, em

...
004011C5  |. E8 24080000    CALL <JMP.&kernel32.lstrcmpA> ; \lstrcmpA
004011CA  |. C9             LEAVE
004011CB  \. C2 0400        RETN 4

Digite F9 e depois F7 para voltar para o ponto de chamada. Você deve encontrar o seguinte:

...
0040106C  |. 0BC0           OR EAX,EAX
0040106E  |. 75 16          JNZ SHORT kiToKGNm.00401086
00401070  |. 6A 40          PUSH 40                        ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
00401072  |. 68 4B304000    PUSH kiToKGNm.0040304B         ; |Title = "kiTo - KgnMe AGAiN!"
00401077  |. 68 03304000    PUSH kiToKGNm.00403003         ; |Text = "Bra, om du inte har gjort en keygen
                                                              än, så är det den ända lösningen ;)"
0040107C  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]      ; |hOwner
0040107F  |. E8 8E090000    CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA

Agora é partir para o abraço. Não queremos que o salto em 0040106E seja feito para que a caixa de mensagem seja apresentada. Podemos fazer duas coisas: transformar JNZ em JE ou simplesmente trocar os dois opcodes (75 16) por NOP. Se você optar pela primeira solução, selecione a linha do salto, aperte a tecla de espaço para obter o editor, troque JNZ por JE e clique no botão [Assemble]; se optar pela segunda, selecione a linha do salto, clique com o botão direito do mouse sobre ela e escolha Binary e Fill with NOPs. Pronto, o salto não será mais realizado. Agora vamos à caixa de mensagem.

O código logo a seguir coloca os parâmetros da função MessageBoxA na pilha para depois chamá-la. Agora é só selecionar a linha com a mensagem de texto em sueco, apertar a tecla de espaço para abrir a janela de edição, trocar o endereço 00403003 pelo endereço do serial calculado (00403338), clicar no btão [Assemble] e fechar a caixa de edição. Imediatamente o serial aparece.

...
0040106C  |. 0BC0           OR EAX,EAX
0040106E     74 16          JE SHORT kiToKGNm.00401086
00401070  |. 6A 40          PUSH 40                        ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
00401072  |. 68 4B304000    PUSH kiToKGNm.0040304B         ; |Title = "kiTo - KgnMe AGAiN!"
00401077     68 38334000    PUSH kiToKGNm.00403338         ;  ASCII "9A3F-71A"
0040107C  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]      ; |hOwner
0040107F  |. E8 8E090000    CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA
...

Tecle F9 e divirta-se com o resultado smile


Tirando as dúvidas

Tudo muito bem, tudo muito bom, só que ainda ficaram algumas dúvidas que precisam ser esclarecidas. De onde vem aquela stringona em Base64 que é concatenada com o nome do usuário também em Base64? Outra coisa, cadê o MD5 que o PEiD indicou como encriptação? Se você quiser escrever seu próprio keygen, vai precisar dos algoritmos. Se você escolher a linguagem Assembly, pode até reaproveitar o código do kiTo. Se escolher outra linguagem de programação, os textos referentes ao assunto (indicados no início deste artigo) podem ajudar. Então, vamos lá.

A stringona Base64

Vimos que a string em Base64 do nome do usuário foi concatenada com uma outra. A chamada para criar a segunda deve ter ocorrido em algum ponto anterior ao que estávamos. Reinicie o programa com Ctrl+F2 e vá rastreando com F7 logo a partir da primeira instrução:

/$ E8 A6000000    CALL kiToKGNm.004010AB

Esta chamada nos leva ao endereço 004010AB:

004010AB  /$ E8 32090000    CALL <JMP.&kernel32.GetVersion>
004010B0  |. BB 08000000    MOV EBX,8
004010B5  |. F7E3           MUL EBX
004010B7  |. 50             PUSH EAX                       ; /<%X>
004010B8  |. 68 00304000    PUSH kiToKGNm.00403000         ; |Format = "%X"
004010BD  |. 68 A8314000    PUSH kiToKGNm.004031A8         ; |s = kiToKGNm.004031A8
004010C2  |. E8 33090000    CALL <JMP.&user32.wsprintfA>   ; \wsprintfA
004010C7  |. 83C4 0C        ADD ESP,0C
004010CA  |. 68 DA314000    PUSH kiToKGNm.004031DA
004010CF  |. 6A 08          PUSH 8
004010D1  |. 68 A8314000    PUSH kiToKGNm.004031A8
004010D6  |. E8 C5070000    CALL kiToKGNm.004018A0
004010DB  |. 68 A8314000    PUSH kiToKGNm.004031A8         ; /StringToAdd = ""
004010E0  |. 68 0C324000    PUSH kiToKGNm.0040320C         ; |ConcatString = ""
004010E5  |. E8 FE080000    CALL <JMP.&kernel32.lstrcatA>  ; \lstrcatA
004010EA  |. 68 DA314000    PUSH kiToKGNm.004031DA         ; /StringToAdd = ""
004010EF  |. 68 0C324000    PUSH kiToKGNm.0040320C         ; |ConcatString = ""
004010F4  |. E8 EF080000    CALL <JMP.&kernel32.lstrcatA>  ; \lstrcatA
004010F9  |. 68 3E324000    PUSH kiToKGNm.0040323E
004010FE  |. 6A 10          PUSH 10
00401100  |. 68 0C324000    PUSH kiToKGNm.0040320C
00401105  |. E8 96070000    CALL kiToKGNm.004018A0
0040110A  \. C3             RETN

Observe que a primeira instrução desta subrotina chama a função GetVersion. Esta função retorna o valor da versão do Windows no registrador EAX (no meu caso, EAX = 0A280105). Logo em seguida, o valor de EAX é multiplicado por 8 (EAX = 51400828). Logo em seguida este valor é transformado em string através da chamada à função wsprintfA e, juntamente com o valor 8, a string é colocada na pilha antes da chamada ao endereço 004018A0 - onde fica a rotina onde é feita sua conversão para Base64 (meu resultado foi NTE0MDA4Mjg=).

Depois da conversão, a string é concatenada com uma string vazia ("" + "51400828" = "51400828") e o resultado é concatenado com a string Base64 ("51400828" + "NTE0MDA4Mjg=" = "51400828NTE0MDA4Mjg="). Assim preparada, além desta string, é colocado na pilha o valor 10h (16 decimal) e a rotina de codificação Base64 é chamada novamente. Apenas 16 caracteres serão usados na codificação e o resultado obtido é NTEMDA4MjhOVEUwTURBNE==. Taí a stringona que procurávamos e agora sabemos que ela depende da versão do Windows do freguês smile


O hash MD5

:confused: Em alguns passos da execução surgiram algumas dúvidas. Chegou a hora de descobrir do que se trata e dar créditos ao PEiD que nos informou a existência de MD5. Repetindo o trecho do código

...
00401166  |. E8 3D060000    CALL kiToKGNm.004017A8        ; inicializa dwords para o MD5
0040116B  |. FF35 A0314000  PUSH DWORD PTR DS:[4031A0]
00401171  |. 68 D4324000    PUSH kiToKGNm.004032D4
00401176  |. E8 6D060000    CALL kiToKGNm.004017E8
0040117B  |. E8 C8060000    CALL kiToKGNm.00401848        ; hash MD5 da Base64 do nome + stringona
...

A primeira chamada inicializa dwords para o MD5. Depois é criado o hash MD5 do nome codificado + stringona através da chamada à subrotina localizada em 00401848. Lembra do endereço 004033F0 que eu pedi para anotar? Chegou a hora de usá-lo. Procure este endereço no painel inferior esquerdo do OllyDbg. Um pouco mais acima você pode ver a string que servirá de base para criar o hash.

Reinicie o programa com Ctrl+F2, clique no botão [Register] para parar no breakpoint. Pilote as instruções com F7 / F8 até chegar na chamada para o cálculo do hash. Entre na rotina co cálculo, faça o passo a passo com F7, sempre observando o painel da memória do OllyDbg até que os endereços de 004033F0 a 004033FF estejam preenchidos com valores. Na minha máquina, o resultado foi o seguinte:

004033B0  64 47 56 7A 64 47 55 3D  dGVzdGU=
004033B8  4E 54 45 30 4D 44 41 34  NTE0MDA4
004033C0  4D 6A 68 4F 56 45 55 77  MjhOVEUw
004033C8  54 55 52 42 4E 45 3D 3D  TURBNE==
004033D0  80 00 00 00 00 00 00 00  €.......
004033D8  00 00 00 00 00 00 00 00  ........
004033E0  00 00 00 00 00 00 00 00  ........
004033E8  00 01 00 00 00 00 00 00  .......
004033F0  46 3D 7A C4 11 DC 27 5B  F=zÄÜ'[
004033F8  21 02 07 2B 4A EE BE 22  !+Jî¾"
00403400  20 00 00 00 20 00 00 00   ... ...

Agora rode o MD5-Checker (ou qualquer outro aplicativo que calcule MD5), coloque na área de texto identificada por String - ASCII o mesmo texto recebido pela rotina analisada (de acordo com este exemplo, dGVzdGU=NTE0MDA4MjhOVEUwTURBNE==), clique no botão [Generate] e anote o resultado obtido (no meu caso foi 463D7AC411DC275B2102072B4AEEBE22). Compare este resultado com o mostrado na memória pelo OllyDbg. É isso aí!

Continuando a linha de execução depois da chamada à rotina do MD5 temos:

...
00401180  |. 33DB           XOR EBX,EBX
00401182  |. 0318           ADD EBX,DWORD PTR DS:[EAX]     ; o endereço é 004033F0
00401184  |. 0358 04        ADD EBX,DWORD PTR DS:[EAX+4]   ; o endereço é 004033F4
00401187  |. 0358 08        ADD EBX,DWORD PTR DS:[EAX+8]   ; o endereço é 004033F8
0040118A  |. 0358 0C        ADD EBX,DWORD PTR DS:[EAX+C]   ; o endereço é 004033FF
0040118D  |. 53             PUSH EBX                       ; /<%X>
0040118E  |. 68 00304000    PUSH kiToKGNm.00403000         ; |Format = "%X"
00401193  |. 68 38334000    PUSH kiToKGNm.00403338         ; |s = kiToKGNm.00403338
00401198  |. E8 5D080000    CALL <JMP.&user32.wsprintfA>  ; \wsprintfA
0040119D  |. 83C4 0C        ADD ESP,0C
004011A0  |. C605 3C334000 >MOV BYTE PTR DS:[40333C],2D
...

O que ocorre é que o registrador EBX é zerado, soma-se a ele o valor do dword em 004033F0, ou seja, EBX = C4 7A 3D 46 (não se esqueça de que é um valor hexadecimal). Observe que os bytes são colocados no registrador na ordem inversa em que aparecem na memória.

Na instrução seguinte o próximo dword é adicionado, ou seja EBX = C47A3D46 + 5B27DC11 = 01 1FA21957. Note que o número de bytes ficou muito grande para caber no registrador, por isso a sobra 01 não entra e EBX mostra 1FA21957.

A próxima soma é EBX = 1FA21957 + 2B070221 = 4AA91B78 e a última é EBX = 4AA91B78 + 22BEEE4A = 6D6809C2.

Este resultado é transformado numa string e o quinto caracter é substituído por "-" resultando em 6D68-9C2, a senha para o nome "teste" no meu sistema.

Finalmentes

É isto aí, pessoal. Espero que este artigo tenha mostrado como usar a engenharia reversa para recuperar a lógica e os dados de um executável cujo código fonte desconhecemos ou perdemos. A todos um grande abraço e até a próxima

vovo

mfx broker как вывести средстваКупить веерную кистьалександр лобановский супермаркет классcpm системаdoctor2.ru новости харьков кернесfbconsult отзывы