mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
358 lines
9.5 KiB
Markdown
358 lines
9.5 KiB
Markdown
# Rust Grundlagen
|
||
|
||
{{#include ../banners/hacktricks-training.md}}
|
||
|
||
### Generische Typen
|
||
|
||
Erstellen Sie eine Struktur, bei der 1 ihrer Werte jeden Typ haben könnte.
|
||
```rust
|
||
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
|
||
|
||
Der Option-Typ bedeutet, dass der Wert vom Typ Some (es gibt etwas) oder None sein könnte:
|
||
```rust
|
||
pub enum Option<T> {
|
||
None,
|
||
Some(T),
|
||
}
|
||
```
|
||
Sie können Funktionen wie `is_some()` oder `is_none()` verwenden, um den Wert der Option zu überprüfen.
|
||
|
||
### Makros
|
||
|
||
Makros sind leistungsfähiger als Funktionen, da sie sich erweitern, um mehr Code zu erzeugen, als den Code, den Sie manuell geschrieben haben. Zum Beispiel muss eine Funktionssignatur die Anzahl und den Typ der Parameter deklarieren, die die Funktion hat. Makros hingegen können eine variable Anzahl von Parametern annehmen: Wir können `println!("hello")` mit einem Argument oder `println!("hello {}", name)` mit zwei Argumenten aufrufen. Außerdem werden Makros erweitert, bevor der Compiler die Bedeutung des Codes interpretiert, sodass ein Makro beispielsweise ein Trait für einen bestimmten Typ implementieren kann. Eine Funktion kann das nicht, da sie zur Laufzeit aufgerufen wird und ein Trait zur Compile-Zeit implementiert werden muss.
|
||
```rust
|
||
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!");
|
||
};
|
||
}
|
||
}
|
||
```
|
||
### Iterieren
|
||
```rust
|
||
// 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() {
|
||
```
|
||
### Rekursive Box
|
||
```rust
|
||
enum List {
|
||
Cons(i32, List),
|
||
Nil,
|
||
}
|
||
|
||
let list = Cons(1, Cons(2, Cons(3, Nil)));
|
||
```
|
||
### Bedingte Anweisungen
|
||
|
||
#### wenn
|
||
```rust
|
||
let n = 5;
|
||
if n < 0 {
|
||
print!("{} is negative", n);
|
||
} else if n > 0 {
|
||
print!("{} is positive", n);
|
||
} else {
|
||
print!("{} is zero", n);
|
||
}
|
||
```
|
||
#### Übereinstimmung
|
||
```rust
|
||
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
|
||
};
|
||
```
|
||
#### Schleife (unendlich)
|
||
```rust
|
||
loop {
|
||
count += 1;
|
||
if count == 3 {
|
||
println!("three");
|
||
continue;
|
||
}
|
||
println!("{}", count);
|
||
if count == 5 {
|
||
println!("OK, that's enough");
|
||
break;
|
||
}
|
||
}
|
||
```
|
||
#### während
|
||
```rust
|
||
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;
|
||
}
|
||
```
|
||
#### für
|
||
```rust
|
||
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
|
||
```rust
|
||
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");
|
||
}
|
||
```
|
||
#### während lass
|
||
```rust
|
||
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.
|
||
}
|
||
```
|
||
### Eigenschaften
|
||
|
||
Erstellen Sie eine neue Methode für einen Typ
|
||
```rust
|
||
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);
|
||
```
|
||
### Tests
|
||
```rust
|
||
#[cfg(test)]
|
||
mod tests {
|
||
#[test]
|
||
fn you_can_assert() {
|
||
assert!(true);
|
||
assert_eq!(true, true);
|
||
assert_ne!(true, false);
|
||
}
|
||
}
|
||
```
|
||
### Threading
|
||
|
||
#### Arc
|
||
|
||
Ein Arc kann Clone verwenden, um weitere Referenzen auf das Objekt zu erstellen, um sie an die Threads zu übergeben. Wenn der letzte Referenzzeiger auf einen Wert außerhalb des Gültigkeitsbereichs ist, wird die Variable verworfen.
|
||
```rust
|
||
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);
|
||
});
|
||
}
|
||
```
|
||
#### Threads
|
||
|
||
In diesem Fall werden wir dem Thread eine Variable übergeben, die er ändern kann.
|
||
```rust
|
||
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));
|
||
}
|
||
}
|
||
```
|
||
### Sicherheit Essentials
|
||
|
||
Rust bietet standardmäßig starke Garantien für die Speichersicherheit, aber Sie können dennoch kritische Schwachstellen durch `unsafe`-Code, Abhängigkeitsprobleme oder logische Fehler einführen. Das folgende Mini-Spickzettel sammelt die Primitiven, mit denen Sie während offensiver oder defensiver Sicherheitsüberprüfungen von Rust-Software am häufigsten in Berührung kommen werden.
|
||
|
||
#### Unsafe-Code & Speichersicherheit
|
||
|
||
`unsafe`-Blöcke verzichten auf die Aliasierung und Bereichsprüfungen des Compilers, sodass **alle traditionellen Speicherbeschädigungsfehler (OOB, use-after-free, double free usw.) wieder auftreten können**. Eine schnelle Prüfungscheckliste:
|
||
|
||
* Suchen Sie nach `unsafe`-Blöcken, `extern "C"`-Funktionen, Aufrufen von `ptr::copy*`, `std::mem::transmute`, `MaybeUninit`, rohen Zeigern oder `ffi`-Modulen.
|
||
* Validieren Sie jede Zeigerarithmetik und jedes Längenargument, das an Low-Level-Funktionen übergeben wird.
|
||
* Bevorzugen Sie `#![forbid(unsafe_code)]` (crate-weit) oder `#[deny(unsafe_op_in_unsafe_fn)]` (1.68 +), um die Kompilierung zu fehlschlagen, wenn jemand `unsafe` wieder einführt.
|
||
|
||
Beispielüberlauf, der mit rohen Zeigern erstellt wurde:
|
||
```rust
|
||
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 auszuführen ist eine kostengünstige Möglichkeit, UB zur Testzeit zu erkennen:
|
||
```bash
|
||
rustup component add miri
|
||
cargo miri test # hunts for OOB / UAF during unit tests
|
||
```
|
||
#### Auditing dependencies with RustSec / cargo-audit
|
||
|
||
Die meisten realen Rust-Sicherheitsanfälligkeiten befinden sich in Drittanbieter-Crates. Die RustSec Advisory-Datenbank (gemeinschaftlich betrieben) kann lokal abgefragt werden:
|
||
```bash
|
||
cargo install cargo-audit
|
||
cargo audit # flags vulnerable versions listed in Cargo.lock
|
||
```
|
||
Integriere es in CI und fehle bei `--deny warnings`.
|
||
|
||
`cargo deny check advisories` bietet ähnliche Funktionalität sowie Lizenz- und Verbotslistenprüfungen.
|
||
|
||
#### Überprüfung der Lieferkette mit cargo-vet (2024)
|
||
|
||
`cargo vet` zeichnet einen Überprüfungs-Hash für jedes Paket auf, das du importierst, und verhindert unbemerkte Upgrades:
|
||
```bash
|
||
cargo install cargo-vet
|
||
cargo vet init # generates vet.toml
|
||
cargo vet --locked # verifies packages referenced in Cargo.lock
|
||
```
|
||
Das Tool wird von der Rust-Projektinfrastruktur und einer wachsenden Anzahl von Organisationen übernommen, um Angriffe mit vergifteten Paketen zu mildern.
|
||
|
||
#### Fuzzing Ihrer API-Oberfläche (cargo-fuzz)
|
||
|
||
Fuzz-Tests erfassen leicht Panics, Ganzzahlüberläufe und Logikfehler, die zu DoS- oder Seitenkanalproblemen werden könnten:
|
||
```bash
|
||
cargo install cargo-fuzz
|
||
cargo fuzz init # creates fuzz_targets/
|
||
cargo fuzz run fuzz_target_1 # builds with libFuzzer & runs continuously
|
||
```
|
||
Fügen Sie das Fuzz-Ziel zu Ihrem Repository hinzu und führen Sie es in Ihrer Pipeline aus.
|
||
|
||
## Referenzen
|
||
|
||
- RustSec Advisory Database – <https://rustsec.org>
|
||
- Cargo-vet: "Auditing your Rust Dependencies" – <https://mozilla.github.io/cargo-vet/>
|
||
|
||
{{#include ../banners/hacktricks-training.md}}
|