mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
L3 review r4 (B-1, m-a, m-b, m-c): judge an mpg:// pack by its first PES header
- ps_scrambled_at walks leading packets that carry no PES header (the system header, and every stream_id Table 2-17 exempts: MS-31, new) by their length, then requires a start code, the PES '10' marker and a header inside the clear first 0x80 bytes: a map-first clear pack is clear, and a scrambled PES after the map is found at its own flags. Only a pack's first PES is judged (documented). - crack_key_scan_with takes the flags-offset function: the disc path passes is_scrambled_pack at 0x14 and cracks the sector as before; only the mpg:// scan presents a moved flags byte to the keyless crack. - The mpg:// crack's rustdoc names ps_scrambled_at, not an unstuffed test.
This commit is contained in:
+40
-24
@@ -176,18 +176,34 @@ pub(crate) fn resolve_dvd_title_key(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Where a 13818-1 pack's first PES flags byte sits: `0x0E + pack_stuffing_length + 6`
|
||||
/// (0x14 unstuffed), `Some` only when that PES is scrambled (m1). The `mpg://` file's test.
|
||||
/// Where a 13818-1 pack's first PES header flags byte sits, `Some` only when that PES is
|
||||
/// scrambled: the `mpg://` file's test (m1, B-1). Leading packets with no PES header (a
|
||||
/// system header, a map, padding) are walked by their length; only the first PES is judged.
|
||||
pub(crate) fn ps_scrambled_at(sector: &[u8]) -> Option<usize> {
|
||||
use crate::consts::pes_stream_id::{PADDING_STREAM, PRIVATE_STREAM_2, SYSTEM_HEADER};
|
||||
if sector.len() < 2048 || sector[..4] != PACK_START || sector[4] >> 6 != 0b01 {
|
||||
return None;
|
||||
}
|
||||
let at = 0x14 + usize::from(sector[0x0D] & 0x07);
|
||||
let sid = sector[at - 3];
|
||||
(!matches!(sid, SYSTEM_HEADER | PADDING_STREAM | PRIVATE_STREAM_2)
|
||||
&& (sector[at] >> 4) & 0x03 != 0)
|
||||
.then_some(at)
|
||||
let mut p = 0x0E + usize::from(sector[0x0D] & 0x07);
|
||||
loop {
|
||||
let h = sector.get(p..p + 7)?;
|
||||
if h[..3] != [0, 0, 1] {
|
||||
return None;
|
||||
}
|
||||
match h[3] {
|
||||
// MS-31: "if (stream_id != program_stream_map && stream_id != padding_stream &&
|
||||
// stream_id != private_stream_2 && stream_id != ECM && stream_id != EMM && …"
|
||||
// (0xBB, the system header, is no PES packet at all).
|
||||
0xBB | 0xBC | 0xBE | 0xBF | 0xF0 | 0xF1 | 0xF2 | 0xF8 | 0xFF => {
|
||||
p += 6 + usize::from(u16::from_be_bytes([h[4], h[5]]));
|
||||
}
|
||||
0xBD..=0xFE => {
|
||||
let at = p + 6;
|
||||
// CSS leaves bytes before 0x80 clear; a header past them cannot be read.
|
||||
return (at < 0x80 && h[6] >> 6 == 0b10 && (h[6] >> 4) & 0x03 != 0).then_some(at);
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`ps_scrambled_at`] as a test.
|
||||
@@ -209,15 +225,15 @@ fn with_flags_at_0x14<T>(sector: &mut [u8], at: usize, f: impl FnOnce(&mut [u8])
|
||||
out
|
||||
}
|
||||
|
||||
/// The `mpg://` file's crack (design §4 step 2.1, D3): raw = false, over the strict
|
||||
/// unstuffed-pack test; the resolved keys, `DecryptKeys::None` when the file is clear.
|
||||
/// The `mpg://` file's crack (design §4 step 2.1, D3): raw = false, judging each pack by
|
||||
/// [`ps_scrambled_at`]; the resolved keys, `DecryptKeys::None` when the file is clear.
|
||||
pub(crate) fn resolve_ps_file_title_key(
|
||||
reader: &mut dyn SectorSource,
|
||||
extents: &[Extent],
|
||||
batch_sectors: u16,
|
||||
halt: Option<&crate::halt::Halt>,
|
||||
) -> std::io::Result<crate::decrypt::DecryptKeys> {
|
||||
match crack_key_scan_with(reader, extents, batch_sectors, halt, is_scrambled_ps_pack) {
|
||||
match crack_key_scan_with(reader, extents, batch_sectors, halt, ps_scrambled_at) {
|
||||
CrackOutcome::Cracked(state) => Ok(crate::decrypt::DecryptKeys::Css {
|
||||
title_key: state.title_key,
|
||||
}),
|
||||
@@ -243,16 +259,19 @@ fn crack_key_scan(
|
||||
batch_sectors: u16,
|
||||
halt: Option<&crate::halt::Halt>,
|
||||
) -> CrackOutcome {
|
||||
crack_key_scan_with(reader, extents, batch_sectors, halt, is_scrambled_pack)
|
||||
crack_key_scan_with(reader, extents, batch_sectors, halt, |s| {
|
||||
is_scrambled_pack(s).then_some(0x14)
|
||||
})
|
||||
}
|
||||
|
||||
// The crack scan with its scramble test (`is_scrambled_ps_pack` for mpg:// files).
|
||||
// The crack scan with its test: `scrambled_at` gives a scrambled pack's flags offset (always
|
||||
// 0x14 on the disc path; `ps_scrambled_at` for mpg:// files).
|
||||
fn crack_key_scan_with(
|
||||
reader: &mut dyn SectorSource,
|
||||
extents: &[Extent],
|
||||
batch_sectors: u16,
|
||||
halt: Option<&crate::halt::Halt>,
|
||||
scrambled: fn(&[u8]) -> bool,
|
||||
scrambled_at: fn(&[u8]) -> Option<usize>,
|
||||
) -> CrackOutcome {
|
||||
#[cfg(test)]
|
||||
CRACK_SCANS.with(|n| n.set(n.get() + 1));
|
||||
@@ -337,17 +356,14 @@ fn crack_key_scan_with(
|
||||
// HARDENED pack-gated check: a clear stub sector with
|
||||
// stray bits at 0x14 must NOT count as scramble evidence,
|
||||
// or an unencrypted title falsely reports E7023.
|
||||
if scrambled(sect) {
|
||||
if let Some(at) = scrambled_at(sect) {
|
||||
saw_scrambled = true;
|
||||
// A stuffed mpg:// pack's flags sit past 0x14 (m1).
|
||||
let key = match ps_scrambled_at(sect).filter(|&at| at != 0x14) {
|
||||
Some(at) => {
|
||||
let mut copy = sect.to_vec();
|
||||
with_flags_at_0x14(&mut copy, at, |c| {
|
||||
keyless::crack_title_key(c)
|
||||
})
|
||||
}
|
||||
None => keyless::crack_title_key(sect),
|
||||
// An mpg:// pack's flags may sit past 0x14 (m1); the disc's never do.
|
||||
let key = if at == 0x14 {
|
||||
keyless::crack_title_key(sect)
|
||||
} else {
|
||||
let mut copy = sect.to_vec();
|
||||
with_flags_at_0x14(&mut copy, at, |c| keyless::crack_title_key(c))
|
||||
};
|
||||
if let Some(key) = key {
|
||||
return CrackOutcome::Cracked(CssState {
|
||||
|
||||
@@ -311,6 +311,16 @@ pub const MS_30_PCM_DVD_HEADER: SpecQuote = SpecQuote {
|
||||
text: "stream->lpcm_header[0] = 0x0c; stream->lpcm_header[1] = (freq << 4) | (((st->codecpar->bits_per_coded_sample - 16) / 4) << 6) | st->codecpar->ch_layout.nb_channels - 1; stream->lpcm_header[2] = 0x80;",
|
||||
};
|
||||
|
||||
pub const MS_31_PES_HEADER_STREAM_IDS: SpecQuote = SpecQuote {
|
||||
id: "MS-31",
|
||||
kind: QuoteKind::Normative,
|
||||
source: H222,
|
||||
section: "§2.4.3.6 Table 2-17 PES packet",
|
||||
locator: "h222:2973-2982",
|
||||
url: H222_URL,
|
||||
text: "if (stream_id != program_stream_map && stream_id != padding_stream && stream_id != private_stream_2 && stream_id != ECM && stream_id != EMM && stream_id != program_stream_directory && stream_id != DSMCC_stream && stream_id != ITU-T Rec. H.222.1 type E stream) { '10' 2 bslbf PES_scrambling_control 2 bslbf",
|
||||
};
|
||||
|
||||
/// Every `MS-n` quote, in ID order.
|
||||
pub const ALL: &[&SpecQuote] = &[
|
||||
&MS_1_PACK,
|
||||
@@ -343,4 +353,5 @@ pub const ALL: &[&SpecQuote] = &[
|
||||
&MS_28_PRIVATE_DESCRIPTOR,
|
||||
&MS_29_PRIVATE_STREAM_1_HEADERS,
|
||||
&MS_30_PCM_DVD_HEADER,
|
||||
&MS_31_PES_HEADER_STREAM_IDS,
|
||||
];
|
||||
|
||||
@@ -76,3 +76,4 @@ MS-27 1011 1100 1 program_stream_map 1011 1101 2 private_stream_1 1011 1110 padd
|
||||
MS-28 A range of private descriptors may be defined by the user. These descriptors shall commence with descriptor_tag and descriptor_length fields. For private descriptors, the value of descriptor_tag may take the values 64-55 as identified in Table 2-39. These descriptors may be placed within a program_stream_map()
|
||||
MS-29 if (startcode == PRIVATE_STREAM_1) { avio_w8(ctx->pb, id); if (id >= 0xa0) { /* LPCM (XXX: check nb_frames) */ avio_w8(ctx->pb, 7); avio_wb16(ctx->pb, 4); /* skip 3 header bytes */ avio_w8(ctx->pb, stream->lpcm_header[0]); avio_w8(ctx->pb, stream->lpcm_header[1]); avio_w8(ctx->pb, stream->lpcm_header[2]); } else if (id >= 0x40) { /* AC-3 */ avio_w8(ctx->pb, nb_frames); avio_wb16(ctx->pb, trailer_size + 1); } }
|
||||
MS-30 stream->lpcm_header[0] = 0x0c; stream->lpcm_header[1] = (freq << 4) | (((st->codecpar->bits_per_coded_sample - 16) / 4) << 6) | st->codecpar->ch_layout.nb_channels - 1; stream->lpcm_header[2] = 0x80;
|
||||
MS-31 if (stream_id != program_stream_map && stream_id != padding_stream && stream_id != private_stream_2 && stream_id != ECM && stream_id != EMM && stream_id != program_stream_directory && stream_id != DSMCC_stream && stream_id != ITU-T Rec. H.222.1 type E stream) { '10' 2 bslbf PES_scrambling_control 2 bslbf
|
||||
|
||||
@@ -73,3 +73,4 @@ src/mux/codec/lpcm.rs dvd_header MS-30
|
||||
src/mux/mpg/scan.rs parse_map MS-8
|
||||
src/mux/mpg/scan.rs parse_map MS-9
|
||||
src/mux/mpg/scan.rs scan MS-23
|
||||
src/css/mod.rs ps_scrambled_at MS-31
|
||||
|
||||
Reference in New Issue
Block a user