Rust Basics

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Generic Types

๊ฐ’ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์–ด๋–ค ํƒ€์ž…์ด ๋  ์ˆ˜ ์žˆ๋Š” 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์˜ ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งคํฌ๋กœ

๋งคํฌ๋กœ๋Š” ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ณด๋‹ค ๋” ๋งŽ์€ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•จ์ˆ˜๋ณด๋‹ค ๋” ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๊ฐ€์ง„ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์ˆ˜์™€ ์œ ํ˜•์„ ์„ ์–ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด์— ๋งคํฌ๋กœ๋Š” ๊ฐ€๋ณ€ ๊ฐœ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: println!("hello")๋ฅผ ํ•˜๋‚˜์˜ ์ธ์ˆ˜๋กœ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ println!("hello {}", name)์„ ๋‘ ๊ฐœ์˜ ์ธ์ˆ˜๋กœ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋งคํฌ๋กœ๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ฝ”๋“œ์˜ ์˜๋ฏธ๋ฅผ ํ•ด์„ํ•˜๊ธฐ ์ „์— ํ™•์žฅ๋˜๋ฏ€๋กœ, ๋งคํฌ๋กœ๋Š” ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ์–ด์ง„ ์œ ํ˜•์— ๋Œ€ํ•ด ํŠธ๋ ˆ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋Š” ๋Ÿฐํƒ€์ž„์— ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋ ˆ์ดํŠธ๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

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);
}
}

์ผ์น˜

#![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
};
}

๋ฃจํ”„ (๋ฌดํ•œ)

#![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;
}
}

for

#![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.
}
}

ํŠน์„ฑ

ํƒ€์ž…์„ ์œ„ํ•œ ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ ์ƒ์„ฑ

#![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์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ฐธ์กฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ์Šค๋ ˆ๋“œ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ’์— ๋Œ€ํ•œ ๋งˆ์ง€๋ง‰ ์ฐธ์กฐ ํฌ์ธํ„ฐ๊ฐ€ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ๋ณ€์ˆ˜๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.

#![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);
});
}
}

Threads

์ด ๊ฒฝ์šฐ ์Šค๋ ˆ๋“œ์— ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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));
}
}

Security Essentials

Rust๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ•๋ ฅํ•œ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ unsafe ์ฝ”๋“œ, ์˜์กด์„ฑ ๋ฌธ์ œ ๋˜๋Š” ๋…ผ๋ฆฌ์  ์‹ค์ˆ˜๋ฅผ ํ†ตํ•ด ์น˜๋ช…์ ์ธ ์ทจ์•ฝ์ ์„ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ฏธ๋‹ˆ ์น˜ํŠธ์‹œํŠธ๋Š” Rust ์†Œํ”„ํŠธ์›จ์–ด์˜ ๊ณต๊ฒฉ์  ๋˜๋Š” ๋ฐฉ์–ด์  ๋ณด์•ˆ ๊ฒ€ํ†  ์ค‘ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์œผ๋กœ ์ ‘ํ•˜๊ฒŒ ๋  ์›์‹œ ์š”์†Œ๋“ค์„ ๋ชจ์•„๋†“์•˜์Šต๋‹ˆ๋‹ค.

Unsafe code & memory safety

