DeepSound is a steganography utility that can hide data inside of audio files. The contents can optionally be protected with a password, in which case DeepSound advertises that it encrypts using AES-256.

Used incorrectly, the security of all cryptographic algorithms, including (or perhaps especially) the beloved AES, can be devastatingly eroded. I took a peek at DeepSound to see if I could find any weaknesses in the way it performs encryption that would allow me recover the payload from a carrier file.

The first thing I noticed was that DeepSound will only prompt for a password when it is fed an audio file that actually does contain an encrypted payload. This ability to distinguish between encrypted and unencrypted payloads without first providing the password means that there is some metadata that should be easily sifted out of the carrier file. This was my first lead to investigate.

Since DeepSound is written using .NET and not obfuscated, it was possible to decompile the binary and just read the code. As a newcomer to reverse engineering C# apps, I found JetBrains dotPeek to be useful for exploring the decompiled code, and dnSpy to be a helpful debugger.

It was easy to understand from the decompiled code how DeepSound stores the header for its payload inside the audio file using a simple encoding mechanism. Remarkably, the payload remains intact after being transcoded to another format and back. After it has located this header, it checks a flag to see whether the payload is encrypted and prompts for the password if so.

public void AnalyzeStream ( Stream stream ) { ... if ( encrypted ) { // Extract 20 bytes from the header byte [] hdrhash = new byte [ 20 ]; Array . Copy ( header , 6 , hdrhash , 0 , 20 ); // Prompt for the password KeyRequiredEventArgs e = new KeyRequiredEventArgs (); this . OnKeyRequired ( this , e ); if ( e . Cancel ) throw new KeyEnterCanceledException (); this . Key = e . Key ; // Check if the SHA-1 hash of the AES key matches what's in the header byte [] keyhash = SHA1 . Create (). ComputeHash ( this . aes . Key ); if (! ArrayTools . CompareArrays ( keyhash , hdrhash )) throw new Exception ( "Wrong password." ); } ... }

To validate the entered password, DeepSound computes the SHA-1 hash of some AES key—not the password directly—and compares it to a hash stored in the header. But it isn’t obvious here where this AES key came from; if it were generated with a good password-based key derivation function, for instance, then this scheme might be reasonably secure.

It turns out that the line this.Key = e.Key , which copies the entered password into an instance variable, does more than meets the eye:

public string Key { set { byte [] buffer = new byte [ 32 ]; Array . Copy ( this . encoding . GetBytes ( value ), buffer , this . encoding . GetBytes ( value ). Length ); this . hash = SHA1 . Create (). ComputeHash ( buffer ); this . aes . Key = buffer ; } }

A secure PBKDF was too much to hope for: the password is used directly as the AES key, and the SHA-1 of the password, unsalted and uniterated, is what’s written into the audio file.

From here it was easy to write a script to locate the payload in a carrier file and extract the SHA-1 hash from its header. Then it should be possible to crack the password by running a tool like John the Ripper or hashcat, or sometimes just by searching Google.

Except that I overlooked something: The Key setter doesn’t compute the hash of the password directly; it copies it into a 32-byte buffer and computes the hash of that. In effect, it truncates or null-pads the password to a length of 32-bytes first, an idiosyncracy that precludes the use of off-the-shelf tools.

I decided to contribute support for this flavor of SHA-1 hash to John the Ripper, a tool that already knows about the imaginative password hashing schemes used by dozens of software packages. The developers of John have realized that most of these schemes are small variations on one another, whether it’s md5(sha1(password)) or sha1(md5(md5(password))) or what have you. Optimizing each of these algorithms by hand is too time consuming, so they have made a clever system that allows these schemes to be expressed in terms of some primitive building blocks.

For instance, DeepSound’s hashing scheme can be expressed in terms of four of these primitives: First, zero out our buffer. Then copy the password to it. Set the length of the buffer to 32, regardless of how long the password was. Lastly, compute the SHA-1 of the buffer.

Func=DynamicFunc__clean_input Func=DynamicFunc__append_keys Func=DynamicFunc__set_input_len_32 Func=DynamicFunc__SHA1_crypt_input1_to_output1_FINAL

Admittedly, finding the right sequence of primitives was not trivial, and there are a number of other switches to flip that I found a bit confusing. But it in the end it took only 8 lines to teach John about the new hashing scheme.

My changes have been contributed back to the John the Ripper community edition, including the deepsound2john.py script for extracting hashes from carrier files. My thanks to Dhiru Kholia for the code review.

Unbeknown to me, DeepSound was featured in a scene of Mr. Robot, which caught the attention of Alfonso Muñoz. Alfonso has a nice write-up of his blackbox reverse engineering of the payload encoding, in which he noticed another bad flaw: the use of ECB mode for encryption. Even without the password you can see penguins.