No blog anterior aqui , nós engenharia reversa um binário simples contendo senha de texto no Linux com a ajuda de GNU Debugger
(GDB). Neste blog, no entanto, usaremos o mesmo código fonte do binário, mas o compilar e depurar no Windows. As ferramentas de
engenharia reversa no Windows são muito diferentes das do Linux, mas no nível da montagem, seria um pouco o mesmo. A única
diferença que você encontraria seria as chamadas no nível do kernel e as DLLs que seriam do Windows e não as bibliotecas do Linux. Esta
é, no entanto, uma nova publicação do meu próprio blog a partir daqui .
Neste post, usarei x64dbg, pois não consegui encontrar uma versão do depurador de imunidade x64 ou do Olly Debugger para fazer
engenharia reversa no binário. No entanto, abaixo estão alternativas, juntamente com os links de download que você pode escolher. Se
você conseguir encontrar outros depuradores x64 para Windows, adicione-os no comentário e eu os mencionarei aqui .:
1. Depurador de imunidade
2. Olly Debugger
3. IDA Pro
4. WinDBG
5. X64dbg
O Immunity Debugger é uma ferramenta incrível se você estiver depurando binários x86. No entanto, como estamos focando apenas no
x64, teremos que usar o x64dbg, que suporta desmontagem de x86 e x64.
Depois de baixar o depurador necessário, você pode compilar o código fonte que é carregado no meu repositório Git aqui . Você pode
compilar o binário no Windows com o comando abaixo:
Certifique-se de usar uma versão de 64 bits do compilador g ++, caso contrário ele será compilado, mas não funcionará. Você também
pode baixar o binário do meu repositório mencionado acima. Prefiro usar o compilador Mingw-x64, mas alguns também usam o clang
x64. Tudo se resume à preferência de qual você está familiarizado.
Desmontagem
Depois de compilar o binário, vamos carregá-lo em x64dbg. Lembre-se de que nosso binário aceita um argumento que é nossa
senha. Portanto, diferente do GDB, onde podemos fornecer o argumento dentro do GDB; no Windows, teremos que fornecê-lo durante o
carregamento do binário através da própria linha de comando.
Para carregar o binário no x64dbg, abaixo está a linha de comando que você pode usar:
Depois que o binário estiver carregado, você verá seis janelas por padrão. Deixe-me explicar rapidamente o que são essas janelas:
A janela superior esquerda exibe o código desmontado. É o mesmo que desmontar main no GDB. Ele o guiará por todo o código de
montagem do binário. A janela superior direita contém os valores dos registros. Como estamos depurando um binário x64, os valores dos
registros x86, por exemplo EAX ou ECX , estarão dentro do próprio RAX ou RCX .
As duas janelas do meio, a esquerda, mostra a seção .text do código da montagem e a direita mostra as chamadas rápidas na montagem
x64. As chamadas rápidas são convenções de chamada x64, realizadas entre apenas 4 registros. Eu recomendaria pular isso se você é
um iniciante. No entanto, para os gatos curiosos, mais informações podem ser encontradas aqui .
A janela inferior esquerda exibe o despejo de memória do binário e o canto inferior direito mostra a pilha. Sempre que as variáveis forem
passadas para outra função, você as verá aqui.
Quando a tela acima estiver carregada, primeiro procuraremos por strings em nosso binário. Conhecemos algumas strings quando
executamos o binário, ou seja, 'Senha incorreta', 'Senha correta' ou 'Ajuda'. Por enquanto, nosso objetivo principal é encontrar a senha
real e o objetivo secundário é modificar o registro RAX para Zero, para exibir 'Correct Password' (Senha correta), pois
nossa função check_pass () retorna 0 ou 1, dependendo da senha estar correta ou errada. .
Para procurar seqüências de caracteres, clique com o botão direito do mouse em qualquer lugar no código desmontado -> Pesquisar -> Todos
os Módulos -> Referências de Sequências
Isso o levará à tela abaixo, onde mostra a sequência Senha incorreta. Como sabemos que haverá uma comparação entre a senha de
entrada e a senha original antes de imprimir se a senha está correta ou não, precisamos encontrar o mesmo no código desmontado para
visualizar os registradores e a pilha para procurar a senha de texto não criptografado. Agora clique com o botão direito do mouse na área
'Senha incorreta' e selecione Seguir no desmontador . Isso exibirá a tela abaixo na área de desmontagem:
O que fiz aqui na imagem acima é que adicionei um ponto de interrupção em 00000000004015F6 . A principal razão para isso é porque eu
posso ver uma instrução jmp e uma instrução de chamada logo acima dela. Isso significa que uma função foi chamada antes de chegar a
esse ponto e a última função a ser executada antes da impressão da 'Senha correta / incorreta' é a função check_pass () . Então, este é o
ponto em que nossa função interessante começa. Vamos apenas pressionar o botão de execução até atingir a execução do ponto de
interrupção.
Depois de atingir esse ponto de interrupção, pressione stepi (F7) até chegar ao endereço mov RCX, RAX ou 0000000000401601 . Uma vez lá,
você pode ver nossa senha pass123 carregada no registro RCX a partir do registro RAX . Isso não passa de nosso argumento carregado na
função check_pass () . Agora, continue avançando para os próximos registros até chegar ao endereço 0000000000401584 , que é onde nossa
senha de texto sem formatação é carregada no registro RAX .
Você pode ver na janela superior direita que nossa senha 'pass123' e a senha original 'PASSWORD1' estão carregadas nos
registros RCX e RAX para comparação. Isso completa nosso principal motivo para obter a senha de texto sem formatação. Agora, como
nossas senhas são diferentes, ela imprimirá 'Senha incorreta'. Agora precisamos modificar o valor de retorno de 1 a 0, que é retornado
pela função check_pass () . Se você vir a imagem acima, três linhas abaixo do código onde a senha é carregada no registro, você testará EAX,
EAX no endereço 0000000000401590 . E vemos duas declarações de salto atrás deles. Portanto, se o valor do teste retornar, eles serão
iguais, ele irá pular (je = pular se for igual )para crack_m3x64.40159B, que é onde moverá 0 para o registro EAX . Mas como a senha digitada
está incorreta, ela não irá para lá e continuará para o próximo segmento de código, onde passará 1 para EAX, ou seja, no
endereço 0000000000401594 . Portanto, apenas configuramos um ponto de interrupção nesse endereço clicando com o botão direito do
mouse e selecionando breakpoint -> toggle, pois precisamos modificar o valor do registro nesse ponto e continuar executando o binário até
que ele atinja esse ponto de interrupção:
Quando esse ponto de interrupção for atingido, você terá o valor 1 carregado no registro RAX no lado direito. O EAX é um registro de 32
bits, que são os últimos 32 bits do registro RAX . Em resumo,
EAX = 16 bits + AX
AX = AH (8 bits) + AL (8 bits)
e assim por diante.
Portanto, quando 1 é carregado no EAX , por padrão, ele entra no registro RAX . Finalmente, podemos apenas selecionar o registro RAX
no lado direito, clicar com o botão direito e diminuí-lo para Zero.
Epílogo
E então você deve ver que o RAX foi alterado para Zero. Agora continue executando o binário até que ele chegue ao ponto em que
verifica o valor de retorno do binário se é Zero ou Um, que está no endereço 000000000040160C . Você pode ver na imagem abaixo que ele
usa cmp para verificar se o valor corresponde a 1.
Ele usa a condição jne (pule se não for igual), o que significa que passará para crack_mex64.401636 se não for igual a
Um. E crack_mex64.401636 não passa de nossa impressão de 'Senha correta' no endereço 0000000000401636 . Você também pode ver no
registro que nossa senha ainda é pass123 e apesar de ter impresso a senha correta.
Isso seria para a sessão de cracking do windows para este blog. No próximo blog, veremos exemplos um pouco mais complexos, em vez
de encontrar apenas senhas de texto sem formatação dos binários.