Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
xanhacks committed Oct 27, 2024
0 parents commit 2dda7f9
Show file tree
Hide file tree
Showing 1,782 changed files with 274,476 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .ctf/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[config]
url = https://ctf.heroctf.fr/
access_token = ctfd_ba540261e30641d81c02719e44bce5550dc07402f36af6a4debcce10908534cc

[challenges]

1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.avi filter=lfs diff=lfs merge=lfs -text
34 changes: 34 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build check on Dockerfiles

on:
push:
branches:
- "main"

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repo content
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker Images
run: |
find . -type f -name "*Dockerfile*" | while read -r dockerfile_path; do
echo "==========[ $dockerfile_path ]=========="
pushd "$(dirname "$dockerfile_path")"
echo "Building image..."
docker buildx build --platform linux/amd64 . &> /tmp/docker-build.log
build_result=$?
if [ $build_result -eq 0 ]; then
echo "Build success!"
else
cat /tmp/docker-build.log
rm /tmp/docker-build.log
echo "Build failed!"
fi
popd
done
24 changes: 24 additions & 0 deletions .github/workflows/syntax-challenge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Syntax check on 'challenge.yml' files

on:
push:
branches:
- "main"

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repo content
uses: actions/checkout@v4

- name: Setup python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: python3 -m pip install PyYAML

- name: Run the syntax checker
run: python3 .github/yaml_chall_checker.py $GITHUB_WORKSPACE
125 changes: 125 additions & 0 deletions .github/yaml_chall_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import os
import sys
import yaml
from yaml.loader import SafeLoader


REQUIRED_ATTRIBUTES = ["name", "author", "category", "description", "value", "type", "flags", "tags", "state", "version"]
OPTIONAL_ATTRIBUTES = ["image", "host", "hints", "files", "requirements", "extra"]
REQUIRED_CATEGORIES = [
"Blockchain",
"Crypto",
"Forensics",
"Forensic",
"Misc",
"OSINT",
"Prog",
"Pwn",
"Reverse",
"Sponsors",
"Steganography",
"System",
"Web"
]

def find_challenge_files(directory: str) -> list[str]:
"""Find all challenge.yml files in subdirectories"""
challenge_files = []
for root, _, files in os.walk(directory):
for file in files:
if file == "challenge.yml":
challenge_files.append(os.path.join(root, file))
return challenge_files

def syntax_check(challenge_file: str) -> None:
"""Validate the syntax, attributes, and types of a 'challenge.yml' file"""
print(f"Checking syntax of '{challenge_file}'...")
try:
with open(os.path.join(challenge_file), "r") as f:
yaml_data = yaml.load(f, Loader=SafeLoader)

missing_attributes = []
for attribute in REQUIRED_ATTRIBUTES:
if attribute not in yaml_data:
missing_attributes.append(attribute)

if missing_attributes:
print(f"Missing required attributes: {', '.join(missing_attributes)}")

for key, value in yaml_data.items():
if key not in REQUIRED_ATTRIBUTES + OPTIONAL_ATTRIBUTES:
print(f"Unknown attribute '{key}'")

match key:
case "name":
if not isinstance(value, str):
print(f"Attribute '{key}' is not a string")
case "author":
if not isinstance(value, str):
print(f"Attribute '{key}' is not a string")
case "category":
if value not in REQUIRED_CATEGORIES:
print(f"Attribute '{key}' is not a valid category")
case "description":
if not isinstance(value, str):
print(f"Attribute '{key}' is not a string")
case "value":
if not (isinstance(value, int) or value is None):
print(f"Attribute '{key}' is not an integer or None")
case "type":
if value != "dynamic":
print(f"Attribute '{key}' is not 'dynamic'")
case "extra":
if not isinstance(value, dict):
print(f"Attribute '{key}' is not a dictionary")
if "initial" not in value or "decay" not in value or "minimum" not in value:
print(f"Attribute '{key}' is missing required sub-attributes")
if not isinstance(value["initial"], int) or not isinstance(value["decay"], int) or not isinstance(value["minimum"], int):
print(f"Attribute '{key}' sub-attributes are not integers")
if value["initial"] != 500:
print(f"Attribute '{key}' sub-attribute 'initial' is not 500")
if value["decay"] != 100:
print(f"Attribute '{key}' sub-attribute 'decay' is not 100")
if value["minimum"] < 1 or value["minimum"] > 50:
print(f"Attribute '{key}' sub-attribute 'minimum' is not between 1 and 50")
case "image":
if value is not None:
print(f"Attribute '{key}' is not None")
case "host":
if value is not None:
print(f"Attribute '{key}' is not None")
case "flags":
if not isinstance(value, list):
print(f"Attribute '{key}' is not a list")
case "tags":
if not isinstance(value, list):
print(f"Attribute '{key}' is not a list")
case "files":
if not (isinstance(value, list) or value is None):
print(f"Attribute '{key}' is not a list or None")
case "state":
if value not in ["hidden", "visible"]:
print(f"Attribute '{key}' is not 'hidden' or 'visible'")
case "version":
if not isinstance(value, str):
print(f"Attribute '{key}' is not a string")
case "requirements":
if not isinstance(value, list):
print(f"Attribute '{key}' is not a list")
case "hints":
if not isinstance(value, list):
print(f"Attribute '{key}' is not a list")

print("Syntax is valid")
except yaml.YAMLError as exc:
print(f"Syntax is invalid: {exc}")


if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python yaml_chall_checker.py <directory>")
sys.exit(1)

challenge_files = find_challenge_files(sys.argv[1])
for challenge_file in challenge_files:
syntax_check(challenge_file)
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
__pycache__/
.env

fullchain.pem
privkey.pem
Empty file added Crypto/.gitkeep
Empty file.
15 changes: 15 additions & 0 deletions Crypto/Halloween/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.11-alpine

RUN apk --update add socat \
&& adduser -D --home /app user \
&& pip3 install gostcrypto

COPY --chown=user . /app

RUN chmod 755 /app/entry.sh /app/chall.py

WORKDIR /app

EXPOSE ${LISTEN_PORT}

ENTRYPOINT ["/app/entry.sh"]
62 changes: 62 additions & 0 deletions Crypto/Halloween/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Halloween

### Category

Crypto

### Description

Boo! Do you believe in ghosts ? I sure don't.<br><br>
Host : **nc crypto.heroctf.fr 9001**<br>
Format : **Hero{flag}**<br>
Author : **Alol**

### Files

- [halloween.zip](halloween.zip)

### Write up

The challenge prints the encrypted flag before repeatedly encrypting user-submitted data. On the surface this is not vulnerable code, the key and nonce are generated properly meaning that the keystream should be 2\*\*68 bytes long before the counter wraps around and the keystream repeats. We need to dig deeper.

Looking into the source code we find this function that, supposedly, increments the counter.
```py
# https://github.com/drobotun/gostcrypto/blob/master/gostcrypto/gostcipher/gost_34_13_2015.py#L841C5-L841C8
def _inc_ctr(self, ctr: bytearray) -> bytearray:
internal = 0
bit = bytearray(self.block_size)
bit[self.block_size - 1] = 0x01
for i in range(self.block_size):
internal = ctr[i] + bit[i] + (internal << 8)
ctr[i] = internal & 0xff
return ctr
```

The screenshot below taken from one of the tickets I handled illustrates clearly what's wrong.

<p align="center">
<img src="https://i.imgur.com/FfoNRKM.png">
</p>

The counter isn't incremented properly, effectively downgrading it from a 64 bit integer to a 8 bit integer. This, in turn, means that the keystream is now only 4096 bytes long. We can perform a chosen plaintext attack to recover the full keystream and decrypt the flag.

