CyBRICS Matreshka — Write-up

Igo0r
8 min readJul 24, 2019

Bom, esse é o primeiro write-up que escrevo. provavelmente a terceira ou quarta vez que tiro um tempo para escrever sobre esse mundo e também é a primeira vez “publicamente” mas enfim, desculpa qualquer erro e vamo lá:

CyBRICS — https://cybrics.net — foi um CTF que rolou dia 20 onde os países que participam do BRICS podiam jogar e ter uma chance de ir jogar na Russia, joguei pela RATF e foi muito divertido, de certa forma conseguimos nos classificar mas ninguém tem grana pra ir aushahusha, uma coisa chata pra todo time de países que não tinham a viagem paga mas enfim… Tô aqui pra falar da chall de Reverse que rolou chamada Matreshka.

“Mathreshka” são aquelas bonecas russas que tem uma dentro da outra tlg

O desafio vem com um código java compilado e um arquivo .bin

Decompilando o código java em http://www.javadecompilers.com/ ( Eu usei o “CFR — very good and well-supported decompiler for modern Java ” como decompiler ) temos esse código aqui:

Que aparentemente faz umas encriptações usando os arrays, e dependendo do valor deles gera um arquivo chamado “stage2.bin"

/*
* Decompiled with CFR 0.145.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.security.Key;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
class Code2 {
Code2() {
}
public static byte[] decode(byte[] arrby, String string) throws Exception {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] arrby2 = string.getBytes();
DESKeySpec dESKeySpec = new DESKeySpec(arrby2);
SecretKey secretKey = secretKeyFactory.generateSecret(dESKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(2, secretKey);
byte[] arrby3 = cipher.doFinal(arrby);
return arrby3;
}
public static byte[] encode(byte[] arrby, String string) throws Exception {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] arrby2 = string.getBytes();
DESKeySpec dESKeySpec = new DESKeySpec(arrby2);
SecretKey secretKey = secretKeyFactory.generateSecret(dESKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(1, secretKey);
byte[] arrby3 = cipher.doFinal(arrby);
return arrby3;
}
public static void main(String[] arrstring) throws Exception {
String string = "matreha!";
byte[] arrby = Code2.encode(System.getProperty("user.name").getBytes(), string);
byte[] arrby2 = new byte[]{76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18};
for (int i = 0; i < arrby2.length; ++i) {
if (arrby2[i] == arrby[i]) continue;
System.out.println("No");
return;
}
File file = new File("data.bin");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] arrby3 = new byte[(int)file.length()];
fileInputStream.read(arrby3);
fileInputStream.close();
byte[] arrby4 = Code2.decode(arrby3, System.getProperty("user.name"));
FileOutputStream fileOutputStream = new FileOutputStream("stage2.bin");
fileOutputStream.write(arrby4, 0, arrby4.length);
fileOutputStream.flush();
fileOutputStream.close();
}
}

Analisando essa parte do código dá pra notar que ele usa um System.getProperty(“user.name”).getBytes() como um parâmetro ( isso vai ser o nome do user em que você estiver logado) e a string “matreha!” que é a chave para criar o array encriptado em “DES”. (dá uma googlada nisso se quiser entender melhor)

E depois ele compara os valores:

String string = "matreha!";
byte[] arrby = Code2.encode(System.getProperty("user.name").getBytes(), string);
byte[] arrby2 = new byte[]{76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18};
for (int i = 0; i < arrby2.length; ++i) {
if (arrby2[i] == arrby[i]) continue;
else
System.out.println("No");
return;
}

Se seguiu os mesmo passos que eu, no seu código não vai ter o else, pq eu só coloquei pra mostrar que é a mesma coisa sem… Se for igual ele vai continuar o looping e dps criar o arquivo, caso não for ele vai printar “não” e acabar o programa, de acordo com as “leis universais do java”…

Como “76 , -99, 37 , 75…. ( os valores do array 2 )” provavelmente não vão ser o user.name da nossa maquina, ele vai retornar o “No”:

E só de curiosidade “Pichau” é 80 105 99 104 97 117 em ascii .

Analisando o código de novo dá pra decodificar o que está no “arrby2” , basicamente eu copiei umas partes do código original, joguei pra uma outra pasta, comentei o array que usava meu usúario e decodifiquei o arrby2 usando a mesma chave ( string = “matreha!” ) e coloquei pra printar

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.security.Key;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
class Code2 {

Code2() {
}

public static byte[] decode(byte[] arrby, String string) throws Exception {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] arrby2 = string.getBytes();
DESKeySpec dESKeySpec = new DESKeySpec(arrby2);
SecretKey secretKey = secretKeyFactory.generateSecret(dESKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(2, secretKey);
byte[] arrby3 = cipher.doFinal(arrby);
return arrby3;
}

public static void main(String[] arrstring) throws Exception {
String string = "matreha!";
// byte[] arrby = Code2.encode(System.getProperty("user.name").getBytes(), string);
byte[] arrby2 = new byte[]{76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18};

byte arrbyDEc[] = Code2.decode(arrby2, string);

for(int y=0;y<arrbyDEc.length;y++){
System.out.print(arrbyDEc[y] + " ");
}

}
}

Output:

108 101 116 116 114 101 104 97 em ascii é lettreha.

Então no “código original” criei uma variável contendo “lettreha” e usei ela no lugar de System.getProperty(“user.name”).getBytes(), usei o “username.getBytes() , (onde username é o nome da variável cm lettreha (vcs vao ver no código… ) )” e tbm passando a string com “matreha!” ( que é a chave), do mesmo jeito que se era encriptado no “código original” . E tbm com a linha do array que pegava o meu user comentada.

/*
* Decompiled with CFR 0.145.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.security.Key;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
class Code2 {
Code2() {
}
public static byte[] decode(byte[] arrby, String string) throws Exception {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] arrby2 = string.getBytes();
DESKeySpec dESKeySpec = new DESKeySpec(arrby2);
SecretKey secretKey = secretKeyFactory.generateSecret(dESKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(2, secretKey);
byte[] arrby3 = cipher.doFinal(arrby);
return arrby3;
}
public static byte[] encode(byte[] arrby, String string) throws Exception {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
byte[] arrby2 = string.getBytes();
DESKeySpec dESKeySpec = new DESKeySpec(arrby2);
SecretKey secretKey = secretKeyFactory.generateSecret(dESKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(1, secretKey);
byte[] arrby3 = cipher.doFinal(arrby);
return arrby3;
}
public static void main(String[] arrstring) throws Exception {
String string = "matreha!";

String username = "lettreha";
// byte[] arrby = Code2.encode(System.getProperty("user.name").getBytes(), string);
byte[] arrby = Code2.encode(username.getBytes(), string);
byte[] arrby2 = new byte[]{76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18};
for (int i = 0; i < arrby2.length; ++i) {
if (arrby2[i] == arrby[i]) continue;
System.out.println("No");
return;
}
File file = new File("data.bin");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] arrby3 = new byte[(int)file.length()];
fileInputStream.read(arrby3);
fileInputStream.close();
byte[] arrby4 = Code2.decode(arrby3,username);
FileOutputStream fileOutputStream = new FileOutputStream("stage2.bin");
fileOutputStream.write(arrby4, 0, arrby4.length);
fileOutputStream.flush();
fileOutputStream.close();
}
}

Executando assim você consegue o aquivo stage2.bin

Dando um file nele dá pra ver que é um elf 64 bits em go etc

Se eu tento executar o arquivo recebo um “Fail” na cara .

Usando o Ghidra e analisando o código é possivel perceber um if e um trecho de código que nunca é executado por que o if não passa por lá

O offset da instrução JNZ é o 0x00476126. O da instrução que chama a “runtime.printlock()”, depois que ele entra nas condições do if, é o 0x00476181.

Então vamo colocar isso no gdb e pular pra lá pra ver no que dá

Comandos :

gdb -q ./stage2.bin

gdb-peda$ info functions

gdb-peda$ disas main.main

Como dá pra ver, “disasemblei” a main e destacado ali está o endereço da instrução jne, onde vamos dar por um breakpoint pra fazer as magia negra

E aqui, o endereço onde se tem a call e o resto do código que antes não era executado:

Damos um break nos dois endereços

Rodamos, assim que ele parar em 0x00476126, damos um jump para 0x00476181 e continuamos o programa

gdb-peda$ r

Executa o binario e para no primeiro breakpoint

gdb-peda$ jump * 0x00476181

Pula para 0x00476181 e já acontece o breakpoint que colocamos em 0x00476181

Digitando “c” e dando enter ele vai continuar a execução dali, então nesse caso ele fala que decodificou o payload rsrs e também cria um arquivo chamado result.pyc

result.pyc é um python compilado, dá pra descompilar ele por aqui: https://www.toolnb.com/tools-lang-en/pyc.html

Ai cê decompila e tem esse código de volta:

# uncompyle6 version 3.3.0
# Python bytecode 3.7 (3394)
# Decompiled from: Python 2.7.5 (default, Apr 9 2019, 14:30:50)
# [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
# Embedded file name: ./1.py
# Size of source mod 2**32: 439 bytes
def decode(data, key):
idx = 0
res = []
for c in data:
res.append(chr(c ^ ord(key[idx])))
idx = (idx + 1) % len(key)
return resflag = [
40, 11, 82, 58, 93, 82, 64, 76, 6, 70, 100, 26, 7, 4, 123, 124, 127, 45, 1, 125, 107, 115, 0, 2, 31, 15]
print('Enter key to get flag:')
key = input()
if len(key) != 8:
print('Invalid len')
quit()
res = decode(flag, key)
print(''.join(res))

Bom, aqui temos a flag no array flag rs, que aparentemente é encriptado em xor com uma chave de 8 caracteres e “cybrics{” , que é o padrão das flags do cybrics tem 8 caracteres, então vamos usar o famigerado cyberChef pra decifrar a verdadeira chave, puxamos o From Decimal, XOR e o Head, colocamos a key, cybrics{ , ajustamos o head para “Nothing” e 8 pra ele fazer só com os 8 primeiros chars… e o From Decimal é pra convertes os números em letras

Ai temos Kr0H4137 , que é a nossa chave, agora é só bloquear o head no cyberChef e colocar Kr0H4137 como chave:

E temos

cybrics{M4TR35HK4_15_B35T} !!@!

Foi isso, espero que tenha entendido e que tenha te acrescentado em alguma coisa… é importante que faça pra entender melhor, se quiser é claro.

Pretendo passar isso pra inglês e começar a escrever sobre essas coisas loucas conforme o tempo… Acho que nesse write-up as bases não ficaram muito explicadas pra quem ta realmente começando agora, não que eu seja o the b34st_666 hacker mas eu pretendo escrever umas coisas mais detalhadas sobre as bases também, com outros desafios… é isso… até a próxima :)

--

--