DualShock4 Reverse Engineering - Part 3
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!
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 |
---|---|---|---|
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.
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!
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!
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
) 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.
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 .
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?
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 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 . 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.
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 ds4@the.al
or ping me on IRC
(the_al@freenode|libera|hackint
) or Discord (the_al
).