# Tape ton MDP

**Catégorie:** Analyse forensique - **Difficulté:** Moyen

{% file src="/files/hoz6efB0TkvALIiydURb" %}

**Description:**

<figure><img src="/files/rMgUQiA1gzZWly71J1Mc" alt=""><figcaption></figcaption></figure>

Solution:

Ici nous avons une capture réseau qui exfiltre des données vers l'extérieur. À la vue du titre du chall ainsi que sa description, il s'agit d'un keylogger, nous allons donc devoir reconstituer ce que la personne a tapé au clavier.

Pour ce faire, après analyse manuelle (et avec une IA 🙃), nous trouvons les données intéressantes se trouvent à chaque requête HTTP vers `/upload` :&#x20;

<figure><img src="/files/cDbv21NbnEpBAdaOCJaS" alt=""><figcaption></figcaption></figure>

Maintenant pour déchiffrer tout cela, nous pouvons utiliser le script suivant :&#x20;

```python
import base64
import struct
import re

# AZERTY mapping based on standard US keycodes
AZERTY_LOWER = {
    1: 'ESC', 2: '&', 3: 'é', 4: '"', 5: "'", 6: '(', 7: '-', 8: 'è', 9: '_', 10: 'ç', 11: 'à',
    12: ')', 13: '=', 14: 'BACKSPACE', 15: 'TAB',
    16: 'a', 17: 'z', 18: 'e', 19: 'r', 20: 't', 21: 'y', 22: 'u', 23: 'i', 24: 'o', 25: 'p',
    26: '^', 27: '$', 28: '\n',  # KEY_ENTER
    29: 'L_CTRL', 30: 'q', 31: 's', 32: 'd', 33: 'f', 34: 'g', 35: 'h', 36: 'j', 37: 'k', 38: 'l',
    39: 'm', 40: 'ù', 41: '²', # Grave accent key (typically left of '1' on US layout, maps to '²')
    42: 'L_SHIFT',
    43: '*', # US backslash key, often '*' on AZERTY. Can also be '<' (keycode 86 for that usually)
    44: 'w', 45: 'x', 46: 'c', 47: 'v', 48: 'b', 49: 'n', 50: ',',
    51: ';', 52: ':', 53: '!', 54: 'R_SHIFT', 55: '*', # Numpad *
    56: 'L_ALT', 57: ' ', 58: 'CAPS_LOCK',
    59: 'F1', 60: 'F2', 61: 'F3', 62: 'F4', 63: 'F5', 64: 'F6', 65: 'F7', 66: 'F8', 67: 'F9', 68: 'F10',
    69: 'NUMLOCK', 70: 'SCROLLLOCK',
    71: '7', 72: '8', 73: '9', # Numpad
    74: '-', # Numpad -
    75: '4', 76: '5', 77: '6', # Numpad
    78: '+', # Numpad +
    79: '1', 80: '2', 81: '3', # Numpad
    82: '0', # Numpad 0
    83: '.', # Numpad .
    86: '<', # Often the key for < > on AZERTY
    96: '\n', # Numpad Enter
    98: '/', # Numpad /
}

AZERTY_UPPER = {
    1: 'ESC', 2: '1', 3: '2', 4: '3', 5: '4', 6: '5', 7: '6', 8: '7', 9: '8', 10: '9', 11: '0',
    12: '°', 13: '+', 14: 'BACKSPACE', 15: 'TAB',
    16: 'A', 17: 'Z', 18: 'E', 19: 'R', 20: 'T', 21: 'Y', 22: 'U', 23: 'I', 24: 'O', 25: 'P',
    26: '¨', 27: '£', 28: '\n', # KEY_ENTER
    29: 'L_CTRL', 30: 'Q', 31: 'S', 32: 'D', 33: 'F', 34: 'G', 35: 'H', 36: 'J', 37: 'K', 38: 'L',
    39: 'M', 40: '%', 41: '', # Shift+² is often nothing or same as unshifted ²
    42: 'L_SHIFT',
    43: 'µ', # If unshifted is '*'
    44: 'W', 45: 'X', 46: 'C', 47: 'V', 48: 'B', 49: 'N', 50: '?',
    51: '.', 52: '/', 53: '§', 54: 'R_SHIFT', 55: '*', # Numpad *
    56: 'L_ALT', 57: ' ', 58: 'CAPS_LOCK',
    59: 'F1', 60: 'F2', 61: 'F3', 62: 'F4', 63: 'F5', 64: 'F6', 65: 'F7', 66: 'F8', 67: 'F9', 68: 'F10',
    69: 'NUMLOCK', 70: 'SCROLLLOCK',
    71: '7', 72: '8', 73: '9', # Numpad
    74: '-', # Numpad -
    75: '4', 76: '5', 77: '6', # Numpad
    78: '+', # Numpad +
    79: '1', 80: '2', 81: '3', # Numpad
    82: '0', # Numpad 0
    83: '.', # Numpad .
    86: '>', # Often the key for < > on AZERTY
    96: '\n', # Numpad Enter
    98: '/', # Numpad /
}

EV_KEY = 0x01
KEY_LEFTSHIFT = 42
KEY_RIGHTSHIFT = 54

def solve():
    collected_raw_tokens = []
    try:
        with open("http_data.txt", "r") as f:
            for line in f:
                line_stripped = line.strip()
                if line_stripped:
                    # Split by comma, but also handle cases where base64 strings might be concatenated
                    # by multipart boundaries if not perfectly clean before.
                    # The previous version's split was likely fine, this is just to be robust.
                    parts = line_stripped.split(',')
                    for part in parts:
                        # Remove potential multipart boundary remnants if any, though unlikely here
                        # as tshark extracts http.file_data which should be cleaner.
                        if '--------------------------' in part:
                            sub_parts = re.split(r"--------------------------[a-f0-9]+(?:--)?\\r\\n", part)
                            for sp in sub_parts:
                                if sp.strip(): collected_raw_tokens.append(sp.strip())
                        else:
                            if part.strip(): collected_raw_tokens.append(part.strip())

    except FileNotFoundError:
        print("Error: http_data.txt not found.")
        return

    if not collected_raw_tokens:
        print("No raw tokens extracted from http_data.txt.")
        return

    all_data_tokens = []
    for token_from_file in collected_raw_tokens:
        token = token_from_file.strip()
        if not token:
            continue

        processed_b64_part = ""
        # Handle the "bGxISEk=" prefix if present
        if token.startswith("bGxISEk=") and len(token) > 8:
            potential_part = token[8:]
            if len(potential_part) == 32:
                processed_b64_part = potential_part
        elif len(token) == 32:
            processed_b64_part = token
        
        if processed_b64_part:
            try:
                # Validate base64 characters and padding
                if not re.fullmatch(r'[A-Za-z0-9+/]*={0,2}', processed_b64_part):
                    continue
                base64.b64decode(processed_b64_part) # Test decode
                all_data_tokens.append(processed_b64_part)
            except Exception:
                pass # Invalid base64

    if not all_data_tokens:
        print("No valid 32-character Base64 data tokens found after filtering and validation.")
        return

    password_chars = []
    shift_pressed = False

    for b64_event_data in all_data_tokens:
        try:
            decoded_data = base64.b64decode(b64_event_data)
            if len(decoded_data) != 24: # sizeof(struct input_event) typically 24 bytes
                continue
            
            # struct input_event: timeval (long long, long long), type (ushort), code (ushort), value (uint)
            # Format: <QQHHI (little-endian, 2x unsigned long long, 2x unsigned short, unsigned int)
            _, _, type_val, code_val, val_val = struct.unpack("<QQHHI", decoded_data)

            if type_val == EV_KEY:
                if code_val == KEY_LEFTSHIFT or code_val == KEY_RIGHTSHIFT:
                    if val_val == 1: # Press
                        shift_pressed = True
                    elif val_val == 0: # Release
                        shift_pressed = False
                elif val_val == 1: # Key press (value=0 is release, value=2 is repeat)
                    char_to_add = None
                    current_map = AZERTY_UPPER if shift_pressed else AZERTY_LOWER
                    
                    char_to_add = current_map.get(code_val)
                    
                    # Fallback for keys that might not be in UPPER map but are in LOWER (e.g. space, enter)
                    if shift_pressed and char_to_add is None and code_val in AZERTY_LOWER:
                         #This case is tricky: if a key like SPACE is pressed with SHIFT, it's still SPACE.
                         #However, for letters, it's different. The current_map logic should handle most cases.
                         #Only if AZERTY_UPPER explicitly misses an entry that AZERTY_LOWER has, this might be useful.
                         pass # char_to_add = AZERTY_LOWER.get(code_val) - this might be wrong for letters.

                    if char_to_add:
                        if char_to_add == 'BACKSPACE':
                            if password_chars:
                                password_chars.pop()
                        # Filter out non-printable control key names unless they are part of the password (e.g. \n)
                        elif char_to_add not in ['L_SHIFT', 'R_SHIFT', 'L_CTRL', 'CAPS_LOCK', 'ESC', 'L_ALT', 'TAB',
                                                 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10',
                                                 'NUMLOCK', 'SCROLLLOCK']:
                            password_chars.append(char_to_add)
        except Exception as e:
            # print(f"Error processing token {b64_event_data}: {e}") # For debugging
            continue
            
    final_password = "".join(password_chars)
    if final_password:
        # The user expects the flag in 404CTF{...} format. 
        # We will print the raw output, and then manually check it.
        print(final_password)
    else:
        print("Could not reconstruct password from events with AZERTY mapping.")

if __name__ == "__main__":
    solve()
```

{% file src="/files/AnDbMH1q9dDgl2uPQWZQ" %}

Ce script va donc prendre toutes les données exfiltrées et les déchiffrer étape par étape. Cela nous donne donc le résultat suivant : <br>

<figure><img src="/files/pC6K7iS66xC0M4pSIRaB" alt=""><figcaption></figcaption></figure>

Nous avons donc le flag affiché, il suffit simplement de lui refaire une petite beauté et tout sera bon :)\
\&#xNAN;*S'il y a `'` et `=` c'est du au fait que notre script ne prenne pas en compte la touche Alt-Gr afin de faire les accolades `{}`*

<details>

<summary>🚩FLAG</summary>

`404CTF{k3yl0gg3r_3xf1ltr4t10n}`

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://writeups.ayweth20.com/2025/404ctf-2025/analyse-forensique/tape-ton-mdp.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
