Page MenuHomedesp's stash

cryptoverse22.md
No OneTemporary

cryptoverse22.md

### 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 <stdio.h>
#include <xmmintrin.h>
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 interesting 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:

File Metadata

Mime Type
text/x-python
Expires
Mon, Jun 16, 6:59 PM (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
60/40/21cee68713e1f0c87483d10ea66a

Event Timeline