Oficina
kiTo KGNmeAGAiN (C-1)
Seg 28 Ago 2006 10:20 |
- Detalhes
- Categoria: Pilotando o rato C-1
- Atualização: Quarta, 22 Abril 2009 11:09
- Autor: vovó Vicki
- Acessos: 11875
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
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
- 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.
- 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 )
- OllyDbg - meu debugger favorito para seguir a execução passo a passo.
- 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.
- 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
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
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 ).
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:
- F7 - Põe o valor 32h (50 decimal) na pilha (o painel da pilha é o inferior direito).
- F7 - Põe na pilha o endereço do buffer.
- F7 - Põe na pilha o ID de controle 3E9.
- F7 - Põe na pilha o manipulador da janela do executável.
- 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. - F7 - Põe na pilha o endereço 004032A2.
- F7 - Põe na pilha o valor de EAX (comprimento do nome).
- F7 - Põe na pilha o nome do usuário (teste).
- F8 - Chama uma subrotina em 004018A0. Na volta da subrotina a pilha está novamente alinhada e agora a linha
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 ...
- F7 - Põe o nome em Base64 (dGVzdGU=) na pilha.
- F7 - Pôe uma string vazia na pilha.
- F8 - Chama a subrotina de concatenação de strings do kernel32 que coloca "" + "dGVzdGU=" no endereço 004032D4.
- F7 - Põe NTE0MDA4MjhOVEUwTURBNE== na pilha (de onde será que veio este Base64?)
- F7 - Põe a string concatenada dGVzdGU= na pilha.
- F8 - Chama a subrotina de concatenação de strings que coloca "dGVzdGU=" + "NTE0MDA4MjhOVEUwTURBNE==" no endereço 004032D4.
- F7 - Põe a string concatenada dGVzdGU=NTE0MDA4MjhOVEUwTURBNE== na pilha.
- F8 - Chama a rotina do kernel32 que determina o comprimento de strings, o que retorna o valor 20h (32 decimal) no registrador EAX.
- F7 - Coloca o valor de EAX (20h) na posição de memória 4031A0.
- F8 - Chama uma rotina em 004017A8 :confused:
- F7 - Põe o valor da posição de memória 4031A0 (20h) na pilha.
- F7 - Põe a string NTE0MDA4MjhOVEUwTURBNE== na pilha.
- F8 - Chamada para 004017E8 :confused:
- 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 ...
- F7 - Zera o registrador EBX.
- 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).
- 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).
- F7 - Soma EBX com o valor que está na posição de memória indicada em EAX+8.
- 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).
- F7 - Põe o valor 6D6809C2 na pilha.
- F7 - Põe o formato %X na pilha.
- 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).
- 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.
- F7 - Adiciona 0Ch (12 decimal) ao ponteiro da pilha.
- 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
- F7 - Põe 32h na pilha.
- F7 - Põe um endereço para um buffer na pilha.
- F7 - Põe o manipulador da janela na pilha.
- 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".
- F7 - Põe "aaaaa" na pilha.
- F7 - Põe "6D68-9C2" na pilha.
- 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
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
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