【picoCTF】Rust fixme 3 - ライフタイムと可変参照を整理してXOR復号

問題概要

picoCTFの「Rust fixme 3」という問題の解説記事です。

  • カテゴリ: General Skills
  • 難易度: Easy

問題文

picoCTF Rust fixme 3

解説

この問題では、与えられたRustコードのコンパイルエラーを修正して、隠されたフラグを表示することが求められます。


ステップ1: ソースコードの確認

まずは、提供されたRustソースコード fixme3.tar.gz を展開し、内容を確認します。
$ tar -xvf fixme3.tar.gz
fixme3/
fixme3/Cargo.toml
fixme3/Cargo.lock
fixme3/src/
fixme3/src/main.rs

展開されたディレクトリに移動し、fixme3/src/main.rs の内容を確認します。
$ cd fixme3
$ cat src/main.rs

use xor_cryptor::XORCryptor;

fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
  // Key for decryption
  let key = String::from("CSUCKS");

  // Editing our borrowed value
  borrowed_string.push_str("PARTY FOUL! Here is your flag: ");

  // Create decryption object
  let res = XORCryptor::new(&key);
  if res.is_err() {
    return;
  }
  let xrc = res.unwrap();

  // Did you know you have to do "unsafe operations in Rust?
  // https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
  // Even though we have these memory safe languages, sometimes we need to do things outside of the rules
  // This is where unsafe rust comes in, something that is important to know about in order to keep things in perspective
    
  // unsafe {
    // Decrypt the flag operations 
    let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);

    // Creating a pointer 
    let decrypted_ptr = decrypted_buffer.as_ptr();
    let decrypted_len = decrypted_buffer.len();
        
    // Unsafe operation: calling an unsafe function that dereferences a raw pointer
    let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);

    borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
  // }
  println!("{}", borrowed_string);
}

fn main() {
  // Encrypted flag values
  let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01", "35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3", "61", "f9", "35"];

  // Convert the hexadecimal strings to bytes and collect them into a vector
  let encrypted_buffer: Vec<u8> = hex_values.iter()
    .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
    .collect();

  let mut party_foul = String::from("Using memory unsafe languages is a: ");
  decrypt(encrypted_buffer, &mut party_foul);
}
この main.rs は「unsafeブロック(unsafe { ... })」がテーマの問題で、コメントの通り unsafe操作を正しく囲めていない ためにコンパイルエラーになります。

まずは一度 cargo run を実行して、コンパイルエラーになることを確認します。
$ cargo run

ステップ2: コンパイルエラーを修正

今回の主なエラーは、次の行です。

let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);
std::slice::from_raw_partsraw pointer(生ポインタ)を dereference してスライスを作る ため、Rustでは unsafe な関数として扱われます。 ところが、このコードは unsafe { ... } がコメントアウトされているため、 call to unsafe function is unsafe and requires unsafe function or block のようなエラーになります。

修正ポイント: unsafeブロックを復活させる

最小修正として、コメントアウトされている unsafe {} を元に戻します。

元のコード(コメントアウトされている)

// unsafe {
  ...
// }

修正後

unsafe {
  // Decrypt the flag operations 
  let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);

  // Creating a pointer 
  let decrypted_ptr = decrypted_buffer.as_ptr();
  let decrypted_len = decrypted_buffer.len();
  
  // Unsafe operation: calling an unsafe function that dereferences a raw pointer
  let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);

  borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
}

ステップ3: 修正後のコードで実行

修正後の main.rs は次のようになります(unsafe を有効化)。
use xor_cryptor::XORCryptor;

fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
  // Key for decryption
  let key = String::from("CSUCKS");

  // Editing our borrowed value
  borrowed_string.push_str("PARTY FOUL! Here is your flag: ");

  // Create decryption object
  let res = XORCryptor::new(&key);
  if res.is_err() {
    return;
  }
  let xrc = res.unwrap();

  unsafe {
    // Decrypt the flag operations
    let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);

    // Creating a pointer
    let decrypted_ptr = decrypted_buffer.as_ptr();
    let decrypted_len = decrypted_buffer.len();

    // Unsafe operation: calling an unsafe function that dereferences a raw pointer
    let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);

    borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
  }

  println!("{}", borrowed_string);
}

fn main() {
  // Encrypted flag values
  let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01", "35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3", "61", "f9", "35"];

  // Convert the hexadecimal strings to bytes and collect them into a vector
  let encrypted_buffer: Vec<u8> = hex_values.iter()
    .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
    .collect();

  let mut party_foul = String::from("Using memory unsafe languages is a: ");
  decrypt(encrypted_buffer, &mut party_foul);
}

実行します。

$ cd fixme3
$ cargo run

出力例

Using memory unsafe languages is a: PARTY FOUL! Here is your flag: picoCTF{xxxxx}

※フラグはマスクしています。


使用したコマンドの軽い解説

tar

tar -xvf fixme3.tar.gz

配布されたアーカイブを展開するコマンドです。

cargo run

cargo run

Rustのビルド&実行をまとめて行います。 コンパイルエラーもここで確認できます。


まとめ

picoCTFの「Rust fixme 3」問題では、raw pointer を扱う処理(std::slice::from_raw_parts)が unsafe操作 である点がポイントでした。

▼ポイントは以下の通りです。

  • from_raw_parts のような操作は unsafe { ... } で囲う必要がある
  • unsafe は「危険なことを許可する」宣言なので、必要最小限の範囲に留める

閲覧ありがとうございました!

NEXT
次におすすめ

【picoCTF】Time Machine - Git履歴をたどってフラグを復元

カテゴリ: General Skills難易度: Easy#picoCTF
次の記事へ →
同じカテゴリ/難易度/picoCTFでの表示順が近い記事を優先しておすすめしています。