Writeup by iv3l for B.A. BA

hardware radio

March 17, 2025

Loading the file into Numpy

iqs = np.fromfile('challenge.iq', np.complex64)
print(f"Loaded IQ data with shape: {iqs.shape}")
  • np.fromfile() reads binary data from the file into a NumPy array.
  • The parameter np.complex64 tells NumPy to interpret each pair of 32-bit floats as a single complex number.
  • The resulting shape (3675100,) indicates that we have about 3.6 million complex samples.

Plotting the Signal Data

fig, axs = plt.subplots(2, 2, sharex=True)
axs[0, 0].plot(np.real(iqs))
axs[0, 0].set_title("Real Part")
axs[1, 0].plot(np.imag(iqs))
axs[1, 0].set_title("Imaginary Part")
axs[0, 1].plot(np.abs(iqs))
axs[0, 1].set_title("Magnitude")
axs[1, 1].plot(np.angle(iqs))
axs[1, 1].set_title("Phase")
plt.show()

Matplotlib

Visual inspection to identify patterns and modulation types. Subplots: 2x2 grid to visualize four aspects of the signal:

  • Real Part: The in-phase component.
  • Imaginary Part: The quadrature component.
  • Magnitude: The signal’s strength (important for distinguishing between “high” and “low” signals).
  • Phase: The angle of the complex number (useful for detecting phase changes).

Magnitude -> showes “high” and “low” states.

Thresholding the Signal Magnitude

discrete = np.abs(iqs) > 0.75
threshold of 0.75 was chosen based on visual inspection of the plot

The result is a binary (True/False) array where:

True means the signal is "high" (strong).
False means the signal is "low" (weak or silent).    

Grouping Consecutive True/False Values

signals = [(value, len(list(group))) for value, group in groupby(discrete)]

Group Consecutive Values: groupby() -> sequences of highs (True) and lows (False). Instead of iterating manually, groupby() gives us lengths of each group directly.

A sequence of 4994 False (silence), followed by 150 True (signal), etc.

Counting Group Sizes

sizesT = Counter(length for value, length in signals if value)
sizesF = Counter(length for value, length in signals if not value)
print(f"Sizes for True values: {sizesT}")
print(f"Sizes for False values: {sizesF}")

Counter -> collections

Frequency Analysis: We want to know which group sizes are most common for both True and False values.
This helps identify standard durations that correspond to Morse code symbols

Decoding Morse Code

def decode_morse(signals):
    morse = ''.join(
        ('.' if length < 300 else '-') if value else (' ' if length > 2000 else '')
        for value, length in signals
    )
    return morse

Dot vs Dash: The analysis showed that:

150±1 length groups correspond to dots (.).
500±1 length groups correspond to dashes (-).

Symbol and Letter Gaps:

1000±1 gap between symbols within a letter.
3500±2 gap between letters.

We directly map the group length to a Morse symbol or a space based on the calculated thresholds.

morse_code = decode_morse(signals)
print(f"Decoded Morse code: {morse_code}")
print("Copy the code and decode it online")

==================================================

Complete Code

import numpy as np
import matplotlib.pyplot as plt
from itertools import groupby
from collections import Counter

# Load the IQ data from the file
iqs = np.fromfile('challenge.iq', np.complex64)
print(f"Loaded IQ data with shape: {iqs.shape}")

# Plotting the IQ data: real, imaginary, magnitude, and phase
fig, axs = plt.subplots(2, 2, sharex=True)
axs[0, 0].plot(np.real(iqs))
axs[0, 0].set_title("Real Part")
axs[1, 0].plot(np.imag(iqs))
axs[1, 0].set_title("Imaginary Part")
axs[0, 1].plot(np.abs(iqs))
axs[0, 1].set_title("Magnitude")
axs[1, 1].plot(np.angle(iqs))
axs[1, 1].set_title("Phase")
plt.show()

# Thresholding the magnitude to identify signals
discrete = np.abs(iqs) > 0.75

# Grouping consecutive True/False values
signals = [(value, len(list(group))) for value, group in groupby(discrete)]
print(f"Signal groups (first 10): {signals[:10]}")

# Counting the durations of True and False groups
sizesT = Counter(length for value, length in signals if value)
sizesF = Counter(length for value, length in signals if not value)
print(f"Sizes for True values: {sizesT}")
print(f"Sizes for False values: {sizesF}")

# Decoding Morse code based on signal lengths
def decode_morse(signals):
    morse = ''.join(
        ('.' if length < 300 else '-') if value else (' ' if length > 2000 else '')
        for value, length in signals
    )
    return morse

morse_code = decode_morse(signals)
print(f"Decoded Morse code: {morse_code}")

