// 09 iniciante-intermediário patch em .rodata

String
Patching

A lógica não tem falha. Mas o que ela imprime — esse você pode mudar.

Terminal seguro. Autenticador. Que sempre nega.
Não tem comparação pra quebrar. Não tem jump pra inverter.
O programa simplesmente imprime ACESSO NEGADO — sempre.

Mas a mensagem existe no binário. E existe outra mensagem logo ao lado.

~/crackmes/09-string-patching
$ ./challenge
╔══════════════════════════════════════╗
║ TERMINAL SEGURO — AUTENTICADOR ║
╚══════════════════════════════════════╝

Identificador: qualquer

>> ACESSO NEGADO. SAIA.
conceitos
Patch de dados
Modificar strings no .rodata sem tocar em nenhuma instrução. A lógica do programa permanece intacta — só o conteúdo da mensagem muda.
Tamanho importa
Strings em .rodata são contíguas. A nova string deve ter o mesmo comprimento da original — ou menor com padding nulo — pra não corromper o binário.
Redirecionamento de ponteiro
Alternativa mais cirúrgica: trocar o endereço que o código usa para apontar para a outra string, sem alterar nenhum byte de dados.

Use strings -o ./challenge para ver as strings com seus offsets no arquivo.
Procura ACESSO NEGADO. Agora procura ACESSO CONCEDIDO.
O que você precisaria copiar de onde pra onde?

solução
// passo 01
Localiza as strings no arquivo
bash
strings -o ./challenge | grep "ACESSO"
output
  4096  >> ACESSO NEGADO. SAIA.        
  4128  >> ACESSO CONCEDIDO. BEM-VINDO.
Ambas têm exatamente 32 bytes (com padding nulo). Mesmo comprimento — troca 1:1 sem deslocar nada.
// passo 02
Patch com Python (mais seguro)
python
binary = bytearray(open('./challenge', 'rb').read())

old = b'>> ACESSO NEGADO. SAIA.        \x00'
new = b'>> ACESSO CONCEDIDO. BEM-VINDO.\x00'

idx = binary.find(old)
if idx != -1:
    binary[idx:idx+len(new)] = new
    open('./challenge', 'wb').write(binary)
    print(f"Patcheado em offset 0x{idx:x}")
// passo 03
Patch com radare2
r2
r2 -w ./challenge
iz                  # lista strings com endereços
s 0x402000          # vai pro endereço de MSG_DENIED
w >> ACESSO CONCEDIDO. BEM-VINDO.
q
// passo 04
Verifica e roda
bash
strings ./challenge | grep "ACESSO"
# deve mostrar CONCEDIDO onde estava NEGADO
./challenge
output
Identificador: qualquer

>> ACESSO CONCEDIDO. BEM-VINDO.
FLAG{patch_the_string_not_the_logic}
// o que mudou no binário
antes do patch:

  .rodata offset 0x2000: ">> ACESSO NEGADO. SAIA.        \0"  ← main imprime isso
  .rodata offset 0x2020: ">> ACESSO CONCEDIDO. BEM-VINDO.\0"  ← nunca executado

depois do patch:

  .rodata offset 0x2000: ">> ACESSO CONCEDIDO. BEM-VINDO.\0"  ← main imprime isso
  .rodata offset 0x2020: ">> ACESSO CONCEDIDO. BEM-VINDO.\0"  ← inalterado

  nenhuma instrução foi alterada.

Por que o tamanho importa: strings em .rodata são contíguas na memória. Se a nova string fosse maior, ela invadiria a próxima string e corromperia o binário. Strings com padding nulo permitem a troca 1:1 segura. Em binários reais, sempre verifique o espaço disponível antes de patchear.

alternativas
MétodoO que mudaRisco
sobrescrever bytesConteúdo da string em .rodataCorrupção se tamanho errado
redirecionar ponteiroOffset no lea rdi,[rip+X]Baixo — string original intacta
injetar em paddingNova string em area vazia do ELFMédio — depende do layout

Se MSG_DENIED e MSG_GRANTED tivessem comprimentos diferentes, qual das abordagens (sobrescrever bytes vs redirecionar ponteiro) ainda funcionaria sem corrupção? Por quê?