PreSonus StudioLive API (Part 3)


My church had recently been hit by a power surge, which unfortunately damaged our PreSonus StudioLive 16 - supposedly one of the PCBs were fried. Thankfully sometime since purchase, PreSonus had decided to extend all their warranty coverage of all StudioLive consoles (including previously purchased consoles) to three years - and our board was therefore still within warranty!

For the time being, we had moved our stage box mixer to FoH to function as a (rack-mount mixer) that I could control with a touchscreen monitor (finally putting the computers I bought earlier this year to use!) - It’s not optimal; but it does work decently…

Apart from the StudioLive 16, a bunch of other gear had also been damaged (to the tune of around ~AU$11500 total). In buying replacement gear, I also decided to purchase the StudioLive 16R rack mixer for my own personal use (AU$1400) - it had been something that I was eyeing on for almost a year and a bit!

I had two reasons for wanting to buy this unit

  1. Stream Rack

This has been a little (albeit expensive) project of mine; to rebuild my streaming rig for when I’m doing corporate or freelance livestreaming / small-scale event audio / etcetera. I was torn between this unit, or the PreSonus Studio 1824c (with its nice metering LEDs at the front) - but ultimately decided (though at 2.5x the price) that the former was better; with its onboard DSP effects and remote mixing capabilities.

  1. To work on my reverse engineering project!

^ this!

Today I just wanted to share a little update (and achievement) regarding the progress of my unofficial StudioLive API - I’ve figured out how to decode the ZB payloads!! 🥳

Payload Breakdown

The ZB payload contain the overall mixer state, compressed with the zlib algorithm.
Comparing the sizes of the compressed (15 KB) and uncompressed data (182 KB) - we get quite an amazing compression ratio of 0.08!

Originally I had thought that the ZB packets were encrypted, rather than just being compressed; however given the prior research - I had came to the understanding that the security implementations on these boards are very lacking…

⚠ Access control is managed client-side; with only the control surface’s “PIN entry” screen and component “disabled” props preventing the average user from controlling the board. This means that if you have the right payload, you could send any control instruction as you want without needing to authenticate yourself ⚠

Then again, your production network should only contain trusted devices, not to mention only production devices.

Having a look at the assembly instructions of the Universal Control applications (Windows 10 x64) - namely the ucnet.dll file, I didn’t really see any calls to external crypto libraries, nor any signs of cryptographic routines; so I decided to whack the payload into CyberChef.

In the output guesses, one of the results made mention of “Raw Inflate”… sounds similar to what gzip and zlib sort of provide!

The magic number for gzip formats are 1f 8b - though I could not find that string near the start of the payload. I did however find 8b 1f further down the file - so perhaps the data was in little-endian? Nah - unlikely.

The magic number for zlib formats are 78 01 / 78 9C / 78 DA, and could be found at offset 4 of the payload! Surely this is right!?

The first four bytes prior to the zlib start header turned out to be a little-endian packed payload size of the compressed data; as is done with the payload that encapsulated this payload.

Upon inflating the data, we can successfully identify sensible strings from within the packet, success!
We’ll eventually need to decode the data format, as it isn’t exactly of a JSON type.

EDIT: Done! -

Payload Rationale

So why do we need these packets?

For the API to be somewhat useful, we would want to be able to know the current mixer state (i.e. the current mute status, volume levels, etcetera). Prior to figuring out the zlib compression, in order to figure out the mute status for Channel 1, you would do some hacky methods… something I had documented here

IDEA: Force get channel state 

If we are unable to figure out how to get initial channel data
then we could do a hackish method to query the information.

A. Send an unmute command and see if there is a response (Will be unmuted regardless now)
  * If there is a unmute event, then we know that the channel was originally muted
    * Then send a mute event
  * If there was no unmute event, then we know the channel was already unmuted

B. Send a mute command and see if there is a response (Will be muted regardless now)
  * If there is a mute event, then we know that the channel was originally unmuted
    * Then send an unmute event
  * If there was no mute event, then we know that the channel was already muted

C. Some commands cause a list of channel mute statuses to be send (Link Aux Mute - MB: mt64).
  * Send that command
  * Read response

However, the issue with any of the above methods is that you would need to modify the board state - but that would be a major hindrance.

So long story short, figuring out the nature of the ZB packet, and more importantly how to decode it was a super important milestone for the continuation of this project!

In hindsight, this accomplishment should have been reached ages ago, as the name of the payload (ZB) should have led me to probe around zlib compression… In fact I feel like I did (unsuccessfully); which is why I had only completely figured it out today given a second look, and more patience.

More posts

DIY 3-Channel WiFi RGB Breakout Board

tbh should've just bought a H801