Introdução à exploração e reversão usando o IDA FREE e outras ferramentas gratuitas. Capítulo 2

Na primeira parte, instalamos várias ferramentas que serão úteis para fazermos este curso. Sua característica é que eles são todos gratuitos. Não usaremos nenhuma ferramenta paga e, daqueles que possuem uma versão paga, como IDA ou PYCHARM, usaremos a versão GRATUITA ou COMUNITÁRIA.

Vamos dar uma olhada em alguns conceitos antes de começarmos os exercícios.

O que é um BAG?

O BAG é o resultado de uma falha ou falta no processo de criação de programas de computador (software) ou um computador. A falha especificada pode ocorrer em qualquer estágio do ciclo de vida do software, embora a falha mais óbvia ocorra no estágio de desenvolvimento e programação.

Como sempre digo, um programador pode cometer erros, e esses erros podem causar falhas ou bugs no programa. Até agora, eu não disse nada de novo.

A questão é saber a diferença entre BAG e VULNERABILITY, então vamos ver o que é VULNERABILITY.

O que é VULNERABILIDADE?

VULNERABILITY é um certo tipo de bug em um programa que permite usá-lo para violar a segurança de um sistema de computador.

Assim, as vulnerabilidades permitem executar ações para as quais o programa não foi planejado e abusar delas.

Em outras palavras, a vulnerabilidade é um certo tipo de bug, um subconjunto entre eles.



Obviamente, existem muitos tipos de vulnerabilidades. Vamos nos concentrar no estudo e na exploração de vulnerabilidades no WINDOWS.

O que é EXPLOIT?


EXPLOIT é um programa de computador que tenta explorar algumas vulnerabilidades de outro programa. O objetivo final de uma exploração pode ser malicioso, por exemplo, como destruir ou desligar um sistema atacado, embora geralmente seja uma violação das medidas de segurança para obter acesso a informações de forma não autorizada e usá-las em seus próprios interesses ou como fonte de outros ataques a terceiros.

O abuso da vulnerabilidade pode levar à falha do aplicativo ou do próprio sistema, à execução do código nativo em máquinas locais ou remotas. Sua operação e complexidade dependem da própria vulnerabilidade, do ambiente e das medidas que a meta possui durante a operação.

O primeiro tipo de vulnerabilidades que examinaremos será o estouro de buffer. Começaremos com os exemplos mais simples e aumentaremos gradualmente a complexidade.

Inicialmente, os recursos de segurança do sistema não serão ativados, mas gradualmente os ativaremos para descobrir como podemos lidar com eles e em que situações.

O que é um BUFFER?


BUFFER é um espaço de memória de um determinado tamanho reservado para armazenamento e gerenciamento de dados.

Um exemplo básico é um frasco de 20 litros, que eu tenho para armazenar conteúdo. Pode ser menor ou igual a 20 litros, que é o tamanho máximo. Se você deseja armazenar mais em um tanque, deve encontrar uma maneira de aumentar o tamanho do buffer, caso contrário, ao tentar economizar, por exemplo, 40 litros em uma lata de 20 litros, ele transbordará.

O que é um buffer overflow?


O FLUXO DE BUFFER ocorre quando um programa de computador excede a quantidade de memória reservada gravando dados em um bloco de memória contíguo.

https://www.welivesecurity.com/la-es/tag/buffer-overflow-la-es

Na verdade, um estouro de buffer ocorre em um aplicativo quando ele não possui as verificações de segurança necessárias em seu código de programa, como medir a quantidade de dados que serão copiados para o buffer e que não excedem o tamanho do buffer.

Os tipos mais comuns de estouros de buffer são estouros de buffer de pilha e estouros de buffer de heap.

Aqui vemos a definição de estouro de buffer e, no exemplo anterior, se eu tentar derramar 40 litros em um tanque de 20 litros, ele transbordará, como o entendemos. Este é um estouro que causa um estouro de buffer, ou seja, transbordamento do meu tanque quando sua capacidade máxima é excedida.

Agora explique a diferença entre a pilha e a pilha.

O que é uma PILHA?


STACK é usado para armazenar variáveis ​​de função local necessárias apenas enquanto a função é executada. Na maioria das linguagens de programação, é importante saber, em tempo de compilação, qual o tamanho de uma variável, se queremos mantê-la na pilha.

O que é MUITO?


O heap é usado para reservar memória dinâmica, cuja vida útil não é conhecida antecipadamente, mas espera-se que dure algum tempo. Se não soubermos seu tamanho ou for determinado em tempo de execução, o tamanho deverá ser calculado e reservado na pilha.

O heap também é usado para objetos que variam em tamanho, porque não sabemos em tempo de compilação por quanto tempo eles serão usados.

Trabalho na nossa empresa há mais de 13 anos como autor de façanhas, e a primeira coisa que fizemos com todas as pessoas contratadas até me fez quando entrei foi tentar desvendar as pilhas e os montes do famoso GERARDO RICHART. Ele é um dos fundadores da CORE SECURITY e um guru de análise de exploração.

Vamos começar devagar com as pilhas mais simples. Obviamente, como eu disse, eles são compilados no momento com proteção mínima e são de 32 bits para facilitar a operação.

Vamos dar uma olhada no código fonte da tarefa STACK1.

https://drive.google.com/open?id=16btJAetpa1V5yHDZE2bnnFWTQNpsUR4H

Vemos uma pasta com exercícios e, dentro, está o código-fonte STACK1 chamado STACK1_VS_2017.CPP.

#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE

#include <stdlib.h>
#include  <stdio.h> 
#include "Windows.h"


int main(int argc, char **argv) 
{


	MessageBoxA((HWND)-0, (LPCSTR) "Imprimir You win..\n", (LPCSTR)"Vamosss", (UINT)0);

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}


Vamos tentar entender esse código e ver onde o estouro de buffer pode ocorrer e se haverá um estouro de buffer na pilha ou no heap.

A chamada da função MessageBoxA foi adicionada ao código-fonte STACK1 para mostrar uma pequena mensagem nos solicitando a resolvê-la. Esta é apenas uma adição que não afeta nada. Essa é uma chamada padrão para a função WINDOWS especificada, que não analisaremos aqui.

Quem precisa de informações sobre esta função, pode obtê-lo aqui.Nós

sabemos que dentro da função, se houver variáveis ​​locais, você deve reservar um local para elas.

Então, ficamos com esse código-fonte criado por GERARDO RICHART.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Vemos em vermelho a primeira parte do programa, onde espaço reservado para variáveis ​​locais. Nesse caso, existem duas variáveis ​​locais, COOKIE e BUF.

Você pode ver os tipos de dados na tabela . Outros tipos de variáveis ​​também estão localizados lá.

O código será compilado em 32 bits.

Vemos que a variável COOKIE será do tipo INT, portanto, 4 bytes de memória serão reservados para essa variável.



No caso da variável BUF, vemos que é uma matriz ou uma sequência de caracteres (tamanho do caractere = 1 byte).



Essa. será uma matriz de 80 caracteres, ou seja, seu comprimento será 80x1 = 80 bytes.

Qualquer pessoa que não saiba o que uma matriz pode ler sobre isso aqui:

https://www.programiz.com/c-programming/c-arrays

Assim, uma matriz pode armazenar muitos valores do mesmo tipo de dados. Você apenas precisa dizer a ele que tipo de dados serão e quanto haverá.



No primeiro exemplo, essa é uma matriz de números inteiros, ou seja, será 100 bytes e, como cada número inteiro ocupa 4 bytes, o comprimento da matriz será 100 x 4 = 400 bytes.

No segundo exemplo, FLOAT usa 4 bytes, portanto, será uma matriz de 5 FLOAT; portanto, seu comprimento será 5 x 4 = 20 bytes.

Quando analisamos a matriz em um nível baixo, veremos que é um espaço de memória ou buffer reservado. Esta não é a única maneira de reservar espaço na memória. Existem outros tipos de dados variáveis ​​que também exigem espaço de reserva na memória que serão buffers para armazenar seu conteúdo.

Voltando ao nosso exercício:

char buf[80];

Essa é uma matriz de caracteres de 80 x 1 = 80 bytes, ou seja, parece o nosso frasco de 20 litros. Se tentarmos armazenar mais de 80 bytes, o banco transbordará.

Agora vamos ver onde o buffer BUF é usado.



Vemos que o buffer é usado em dois locais marcados com setas vermelhas.

A primeira instrução possui uma função PRINTF, usada para exibir uma mensagem no console, que será uma cadeia de caracteres entre aspas.

"buf: %08x cookie: %08x\n"

Mas a função PRINTF não apenas imprime a sequência entre aspas, mas também imprime a sequência no formato especificado. As porcentagens contidas nos informam que uma linha de saída será criada. Vemos que a string é apenas o primeiro argumento para a função. O formato de saída e outros argumentos podem ser vários (haverá um para cada argumento% no formato). No nosso caso, existem dois deles.



