A análise de exploração de tipos de dados em CTF frequantemente envolve a inspeção de estruturas de memória e o uso de técnicas de força bruta quando o algoritmo apresenta complexidade baixa. A seguir, são apresentados dois métodos comuns para resolver desafios desse tipo.
Inspeção de segmentos de memória em binários ELF
Ao analisar um binário compilado, é fundamental entender a organização de seus segmentos:
- .data: Armazena variáveis globais e estáticas inicializadas.
- .bss: Reserva espaço para variáveis globais e estáticas não inicializadas.
- .rodata: Contém dados somente leitura, como constantes e strings.
- Blocos unk_*: Representam áreas de dados globais frequentemente associadas a arrays ou estruturas não tipadas explicitamente.
- Variáveis byte_*: Indicam que um determinado endereço é usado para armazenar um único byte.
No desafio analisado, o bloco unk_601060 contém os dados de um tabuleiro de Sudoku. A função sub_400881 substitui os caracteres '#' nesse bloco pela sequência armazenada na variável v7, que deve ser "0421421430".
Geração da sequência por travessia de árvore binária
A variável v7 é obtida a partir do valor de entrada v5, processado por duas funções (sub_400758 e sub_400807) que implementam uma travessia em árvore binária.
A função sub_400758 constrói uma árvore binária a partir de v5 utilizando a travessia em pré-ordem. Cada nó é representado por um array de três elementos:
v4[0]: Valor armazenado no nó.v4[1]ev4[2]: Ponteiros para os nós filhos esquerdo e direito, respectivamente.
A implementação equivalente em C para gerar a travessia em pré-ordem produz a sequência "0137849256":
#include <iostream>
using namespace std;
void gerar_sequencia(int inicio, int limite) {
if (inicio > limite) return;
cout << inicio << " ";
gerar_sequencia(2 * inicio + 1, limite);
gerar_sequencia(2 * (inicio + 1), limite);
}
int main() {
gerar_sequencia(0, 9);
return 0;
}
}
Para obter v7, aplica-se a travessia em-ordem sobre a árvore, mapeando os índices resultantes de 0 a 9 para a sequência "0137849256". A solução em Python abaixo realiza esse mapeamento:
percurso_inordem = [7, 3, 8, 1, 9, 4, 0, 5, 2, 6]
codigo_alvo = [0, 4, 2, 1, 4, 2, 1, 4, 3, 0]
resultado = []
for i in range(10):
posicao = percurso_inordem.index(i)
resultado.append(chr(codigo_alvo[posicao] + ord('0')))
print(''.join(resultado))
Técnica de força bruta para resolução do Sudoku
Uma abordagem alternativa para preencher as células vazias do Sudoku (#) é utilizar força bruta. O código abaixo define uma função de validação e realiza uma busca exaustiva por soluções válidas:
#include <iostream>
#include <vector>
using namespace std;
const int TAMANHO_TABULEIRO = 5;
bool validar_sudoku(const vector<char>& tabuleiro) {
for (int linha = 0; linha < TAMANHO_TABULEIRO; ++linha) {
for (int col1 = 0; col1 < TAMANHO_TABULEIRO; ++col1) {
for (int col2 = col1 + 1; col2 < TAMANHO_TABULEIRO; ++col2) {
if (tabuleiro[linha * TAMANHO_TABULEIRO + col1] == tabuleiro[linha * TAMANHO_TABULEIRO + col2])
return false;
if (tabuleiro[col1 * TAMANHO_TABULEIRO + linha] == tabuleiro[col2 * TAMANHO_TABULEIRO + linha])
return false;
}
}
}
return true;
}
int main() {
vector<char> grade = {
'1', '4', '#', '2', '3',
'3', '0', '#', '1', '#',
'0', '#', '2', '3', '#',
'#', '3', '#', '#', '0',
'4', '2', '#', '#', '1'
};
vector<int> posicoes_vazias;
for (int i = 0; i < grade.size(); ++i) {
if (grade[i] == '#') posicoes_vazias.push_back(i);
}
const char caracteres[] = {'0', '1', '2', '3', '4'};
const int total_opcoes = 5;
const int celulas_vazias = posicoes_vazias.size();
for (int a = 0; a < total_opcoes; ++a)
for (int b = 0; b < total_opcoes; ++b)
for (int c = 0; c < total_opcoes; ++c)
for (int d = 0; d < total_opcoes; ++d)
for (int e = 0; e < total_opcoes; ++e)
for (int f = 0; f < total_opcoes; ++f)
for (int g = 0; g < total_opcoes; ++g)
for (int h = 0; h < total_opcoes; ++h)
for (int i = 0; i < total_opcoes; ++i)
for (int j = 0; j < total_opcoes; ++j) {
grade[posicoes_vazias[0]] = caracteres[a];
grade[posicoes_vazias[1]] = caracteres[b];
grade[posicoes_vazias[2]] = caracteres[c];
grade[posicoes_vazias[3]] = caracteres[d];
grade[posicoes_vazias[4]] = caracteres[e];
grade[posicoes_vazias[5]] = caracteres[f];
grade[posicoes_vazias[6]] = caracteres[g];
grade[posicoes_vazias[7]] = caracteres[h];
grade[posicoes_vazias[8]] = caracteres[i];
grade[posicoes_vazias[9]] = caracteres[j];
if (validar_sudoku(grade)) {
cout << "Solução encontrada: ";
for (char c : grade) cout << c;
cout << endl;
}
}
return 0;
}
Ao combinar a análise estática do binário com técnicas de exploração de dados e força bruta, é possível resolver desafios de CTF que envolvem validação de tipos de dados e estruturas complexas.