Análise de Reversão de Código com Codificação Base64 Modificada

Ao iniciar a análise de um binário sem empacotamento em um ambiente de reversão, como o IDA Pro, uma das primeiras etapas é inspecionar as strings internas. No caso em questão, a varredura (shift+F12) revelou duas sequências de caracteres peculiares, notavelmente terminadas por ==. Esse padrão é um forte indicativo de codificação Base64.

A investigação da função principal (main) do programa demonstrou que ele solicita uma entrada ao usuário, designada como "flag". Posteriormente, essa entrada é submetida a um processo de tratamento e validação, comparando o resultado com um valor pré-definido para determinar a correção da flag.

Prosseguindo para a função crítica identificada, sub_401570, observamos a implementação de um algoritmo de codificação. Esta função é responsável por transformar uma sequência de bytes de entrada em uma string codificada em Base64, mas com uma variação importante: o uso de um alfabeto de caracteres diferente do padrão.

A lógica de sub_401570, em sua essência, segue os passos gerais da codificação Base64:

  1. Cálculo do Comprimento: Determina o tamanho da string de entrada.
  2. Alocação de Memória: Baseado no comprimento da entrada, calcula o espaço necessário para a string codificada e aloca dinamicamente essa memória.
  3. Processamento em Blocos: A string de entrada é processada em blocos de três bytes, que são convertidos em quatro caracteres Base64.
  4. Mapeamento de Caracteres: Cada valor de 6 bits resultante da divisão dos bytes de entrada é mapeado para um caractere específico de um alfabeto personalizado.
  5. Adição de Preenchimento (Padding): Caso o comprimento da entrada não seja um múltiplo de três, caracteres = são adicionados ao final da string codificada, conforme a regra do Base64.
  6. Retorno: O endereço da string Base64 modificada é retornado.

A seguir, uma representação em C da lógica de codificação utilizando um alfabeto variante, similar ao que foi observado no disassemb. Esta implementação ilustra como a string de entrada é convertida para a forma Base64, empregando um conjunto de caracteres não padrão para os 64 valores possíveis.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// O alfabeto personalizado Base64, derivado da análise do binário.
// Este é o 'aQvejafhmuyjbac' do disassembly.
const char *ALFABETO_BASE64_CUSTOM = "qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD";

/**
 * @brief Codifica uma string de entrada utilizando um algoritmo Base64 com um alfabeto personalizado.
 *
 * @param dados_originais Ponteiro para a string de entrada a ser codificada.
 * @return Um ponteiro para uma nova string alocada dinamicamente contendo os dados codificados.
 *         Retorna NULL em caso de falha na alocação de memória.
 */
char* codificar_base64_modificado(const char *dados_originais) {
    if (!dados_originais) return NULL;

    size_t len_origem = strlen(dados_originais);
    // Calcula o tamanho necessário para a string de saída.
    // Para cada 3 bytes de entrada, 4 bytes de saída são gerados.
    // O '+2' e a divisão por 3 garantem o arredondamento para cima,
    // e o resultado é multiplicado por 4.
    size_t len_saida = 4 * ((len_origem + 2) / 3);
    char *buffer_codificado = (char *)malloc(len_saida + 1); // +1 para o terminador nulo
    if (!buffer_codificado) return NULL;
    buffer_codificado[len_saida] = '\0'; // Garante terminação nula

    size_t idx_entrada = 0;
    size_t idx_saida = 0;
    unsigned char bloco_3_bytes[3];
    unsigned char indices_6_bits[4];

    while (idx_entrada < len_origem) {
        // Pega até 3 bytes da entrada. Se não houver, preenche com 0.
        bloco_3_bytes[0] = dados_originais[idx_entrada++];
        bloco_3_bytes[1] = (idx_entrada < len_origem) ? dados_originais[idx_entrada++] : 0;
        bloco_3_bytes[2] = (idx_entrada < len_origem) ? dados_originais[idx_entrada++] : 0;

        // Divide os 3 bytes (24 bits) em 4 grupos de 6 bits.
        indices_6_bits[0] = (bloco_3_bytes[0] & 0xFC) >> 2;
        indices_6_bits[1] = ((bloco_3_bytes[0] & 0x03) << 4) | ((bloco_3_bytes[1] & 0xF0) >> 4);
        indices_6_bits[2] = ((bloco_3_bytes[1] & 0x0F) << 2) | ((bloco_3_bytes[2] & 0xC0) >> 6);
        indices_6_bits[3] = (bloco_3_bytes[2] & 0x3F);

        // Mapeia os índices de 6 bits para caracteres usando o alfabeto personalizado.
        buffer_codificado[idx_saida++] = ALFABETO_BASE64_CUSTOM[indices_6_bits[0]];
        buffer_codificado[idx_saida++] = ALFABETO_BASE64_CUSTOM[indices_6_bits[1]];
        buffer_codificado[idx_saida++] = ALFABETO_BASE64_CUSTOM[indices_6_bits[2]];
        buffer_codificado[idx_saida++] = ALFABETO_BASE64_CUSTOM[indices_6_bits[3]];
    }

    // Gerencia o preenchimento (padding) com '='.
    int bytes_faltantes = len_origem % 3;
    if (bytes_faltantes == 1) { // Faltam 2 bytes para um bloco completo de 3
        buffer_codificado[len_saida - 2] = '=';
        buffer_codificado[len_saida - 1] = '=';
    } else if (bytes_faltantes == 2) { // Falta 1 byte para um bloco completo de 3
        buffer_codificado[len_saida - 1] = '=';
    }

    return buffer_codificado;
}

Para decodificar a string codificada fornecida pelo programa (que seria o resultado da codificação da flag correta), é necessário reverter essa transformação. Primeiramente, é preciso mapear os caracteres do alfabeto variante de volta para o alfabeto Base64 padrão. Somente após essa normalização, a decodificação Base64 tradicional pode ser aplicada.

O script Python abaixo realiza este processo:

import base64

# String codificada encontrada no binário, que representa a flag esperada.
string_alvo_codificada = "5Mc58bPHLiAx7J8ocJIlaVUxaJvMcoYMaoPMaOfg15c475tscHfM/8=="

# O alfabeto personalizado que a função de codificação do binário utiliza.
# Este é o 'aQvejafhmuyjbac' que foi identificado no disassembly.
alfabeto_modificado_nss = "qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD"

# O alfabeto padrão da codificação Base64, para o qual normalizaremos a string.
alfabeto_base64_padrao = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

# Cria uma tabela de tradução que mapeia cada caractere do alfabeto modificado
# para seu equivalente no alfabeto Base64 padrão.
tabela_conversao = str.maketrans(alfabeto_modificado_nss, alfabeto_base64_padrao)

# Aplica a tradução à string codificada, convertendo-a para uma forma Base64 padrão.
string_normalizada_base64 = string_alvo_codificada.translate(tabela_conversao)

# Decodifica a string normalizada usando a função b64decode do módulo base64 do Python.
# O resultado é uma sequência de bytes, que então é decodificada para uma string UTF-8.
dados_decodificados = base64.b64decode(string_normalizada_base64).decode('utf-8')

# Imprime a flag decodificada.
print(f"A flag decodificada é: {dados_decodificados}")

A execução do script Python revela a flag oculta:

A flag decodificada é: NSSCTF{a8d4347722800e72e34e1aba3fe914ae}

Tags: Base64 ReverseEngineering CTF IDAPro Python

Publicado em 8-26 12:57