Nesse caso, temos dois formatos% X, por isso, se eu me referir à tabela de formatos PRINTF:



vemos que a função pega esses números inteiros (INT) e os insere na linha de saída com a base do sistema numérico 16, ou seja em formato hexadecimal. 08 indica que se o número contiver menos de 8 dígitos, a função o preencherá com espaços.

A saída para "buf:% 31x", & buf será assim

buf:             19FED4 

Vemos que, neste exemplo, são preenchidos com espaços antes do número. Existem vários modificadores para mostrar a saída.

Todos os casos possíveis estão listados aqui:

http://www.cplusplus.com/reference/cstdio/printf/

Nosso caso é o seguinte:





vemos que o resultado não é truncado, é preenchido com espaços apenas se o comprimento do argumento a ser inserido for menor que o valor antes de X.

Portanto, sabemos que a função imprime dois números hexadecimais, que são obtidos de dois argumentos.

printf("buf: %08x cookie: %08x\n", &buf, &cookie);

Sabemos que uma variável tem um endereço de memória e um valor que pode ser armazenado. Parece o nosso frasco de 20 litros. Tem o seu conteúdo ou significado, ou seja, litros armazenados no interior, mas também se eu tiver uma garagem cheia de latas semelhantes, preciso de uma maneira de determinar onde a lata que quero está entre todas as que tenho.

O símbolo & indica isso. Ele retorna o endereço ou o local do jar, não seu conteúdo ou valor.

Definição de AMPERSAND


AMPERSAND é usado para indicar o endereço de memória da variável na qual os dados serão armazenados.

Portanto, se eu executar o arquivo executável no console, verei, por exemplo, que quando executar a função PRINTF, ele imprimirá:



Os endereços podem mudar no seu PC, mas como o endereço inferior de ambos os endereços corresponde ao endereço BUF, vemos que eles estão localizados desta maneira:



O endereço BUF é menor que o endereço de COOKIE, portanto aumentará.

E o que esses endereços variáveis ​​nos dizem? (No meu caso, & BUF = 0x19FED4 e & COOKIE = 0x19FF24)



Ambos estão no formato hexadecimal. Lembra que este era o formato% X? Então, coloquei 0x à frente para distinguir números decimais que representaremos sem acréscimos.

Se eu fizer uma subtração no console PYTHON ou no PYCHARM: Obtemos



um resultado de 80 bytes, pois a variável COOKIE supostamente inicia exatamente onde o buffer BUF termina, então a diferença nos dá o tamanho do buffer.

Freqüentemente, quando fazemos esse tipo de variável com base no código-fonte, pode acontecer que o compilador nos dê um tamanho maior que o que está reservado no código-fonte. O compilador garante que reservará pelo menos 80 bytes, ou seja, ele pode reservar mais, não menos.

O fato é que já sabemos algo sobre o código, o tamanho das variáveis ​​e sua localização, devido ao fato de ele possuir a função PRINTF.

Agora, vamos ver outro local em que o buffer BUF é usado, porque agora o programa apenas imprime seu endereço, mas não o usa para armazenar dados nele.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Aqui, na linha vermelha, GET é uma função para inserir dados do teclado. Os dados serão inseridos até eu pressionar Enter.

O programa não pode limitar a quantidade de dados inseridos pelo usuário e também não há como verificar esses dados. Tudo o que é digitado antes de pressionar a tecla ENTER é copiado para o buffer BUF.

Este é o problema. Dissemos que o BUF só pode armazenar 80 bytes no máximo; portanto, se inserirmos mais, criaremos um buffer overflow, e aqui estão todas as condições para isso, porque se o usuário gravar mais de 80 bytes, nosso tanque transbordará e o líquido escorrerá .



O fato é que, sob o BUF, é a variável COOKIE, o excesso excede e o preenche com um valor que você pode controlar.

Por exemplo, se alguém que imprime escreve 80 * A e 4 * B, 80 * A preenche BUF e 4 * B preenche COOKIE e, como sabemos, quando alguém imprime um caractere no console, o valor permanecerá baixo. ASCII.



Como o cookie será preenchido com quatro letras B, que são equivalentes a um valor de 0x42, podemos garantir que o valor do cookie seja 0x42424242, ou seja, no meu computador, o endereço de cookie 0x19FF24 terá 0x42424242 como o conteúdo.

