In part 2, we talked about Feature Requests, commands that are accepted by the DualShock4 and they can do stuff.
Here’s the table of commands recovered from those used in
already reported in Part 2. The striked-out commands are the ones we already
discussed in the previous part.
|GET||0x12||Get Pairing Info||(from hid-sony): 15 bytes with pairing info|
|SET||0x13||Set BT Link Info||
|SET||0xa2||Enable DFU Mode||1 byte: 1 or 0|
Let’s investigate what the other commands do, starting from Set BT Link Info.
Set BT Link Info
I think this one is used by the PS4 to do Bluetooth Pairing with the DS4 in the moment you connect the DS4 via USB.
You can execute this command providing a payload of (6 + 16) bytes: the MAC Address of the PS4 and a 16-bytes “Link Key”.
I didn’t investigate further this function, probably the Link Key is this one, once you call that, the Bluetooth is paired to your device.
Anyway, there is a corresponding GET request that allows you to get back information about the status of pairing.
The request is
GET 0x12, and the output payload has this format:
[6 bytes: Local BT MAC] [08 25 00] [6 bytes: Remote BT MAC]
[08 25 00] are three fixed bytes, not sure about the meaning.
Live example with my working DS4:
$ python jedi_tool.py # My working DS4 Paired with 11:22:33:44:55:66 RID:0x12 data: 28516511aea4082500112233445566
The Feature Request
SET 0xa0 is called
jedi_tool.py. It has
multiple functionalities, depending on what is the first byte of the payload.
jedi_tool.py we see two use cases:
SET [0xa0,0x01][0|1]: Play a Sine from the headphones jack (1: start playing, 0: stop playing)
SET [0xa0,0x04]: Reset the device
Both of them work perfectly! The fact that one is
0x01 and the other
0x04 makes me think that
there are also
0x03. We will go back to them later!
For those who never heard of DFU Mode, it means “Device Firmware Update”. This is an alternate boot mode that lots of embedded devices have and allows to upload a firmware which will replace the current one.
Usually that firmware is encrypted and digitally signed by the vendor, as it seems to be in this case.
This mode can be triggered by calling
SET 0xa2 [0x01] and, probably, having a
firmware to upload would solve any issue I have. Unfortunately, I do not have
This is actually a call to anyone who ever reverse engineered this. If you have a working version of the firmware compatible with my controller, please let me know!
There is some commented code in the
jedi_tool.py that uses this feature:
dfu_enable(True) # SET 0xa2 test_reset() # SET 0xa0 0x04 wait_for_device((0x054c, 0x0856)) # What is this? print('sending fw...') for i in range(0, 0x38000, 0x38): while True: try: dfu_send_fw_block(0, i, b'\xff' * 0x38) # SET 0xf0 0x00 ?? print(dev.read(0x84, 0x40)) break except: time.sleep(.1) print('exit dfu...') time.sleep(1) dfu_send_fw_block(1, 0, b'\0' * 0x38) # SET 0xf0 0x01
The script calls
SET 0xa2 to enable DFU mode at the next reboot.
It calls then
SET 0xa0 0x04 to trigger the reboot.
At this point, the device should reveal itself as another device id, a strange
054c:0856 (Vendor ID: Sony, Device ID: ????) .
After that, the script uploads a new firmware (not included in the package
SET 0xf0 0x00 for each data chunk and finally it sends
SET 0xf0 0x01 to reboot the device.
Ok, let’s hack a bit the script so that it just puts the device into DFU mode and then goes back to normal mode.
In the video below you can see the result of it. The screen is split in half,
above there is a terminal where I launch the script; below instead there is
dmesg -w, so that you can see kernel messages.
You can see in the video that, when the DS4 reboots in DFU mode, it is recognized
idVendor=054c, idProduct=0856. Linux does not have a name for this device.
I googled a bit about this device id but I haven’t found anything yet. Let me know if you have any documentation about it!
Funny story: When I rebooted my DualShock4 in DFU mode for the first time, it
took me more than one hour to figure out how to turn off the DFU mode .
This is because the only way I found to boot it back to normal mode is
SET 0xf0 0x01. If you don’t know about this request and you try to
SET 0xa2 0x00 (DFU Mode Off), it will reject the request.
So you may try to disconnect and reconnect the DS4 or reset it using the button
on the JDM-055, it will always boot back again in DFU.
List all supported Get Reports
At this point, why not listing all the supported Get Reports with a simple for-loop?
It’s now extremely easy to hack
jedi_tool.py and write:
for i in range(0, 256): dump_req(i, 256)
This is the output when the script is executed with my working DS4 connected. I expect way more zeroed data on the broken one:
RID:0x02 data: ffff0000050090226bdd812285dd982277dd1c021c02ab1f55e04320bddffb1f06e00500 RID:0x10 data: 00000f00 RID:0x11 data: 0091 RID:0x12 data: 28516511aea40825009081ee0cdaf8 RID:0x15 data: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 RID:0x81 data: 28516511aea4 RID:0x84 data: 00000000 RID:0x86 data: 4e818e75a92a RID:0x87 data: 0800205365702032312032303138000000000030343a35303a35310000000000000000 RID:0x89 data: 00ff RID:0x91 data: ffffff RID:0x92 data: ffffff RID:0x93 data: ffff0000007020323120323031 RID:0x94 data: ffff000000ffff32312032303138000000000030343a35303a35310000000000000000000000000000000000000000000000000000000000000000 RID:0xa3 data: 5365702032312032303138000000000030343a35303a35310000000000000000000100b4010000000aa0102000a00200 RID:0xa4 data: ffff0000003120323031380000 RID:0xa8 data: 00 RID:0xa9 data: 91e4ba38f6a5941b RID:0xab data: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 RID:0xac data: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 RID:0xad data: a60a11aa060603030000 RID:0xae data: 00 RID:0xaf data: 0000 RID:0xd4 data: ffff0000000000000aa0102000a002004e818e75a92a28516511aea4020101d301a60a11aa06060303000091e4ba38f6a5941b0000000000000000 RID:0xf2 data: 0001000000000000000000dadda13c
Woah, so many unknown reports to reverse-engineer .
A trained eye can see that
GET 0xa4 contain printable bytes,
and they are similar to
GET 0xa3 (Get Firmware Info).
What happens if we convert them to string? (replacing \x00 and \xff with
\x08. Sep 21 2018.....04:50:51........
.....p 21 201
Lots of copies of the same data . I’ll be lucky next time.
Maybe with some
SET request we can change them, but not interesting for now.
Recap of what we discovered:
GET 0x02: IMU calibration data GET 0x10: ??? GET 0x11: No clue, but with 0x08 can be used to read the whole flash GET 0x12: Get Bluetooth Pairing Info GET 0x15: ??? GET 0x81: Get Local Mac Address GET 0x84: ??? GET 0x86: ??? GET 0x87: Seems a copy of Firmware Info GET 0x89: ??? GET 0x91: ??? All 0xff? GET 0x92: ??? All 0xff? GET 0x93: Another copy of Firmware Info? GET 0x94: Another copy of Firmware Info? GET 0xa3: The Real Firmware Info :joy: GET 0xa4: Another copy of Firmware Info? GET 0xa8: ?? GET 0xa9: Unknown string GET 0xab: All zeros, but lots of them :/ GET 0xac: Same as above (0xab) GET 0xad: A strange string GET 0xae: ??? GET 0xaf: ??? GET 0xd4: ??? Unknown but seems interesting GET 0xf2: ??? Unknown but seems interesting
Well, enough playing with the DualShock for today.
After discovering what I wrote here, I thought I hit a dead-end: it’s easy to
list all the
GET reports, but doing the same with the
SET reports will
probably brick the device.
Well, luck was on my side when few hours later I stumbled upon a leaked reverse-engineered code of the PS4 uploaded on archive.org (wtf, really?) .
I could not believe my eyes when I found
DS4_Flash-8.3.13.c inside the ZIP
file, which contains the reverse-engineered code of an old version of the DS4.
We’ll get into that in the next part!
Thank you for reading!
EDIT: Part 4 here
If you want to get in touch, drop me an email at
firstname.lastname@example.org or ping me on IRC
the_al@freenode|libera|hackint) or Discord (