Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ params:
tags: [ 'rev', 'web', 'osint' ]
description:
- When life gives you pears, don't write a book about it
- name: h3pha
link: https://github.com/VladSteopoaie/
picture: https://avatars.githubusercontent.com/u/69504268?s=400&u=0bb78ff707338ad58e677d856fbe92766e116010&v=4
tags: [ 'pwn', 'web', 'rev', 'misc', 'network' ]
description:
- Don't hate me, but... Try harder!
excludedSections:
- about
- events
Expand Down
110 changes: 110 additions & 0 deletions content/KalmarCTF_2025/Very_Serious_Cryptography.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Very Serious Cryptography
date: 2025-03-10T03:15:35+03:00
description: Writeup for Very Serious Cryptography [KalmarCTF 2025]
author: h3pha
tags:
- crypto
draft: false
---
___

>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!

## Challenge Description

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!?!

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!

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)

nc very-serious.chal-kalmarc.tf 2257

## Intuition

Challenge file:
```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import os

with open("flag.txt", "rb") as f:
flag = f.read()

key = os.urandom(16)

# Efficient service for pre-generating personal, romantic, deeply heartfelt white day gifts for all the people who sent you valentines gifts
for _ in range(1024):
# Which special someone should we prepare a truly meaningful gift for?
recipient = input("Recipient name: ")

# whats more romantic than the abstract notion of a securely encrypted flag?
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}'

aes = AES.new(key, AES.MODE_CBC, iv=b'preprocessedlove')
print(f'heres a thoughtful and unique gift for {recipient}: {aes.decrypt(pad(romantic_message.encode(), AES.block_size)).hex()}')
```

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.

This means that we can brute force each character of the flag like this:

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

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.

=> `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`

We encrypt the text and then we can use as input this:
`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.

Now if we compare all the encrypted messages with the original encrypted text we can find the character from the flag.

Repeating this for all the characters until we reach `}` will give us the whole flag.

## Solution

Solver:
```python
from pwn import *

charset = "abcdefghijklmnopqrstuvwxyz'{}_"
prefix = "Dear "
middle = ", as a token of the depth of my feelings, I gift to you that which is most precious to me. A "
flag = ""
p = process(["python", "chal.py"])
# p = remote("very-serious.chal-kalmarc.tf", 2257)

def send_input_list(p, input_list):
output_list = []
for i in input_list:
p.sendline(i.encode())
# takes only the encrypted text
output = bytes.fromhex(p.recvline().decode().split()[-1])
output_list.append(output)
return output_list

while "}" not in flag:
try:
# ensure that the character we are searching is at the end of the block
padding = "_" * ((15 - len(prefix + middle + flag)) % 16)
# this is where the original flag is encrypted
original = send_input_list(p, [padding])[0]
# create all possible variants of the characters withing the flag
brute_input = [padding + middle + flag + c for c in charset]
# send the variants, and receive all encryptions
brute_output = send_input_list(p, brute_input)
# this is the position of the end of the block
character_position = len(prefix + padding + middle + flag) + 1
for i in range(len(brute_output)):
if brute_output[i][:character_position] == original[:character_position]:
flag += charset[i]
print(flag)
except EOFError:
p = process(["python", "chal.py"])
```

### Flag

`kalmar{i_wonder_how_many_challenges_have_been_made_based_off_this_tweet}`
7 changes: 7 additions & 0 deletions content/KalmarCTF_2025/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: KalmarCTF 2025
date: 2025-03-10T02:46:38+03:00
description: Writeups for [KalmarCTF 2025]
place: 145
total: 287
---
122 changes: 122 additions & 0 deletions content/KalmarCTF_2025/babyKalmarCTF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
title: babyKalmarCTF
date: 2025-03-10T03:10:19+03:00
description: Writeup for babyKalmarCTF [KalmarCTF 2025]
author: h3pha
tags:
- misc
draft: false
---
___

## Challenge Description

Ever played a CTF inside a CTF?

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.

Thats when we came across this ingenious suggestion! https://github.com/sigpwny/ctfd-dynamic-challenges-mod/issues/1

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.

If you manage to win babykalmarCTF, we'll even give you a flag at /flag!

Spin up your own personal babykalmarCTF here: https://lab1.kalmarc.tf/

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.
Its solvable through very normal interactions with a CTFd instance. We encourage the whole team working together on the same remote.

## Intuition

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.

## Solution


Create users with this script:
```python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time

