Skip to content

Commit e3ef0b1

Browse files
authored
Merge pull request #164 from VladSteopoaie/main
Added KalmarCTF writeups
2 parents fef8d62 + e8659b5 commit e3ef0b1

7 files changed

Lines changed: 431 additions & 0 deletions

File tree

config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ params:
105105
tags: [ 'rev', 'web', 'osint' ]
106106
description:
107107
- When life gives you pears, don't write a book about it
108+
- name: h3pha
109+
link: https://github.com/VladSteopoaie/
110+
picture: https://avatars.githubusercontent.com/u/69504268?s=400&u=0bb78ff707338ad58e677d856fbe92766e116010&v=4
111+
tags: [ 'pwn', 'web', 'rev', 'misc', 'network' ]
112+
description:
113+
- Don't hate me, but... Try harder!
108114
excludedSections:
109115
- about
110116
- events
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
title: Very Serious Cryptography
3+
date: 2025-03-10T03:15:35+03:00
4+
description: Writeup for Very Serious Cryptography [KalmarCTF 2025]
5+
author: h3pha
6+
tags:
7+
- crypto
8+
draft: false
9+
---
10+
___
11+
12+
>This writeup is just a better explanation of [this](https://connor-mccartney.github.io/cryptography/other/KalmarCTF2025#very-serious-cryptography) one. Make sure to check it too!
13+
14+
## Challenge Description
15+
16+
As CTF becomes more mainstream, a troubling new trend is emerging of player fanclubs becoming so large that top players and challenge authors are having their lives disrupted from the sheer volume of valentines gifts they are receiving! With some instances of the extreme valentines pressure even leading to the last minute postponement of major CTFs!?!
17+
18+
As such, we have decided to expand our traditional CTF valentines cards service, to provide a utility for efficiently generating meaningful, romantic gifts. We hope this will enable busy CTF players to be all set for the upcoming white day, and the huge number of return gifts they will inevitably have to send back, ensuring that no more CTF's will have to be postponed this year!
19+
20+
Note: Our infra team was worried that the sheer number of gifts required could take down our servers. But luckily i stumbled upon a solution that lets me generate them much more efficiently! Thanks to [https://x.com/veorq/status/1805877920306499868](https://x.com/veorq/status/1805877920306499868)
21+
22+
nc very-serious.chal-kalmarc.tf 2257
23+
24+
## Intuition
25+
26+
Challenge file:
27+
```python
28+
from Crypto.Cipher import AES
29+
from Crypto.Util.Padding import pad
30+
import os
31+
32+
with open("flag.txt", "rb") as f:
33+
flag = f.read()
34+
35+
key = os.urandom(16)
36+
37+
# Efficient service for pre-generating personal, romantic, deeply heartfelt white day gifts for all the people who sent you valentines gifts
38+
for _ in range(1024):
39+
# Which special someone should we prepare a truly meaningful gift for?
40+
recipient = input("Recipient name: ")
41+
42+
# whats more romantic than the abstract notion of a securely encrypted flag?
43+
romantic_message = f'Dear {recipient}, as a token of the depth of my feelings, I gift to you that which is most precious to me. A {flag}'
44+
45+
aes = AES.new(key, AES.MODE_CBC, iv=b'preprocessedlove')
46+
print(f'heres a thoughtful and unique gift for {recipient}: {aes.decrypt(pad(romantic_message.encode(), AES.block_size)).hex()}')
47+
```
48+
49+
The idea behind this one is to use the decryption property of `AES-CBC` that uses the IV to decrypt the first block and then it uses the past blocks to decrypt next blocks of data.
50+
51+
This means that we can brute force each character of the flag like this:
52+
53+
Text to encrypt: `Dear {our input} , as a token of the depth of my feelings, I gift to you that which is most precious to me. A {flag}`
54+
55+
To brute force the first character we ensure that the input we give will pad the text in such a way so that the first character of the flag is the last character in a block.
56+
57+
=> `len("Dear {our input} , as a token of the depth of my feelings, I gift to you that which is most precious to me. A") == 15 mod 16` => `padding`
58+
59+
We encrypt the text and then we can use as input this:
60+
`Dear {padding} , as a token of the depth of my feelings, I gift to you that which is most precious to me. A ` + character to brute force.
61+
62+
Now if we compare all the encrypted messages with the original encrypted text we can find the character from the flag.
63+
64+
Repeating this for all the characters until we reach `}` will give us the whole flag.
65+
66+
## Solution
67+
68+
Solver:
69+
```python
70+
from pwn import *
71+
72+
charset = "abcdefghijklmnopqrstuvwxyz'{}_"
73+
prefix = "Dear "
74+
middle = ", as a token of the depth of my feelings, I gift to you that which is most precious to me. A "
75+
flag = ""
76+
p = process(["python", "chal.py"])
77+
# p = remote("very-serious.chal-kalmarc.tf", 2257)
78+
79+
def send_input_list(p, input_list):
80+
output_list = []
81+
for i in input_list:
82+
p.sendline(i.encode())
83+
# takes only the encrypted text
84+
output = bytes.fromhex(p.recvline().decode().split()[-1])
85+
output_list.append(output)
86+
return output_list
87+
88+
while "}" not in flag:
89+
try:
90+
# ensure that the character we are searching is at the end of the block
91+
padding = "_" * ((15 - len(prefix + middle + flag)) % 16)
92+
# this is where the original flag is encrypted
93+
original = send_input_list(p, [padding])[0]
94+
# create all possible variants of the characters withing the flag
95+
brute_input = [padding + middle + flag + c for c in charset]
96+
# send the variants, and receive all encryptions
97+
brute_output = send_input_list(p, brute_input)
98+
# this is the position of the end of the block
99+
character_position = len(prefix + padding + middle + flag) + 1
100+
for i in range(len(brute_output)):
101+
if brute_output[i][:character_position] == original[:character_position]:
102+
flag += charset[i]
103+
print(flag)
104+
except EOFError:
105+
p = process(["python", "chal.py"])
106+
```
107+
108+
### Flag
109+
110+
`kalmar{i_wonder_how_many_challenges_have_been_made_based_off_this_tweet}`

content/KalmarCTF_2025/_index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: KalmarCTF 2025
3+
date: 2025-03-10T02:46:38+03:00
4+
description: Writeups for [KalmarCTF 2025]
5+
place: 145
6+
total: 287
7+
---
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
---
2+
title: babyKalmarCTF
3+
date: 2025-03-10T03:10:19+03:00
4+
description: Writeup for babyKalmarCTF [KalmarCTF 2025]
5+
author: h3pha
6+
tags:
7+
- misc
8+
draft: false
9+
---
10+
___
11+
12+
## Challenge Description
13+
14+
Ever played a CTF inside a CTF?
15+
16+
We were looking for a new scoring algorithm which would both reward top teams for solving super hard challenges, but also ensure that the easiest challenges wouldn't go to minimum straight away if more people played than we expected.
17+
18+
Thats when we came across this ingenious suggestion! https://github.com/sigpwny/ctfd-dynamic-challenges-mod/issues/1
19+
20+
We've implemented it this scoring idea(see here: https://github.com/blatchley/ctfd-dynamic-challenges-mod ) and spun up a small test ctf to test it out.
21+
22+
If you manage to win babykalmarCTF, we'll even give you a flag at /flag!
23+
24+
Spin up your own personal babykalmarCTF here: https://lab1.kalmarc.tf/
25+
26+
Note: Rather than each member starting their own, we encourage one person to make a remote for your team, and then share the link with everyone else! Please be nice to instances, getting flag doesn't involve heavy compute/"hacking CTFd" or abuse on remote.
27+
Its solvable through very normal interactions with a CTFd instance. We encourage the whole team working together on the same remote.
28+
29+
## Intuition
30+
31+
After analyzing the links provided, I saw that the points for the completed challenges are based on the number of teams paying the CTF. So if we manage to create a lot of teams we can increase the points for each challenge.
32+
33+
## Solution
34+
35+
36+
Create users with this script:
37+
```python
38+
from selenium import webdriver
39+
from selenium.webdriver.common.by import By
40+
from selenium.webdriver.common.keys import Keys
41+
import time
42+
43+
url = "https://4ff92cbb806a6203161d7da5b1400ac4-39338.inst1.chal-kalmarc.tf/"
44+
45+
driver = webdriver.Chrome()
46+
47+
for i in range(0, 100):
48+
driver.get(url + "register")
49+
time.sleep(0.5)
50+
51+
# create user
52+
driver.find_element(By.NAME, "name").send_keys(str(i))
53+
driver.find_element(By.NAME, "email").send_keys(str(i) + "@a.a")
54+
driver.find_element(By.NAME, "password").send_keys(str(i))
55+
driver.find_element(By.ID, "_submit").click()
56+
time.sleep(0.3)
57+
58+
# create team
59+
driver.get(url + "/teams/new")
60+
time.sleep(0.3)
61+
62+
driver.find_element(By.NAME, "name").send_keys(str(i))
63+
driver.find_element(By.NAME, "password").send_keys(str(i))
64+
driver.find_element(By.ID, "_submit").click()
65+
time.sleep(0.3)
66+
67+
driver.get(url + "/logout")
68+
time.sleep(0.3)
69+
70+
driver.quit()
71+
```
72+
73+
I let the script create around 100 teams.
74+
75+
Solved the challenges inside the babyCTF:
76+
77+
Welcome challenge: `babykalmar{welcome_to_babykalmar_CTF}`
78+
79+
Rev challenge: `babykalmar{string_compare_rev_ayoooooooo}`
80+
81+
Misc challenge: `BABYKALMAR{SUPERORIGINALMORSECODECHALLENGE}`
82+
83+
Crypto challenge: `babykalmar{wow_you_are_an_rsa_master!!!!!}`
84+
85+
OSINT challenge: `babykalmar{aarhus}`
86+
87+
And got the flag at `/flag`.
88+
89+
### Flag
90+
91+
`kalmar{w0w_y0u_b34t_k4lm4r_1n_4_c7f?!?}`
92+
93+
## Additional
94+
95+
For RSA challenge:
96+
```python
97+
import math
98+
from Crypto.Util.number import long_to_bytes
99+
100+
n1 = 92045071469462918382808444819504749563961839349096597384482544087908047186245341810642171828493439415203636331750819922984117530107215197072782880474039650967711411408034481971170502798025943494586125686145145275611434604037182033168196599652119558449773401870500131970644786235514317736653798125756404891127
101+
c1 = 83837022114533675382122799116377123399567305874353525217531313052347013266429457590484976944405567987615711918756165213164809141929523845319047846779529628627662566542055574929528850262048285117600900265045865263948170688845876052722196561247534915037323009007843324908963180407442831108561689170430284682827
102+
n2 = 138872353325175299307460237192549876070806082965466021111327520189900415231224864814489473847190673904249096844311163666118481717154197936898625500598207447786178788728989474031735348581801399821380599701957041743964351118199095341359179067904834006929292304447601473687076874217599854120530320878903822568483
103+
n3 = 96873643524161216047523283610645732806192956944624208819078561364455621631633510067022852244593247313195537163455457833157440906743895116798782534912117642844197952559448815829606193149605373700004399064513744456542191695589096233791113561406431990041145854326610075794048654641871205275800952496149515217589
104+
e = 65537
105+
106+
# Factor the moduli using GCD
107+
q = math.gcd(n1, n2)
108+
p = n1 // q
109+
r = n2 // q
110+
111+
assert p * r == n3, "Factorization failed"
112+
113+
# Compute private exponent for n1
114+
phi_n1 = (p - 1) * (q - 1)
115+
d1 = pow(e, -1, phi_n1)
116+
117+
# Decrypt the flag
118+
m = pow(c1, d1, n1)
119+
flag = long_to_bytes(m)
120+
121+
print(flag.decode())
122+
```
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
title: RWX-Bronze
3+
date: 2025-03-10T02:48:43+03:00
4+
description: Writeup for RWX-Bronze [KalmarCTF 2025]
5+
author: h3pha
6+
tags:
7+
- misc
8+
draft: false
9+
---
10+
___
11+
12+
## Challenge Description
13+
14+
We give you file read, file write and code execution. But can you get the flag? Let's start out gently.
15+
16+
NOTE: If you get a 404 error, try using one of the endpoints described in the handout!
17+
18+
## Intuition
19+
20+
The challenge lets us execute commands of length 7, so we cannot execute `/would` with the necessary argument. My first attempt was to create a script file and run it with a command.
21+
22+
## Solution
23+
24+
Wrote the script into the `/tmp` directory:
25+
```
26+
POST /write?filename=/tmp/a HTTP/2
27+
28+
#!/bin/sh
29+
/would you be so kind to provide me with a flag
30+
```
31+
32+
Executed the script: `sh /*/a`
33+
```
34+
GET /exec?cmd=sh%20/*/a HTTP/2
35+
```
36+
37+
### Flag
38+
39+
`kalmar{ok_you_demonstrated_your_rwx_abilities_but_let_us_put_you_to_the_test_for_real_now}`

0 commit comments

Comments
 (0)