Перейти к содержанию

Макросы, строки и секреты - магия запутывания Rust

Posted on:25 апреля 2023 г. at 09:07

Example Dynamic OG Image link Rust известен своими функциями безопасности, но знаете ли вы, что он также может помочь вам защитить ваш код от хакеров? Используя мощные функции защиты памяти Rust в сочетании с запутыванием строк, вы можете создавать код, который одновременно безопасен и труден для обратного инжиниринга. В этой статье мы рассмотрим, как Rust можно использовать для запутывания ваших строк и защиты вашего кода от посторонних глаз.

use std::env;

const KEY:&str = "SUPER_SECRET_PASSWORD";
fn main() {
   let guess = env::args().nth(1).expect("No guess given");
   if KEY == guess {
       println!("correct");
   } else {
       println!("Incorrect");
   }
}

Без каких-либо подсказок это кажется невозможным решить. Однако ответ хранится ясно как божий день в самом двоичном коде! Каждый двоичный файл имеет память, доступную только для чтения (ROM), где хранятся все константы и строковые литералы, на которые программа может ссылаться на протяжении всего времени выполнения. Strings - это программа, которая просматривает ПЗУ двоичного файла и выводит все, что имеет длину более 4 символов. Что делает поиск пароля тривиальным.

// opt-level=z в основном является cargo build --release
rustc -C opt-level=z main.rs
// Grep для фильтрации шума
strings main | grep SUPER_SECRECT_PASSWORD
No guess givenmain.rsSUPER_SECRET_PASSWORDcorrect

Я немного схитрил, но это показывает, что не требуется много усилий, чтобы раскрыть конфиденциальную информацию злоумышленнику. Чтобы этого не произошло, нам нужно запутать наши строки. К счастью, в Rust есть несколько методов для этого, и мы рассмотрим некоторые из них в следующем разделе.

Святой макрос хеширования

Макросы - это мощный инструмент в Rust для генерации кода во время компиляции. Они позволяют вам писать код, который записывает больше кода, что может быть невероятно полезно для запутывания строк. Хотя поиск предыдущего пароля был тривиальным, наличие конфиденциальной информации в ПЗУ работает в нашу пользу. Система владения и заимствования Rust гарантирует, что эти строки доступны только тому коду, который в них нуждается. Это означает, что он предотвращает изменение данных во время выполнения. Вот тут-то и вступает в силу магия. Используя макросы, мы можем генерировать эти конфиденциальные строки во время компиляции, вместо того чтобы сохранять их в виде обычного текста в памяти двоичного файла. Это значительно затрудняет злоумышленникам извлечение конфиденциальной информации из двоичного файла. Чтобы еще больше защитить наши строки, мы можем хэшировать их с помощью SHA-256. Выполнение этого может еще больше повысить безопасность строк, добавив значение соли для предотвращения атак rainbow и dictionar.

use sha2::{Digest,Sha256};
use std::env;
macro_rules! sha256 {
   ($input:expr,$salt:expr) => {{
       let input = format!("{}{}",$input,$salt);
       let mut hasher = Sha256::new();
       hasher.update(input);
       hasher.finalize()
   }};
}
fn main() {
   let key = sha256!("SUPER_SECRET_PASSWORD","SUPER_SECRET_SALT");
   let guess = env::args().nth(1).expect("No Guess");
   let guess_hash = sha256!(guess,"SUPER_SECRET_SALT");
   if key == guess_hash {
       println!("Correct");
   } else {
       println!("Incorrect");
   }
}

Теперь все конфиденциальные данные находятся в дополнительной безопасности! Двоичный файл содержит только зашифрованный SHA-256 хэш моего пароля. Это означает, что даже если злоумышленник каким-то образом узнает пароль, ему также потребуется восстановить уникальную соль, использованную в процессе хеширования, чтобы взломать код. Использование скрытого хэширования значительно увеличивает сложность взлома пароля и обеспечивает дополнительный уровень безопасности хранимых данных.

Некромантия! Мертвые ветви кода

Генерация мертвых ветвей в вашем коде является эффективной контрмерой против обратного инжиниринга. Делая это, вы можете запутать злоумышленников, добавив ненужные пути к коду. В следующем примере мы будем использовать другой макрос для генерации мертвых ветвей, которые выполняются только на определенных архитектурах. Т.е. добавим еще один уровень запутывания в код. Хотя генерация мертвых ветвей не делает ваш код полностью безопасным, это может усложнить и отнять больше времени у злоумышленников на анализ и обратного инжиниринга.

use sha2::{Digest,Sha256};
use std::env;
macro_rules! generate_branches {
   ($count:expr) => {{
       #[allow(unused_assignments)]
       let mut result = 0;
       for i in 0..$count {
           match std::env::consts::ARCH {
               "x86_64" => result += i,
               "arm" => result -= i,
               _ => result *= i,
           }
       }
       result
   }};
}

macro_rules! sha256 {
   ($input:expr,$salt:expr) => {{
       let input = format!("{}{}",$input,$salt);
       let mut hasher = Sha256::new();
       hasher.update(input);
       hasher.finalize()
   }};
}
fn main() {
   let key = sha256!("SUPER_SECRET_PASSWORD","SALTYBOY");
   let guess = env::args().nth(1).expect("No Guess");
   let guess_hash = sha256!(guess,"SALTYBOY");
   generate_branches!(1000);
   if key == guess_hash {
       println!("correct");
   } else {
       println!("Incorrect");
   }
}

В заключение, функции защиты памяти Rust и мощные макросы могут быть использованы для создания запутанного кода, который одновременно безопасен и труден для обратного проектирования. Используя обфускацию строк (такие методы, как хэширование и генерация мертвых ветвей), мы можем значительно затруднить злоумышленникам извлечение конфиденциальной информации из нашего кода. Важно помнить, что никакое количество запутываний не может сделать код полностью безопасным. Однако это может усложнить и отнять много времени у злоумышленников на анализ и обратный инжиниринг. Используя мощные инструменты запутывания Rust, мы можем значительно затруднить хакерам доступ к нашим конфиденциальным данным и коду.