Audit fixes + DVD support foundation (IFO, PS demux, MPEG-2, CSS crack)
Audit fixes (14 critical, 22 warnings): - UDF: bounds checks on all ICB/FID parsing from disc data - SCSI Linux: saturating_sub on residual, CDB length guard, buffer size guard - SCSI macOS: SCSITaskStatus u32 (was u8 — stack corruption) - AACS: EC mod_inv returns infinity instead of panic, key reduced mod n - AACS: do_handshake tries all host certs (was returning on first failure) - H.264: bounds check on SPS < 4 bytes - ContentReader: error on missing unit key (was zero-fill) - KEYDB: flat redirect loop (was recursive), 100MB response limit, Windows HOME fallback - ISO writer: AVDP extent order, partition length, allocation cap - Network: removed TCP_NODELAY on bulk stream - MKV: guard on u64::MAX seek - disc.rs: saturating_sub on extent offset, simplified dead region code - cargo fmt (610 violations), cargo clippy --fix (55 auto-fixes) DVD support (new files): - src/ifo.rs — IFO parser (VIDEO_TS.IFO, VTS_XX_0.IFO, PGC chains, cells, streams) — 13 tests - src/mux/ps.rs — MPEG-2 Program Stream demuxer (pack headers, PES, private stream 1) — 12 tests - src/mux/codec/mpeg2.rs — MPEG-2 video parser (sequence headers, I-frame detection) — 15 tests - src/css/crack.rs — split-attack algorithm (LFSR cipher needs verification — test ignored) 226 tests total (was 186), 1 ignored (CSS crack needs cipher verification).
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
//! CSS (Content Scramble System) — DVD disc encryption.
|
||||
//!
|
||||
//! CSS uses a weak 40-bit LFSR stream cipher (broken since 1999).
|
||||
//! No keys needed — the title key is cracked from encrypted content
|
||||
//! using a known-plaintext attack on MPEG-2 PES headers.
|
||||
//!
|
||||
//! Usage:
|
||||
//! ```rust,ignore
|
||||
//! let key = css::crack_key(reader, &extents)?;
|
||||
//! css::descramble_sector(&key, &mut sector);
|
||||
//! ```
|
||||
|
||||
pub mod crack;
|
||||
pub mod lfsr;
|
||||
|
||||
use crate::disc::Extent;
|
||||
use crate::sector::SectorReader;
|
||||
|
||||
/// CSS decryption state for a DVD title.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CssState {
|
||||
/// Cracked 5-byte title key
|
||||
pub title_key: [u8; 5],
|
||||
}
|
||||
|
||||
/// Crack the CSS title key by reading encrypted sectors and applying
|
||||
/// a known-plaintext attack on MPEG-2 headers.
|
||||
///
|
||||
/// Reads a few sectors from the first extent, finds one with the
|
||||
/// scramble flag set, and cracks the key.
|
||||
pub fn crack_key(reader: &mut dyn SectorReader, extents: &[Extent]) -> Option<CssState> {
|
||||
if extents.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ext = &extents[0];
|
||||
let mut sectors = Vec::new();
|
||||
|
||||
// Read first 10 sectors from the main extent
|
||||
let count = ext.sector_count.min(10);
|
||||
for i in 0..count {
|
||||
let mut buf = vec![0u8; 2048];
|
||||
if reader.read_sectors(ext.start_lba + i, 1, &mut buf).is_ok() {
|
||||
sectors.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Try cracking from the collected sectors
|
||||
let key = crack::crack_from_sectors(§ors)?;
|
||||
|
||||
Some(CssState { title_key: key })
|
||||
}
|
||||
|
||||
/// Descramble a single CSS-encrypted sector in place.
|
||||
pub fn descramble_sector(state: &CssState, sector: &mut [u8]) {
|
||||
lfsr::descramble_sector(&state.title_key, sector);
|
||||
}
|
||||
|
||||
/// Check if a sector has the CSS scramble flag set.
|
||||
pub fn is_scrambled(sector: &[u8]) -> bool {
|
||||
sector.len() >= 2048 && (sector[0x14] >> 4) & 0x03 != 0
|
||||
}
|
||||
Reference in New Issue
Block a user