Skip to content

Instantly share code, notes, and snippets.

@dogtopus
Last active July 18, 2021 23:57
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save dogtopus/931ba2cc911a1c13b21d1b3fd34de493 to your computer and use it in GitHub Desktop.
DS4 Bluetooth auth2/"weak auth" checker.
#!/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