e aqui encontramos a rotina principal e suas chamadas a diversas funçoes, basicamente
Dps temos a criação de um arquivo com o nome data.dat(agora temos algo interessante a explorar ) no modo read (no meio disso ha um pequeno salto condicional com JE, que verifica se o arquivo jah existia, se jah existisse algum arquivo com esse nome, passa a deletar ele usando uma chama
Sinceiramente nada muito interessante por aqui, fora a criação do arquivo data.dat, porem, agr temos nossa primeira chamada a uma funçao interna (
fcn.00001628 vai mostrar no terminal aquela mensagem ascii contendo (Encryption Bot), dps uma chamada a printf com a mensagem Enter the text to encrypt : como argumento e uma scanf com nosso input que sera armazenado na *var_30h*Dps temos a criação de um arquivo com o nome data.dat
system com o argumento rm data.dat) e dps passa a escrever um salto de linea ( \n | 0xa em hexa) com putcharSinceiramente nada muito interessante por aqui, fora a criação do arquivo data.dat, porem, agr temos nossa primeira chamada a uma funçao interna (
fcn.000015dc), o argumento passado a essa funçao eh a *var_30h* (ou seja, nosso input)e aqui temos a resposta a nosso primeiro elemento da checklist, basicamente aqui nosso input eh passado pra a library
strlen e caso seja menor que 0x1b (27), ira nos retornar aquela mensagem de erro, entao vamos executar novamente e inserir 27 caracteres e observar a respostaa resposta eh uma mensagem criptografa com 36 caracteres, equivalente a nosso input, com essa resposta podemos observar algumas coisas, como por exemplo, a saida eh igual ao tamanho da flag.enc, entao ja sabemos o que deve ser feito pra obter nossa flag, entrar com um input que gere de forma equivalente o conteúdo da flag.enc
Uma resposta possivel seria usar alguma tecnica de bruteforce como a execução simbólica, mas pra isso precisamos encontrar o algoritmo de cifrado dentro do binario e o comportamento da entrada
- - -
ateh agr encontramos a quantidade de caracteres do input necessário pra nao receber um erro, agr vamos continuar nossa analise, jah sabemos que devemos procurar um comportamento parecido a um algoritmo da criptografia ou alguma chamada relativa
voltando pra
Eu ateh pensei em colocar ela aqui e explicar passo a passo como tenho feito até agr, porem ela eh bastante grande e parece mais complexa do que realmente eh (jah que inclui as optimizaçoes do compilador ), mas basicamente vou fazer um pequeno resumo dela aqui
Assim sabemos que o arquivo data.dat eh o equivalente em binario a nosso input, vamos agr pra seguinte funçao
Uma resposta possivel seria usar alguma tecnica de bruteforce como a execução simbólica, mas pra isso precisamos encontrar o algoritmo de cifrado dentro do binario e o comportamento da entrada
- - -
ateh agr encontramos a quantidade de caracteres do input necessário pra nao receber um erro, agr vamos continuar nossa analise, jah sabemos que devemos procurar um comportamento parecido a um algoritmo da criptografia ou alguma chamada relativa
voltando pra
main no radare2 temos que dps da função fcn.00001628 (que verifica o tamanho da entrada observamos que dps de sair da funçao, o 0 eh movido pra rax e ha outra chamada a fcn.0000128a (nada relevante nela, eh soh um alinhamento da stack), dps na main podemos ver que variavel que armazena nosso input eh usado como argumento pra funçao fcn.0000131dEu ateh pensei em colocar ela aqui e explicar passo a passo como tenho feito até agr, porem ela eh bastante grande e parece mais complexa do que realmente eh (
)
basicamente a funçao pega nosso input (como um vetor armazenado em *var_30h*) e usa um loop para extrair cada elemento do vetor e converte ele em ascii (em hexadecimal, que no nosso caso seria equivalente a "a" = 0x61) e chama uma funçao interna dentro do loop, que converte o elemento hexadecimal a binario ( 0x61 = 01100001) e faz isso ateh o final do vetor e escrevendo a saida no arquivo *data.dat*
assim, o arquivo *data.dat* possui 216 bytes (27 * 8
Assim sabemos que o arquivo data.dat eh o equivalente em binario a nosso input, vamos agr pra seguinte funçao
fcn.000014ba pra descobrir o que o binario faz com o arquivoAgr as coisas começaram a ficar interessantes
Antes de continuar, jah que sabemos que precisamos inserir a flag, vamos extrair as strings do binario (podemos fazer isso usando
Antes de continuar, jah que sabemos que precisamos inserir a flag, vamos extrair as strings do binario (podemos fazer isso usando
izz com r2 pra extrair as strings globais)e BOOM, a string HTB{I_4M_R3v3rse_EnG1ne3eR} ali no endereço
0x0000202e. Sera que era soh isso, pq eu enrolei ateh agr analisando o binario se a flag tava o tempo todo como uma string sem ofuscação dentro dele??, bem, vamos testar elae como podemos ver, o resultado gerado pela suposta flag eh diferente da flag cifrada que precisamos gerar, ainda assim os primeiros caracteres sao iguais, ou seja, podemos deduzir que a flag real possui HTB{ como primeiros 5 caracteres, o que nao eh nenhuma surpresa sabendo que esse eh o formato das flags usadas pelo HTB. Porem vamos seguir usando esta entrada em nossos testes a fim de analisar o comportamento e encontrar o algoritmo usado pra criptografar o input
- - - -
Bem, voltando pra funçao, podemos ver o seguinte.
A funçao abre o arquivo data.dat e faz um loop onde itera a
Pra continuar precisamos descobrir o que aquele bloco de operaçoes em
Para isso vamos usar
Uma vez la podemos observar que as operações visam encontrar um numero múltiplo de 6 (eh usado uma multiplicação
e no final da iteraçao substraindo -1 o valor de
- - - -
Bem, voltando pra funçao, podemos ver o seguinte.
A funçao abre o arquivo data.dat e faz um loop onde itera a
*var_4* e com fgetc extrai um caractere do arquivo e faz desvios comparativos entre 0 e 1 e dependendo do resultado armazena na stack o 0 ou 1, com cada comparaçao aumenta +1 dentro da *var_4* (equivalente a um (int i = 0; i <= 216; i++) ) onde i eh a *var_4* e passa a executar um bloco de operaçoes (*0x00001541*) com o valor de *var_4* e dependendo do resultado das operaçoes volta pra o inicio do loop fazendo um salto com JNE ou entra pra um pedaço de instruçoes as quais substrai 1 a *var_4* (i) e armazena o resultado em *var_8* (ou seja *var_8* = *var_4* - 1) e entra em outro loop for usando *var_10* com iterador e armazenando 0 dentro de 2 variaveis novas (*var_c*, *var_10*)Pra continuar precisamos descobrir o que aquele bloco de operaçoes em
*0x00001541* faz e da qual o resultado sera usado em *var_4* pra entrar na iteraçao com *var_10*Para isso vamos usar
GDB (com GEF) pra fazer uma analise dinamica e analisar os resultados em stack, vamos abrir o binario com GDB e vamos colocar um breakpoint no endereço equivalente a 0x00001541 e uma vez no breakpoint vamos analisar a stack, nesse caso a *var_4* no r2 eh equivalente a *rbp-0x4* e vamos colocar outro breakpoint no endereço de test antes de entrar no loop iterador (0x00001565)Uma vez la podemos observar que as operações visam encontrar um numero múltiplo de 6 (eh usado uma multiplicação
imul e 2 operaçoes de deslocamento de bits shr e sar, eh bastante interessante conhecer elas) e uma vez atingindo o resultado de igualdade JNE, entra na iteraçao com *var_8*(*rbp-0x8*) = 5 e *var_10* = 0e no final da iteraçao substraindo -1 o valor de
rbp-0x8 e adicionando +1 no rbp-0x10
(int RBP_0x10 = 0; for RBP_0x10 <= 5; RBP_0x10++)
..
..
RBP_0x8 = RBP_0x8 -1
dentro do loop, usa o valor de *var_10* como argumento a uma chamada a fcn.000013abAqui temos mais uma funçao que eh basicamente um loop com o argumento inserido, neste loop o argumento eh uma potencia de 2, ou seja, podemos representar com um pequeno script em
python
var_8h = 2 #nao confundir com rbp-0x8 da funçao anterior, as duas estao em frames diferentes da stack
for i in range(6):
var_4 = var_8 ** 2
print(var_4)
onde
i eh equivalente a *var_10* = [0, 1, 2, 3, 4, 5] e a cada saida da funçao sera retornado um inteiro equivalente a potencia de 2 [1, 2, 4, 8, 16, 32] durante os 6 loops de *var_10*e ao voltar a funçao basicamente aqui ele pegando a posiçao atual do arquivo e ta substraindo as iteraçoes ) e tira o bit relativo a posiçao pra ser multiplicado pelo retorno da funçao anterior (
- e soh pra esclarecer, faz bastante sentido jah que 216 / 6 = 36 (que como lembramos eh o tamanho da nossa flag criptograda) e 216 bits sao 27 bytes, o tamanho da nossa entrada e como confirmamos antes, o unico tamanho possivel
entao temos que cada valor da potencia de
e dps de terminar o
fcn.000014ba cada resultado eh armazenado na variavel *var_20*, dps passa *var_8* (*var_20*) e ser armazenado na *var_c*. E como no arquivo soh existem 0 e 1, logo cada retorno ou vai adicionar 0 (nada) ou vai adicionar algum elemento relativo ao retorno da funçao anterior - e soh pra esclarecer, faz bastante sentido jah que 216 / 6 = 36 (que como lembramos eh o tamanho da nossa flag criptograda) e 216 bits sao 27 bytes, o tamanho da nossa entrada e como confirmamos antes, o unico tamanho possivel
entao temos que cada valor da potencia de
2 (0..5) = [1, 2, 4, 8, 16, 32] pode ser somado a *var_c* dependendo do que tiver na posiçao (0 nao soma, 1 soma) e dps de terminar o
for com *var_10* eh feita a chamada a funçao fcn.000013e9 com *var_c* como argumento👍1
e
Entao, basicamente o que precisamos pra gerar a string criptografa eh passar no argumento o valor equivalente a posiçao no vetor da letra na string *lag.enc
podemos fazer um pequeno script com
BOOM (agr de vdd), vemos agr o algoritmo de permutação, onde basicamente temos um vetor (começando pelo rbp-0x190 ateh rbp-0x158) e como vemos no endereço 0x000014a1 o argumento eh usado como valor pra procurar no índice do vetor e colocar no terminal com putsEntao, basicamente o que precisamos pra gerar a string criptografa eh passar no argumento o valor equivalente a posiçao no vetor da letra na string *lag.enc
podemos fazer um pequeno script com
python pra nao precisar fazer na mao#!usr/bin/python3
vetor = "RSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQabcdefghijklmnopqrstuvwxyz" # vetor (rbp-0x190 - rpb-0x158)
key = "9W8TLp4k7t0vJW7n3VvMCpWq9WzT3C8pZ9Wz" # flag criptograda
ii = 0
keyPosition = [] # lista onde vamos guardar a posiçao das letras da flag criptografada no vetor
for iz in key:
for i in vetor:
ii = ii + 1
if i == iz:
keyPosition.append(ii-1)
if ii == 62: # aqui vamos procurar no vetor e usar ii que sera somado +1 como indice pra o vetor em python, caso a letra da chave seja encontrada, armazena ii - 1 na lista e repete ateh o final da key
ii = 0
print(keyPosition)
dps de usar nosso script, descobrimos que os argumentos (
E como fazemos isso??
Sabendo que cada numero que retorna do loop for com
e juntando tdo temos os bits
- - - -
e
*var_c*) que precisamos passar pra funçao fcn.000013e9 sao (18,5,17,2,30,51,13,46,16,55,9,57,28,5,16,49,12,4,57,31,21,51,5,52,18,5,61,2,12,21,17,51,8,18,5,61) para atingir as posiçoes no vetorE como fazemos isso??
Sabendo que cada numero que retorna do loop for com
*var_10* na funçao anterior sera somado a *var_c* dependendo dos valores 0 ou 1, vamos testar isso na pratica, lembrando que cada byte tem 8 bits e a funçao usa 6 elementos [1, 2, 4, 8, 16, 32], entao vamos fazer 4 vezes pra gerar um multiplo de 8 (8 * 3 = 24 || 6 * 4 = 24) e ter uma string que nao esteja quebrada2 + 16 = (18) ||32 16 8 4 2 1
0 1 0 0 1 0
1 + 4 = (5)
0 0 0 1 0 1
1 + 16 = 17
0 1 0 0 1
2 = 2
0 0 0 0 1 0
e juntando tdo temos os bits
01001000010101001000010, convertendo pra ascii obtemos a string HTB- - - -
e
BOOM, conseguimos reverter o algoritmo de criptografia do nosso binario, agr soh basta automatizar e obter nossa string#!/usr/bin/python3
keyPosition = [18, 5, 17, 2, 30, 51, 13, 46, 16, 55, 9, 57, 28, 5, 16, 49, 12, 4, 57, 31, 21, 51, 5, 52, 18, 5, 61, 2, 12, 21, 17, 51, 8, 18, 5, 61]
var_4 = [32, 16, 8, 4, 2, 1]
keyBin = []
for i in keyPosition:
for ii in var_4:
zz = i > ii
if zz == False:
if i == ii:
keyBin.append('1')
i = i - ii
print(zz, ii, i)
else:
k.append("0")
else:
i = i - ii
keyBin.append('1')
print(zz, ii, i)
print(k)
e bem, assim vamos obter a sequencia de bits que ao converter em ascii, vai nos dar a nossa flag