
استدعاء تزوير مكدس الاستدعاءات لـ Rust
توفر Unwinder تسليحًا كاملًا لتقنية SilentMoonWalk، مما يسمح بالحصول على تزييف كامل ومستقر لمكدس الاستدعاءات في Rust.
تتميز هذه التقنية بالخصائص التالية:
كل التقدير لمبتكري تقنية SilentMoonWalk:
وبالطبع تحية كبيرة إلى namazso بفضل السلسلة على تويتر التي ألهمت هذا المشروع بأكمله.
استورد هذه الحزمة إلى مشروعك بإضافة السطر التالي إلى ملف cargo.toml وقم بالترجمة في وضع release:
[dependencies]
unwinder = "=0.1.4"
تم تغليف الوظيفة الرئيسية لهذه الحزمة في ماكرون:
call_function!() تشغيل أي دالة تعسفية بمكدس استدعاءات نظيف.indirect_syscall!() استدعاء النظام (غير المباشر) المحدد بمكدس استدعاءات نظيف.لاستخدام أي من هذين الماكروين، يلزم استيراد نوع البيانات std::ffi::c_void.
يعيد كلا الماكروين قيمة من نوع *mut c_void يمكن استخدامها لاسترجاع القيمة التي تُرجعها الدالة المنفذة. معلومات أكثر تفصيلًا في قسم الأمثلة.
يُستخدم هذا الماكرو لاستدعاء أي دالة مرغوبة بمكدس استدعاءات نظيف. يتوقع الماكرو الوسائط التالية:
usize أو isize أو مؤشر.يُستخدم هذا الماكرو لتنفيذ أي استدعاء نظام غير مباشر مرغوب بمكدس استدعاءات نظيف. يتوقع الماكرو الوسائط التالية:
من أجل تمرير وسائط من أنواع مختلفة إلى هذين الماكروين، يجب مراعاة الاعتبارات التالية:
usize (u8-u64، i8-i64، bool، إلخ) مباشرة إلى الماكروين.&str و String) كمؤشر.ptr::null()، ptr::null_mut()، إلخ) كقيمة 0 (بغض النظر عما إذا كانت u8 أو u16 أو i32 أو غير ذلك).let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep()
let miliseconds = 1000i32;
unwinder::call_function!(sleep, false, miliseconds);
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let open_process: isize = dinvoke_rs::dinvoke::get_function_address(k32, "Openprocess");
let desired_access: u32 = 0x1000;
let inherit = 0i32;
let pid = 20628i32;
let handle = unwinder::call_function!(open_process, false, desired_access, inherit, pid); // returns *mut c_void
let handle: HANDLE = std::mem::transmute(handle);
println!("Handle id: {:x}", handle.0);
لاحظ أن الماكرو يُرجع قيمة من نوع *mut c_void يمكن تحويلها مباشرة إلى HANDLE نظرًا لأن كلا النوعين لهما نفس الحجم. وهذا يتيح الوصول إلى القيمة التي يُرجعها OpenProcess، وهي المقبض الجديد للعملية الهدف.
let large = 0x8000000000000000 as u64; // Sleep indefinitely
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); // returns *mut c_void
println!("ntstatus: {:x}", ntstatus as i32);
لاحظ أن الماكرو يُرجع قيمة من نوع *mut c_void يمكن استخدامها لاسترجاع NTSTATUS التي يُرجعها NtDelayExecution.
يمكن تسلسل عملية التزييف أي عدد من المرات دون زيادة غير طبيعية في حجم مكدس الاستدعاءات. كما سيتم الحفاظ على تدفق التنفيذ. الكود التالي مثال على ذلك:
fn main()
{
function_a();
}
fn function_a()
{
unsafe
{
let func_b = function_b as usize;
call_function!(func_b, false);
println!("function_a done.");
}
}
fn function_b()
{
unsafe
{
let func_c = function_c as usize;
call_function!(func_c, false);
println!("function_b done.")
}
}
fn function_c()
{
unsafe
{
let large = 0x0000000000000000 as u64; // Don't sleep so we return to function_b, allowing to check the execution flow preservation.
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large);
println!("ntstatus: {:x}", (ntstatus as usize) as i32); //NTSTATUS is a i32, although that second casting is not really required in this case.
}
}
إذا ضبطت الوسيط الثاني على true (في كلا الماكروين)، فستحاول عملية التزييف الاحتفاظ بإطار عنوان بداية الخيط في مكدس الاستدعاءات لزيادة الشرعية.

أحيانًا، لا تنفذ دالة بداية الخيط استدعاء call لدالة لاحقة (على سبيل المثال، يتم تنفيذ تعليمة jmp بدلاً من ذلك)، مما يعني عدم وجود عنوان عودة مدفوع على المكدس. في هذا السيناريو (وأيضًا إذا ضبطت الوسيط الثاني على false)، سيبدأ مكدس الاستدعاءات المُزيَّف من إطار BaseThreadInitThunk.
