Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audible AAX support #99

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Audible AAX support #99

wants to merge 3 commits into from

Conversation

LinusU
Copy link
Contributor

@LinusU LinusU commented Mar 19, 2023

Opening this for some early feedback. My goal is to read and decrypt audio data from Audibles AAX files.

I learned how the adrm boxed worked by looking at FFmpeg and at some sample files with a hex editor. There is still 60 bytes that I don't know what they are for (unknown0) but they seem to be random bytes, maybe more checksums? 馃

FFmpeg/libavformat also does the actual decryption when reading the track data, but I thought that that was out of scope for this project. Let me know what you think!

The biggest issue right now is the BoxType::UnknownBox(0x61617664) part. aavd boxes are basically identical to mp4a boxes, so I wanted to just reuse that instead of re-implementing the entire Mp4aBox again. It seems like something similar is also true for alac and fLaC as well. I'm very open to suggestions here!

@LinusU
Copy link
Contributor Author

LinusU commented Mar 19, 2023

Here is some work in progress code for actually decoding the data:

use aes::{
    cipher::{generic_array::GenericArray, BlockDecryptMut, KeyIvInit},
    Aes128,
};
use cbc::Decryptor;
use mp4::Mp4Reader;
use sha1::{Digest, Sha1};
use std::fs::File;
use std::io::BufReader;

const AUDIBLE_FIXED_KEY: [u8; 16] = [
    0x77, 0x21, 0x4d, 0x4b, 0x19, 0x6a, 0x87, 0xcd, 0x52, 0x00, 0x45, 0xfd, 0x20, 0xa5, 0x1d, 0x67,
];

fn get_reader(path: &str) -> Mp4Reader<BufReader<File>> {
    let f = File::open(path).unwrap();
    let f_size = f.metadata().unwrap().len();
    let reader = BufReader::new(f);

    mp4::Mp4Reader::read_header(reader, f_size).unwrap()
}

#[test]
fn test_read_aax() {
    let mut mp4 = get_reader("tests/samples/YourFirstListen_ep7.aax");
    assert_eq!(mp4.ftyp.major_brand, "aax ".parse().unwrap());
    let track = mp4.tracks().get(&1).unwrap();

    // Put your activation bytes here!
    let activation_bytes = [0x00, 0x00, 0x00, 0x00];

    let adrm = track
        .trak
        .mdia
        .minf
        .stbl
        .stsd
        .mp4a
        .as_ref()
        .and_then(|mp4a| mp4a.adrm.as_ref())
        .unwrap();

    // Key Derivation

    let mut sha = Sha1::new();
    sha.update(AUDIBLE_FIXED_KEY);
    sha.update(activation_bytes);
    let intermediate_key = sha.finalize();

    let mut sha = Sha1::new();
    sha.update(AUDIBLE_FIXED_KEY);
    sha.update(intermediate_key);
    sha.update(activation_bytes);
    let intermediate_iv = sha.finalize();

    let mut sha = Sha1::new();
    sha.update(&intermediate_key[..16]);
    sha.update(&intermediate_iv[..16]);
    let calculated_checksum = sha.finalize();

    eprintln!("file_checksum: {:x?}", adrm.file_checksum);
    eprintln!("calculated_checksum: {:x?}", calculated_checksum);

    assert_eq!(calculated_checksum, adrm.file_checksum.into());

    // Decryption setup

    let mut aes =
        Decryptor::<Aes128>::new_from_slices(&intermediate_key[..16], &intermediate_iv[..16])
            .unwrap();

    let mut data = adrm.drm_blob.to_owned();

    aes.decrypt_block_mut(GenericArray::from_mut_slice(&mut data[0..16]));
    aes.decrypt_block_mut(GenericArray::from_mut_slice(&mut data[16..32]));
    aes.decrypt_block_mut(GenericArray::from_mut_slice(&mut data[32..48]));

    assert_eq!(activation_bytes[0], data[3]);
    assert_eq!(activation_bytes[1], data[2]);
    assert_eq!(activation_bytes[2], data[1]);
    assert_eq!(activation_bytes[3], data[0]);

    eprintln!(
        "Activation bytes: {:02x}{:02x}{:02x}{:02x}",
        data[3], data[2], data[1], data[0]
    );

    // Read the entire track

    for sample_id in 1.. {
        if let Some(sample) = mp4.read_sample(1, sample_id).unwrap() {
            assert!(!sample.bytes.is_empty());

            let mut data = sample.bytes.to_vec();

            // Reset the IV for each sample
            let mut aes = Decryptor::<Aes128>::new_from_slices(
                &intermediate_key[..16],
                &intermediate_iv[..16],
            )
            .unwrap();

            // trailing bytes are not encrypted!
            let block_count = data.len() / 16;

            for j in 0..block_count {
                let start = j * 16;
                let end = start + 16;
                aes.decrypt_block_mut(GenericArray::from_mut_slice(&mut data[start..end]));
            }

            eprintln!("Decrypted sample {}: {} bytes", sample_id, data.len());
        } else {
            break;
        }
    }
}

Now, I think that this works, but I haven't actually tested it yet since I need to figure out how to write the decoded bytes into a new mp4 file with the aac data 馃槄

@LinusU
Copy link
Contributor Author

LinusU commented Jun 20, 2023

(rebased on master, should fix CI)

ping @alfg, do you have any input on this? 馃檹

@alfg
Copy link
Owner

alfg commented Jun 21, 2023

Hey @LinusU, sorry for the late response and thanks for the PR. I will check this out soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants