mirror of
https://github.com/NohamR/LetCTF.git
synced 2026-05-25 19:59:23 +00:00
fix + cattheflag support + crackmy support
This commit is contained in:
@@ -6,8 +6,10 @@ A Python tool to automatically generate CTF writeup templates and organize chall
|
|||||||
- https://hackropole.fr
|
- https://hackropole.fr
|
||||||
- https://theblackside.fr
|
- https://theblackside.fr
|
||||||
- https://crackmes.one
|
- https://crackmes.one
|
||||||
|
- https://crackmy.app
|
||||||
|
|
||||||
### Will add :
|
### Will add :
|
||||||
|
- https://cattheflag.org/defis.php
|
||||||
- https://imaginaryctf.org
|
- https://imaginaryctf.org
|
||||||
- https://challenges.ecsc.eu/challenges
|
- https://challenges.ecsc.eu/challenges
|
||||||
- https://www.root-me.org
|
- https://www.root-me.org
|
||||||
|
|||||||
30
main.py
30
main.py
@@ -1,14 +1,13 @@
|
|||||||
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.platforms.crackmes import CrackmesPlatform
|
||||||
|
from src.platforms.crackmy import CrackmyPlatform
|
||||||
|
from src.platforms.cattheflag import CatTheFlagPlatform
|
||||||
from src.generator import WriteupGenerator
|
from src.generator import WriteupGenerator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
def hackropole():
|
def hackropole():
|
||||||
challenge_url = 'https://hackropole.fr/fr/challenges/reverse/fcsc2023-reverse-chaussette-xs/'
|
challenge_url = 'https://hackropole.fr/fr/challenges/reverse/fcsc2023-reverse-chaussette-xs/'
|
||||||
# challenge_url = 'https://hackropole.fr/fr/challenges/crypto/fcsc2022-crypto-t-rex/'
|
|
||||||
# challenge_url = 'https://hackropole.fr/fr/challenges/crypto/fcsc2022-crypto-a-laise/'
|
|
||||||
|
|
||||||
platform = HackropolePlatform()
|
platform = HackropolePlatform()
|
||||||
|
|
||||||
generator = WriteupGenerator(platform, Path("./writeups"))
|
generator = WriteupGenerator(platform, Path("./writeups"))
|
||||||
@@ -33,6 +32,29 @@ def crackmes():
|
|||||||
print(generator.challenges)
|
print(generator.challenges)
|
||||||
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
|
def crackmy():
|
||||||
|
challenge_url = 'https://crackmy.app/crackmes/yet-another-packer-v1-5514'
|
||||||
|
# platform = CrackmyPlatform(config_file="crackmy.json")
|
||||||
|
platform = CrackmyPlatform()
|
||||||
|
|
||||||
|
generator = WriteupGenerator(platform, Path("./writeups"))
|
||||||
|
generator.fetch_challenge(challenge_url=challenge_url)
|
||||||
|
print(generator.challenges)
|
||||||
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
|
def cattheflag():
|
||||||
|
challenge_url = 'https://cattheflag.org/defis/reverse2.php'
|
||||||
|
platform = CatTheFlagPlatform(config_file="catthefile.json")
|
||||||
|
|
||||||
|
generator = WriteupGenerator(platform, Path("./writeups"))
|
||||||
|
generator.fetch_challenges() # Mandatory to get every information about a specific challenge for this platform
|
||||||
|
generator.fetch_challenge(challenge_url=challenge_url)
|
||||||
|
print(generator.challenges)
|
||||||
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
|
|
||||||
# theblackside()
|
# theblackside()
|
||||||
# hackropole()
|
# hackropole()
|
||||||
crackmes()
|
# crackmes()
|
||||||
|
# crackmy()
|
||||||
|
# cattheflag()
|
||||||
@@ -8,7 +8,7 @@ class WriteupGenerator:
|
|||||||
def __init__(self, platform: CTFPlatform, output_dir: Path):
|
def __init__(self, platform: CTFPlatform, output_dir: Path):
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
self.output_dir = output_dir
|
self.output_dir = output_dir
|
||||||
self.challenges = []
|
self.challenges = {}
|
||||||
|
|
||||||
def fetch_challenges(self):
|
def fetch_challenges(self):
|
||||||
"""Fetch all challenges from the platform"""
|
"""Fetch all challenges from the platform"""
|
||||||
@@ -27,6 +27,10 @@ class WriteupGenerator:
|
|||||||
platform_dir.mkdir(parents=True, exist_ok=True)
|
platform_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
challenge_dir = platform_dir / self._sanitize_filename(challenge.id)
|
challenge_dir = platform_dir / self._sanitize_filename(challenge.id)
|
||||||
|
if challenge_dir.exists():
|
||||||
|
print(f"Challenge directory for {challenge.id} already exists. Skipping...")
|
||||||
|
continue
|
||||||
|
|
||||||
challenge_dir.mkdir(exist_ok=True)
|
challenge_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
files_dir = challenge_dir / "files"
|
files_dir = challenge_dir / "files"
|
||||||
@@ -35,18 +39,11 @@ class WriteupGenerator:
|
|||||||
|
|
||||||
self.platform.generate_template(challenge, hugo_header, translated)
|
self.platform.generate_template(challenge, hugo_header, translated)
|
||||||
|
|
||||||
if (challenge_dir / "index.md").exists():
|
(challenge_dir / "index.md").write_text(challenge.template)
|
||||||
print(f"Writeup for {challenge.id} already exists. Skipping...")
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
(challenge_dir / "index.md").write_text(challenge.template)
|
|
||||||
|
|
||||||
if translated:
|
if translated:
|
||||||
if (challenge_dir / "index.fr.md").exists():
|
(challenge_dir / "index.fr.md").write_text(challenge.template_translated)
|
||||||
print(f"Writeup for {challenge.id} already exists. Skipping...")
|
print(f"Writeup for {challenge.id} has been generated in {challenge_dir}")
|
||||||
continue
|
|
||||||
else:
|
|
||||||
(challenge_dir / "index.fr.md").write_text(challenge.template_translated)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
276
src/platforms/cattheflag.py
Normal file
276
src/platforms/cattheflag.py
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
from typing import List, Dict
|
||||||
|
from pathlib import Path
|
||||||
|
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 requests
|
||||||
|
import re
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
class CatTheFlagPlatform(CTFPlatform):
|
||||||
|
def __init__(self, url: str = "https://cattheflag.org", config_file: str | Path = None):
|
||||||
|
super().__init__(url)
|
||||||
|
self.url = url
|
||||||
|
self.headers = {
|
||||||
|
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||||
|
'accept-language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
|
'origin': 'https://cattheflag.org',
|
||||||
|
'pragma': 'no-cache',
|
||||||
|
'priority': 'u=0, i',
|
||||||
|
'referer': 'https://cattheflag.org/connexion.php',
|
||||||
|
'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
|
'sec-fetch-dest': 'document',
|
||||||
|
'sec-fetch-mode': 'navigate',
|
||||||
|
'sec-fetch-site': 'same-origin',
|
||||||
|
'sec-fetch-user': '?1',
|
||||||
|
'upgrade-insecure-requests': '1',
|
||||||
|
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
|
||||||
|
}
|
||||||
|
if config_file:
|
||||||
|
self.load_config(config_file)
|
||||||
|
self.csrf_token = self.get_csrf_token()
|
||||||
|
self.login()
|
||||||
|
else:
|
||||||
|
raise Exception("No configuration file provided")
|
||||||
|
|
||||||
|
def get_csrf_token(self) -> str:
|
||||||
|
try:
|
||||||
|
response = self.session.get('https://cattheflag.org/connexion.php', headers=self.headers)
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
csrf_token = soup.find('input', {'name': 'csrf_token'})['value']
|
||||||
|
return csrf_token
|
||||||
|
except requests.RequestException:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_config(self, config_file: str | Path):
|
||||||
|
"""Load configuration from file"""
|
||||||
|
config = load_config(config_file)
|
||||||
|
self.email = config.get("email")
|
||||||
|
self.password = config.get("password")
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
data = {
|
||||||
|
'csrf_token': self.csrf_token,
|
||||||
|
'email': self.email,
|
||||||
|
'mot_de_passe': self.password,
|
||||||
|
}
|
||||||
|
response = self.session.post('https://cattheflag.org/connexion.php', headers=self.headers, data=data)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise f"Error logging in: {response.status_code}"
|
||||||
|
else:
|
||||||
|
print("Successfully logged in")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_challenges(self) -> List[Challenge]:
|
||||||
|
"""Get all challenges from platform"""
|
||||||
|
try:
|
||||||
|
response = self.session.get('https://cattheflag.org/defis.php', headers=self.headers)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"Error fetching challenges: {response.status_code}")
|
||||||
|
except requests.RequestException as e:
|
||||||
|
raise Exception(f"Error fetching challenges: {e}")
|
||||||
|
|
||||||
|
response.encoding = "utf-8"
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
challenges = {}
|
||||||
|
|
||||||
|
challenge_sections = soup.find_all('div', class_='challeng__wrap')
|
||||||
|
|
||||||
|
for section in challenge_sections:
|
||||||
|
category = section.find('h3').text.strip()
|
||||||
|
if category != 'Histoire':
|
||||||
|
table = section.find('table')
|
||||||
|
rows = table.find_all('tr')[1:]
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
cols = row.find_all(['th', 'td'])
|
||||||
|
|
||||||
|
link = cols[3].find('a')
|
||||||
|
url = link['href'] if link else None
|
||||||
|
|
||||||
|
challenge_name = cols[0].text.strip()
|
||||||
|
challenge_id = url.split('/')[-1].replace('.php', '') if url else re.sub(r'[^a-zA-Z0-9]', '-', challenge_name.lower())
|
||||||
|
|
||||||
|
challenge_url = self.base_url + url
|
||||||
|
challenges[challenge_url] = Challenge(
|
||||||
|
id=challenge_id,
|
||||||
|
url=challenge_url,
|
||||||
|
platform="CatTheFlag",
|
||||||
|
name=challenge_name,
|
||||||
|
author=None,
|
||||||
|
category=category,
|
||||||
|
description=None,
|
||||||
|
difficulty=cols[1].text.strip(),
|
||||||
|
points=int(cols[2].text.strip()),
|
||||||
|
files=None,
|
||||||
|
additional_info={'validation_rate': float(cols[4].text.strip().replace('%', ''))}
|
||||||
|
)
|
||||||
|
self.challenges = challenges
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
title = soup.find('h1').text.strip()
|
||||||
|
|
||||||
|
description = soup.find('p', style='color:white').text.strip()
|
||||||
|
|
||||||
|
author_link = soup.find('a', href=lambda x: x and 'page_membre.php' in x)
|
||||||
|
author_name = author_link.text.strip() if author_link else None
|
||||||
|
|
||||||
|
file_link = soup.find('a', href=lambda x: x and 'cdn.cattheflag.org' in x)
|
||||||
|
if file_link:
|
||||||
|
files = [File(
|
||||||
|
name=file_link['href'].split('/')[-1],
|
||||||
|
url=file_link['href'],
|
||||||
|
hash=None
|
||||||
|
)]
|
||||||
|
else:
|
||||||
|
files = []
|
||||||
|
|
||||||
|
challenge = self.challenges.get(challenge_url)
|
||||||
|
challenge.description = description
|
||||||
|
challenge.author = author_name
|
||||||
|
challenge.files = files
|
||||||
|
return challenge
|
||||||
|
|
||||||
|
def download_challenge_files(self, challenge: Challenge, output_dir: Path):
|
||||||
|
download_files(self, challenge, output_dir)
|
||||||
|
|
||||||
|
def generate_tags(self, challenge):
|
||||||
|
tags = []
|
||||||
|
tags.append(challenge.category)
|
||||||
|
tags.append("CatTheFlag")
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 {challenge.category.lower()} challenge with a "{challenge.difficulty.lower()}" difficulty (sucess rate : {challenge.additional_info['validation_rate']}%)."
|
||||||
|
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 de {challenge.category.lower()} avec une difficulté {challenge.difficulty.lower()} (taux de réussite : {challenge.additional_info['validation_rate']}%)."
|
||||||
|
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
|
||||||
|
---
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
difficulty_stars = {
|
||||||
|
'facile': 1,
|
||||||
|
'simple': 2,
|
||||||
|
'medium': 3,
|
||||||
|
'difficile': 4
|
||||||
|
}
|
||||||
|
stars = "⭐" * difficulty_stars.get(challenge.difficulty.lower(), 1)
|
||||||
|
|
||||||
|
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: {stars} ({challenge.difficulty}, success rate: {challenge.additional_info['validation_rate']}%)
|
||||||
|
- 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é: {stars} ({challenge.difficulty}, taux de réussite : {challenge.additional_info['validation_rate']}%)
|
||||||
|
- 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
|
||||||
252
src/platforms/crackmy.py
Normal file
252
src/platforms/crackmy.py
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
from typing import List, Dict
|
||||||
|
from pathlib import Path
|
||||||
|
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 requests
|
||||||
|
|
||||||
|
class CrackmyPlatform(CTFPlatform):
|
||||||
|
def __init__(self, url: str = "https://crackmy.app", config_file: str | Path = None):
|
||||||
|
super().__init__(url)
|
||||||
|
self.url = url
|
||||||
|
if config_file:
|
||||||
|
self.load_config(config_file)
|
||||||
|
self.csrf_token = self.get_csrf_token()
|
||||||
|
self.login()
|
||||||
|
self.headers = {
|
||||||
|
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||||
|
'accept-language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
'pragma': 'no-cache',
|
||||||
|
'priority': 'u=0, i',
|
||||||
|
'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
|
'sec-fetch-dest': 'document',
|
||||||
|
'sec-fetch-mode': 'navigate',
|
||||||
|
'sec-fetch-site': 'same-origin',
|
||||||
|
'sec-fetch-user': '?1',
|
||||||
|
'upgrade-insecure-requests': '1',
|
||||||
|
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_csrf_token(self) -> str:
|
||||||
|
try:
|
||||||
|
response = self.session.get('https://crackmy.app/api/auth/csrf', headers=self.headers)
|
||||||
|
return response.json()['csrfToken']
|
||||||
|
except requests.RequestException:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_config(self, config_file: str | Path):
|
||||||
|
"""Load configuration from file"""
|
||||||
|
config = load_config(config_file)
|
||||||
|
self.email = config.get("email")
|
||||||
|
self.password = config.get("password")
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
data = {
|
||||||
|
'email': self.email,
|
||||||
|
'password': self.password,
|
||||||
|
'remember': 'false',
|
||||||
|
'redirect': 'false',
|
||||||
|
'csrfToken': self.csrf_token,
|
||||||
|
'callbackUrl': 'https://crackmy.app/',
|
||||||
|
'json': 'true'
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = self.session.post('https://crackmy.app/api/auth/callback/credentials', headers=self.headers, data=data).json()
|
||||||
|
if response['url'] != "https://crackmy.app/api/auth/error?error=CredentialsSignin&provider=credentials":
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except requests.RequestException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
api_url = challenge_url.replace('https://crackmy.app/crackmes/', 'https://crackmy.app/api/crackmes/')
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.session.get(api_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 = response.json()
|
||||||
|
title = response['title']
|
||||||
|
id = title.replace(' ', '-').lower()
|
||||||
|
author_name = response['author']['name']
|
||||||
|
description = response['description']
|
||||||
|
|
||||||
|
additional_info = {
|
||||||
|
'platform': response['os'],
|
||||||
|
'architecture': response['architecture'],
|
||||||
|
'quality': response['qualityRating'],
|
||||||
|
'category': response['category'],
|
||||||
|
'rating': response['rating'],
|
||||||
|
}
|
||||||
|
|
||||||
|
difficulty = {
|
||||||
|
"difficulty": response["difficulty"],
|
||||||
|
"difficultyRating": response["difficultyRating"],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = {"fileId":response['file']["id"]}
|
||||||
|
download_request = self.session.post('https://crackmy.app/api/download/create', headers=self.headers, json=data).json()
|
||||||
|
except requests.RequestException as e:
|
||||||
|
raise Exception(f"Error fetching challenge {challenge_url}: {e}")
|
||||||
|
file_url = self.base_url + download_request['url']
|
||||||
|
|
||||||
|
files = [
|
||||||
|
File(
|
||||||
|
name=response["file"]["fileName"],
|
||||||
|
url=file_url,
|
||||||
|
hash=response["file"]["fileSha256"],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return Challenge(
|
||||||
|
id=id,
|
||||||
|
url=challenge_url,
|
||||||
|
platform="Crackmy",
|
||||||
|
name=title,
|
||||||
|
author=author_name,
|
||||||
|
category="Reverse",
|
||||||
|
description=description,
|
||||||
|
difficulty=difficulty,
|
||||||
|
points=None,
|
||||||
|
files=files,
|
||||||
|
additional_info=additional_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
def download_challenge_files(self, challenge: Challenge, output_dir: Path):
|
||||||
|
download_files(self, challenge, output_dir)
|
||||||
|
|
||||||
|
def generate_tags(self, challenge):
|
||||||
|
tags = []
|
||||||
|
tags.append(challenge.category)
|
||||||
|
tags.append("Crackmy")
|
||||||
|
if challenge.additional_info["platform"]:
|
||||||
|
tags.append(challenge.additional_info["platform"])
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 {challenge.category.lower()} challenge with a {challenge.difficulty['difficulty'].lower()} difficulty ({challenge.difficulty['difficultyRating'] if challenge.difficulty['difficultyRating'] > 0 else 0}/10)."
|
||||||
|
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 de {challenge.category.lower()} avec une difficulté {challenge.difficulty['difficulty'].lower()} ({challenge.difficulty['difficultyRating'] if challenge.difficulty['difficultyRating'] > 0 else 0}/10)."
|
||||||
|
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
|
||||||
|
---
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
stars = "⭐" * (int(challenge.difficulty['difficultyRating']) // 2 if challenge.difficulty['difficultyRating'] > 0 else 1)
|
||||||
|
|
||||||
|
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: {stars} ({challenge.difficulty['difficultyRating'] if challenge.difficulty['difficultyRating'] > 0 else 1}/10)
|
||||||
|
- 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é: {stars} ({challenge.difficulty['difficultyRating'] if challenge.difficulty['difficultyRating'] > 0 else 1}/10)
|
||||||
|
- 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
|
||||||
@@ -12,9 +12,7 @@ import textwrap
|
|||||||
|
|
||||||
|
|
||||||
class HackropolePlatform(CTFPlatform):
|
class HackropolePlatform(CTFPlatform):
|
||||||
def __init__(
|
def __init__(self, url: str = "https://hackropole.fr", config_file: str | Path = None):
|
||||||
self, url: str = "https://hackropole.fr", config_file: str | Path = None
|
|
||||||
):
|
|
||||||
super().__init__(url)
|
super().__init__(url)
|
||||||
if config_file:
|
if config_file:
|
||||||
self.load_config(config_file)
|
self.load_config(config_file)
|
||||||
|
|||||||
Reference in New Issue
Block a user