url = "https://4ff92cbb806a6203161d7da5b1400ac4-39338.inst1.chal-kalmarc.tf/"

driver = webdriver.Chrome()

for i in range(0, 100):
driver.get(url + "register")
time.sleep(0.5)

# create user
driver.find_element(By.NAME, "name").send_keys(str(i))
driver.find_element(By.NAME, "email").send_keys(str(i) + "@a.a")
driver.find_element(By.NAME, "password").send_keys(str(i))
driver.find_element(By.ID, "_submit").click()
time.sleep(0.3)

# create team
driver.get(url + "/teams/new")
time.sleep(0.3)

driver.find_element(By.NAME, "name").send_keys(str(i))
driver.find_element(By.NAME, "password").send_keys(str(i))
driver.find_element(By.ID, "_submit").click()
time.sleep(0.3)

driver.get(url + "/logout")
time.sleep(0.3)

driver.quit()
```

I let the script create around 100 teams.

Solved the challenges inside the babyCTF:

Welcome challenge: `babykalmar{welcome_to_babykalmar_CTF}`

Rev challenge: `babykalmar{string_compare_rev_ayoooooooo}`

Misc challenge: `BABYKALMAR{SUPERORIGINALMORSECODECHALLENGE}`

Crypto challenge: `babykalmar{wow_you_are_an_rsa_master!!!!!}`

OSINT challenge: `babykalmar{aarhus}`

And got the flag at `/flag`.

### Flag

`kalmar{w0w_y0u_b34t_k4lm4r_1n_4_c7f?!?}`

## Additional

For RSA challenge:
```python
import math
from Crypto.Util.number import long_to_bytes

n1 = 92045071469462918382808444819504749563961839349096597384482544087908047186245341810642171828493439415203636331750819922984117530107215197072782880474039650967711411408034481971170502798025943494586125686145145275611434604037182033168196599652119558449773401870500131970644786235514317736653798125756404891127
c1 = 83837022114533675382122799116377123399567305874353525217531313052347013266429457590484976944405567987615711918756165213164809141929523845319047846779529628627662566542055574929528850262048285117600900265045865263948170688845876052722196561247534915037323009007843324908963180407442831108561689170430284682827
n2 = 138872353325175299307460237192549876070806082965466021111327520189900415231224864814489473847190673904249096844311163666118481717154197936898625500598207447786178788728989474031735348581801399821380599701957041743964351118199095341359179067904834006929292304447601473687076874217599854120530320878903822568483
n3 = 96873643524161216047523283610645732806192956944624208819078561364455621631633510067022852244593247313195537163455457833157440906743895116798782534912117642844197952559448815829606193149605373700004399064513744456542191695589096233791113561406431990041145854326610075794048654641871205275800952496149515217589
e = 65537

# Factor the moduli using GCD
q = math.gcd(n1, n2)
p = n1 // q
r = n2 // q

assert p * r == n3, "Factorization failed"

# Compute private exponent for n1
phi_n1 = (p - 1) * (q - 1)
d1 = pow(e, -1, phi_n1)

# Decrypt the flag
m = pow(c1, d1, n1)
flag = long_to_bytes(m)

print(flag.decode())
```
39 changes: 39 additions & 0 deletions content/KalmarCTF_2025/rwx-bronze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: RWX-Bronze
date: 2025-03-10T02:48:43+03:00
description: Writeup for RWX-Bronze [KalmarCTF 2025]
author: h3pha
tags:
- misc
draft: false
---
___

## Challenge Description

We give you file read, file write and code execution. But can you get the flag? Let's start out gently.

NOTE: If you get a 404 error, try using one of the endpoints described in the handout!

## Intuition

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.

## Solution

Wrote the script into the `/tmp` directory:
```
POST /write?filename=/tmp/a HTTP/2

#!/bin/sh
/would you be so kind to provide me with a flag
```

Executed the script: `sh /*/a`
```
GET /exec?cmd=sh%20/*/a HTTP/2
```

### Flag

`kalmar{ok_you_demonstrated_your_rwx_abilities_but_let_us_put_you_to_the_test_for_real_now}`
Loading