Основи Rust

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Власність змінних

Пам’ять керується системою власності з такими правилами, які компілятор перевіряє на етапі компіляції:

  1. Кожне значення в Rust має змінну, яку називають його власником.
  2. Одночасно може бути лише один власник.
  3. Коли власник виходить з області видимості, значення буде звільнене.
fn main() {
let student_age: u32 = 20;
{ // Scope of a variable is within the block it is declared in, which is denoted by brackets
let teacher_age: u32 = 41;
println!("The student is {} and teacher is {}", student_age, teacher_age);
} // when an owning variable goes out of scope, it will be dropped

// println!("the teacher is {}", teacher_age); // this will not work as teacher_age has been dropped
}

Узагальнені типи

Створіть struct, де одне зі значень може мати будь-який тип

#![allow(unused)]
fn main() {
struct Wrapper<T> {
value: T,
}

impl<T> Wrapper<T> {
pub fn new(value: T) -> Self {
Wrapper { value }
}
}

Wrapper::new(42).value
Wrapper::new("Foo").value, "Foo"
}

Option, Some & None

Тип Option означає, що значення може бути типу Some (є дещо) або None:

#![allow(unused)]
fn main() {
pub enum Option<T> {
None,
Some(T),
}
}

Ви можете використовувати функції, такі як is_some() або is_none(), щоб перевірити значення Option.

Result, Ok & Err

Використовується для повернення та поширення помилок

#![allow(unused)]
fn main() {
pub enum Result<T, E> {
Ok(T),
Err(E),
}
}

You can use functions such as is_ok() or is_err() to check the value of the result

The Option enum should be used in situations where a value might not exist (be None). The Result enum should be used in situations where you do something that might go wrong

Макроси

Макроси потужніші за функції, тому що вони розгортаються, щоб згенерувати більше коду, ніж той, який ви написали вручну. Наприклад, сигнатура функції має оголосити кількість і тип параметрів, які має функція. Натомість макроси можуть приймати змінну кількість параметрів: ми можемо викликати println!("hello") з одним аргументом або println!("hello {}", name) з двома аргументами. Крім того, макроси розгортаються до того, як компілятор інтерпретує значення коду, тому макрос, наприклад, може реалізувати trait для заданого типу. Функція не може цього зробити, оскільки вона викликається під час виконання, а trait має бути реалізований під час компіляції.

macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($val:expr) => {
println!("Look at this other macro: {}", $val);
}
}
fn main() {
my_macro!();
my_macro!(7777);
}

