Tape ton MDP
Last updated
Was this helpful?
Last updated
Was this helpful?
Catégorie: Analyse forensique - Difficulté: Moyen
Description:
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
:
Maintenant pour déchiffrer tout cela, nous pouvons utiliser le script suivant :
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()
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 :
Nous avons donc le flag affiché, il suffit simplement de lui refaire une petite beauté et tout sera bon :)
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 {}