Page MenuHomedesp's stash

No OneTemporary

diff --git a/ctfs/comments/magpie23.md b/ctfs/comments/magpie23.md
new file mode 100644
index 0000000..e9c4f3a
--- /dev/null
+++ b/ctfs/comments/magpie23.md
@@ -0,0 +1,465 @@
+### missing flag
+
+chall was basically just extracting this from the .docm ~~which ms word totally doesnt plaster warnings everywhere for~~
+```vba
+-------------------------------------------------------------------------------
+VBA MACRO Module1.bas
+in file: word/vbaProject.bin - OLE stream: 'VBA/Module1'
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+Sub galf()
+ Dim ajaofaer, faheior, vusirht, iruthvb, akjrhoeifr, nvritwh
+ vusirht = Array("m", "a", "g", "p", "i", "e", "{", "f", "a", "k", "e", "f", "l", "a", "g", "}")
+ iruthvb = 0
+ For Each urhuerb In vusirht
+ Selection.TypeText Text:=Asc(vusirht(iruthvb)) * 10
+ iruthvb = iruthvb + 1
+ Next
+
+
+End Sub
+```
+which just says they pasted the ascii of the flag chars in the document itself with an arbitrary 0 as delimiter
+
+~~why would anyone ever make such an annoying way of encoding things zzz the ordinals of the flag chars literally have 0s in it and its not even novel~~
+
+anyways with such a *unique* encoding method the fastest way was to manually extract it by identifying the range of the decimals that were likely a flag char *one by one*
+
+sooo lol `magpie{why_d1d_1_0p3n_th1s_m4cr0_TWT}`
+
+* * *
+
+*as you can see im totally not upset at the challs in this ctf*
+
+most of the challs got wiped coz of how easy it is by the time i could work on the ctf since i came in like 30 mins late compared to my other teammates
+
+and the rest was just generally a nightmare of some sort lol
+
+i actually co-solved more challs than those ive written here but alas the parts i worked on on those were just either not that interesting or plain tedious
+
+
+### take a wild guess
+
+honestly one of the nicer challs in this ctf tbh
+
+like it actually feels like a normal beginner chall for once and not either blatantly obvious or with entirely artifically inflated difficulty
+
+anyways the main func is where everythings living in - it looks kinda yikes for a beginner chall on first glance with a ton of string operations, but we can easily infer the following from the decomp:
+
+ - its a flag checker (the signature if else success/failure msg prints)
+ - there exists inlined data that looks very similar to some sort of flag data to compare against at the top
+ - most of the operations operate on the input we gave, and only one single xor operation was done on the flag data before comparison
+
+using this, we can whip up a z3 script ~~which was probably overkill but i was also too sleepy to figure out the inverse of the xor operation myself lmao not to mention even with this i saw 40 as 0x40 and got confused for way too long anyway so i never trust my math anymore~~:
+
+```py
+data = b''.join([0x44446F4B466E7F4A.to_bytes(8, byteorder='little')
+, 0x1A657C6618611A4D.to_bytes(8, byteorder='little')
+, 0x5166454A52106E66.to_bytes(8, byteorder='little')
+, 0x5D6A7C651B69704D.to_bytes(8, byteorder='little')
+, 0x19656C4A19627064.to_bytes(8, byteorder='little')
+, 0x15187061.to_bytes(4, byteorder='little')])
+
+print(data.hex())
+
+from z3 import *
+
+b = [BitVec(f'byte {i}', 8) for i in range(44)]
+
+s = Solver()
+
+print(len(data))
+
+for i, d in enumerate(data):
+ s.add(d ^ b[i] == 40)
+ s.add(b[i] >= 48)
+ s.add(b[i] <= 122)
+
+
+s.check()
+
+model = s.model()
+
+
+data = ''
+for i in b:
+ if str(model[i]) != 'None':
+ data += chr(int(str(model[i])))
+
+print(data)
+```
+
+which looks suspiciously like base64
+
+and yep it is lmao we just bypassed the need to reverse all the base64 code
+
+`magpie{b45364_3ncryp710n-rul35!}`
+
+~~in hindsight the alphabet they gave there (`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`) shouldve been a good hint that its just base64 but lmao~~
+
+
+### satshell
+
+oh boy here we go time for the wackier challs
+
+this chall on its own isnt *too* bad but everything bundled together ended up being very annoying lol
+
+the wacky manual that was apparently wrong in a lot of places according to including the block diagram which was critical, ambiguous wording on how the operations shouldve actually been done, a given binary that basically was just entirely red herring since the logic was hidden in remote in a shared library, and the last straw being the math ended up just plain wrong on remote
+
+like this chall is pretty interesting in theory but the execution was genuinely just iffy lol
+
+we got the solve script a whole day before we actually solved it yet we just doubted ourselves on vector math until we realized it wasnt even our fault to begin with
+
+at least the chall author was genuinely really nice and apologetic wrt the
+
+and well its also our fault for not reaching out earlier anyway
+
+i do find it kinda funny how everyone was stuck on the math that the moment it was fixed 3 teams solved it instantly lol
+
+and we got sniped by 2 mins coz i couldnt find the right version of the script that wasnt just testing codes lmao
+
+im pretty sure we wouldve got the first blood too seeing from the discussion after the ctf coz most teams solved it day of the issue being fixed
+
+* * *
+
+anyway the gist is you are given a partially burnt manual about a satellite that has its own architecture ~~that has visible lorem ipsums btw which was kinda funny~~, and a client binary that interacts with the satellite
+
+the goal is to align the satellites using the information given in the client terminal by running debug shellcode directly on the satellite from analysing the manual
+
+and since the manual is partially burnt only a very limited amount of instructions are documented and no information about how the instructions are encoded was present aside from a block diagram of how the cpu is implemented
+
+~~which illustrated just the fully unburnt instructions btw with the rest of the partially visible instructions being impossible to encode with the scheme shown and also apparently had bugs according to [@Kevin](https://maplebacon.org/authors/Kevin/)~~
+
+so the approach is to figure out the encoding of the available instructions from the block diagram, get an arbitrary write primitive with them to the rotation registers since the available instructions modify only parts of the registers and the satellites already had some rotation to it, and perform vector math to align the satellites according to the graph given to us through the client
+
+parsing the client was really straightforward, and [@Kevin](https://maplebacon.org/authors/Kevin/) figured out the encoding pretty much instantly too, so all thats left was vector math
+
+the math we had to do was some sort of vector to angle conversion, since we have the 2 points given by linking the current satellite to the next, but we dont really know what angles they exactly want - the angles given could be any of the following:
+
+ - euler rotation angles
+ - 2D angles of one of the boards given through the client which encodes the (XZ, XY, YZ) planes
+ - 3D angles
+
+all of which are against the respective axis vectors since we need 3 angles
+
+3D angles sounded the most logical, except 3D angles have no direction so it would be [0, 180] and wouldnt have been possible to get an angle of the range [0, 360] as shown with the existing rotation angles of the satellite
+
+so we were just testing out every one of them and sanity checking if we were doing the math wrong ourselves, made some coding mistakes, or if that wasnt the right method at all
+
+which ended up taking us a good few hours until the next day, where i got pretty fed up and decided to open a ticket to ask
+
+and from the top of this comment you know what ended up happening lmao
+
+`magpie{1'v3-c0nn3c+3d-t43-d0ts}`
+
+ahhh it is what it is
+
+anyway heres the solve script
+
+```py
+from pwn import *
+import numpy as np
+from numpy import *
+import vg
+
+
+from scipy.spatial.transform import Rotation
+
+
+def normalize(v):
+ return v / np.linalg.norm(v)
+
+
+def find_additional_vertical_vector(vector):
+ ez = np.array([0, 0, 1])
+ look_at_vector = normalize(vector)
+ up_vector = normalize(ez - np.dot(look_at_vector, ez) * look_at_vector)
+ return up_vector
+
+
+def calc_rotation_matrix(v1_start, v2_start, v1_target, v2_target):
+ """
+ calculating M the rotation matrix from base U to base V
+ M @ U = V
+ M = V @ U^-1
+ """
+
+ def get_base_matrices():
+ u1_start = normalize(v1_start)
+ u2_start = normalize(v2_start)
+ u3_start = normalize(np.cross(u1_start, u2_start))
+
+ u1_target = normalize(v1_target)
+ u2_target = normalize(v2_target)
+ u3_target = normalize(np.cross(u1_target, u2_target))
+
+ U = np.hstack([u1_start.reshape(3, 1), u2_start.reshape(3, 1), u3_start.reshape(3, 1)])
+ V = np.hstack([u1_target.reshape(3, 1), u2_target.reshape(3, 1), u3_target.reshape(3, 1)])
+
+ return U, V
+
+ def calc_base_transition_matrix():
+ return np.dot(V, np.linalg.inv(U))
+
+ if not np.isclose(np.dot(v1_target, v2_target), 0, atol=1e-03):
+ raise ValueError("v1_target and v2_target must be vertical")
+
+ U, V = get_base_matrices()
+ return calc_base_transition_matrix()
+
+
+def get_euler_rotation_angles(start_look_at_vector, target_look_at_vector, start_up_vector=None, target_up_vector=None):
+ if start_up_vector is None:
+ start_up_vector = find_additional_vertical_vector(start_look_at_vector)
+
+ if target_up_vector is None:
+ target_up_vector = find_additional_vertical_vector(target_look_at_vector)
+
+ rot_mat = calc_rotation_matrix(start_look_at_vector, start_up_vector, target_look_at_vector, target_up_vector)
+ is_equal = np.allclose(rot_mat @ start_look_at_vector, target_look_at_vector, atol=1e-03)
+ print(f"rot_mat @ start_look_at_vector1 == target_look_at_vector1 is {is_equal}")
+ rotation = Rotation.from_matrix(rot_mat)
+ return rotation.as_euler(seq="xyz", degrees=True)
+
+
+
+
+def unit_vector(vector):
+ """ Returns the unit vector of the vector. """
+ return vector / np.linalg.norm(vector)
+
+def angle_between(v1, v2):
+ """ Returns the angle in radians between vectors 'v1' and 'v2'::
+
+ >>> angle_between((1, 0, 0), (0, 1, 0))
+ 1.5707963267948966
+ >>> angle_between((1, 0, 0), (1, 0, 0))
+ 0.0
+ >>> angle_between((1, 0, 0), (-1, 0, 0))
+ 3.141592653589793
+ """
+ v1_u = unit_vector(v1)
+ v2_u = unit_vector(v2)
+
+ print(v1_u, v2_u)
+
+ return np.rad2deg(np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)))
+
+def angle_between_clockwise(v1, v2):
+ unit_vector = v1 / np.linalg.norm(v1)
+ unit_y = v2 / np.linalg.norm(v2)
+ dot_product = np.dot(unit_vector, unit_y)
+ angle = np.arccos(dot_product)
+ deg = 360*angle/(2*np.pi)
+ if v1[0]<0:
+ deg = 360-deg
+ return deg
+
+
+
+p = remote('srv2.2023.magpiectf.ca',31185)
+
+p.recvuntil(b'awaiting input...')
+
+
+#parse board
+p.sendline(b'POSN')
+
+boards = p.recvuntil(b'awaiting input...').decode()
+
+print(boards)
+
+board_yx, board_yz, board_xz = [board.split('\n')[2:-2][::-1] for board in boards.split('+----------------------------------------------------+')]
+
+sats = [[0, 0, 0] for _ in range(4)]
+
+for y, l in enumerate(board_yx):
+ chunks = l.split('[')
+ for i, sat in enumerate(['A', 'B', 'C', 'D']):
+ if sat in l:
+ sats[i][1] = y
+ sats[i][0] = chunks.index(next(v for v in chunks if sat in v)) - 1
+
+for y, l in enumerate(board_yz):
+ chunks = l.split('[')
+ for i, sat in enumerate(['A', 'B', 'C', 'D']):
+ if sat in l:
+ sats[i][1] = y
+ sats[i][2] = chunks.index(next(v for v in chunks if sat in v)) - 1
+
+for x, l in enumerate(board_xz):
+ chunks = l.split('[')
+ for i, sat in enumerate(['A', 'B', 'C', 'D']):
+ if sat in l:
+ sats[i][0] = x
+ sats[i][2] = chunks.index(next(v for v in chunks if sat in v)) - 1
+
+print(sats)
+
+# sats = sats[::-1]
+
+#connect all of the sats
+for i, sat in enumerate([b'A', b'B', b'C']):
+ print()
+
+ p.sendline(b'STAT:' + sat)
+
+ curr_degs = [float(v[3:].split(b'deg')[0]) for v in p.sendlineafter(b'awaiting input...', b'ORNT:' + sat).split(b'theta_')[1:]]
+
+ print(curr_degs)
+
+ #calculate angles needed
+
+ #assuming the boards are the respective deltas: yx, yz, xz
+ # vec_x, vec_y, vec_z = (sats[i+1][1]-sats[i][1], sats[i+1][0]-sats[i][0]), (sats[i+1][1]-sats[i][1], sats[i+1][2]-sats[i][2]), (sats[i+1][0]-sats[i][0], sats[i+1][2]-sats[i][2])
+ # print(vec_x, vec_y, vec_z)
+
+ #target_degs = [math.degrees(math.atan2(sats[i+1][1]-sats[i][1], sats[i+1][0]-sats[i][0])), math.degrees(math.atan2(sats[i+1][1]-sats[i][1], sats[i+1][2]-sats[i][2])), math.degrees(math.atan2(sats[i+1][0]-sats[i][0], sats[i+1][2]-sats[i][2]))]
+ #target_degs = [angle_between_clockwise(vec_x, (0, 1)), angle_between_clockwise(vec_y, (1, 0)), angle_between_clockwise(vec_z, (0, 1))]
+
+ vec = (sats[i+1][0] - sats[i][0], sats[i+1][1] - sats[i][1], sats[i+1][2] - sats[i][2])
+ # sign = [(-1 if v < 0 else 1) for v in vec]
+
+ target_degs = [angle_between(vec, (1, 0, 0)), angle_between(vec, (0, 1, 0)), angle_between(vec, (0, 0, 1))]
+
+ #target_degs = get_euler_rotation_angles(np.array(sats[i]), np.array(sats[i+1]))
+
+ print(target_degs)
+
+ #calculate actual degrees we need to rotate from the current rotation the satellite is already having, since we can only add to the rotation registers not set them
+
+ #as_euler is -180 - 180 not 0 to 360
+ #diff_degs = [((((t + 180) - c) + 360) % 360) for t, c in zip(target_degs, curr_degs)]
+ diff_degs = [(((t - c) + 360) % 360) for t, c in zip(target_degs, curr_degs)]
+
+ print(diff_degs)
+
+ #convert to shellcode
+
+ target = [np.array([v], np.float16).tobytes() for v in diff_degs]
+
+ print(target)
+
+ p.sendlineafter(b'awaiting instrucions.', bytes(
+ #left shift srcreg to dstreg by 16; add immediate (top 8 bits), add srcreg to dstreg (low 8 bits)
+ [0b10_101_101, (16).to_bytes(1, byteorder='little')[0], 0b11_000_101, target[0][1], 0b00_101_101, target[0][0]] +
+ [0b10_110_110, (16).to_bytes(1, byteorder='little')[0], 0b11_000_110, target[1][1], 0b00_110_110, target[1][0]] +
+ [0b10_111_111, (16).to_bytes(1, byteorder='little')[0], 0b11_000_111, target[2][1], 0b00_111_111, target[2][0]]
+ ))
+
+ p.recvuntil(b'awaiting input...')
+
+p.interactive()
+```
+
+im leaving in all the wrong math that ive attempted to immortalize the amount of pain we went through lmao
+
+its not even all of them either ive deleted a ton of them already since it was clogging up the script
+
+
+### knock knock
+
+this one is genuinely more forensics than networking if i can even call it forensics to begin with
+
+the part 1 of this chall literally involves absolutely no internet concepts aside from one little WAF bypass that aint even technically networking coz its not protocol related
+
+* * *
+
+anyways we are given a site to connect to, which can run arbitrary bash commands
+
+except sometimes it just throws a HTTP 403
+
+which we eventually figured out was some form of weird filtering, and [@ayna](https://maplebacon.org/authors/aynakeya/) quickly figured out encoding the index.php with base64 can bypass the filter easily
+
+so i extended that method to smuggle all of the commands since it seems like both inbound and outbound data can be smuggled with base64, and made a script for basically a scuffed reversed shell
+
+```py
+import requests, base64
+
+headers = {
+ 'Cookies': 'session-id="' + base64.b64encode(b'10.124.0.3:8233').decode() + '"',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0',
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
+ 'Accept-Language': 'en-US,en;q=0.5',
+ 'Accept-Encoding': 'gzip, deflate',
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Origin': 'http://srv2.2023.magpiectf.ca:8233',
+ 'Connection': 'keep-alive',
+ 'Referer': 'http://srv2.2023.magpiectf.ca:8233/',
+ 'Upgrade-Insecure-Requests': '1',
+ 'Pragma': 'no-cache',
+ 'Cache-Control': 'no-cache',
+}
+
+test = lambda cmd:requests.post('http://srv2.2023.magpiectf.ca:8233/', headers=headers, data='command=printf ' + base64.b64encode(cmd).decode() + ' | base64 -d | sh&run=RUN').text
+```
+
+after this we have absolutely no idea what to do - we have arbitrary access to the server which is usually the end goal itself, but theres basically nothing resembling a flag with a fair bit of digging
+
+until we realized theres a message.txt that is in another user's directory, and weird tcpdump perms exist
+
+we still thought this is some form of internet related chall at this point, so my thought was to see if theres any way we can privilege escalate through some exposed service that is only accessible locally
+
+but theres no netstat or ss, and `/proc/net/tcp` didnt have much either aside from connections presumably from reverse shells and a lot of apache instances
+
+there was also a session cookie that was base64 encoded to be an ip, so we were thinking whether we need to leak something from that host too but theres no curl on the server and the endpoint seems largely irresponsive without port scanning
+
+*which pretty much right after we got an announcement right after this that scripted enumeration local or remote arent allowed, which wasnt stated in the rules anywhere btw*
+
+so thats a dead end
+
+and then we got an even funnier announcement: priv esc to root was not allowed, and the chall would be taken down until they figured out what to do with it (which would be after most organizers have woken up) :clown:
+
+apparently some team priv esc'd to root with a misconfiguration on the host, and they didnt have enough protections on the host against that so they were panicking lmao
+
+anyway we were fully expecting this chall to be in the grave considering this ctf is only 48h and reopening it wouldve been unfair considering we get much less time to work on this than other challs but they have the same dynamic scoring
+
+but then another announcement came up a few hours before saying its being redeployed with no compensation and also with officially root off limits now :clown: :clown:
+
+also with this redeployment the session cookie is now gone, which means that dead end is even more dead now
+
+so its time for even more poking to see in what way we can priv esc to the correct user and not root and without scripting
+
+which came up fruitless regardless
+
+at this point we were on the verge of giving up on this chall but then we literally had like 2 challs left only and this is one of them
+
+but i just couldnt be assed to work on it so i went to sleep anyway
+
+turns out [@Kevin](https://maplebacon.org/authors/Kevin/) figured out how we would priv esc to the other user during my sleep LOL
+
+the password was literally just sitting in `/opt/backup`
+
+which after `su`ing with it the message.txt i found sus indeed had the first part of the flag
+
+like how is this a networking chall at this point??????
+
+anyway remember how i mentioned tcpdump had weird perms
+
+[@ayna](https://maplebacon.org/authors/aynakeya/) was playing around with it before he went to sleep since the user we are now in has perms for it
+
+which he found a really weird stream of data that mentions something about "sending this message as a secret" from `sneaky-messager.super-secure-network` but nothing indicative of a flag anywhere
+
+so i picked up on his work and tried to analyse the ICMP packets he got by actually dumping a pcap and exfiltrating it with the reverse shell i had so i can use wireshark with `tcpdump -w /run/lock/data -nnA dst 172.16.238.10 && net 172.16.238.0/24`
+
+and pretty much right after i saw what looks like flag chars in one of the ICMP fields lol
+
+which was easily extractable from the pcap with a scapy script
+
+```py
+from scapy.all import *
+
+pcap = rdpcap('dump.pcap')
+
+data = [p[ICMP] for p in pcap if ICMP in p]
+
+print(b''.join([bytes([d.id]) for d in data]).split(b'}'))
+```
+
+`magpie{y0u_h4v3_7h3_p0w32_70_54v3_7h3_w021d}`
+
+like genuinely if this chall was just the second part i wouldnt have been this mad lol coz it actually resembles a networking chall
+
+meanwhile with the entire thing its literally just guessing all the way
+
+like this is not pentesting but a ctf chall dude throwing an entire system at us with basically no pointers will never end well
\ No newline at end of file
diff --git a/ctfs/magpie23.yml b/ctfs/magpie23.yml
new file mode 100644
index 0000000..2192490
--- /dev/null
+++ b/ctfs/magpie23.yml
@@ -0,0 +1,42 @@
+#refer to hkcert21.yml for definitions
+name: "Magpie CTF 2023"
+url: null
+
+date: 2023-02-24
+duration: 48
+
+type: "Jeopardy"
+
+organizer: false
+rank: '<span class="text-first">1<sup>st</sup></span>&nbsp;(CA students), 4<sup>th</sup>&nbsp;(Overall)'
+full-clear: false
+
+team: '<span class="nav-link p-0" data-toggle="tooltip" title="Split into Maple/Bacon due to size limit">Bacon</span>'
+
+challenges:
+ - name: "missing flag"
+ category: rev
+ points: "?"
+ solve-count: "?"
+ solve-status: solved
+ writeup-url: null
+ first-blood: true
+ - name: "take a wild guess"
+ category: rev
+ points: "?"
+ solve-count: "?"
+ solve-status: solved
+ writeup-url: null
+ first-blood: true
+ - name: "knock knock"
+ category: networking
+ points: "?"
+ solve-count: "?"
+ solve-status: co-solved
+ writeup-url: null
+ - name: "satshell"
+ category: rev
+ points: "?"
+ solve-count: "?"
+ solve-status: co-solved
+ writeup-url: null
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 22, 3:43 AM (1 d, 18 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7c/25/2560fd851786f7aea83cfe1a2ac1

Event Timeline