tegra_rcm/
payload.rs

1use std::path::{Path, PathBuf};
2
3use log::{debug, trace};
4use thiserror::Error;
5
6/// A constructed payload, this is transferred to the switch in RCM mode to execute bootROM exploit
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Payload {
9    data: Box<[u8]>,
10}
11
12/// The max length for the total payload
13const BUILT_PAYLOAD_MAX_LENGTH: usize = 0x30298;
14// TODO: find out if this is true
15/// The min length of the provided payload (inclusive)
16const PAYLOAD_MIN_LENGTH: usize = PADDING_SIZE_2;
17/// The max length of the provided payload (exclusive)
18const PAYLOAD_MAX_LENGTH: usize = 183_640;
19
20/// hardcoded address for the start of the stack spray
21const STACK_SPRAY_START: usize = 0x4001_4E40;
22/// hardcoded address for the end of the stack spray
23const STACK_SPRAY_END: usize = 0x4001_7000;
24const PADDING_SIZE_2: usize = STACK_SPRAY_START - PAYLOAD_START_ADDR;
25
26const PAYLOAD_START_ADDR: usize = 0x4001_0E40;
27const RCM_PAYLOAD_ADDR: usize = 0x4001_0000;
28
29const REPEAT_COUNT: usize = (STACK_SPRAY_END - STACK_SPRAY_START) / 4;
30
31impl Payload {
32    /// Construct a new payload
33    /// length should be >= 16384 and < 183640
34    pub fn new(payload: &[u8]) -> Result<Self, PayloadError> {
35        if payload.len() < PAYLOAD_MIN_LENGTH {
36            return Err(PayloadError::PayloadTooShort(payload.len()));
37        }
38
39        if payload.len() >= PAYLOAD_MAX_LENGTH {
40            return Err(PayloadError::PayloadTooLong(payload.len()));
41        }
42
43        debug!(
44            "Provided payload within size bounds with a size is: {} bytes",
45            payload.len()
46        );
47
48        const INTERMEZZO: &[u8; 124] = include_bytes!("intermezzo/intermezzo.bin");
49        trace!("Injected intermezzo.bin");
50
51        let mut payload_builder = Vec::with_capacity(BUILT_PAYLOAD_MAX_LENGTH);
52        // start with the max_len arg
53        payload_builder.extend((BUILT_PAYLOAD_MAX_LENGTH as u32).to_le_bytes());
54        // pad with data to get to the start of IRAM
55        payload_builder
56            .extend([b'\0'; 680 - (BUILT_PAYLOAD_MAX_LENGTH as u32).to_le_bytes().len()]);
57        // add the intermezzo bin
58        payload_builder.extend(INTERMEZZO);
59
60        const PADDING_SIZE_1: usize = PAYLOAD_START_ADDR - (RCM_PAYLOAD_ADDR + INTERMEZZO.len());
61        payload_builder.extend([b'\0'; PADDING_SIZE_1]);
62
63        // fit a a part of the payload before the stack spray
64        let split = payload.split_at(PADDING_SIZE_2);
65        payload_builder.extend(split.0);
66        // start stack spray
67        for _ in 0..REPEAT_COUNT {
68            payload_builder.extend((RCM_PAYLOAD_ADDR as u32).to_le_bytes());
69        }
70        payload_builder.extend(split.1);
71
72        // finish padding to be a size of 0x1000
73        let padding_size = 0x1000 - (payload_builder.len() % 0x1000);
74        payload_builder.resize(payload_builder.len() + padding_size, b'\0');
75
76        debug_assert_eq!(payload_builder.len() % 0x1000, 0);
77
78        let data = payload_builder.into_boxed_slice();
79
80        debug_assert!(data.len() <= BUILT_PAYLOAD_MAX_LENGTH);
81        debug!(
82            "A completed payload has been build with a size of: {} bytes",
83            data.len()
84        );
85
86        Ok(Self { data })
87    }
88
89    /// Read a payload from a file
90    pub fn read<P: AsRef<Path>>(path: P) -> Result<Self, PayloadError> {
91        let Ok(bytes) = std::fs::read(path.as_ref()) else {
92            return Err(PayloadError::Io(path.as_ref().into()));
93        };
94        Self::new(&bytes)
95    }
96
97    /// Get the data for the payload
98    pub fn data(&self) -> &[u8] {
99        &self.data
100    }
101}
102
103/// An error while trying to create a payload
104#[derive(Debug, PartialEq, Eq, Error, Clone)]
105#[non_exhaustive]
106pub enum PayloadError {
107    /// Reading payload failed, std::io::Error
108    #[error("Payload failed to read from file: {0}")]
109    Io(PathBuf),
110
111    /// Payload is less than the minimum length
112    #[error("Payload invalid size: `{0}` (expected >= {})", PAYLOAD_MIN_LENGTH)]
113    PayloadTooShort(usize),
114
115    /// Payload is greater than the maximum length
116    #[error("Payload invalid size: `{0}` (expected < {})", PAYLOAD_MAX_LENGTH)]
117    PayloadTooLong(usize),
118}
119
120#[cfg(test)]
121mod tests {
122    use super::Payload;
123
124    /// Tests that we generate the same bin as the reference implementation
125    #[test]
126    fn basic_correctness() {
127        let correct = include_bytes!("test/hekate_ctcaer_5.7.2_ref_payload.bin");
128        let payload = Payload::new(include_bytes!("test/hekate_ctcaer_5.7.2.bin"))
129            .expect("This should give us a valid payload");
130
131        assert_eq!(payload.data.as_ref(), correct);
132    }
133}