0x19FF24 => 42424242



O fato é que já vimos como estourar e controlar o valor de COOKIE.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Você deve imprimir "você venceu" para concluir o exercício. Para isso, o COOKIE deve ser igual ao valor 0x41424344 e, se não houver excesso, isso seria impossível, pois o valor do COOKIE nunca foi alterado desde o início do programa. Não conseguiremos imprimir "você ganha" e, para isso, usamos um estouro de buffer, o que indica que isso pode fazer com que o programa execute alguma ação diferente da que foi programada.

Nesse caso, você nunca pode digitar “você ganha”, apenas o estouro permitirá que você faça isso.

AAAAAAAA

Em outras palavras, em vez de passar, por exemplo, 80 * A e 4 * B, para imprimir "você ganha", você deve passar 80 * A e depois as letras DCBA, pois isso fará com que os valores sejam armazenados no COOKIE ASCII.

44434241

https://www.arumeinformatica.es/blog/los-formatos-big-endian-y-little-endian/

O formato no qual os dados são armazenados é LITTLE ENDIAN. Em outras palavras, os dados na memória são armazenados em ordem inversa, para simplificar.



E se a sequência 0x41424344 for salva, o sistema a salvará na memória como:

44 43 42 41

Por esse motivo, ao copiar para a memória, a cópia ocorrerá enquanto digitamos, portanto, devemos escrever o valor na ordem inversa para que, ao ler na memória, estava na forma correta.

Nós podemos executar o executável no console.



E o cursor piscará, pois a função GET me pede para inserir a entrada. Digite com cuidado 80 caracteres A e depois DCBA.

No console PYTHON ou PYCHARM, posso imprimir a linha, copiá-la sem aspas e colá-la no console para não imprimi-la como louca e, em seguida, pressione a tecla ENTER para inseri-la.





Vemos que conseguimos "você ganha".

Podemos ver isso no depurador. Para isso, usaremos o X64DBG.



Eu escolhi a versão de 32 bits.





Se pararmos na biblioteca NTDLL.DLL, pressionaremos RUN novamente com F9.

Vemos que o depurador para na primeira instrução do módulo STACK1, chamada ENTRY POINT ou na primeira instrução executada pelo módulo.



Obviamente, isso não é como o nosso código-fonte. Você deve entender que o compilador adiciona muito código para fazer o executável funcionar e executar corretamente. Vamos tentar encontrar nossa função principal. Podemos nos orientar olhando as linhas do programa.



Selecionamos apenas a pesquisa na região atual. Sabemos que as linhas estarão na mesma seção.



Aqui vemos que existem linhas de programa e outras que o compilador adicionou. Clicamos duas vezes em uma de nossas linhas.



Agora você pode ver muito mais. Vemos uma chamada para a função MessageBoxA, PRINTF, GETS e uma comparação com o valor 0x41424344.

Além disso, estamos adicionando um plugin para descompilar o SNOWMAN. Podemos tentar ver como ele descompila o código, ou seja, como ele está tentando obter o código fonte ou algo o mais semelhante possível do arquivo compilado.





Vemos que isso não é perfeito, mas é melhor do que era.

Vou colocar o BP no início da função e pressionar F9 até o depurador parar.



Para quem não sabe o que é um argumento de função.



No nosso caso, a função principal possui argumentos, mas eles não são usados ​​dentro da função.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Aqui vemos que existem dois argumentos, e eles são passados ​​pela pilha quando o executável é compilado em 32 bits.

Pouco antes da chamada da função, os argumentos serão armazenados na pilha.

Quando paramos no início da função, o primeiro valor na pilha será RETURN ADDRESS, ou seja, onde a função retornará após a conclusão da função e, abaixo desse valor, estarão os argumentos dessa função.

Se clicar com o botão direito do mouse em RETURN ADDRESS e selecionar SIGA DWORD IN DISASSEMBLER, verei para onde o depurador deve retornar após a conclusão da função.



Ele voltará aqui. Isso significa que a função principal foi chamada a partir de uma chamada que é mais alta. Eu posso colocar a BP aqui, reiniciar o exercício e ter certeza de que está.



Vou colocar a BP um pouco mais cedo e reiniciar o programa.



O depurador irá parar aqui.



Ele salvará os argumentos na função principal usando estas instruções PUSH.

Abaixo está um link para quem deseja saber mais sobre os argumentos da função:

https://publications.gbdirect.co.uk/c_book/chapter10/arguments_to_main.html

Isso não é muito difícil. O primeiro argumento para ARGC é INT, que indica o número de parâmetros do console usados ​​para executar o programa, incluindo o caminho para o executável, e ARGV é uma matriz de ponteiros para seqüências de caracteres.

Vemos que, se eu alterar a linha de comando, passarei mais argumentos e recarregarei.





Aqui, o depurador para quando está prestes a salvar argumentos usando a instrução PUSH. O primeiro argumento armazenado é o mais distante e o último que você salvar será o primeiro argumento da função.

Eu rastreio e cada instrução PUSH salva os argumentos.



Aqui eu posso ver os argumentos da função. Acima está o primeiro argumento para o ARGC. É 3, pois marca o número de argumentos que são passados ​​para o console.



Aqui estão três argumentos.

Agora pressionamos F7 para fazer o STEP INTO e entrar na função.

Aqui vemos que, ao entrar na CALL, o depurador salva o RETURN ADDRESS na pilha.



Portanto, como dissemos ao inserir a função, a primeira coisa que será salva na pilha é RETURN ADDRESS (na compilação de 32 bits) e abaixo estão os argumentos para a função, primeiro o primeiro argumento e depois o restante em sequência.



O segundo argumento, como vimos, é uma matriz de indicadores. Aqui vemos na memória que existem três ponteiros para três linhas que são passados ​​como argumentos.



Aqui paramos no início da função, um pouco mais baixo, temos RETORNOS ENDEREÇOS e ARGUMENTOS.

Esclarecemos que o arquivo é compilado em 32 bits, porque na versão de 64 bits, os argumentos são transmitidos de maneira diferente. Veremos mais tarde.

Então a função começa a executar. A primeira coisa é o chamado PROLOGUE, que armazena o valor EBP da função que chamou nossa.



Isso manterá o valor EBP logo acima do endereço de retorno.



Se eu seguir as instruções usando F7.

Vejo que o valor EBP GUARDADO está na pilha acima do endereço de retorno.



A seguinte instrução em PROLOGUE:

MOV EBP, ESP

Define o valor de EBP para a função atual que foi salva e foi a função pai que chama nossa (neste caso, minha principal função é a função baseada em EBP, em outros casos, pode ser diferente e nós veja-os mais tarde)

Ao colocar o valor atual do ESP no EBP, garantimos a criação de um quadro para nossa função atual.



Agora, como essa função é baseada em EBP ou EBP BASED, o valor de EBP será armazenado dentro da função e será aceito como referência, e o ESP será alterado.



Este valor EBP é tomado como base.

Nas funções baseadas em EBP, variáveis ​​e argumentos podem ser nomeados pela distância deste endereço, que será armazenado no valor EBP até o seu epílogo.

Podemos ver várias variáveis ​​na lista denominadas EBP-4 ou EBP-54, referentes ao valor de EBP que está aceitando no momento.

Podemos dizer que, assim que o EBP assumir seu valor após o PROLOGUE, parecerá um ralo, portanto os argumentos sempre seguirão nessa direção; portanto, EBP + XXX se refere aos argumentos (o EBP armazenado e o RETURN ADDRESS também são mais baixos, mas não terão links no código), enquanto as variáveis, como veremos, estarão acima desse endereço, portanto o link para EBP-XXX se refere a alguma variável local.

Assim, em funções baseadas em EBP:

EBP + XXXX = argumentos passados ​​para a função
EBP - XXXX = variáveis ​​de função local

Após o PROLOGUE, haverá uma maneira de reservar espaço para as variáveis. No nosso caso, isso é feito movendo o ESP para cima, para que o espaço restante abaixo seja reservado para a soma de todos os comprimentos variáveis ​​e, às vezes, um pouco mais apenas no caso, o que depende do compilador.

00401043 83EC 54 | SUB ESP, 54

Vemos que o ESP está localizado acima do EBP, que permanecerá o link, e que 0x54 convertido em decimal é 84, que é a soma dos comprimentos BUF e COOKIE. Lembre-se que eles eram 80 e 4, respectivamente.







Na execução, um espaço é criado para as variáveis ​​BUF e COOKIE de 84 bytes de tamanho. Você pode clicar na primeira coluna na direção horizontal, examinar o valor EBP e encontrar esse valor na pilha. Obviamente, agora será mais baixo.



Clico duas vezes aqui.



Assim, teremos valores em relação à EBP também na pilha.

