Last active
July 18, 2021 23:57
-
-
Save dogtopus/931ba2cc911a1c13b21d1b3fd34de493 to your computer and use it in GitHub Desktop.
DS4 Bluetooth auth2/"weak auth" checker.
This file contains hidden or 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
#!/usr/bin/env python3 | |
import hid | |
import contextlib | |
import ctypes | |
import enum | |
import hashlib | |
import os | |
import sys | |
import zlib | |
CRC_GET_REPORT_FEATURE = zlib.crc32(b'\xa3') | |
CRC_SET_REPORT_FEATURE = zlib.crc32(b'\x53') | |
SHARED_SECRET_HASH = b'\x98\xb7\x00\xc5h\xbd@\xd1\xa3\xa2\x80\xa69\x18\xc3W\xb1q6f\xd7\xfb\x99*\xb0\x8e\x19\xf9k\xa3\x1b}' | |
class Auth2Challenge(ctypes.LittleEndianStructure): | |
_pack_ = 1 | |
_fields_ = ( | |
('type', ctypes.c_uint8), | |
('seq', ctypes.c_uint8), | |
('sec_slot_num', ctypes.c_uint8), | |
('challenge', ctypes.c_uint8 * 32), | |
('crc32', ctypes.c_uint32), | |
) | |
class Auth2Response(ctypes.LittleEndianStructure): | |
_pack_ = 1 | |
_fields_ = ( | |
('type', ctypes.c_uint8), | |
('seq', ctypes.c_uint8), | |
('sec_slot_num', ctypes.c_uint8), | |
('response', ctypes.c_uint8 * 20), | |
('salt', ctypes.c_uint8 * 20), | |
('crc32', ctypes.c_uint32), | |
) | |
def load_secret_file(): | |
result = [] | |
# Valid for slot #0-15. TODO figure out what are #16+ (undefined/broken behavior?) | |
# Only slot 0 is used by PS4 (see 7.00 kernel dump @ 0xffffffff82cf6f90 for example) | |
# That jedi flash @ 0x30358 | |
with open('jedi_auth2_ss.bin', 'rb') as f: | |
verify = hashlib.sha256() | |
while True: | |
sec = f.read(32) | |
if len(sec) == 0: | |
break | |
elif len(sec) < 32: | |
raise ValueError('Bad shared secret file: not 32-byte aligned.') | |
result.append(sec) | |
verify.update(sec) | |
if verify.digest() != SHARED_SECRET_HASH: | |
print('WARNING: Shared secret file not original. Verification results will not be accurate.') | |
return result | |
def parse_vidpid(vidpidstr): | |
vids, pids = vidpidstr.split(':')[:2] | |
return int(vids, 16) & 0xffff, int(pids, 16) & 0xffff | |
if __name__ == '__main__': | |
secrets = load_secret_file() | |
if len(sys.argv) < 3: | |
print(f'Usage: {sys.argv[0]} vid:pid bdaddr') | |
sys.exit(1) | |
with contextlib.closing(hid.device()) as dev: | |
dev.open(*parse_vidpid(sys.argv[1]), sys.argv[2]) | |
# Enable DS4 mode | |
#print(bytes(dev.get_feature_report(0x02, 64)).hex()) | |
for slot, sec in enumerate(secrets): | |
print('==== Slot', slot, '====') | |
# Prepare challenge | |
challenge = os.urandom(32) | |
report_0x03 = Auth2Challenge(type=0x03, seq=0x02, sec_slot_num=slot) | |
ctypes.memmove(report_0x03.challenge, os.urandom(32), Auth2Challenge.challenge.size) | |
report_0x03.crc32 = zlib.crc32(bytes(report_0x03)[:ctypes.sizeof(Auth2Challenge)-ctypes.sizeof(ctypes.c_uint32)], CRC_SET_REPORT_FEATURE) | |
print('0x03:', memoryview(report_0x03).hex()) | |
# Submit challenge and get response | |
dev.send_feature_report(report_0x03) | |
report_0x04 = Auth2Response.from_buffer(bytearray(dev.get_feature_report(0x04, ctypes.sizeof(Auth2Response)))) | |
print('0x04:', memoryview(report_0x04).hex()) | |
# Check CRC32 | |
crc32 = zlib.crc32(bytes(report_0x04)[:ctypes.sizeof(Auth2Response)-ctypes.sizeof(ctypes.c_uint32)], CRC_GET_REPORT_FEATURE) | |
if crc32 != report_0x04.crc32: | |
print('WARNING: Bad CRC.') | |
# Verify response | |
verify = hashlib.sha1() | |
verify.update(sec) | |
verify.update(report_0x03.challenge) | |
verify.update(report_0x04.salt) | |
actual = verify.digest() | |
if actual != bytes(report_0x04.response): | |
print('Verification NG.') | |
print('Expecting:', actual.hex()) | |
else: | |
print('Verification OK.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment