sergio-echigo/malware_analysis_cp1
GitHub: sergio-echigo/malware_analysis_cp1
Stars: 0 | Forks: 0
# Análise inicial do programa
A princípio, logo após a execução do programa, pôde-se perceber que ele se baseia na entrada de valores válidos para o nome de usuário e para a senha, "username" e "password", respectivamente. Logo de cara deve-se dar entrada ao nome de usuário e a senha, no entanto, percebe-se que desde que o nome de usuário esteja incorreto, a execução do programa é finalizada. Portanto, o primeiro passo para se obter as credenciais válidas é obtendo algum nome de usuário válido. A primeira coisa a ser feita foi buscar referências textuais (strings) que condiziam com a mensagem de erro referente ao nome de usuário inválido, que é "Pay attention to the username."

Além dessa referência encontrada, é importante notar que outras referências textuais existem e que estas condizem com a lógica até agora encontrada, que é a entrada dos dados, a validação do nome de usuário, a finalização ou continuação do programa — a depender da validade do nome de usuário —, a validação da senha e a mensagem de sucesso ou de erro conforme os dados encontrados.


## Validação do nome de usuário
Após uma série de *breakpoints* criados e uma longa depuração do código em assembly, pôde-se mapear as seguintes instruções, que se referem à saída e a entrada de dados do programa:

Na imagem é possível observar uma série de comentários que foram adicionados para facilitar o entendimento do código, além da adição de *labels* para o entendimento de algumas funções — como **OutputFunction** e **InputFunction**. Ademais percebe-se uma condição um tanto quanto especial, que faz com que a mensagem referente a um nome de usuário inválido seja mostrada na tela, que é a seguinte:
je 2tdcg.cp01.thestartpoint.351301
A condição *je* executa um *jump* para determinado endereço desde que a *flag* **ZF** seja 1. No geral, *jumps* condicionais são executados logo após a execução de instruções de comparação de determinados valores, que é o que ocorre durante a seguinte instrução:
cmp dword ptr ss:[ebp-30],B
A instrução acima realiza uma comparação entre dois argumentos — mais especificamente, uma subtração — e atribui valor 1 para a *flag* **ZF** caso o resultado da comparação seja zero. Portanto, aqui pode-se determinar que os valores comparados devem ser iguais e dessa forma, o nome de usuário estará sendo validado corretamente. No entanto, o que significa o primeiro operando?
;dword significa double word, equivalente a 32 bits (quatro bytes).
;ptr significa que é um endereço na memória.
;ss:[ebp-30] significa um endereço da stack.
Procurando por referências dele, a única coisa que se encontra é que, antes de todo esse esquema de saída e entrada de valores começar, é atribuído um valor de zero a ele. Observando mais a fundo, assim como é mostrado no vídeo abaixo, percebe-se que o valor no endereço de memória indicado é sempre igual ao comprimento do nome de usuário.
https://youtu.be/L2MYrhpcdyU
Sendo assim, conclui-se que o nome de usuário deve ter um comprimento de **B** — onze, em decimal. A prova real vem a seguir:
https://youtu.be/IFVBfQHiyf8
Agora, deve-se buscar uma senha válida.
## Validação da senha
Mais uma vez é possível encontrar diversos saltos condicionais, logo após a execução de inúmeras outras instruções que felizmente não redirecionam a execução do programa para mais nenhum lugar — se isso ocorresse, e durante esses outros lugares existissem mais redirecionamentos, o código ficaria um tanto quanto complicado de se seguir, ou pelo menos é o que se pensa de antemão. Enfim, tais saltos condicionais são responsáveis por fazer com que a aplicação indique que a senha entregue é inválida. Dessa forma, deve-se analisar cada um deles para que se possa validar corretamente a senha dada.
### Condição 1
A primeira instrução que poderia modificar o valor da *flag* **ZF** e fazer com que o salto condicional ocorresse — ou não — é a seguinte:
test eax, eax
Essa instrução realiza uma operação *bitwise* **AND** entre dois valores. A operação **AND** pode ser vista como uma comparação de cada bit de determinado valor; para cada bit, se os dois têm valor igual a 1, o resultado da operação é 1; caso contrário, se pelo menos um dos valores for zero, o resultado é zero. Durante a instrução **test**, o valor zero é atribuído para a *flag* **ZF** quando o resultado da operação é diferente de zero. Portanto, pode-se descrever a seguinte "tabela-verdade":
| eax & eax | ZF |
| :-------: | :-: |
| 0 | 1 |
| ~0 | 0 |
Por isso, deve-se fazer com que o valor de **eax** seja diferente de zero, o que já acontece por padrão, afinal, o valor de **eax** nesse ponto de execução da aplicação está sendo igual ao comprimento do valor da senha. Por isso essa condição não influencia de forma alguma a validação da senha — a não ser que de alguma forma algum valor vazio fosse *inputado*.

