Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
Unwinder — Call stack spoofing for Rust | Kitploit
أدوات/GitHubGitHub/kudaes/unwinder
IDS/IPS EvasionPost-ExploitationRed TeamingPayload DevelopmentAdversarial Attack
GitHubkudaes/unwinder

Unwinder

Call stack spoofing for Rust

عرض المستودع
38436منذ سنة واحدةتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

المحتوى

  • SilentMoonWalk
    • الوصف
    • الفضل
    • الاستخدام
      • ماكرو call_function!()
      • ماكرو indirect_syscall!()
      • تمرير الوسائط
    • أمثلة
      • استدعاء kernel32.dll!Sleep()
      • استدعاء kernel32.dll!OpenProcess()
      • استدعاء NtDelayExecution كاستدعاء نظام غير مباشر
      • تسلسل استدعاءات الماكرو
    • اعتبارات
      • الإطار الأولي
      • إثبات المفهوم
  • استبدال المكدس
    • وصف التقنية
    • كيفية الاستخدام
    • مثال عملي
    • ملاحظات

SilentMoonWalk

الوصف

توفر Unwinder تسليحًا كاملًا لتقنية SilentMoonWalk، مما يسمح بالحصول على تزييف كامل ومستقر لمكدس الاستدعاءات في Rust.

تتميز هذه التقنية بالخصائص التالية:

  • دعم تشغيل أي دالة تعسفية بحد أقصى 11 وسيطًا.
  • دعم تنفيذ استدعاءات النظام غير المباشرة (دون تخصيصات إضافية في الكومة) بحد أقصى 11 وسيطًا.
  • تسمح هذه الحزمة (crate) باسترجاع القيمة التي تُرجعها الدوال المستدعاة من خلالها.
  • يمكن تسلسل عملية التزييف أي عدد من المرات دون زيادة حجم مكدس الاستدعاءات.
  • يُستخدم TLS لزيادة الكفاءة أثناء عملية التزييف.
  • يُستخدم dinvoke_rs لإجراء أي استدعاء لواجهة برمجة تطبيقات Windows تتطلبه الحزمة.

الفضل

كل التقدير لمبتكري تقنية SilentMoonWalk:

  • KlezVirus
  • Waldo-IRC
  • Trickster0

وبالطبع تحية كبيرة إلى namazso بفضل السلسلة على تويتر التي ألهمت هذا المشروع بأكمله.

الاستخدام

استورد هذه الحزمة إلى مشروعك بإضافة السطر التالي إلى ملف cargo.toml وقم بالترجمة في وضع release:

root@kitploit:~
[dependencies]
unwinder = "=0.1.4"

تم تغليف الوظيفة الرئيسية لهذه الحزمة في ماكرون:

  • يتيح ماكرو call_function!() تشغيل أي دالة تعسفية بمكدس استدعاءات نظيف.
  • ينفذ ماكرو indirect_syscall!() استدعاء النظام (غير المباشر) المحدد بمكدس استدعاءات نظيف.

لاستخدام أي من هذين الماكروين، يلزم استيراد نوع البيانات std::ffi::c_void.

يعيد كلا الماكروين قيمة من نوع *mut c_void يمكن استخدامها لاسترجاع القيمة التي تُرجعها الدالة المنفذة. معلومات أكثر تفصيلًا في قسم الأمثلة.

ماكرو call_function

يُستخدم هذا الماكرو لاستدعاء أي دالة مرغوبة بمكدس استدعاءات نظيف. يتوقع الماكرو الوسائط التالية:

  • الوسيط الأول هو عنوان الذاكرة الذي سيتم الاستدعاء إليه بعد تزييف مكدس الاستدعاءات. يجب تمرير هذا الوسيط كنوع usize أو isize أو مؤشر.
  • الوسيط الثاني هو قيمة منطقية (bool) تشير إلى ما إذا كان سيتم الاحتفاظ بإطار دالة البداية أم لا. إذا لم تكن متأكدًا من ذلك، فاضبطه على false فهو يضمن دائمًا مكدس استدعاءات سليمًا.
  • الوسائط التالية هي تلك الوسائط التي سيتم إرسالها إلى الدالة بعد تزييف مكدس الاستدعاءات.

ماكرو indirect_syscall

يُستخدم هذا الماكرو لتنفيذ أي استدعاء نظام غير مباشر مرغوب بمكدس استدعاءات نظيف. يتوقع الماكرو الوسائط التالية:

  • الوسيط الأول هو سلسلة نصية تحتوي على اسم دالة NT التي تريد تنفيذ استدعاء النظام الخاص بها.
  • الوسيط الثاني هو قيمة منطقية (bool) تشير إلى ما إذا كان سيتم الاحتفاظ بإطار دالة البداية أم لا. إذا لم تكن متأكدًا من ذلك، فاضبطه على false فهو يضمن دائمًا مكدس استدعاءات سليمًا.
  • الوسائط التالية هي تلك الوسائط التي سيتم إرسالها إلى دالة NT.

تمرير الوسائط

من أجل تمرير وسائط من أنواع مختلفة إلى هذين الماكروين، يجب مراعاة الاعتبارات التالية:

  • يمكن تمرير أي نوع بيانات أساسي يمكن تحويله إلى usize (u8-u64، i8-i64، bool، إلخ) مباشرة إلى الماكروين.
  • يتم تمرير البنى (structs) والاتحادات (unions) ذات الحجم 8 أو 16 أو 32 أو 64 بت كما لو كانت أعدادًا صحيحة بنفس الحجم.
  • يجب تمرير البنى والاتحادات التي يزيد حجمها عن 64 بت كمؤشر.
  • يجب تمرير السلاسل النصية (&str و String) كمؤشر.
  • يتم تمرير المؤشرات الفارغة (ptr::null()، ptr::null_mut()، إلخ) كقيمة 0 (بغض النظر عما إذا كانت u8 أو u16 أو i32 أو غير ذلك).
  • الوسائط ذات الفاصلة العائمة والدقة المزدوجة غير مدعومة حاليًا.
  • يجب تمرير أي نوع بيانات آخر كمؤشر.

أمثلة

استدعاء Sleep

root@kitploit:~
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);

استدعاء OpenProcess

root@kitploit:~
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، وهي المقبض الجديد للعملية الهدف.

استدعاء NtDelayExecution كاستدعاء نظام غير مباشر

root@kitploit:~
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.

تسلسل استدعاءات الماكرو

يمكن تسلسل عملية التزييف أي عدد من المرات دون زيادة غير طبيعية في حجم مكدس الاستدعاءات. كما سيتم الحفاظ على تدفق التنفيذ. الكود التالي مثال على ذلك:

root@kitploit:~
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.

مكدس استدعاءات مُزيَّف دون الوحدة الرئيسية.

إثبات المفهوم

من أجل اختبار تنفيذ التقنية، تم استخدام PE-sieve مع العلم /threads. تُظهر نتائج الاختبار أن فحص مكدس الاستدعاءات لا يكشف عن وجود الحمولة عند استخدام وظائف هذه الحزمة. وكما يظهر في الصورة الثانية، يتم اكتشاف الحمولة عند عدم استخدام unwinder.

نتائج PE-sieve عند استخدام unwinder. نتائج PE-sieve عند عدم استخدام unwinder.

استبدال المكدس

وصف التقنية

هذه بديل لتزييف مكدس الاستدعاءات يعتمد على تقنية SilentMoonWalk، ويتيح الحفاظ على مكدس استدعاءات نظيف أثناء تنفيذ برنامجك. الفكرة الرئيسية وراء هذه التقنية هي أن كل دالة تُستدعى داخل وحدتك (module) تعتني بعنوان العودة الذي دُفع سابقًا، فتبحث في وقت التشغيل عن دالة شرعية بنفس حجم الإطار الذي يجب تزييف عنوان العودة فيه. وبمجرد العثور على دالة شرعية بنفس حجم الإطار، يُحسب إزاحة (offset) بداخلها ويُستخدم العنوان النهائي لاستبدال عنوان العودة الأخير، مما يخفي أي إدخال شاذ في مكدس الاستدعاءات ويُبقيه قابلًا للفك (unwindable). يحتفظ unwinder بعنوان العودة الأصلي ويُعيده إلى الموضع الصحيح في المكدس قبل تنفيذ تعليمة return، مما يتيح مواصلة التدفق الطبيعي للبرنامج.

استبدال المكدس

هذه ميزة تجريبية، ورغم أنها تعمل بشكل كامل إلا أنها ما تزال قيد التطوير والبحث، لذا تأكد من اختبار كودك إذا قررت دمج هذه التقنية فيه.

كيفية الاستخدام

