Курс — Хакинг на Rust. #31 Продвинутые темы. Интеграция с другими языками: Встраивание Rust в C/C++ проекты
Здравствуйте, дорогие друзья.
Rust и C/C++ часто называют «естественными союзниками». C/C++ доминируют в системном программировании, но их сложность управления памятью и уязвимости вроде переполнения буфера делают их рискованными для критичных к безопасности задач. Rust, с его системой владения и безопасностью на этапе компиляции, позволяет поэтапно модернизировать legacy-проекты, заменяя уязвимые компоненты без переписывания всего кода.
Примеры применения:
- Ускорение «горячих» участков кода на C/C++ через Rust.
 - Замена уязвимых функций работы с памятью (например, 
strcpy→ безопасный аналог на Rust). - Создание оберток для C-библиотек с автоматической проверкой безопасности.
 
Основы FFI: Foreign Function Interface
Rust предоставляет атрибут #[no_mangle] для предотвращения изменения имен функций компилятором и ключевое слово extern для указания внешнего ABI.
Пример 1: Экспорт функции из Rust в C
| 
					 1 2 3 4 5 6 7  | 
						// src/lib.rs #[no_mangle] pub extern "C" fn rust_hash(data: *const u8, len: usize) -> u32 {     let slice = unsafe { std::slice::from_raw_parts(data, len) };     // Пример простой хэш-функции     slice.iter().fold(0, |acc, &x| acc.wrapping_add(x as u32)) }  | 
					
Компиляция в статическую библиотеку:
| 
					 1 2  | 
						cargo build --release # Результат: target/release/libyour_crate.a  | 
					
Использование в C++:
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						#include <iostream> // Объявляем функцию из Rust extern "C" uint32_t rust_hash(const uint8_t* data, size_t len); int main() {     const char* text = "Hello, FFI!";     uint32_t hash = rust_hash(reinterpret_cast<const uint8_t*>(text), strlen(text));     std::cout << "Hash: " << hash << std::endl;     return 0; }  | 
					
Работа с памятью и строками
Передача строк между языками требует осторожности. В C строки — это char*, в Rust — String или &str.
Пример 2: Возврат строки из Rust в C
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
						use std::ffi::CString; use std::os::raw::c_char; #[no_mangle] pub extern "C" fn get_rust_string() -> *mut c_char {     let s = CString::new("Hello from Rust!").unwrap();     // Передаем владение C-коду     s.into_raw() } #[no_mangle] pub extern "C" fn free_rust_string(s: *mut c_char) {     unsafe {         if !s.is_null() {             // Освобождаем память, выделенную Rust             CString::from_raw(s);         }     } }  | 
					
В C:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						#include <stdio.h> #include <stdlib.h> extern char* get_rust_string(); extern void free_rust_string(char* s); int main() {     char* str = get_rust_string();     printf("%s\n", str);     free_rust_string(str); // Обязательно!     return 0; }  | 
					
Интеграция через bindgen: генерация оберток для C-кода
Утилита bindgen автоматически создает Rust-обертки для C-заголовков.
Пример 3: Использование bindgen для работы с OpenSSL
- Добавьте 
bindgenвCargo.toml: 
| 
					 1 2  | 
						[build-dependencies] bindgen = "0.60"  | 
					
2. Создайте build.rs:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						extern crate bindgen; use std::path::PathBuf; fn main() {     let bindings = bindgen::Builder::default()         .header("wrapper.h")  // Заголовок с #include <openssl/sha.h>         .generate()         .unwrap();     bindings.write_to_file(PathBuf::from("src/bindings.rs")).unwrap(); }  | 
					
3. В wrapper.h:
| 
					 1  | 
						#include <openssl/sha.h>  | 
					
Теперь в Rust можно вызывать функции OpenSSL:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						use bindings::{SHA256_CTX, SHA256_Init, SHA256_Update, SHA256_Final}; pub fn sha256_hash(data: &[u8]) -> [u8; 32] {     let mut ctx = SHA256_CTX::default();     unsafe {         SHA256_Init(&mut ctx);         SHA256_Update(&mut ctx, data.as_ptr() as _, data.len());         let mut hash = [0u8; 32];         SHA256_Final(hash.as_mut_ptr(), &mut ctx);         hash     } }  | 
					
Обработка ошибок
В C ошибки обычно возвращаются через коды, в Rust — через Result. Для совместимости можно преобразовать Result в целочисленные коды.
Пример 4: Возврат кода ошибки из Rust
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
						#[repr(C)] pub enum RustError {     Success = 0,     NullPointer = 1,     InvalidData = 2, } #[no_mangle] pub extern "C" fn process_data(data: *const u8, len: usize) -> RustError {     if data.is_null() {         return RustError::NullPointer;     }     let slice = unsafe { std::slice::from_raw_parts(data, len) };     if slice.len() == 0 {         return RustError::InvalidData;     }     // Логика обработки     RustError::Success }  | 
					
В C:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  | 
						typedef enum {     RustError_Success,     RustError_NullPointer,     RustError_InvalidData } RustError; extern RustError process_data(const uint8_t* data, size_t len); int main() {     uint8_t data[] = {1, 2, 3};     RustError err = process_data(data, 3);     if (err != RustError_Success) {         // Обработка ошибки     }     return 0; }  | 
					
Интеграция в CMake и Makefile
Для сборки проектов на C/C++ с использованием Rust-кода можно вызывать cargo из CMake:
CMakeLists.txt:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
						cmake_minimum_required(VERSION 3.14) project(MyProject) # Сборка Rust-библиотеки add_custom_command(     OUTPUT libmy_rust_lib.a     COMMAND cargo build --release     WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/rust_lib ) # Добавление статической библиотеки add_library(my_rust_lib STATIC IMPORTED) set_target_properties(my_rust_lib PROPERTIES     IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/rust_lib/target/release/libmy_rust_lib.a ) # Основная цель add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE my_rust_lib)  | 
					
Безопасность и анти-паттерны
- Избегайте 
unsafeбез необходимости. Даже при работе с FFI старайтесь минимизировать его использование. - Проверяйте входные данные. Например, перед преобразованием 
*const u8в срез, убедитесь, что указатель не нулевой и длина корректна. - Используйте 
ManuallyDropдля управления временем жизни объектов. 
Пример из кибербезопасности: оптимизация фаззера
Предположим, у вас есть медленный фаззер на Python. Вы можете переписать его ядро на Rust и вызывать через FFI:
Rust-код (fuzzer_core.rs):
| 
					 1 2 3 4 5 6  | 
						#[no_mangle] pub extern "C" fn fuzz(data: *const u8, len: usize) -> bool {     let data = unsafe { std::slice::from_raw_parts(data, len) };     // Логика фаззинга     data.iter().sum::<u8>() % 2 == 0 // Пример условия }  | 
					
Python-код (с использованием ctypes):
| 
					 1 2 3 4 5 6 7 8 9  | 
						import ctypes lib = ctypes.CDLL('./libfuzzer_core.so') lib.fuzz.argtypes = (ctypes.POINTER(ctypes.c_ubyte), ctypes.c_size_t) lib.fuzz.restype = ctypes.c_bool def fuzz(data):     buf = (ctypes.c_ubyte * len(data))(*data)     return lib.fuzz(buf, len(data))  | 
					
Заключение
Интеграция Rust с C/C++ позволяет совместить высокую производительность и безопасность. Это особенно ценно в кибербезопасности, где ошибки управления памятью приводят к критическим уязвимостям. Начните с замены отдельных компонентов, используйте bindgen для автоматизации и всегда проверяйте входные данные в FFI-функциях.

На этом все. Всем хорошего дня!