# Final message
print("Copy the Morse code and decode it using an online Morse code decoder.")
$ python3 script02.py
Loaded IQ data with shape: (3675100,)
Signal groups (first 10): [(False, 4994), (True, 150), (False, 1000), (True, 500), (False, 999), (True, 150), (False, 1000), (True, 150), (False, 3498), (True, 150)]
Sizes for True values: Counter({150: 820, 500: 506, 499: 161, 149: 114, 151: 19, 501: 2})
Sizes for False values: Counter({1000: 518, 999: 475, 3498: 472, 3499: 154, 3497: 2, 4994: 1, 3489: 1})
Decoded Morse code:  .-.. . -.-. --- -.. . -- --- .-. ... . .. -. - . .-. -. .- - .. --- -. .- .-.. --- ..- .-.. .- .-.. .--. .... .- -... . - -- --- .-. ... . .. -. - . .-. -. .- - .. --- -. .- .-.. . ... - ..- -. -.-. --- -.. . .--. . .-. -- . - - .- -. - -.. . - .-. .- -. ... -- . - - .-. . ..- -. - . -..- - . .- .-.. .- .. -.. . -.. . ... . .-. .. . ... -.. .. -- .--. ..- .-.. ... .. --- -. ... -.-. --- ..- .-. - . ... . - .-.. --- -. --. ..- . ... --.- ..- . .-.. .-.. . ... ... --- .. . -. - .--. .-. --- -.. ..- .. - . ... .--. .- .-. -.. . ... ... .. --. -. . ... ..- -. . .-.. ..- -- .. . .-. . ..- -. ... --- -. --- ..- ..- -. --. . ... - . ... - --- .--. -.-. . -.-. --- -.. . . ... - ... --- ..- ...- . -. - .- - - .-. .. -... ..- . .- ... .- -- ..- . .-.. -- --- .-. ... . -.-. . .--. . -. -.. .- -. - .--. .-.. ..- ... .. . ..- .-. ... -.-. --- -. - . ... - . -. - -.-. . - - . .--. .-. .. -- .- ..- - . . - - . -. -.. . -. - .- .- - - .-. .. -... ..- . .-. .-.. .- .--. .- - . .-. -. .. - . -.. ..- .-.. .- -. --. .- --. . .- ... --- -. .- ... ... .. ... - .- -. - .- .-.. ..-. .-. . -.. ...- .- .. .-.. ... - --- .--. .-.. . ..-. .-.. .- --. . ... - . -.... ---.. ----. .---- -.-. ---.. -... -... ---.. -.-. -.-. ..-. . ----. ..... .- --... -.... --... ....- ..... --... ..-. ...-- .- .---- -... --... ...-- ---.. -.... .---- ----. ..--- ---.. .- ----- ----- ..--- -.-. ..... ----- ----. ....- --... -.... ..--- -.. ..... ---.. ----- -.-. -.. --... -... -.-. ....- .- .---- ..... -.-. -.... ....- ... - --- .--. .. -. ...- . -. - . . -. .---- ---.. ...-- ..--- .--. --- ..- .-. .-.. .- - . .-.. . --. .-. .- .--. .... .. . -.-. . -.-. --- -.. .- --. . -.. . -.-. .- .-. .- -.-. - . .-. . ... .- ... ... .. --. -. . .- -.-. .... .- --.- ..- . .-.. . - - .-. . -.-. .... .. ..-. ..-. .-. . . - ... .. --. -. . -.. . .--. --- -. -.-. - ..- .- - .. --- -. ..- -. . -.-. --- -- -... .. -. .- .. ... --- -. ..- -. .. --.- ..- . -.. . ... .. --. -. .- ..- -..- .. -. - . .-. -- .. - - . -. - ... ... - --- .--. .-.. . -.-. --- -.. . -- --- .-. ... . . ... - -.-. --- -. ... .. -.. . .-. . -.-. --- -- -- . .-.. . .--. .-. . -.-. ..- .-. ... . ..- .-. -.. . ... -.-. --- -- -- ..- -. .. -.-. .- - .. --- -. ... -. ..- -- . .-. .. --.- ..- . ... ... - --- .--. 
Copy the Morse code and decode it using an online Morse code decoder.

Screenshot from 2025-03-17 11-21-07

Make it minuscule as the instrcutions say:

Python

def to_minuscule():
  """
  Asks the user for a string and prints it in minuscule (lowercase).
  """
  input_string = input("Enter a string: ")
  minuscule_string = input_string.lower()
  print("Minuscule string:", minuscule_string)

to_minuscule()
(kali㉿kali)-[~/hack]
└─$ python script03.py 
Enter a string: E6891C8BB8CCFE95A767457F3A1B73861928A002C5094762D580CD7BC4A15C64
Minuscule string: e6891c8bb8ccfe95a767457f3a1b73861928a002c5094762d580cd7bc4a15c64