لاستخدام وظيفة استبدال المكدس، يجب إضافة السطر التالي إلى ملف cargo.toml والترجمة في وضع release:

root@kitploit:~
[dependencies]
unwinder = {version = "0.1.4", features = ["Experimental"]}

تم تغليف الوظيفة الرئيسية لهذه الميزة في الماكروهات التالية:

  • زوج الماكروين start_stack_replacement!()/end_replacement!() يوجّه unwinder لبدء/إنهاء عملية استبدال المكدس. يجب استدعاء هذين الماكروين في نقطة دخول كودك (مثل الدوال المُصدَّرة من ملف dll).
  • زوج الماكروين replace_and_continue!()/restore!() ينفذ استبدال/استعادة عنوان العودة الأخير.
  • أخيرًا، يُستخدم زوج الماكروين replace_and_call!()/replace_and_syscall!() لتنفيذ استبدال المكدس عندما نريد استدعاء دوال خارج الوحدة الحالية (مثل استخدام Windows API أو استدعاء كود من أي ملف dll آخر). يُرجع كلا الماكروين قيمة من نوع *mut c_void تحتوي على القيمة التي تُرجعها الدالة المستدعاة بهذه الطريقة (أي أنهما يعملان بنفس الطريقة الموصوفة للماكروين call_function و indirect_syscall المستخدمين لتنفيذ SilentMoonWalk).

لاستخدام هذه الماكروهات، يلزم استيراد نوع البيانات std::ffi::c_void. يجب أن تُوسم جميع الدوال التي تستخدم أيًا من هذه الماكروهات بالسمات #[no_mangle] أو #[inline(never)] لمنع مترجم Rust من تضمينها أثناء عملية التحسين.

قبل الخوض في مثال عملي يوضح كيفية استخدام كل هذا، إليك نظرة سريعة على زوج الماكروين replace_and_call/replace_and_syscall وكيفية تمرير الوسائط المتوقعة إليهما.

replace_and_call

يُستخدم هذا الماكرو لاستدعاء أي دالة مرغوبة خارج الوحدة الحالية بمكدس استدعاءات نظيف مع استخدام استبدال المكدس. يتوقع الماكرو الوسائط التالية:

  • الوسيط الأول هو عنوان الذاكرة للدالة المراد استدعاؤها. يجب تمرير هذا الوسيط كنوع usize أو isize أو مؤشر.
  • الوسائط التالية هي تلك الوسائط التي سيتم إرسالها إلى الدالة المحددة. وهي تتبع نفس القواعد الموضحة في قسم تمرير الوسائط.

replace_and_syscall

يُستخدم هذا الماكرو لتنفيذ أي استدعاء نظام غير مباشر مرغوب بمكدس استدعاءات نظيف مع استخدام استبدال المكدس. يتوقع الماكرو الوسائط التالية:

  • الوسيط الأول هو سلسلة نصية تحتوي على اسم دالة NT التي تريد تنفيذ استدعاء النظام الخاص بها.
  • الوسائط التالية هي تلك الوسائط التي سيتم إرسالها إلى دالة NT. وهي تتبع نفس القواعد الموضحة في قسم تمرير الوسائط.

مثال

أعتقد أن أفضل طريقة لإظهار كيفية استخدام هذه الماكروهات هي من خلال مثال عملي. لنفترض أننا ننشئ ملف dll سيتم حقنه انعكاسيًا في الذاكرة. سيُصدّر هذا الملف دالتين ExportA و ExportB، لذا سنعتبر هاتين الدالتين نقطتي دخول للوحدة. يجب على كلتيهما استدعاء ماكرو start_stack_replacement في البداية مباشرة، كما يجب عليهما استدعاء الماكرو المعاكس end_replacement قبل العودة. يتوقع ماكرو start_stack_replacement عنوان الأساس للوحدة كوسيط، أو يمكنك تمرير 0 إذا كنت لا تعرف هذا العنوان في وقت التشغيل، فسيحاول الماكرو اكتشافه بنفسه.

root@kitploit:~
#[no_mangle]
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    ...
    unwinder::end_replacement!();

    true
}

#[no_mangle]
fn ExportedB() -> bool
{
    unwinder::start_replacement!(0);
    ...
    unwinder::end_replacement!();

    true
}

تبدأ عملية استبدال المكدس بإنشاء مكدس جديد يدويًا سيتم استخدامه حتى يتم استدعاء ماكرو end_replacement. توضح الصورة التالية ما يحدث في الخلفية:

