// 05 intermediário call relativo · offset

Redirect de
Função

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?

~/cracklab/05-redirect-funcao
╔══════════════════════════════════╗
║ TERMINAL DE ACESSO REMOTO ║
╚══════════════════════════════════╝
Usuario: hacker
Senha: qualquer
╔══════════════════════════════════╗
║ ACESSO NEGADO ║
║ Credenciais invalidas. ║
╚══════════════════════════════════╝
// conceitos necessários
call relativo E8
A instrução 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.
offset relativo
O CPU calcula:
destino = RIP_após_call + offset
Onde RIP_após_call = endereço do call + 5. Mudar o offset muda o destino.
struct.pack python
struct.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?

// solução passo a passo
01
Mapeia as funções
bash
objdump -d ./challenge | grep -E "<access_denied>|<access_granted>|<authenticate>"
Resultado:
output
4011b6 <access_denied>
401204 <access_granted>
401278 <authenticate>
02
Encontra os call sites em authenticate
bash
objdump -d ./challenge | grep "call.*access"
Você vai ver os dois calls:
asm
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.

03
Calcula o novo offset
A CPU calcula o destino assim: destino = (endereço_do_call + 5) + offset_signed
// cálculo do offset
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 — calcula o offset
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
04
Aplica o patch
radare2
r2 -w ./challenge
s 0x4012d1
wx e8 2e ff ff ff
q
bash — com dd
printf '\xe8\x2e\xff\xff\xff' | dd of=./challenge bs=1 seek=$((0x4012d1)) conv=notrunc 2>/dev/null
05
Verifica e roda
bash
# deve mostrar: call 401204 <access_granted>
objdump -d ./challenge | grep "4012d1"
~/cracklab/05-redirect-funcao $ ./challenge
Usuario: qualquer
Senha: qualquer
╔══════════════════════════════════╗
║ ACESSO AUTORIZADO ║
║ Bem-vindo, operador. ║
║ > FLAG{redirect_the_call} ║
╚══════════════════════════════════╝
// a matemática do call relativo
// como a CPU executa um call E8
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ções pra explorar
variaçãoondeo que faz
redirect no call0x4012d1 — dentro de authenticateRedireciona access_denied → access_granted. A solução do writeup.
redirect no main0x40137f — mainRedireciona o call de authenticate direto pra access_granted. A função nem executa.
5 NOPs0x4012d1NOP 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?