### Condição 2
A próxima condição encontrada é idêntica a de cima, exceto pelo valor de **eax**, que é calculado de forma um pouco mais complicada. Fazendo uma análise profunda do que está acontecendo no código, percebe-se que, a depender dos dados que se coloque, o valor zero é atribuído a **eax**, e então, durante a instrução `test eax, eax`, a *flag* **ZF** possui valor zero, o que faz com que a senha seja dada como inválida. O seguinte trecho do código em **assembly** demonstra a atribuição implícita de valor para o registrador **eax**:

É exatamente durante a execução da instrução `call ` que o valor de **eax** muda para determinados valores de entrada para o nome de usuário e para a senha. Com um pouco de pesquisa, nota-se que essa instrução chama uma função nativa de **C/C++**, que é a `memchr`. Essa função é responsável por retornar um ponteiro para o endereço de memória no qual um determinado caractere é encontrado numa *string*, ou também, um endereço de memória. A seguinte definição é encontrada na linguagem **C**, por exemplo:
void *memchr(const void *str, int c, size_t n)
Brevemente nota-se que os seguintes parâmetros estão presentes: o endereço de memória `str` para se buscar o caractere `c`, limitando-se a `n` caracteres desde o início do endereço de memória `str`. Novamente analisando o código em **assembly**, percebe-se que três valores bem específicos são adicionados ao topo da *stack*, respectivamente:
- Um inteiro;
- Um inteiro positivo, que, no entanto, sempre representa um caractere válido em **ASCII**;
- Uma *string*.
Associando tais valores, *debugando* o código e fazendo o entendimento dele, percebe-se que esses valores se referem ao comprimento da senha, o caractere a se buscar, e a *string* (senha) em si. Mais uma vez *debugando* o código, pode-se perceber que essa repetição ocorre num máximo de três vezes, testando valores bem específicos, que são: o terceiro, quinto e último caracteres do nome de usuário, e a senha em si. Portanto, pôde-se determinar que esses caracteres devem estar presentes na senha, pois caso contrário, **eax** tem seu valor zerado e a senha é determinada como inválida. O seguinte mapeamento de dados adicionados a *stack* auxiliam na análise desse comportamento:
==== REPRESENTAÇÃO 1 ====
Username: 01234567890
Password: 234567890
0x9
0x32 ('2')
0093F778 ("234567890")
Portanto, EAX=0093F778 ("234567890")
E, ESP=0093F73C
0x9
0x34 ('4')
0093F778 ("234567890")
Portanto, EAX=0093F77A ("4567890")
E, ESP=0093F73C
0x9
0x30 ('0')
0093F778 ("234567890")
Portanto, EAX=0093F780
E, ESP=0093F73C
==== REPRESENTAÇÃO 2 ====
Username: 01234567890
Password: VITOR
0x5
0x32 ('2')
("VITOR")
Portanto, EAX=0
Aqui
Desde que se faça isso, a senha ainda é considerada válida e o código avança. Alguns *jumps* condicionais são ignorados, e logo a próxima condição para a validação da senha é encontrada.
### Condição 3
Apesar das duas condições anteriores levarem a saltos condicionais que iriam indicar uma senha inválida, esta terceira é responsável justamente por fazer com que a senha seja validada como correta. E a seguinte linha do código demonstra isso:
cmova eax,esi
cmp byte ptr ds:[eax+3],2D
je 2tdcg.cp01.thestartpoint.1E13A7
Considerando que o valor do registrador **esi** é a senha, nota-se que o novo valor de **eax** agora é a própria senha. Finalmente, se compara o quarto caractere da senha — o que indica que pelo menos quatro caracteres são requeridos para a senha — com o hexadecimal **0x2D**, que representa, na tabela **ASCII**, o caractere **-**. Portanto, o quarto caractere deve ser um hífen. A seguinte imagem demonstra a validação dos dados indicados:

E com isso, pode-se pegar um nome de usuário e senha válidos. Contudo, analisando o restante do código em **assembly**, percebe-se que há, também, outra condição para a geração de uma senha válida: que o nono caractere seja igual a um hífen, assim como a possibilidade do terceiro ser. A seguinte imagem também demonstra isso:

## Conclusão
Dito tudo isso, pode-se descrever as seguintes condições para a escolha de um nome de usuário e senha válidos:
- O nome de usuário **deve** possuir exatamente onze caracteres.
- A senha **deve** conter o terceiro, quinto e último caracteres do nome de usuário.
- O quarto caractere ou o nono caractere da senha deve ser um hífen.
Alguns dados válidos são:
- 01234567890,240-
- VITOR_VITOR,TRpAAAAA-
- rm988248901,ma933801-

RM98824