Skip to content

Instantly share code, notes, and snippets.

@heathhenley
Last active April 24, 2024 22:32
Show Gist options
  • Save heathhenley/2f9ad47cf8b818f3ddb592569a013c87 to your computer and use it in GitHub Desktop.
Save heathhenley/2f9ad47cf8b818f3ddb592569a013c87 to your computer and use it in GitHub Desktop.
hacking_on_S63.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyO2uG4+5eGAoWrEYSQo7Oec",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/heathhenley/2f9ad47cf8b818f3ddb592569a013c87/hacking_on_s63.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Ixt3NqDz5_V5",
"outputId": "f5ac42f4-50b3-4d56-e771-639467a3ad40"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Collecting pycryptodome\n",
" Downloading pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.1/2.1 MB\u001b[0m \u001b[31m10.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[?25hInstalling collected packages: pycryptodome\n",
"Successfully installed pycryptodome-3.20.0\n"
]
}
],
"source": [
"# install to use encryption algos\n",
"%pip install pycryptodome\n",
"\n",
"# generate permutations of subsets of chars of length length\n",
"def ascii_generator_tr(length: int, chars: str, prefix: str = '') -> str:\n",
" if length == 0:\n",
" yield prefix\n",
" else:\n",
" for c in chars:\n",
" yield from ascii_generator_tr(length - 1, chars, prefix + c)\n",
"\n",
"# utility to check for valid padding\n",
"def valid_padding(text: bytes) -> bool:\n",
" pad_len = text[-1]\n",
" if pad_len == 0 or pad_len > len(text):\n",
" return False\n",
" for i in range(1, pad_len + 1):\n",
" if text[-i] != pad_len:\n",
" return False\n",
" return True"
]
},
{
"cell_type": "code",
"source": [
"# Given the UPN, can we find the hardware id?\n",
"import binascii\n",
"from Crypto.Cipher import Blowfish\n",
"\n",
"# Here's a UPN we know some how, it contains the encrypted hwid\n",
"# but we don't know the mkey used to encrypt it...\n",
"upn = \"66B5CBFDF7E4139D5B6086C23130\"\n",
"\n",
"# The encrypted hw id is the first 16 chars\n",
"encrypted_hw_id = upn[:16]\n",
"\n",
"# the crc check is the next 8:\n",
"crc = upn[16:24]\n",
"\n",
"# we can make sure it matches to ensure there was no error / data\n",
"# integrity issues:\n",
"assert crc == f\"{binascii.crc32(encrypted_hw_id.encode()):x}\".upper()\n",
"\n",
"# The crc checks out, time to try to decrypt it, even without the key\n",
"for possible_key in ascii_generator_tr(5, \"0123456789ABCDEF\"):\n",
" cipher = Blowfish.new(possible_key.encode(), Blowfish.MODE_ECB)\n",
" decrypted_hw_id = cipher.decrypt(bytes.fromhex(encrypted_hw_id))\n",
" # the real hw id will have valid padding:\n",
" if decrypted_hw_id[-1] == 3 and valid_padding(decrypted_hw_id):\n",
" # only the first 5, the rest is padding\n",
" print(f\"Found the HW_ID: {decrypted_hw_id[:5].decode()}\")\n",
" print(f\"The M_KEY of the manufacturer: {possible_key}\")\n",
" break"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "38PlJp3_6JC1",
"outputId": "072946e9-90a6-44b2-8305-b974aa35afaa"
},
"execution_count": 2,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Found the HW_ID: 12345\n",
"The M_KEY of the manufacturer: 10121\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# What if we have a permit, but we don't know anything else? For example,\n",
"# it was made for another system, etc...\n",
"import zipfile\n",
"\n",
"full_permit = \"NO4D051220040826F7B3814E59C84805D150D571B9BE53A642E5B05951975E9C\"\n",
"\n",
"# you can download this file from: https://heathhenley.github.io/s63/NO4D0512_encrypted.000\n",
"# or from the IHO test kit\n",
"with open(\"NO4D0512_encrypted.000\", \"rb\") as f:\n",
" encrypted_s57 = f.read()\n",
"\n",
"# extract encrypted cell key, just check the first one:\n",
"eck1 = full_permit[16:32]\n",
"\n",
"# we don't know the hw_id that was used to encrypt the permit - so we have to\n",
"# guess it - basically the same idea as above, except the permits are encrypted\n",
"# wih a 6 byte key, the normal 5 byte hw_id and the first byte appended again.\n",
"for possible_key in ascii_generator_tr(5, \"0123456789ABCDEF\"):\n",
" key = possible_key + possible_key[0]\n",
" cipher = Blowfish.new(key.encode(), Blowfish.MODE_ECB)\n",
" decrypted_cell_key = cipher.decrypt(bytes.fromhex(eck1))\n",
" # the real key will have valid padding:\n",
" if decrypted_cell_key[-1] == 3 and valid_padding(decrypted_cell_key):\n",
" ck1 = decrypted_cell_key[0:5]\n",
" print(f\"Possible cell key: {ck1}\")\n",
" # try to decrypt S63 data with this cell key we found:\n",
" cipher = Blowfish.new(ck1, Blowfish.MODE_ECB)\n",
" decrypted_s57_zip = cipher.decrypt(encrypted_s57)\n",
" if decrypted_s57_zip[0:2] == b\"PK\": # zips start with this\n",
" if b\"NO4D0512.000\" in decrypted_s57_zip[:64]:\n",
" print(f\"Found cell key for this ENC: {ck1.hex().upper()}\")\n",
" print(f\"Hardware ID: {possible_key}\")\n",
" break"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4C3U34__wJkb",
"outputId": "592e0c13-c8f6-4743-986d-a554f5172cbb"
},
"execution_count": 18,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Possible cell key: b'\\x9cF}5\\x9d'\n",
"Found cell key for this ENC: 9C467D359D\n",
"Hardware ID: 12345\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# What if we know nothing all, and we just have the encrypted data?\n",
"#\n",
"# You can see that the cell key used to encrypt that data is\n",
"# 5 bytes, as above. One approach to brute force it might just be\n",
"# to keep trying 5 byte keys until the first couple of bytes of the\n",
"# decrypted data look like a zip (b\"PK\")... this is slow for big files,\n",
"# in that case it's probably better to only decrypt a handful of blocks\n",
"# worth of data. The decrypted data also contains the cell name in the\n",
"# the start of the file, so we can check to see if that's there... (checking for\n",
"# pk only gives false positives)\n",
"# This takes a lot longer - the key can be made of any random bytes this time,\n",
"# so there are more options.\n",
"# Still, it's not intractable to brute force... though in python, and processing\n",
"# in serial here in this notebook, it takes about 40secs per million keys, so\n",
"# it's about a year and half of processing in serial. Anyone serious about\n",
"# cracking it could use a compiled language and distribute to multple processes\n",
"# and threads, and find the key much faster.\n",
"for idx, possible_key in enumerate(ascii_generator_tr(10, \"0123456789ABCDEF\")):\n",
" cipher = Blowfish.new(bytes.fromhex(possible_key), Blowfish.MODE_ECB)\n",
" # try decrypting the data and check whether it's a zip\n",
" decrypted_s57_zip = cipher.decrypt(encrypted_s57[:64])\n",
" if decrypted_s57_zip[0:2] == b\"PK\" and b\"NO4D0512.000\" in decrypted_s57_zip:\n",
" print(f\"Found cell key for this ENC: {possible_key}\")\n",
" break\n",
" if idx % 1_000_000 == 0:\n",
" print(f\" processed {idx+1} keys...\")"
],
"metadata": {
"id": "NKrOMA7806WE"
},
"execution_count": null,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment