Курс — Хакинг на 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-функциях.

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