Last active
April 24, 2024 22:32
-
-
Save heathhenley/2f9ad47cf8b818f3ddb592569a013c87 to your computer and use it in GitHub Desktop.
hacking_on_S63.ipynb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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