```py
from pwn import *

BS = 16
# io = process(["python3", "chall.py"])
io = remote("crypto.heroctf.fr", 9001)

io.recvuntil(b"It's almost Halloween, time to get sp00")
flag = bytes.fromhex(io.recvuntil(b"00ky")[:-4].decode())
io.recvline()

io.sendline(b"00" * BS * 256)
keystream = bytes.fromhex(io.recvlineS())

print(xor(flag, keystream[-(1 + len(flag) // BS) * BS :])[: len(flag)])
```

### Flag

```Hero{5p00ky_5c4ry_fl4w3d_cryp70_1mpl3m3n74710ns_53nd_5h1v3r5_d0wn_y0ur_5p1n3}```
16 changes: 16 additions & 0 deletions Crypto/Halloween/chall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
import gostcrypto
import os

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

key, iv = os.urandom(32), os.urandom(8)
cipher = gostcrypto.gostcipher.new(
"kuznechik", key, gostcrypto.gostcipher.MODE_CTR, init_vect=iv
)

print(f"It's almost Halloween, time to get sp00{cipher.encrypt(flag).hex()}00ky 👻!")

while True:
print(cipher.encrypt(bytes.fromhex(input())).hex())
33 changes: 33 additions & 0 deletions Crypto/Halloween/challenge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: Halloween
author: Alol
category: Crypto
description: "Boo! Do you believe in ghosts ? I sure don't.<br><br>
Host : **nc crypto.heroctf.fr 9001**<br>
Format : **Hero{flag}**<br>
Author : **Alol**"
value: null
type: dynamic
extra:
initial: 500
decay: 100
minimum: 50

image: null
host: null

flags:
- {
type: "static",
content: "Hero{5p00ky_5c4ry_fl4w3d_cryp70_1mpl3m3n74710ns_53nd_5h1v3r5_d0wn_y0ur_5p1n3}",
data: "case_insensitive",
}

tags:
- hard

files:
- halloween.zip

state: visible
version: "0.1"
6 changes: 6 additions & 0 deletions Crypto/Halloween/entry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#! /bin/sh

while :
do
socat TCP-LISTEN:${LISTEN_PORT},forever,reuseaddr,fork EXEC:'/app/chall.py' 2>/dev/null
done
1 change: 1 addition & 0 deletions Crypto/Halloween/flag.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hero{5p00ky_5c4ry_fl4w3d_cryp70_1mpl3m3n74710ns_53nd_5h1v3r5_d0wn_y0ur_5p1n3}
Binary file added Crypto/Halloween/halloween.zip
Binary file not shown.
15 changes: 15 additions & 0 deletions Crypto/Halloween/halloween/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.11-alpine

RUN apk --update add socat \
&& adduser -D --home /app user \
&& pip3 install gostcrypto

COPY --chown=user . /app

RUN chmod 755 /app/entry.sh /app/chall.py

WORKDIR /app

EXPOSE ${LISTEN_PORT}

ENTRYPOINT ["/app/entry.sh"]
16 changes: 16 additions & 0 deletions Crypto/Halloween/halloween/chall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
import gostcrypto
import os

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

key, iv = os.urandom(32), os.urandom(8)
cipher = gostcrypto.gostcipher.new(
"kuznechik", key, gostcrypto.gostcipher.MODE_CTR, init_vect=iv
)

print(f"It's almost Halloween, time to get sp00{cipher.encrypt(flag).hex()}00ky 👻!")

while True:
print(cipher.encrypt(bytes.fromhex(input())).hex())
6 changes: 6 additions & 0 deletions Crypto/Halloween/halloween/entry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#! /bin/sh

while :
do
socat TCP-LISTEN:${LISTEN_PORT},forever,reuseaddr,fork EXEC:'/app/chall.py' 2>/dev/null
done
Loading

0 comments on commit 2dda7f9

Please sign in to comment.