استبدال المكدس

على الرغم من أنه من الناحية النظرية لن يكون من الضروري البدء بمكدس جديد من الصفر، فقد قررت تنفيذ العملية بهذه الطريقة لضمان الاستقرار ومنع أي شيء من الانهيار.

الآن، لنفترض أن دالتنا ExportedA تقوم بعدة استدعاءات لدالتين داخليتين أخريين. هاتان الدالتان الداخليتان مسؤولتان عن استبدال/استعادة عنوان العودة الأصلي الذي سيشير إلى مكان ما داخل ExportedA، مما يكسر مكدس الاستدعاءات ما لم نتعامل معه. تتضمن عملية الاستبدال هذه تغليف كود الدالة الداخلية بين الماكروين replace_and_continue و restore:

root@kitploit:~
#[no_mangle]
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    let ret_a = internal_a();
    let ret_b = internal_b(ret_a);
    unwinder::end_replacement!();

    ret_b
}

#[inline(never)] // This attribute is mandatory
fn internal_a() -> bool
{
    unwinder::replace_and_continue();
    ...
    unwinder::restore();
    
    some_value
} 

#[inline(never)] // This attribute is mandatory
fn internal_b(value: bool) -> bool
{
    unwinder::replace_and_continue();
    ...
    unwinder::restore();
    
    some_value
} 

أخيرًا، تستخدم كلتا الدالتين internal_a و internal_b بعض وظائف Windows API. للحفاظ على مكدس استدعاءات قابل للفك، يجب تنفيذ هذه الاستدعاءات عبر الماكروين replace_and_call (استدعاء عادي) أو replace_and_syscall (استدعاء نظام غير مباشر).

root@kitploit:~
#[no_mangle] // This attribute is mandatory
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    let ret_a = internal_a();
    let ret_b = internal_b(ret_a);
    unwinder::end_replacement!();

    ret_b
}

#[inline(never)] // This attribute is mandatory
fn internal_a() -> bool
{
    unwinder::replace_and_continue();
    ...
    let module_name = "advapi32.dll";
    let module_name = CString::new(module_name.to_string()).expect("");
    let module_name_ptr: *mut u8 = std::mem::transmute(module_name.as_ptr());
    let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
    let load_library = dinvoke_rs::dinvoke::get_function_address(k32, "LoadLibraryA");
    let ret = unwinder::replace_and_call!(load_library, module_name_ptr); // Load a dll with an unwindable call stack
    println!("advapi.dll base address: 0x{:x}", ret as usize);
    ...
    unwinder::restore();
    
    some_value
} 

#[inline(never)] // This attribute is mandatory
fn internal_b(value: bool) -> bool
{
    unwinder::replace_and_continue();
    ...
    let large = 0xFFFFFFFFFF676980 as u64; // Sleep one second
    let large: *mut i64 = std::mem::transmute(&large);
    let alertable = false;
    let ntstatus = unwinder::replace_and_syscall!("NtDelayExecution", alertable, large);
    println!("ntstatus: {:x}", ntstatus as usize);
    ...
    unwinder::restore();
    
    some_value
} 

ملاحظات

نظرًا لأن هذه ميزة قيد التطوير، يجب مراعاة بعض الأمور:

  • إذا كنت تزيل ترويسات ملف PE الخاص بك أثناء عملية التحميل، يجب تمرير عنوان الأساس للوحدة إلى ماكرو start_stack_replace. حاليًا، لن يتمكن من العثور عليه بنفسه (سيتم حل ذلك في التحديث القادم).
  • إذا كنت تتساءل، فإن استبدال المكدس يستخدم نفس مجموعة jmp rbx + إطار الإخفاء (concealment frame) المستخدمة في تقنية SilentMoonWalk. يحدث هذا فقط عند استخدام الماكروين replace_and_call و replace_and_syscall ومن المخطط تغييره في التحديث القادم.
  • يُرجع كل من الماكروين replace_and_call و replace_and_syscall قيمة من نوع *mut c_void يمكن استخدامها لاسترجاع القيمة التي تُرجعها الدالة المنفذة من خلالهما. هذا هو نفس السلوك الموصوف للماكروين call_function و indirect_syscall.
  • يسمح الماكروان replace_and_call و replace_and_syscall بحد أقصى 11 وسيطًا.

يُرجى إبلاغي بأي خطأ قد يظهر عند استخدام هذه الميزة.

تنزيل الأداة