Por exemplo, EBP-4 corresponde à listagem, -4 é exibido na primeira coluna da pilha e, na explicação, também é exibido como EBP-4.



Se eu rastrear, vemos que a partir do local em que o ESP foi localizado para reservar as variáveis, ele sempre subirá, pois deve levar em consideração o espaço alocado para as variáveis. Ao executar 4 PUSHs para MessageBoxA, o depurador coloca as variáveis ​​acima do espaço reservado e aumenta o ESP.



Se eu olhar para a pilha, vejo 4 argumentos verdes que adiciono sobre o espaço reservado marcado em vermelho.



Quando você insere a função MessageBoxA, o RETURN ADDRESS dessa função é armazenado na pilha.



Aqui está o endereço de retorno da MessageBoxA. Quando chego ao RET dessa função, rastreando com F8, e executo o MessageBoxA.



Vemos que o depurador retornará logo abaixo da chamada MessageBoxA.



E os valores PUSH que você passou para a função MessageBoxA e o RETURN ADDRESS para essa função já foram usados, e o ESP está novamente logo acima da área reservada, como antes de qualquer função ser chamada. O mesmo acontecerá com a chamada da função PRINTF.

Depois de passar na função PRINTF, os endereços BUF e COOKIE serão impressos.



O endereço BUF na minha máquina será 0x19FED4 e o endereço de COOKIE será 0x19FF24.

Aqui, o programa lê o endereço BUF para passá-lo para a função GETS e preencher o BUF. Podemos verificar se o endereço corresponde ao que o console 0x19FED4 mostra.





Aqui vemos que é o EBP-54. Se clicar duas vezes na pilha onde mostra -54, mostrará que o endereço é BUF = 0x19FED4 na minha máquina.



Agora, quando vou salvar os dados inseridos nesse endereço, posso colocá-los em um despejo para ver como os bytes são armazenados lá.





Aqui estão eles. Além disso, nada é exibido abaixo, pois não há dados.

Quando eu chamo a função GETS usando F8, precisarei ir ao console, digite e pressione ENTER para preencher o buffer BUF e reescrever o COOKIE.



Vemos que a variável COOKIE estava em 19FF24 na minha máquina.

Aqui, o programa compara o cookie com 0x41424344.



Vemos que o EBP-4 diz que é um BOLINHO, além do endereço, se definirmos o HORIZON para o valor do EBP, como antes.



Clico duas vezes aqui.

Vemos que o EBP-4 é um BOLINHO, pois a variável está no nível da pilha -4, zerando o HORIZONTE.



Vemos que o programa não vai pular e vai nos mostrar que você ganhou!





Então, alcançamos manualmente o objetivo que você conquista !,

analisamos dinamicamente o STACK1 usando o X64DBG, que é um depurador e não nos permite analisar o programa sem iniciá-lo. Para fazer isso, precisamos usar outras ferramentas, como IDA PRO, GHIDRA ou RADARE.

Eu posso criar um modelo de script para operar o exercício da PYTHON.

import sys
from subprocess import Popen, PIPE

payload = b"A" * 80 + b"\x44\x43\x42\x41"

p1 = Popen(r"C:\Users\ricardo\Desktop\abos y stack nuevos\STACK1_VS_2017.exe", stdin=PIPE)
print ("PID: %s" % hex(p1.pid))
print ("Enter para continuar")
p1.communicate(payload)
p1.wait()
input()

No caso do PYTHON 3, preciso colocar parênteses na função PRINT e ter cuidado ao adicionar linhas que devem ser bytes (coloque b na frente das linhas no PYTHON 2).

Verifico se o caminho está correto e quando executo o arquivo.



Boa. Já temos um modelo de script para o PYTHON 3 para operar STACK1. Na próxima parte, continuaremos a análise estática em IDA, RADARE e GHIDRA.


Observe que, além da tarefa STACK1, também existem versões 2, 3 e 4. Você pode tentar resolvê-las. Eles são muito simples e semelhantes ao STACK1, portanto, não fique entediado.

Na próxima parte, veremos a IDA FREE, RADARE e GHIDRA.

Vejo você na próxima parte 3.

Ricardo Narvaha
25/10/2019

PS # 1
Um belo PDF pode ser baixado na minha página inicial - yasha.su

PS # 2
Em breve, escreverei uma continuação do artigo https://habr.com/en/post/464117/ sobre como coletei ajuda para o padre Chris Kaspersky e o que é isso aconteceu.

Não fique entediado.

All Articles