// Export a macro from a module
mod macros {
#[macro_export]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}

Ітерація

#![allow(unused)]
fn main() {
// Iterate through a vector
let my_fav_fruits = vec!["banana", "raspberry"];
let mut my_iterable_fav_fruits = my_fav_fruits.iter();
assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
assert_eq!(my_iterable_fav_fruits.next(), None); // When it's over, it's none

// One line iteration with action
my_fav_fruits.iter().map(|x| capitalize_first(x)).collect()

// Hashmap iteration
for (key, hashvalue) in &*map {
for key in map.keys() {
for value in map.values() {
}

Рекурсивна коробка

#![allow(unused)]
fn main() {
enum List {
Cons(i32, List),
Nil,
}

let list = Cons(1, Cons(2, Cons(3, Nil)));
}

Умовні оператори

if

#![allow(unused)]
fn main() {
let n = 5;
if n < 0 {
print!("{} is negative", n);
} else if n > 0 {
print!("{} is positive", n);
} else {
print!("{} is zero", n);
}
}

match

#![allow(unused)]
fn main() {
match number {
// Match a single value
1 => println!("One!"),
// Match several values
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
// TODO ^ Try adding 13 to the list of prime values
// Match an inclusive range
13..=19 => println!("A teen"),
// Handle the rest of cases
_ => println!("Ain't special"),
}

let boolean = true;
// Match is an expression too
let binary = match boolean {
// The arms of a match must cover all the possible values
false => 0,
true => 1,
// TODO ^ Try commenting out one of these arms
};
}

loop (безкінечний)

#![allow(unused)]
fn main() {
loop {
count += 1;
if count == 3 {
println!("three");
continue;
}
println!("{}", count);
if count == 5 {
println!("OK, that's enough");
break;
}
}
}

while

#![allow(unused)]
fn main() {
let mut n = 1;
while n < 101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
n += 1;
}
}

для

#![allow(unused)]
fn main() {
for n in 1..101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else {
println!("{}", n);
}
}

// Use "..=" to make inclusive both ends
for n in 1..=100 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
}

// ITERATIONS

let names = vec!["Bob", "Frank", "Ferris"];
//iter - Doesn't consume the collection
for name in names.iter() {
match name {
&"Ferris" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
//into_iter - COnsumes the collection
for name in names.into_iter() {
match name {
"Ferris" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
//iter_mut - This mutably borrows each element of the collection
for name in names.iter_mut() {
*name = match name {
&mut "Ferris" => "There is a rustacean among us!",
_ => "Hello",
}
}
}

if let

#![allow(unused)]
fn main() {
let optional_word = Some(String::from("rustlings"));
if let word = optional_word {
println!("The word is: {}", word);
} else {
println!("The optional word doesn't contain anything");
}
}

while let

#![allow(unused)]
fn main() {
let mut optional = Some(0);
// This reads: "while `let` destructures `optional` into
// `Some(i)`, evaluate the block (`{}`). Else `break`.
while let Some(i) = optional {
if i > 9 {
println!("Greater than 9, quit!");
optional = None;
} else {
println!("`i` is `{:?}`. Try again.", i);
optional = Some(i + 1);
}
// ^ Less rightward drift and doesn't require
// explicitly handling the failing case.
}
}

Traits

Створіть новий метод для типу

#![allow(unused)]
fn main() {
trait AppendBar {
fn append_bar(self) -> Self;
}

impl AppendBar for String {
fn append_bar(self) -> Self{
format!("{}Bar", self)
}
}

let s = String::from("Foo");
let s = s.append_bar();
println!("s: {}", s);
}

Тести

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
#[test]
fn you_can_assert() {
assert!(true);
assert_eq!(true, true);
assert_ne!(true, false);
}
}
}

Threading

Arc

Arc може використовувати Clone для створення додаткових посилань на об’єкт, щоб передавати їх у threads. Коли останнє посилання на значення виходить з області видимості, змінна звільняється.

#![allow(unused)]
fn main() {
use std::sync::Arc;
let apple = Arc::new("the same apple");
for _ in 0..10 {
let apple = Arc::clone(&apple);
thread::spawn(move || {
println!("{:?}", apple);
});
}
}

Потоки

У цьому випадку ми передамо потоку змінну, яку він зможе змінити

fn main() {
let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
let status_shared = Arc::clone(&status);
thread::spawn(move || {
for _ in 0..10 {
thread::sleep(Duration::from_millis(250));
let mut status = status_shared.lock().unwrap();
status.jobs_completed += 1;
}
});
while status.lock().unwrap().jobs_completed < 10 {
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
}
}

Основи безпеки

Rust за замовчуванням забезпечує сильні гарантії безпеки пам’яті, але ви все ще можете ввести критичні вразливості через unsafe код, проблеми з залежностями або логічні помилки. Нижче — коротка шпаргалка з примітивів, з якими ви найчастіше стикатиметесь під час offensive or defensive security reviews Rust-проєктів.

unsafe-код та безпека пам’яті

unsafe блоки відключають перевірки компілятора щодо aliasing та bounds, тому усі традиційні помилки корупції пам’яті (OOB, use-after-free, double free тощо) можуть з’явитися знову. Короткий чекліст для аудиту:

  • Шукайте unsafe блоки, extern "C" функції, виклики ptr::copy*, std::mem::transmute, MaybeUninit, raw pointers або модулі ffi.
  • Перевіряйте всі операції з арифметикою вказівників та аргументи довжини, які передаються в низькорівневі функції.
  • Віддавайте перевагу #![forbid(unsafe_code)] (для всього crate) або #[deny(unsafe_op_in_unsafe_fn)] (1.68 +), щоб компіляція не проходила, коли хтось повторно вводить unsafe.

Example overflow created with raw pointers:

#![allow(unused)]
fn main() {
use std::ptr;

fn vuln_copy(src: &[u8]) -> Vec<u8> {
let mut dst = Vec::with_capacity(4);
unsafe {
// ❌ copies *src.len()* bytes, the destination only reserves 4.
ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), src.len());
dst.set_len(src.len());
}
dst
}
}

Запуск Miri — недорогий спосіб виявити UB під час тестування:

rustup component add miri
cargo miri test  # hunts for OOB / UAF during unit tests

Аудит залежностей за допомогою RustSec / cargo-audit

Більшість реальних Rust vulns знаходяться в сторонніх crates. RustSec advisory DB (підтримується спільнотою) можна опитати локально:

cargo install cargo-audit
cargo audit              # flags vulnerable versions listed in Cargo.lock

Інтегруйте це в CI і припиняйте виконання при --deny warnings.

cargo deny check advisories пропонує схожу функціональність, а також перевірки ліцензій і чорних списків.

Покриття коду з cargo-tarpaulin

cargo tarpaulin — інструмент звіту про покриття коду для системи збірки Cargo

cargo binstall cargo-tarpaulin
cargo tarpaulin              # no options are required, if no root directory is defined Tarpaulin will run in the current working directory.

На Linux бекенд трасування Tarpaulin за замовчуванням все ще Ptrace і працює лише на процесорах x86_64. Це можна змінити на llvm coverage instrumentation за допомогою --engine llvm. Для Mac і Windows це метод збору за замовчуванням.

Перевірка ланцюга постачання за допомогою cargo-vet (2024)

cargo vet записує review hash для кожного crate, який ви імпортуєте, і запобігає непоміченим оновленням:

cargo install cargo-vet
cargo vet init      # generates vet.toml
cargo vet --locked  # verifies packages referenced in Cargo.lock

Цей інструмент впроваджується в інфраструктуру проєкту Rust та дедалі більшою кількістю організацій, щоб зменшити poisoned-package attacks.

Fuzzing вашої поверхні API (cargo-fuzz)

Fuzz tests легко виявляють panics, integer overflows та logic bugs, які можуть перетворитися на DoS або side-channel проблеми:

cargo install cargo-fuzz
cargo fuzz init              # creates fuzz_targets/
cargo fuzz run fuzz_target_1 # builds with libFuzzer & runs continuously

Додайте fuzz target до вашого репо та запустіть його у вашому pipeline.

Посилання

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks