Google CTF JS1.0
Neste tutorial, apresentarei a vocês minha solução para o desafio JS SAFE 1.0 do GoogleCTF2018 para iniciantes, todos os anos o Google lança uma série de desafios fascinantes em uma variedade de tópicos, como:
- CRYPTO
- MISC
- PWN
- RE
- WEB
Tento todos os anos resolver pelo menos um desafio da web – Em segundo lugar, esses são desafios relativamente difíceis em relação a outras competições da CTF das quais participo. Além da competição, desta vez o Google também lançou uma série de desafios para iniciantes.
Os desafios são muito fáceis para quem é novo na área, recomendo fortemente que tente resolvê-los. Em nossos cursos até dou os exercícios como lição de casa e apresento as soluções na aula.
O objetivo do desafio js_safe_1 é entender como o código funciona e encontrar a senha do cofre.
A fim de lhe ensinar o método de pensamento e como resolver esses desafios, gravei o seguinte vídeo para você:
Este desafio começa quando você recebe o arquivo js_safe_1 Para baixar o arquivo
Para encontrar a função que lida com a entrada de dados no campo de entrada, clique em inspecionar e vá para a guia Event Listeners:
Agora clique na função e ela o levará diretamente para a guia source:
Procure a função open_safe em um arquivo parecido com este:
Neste código temos uma expressão regular que se não existir ou que se a função x devolver False o cofre imprime-nos Access Denied caso contrário obteremos Access granted.
A função x é definida acima e contém o seguinte código, iremos dividir o código em várias partes da seguinte maneira:
Detalhes da divisão que fiz:
-
Nesta seção, vemos uma string Unicode que contém muitos caracteres, se você focar nos caracteres, notará que alguns deles contêm os nomes das funções dentro env.
-
Esta seção contém a variável env que contém uma série de operações padrão com as quais podemos produzir qualquer função que quisermos durante a execução, as operações são:
-
(x,y) => x[y]
-
Function.constructor.apply.apply(x,y)
`
a = { test1: (x,y) => Function.constructor.apply(x, y), test2: (x,y) => Function.constructor.apply.apply(x, y) }; roman_apply = a["test1"](this,[["x","y"],"return x+y"]); console.log(roman_apply(2,3)); roman_apply_apply = a["test2"](roman_apply,[,[2,3]]); console.log(roman_apply_apply);
`
-
(x,y) => x+y
-
(x) => String.fromCharCode(x)
-
• Os comandos a seguir contêm os dígitos 0 e 1, esses dígitos são usados pelas funções dinâmicas para criar diferentes números usando operações matemáticas.
-
new TextEncoder().encode(password)
Esta ação converte texto em byte array que precisamos para determinadas operações, geralmente operações de criptografia.
-
A última ação h é um valor de retorno que se repetirá em nosso software e deve ser 0, caso contrário obteremos a resposta false.
- Nesta parte, vemos o seguinte loop:
Você pode ver que há uma execução no código em saltos de 4 que são divididos em:
- lhs – O valor do resultado da operação
- fn – Função qualquer a ser executada
- arg1 – A primeira variável
- arg2 – A segunda variável
Além disso, temos uma função de criação, execução e salvamento do valor do resultado, então basicamente este código cria para nós todo o resto da lógica de forma dinâmica. Eu recomendo que você examine o código usando um depurador e veja por si mesmo como as funções são criadas durante a execução.
Claro que vai demorar muito para testar tal exercício em um depurador, então pensei no seguinte "atalho" - usaremos o console para imprimir todas as operações e testar o resultado.
Foi assim que cheguei aos seguintes comandos, adicioneo-os ao seu código e execute-os:
`
console.log(i, env[lhs], env[fn], arg1 + "=" + env[arg1], arg2 + "=" + env[arg2]); console.log("-".repeat(50));
`
Olhe para o console e você verá as operações sendo realizadas:
Você pode ver primeiro que há uma criação de letras e números para a criação de palavras como a palavra return que será usada por nós nas funções abaixo.
Você pode então ver a função sha-256 que é executada em nossa senha no formato bytes array e o resultado é:
Então, se nós mesmos testarmos usando python:
59 na base hexadecimal é equivalente a 89 na base decimal (O primeiro caractere emconsole) Então nós temos a mesma coisa.
Você pode então ver uma ação XOR entre o sha256 de nossa senha contra sha256 calculado pelo desafio e, em seguida, fazer ou com a variável H que é o nosso valor de retorno
Devemos continuar com este método até percorrermos todos os sha256 e chegarmos ao último resultado deixado em h, além do fato de termos que retornar h com o valor 0, podemos concluir que a única maneira de conseguir isso é realizando as operações matemáticas no mesmo h-hash.
Para chegar ao hash, pegaremos todos os caracteres que comparamos com eles usando o seguinte código:
`
let output = [] let cursor = 964; if ( i == cursor ) { cursor += 20 output.push(env[arg2]); }
`
E vamos receber:
Se pegarmos os primeiros 32 caracteres, que é basicamente a quantidade de caracteres em sha256:
`
output = [ "%.2x" % i for i in [230, 104, 96, 84, 111, 24,205, 187, 205, 134, 179, 94, 24, 181, 37, 191, 252, 103, 247, 114, 198, 80, 206, 223, 227, 255, 122, 0, 38, 250, 29, 238]] "".join(output)
`
Vamos colocar em nosso código Python e obter: e66860546f18cdbbcd86b35e18b525bffc67f772c650cedfe3ff7a0026fa1dee
Usaremos a pista que aparece no início do desafio, e vamos procurar no Google o sha256 que saiu: TODO: check if they can just use Google to get the password once they understand how this works.
E vamos receber:
Vamos inserir a senha, resolvendo assim o desafio: