Skip to content

Commit

Permalink
v1.0.5 | 2022-01-08 - More update
Browse files Browse the repository at this point in the history
- Removed context_menu.reg and replaced with batch file instead to automatically assign the current check.bat full path
- Added current full path in check.bat instead of adding manually
- Renamed proto to wv_proto to avoid conflict from proto import
- colored output for device info and logger
- You can edit the output file name format (see in config.py)
- And some little changes
  • Loading branch information
zackmark29 committed Jan 8, 2022
1 parent 72259a3 commit 07cf223
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 91 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -3,4 +3,5 @@ __pycache__
*.html
device*
*.json
\test
\test
test.py
30 changes: 16 additions & 14 deletions README.md
@@ -1,7 +1,8 @@
# CDM-Device-Checker
---

Easily parse the cdm device info response from: https://tools.axinom.com/decoders/LicenseRequest
Easily parse the cdm device info response from: https://tools.axinom.com/decoders/LicenseRequest

SITE STATUS: `ACTIVE`

---
Expand All @@ -10,18 +11,9 @@ SITE STATUS: `ACTIVE`

INSTRUCTIONS:

- Change the dir from check.bat
- Change the path from context_menu.reg
- double click `context_menu.reg` to add it to your registry.
- You can also change the context title: `Check Client ID Blob`
- You can also drag and drop the blob directly to check.bat

```re
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\Check Client ID Blob\command]
@="\"C:\\User\\Admin\\Downloads\\cdm_device_checker\\check.bat\" %1"
```
- Just double click the **`add_context_menu.bat`** to automatically add context menu into your registry
- You can also change the context title ("Check Client Id Blob") inside the bat file
- Now you can do like the ss below or you can just drag and drop the file in the bat file

`From client_id_blob`

Expand All @@ -34,11 +26,21 @@ Windows Registry Editor Version 5.00

---

## **CHANGELOGS**:
## **CHANGELOGS**

## [1.0.5] | 2022-01-08

- Removed context_menu.reg and replaced with batch file instead to automatically assign the current check.bat full path
- Added current full path in check.bat instead of adding manually
- Renamed proto to wv_proto to avoid conflict from proto import
- colored output for device info and logger
- You can edit the output file name format (see in config.py)
- And some little changes

---

## [1.0.4] | 2021-12-27

- [1087720375](https://github.com/zackmark29/CDM-Device-Checker/issues/2#issue-1087720375) |
Added registry context menu for easy check from challange.txt or client_id_blob
- [1087669514](https://github.com/zackmark29/CDM-Device-Checker/issues/1#issue-1087669514) |
Expand Down
7 changes: 7 additions & 0 deletions add_context_menu.bat
@@ -0,0 +1,7 @@
@echo off
echo.
set cwd=%~dp0
set bat_file="%cwd%check.bat"
REG ADD "HKEY_CLASSES_ROOT\*\shell\Check Client Id Blob\command" /d "%bat_file% %%1"
echo.
pause
6 changes: 3 additions & 3 deletions cdm.py
@@ -1,10 +1,10 @@
import base64

from pathlib import Path
from simple_logger import info, error
from config import error
from google.protobuf import text_format
from google.protobuf.message import DecodeError
from proto.wv_proto2_pb2 import ClientIdentification, SignedLicenseRequest
from wv_proto.wv_proto2_pb2 import ClientIdentification, SignedLicenseRequest


def extract_challenge(client_id_blob: Path, quite: bool) -> str:
Expand All @@ -25,7 +25,7 @@ def extract_challenge(client_id_blob: Path, quite: bool) -> str:
if not quite:
print()
for msg in text_format.MessageToString(lic_request).splitlines():
info(msg)
print(msg)

print()
challenge_b64 = base64.b64encode(license_challenge).decode()
Expand Down
2 changes: 1 addition & 1 deletion challenge.txt
@@ -1 +1 @@
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
4 changes: 2 additions & 2 deletions check.bat
@@ -1,8 +1,8 @@
@echo off
echo.
cd /d "C:\Users\Admin\Downloads\cdm_device_checker"
set cwd=%~dp0
cd /d %cwd%
set arg=%*
echo %arg%
py check.py %arg%
echo.
pause
166 changes: 115 additions & 51 deletions check.py
@@ -1,22 +1,32 @@
__version__ = '1.0.5'

import sys
import re
import json
import base64
import binascii
import argparse

from simple_logger import *
from pathlib import Path
from requests import Session

TOKEN = 'CfDJ8J3HFu-R0HlNvutoAYRa68EjeSliPuQR8Ogt7niNVzgha2wJQ1jDNyCmxzGv95aKF911acVDx12U1BFi0z-JcR2taHJKjY6X0qWq2H5MD65oEZJYGUkzop-l7rOa8eJJKs1V9BjJZDjLPmdOsXmquBU'
COOKIE = '.AspNetCore.Antiforgery.pcAz0O62JyE=CfDJ8J3HFu-R0HlNvutoAYRa68FIgcbDEr06bq4cJyWCC5Pl6SyEHKMWynbvPsY7pf8KYwtuZLNZMHoMv8uhJxOUyWYpCA-yrH3NW8uH0pMACoc_u8afV2xtezmcOmG53Qe9uq8U7Hv-84UWvRRraNvbsQI'
from config import (
fg,
SITE,
TOKEN,
COOKIE,
info,
error,
warn,
FILE_NAME_FORMAT,
FILE_NAME_SEPARATOR
)


def fetch_challenge_data(challenge: str) -> str:
info('Fetching license request data')
res = Session().post(
url='https://tools.axinom.com/decoders/LicenseRequest',
url=SITE,
data={
'LicenseRequestEncoding': 'Base64',
'LicenseRequest': challenge,
Expand All @@ -25,7 +35,7 @@ def fetch_challenge_data(challenge: str) -> str:
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': 'https://tools.axinom.com/',
'Accept': '*.*',
'Accept': '*/*',
'Cookie': COOKIE
}
)
Expand All @@ -46,6 +56,12 @@ def parse_challenge_data(data: str) -> dict:
td = r'>(.+)<\/td>.*\n.+<td>'
tags = re.findall(td + r'(.+)[<^]', data)
divs = re.findall(td + r'\n.+<div>(.+)[<^]', data)

if not tags and not divs:
error('No any result found. Possible problem are: '
'Invalid challenge, Invalid RegEx or Site has been updated')

# Since it has mutlitple Type result, just get the second one
client_type = [x for x in tags if 'Type' in x][0][1]

dic = dict(
Expand All @@ -58,70 +74,101 @@ def parse_challenge_data(data: str) -> dict:

def get_device_info(challenge: str, cwd: Path) -> None:

data = fetch_challenge_data(challenge)
tags = parse_challenge_data(data)
main_info = fetch_challenge_data(challenge)
tags = parse_challenge_data(main_info)

def get(tag: str) -> str:
match = tags.get(tag)
match = tags.get(tag, "")

if match is None:
if match == "":
warn(f'No match found for: {tag}')

return match

system_id = get('System ID')

if system_id is None:
error(f'[ERROR]: System ID is not found. '
error(f'System ID is not found. '
'Looks like your challenge data is invalid')

status = get('Status')
sec_level = get('Security Level')
model_name = get('model_name')
device_name = get('device_name')
sys_chip = get('System on Chip')

data = {
'status': status.upper(),
'ForTestingOnly': get('For Testing Only'),
main_info = {
'status': get('Status').upper(),
'forTestingOnly': get('For Testing Only'),
'systemId': system_id,
'securityLevel': f'LEVEL_{sec_level}',
'manufacturer': get('Manufacturer'),
'model': get('Model'),
'modelYear': get('Model Year'),
'modelName': model_name,
'systemOnChip': sys_chip,
'type': get('Type'),
'AdditionalInfo': {
'applicationName': get('application_name'),
'architectureName': get('architecture_name'),
'buildInfo': get('build_info'),
'companyName': get('company_name'),
'deviceId': get('device_id'),
'deviceName': device_name,
'productName': get('product_name'),
'widevineCdmVersion': get('widevine_cdm_version')
}
'modelName': get('model_name'),
'systemOnChip': get('System on Chip'),
'type': get('Type')
}
additional_info = {
'applicationName': get('application_name'),
'architectureName': get('architecture_name'),
'buildInfo': get('build_info'),
'companyName': get('company_name'),
'deviceId': get('device_id'),
'deviceName': get('device_name'),
'productName': get('product_name'),
'widevineCdmVersion': get('widevine_cdm_version')
}
if status == 'REVOKED':
warn('This device was already REVOKED :(')
else:
info('This device is currently ACTIVE :)')

name = model_name.replace(" ", "-")
if sys_chip and 'generic' in sys_chip:
name = device_name
print('\n', '-' * 50)

file_name = cwd / f'{name}-{system_id}-L{sec_level}-[{status}].json'
data = json.dumps(data, indent=4, ensure_ascii=False)
file_name.write_text(data)
print('\n', data)
def colored_print(dic: dict):
for key, val in dic.items():
if key is None:
continue
if key == 'status':
if val == 'REVOKED':
val = f'{fg.RED}{val}{fg.RESET}'
else:
val = f'{fg.GREEN}{val}{fg.RESET}'
print(' ' * 4, f'{fg.CYAN}{key}: {fg.RESET}{val}')

info(f'Device info has been saved to: "{file_name}"')
colored_print(main_info)
print()
colored_print(additional_info)

print('-' * 50, '\n')

file_name = format_file_name(main_info)

main_info.update({'additionalInfo': additional_info})
main_info = json.dumps(main_info, indent=4, ensure_ascii=False)
output = cwd / f'{file_name}.json'
output.write_text(main_info)

info(f'Output File Name: "{file_name}"')
info(f'Device info has been saved to: "{output}"')
sys.exit()


def is_base64(txt) -> bool:
def format_file_name(data: dict) -> str:
keys = []
for name in FILE_NAME_FORMAT.split(' '):
# removed first the square brackets
key = re.sub(r'[\[\]]', '', name)
key = data.get(key)

if key is None:
continue

# just use square brackets if it is set in the template
if re.match(r'(\[.+\])', name):
key = f'[{key}]'

# replace LEVEL_1/3
key = str(key).replace('LEVEL_', 'L').replace(' ', '-')
keys.append(key)

return FILE_NAME_SEPARATOR.join(keys)


def is_base64(txt: str) -> bool:
try:
base64.b64decode(txt, validate=True)
return True
Expand All @@ -130,16 +177,29 @@ def is_base64(txt) -> bool:


def main(arg):
chal_path = Path(arg.challenge)
txt_file = 'challenge.txt'

if arg.challenge is None:
error('Challenge/Client Id Blob input is empty.'
f' Will load the default challenge from "{txt_file}" instead', False)

# base64 string
default_chal = Path(txt_file)

if not default_chal.exists():
error(f'{txt_file} is not exist')

arg.challenge = default_chal.read_text()

# check if the first arg is a valid base64 else assume that it is a file instead
if is_base64(arg.challenge):
# info('Using input challenge base64')
get_device_info(arg.challenge, Path().cwd())
elif not chal_path.is_file():
error('Invalid challenge base64 input')

# file argument
chal_path = Path(arg.challenge)

if not chal_path.exists():
error(f'{chal_path} does not exist')
error(f'"{chal_path}" does not exist')

# device_client_id_blob
if 'blob' in arg.challenge or chal_path.suffix == '.bin':
Expand All @@ -148,7 +208,7 @@ def main(arg):
info(f'Extracting challenge data from: "{chal_path}"')
challenge = extract_challenge(chal_path, arg.quite)
info('Writing challenge base64 to "challenge.txt" file')
Path('challenge.txt').write_text(challenge)
Path(txt_file).write_text(challenge)

# any txt file name with .txt extension
elif chal_path.suffix == '.txt':
Expand All @@ -161,7 +221,11 @@ def main(arg):


if __name__ == '__main__':
parser = argparse.ArgumentParser("Simple util to parse CDM device info from license request/challenge.")
parser = argparse.ArgumentParser(
prog=f'CDM Device Checker | Version {__version__}',
description='Simple util to parse CDM device info from license request/challenge.'
)
parser.add_argument(dest='challenge', nargs='?', default=None, help='Challenge or license request')
parser.add_argument('-q', '--quite', default=False, action='store_true', help='Don\'t print the results')
sys.exit(main(parser.parse_args()))
args = parser.parse_args()
sys.exit(main(args))

0 comments on commit 07cf223

Please sign in to comment.