Skip to content

Commit ee836cc

Browse files
committed
adding blog posts, removing metadata
1 parent d27254c commit ee836cc

File tree

60 files changed

+519
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+519
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
title: Stadium!!
3+
date: 2025-05-22
4+
categories: [Capture The Flags, VishwaCTF 2025]
5+
tags: [ctf, vishwactf, osint, writeups]
6+
description: VishwaCTF 2025 Stadium!! Challenge
7+
---
8+
> Challenge description
9+
>
10+
> My friend wanted to play a cricket match, he told me to find the best place to play a game. So I did. Can you identify the stadium where we played our match?
11+
{: .prompt-info }
12+
13+
Okay so this is a pretty standard reverse image search challenge, let's throw the image we got into Google Images.
14+
15+
![The image](/assets/img/vishwactf-2025/stadium/Chall_osint.png)
16+
17+
We ended up finding [this Instagram post](https://www.instagram.com/p/CSKcUKyBCyh/?locale=ko), which mentions a `Ghanche Cricket Stadium`, and looking into that reveals the following image:
18+
19+
![found it](/assets/img/vishwactf-2025/stadium/found.png)
20+
21+
Looks like wee we're able to do it!
22+
23+
FLAG: `VishwaCTF{Saling_Cricket_Stadium_Ghanche}`
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
title: Cryptoclock
3+
date: 2025-05-26
4+
categories: [Capture The Flags, NahamCon CTF 2025]
5+
tags: [ctf, nahamcon ctf 2025, crypto, writeups]
6+
description: NahamCon CTF 2025 Cryptoclock Challenge
7+
---
8+
9+
> Challenge description:
10+
>
11+
> Just imagine it, _the Cryptoclock!!_ Just like you've seen in the movies, a magical power to be able to manipulate the world's numbers across time!!
12+
13+
For this challenge, we are given `server.py`, let's take a look at that script.
14+
15+
```py
16+
#!/usr/bin/env python3
17+
import socket
18+
import threading
19+
import time
20+
import random
21+
import os
22+
from typing import Optional
23+
24+
def encrypt(data: bytes, key: bytes) -> bytes:
25+
"""Encrypt data using XOR with the given key."""
26+
return bytes(a ^ b for a, b in zip(data, key))
27+
28+
def generate_key(length: int, seed: Optional[float] = None) -> bytes:
29+
"""Generate a random key of given length using the provided seed."""
30+
if seed is not None:
31+
random.seed(int(seed))
32+
return bytes(random.randint(0, 255) for _ in range(length))
33+
34+
def handle_client(client_socket: socket.socket):
35+
"""Handle individual client connections."""
36+
try:
37+
with open('flag.txt', 'rb') as f:
38+
flag = f.read().strip()
39+
40+
current_time = int(time.time())
41+
key = generate_key(len(flag), current_time)
42+
43+
encrypted_flag = encrypt(flag, key)
44+
45+
welcome_msg = b"Welcome to Cryptoclock!\n"
46+
welcome_msg += b"The encrypted flag is: " + encrypted_flag.hex().encode() + b"\n"
47+
welcome_msg += b"Enter text to encrypt (or 'quit' to exit):\n"
48+
client_socket.send(welcome_msg)
49+
50+
while True:
51+
data = client_socket.recv(1024).strip()
52+
if not data:
53+
break
54+
55+
if data.lower() == b'quit':
56+
break
57+
58+
key = generate_key(len(data), current_time)
59+
encrypted_data = encrypt(data, key)
60+
61+
response = b"Encrypted: " + encrypted_data.hex().encode() + b"\n"
62+
client_socket.send(response)
63+
64+
except Exception as e:
65+
print(f"Error handling client: {e}")
66+
finally:
67+
client_socket.close()
68+
69+
def main():
70+
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
71+
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
72+
73+
server.bind(('0.0.0.0', 1337))
74+
server.listen(5)
75+
76+
print("Server started on port 1337...")
77+
78+
try:
79+
while True:
80+
client_socket, addr = server.accept()
81+
print(f"Accepted connection from {addr}")
82+
client_thread = threading.Thread(target=handle_client, args=(client_socket,))
83+
client_thread.start()
84+
except KeyboardInterrupt:
85+
print("\nShutting down server...")
86+
finally:
87+
server.close()
88+
89+
if __name__ == "__main__":
90+
main()
91+
```
92+
{: file="server.py"}
93+
94+
Okay, so a good chunk of the script is just handling the client connections, but let's take a closer look at where they generate the key for the encryption.
95+
96+
```py
97+
def generate_key(length: int, seed: Optional[float] = None) -> bytes:
98+
"""Generate a random key of given length using the provided seed."""
99+
if seed is not None:
100+
random.seed(int(seed))
101+
return bytes(random.randint(0, 255) for _ in range(length))
102+
```
103+
{: file="server.py" }
104+
105+
So in this script they generate their key using a random seed, and then use that random seed to randomly add onto the key until it reaches a specified length. The issue with this is that if you are able to figure out the same seed, you ill get the same key. To add on to the issue, lets look at how they generate the seed.
106+
107+
```py
108+
with open('flag.txt', 'rb') as f:
109+
flag = f.read().strip()
110+
111+
current_time = int(time.time())
112+
key = generate_key(len(flag), current_time)
113+
```
114+
{: file="server.py" }
115+
116+
So whats wrong with this? Well, its because in the `generate_key` function, they make the seed an int, which removes any decimals after it, so the seed becomes something like `1748285129`, rather than `1748285129.9751241`, this makes it very easy to guess the seed, as long as we know when thee script is run. And since we know the script is run when we connect, we can be pretty sure down to a few seconds.
117+
118+
Okay, so we can reproduce the key, but so what? Don't we need to also reverse the encryption? We'll yes, but that's easy. Let's take a look at the `encrypt` function:
119+
120+
```py
121+
def encrypt(data: bytes, key: bytes) -> bytes:
122+
"""Encrypt data using XOR with the given key."""
123+
return bytes(a ^ b for a, b in zip(data, key))
124+
```
125+
{: file="server.py" }
126+
127+
What we see is a very basic `XOR` function, and given that you can obtain the plaintext by simply `XOR`ing the ciphertext and key, it'll be a peice of cake. Let's take a look at the script in action:
128+
129+
```terminal
130+
┌─[✗]─[slavetomints@parrot]─[~]
131+
└──╼ $nc challenge.nahamcon.com 30851
132+
Welcome to Cryptoclock!
133+
The encrypted flag is: f3703a3c13e737316dcc0bb7bfddf602bb5ffe2829a8d819663133251a93155f4774b45ba00e
134+
Enter text to encrypt (or 'quit' to exit):
135+
136+
```
137+
138+
I made sure to take a note of the UNIX time when I ran the script, `1748032152`, the nice thing about this one is that the script to brute force the flag is very similar to the provided script.
139+
140+
```py
141+
import re
142+
import random
143+
import time
144+
145+
approx_timestamp = 1748032152
146+
147+
encrypted_flag_hex = "f3703a3c13e737316dcc0bb7bfddf602bb5ffe2829a8d819663133251a93155f4774b45ba00e"
148+
encrypted_flag = bytes.fromhex(encrypted_flag_hex)
149+
150+
def generate_key(seed: int, length: int) -> bytes:
151+
random.seed(seed)
152+
return bytes(random.randint(0, 255) for _ in range(length))
153+
154+
def decrypt_flag(encrypted: bytes, key: bytes) -> bytes:
155+
return bytes(a ^ b for a, b in zip(encrypted, key))
156+
157+
def is_valid_flag(flag: bytes) -> bool:
158+
try:
159+
flag_str = flag.decode()
160+
return bool(re.fullmatch(r'flag\{[0-9a-f]{32}\}', flag_str))
161+
except:
162+
return False
163+
164+
print(f"Trying seeds from {approx_timestamp - 5} to {approx_timestamp + 5}...")
165+
166+
for ts in range(approx_timestamp - 5, approx_timestamp + 6):
167+
key = generate_key(ts, len(encrypted_flag))
168+
candidate_flag = decrypt_flag(encrypted_flag, key)
169+
170+
if is_valid_flag(candidate_flag):
171+
print(f"[+] Seed {ts} matched!")
172+
print(f"Decrypted flag: {candidate_flag.decode()}")
173+
```
174+
{: file="brute.py"}
175+
176+
With this script, I made sure to include a few seconds before and after my timestamp to account for any errors in generating the timestamp. And in action, it worked like a charm.
177+
178+
```terminal
179+
┌─[slavetomints@parrot]─[~]
180+
└──╼ $python brute.py
181+
Trying seeds from 1748032147 to 1748032157...
182+
[+] Seed 1748032152 matched!
183+
Decrypted flag: flag{0e42ba180089ce6e3bb50e52587d3724}
184+
```
185+
186+
FLAG: `flag{0e42ba180089ce6e3bb50e52587d3724}`
187+

0 commit comments

Comments
 (0)