In case you missed previous episodes, here is Part 1, where you can read about this DualShock 4 totally messed up that I’m trying to repair and Part 2 where I figure out which commands I can send.

Welcome back! :grin:

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 jedi_tool.py, already reported in Part 2. The striked-out commands are the ones we already discussed in the previous part.

Type Req. Id Description Payload
SET 0xa1 Enable Bluetooth 1 byte: 1 or 0
SET 0x08 Set Flash Read Pos 3 bytes: 0xff + 2 bytes for the offset
GET 0x11 Read from Flash 2 bytes for the payload
GET 0x12 Get Pairing Info (from hid-sony): 15 bytes with pairing info
SET 0x13 Set BT Link Info host_mac_addr(6 bytes) + link_key(16 bytes)
SET 0xa0 Test arg0(1 byte) + arg1(1 byte) + arg2(1 byte)
SET 0xa2 Enable DFU Mode 1 byte: 1 or 0

Let’s investigate what the other commands do, starting from 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

Test

The Feature Request SET 0xa0 is called Test into jedi_tool.py. It has multiple functionalities, depending on what is the first byte of the payload.

In 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 0x02 and 0x03. We will go back to them later! :smile:

DFU Mode

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 one.

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! :pray: :heart_eyes:

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: ????) :open_mouth:.

After that, the script uploads a new firmware (not included in the package :disappointed: ) using 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. :cowboy_hat_face:

You can see in the video that, when the DS4 reboots in DFU mode, it is recognized as 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 :joy:. This is because the only way I found to boot it back to normal mode is to send SET 0xf0 0x01. If you don’t know about this request and you try to send 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? :smirk:

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 :astonished:.

A trained eye can see that GET 0x87, GET 0x93, GET 0x94, 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 .)

  • GET 0x87: \x08. Sep 21 2018.....04:50:51........
  • GET 0x93: .....p 21 201
  • GET 0x94: .......21 2018.....04:50:51................................
  • GET 0xa4: .....1 2018..

Lots of copies of the same data :disappointed:. 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

Conclusion

Well, enough playing with the DualShock for today. :cowboy_hat_face:

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?) :astonished:.

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! :rocket:

Thank you for reading! :heart:

EDIT: Part 4 here

~~~

If you want to get in touch, drop me an email at ds4@the.al or ping me on IRC (the_al@freenode|libera|hackint) or Discord (the_al).