mirror of
https://github.com/NohamR/LetCTF.git
synced 2026-05-25 04:07:16 +00:00
theblackside support
This commit is contained in:
@@ -4,10 +4,10 @@ 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
|
||||||
|
|
||||||
### Will add :
|
### Will add :
|
||||||
- https://imaginaryctf.org
|
- https://imaginaryctf.org
|
||||||
- https://theblackside.fr
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|||||||
22
main.py
22
main.py
@@ -1,12 +1,13 @@
|
|||||||
from src.platforms.hackropole import HackropolePlatform
|
from src.platforms.hackropole import HackropolePlatform
|
||||||
|
from src.platforms.theblackside import TheBlackSidePlatform
|
||||||
from src.generator import WriteupGenerator
|
from src.generator import WriteupGenerator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
challenge_url = 'https://hackropole.fr/fr/challenges/reverse/fcsc2023-reverse-chaussette-xs/'
|
def hackropole():
|
||||||
# challenge_url = 'https://hackropole.fr/fr/challenges/crypto/fcsc2022-crypto-t-rex/'
|
challenge_url = 'https://hackropole.fr/fr/challenges/reverse/fcsc2023-reverse-chaussette-xs/'
|
||||||
# challenge_url = 'https://hackropole.fr/fr/challenges/crypto/fcsc2022-crypto-a-laise/'
|
# challenge_url = 'https://hackropole.fr/fr/challenges/crypto/fcsc2022-crypto-t-rex/'
|
||||||
|
# challenge_url = 'https://hackropole.fr/fr/challenges/crypto/fcsc2022-crypto-a-laise/'
|
||||||
|
|
||||||
def main():
|
|
||||||
platform = HackropolePlatform()
|
platform = HackropolePlatform()
|
||||||
|
|
||||||
generator = WriteupGenerator(platform, Path("./writeups"))
|
generator = WriteupGenerator(platform, Path("./writeups"))
|
||||||
@@ -14,5 +15,14 @@ def main():
|
|||||||
print(generator.challenges)
|
print(generator.challenges)
|
||||||
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def theblackside():
|
||||||
main()
|
challenge_url = 'https://theblackside.fr/challenges/steganographie/Meow'
|
||||||
|
platform = TheBlackSidePlatform(cookies_file="cookies.json")
|
||||||
|
|
||||||
|
generator = WriteupGenerator(platform, Path("./writeups"))
|
||||||
|
generator.fetch_challenge(challenge_url=challenge_url)
|
||||||
|
print(generator.challenges)
|
||||||
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
|
theblackside()
|
||||||
|
# hackropole()
|
||||||
@@ -16,6 +16,7 @@ class Challenge:
|
|||||||
additional_info: Dict = None
|
additional_info: Dict = None
|
||||||
template: str = None
|
template: str = None
|
||||||
template_translated: str = None
|
template_translated: str = None
|
||||||
|
solved_number: int = 0
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class File:
|
class File:
|
||||||
|
|||||||
@@ -233,11 +233,11 @@ class HackropolePlatform(CTFPlatform):
|
|||||||
|
|
||||||
main_content = textwrap.dedent(
|
main_content = textwrap.dedent(
|
||||||
f"""\
|
f"""\
|
||||||
- Author: {challenge.author}
|
- Author: {challenge.author}
|
||||||
- Category: {challenge.category}
|
- Category: {challenge.category}
|
||||||
- Difficulty: {stars} ({challenge.difficulty if challenge.difficulty > 0 else 1}/5)
|
|
||||||
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
|
|
||||||
- Challenge description: {challenge.description}
|
- Challenge description: {challenge.description}
|
||||||
|
- 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
|
||||||
@@ -248,9 +248,9 @@ class HackropolePlatform(CTFPlatform):
|
|||||||
f"""\
|
f"""\
|
||||||
- Auteur: {challenge.author}
|
- Auteur: {challenge.author}
|
||||||
- Catégorie: {challenge.category}
|
- Catégorie: {challenge.category}
|
||||||
|
- 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})
|
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
|
||||||
- Description du challenge: {challenge.description}
|
|
||||||
- Fichiers fournis: {files_section}
|
- Fichiers fournis: {files_section}
|
||||||
|
|
||||||
## Writeup
|
## Writeup
|
||||||
|
|||||||
238
src/platforms/theblackside.py
Normal file
238
src/platforms/theblackside.py
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
from typing import List, Dict
|
||||||
|
from pathlib import Path
|
||||||
|
import requests
|
||||||
|
from .base import CTFPlatform
|
||||||
|
from ..models import Challenge, File
|
||||||
|
from ..utils.cookie_handler import load_cookies_from_file
|
||||||
|
from ..utils.challenge_handler import download_files
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import unicodedata
|
||||||
|
from datetime import datetime
|
||||||
|
import textwrap
|
||||||
|
import re
|
||||||
|
|
||||||
|
class TheBlackSidePlatform(CTFPlatform):
|
||||||
|
def __init__(
|
||||||
|
self, url: str = "https://theblackside.fr/", cookies_file: str | Path = None
|
||||||
|
):
|
||||||
|
super().__init__(url)
|
||||||
|
if cookies_file:
|
||||||
|
self.cookie = load_cookies_from_file(cookies_file)
|
||||||
|
else:
|
||||||
|
raise Exception("No cookies provided")
|
||||||
|
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",
|
||||||
|
"referer": "https://theblackside.fr/",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""
|
||||||
|
Not implementing traditional login as we're using cookies
|
||||||
|
Returns True if we can access authenticated endpoints
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self.session.get(
|
||||||
|
"https://theblackside.fr/",
|
||||||
|
headers=self.headers,
|
||||||
|
cookies=self.cookie,
|
||||||
|
)
|
||||||
|
if "/profil/" in response.text:
|
||||||
|
print("Logged in")
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
raise Exception("Failed to login")
|
||||||
|
|
||||||
|
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, cookies=self.cookie)
|
||||||
|
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")
|
||||||
|
|
||||||
|
main = soup.find('main')
|
||||||
|
|
||||||
|
title = main.find('h1').text.strip()
|
||||||
|
id = "-".join([word.lower() for word in title.split()])
|
||||||
|
description = main.find('p').text.strip()
|
||||||
|
|
||||||
|
author_link = main.find('a', href=re.compile(r'/profil/'))
|
||||||
|
author_name = author_link.find('span').find('a').text.strip() if author_link else None
|
||||||
|
|
||||||
|
metadata_div = main.find('div', class_='metadata')
|
||||||
|
points = int(metadata_div.find('div', class_='button').find('span').text.strip())
|
||||||
|
solved_number = int([div for div in metadata_div.find_all('div', class_='button')
|
||||||
|
if div.find('svg', class_='feather-check-circle')][0]
|
||||||
|
.find('span').text.strip())
|
||||||
|
|
||||||
|
category_button = metadata_div.find('a', href=re.compile(r'/challenges/'))
|
||||||
|
category = category_button.find('span').text.strip() if category_button else "Uncategorized"
|
||||||
|
|
||||||
|
categorydict = {
|
||||||
|
"Web": "Web",
|
||||||
|
"Stéganographie": "Steganography",
|
||||||
|
"Cryptographie": "Cryptography",
|
||||||
|
"Reverse": "Reverse",
|
||||||
|
"Réseau": "Network",
|
||||||
|
"Forensic": "Forensic",
|
||||||
|
"Développement": "Development",
|
||||||
|
"Pwn": "Pwn",
|
||||||
|
"Box": "Box",
|
||||||
|
"Divers": "Miscellaneous",
|
||||||
|
}
|
||||||
|
category = categorydict.get(category, category)
|
||||||
|
|
||||||
|
|
||||||
|
file_url = main.find('a', class_='startChall')['href']
|
||||||
|
|
||||||
|
file = File(
|
||||||
|
name=file_url.split('/')[-1],
|
||||||
|
url=file_url,
|
||||||
|
hash=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return Challenge(
|
||||||
|
id=id,
|
||||||
|
url=challenge_url,
|
||||||
|
platform="TheBlackSide",
|
||||||
|
name=title,
|
||||||
|
author=author_name,
|
||||||
|
category=category if category else "Uncategorized",
|
||||||
|
description=description,
|
||||||
|
difficulty=None,
|
||||||
|
solved_number=solved_number,
|
||||||
|
points=points,
|
||||||
|
files=[file],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def download_challenge_files(self, challenge: Challenge, output_dir: Path):
|
||||||
|
download_files(self, challenge, output_dir)
|
||||||
|
|
||||||
|
def generate_template(self, challenge: Challenge, hugo_header: bool = False, translated: bool = False):
|
||||||
|
"""Generate writeup template for challenge"""
|
||||||
|
|
||||||
|
tags = [challenge.category, "TheBlackSide"]
|
||||||
|
tags_str = ", ".join(tags)
|
||||||
|
|
||||||
|
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.points} points {challenge.category.lower()} challenge with {challenge.solved_number} solves."
|
||||||
|
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()} à {challenge.points} points avec {challenge.solved_number} résolutions."
|
||||||
|
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"""\
|
||||||
|
- Author: {challenge.author}
|
||||||
|
- Category: {challenge.category}
|
||||||
|
- Challenge description: {challenge.description}
|
||||||
|
- Points: {challenge.points}
|
||||||
|
- Solved by: {challenge.solved_number} users
|
||||||
|
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
|
||||||
|
- Files provided: {files_section}
|
||||||
|
|
||||||
|
## Writeup
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
main_content_fr = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
- Auteur: {challenge.author}
|
||||||
|
- Catégorie: {challenge.category}
|
||||||
|
- Description du challenge: {challenge.description}
|
||||||
|
- Points: {challenge.points}
|
||||||
|
- Résolu par: {challenge.solved_number} utilisateurs
|
||||||
|
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
|
||||||
|
- 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
|
||||||
Reference in New Issue
Block a user