crackmes support + fixes

This commit is contained in:
√(noham)²
2025-02-17 02:42:06 +01:00
parent 0c5d60856f
commit 6c1ed3230c
6 changed files with 339 additions and 12 deletions

View File

@@ -5,9 +5,13 @@ A Python tool to automatically generate CTF writeup templates and organize chall
### Supported CTF Websites : ### Supported CTF Websites :
- https://hackropole.fr - https://hackropole.fr
- https://theblackside.fr - https://theblackside.fr
- https://crackmes.one
### Will add : ### Will add :
- https://imaginaryctf.org - https://imaginaryctf.org
- https://challenges.ecsc.eu/challenges
- https://www.root-me.org
- https://www.hackthissite.org
## Features ## Features

16
main.py
View File

@@ -1,5 +1,6 @@
from src.platforms.hackropole import HackropolePlatform from src.platforms.hackropole import HackropolePlatform
from src.platforms.theblackside import TheBlackSidePlatform from src.platforms.theblackside import TheBlackSidePlatform
from src.platforms.crackmes import CrackmesPlatform
from src.generator import WriteupGenerator from src.generator import WriteupGenerator
from pathlib import Path from pathlib import Path
@@ -17,12 +18,21 @@ def hackropole():
def theblackside(): def theblackside():
challenge_url = 'https://theblackside.fr/challenges/steganographie/Meow' challenge_url = 'https://theblackside.fr/challenges/steganographie/Meow'
platform = TheBlackSidePlatform(cookies_file="cookies.json") platform = TheBlackSidePlatform(cookies_file="theblackside.cookies.json")
generator = WriteupGenerator(platform, Path("./writeups"))
generator.fetch_challenge(challenge_url=challenge_url)
generator.generate_writeup_structure(hugo_header=True, translated=True)
def crackmes():
challenge_url = 'https://crackmes.one/crackme/6784f8a84d850ac5f7dc5173'
platform = CrackmesPlatform()
generator = WriteupGenerator(platform, Path("./writeups")) generator = WriteupGenerator(platform, Path("./writeups"))
generator.fetch_challenge(challenge_url=challenge_url) generator.fetch_challenge(challenge_url=challenge_url)
print(generator.challenges) print(generator.challenges)
generator.generate_writeup_structure(hugo_header=True, translated=True) generator.generate_writeup_structure(hugo_header=True, translated=True)
theblackside() # theblackside()
# hackropole() # hackropole()
crackmes()

264
src/platforms/crackmes.py Normal file
View File

@@ -0,0 +1,264 @@
from typing import List, Dict
from pathlib import Path
import requests
from .base import CTFPlatform
from ..models import Challenge, File
from ..utils.config_handler import load_config
from ..utils.challenge_handler import download_files
from bs4 import BeautifulSoup
import unicodedata
from datetime import datetime
import textwrap
import re
class CrackmesPlatform(CTFPlatform):
def __init__(self, url: str = "https://crackmes.one"):
super().__init__(url)
self.headers = {}
def login(self) -> bool:
"""Not implementing login since we don't need it :D"""
return True
def get_challenges(self) -> List[Challenge]:
raise NotImplementedError("Method not implemented")
def get_challenge(self, challenge_url: str) -> Challenge:
"""Get a specific challenge by URL"""
try:
response = self.session.get(challenge_url, headers=self.headers)
if response.status_code != 200:
raise Exception(
f"Error fetching challenge {challenge_url}: {response.status_code}"
)
except requests.RequestException as e:
raise Exception(f"Error fetching challenge {challenge_url}: {e}")
response.encoding = "utf-8" # Force UTF-8 encoding
soup = BeautifulSoup(response.text, "html.parser")
container = soup.find('div', class_='container grid-lg wrapper')
download_link = soup.find('a', class_='btn-download')
author_element = container.find('a', href=re.compile(r'/user/'))
author_name = author_element.text if author_element else None
title_element = container.find('h3')
name = title_element.text.strip().replace("{author_name}'s ".format(author_name=author_name), "") if title_element else None
id = f"{name}-{author_name}"
description_element = container.find('span', style='white-space: pre-line')
description = description_element.text.strip() if description_element else None
difficulty_element = container.find('p', text=re.compile('Difficulty:'))
difficulty = float(difficulty_element.text.replace('Difficulty:', '').strip()) if difficulty_element else None
if download_link:
file = File(
name = download_link['href'].split('/')[-1],
url = "https://crackmes.one" + download_link['href'],
hash=None,
)
columns = container.find_all('div', class_='column')
def get_text_after_br(element):
if not element:
return None
br = element.find('br')
if br and br.next_sibling:
return br.next_sibling.strip()
return None
platform = None
language = None
architecture = None
quality = None
difficulty = None
for column in columns:
p_tag = column.find('p')
if not p_tag:
continue
text = p_tag.text.strip()
if text.startswith('Language:'):
language = get_text_after_br(p_tag)
elif text.startswith('Platform'):
platform = get_text_after_br(p_tag)
elif text.startswith('Arch:'):
architecture = get_text_after_br(p_tag)
elif text.startswith('Quality:'):
quality_text = get_text_after_br(p_tag)
if quality_text:
try:
quality = float(quality_text)
except ValueError:
quality = None
elif text.startswith('Difficulty:'):
difficulty_text = get_text_after_br(p_tag)
if difficulty_text:
try:
difficulty = float(difficulty_text)
except ValueError:
difficulty = None
additional_info = {
'platform': platform,
'language': language,
'architecture': architecture,
'quality': quality,
}
print('additional_info: ', additional_info)
return Challenge(
id=id,
url=challenge_url,
platform="Crackmes",
name=name,
author=author_name,
category="Reverse",
description=description,
difficulty=difficulty,
points=None,
files=[file],
additional_info=additional_info,
)
def download_challenge_files(self, challenge: Challenge, output_dir: Path):
download_files(self, challenge, output_dir, password="crackmes.one")
def get_difficulty_str(self, difficulty):
if difficulty is None:
return None
if difficulty < 2:
return "easy"
elif difficulty < 3:
return "medium"
elif difficulty < 4:
return "hard"
else:
return "very hard"
def generate_tags(self, challenge):
tags = []
tags.append(challenge.category)
tags.append("Crackmes")
if challenge.additional_info["platform"]:
tags.append(challenge.additional_info["platform"])
if challenge.additional_info["language"]:
tags.append(challenge.additional_info["language"])
if challenge.additional_info["architecture"]:
tags.append(challenge.additional_info["architecture"])
tags = list(set(filter(None, tags)))
tags_str = '", "'.join(tags)
return tags_str
def generate_template(self, challenge: Challenge, hugo_header: bool = False, translated: bool = False):
"""Generate writeup template for challenge"""
tags_str = self.generate_tags(challenge)
difficulty_str = self.get_difficulty_str(challenge.difficulty)
hugo_header_template = textwrap.dedent(
f"""\
---
title: "{challenge.name}"
date: "{datetime.now().isoformat()}"
tags: ["{tags_str}"]
author: "Noham"
summary: "Writeup for {challenge.name} from {challenge.platform}. A "{difficulty_str}" challenge."
showToc: false
TocOpen: false
draft: false
hidemeta: false
comments: true
disableHLJS: false
disableShare: false
hideSummary: false
searchHidden: false
ShowReadingTime: true
ShowBreadCrumbs: true
searchHidden: true
ShowPostNavLinks: true
ShowWordCount: true
ShowRssButtonInSectionTermList: true
UseHugoToc: true
---
"""
)
hugo_header_template_fr = textwrap.dedent(
f"""\
---
title: "{challenge.name}"
date: "{datetime.now().isoformat()}"
tags: ["{tags_str}"]
author: "Noham"
summary: "Writeup pour {challenge.name} de {challenge.platform}. Un challenge {difficulty_str}."
showToc: false
TocOpen: false
draft: false
hidemeta: false
comments: true
disableHLJS: false
disableShare: false
hideSummary: false
searchHidden: false
ShowReadingTime: true
ShowBreadCrumbs: true
searchHidden: true
ShowPostNavLinks: true
ShowWordCount: true
ShowRssButtonInSectionTermList: true
UseHugoToc: true
---
"""
)
files_section = ""
for file in challenge.files:
hash_text = f" *(SHA256: {file.hash})*" if file.hash else ""
files_section += f"[{file.name}]({file.url}){hash_text}, "
files_section = files_section.rstrip(", ")
main_content = textwrap.dedent(
f"""\
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
- Author: {challenge.author}
- Category: {challenge.category}
- Challenge description: {challenge.description}
- Difficulty: {challenge.difficulty}/5
- Files provided: {files_section}
## Writeup
"""
)
main_content_fr = textwrap.dedent(
f"""\
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
- Auteur: {challenge.author}
- Catégorie: {challenge.category}
- Description du challenge: {challenge.description}
- Difficulté: {challenge.difficulty}/5
- Fichiers fournis: {files_section}
## Writeup
"""
)
if hugo_header:
challenge.template = hugo_header_template + main_content
if translated:
challenge.template_translated = hugo_header_template_fr + main_content_fr
else:
challenge.template = main_content
if translated:
challenge.template_translated = main_content_fr

View File

@@ -164,7 +164,7 @@ class HackropolePlatform(CTFPlatform):
def generate_template(self, challenge: Challenge, hugo_header: bool = False, translated: bool = False): def generate_template(self, challenge: Challenge, hugo_header: bool = False, translated: bool = False):
"""Generate writeup template for challenge""" """Generate writeup template for challenge"""
all_tags = list(set([challenge.category] + challenge.additional_info["badges"])) # Remove duplicates all_tags = list(set([challenge.category] + "Hackropole" + challenge.additional_info["badges"])) # Remove duplicates
tags_str = '", "'.join(all_tags) tags_str = '", "'.join(all_tags)
hugo_header_template = textwrap.dedent( hugo_header_template = textwrap.dedent(
@@ -233,11 +233,11 @@ class HackropolePlatform(CTFPlatform):
main_content = textwrap.dedent( main_content = textwrap.dedent(
f"""\ f"""\
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
- Author: {challenge.author} - Author: {challenge.author}
- Category: {challenge.category} - Category: {challenge.category}
- Challenge description: {challenge.description} - Challenge description: {challenge.description}
- Difficulty: {stars} ({challenge.difficulty if challenge.difficulty > 0 else 1}/5) - Difficulty: {stars} ({challenge.difficulty if challenge.difficulty > 0 else 1}/5)
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
- Files provided: {files_section} - Files provided: {files_section}
## Writeup ## Writeup
@@ -246,11 +246,11 @@ class HackropolePlatform(CTFPlatform):
main_content_fr = textwrap.dedent( main_content_fr = textwrap.dedent(
f"""\ f"""\
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
- Auteur: {challenge.author} - Auteur: {challenge.author}
- Catégorie: {challenge.category} - Catégorie: {challenge.category}
- Description du challenge: {challenge.description} - Description du challenge: {challenge.description}
- Difficulté: {stars} ({challenge.difficulty if challenge.difficulty > 0 else 1}/5) - Difficulté: {stars} ({challenge.difficulty if challenge.difficulty > 0 else 1}/5)
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
- Fichiers fournis: {files_section} - Fichiers fournis: {files_section}
## Writeup ## Writeup

View File

@@ -202,12 +202,12 @@ class TheBlackSidePlatform(CTFPlatform):
main_content = textwrap.dedent( main_content = textwrap.dedent(
f"""\ f"""\
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
- Author: {challenge.author} - Author: {challenge.author}
- Category: {challenge.category} - Category: {challenge.category}
- Challenge description: {challenge.description} - Challenge description: {challenge.description}
- Points: {challenge.points} - Points: {challenge.points}
- Solved by: {challenge.solved_number} users - Solved by: {challenge.solved_number} users
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
- Files provided: {files_section} - Files provided: {files_section}
## Writeup ## Writeup
@@ -216,12 +216,12 @@ class TheBlackSidePlatform(CTFPlatform):
main_content_fr = textwrap.dedent( main_content_fr = textwrap.dedent(
f"""\ f"""\
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
- Auteur: {challenge.author} - Auteur: {challenge.author}
- Catégorie: {challenge.category} - Catégorie: {challenge.category}
- Description du challenge: {challenge.description} - Description du challenge: {challenge.description}
- Points: {challenge.points} - Points: {challenge.points}
- Résolu par: {challenge.solved_number} utilisateurs - Résolu par: {challenge.solved_number} utilisateurs
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
- Fichiers fournis: {files_section} - Fichiers fournis: {files_section}
## Writeup ## Writeup

View File

@@ -2,18 +2,67 @@ from ..models import Challenge, File
from typing import List from typing import List
from pathlib import Path from pathlib import Path
import requests import requests
import zipfile
import os
def download_files(self, challenge: Challenge, destination: Path) -> List[Path]: def download_files(self, challenge: Challenge, destination: Path, password: str = None) -> List[Path]:
"""Download challenge files to the specified directory""" """
Download challenge files to the specified directory and handle zip extraction
Args:
challenge (Challenge): Challenge object containing files
destination (Path): Destination directory path
password (str, optional): Password for encrypted zip files
Returns:
List[Path]: List of paths to downloaded/extracted files
"""
for file in challenge.files: for file in challenge.files:
file_url = file.url file_url = file.url
name = file.name name = file.name
name = name.replace("public.yml", ".yml") name = name.replace("public.yml", ".yml")
try: try:
response = self.session.get(file_url) response = self.session.get(file_url)
if response.status_code == 200: if response.status_code == 200:
file_path = destination / name file_path = destination / name
file_path.write_bytes(response.content) file_path.write_bytes(response.content)
print(f"Downloaded {name} to {destination}") print(f"Downloaded {name} to {file_path}")
# Handle zip files
if name.lower().endswith('.zip'):
try:
with zipfile.ZipFile(file_path, 'r') as zip_ref:
# Check if zip is password protected
if zip_ref.namelist()[0].endswith('/'):
is_encrypted = False
else:
try:
zip_ref.read(zip_ref.namelist()[0])
is_encrypted = False
except RuntimeError:
is_encrypted = True
# Extract file
if is_encrypted and password:
zip_ref.extractall(
path=destination,
pwd=password.encode('utf-8')
)
print(f"Extracted encrypted zip {name} with password")
elif not is_encrypted:
zip_ref.extractall(path=destination)
print(f"Extracted zip {name}")
else:
print(f"Zip file {name} is encrypted but no password provided")
for extracted_file in zip_ref.namelist():
extracted_path = destination / extracted_file
if extracted_path.is_file():
print(f"Extracted file {extracted_file}")
except zipfile.BadZipFile:
raise f"Error: {name} is not a valid zip file or password is incorrect"
except requests.RequestException as e: except requests.RequestException as e:
raise f"Error downloading file {file_url}: {e}" raise Exception(f"Error downloading file {file_url}: {e}")