unsafe ๋ธ”๋ก์€ ์ปดํŒŒ์ผ๋Ÿฌ์˜ ๋ณ„์นญ ๋ฐ ๊ฒฝ๊ณ„ ๊ฒ€์‚ฌ๋ฅผ ์„ ํƒ ํ•ด์ œํ•˜๋ฏ€๋กœ ๋ชจ๋“  ์ „ํ†ต์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์†์ƒ ๋ฒ„๊ทธ(OOB, use-after-free, double free ๋“ฑ)๊ฐ€ ๋‹ค์‹œ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น ๋ฅธ ๊ฐ์‚ฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ:

  • unsafe ๋ธ”๋ก, extern "C" ํ•จ์ˆ˜, ptr::copy*, std::mem::transmute, MaybeUninit, ์›์‹œ ํฌ์ธํ„ฐ ๋˜๋Š” ffi ๋ชจ๋“ˆ์„ ์ฐพ์œผ์„ธ์š”.
  • ์ €์ˆ˜์ค€ ํ•จ์ˆ˜์— ์ „๋‹ฌ๋˜๋Š” ๋ชจ๋“  ํฌ์ธํ„ฐ ์‚ฐ์ˆ  ๋ฐ ๊ธธ์ด ์ธ์ˆ˜๋ฅผ ๊ฒ€์ฆํ•˜์„ธ์š”.
  • ๋ˆ„๊ตฐ๊ฐ€ unsafe๋ฅผ ๋‹ค์‹œ ๋„์ž…ํ•  ๋•Œ ์ปดํŒŒ์ผ์ด ์‹คํŒจํ•˜๋„๋ก #![forbid(unsafe_code)] (ํฌ๋ ˆ์ดํŠธ ์ „์ฒด) ๋˜๋Š” #[deny(unsafe_op_in_unsafe_fn)] (1.68 +)๋ฅผ ์„ ํ˜ธํ•˜์„ธ์š”.

์›์‹œ ํฌ์ธํ„ฐ๋กœ ์ƒ์„ฑ๋œ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ์˜ˆ:

#![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

Auditing dependencies with RustSec / cargo-audit

๋Œ€๋ถ€๋ถ„์˜ ์‹ค์ œ Rust ์ทจ์•ฝ์ ์€ ์„œ๋“œํŒŒํ‹ฐ ํฌ๋ ˆ์ดํŠธ์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. RustSec ์ž๋ฌธ DB(์ปค๋ฎค๋‹ˆํ‹ฐ ๊ธฐ๋ฐ˜)๋Š” ๋กœ์ปฌ์—์„œ ์ฟผ๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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

CI์— ํ†ตํ•ฉํ•˜๊ณ  --deny warnings์—์„œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

cargo deny check advisories๋Š” ์œ ์‚ฌํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ ๋ผ์ด์„ผ์Šค ๋ฐ ๊ธˆ์ง€ ๋ชฉ๋ก ๊ฒ€์‚ฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

cargo-vet์„ ํ†ตํ•œ ๊ณต๊ธ‰๋ง ๊ฒ€์ฆ (2024)

cargo vet๋Š” ๊ฐ€์ ธ์˜ค๋Š” ๋ชจ๋“  crate์— ๋Œ€ํ•œ ๊ฒ€ํ†  ํ•ด์‹œ๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋ˆˆ์น˜์ฑ„์ง€ ๋ชปํ•œ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค:

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

์ด ๋„๊ตฌ๋Š” Rust ํ”„๋กœ์ ํŠธ ์ธํ”„๋ผ์™€ ์ฆ๊ฐ€ํ•˜๋Š” ์ˆ˜์˜ ์กฐ์ง์—์„œ ์˜ค์—ผ๋œ ํŒจํ‚ค์ง€ ๊ณต๊ฒฉ์„ ์™„ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ฑ„ํƒ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

API ํ‘œ๋ฉด์˜ ํผ์ง• (cargo-fuzz)

ํผ์ง• ํ…Œ์ŠคํŠธ๋Š” ํŒจ๋‹‰, ์ •์ˆ˜ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ๋ฐ DoS ๋˜๋Š” ์‚ฌ์ด๋“œ ์ฑ„๋„ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š” ๋…ผ๋ฆฌ ๋ฒ„๊ทธ๋ฅผ ์‰ฝ๊ฒŒ ํฌ์ฐฉํ•ฉ๋‹ˆ๋‹ค:

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

๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ํผ์ฆˆ ํƒ€๊ฒŸ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์‹คํ–‰ํ•˜์„ธ์š”.

References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