O call não guarda o nome da função. Só a distância. Você troca a distância.
Terminal de acesso remoto. Usuário e senha desconhecidos.
Existe uma função access_granted() em algum lugar no binário.
Ela nunca é chamada com suas credenciais.
E se você mudasse quem é chamado?
call em x86-64 usa 5 bytes: e8 + 4 bytes de offset relativo. Não armazena o endereço absoluto — só a distância até o destino.destino = RIP_após_call + offsetstruct.pack('<i', valor) converte um inteiro com sinal em 4 bytes little-endian — exatamente o formato que o x86 espera no offset do call.
Abre o disassembly e procura as duas funções: access_denied e access_granted.
Anota os endereços de cada uma.
Agora procura onde access_denied é chamada dentro de authenticate.
O que você precisaria mudar pra esse call ir parar em access_granted?
objdump -d ./challenge | grep -E "<access_denied>|<access_granted>|<authenticate>"4011b6 <access_denied> 401204 <access_granted> 401278 <authenticate>
objdump -d ./challenge | grep "call.*access"4012c5: e8 3a ff ff ff call 401204 <access_granted> ← caminho válido 4012d1: e8 e0 fe ff ff call 4011b6 <access_denied> ← seu alvo
O call em 0x4012d1 está chamando access_denied.
Você quer que ele chame access_granted em vez disso.
Os bytes e0 fe ff ff são o offset atual. Você vai recalcular e reescrever.
destino = (endereço_do_call + 5) + offset_signed
endereço do call: 0x4012d1 próxima instrução: 0x4012d1 + 5 = 0x4012d6 destino desejado: 0x401204 (access_granted) offset = destino - próxima_instrução offset = 0x401204 - 0x4012d6 = -0xD2 -0xD2 em i32 little-endian = 2E FF FF FF novo call: e8 2e ff ff ff → call 401204 <access_granted>
python3 -c " import struct call_addr = 0x4012d1 next_instr = call_addr + 5 target = 0x401204 offset = target - next_instr b = struct.pack('<i', offset) print(f'patch bytes: e8 {b.hex()}') " # patch bytes: e8 2effffff
r2 -w ./challenge s 0x4012d1 wx e8 2e ff ff ff q
printf '\xe8\x2e\xff\xff\xff' | dd of=./challenge bs=1 seek=$((0x4012d1)) conv=notrunc 2>/dev/null# deve mostrar: call 401204 <access_granted> objdump -d ./challenge | grep "4012d1"
instrução call em 0x4012d1: e8 [offset 4 bytes] CPU executa assim: RIP = 0x4012d1 + 5 = 0x4012d6 (avança pro próximo) RIP = RIP + offset_signed (aplica o salto) para ir a 0x401204: offset = 0x401204 - 0x4012d6 = -0xD2 -0xD2 em i32 little-endian = 2E FF FF FF
Guarda esse cálculo — vale pra qualquer call relativo em x86-64.
| variação | onde | o que faz |
|---|---|---|
| redirect no call | 0x4012d1 — dentro de authenticate | Redireciona access_denied → access_granted. A solução do writeup. |
| redirect no main | 0x40137f — main | Redireciona o call de authenticate direto pra access_granted. A função nem executa. |
| 5 NOPs | 0x4012d1 | NOP o call de access_denied. Nenhuma das duas é chamada. Funciona mas output fica estranho. |
Se o binário tivesse PIE ativado (endereços aleatorizados a cada execução), esse patch ainda funcionaria? O que mudaria no cálculo?