带 windows CryptoUnprotectData 的 Rust FFI

Rust FFI with windows CryptoUnprotectData

我正在尝试从一些简单的东西开始(并结合实际使用)来学习 FFI,但这似乎行不通:

mod bindings {
    ::windows::include_bindings!();
}

use std::{convert::TryFrom, ptr};
 
use bindings::{
    windows::win32::security::CryptUnprotectData,
    windows::win32::security::CRYPTOAPI_BLOB
};
 
// Powershell code to generate the token
// $pw = read-host "Enter Token" -AsSecureString
// ConvertFrom-SecureString $pw
 
fn main() -> windows::Result<()> {
    // The encrypted string is 'foobar'
    let encrypted_token = "01000000d08c9ddf0115d1118c7a00c04fc297eb01000000c336dca1c99b7d40ae3f797c2b5d2951000000000200000000001066000000010000200000007a87d6ac2fc8037bef45e3dbcb0b652432a22a9b48fc5fa3e4fcfd9aaf922949000000000e8000000002000020000000eeaa76a44b6cd5da837f4b0f7040de8e2795ed846f8abe2c7f2d2365d00cf89c1000000069fcaa7fa475178d623f4adab1b08ac4400000008af807014cba53ed2f1e7b8a54c6ad89ff57f0ee3d8c51ecd8c5b48e99b58d0e738c9fae9fc41b4280938865a047f2724106d34313c88a0f3852d5ba9d75abfd";
    let mut et_bytes = hex::decode(encrypted_token).unwrap();
    let size = u32::try_from(et_bytes.len()).unwrap();
    let mut decrypted = vec![0u8; et_bytes.len()];
    let dt_bytes = &mut decrypted;

    let mut p_data_in = CRYPTOAPI_BLOB {
        cb_data: size,
        pb_data: et_bytes.as_mut_ptr(),
    };
    let mut p_data_out = CRYPTOAPI_BLOB {
        cb_data: size,
        pb_data: dt_bytes.as_mut_ptr(),
    };

    let pin = &mut p_data_in;
    let pout = &mut p_data_out;
 
    unsafe {
        let result = CryptUnprotectData(
            pin,
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            0,
            pout
        );

        println!("{:?}, {:?}", dt_bytes, result);
    }
 
    Ok(())
}

基本上它 return 是全零数组,但是 CryptUnprotectData return 的结果是 1,根据文档意味着成功:https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptunprotectdata

我已经通过尝试破坏十六进制字符串从而破坏加密数据,从而导致它 return 0 来验证这一点。我不确定它是否正在写入错误的位置或其他东西,但是大概成功条件意味着它写在某处。

CryptUnprotectData API 为您分配输出缓冲区。它不会写入您提供的缓冲区。这就是为什么您不断获取原始数据,而不管 API 调用的结果如何。

相反,您需要传入一个(默认初始化的)CRYPTOAPI_BLOB 结构,并观察 API 传回的值,如下所示:

fn main() -> windows::Result<()> {
    // The encrypted string is 'foobar'
    let encrypted_token = "01000000d08c9ddf0115d1118c7a00c04fc297eb01000000c336dca1c99b7d40ae3f797c2b5d2951000000000200000000001066000000010000200000007a87d6ac2fc8037bef45e3dbcb0b652432a22a9b48fc5fa3e4fcfd9aaf922949000000000e8000000002000020000000eeaa76a44b6cd5da837f4b0f7040de8e2795ed846f8abe2c7f2d2365d00cf89c1000000069fcaa7fa475178d623f4adab1b08ac4400000008af807014cba53ed2f1e7b8a54c6ad89ff57f0ee3d8c51ecd8c5b48e99b58d0e738c9fae9fc41b4280938865a047f2724106d34313c88a0f3852d5ba9d75abfd";
    let mut et_bytes = hex::decode(encrypted_token).unwrap();
    let size = u32::try_from(et_bytes.len()).unwrap();

    let mut p_data_in = CRYPTOAPI_BLOB {
        cb_data: size,
        pb_data: et_bytes.as_mut_ptr(),
    };
    // Default-initialze; don't allocate any memory
    let mut p_data_out = CRYPTOAPI_BLOB::default();

    let pin = &mut p_data_in;
    let pout = &mut p_data_out;
 
    unsafe {
        let result = CryptUnprotectData(
            pin,
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            0,
            pout
        );

        // Probably safe to ignore `result`
        if !p_data_out.pb_data.is_null() {
            // Construct a slice from the returned data
            let output = from_raw_parts(p_data_out.pb_data, p_data_out.cb_data as _);
            println!("{:?}", output);

            // Cleanup
            LocalFree(p_data_out.pb_data as _);
    }
 
    Ok(())
}

这会为我生成以下输出:

[102, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0]

这是 foobar 的 UTF-16LE 编码。


请注意,您需要生成并导入 windows::win32::system_services::LocalFree 才能执行清理。