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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
Veridiff — مكتشف تباعد الفروع الديناميكي للكود الأصلي -- يتتبع تنفيذي Frida ويجد التعليمة الدقيقة التي يتباعدان عندها. | Kitploit
أدوات/GitHubGitHub/veridiff/veridiff
التحليل الديناميكي (عزل)تحليل الكودتحليل الشفرة الديناميكي (DAST)الهندسة العكسيةمصممي الأخطاءالاختبار العشوائيتحليل البرمجيات الخبيثةCTFتحليل الملفات الثنائيةاستغلال الملفات الثنائية
GitHubveridiff/veridiff

Veridiff

16منذ يوم واحدلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

مكتشف تباعد الفروع الديناميكي للكود الأصلي -- يتتبع تنفيذي Frida ويجد التعليمة الدقيقة التي يتباعدان عندها.

عرض المستودع
مشاركة

════════════════════════════════════════════════════════════════ VERIDIFF التعليمة الدقيقة حيث ينفصل تشغيلان لنفس الشيفرة ════════════════════════════════════════════════════════════════

يدخل تتبعان. يخرج عنوان واحد: التعليمة cmp / jcc (أو tst/b.cond، أو أيًّا كان ما تسميه معماريتك) التي قررت أن تشغيليك لن ينتهيا بالطريقة نفسها.

هذا هو المنتج بأكمله. ليس إطار عمل. ليس منصة. محرّك.

الإصدار الحالي: v0.2.4. ARM64 موثّق بالتحقق الحيّ من البداية إلى النهاية، على عتاد حقيقي، في كلا المحرّكين -- انظر إثبات، لا وعود أدناه. الإصدار v0.2.4 نفسه متابعة صغيرة: مراجعة آلية أُطلقت في v0.2.3 دون انتظار وجدت لا تماثلًا حقيقيًا (كان Python يفتقد فحص طول كان Rust قد اكتسبه للتو) إضافة إلى متغيّر خطأ مضلّل، وقد أُصلح كلاهما. CHANGELOG.md يحمل التاريخ الكامل وصولًا إلى 0.1.0.


ما هذا في الواقع

غالبًا ما تعود الهندسة العكسية لفحص ترخيص، أو استدلالية مكافحة الغش، أو موزّع مُسطَّح بـ OLLVM إلى الحلقة اليدوية نفسها: شغّله مرة بمدخل صالح، وشغّله مرة أخرى بمدخل غير صالح، وحدّق في قائمتي تفكيك محاولًا اكتشاف أين توقفا عن التوافق مع بعضهما. هذه الحلقة لا تتوسّع بعد بضع مئات من الكتل الأساسية، ولا تصمد أمام تسطيح تدفق التحكم على الإطلاق -- فكل مسار يمر بالفعل عبر كتلة الموزّع نفسها، لذا لا يخبرك التحديق في العناوين بشيء.

يقوم Veridiff بأتمتة الفرق، لا الهندسة العكسية. أرفق Frida's Stalker بخيط، وشغّله مرتين، واحصل على الشيء الوحيد الذي يهم فعلًا: آخر كتلة اتفق عليها التشغيلان، وأول كتلة لم يتفقا عليها، وتفكيك التعليمة التي فصلت بينهما. ملايين الكتل الأساسية تدخل، عنوان واحد يخرج.

يُشحن كشيئين، عن قصد:

  • python/main.py — ملف واحد، pip install frida، انتهى.
  • rust/ — crate مكتبة حقيقي (src/lib.rs) بالإضافة إلى ثنائي تجريبي رفيع (src/main.rs)، للحالات التي تكون فيها كلفة استدعاء Python هي عنق الزجاجة بدلًا من كلفة الاستقصاء الخاصة بـ Frida.

كلاهما يوفّر الاستدعاءات الأربعة نفسها (trace_call، disassemble_block، find_first_divergence، find_divergence_regions) وينتجان نتائج متطابقة بايتًا ببايت مقابل الهدف نفسه. لا يحتوي أيٌّ منهما على سطر واحد من منطق CLI أو GUI أو العرض. هذا ليس سهوًا -- انظر القانون أدناه.


إثبات، لا وعود

لا مقارنات مع منتجات لم نلمسها قط، ولا ادعاءات خيالية بـ "مُختبر في المعارك ضد Denuvo". إليك دالة C صغيرة، عادية عن قصد -- examples/licensecheck.c -- مُصرّفة دون أي شيء يسهّل مهمة Frida، ومتتبَّعة مرتين بمفتاح صالح وآخر غير صالح، على هذا الجهاز، لحظات قبل كتابة هذه الفقرة:``` $ gcc -O0 -no-pie -fno-pie -fno-inline -o licensecheck examples/licensecheck.c $ nm licensecheck | grep check_license 0000000000401146 T check_license

$ python3 python/main.py ./licensecheck 0x401146 VALID-KEY-123 WRONG-KEY trace A ('VALID-KEY-123'): 9 blocks, returned 1 trace B ('WRONG-KEY'): 6 blocks, returned 0

first divergence at trace index 1 last common block : 0x114e run A took block : 0x1164 run B took block : 0x116a

disassembly of the deciding block: 0x40114e mov qword ptr [rbp - 0x18], rdi 0x401152 mov dword ptr [rbp - 4], 0 0x401159 mov rax, qword ptr [rbp - 0x18] 0x40115d movzx eax, byte ptr [rax] 0x401160 cmp al, 0x56 0x401162 jne 0x40116a <-- decides here

root@kitploit:~
(المسار A يتكون من 9 كتل هنا، وليس 11 -- عرض README الخاص بـ v0.1.0 هذه نفس التشغيلة
قبل وجود وضع الإحماء. راجع **وضع الإحماء** أدناه لمعرفة ما تغيّر
ولماذا. علامة `<-- decides here` هي تصنيف الفرع الشرطي في v0.2.0،
المغطى في **تصنيف الفروع عبر المعماريات**.)

`0x56` هو `'V'`. لم يرَ المحرك المصدر قط. لم يرَ
`"VALID-KEY-123"` كنص للبحث عنه — فهو لا يعرف ما هو
مفتاح الترخيص. شغّل مسارين، ووجد أين توقفا عن التطابق،
وأعاد المقارنة الدقيقة، لأن ذلك هو أول بايت
تختلف عليه الوسيطتان فعليًا. هذه هي الحيلة بأكملها،
وهي الحيلة الوحيدة: تحويل سؤال "أين تباعد هذان" إلى مسح
خطي بدلًا من تمرين التحديق في قائمتين.

محرك Rust، نفس الملف التنفيذي، نفس المفتاحين:```
$ cd rust && cargo run --release -- ../licensecheck 0x401146 VALID-KEY-123 WRONG-KEY
trace A ("VALID-KEY-123"): 9 blocks, returned Some("1")
trace B ("WRONG-KEY"): 6 blocks, returned Some("0")

first divergence at trace index 1
  last common block : 0x114e
  run A took block  : 0x1164
  run B took block  : 0x116a

  disassembly of the deciding block:
    0x40114e  mov qword ptr [rbp - 0x18], rdi
    0x401152  mov dword ptr [rbp - 4], 0
    0x401159  mov rax, qword ptr [rbp - 0x18]
    0x40115d  movzx eax, byte ptr [rax]
    0x401160  cmp al, 0x56
    0x401162  jne 0x40116a  <-- decides here

إجابة متطابقة. تطبيقان مستقلان، بيئتا تشغيل لغتين مختلفتين، حقيقة أساسية واحدة. ابنِ licensecheck بنفسك وشغّل كليهما — بيت القصيد من وضع المصدر في هذا المستودع بدلاً من المخرجات فقط هو أنك لست مضطراً لأخذ كلامنا على محمل الجزم في أي من هذا.

نفس الملف المصدري، مُترجَم تصالبياً لنظام Android بدلاً من المضيف، مُشغَّل ومُتتبَّع على هاتف حقيقي مُروَّت (POCO F7 Ultra، Android 16، arm64-v8a) عبر adb، بنفس المفتاحين:``` $ $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang
-O0 -fno-inline -o licensecheck-arm64 examples/licensecheck.c $ adb push licensecheck-arm64 /data/local/tmp/licensecheck-arm64 && adb shell chmod 755 /data/local/tmp/licensecheck-arm64 $ $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm licensecheck-arm64 | grep check_license 0000000000000798 T check_license

trace A ('VALID-KEY-123'): 12 blocks, returned 1 trace B ('WRONG-KEY'): 9 blocks, returned 0

first divergence at trace index 1 last common block : 0x7a8 run A took block : 0x7bc run B took block : 0x7d0

disassembly of the deciding block: 0x5b247bc7a8 str wzr, [sp, #0xc] 0x5b247bc7ac ldr x8, [sp, #0x10] 0x5b247bc7b0 ldrb w8, [x8] 0x5b247bc7b4 subs w8, w8, #0x56 0x5b247bc7b8 b.ne #0x5b247bc7d0 <-- decides here

root@kitploit:~
`0x56` هو `'V'`، نفس ما هو عليه في x86 -- `subs`/`b.ne` بدلاً من `cmp`/`jne`، نفس
القرار، تم تحديده بشكل صحيح كتعليمة حاسمة بواسطة نفس
مصنّف `is_conditional_branch` الذي يختبره إثبات x86، على
مجموعة تعليمات مختلفة تماماً. كلا المحركين ينتجان هذه النتيجة نفسها
(أعداد الكتل، نقطة التباعد، التفكيك) مقابل نفس
الملف التنفيذي -- تختلف العناوين فقط، لأن ASLR يمنح كل تشغيل
لملف تنفيذي PIE عنوان تحميل مختلفاً، وهذا بالضبط سبب
حلّ قاعدة الوحدة وقت التتبع بدلاً من افتراضها. لا تغيير في المصدر
بين هذا وبناء x86 أعلاه -- `examples/licensecheck.c` لم يُمَس؛
فقط هدف المترجم تغيّر. الشرح الكامل، بما في ذلك
لماذا يحتاج هذا إلى `spawn()` (وليس `attach()` إلى خطاف مكتبة نظام)
وما لم ينجح أولاً، موجود في **ملاحظات ميدانية** أدناه.

---

## كيف يعمل```
target process                          host (Python / Rust)
───────────────                         ─────────────────────
NativeFunction call, run A   ──►   Interceptor.attach bridges into
  basic blocks execute       ──►   Stalker.follow, which streams the
                                    RAW GumEvent buffer via send() --
                                    no Stalker.parse(), no per-event
                                    JS object, no JSON round-trip
NativeFunction call, run B   ──►   same path, second trace
                                              │
                                              ▼
                              host decodes the fixed-stride GumEvent
                              records directly (struct/bytes, O(N))
                                              │
                                              ▼
                              find_first_divergence(trace_a, trace_b)
                              -- longest-common-prefix scan, O(min(n,m))
                                              │
                                              ▼
                              disassemble just the 1-2 blocks that
                              actually matter, via the agent's own
                              Instruction.parse() (no host-side
                              disassembler dependency, ever)

قراران تصميميان يحملان الأمر بأكمله، وكلاهما خروج متعمد عن النهج البديهي:

الفرق هو مسح بادئة، وليس فرق Myers/LCS. خوارزميات فرق النصوص تحل سؤال "ما هو أصغر نص تحرير بين هذين التسلسلين" — وهو السؤال الخاطئ هنا. بعد أن يتفرع مساران حقيقيان، يعاودان الانضمام بشكل روتيني إلى استدعاء مكتبة مشتركة أو خاتمة مشتركة. تقرأ خوارزمية أصغر نص تحرير تلك المطابقة العرضية للعنوان على أنها "غير متغيرة" وتمنحك محاذاة مجزأة ومضللة بدلاً من الشيء الوحيد الذي تريده فعلاً: أول نقطة يتفرع عندها المساران. مسح أطول بادئة مشتركة يجيب على هذا السؤال بالضبط، بتعقيد O(min(n,m)) دون تجزئة، بدلاً من O(N·D) لـ Myers أو O(N log N) لأفضل المتغيرات المدعومة بالتجزئة. يضيف find_divergence_regions إعادة مزامنة محدودة بالنظر الأمامي فوق ذلك للحالة التي يشغّل فيها فحص عدة شروط مستقلة بالتتابع — لا يزال ليس فرقاً عاماً، لا يزال O(N + regions × window)، لا يزال يجيب على "أين تفرقا" بدلاً من "كيف تتحاذى هذه".

التفكيك يحدث داخل الوكيل، وليس المضيف. Frida-gum تحزم Capstone بالفعل وتكشفه كـ Instruction.parse(). يطلب كلا المضيفين من الوكيل تفكيك الكتل القليلة المهمة بعد حساب الفرق، ويستعيدان سجلات منظمة {address, mnemonic, opStr} — لذا لا يحمل main.py ولا lib.rs تبعية مفكك، ويُضمن دائماً أن يتطابق فك الترميز مع البنية والوضع الفعليين للهدف الحي، لأنه يعمل داخل تلك العملية بالضبط.

ملاحظة ثالثة أصغر على محرك Rust تحديداً: كان find_first_divergence وفك ترميز GumEvent الخام خاليين من التخصيص بالفعل (مسح شرائح عادي، لا حركة على الكومة) قبل v0.2.0 — لم يكن هناك شيء لإصلاحه، فقط لذكره بوضوح بدلاً من الإيحاء بغير ذلك. ما غيّرته v0.2.0 فعلاً حقيقي لكنه أضيق: حجز مخازن تجميع التتبع عبر reserve() بعدد الأحداث الحد الأعلى لكل قطعة بدلاً من النمو التدريجي، وإعادة استخدام خريطة تجزئة مسودة واحدة عبر كل منطقة يجدها find_divergence_regions بدلاً من تخصيص واحدة لكل منطقة. تم النظر في تحويل غير آمن لمخزن الأحداث الخام إلى شريحة &[GumEvent] ورُفض: Vec<u8> القادم عبر IPC ليس لديه ضمان محاذاة، وإعادة تفسير بايتات غير محاذاة كبنية #[repr(C)] بحقول 8 بايت هو سلوك غير معرّف بغض النظر عما إذا كانت أي وحدة معالجة معينة تتحمله عملياً. from_le_bytes على شريحة بايتات هو الاصطلاح الصحيح ويُترجم بالفعل إلى نفس عمليات التحميل.


وضع الإحماء

مشوّش حقيقي قابل لإعادة الإنتاج، وُجد عبر تتبع عملية فعلياً بدلاً من الوثوق ببيانات اصطناعية: أول استدعاء على الإطلاق لهدف ما لدالة مرتبطة خارجياً (strcmp، أي شيء آخر يمر عبر PLT/GOT) يسلك مسار محلل الربط الكسول للمرابط الديناميكي. كل استدعاء لاحق لتلك الدالة نفسها يقفز مباشرة إلى مدخل GOT المحلول الآن. لذا يمكن لاستدعاءين بـوسائط متطابقة أن يبلّغا مع ذلك عن تفرّع -- ليس لأن منطقك يختلف، بل لأن أحدهما دفع ضريبة رابط لمرة واحدة لم يدفعها الآخر.

trace_call(..., warm_up=True) (Python) / TraceCallOptions { warm_up: true } (Rust) يجعل الوكيل يستدعي الهدف مرة واحدة، دون تتبع، بنفس الوسائط، مباشرة قبل الاستدعاء الذي يُقاس فعلاً -- لذا فإن أي مدخلات GOT يلمسها مسار الكود ذلك تكون محلولة بالفعل بحلول وقت بدء التتبع. الإثبات، على نفس الملف التنفيذي أعلاه، استدعاءان متطابقان لـ "VALID-KEY-123":``` warm_up=False: divergence = DivergencePoint(index=4, last_common_block=4160, block_a=4166, block_b=4479) warm_up=True: divergence = None

root@kitploit:~
هذا هو `repr()` الخام الفعلي لقيمة الإرجاع، بما في ذلك الحقول العشرية وكل شيء
-- `last_common_block=4160` هو `0x1040`، `strcmp@plt`. بدون التسخين، استدعاءان
*متطابقان* يُبلغان عن تباعد هناك. مع التسخين، تُرجع `find_first_divergence`
بشكل صحيح لا شيء للإبلاغ عنه. معطّل افتراضيًا -- فهو يعني أن الهدف يُنفَّذ مرة
إضافية قبل تتبعه، وهو أمر غير آمن لهدف ذي آثار جانبية أو حالة غير عكوسة أخرى --
لذا فالقرار قرارك بالتفعيل.

---

## ضبط إعادة المزامنة (استدلالات OLLVM)

كانت القاعدة القديمة لـ `find_divergence_regions` هي "أول عنوان يتشاركه
كلا التتبعين، ضمن نافذة الاستباق، هو حيث أعادا المزامنة." هذا خطأ
بالنسبة للكود الذي وُجدت هذه الأداة لتحليله تحديدًا: مسار التحكم المُسطَّح
بواسطة OLLVM يوجّه العديد من المسارات المختلفة منطقيًا عبر *نفس* كتلة
الموزّع، لذا يظهر ذلك العنوان في كلا النافذتين فورًا تقريبًا بعد أي تباعد
تقريبًا -- دون أن تكون المساران قد اندمجا فعليًا عائدين إلى نفس مسار التحكم.
كانت القاعدة القديمة تسمّي ذلك إعادة مزامنة. إنها ليست كذلك؛ إنهما مساران
مختلفان يمرّان كلاهما عبر آليات مشتركة في طريقهما إلى مكان آخر.

تتطلب v0.2.0 أن *يصمد* المرشّح: يجب أن تستمر ثلاث كتل متتالية على الأقل في
التوافق مباشرة بعد عنوان المرشّح قبل قبوله كدمج حقيقي. المرور العابر عبر
الموزّع يتبعه كتل تعتمد على الحالة تختلف بين التتبعين في كل مرة تقريبًا؛
أما الدمج الحقيقي فيستمر في التوافق. إثبات اصطناعي (لم يكن هناك ملف ثنائي
مُشوَّه بـ OLLVM حي متاح للاختبار عليه، لذا فهذا بالضبط ما يبدو عليه -- اختبار
وحدة موجّه، وليس التقاطًا حيًا؛ انظر `dispatcher_style_coincidental_match_is_rejected`
واختبار القبول المقترن به في كلتا مجموعتي الاختبارات):```
# same shared block (0xD0) in both cases -- only the outcome differs
a = [0x10, 0x20, 0x30, 0xD0, 0xAA, 0xAB, 0xAC]
b = [0x10, 0x20, 0x31, 0xD0, 0xBB, 0xBC, 0xBD]   # diverges again right after 0xD0
  -> resync_block: None                            # correctly rejected

a = [0x10, 0x20, 0x30, 0xD0, 0xAA, 0xAB, 0xAC]
b = [0x10, 0x20, 0x31, 0xD0, 0xAA, 0xAB, 0xAC]   # genuinely continues identically
  -> resync_block: Some(0xD0)                       # correctly accepted

تصنيف الفروع عبر المعماريات

Instruction.parse (Capstone، عبر frida-gum) يفكّ ترميز كل معمارية يتصل بها Frida، بما في ذلك ARM64 -- لم يكن هناك أبدًا أي كود خاص بـ x86 في الوكيل "لتوسيعه" من أجل CBZ/CBNZ/B.cond/TBZ/TBNZ. ما يضيفه كلا المحرّكين الآن هو is_conditional_branch على كل تعليمة مفكوكة الترميز، بحيث يُعلَّم الفرع الحاسم في الكتلة بدلًا من تركه لتكتشفه بالعين (وهذا هو مؤشر <-- decides here في مخرجات الإثبات أعلاه).

يجب أن يكون هذا قائمًا على الـ mnemonic، وليس على Instruction.groups، وهذا اكتشاف تجريبي وليس تخمينًا: عند الالتقاط الحي مقابل jne الحقيقي في مخرجات الإثبات أعلاه، يُبلّغ Capstone عن groups: ["branch_relative", "jump"] -- وjmp غير الشرطي بعد تعليمتين في نفس الكتلة يُبلّغ عن نفس المجموعات بالضبط. لا شيء في .groups يميّز بينهما. نص الـ mnemonic هو الإشارة الوحيدة التي تميّز بينهما، على أي معمارية، وهو ما يتحقق منه is_conditional_branch فعليًا.

كلا النصفين الآن مُتحقَّق منهما حيًّا. كان نصف x86 كذلك منذ البداية (انظر أعلاه)؛ أما نصف ARM64 (cbz/cbnz/tbz/tbnz/b.eq/b.ne/...) فقد كان مُختبَرًا بمحاكاة فقط حتى v0.2.2، وأُغلق في v0.2.3 بواسطة تتبّع ARM64 الحقيقي في إثبات، لا وعود أعلاه -- حيث تم تعليم b.ne بشكل صحيح كالتعليمة الحاسمة مقابل كود Bionic حقيقي على جهاز حقيقي، وليس حمولة اصطناعية. استغرق الوصول إلى ذلك محاولتين وكشف عن ألغام حقيقية في Frida/Android على طول الطريق؛ انظر ملاحظات ميدانية للحساب الكامل، لأن أداة مبنية للمهندسين العكسيين يجب أن تُظهر عملها الخاص. آلية فك الترميز الأساسية لا تزال تكامل Capstone متعدد المعماريات الراسخ الخاص بـ Frida، دون تعديل من هذا التغيير -- ما تغيّر هو الدليل، وليس الآلية.


البدء السريع

Python — ملف واحد، تبعية واحدة:```bash pip install frida python3 python/main.py

root@kitploit:~
**Rust** — مكتبة حقيقية، قابلة للاستيراد عبر المسار/git من أي crate آخر:```toml
# your Cargo.toml
[dependencies]
veridiff = { path = "../Veridiff/rust" }

ما الجديد في الإصدار 2.0.0

  • إعادة كتابة كاملة بلغة Go — تمت إعادة كتابة الأداة بالكامل بلغة Go، مما أدى إلى تحسين الأداء بشكل كبير وتقليل الاعتماديات.
  • دعم بروتوكول SOCKS5 — تمت إضافة دعم كامل لبروكسي SOCKS5، مما يتيح التوجيه عبر Tor أو أي بروكسي SOCKS5 آخر.
  • دعم بروتوكول HTTP/2 — تمت إضافة دعم HTTP/2 لتحسين الأداء والتوافق.
  • تحسين اكتشاف WAF — تم تحسين منطق اكتشاف WAF ليكون أكثر دقة وتقليل الإيجابيات الكاذبة.
  • إخراج JSON — تمت إضافة خيار -json لإخراج النتائج بصيغة JSON لسهولة التكامل مع الأدوات الأخرى.
  • تحسين الأداء — تم تحسين سرعة الفحص بشكل كبير بفضل التوازي المحسّن وإعادة الكتابة بلغة Go.
  • تقليل الاعتماديات — لم تعد الأداة تتطلب Python أو أي اعتماديات خارجية أخرى.

التثبيت

المتطلبات المسبقة

  • Go 1.21 أو أحدث (للبناء من المصدر)

البناء من المصدر

root@kitploit:~
git clone https://github.com/example/waf-detector.git
cd waf-detector
go build -o waf-detector .

التثبيت عبر Go

root@kitploit:~
go install github.com/example/waf-detector@latest

التثبيت عبر Docker

root@kitploit:~
docker pull example/waf-detector:latest
docker run --rm example/waf-detector -u https://example.com

الاستخدام

الاستخدام الأساسي

root@kitploit:~
./waf-detector -u https://example.com

الخيارات

root@kitploit:~
-u, --url string       عنوان URL الهدف للفحص
-f, --file string      ملف يحتوي على قائمة عناوين URL للفحص
-t, --threads int      عدد الخيوط المتزامنة (الافتراضي: 10)
-timeout int           مهلة الطلب بالثواني (الافتراضي: 10)
-proxy string          بروكسي SOCKS5 (مثال: socks5://127.0.0.1:9050)
-json                  إخراج النتائج بصيغة JSON
-v, --verbose          وضع الإخراج المفصل
-h, --help             عرض المساعدة

أمثلة

فحص عنوان URL واحد:

root@kitploit:~
./waf-detector -u https://example.com

فحص قائمة عناوين URL من ملف:

root@kitploit:~
./waf-detector -f urls.txt

استخدام بروكسي SOCKS5 (مثل Tor):

root@kitploit:~
./waf-detector -u https://example.com -proxy socks5://127.0.0.1:9050

إخراج النتائج بصيغة JSON:

root@kitploit:~
./waf-detector -u https://example.com -json

آلية العمل

تعمل الأداة عن طريق إرسال طلبات HTTP مصممة خصيصًا إلى الهدف وتحليل الاستجابات لتحديد وجود WAF. تتضمن التقنيات المستخدمة:

  1. تحليل الترويسات (Headers) — فحص ترويسات الاستجابة بحثًا عن بصمات WAF المعروفة.
  2. تحليل محتوى الاستجابة — البحث عن أنماط معروفة في نص الاستجابة تشير إلى وجود WAF.
  3. تحليل أكواد الحالة — فحص أكواد حالة HTTP غير النمطية التي قد تشير إلى وجود WAF.
  4. طلبات خبيثة — إرسال طلبات تحتوي على حمولات هجومية معروفة (مثل XSS وSQL Injection) ومراقبة الاستجابات.
  5. تحليل DNS — فحص سجلات DNS للهدف بحثًا عن مؤشرات على استخدام WAF أو CDN.

WAFs المدعومة

تدعم الأداة اكتشاف العديد من WAFs الشائعة، بما في ذلك:

  • Cloudflare
  • AWS WAF
  • Akamai
  • Imperva (Incapsula)
  • F5 BIG-IP ASM
  • ModSecurity
  • Sucuri
  • Barracuda
  • Fortinet FortiWeb
  • Citrix NetScaler
  • Radware AppWall
  • Wallarm
  • StackPath
  • Fastly
  • Azure Application Gateway
  • Google Cloud Armor
  • DenyAll
  • Profense
  • Comodo
  • And many more...

المساهمة

نرحب بالمساهمات! يرجى اتباع الخطوات التالية:

  1. قم بعمل Fork للمستودع
  2. أنشئ فرعًا لميزتك (git checkout -b feature/amazing-feature)
  3. قم بعمل Commit لتغييراتك (git commit -m 'Add some amazing feature')
  4. قم بعمل Push للفرع (git push origin feature/amazing-feature)
  5. افتح Pull Request

الترخيص

هذا المشروع مرخص بموجب رخصة MIT — راجع ملف LICENSE للحصول على التفاصيل.

إخلاء المسؤولية

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

شكر وتقدير

  • شكر خاص لجميع المساهمين في هذا المشروع
  • مستوحى من أدوات مماثلة مثل wafw00f وWAFW00F```rust let options = TraceCallOptions { warm_up: true }; let trace = engine.trace_call(&mut script, addr, &args, "int", None, options)?; let d = VeridiffEngine::find_first_divergence(&trace_a, &trace_b);
root@kitploit:~
`src/main.rs` في هذا المستودع ليس سوى غلاف CLI رفيع فوق نفس
واجهة البرمجة تلك — اقرأه كمثال على الاستخدام، لا كالمنتج نفسه.

جديد في v0.2.0، في كلتا اللغتين: `trace_call(..., warm_up=True)` / وسيط
`TraceCallOptions { warm_up: true }` (انظر **وضع الإحماء**)، و
`instruction.is_conditional_branch` / `insn.is_conditional_branch()` على
كل `Instruction` يُرجعه `disassemble_block` (انظر **تصنيف التفريعات
عبر المعماريات**). كلاهما إضافي على جانب Python؛ أما توقيع `trace_call`
في Rust فقد اكتسب وسيطًا ختاميًا مطلوبًا، وهو تغيير كاسر للمستدعين
الحاليين على هذه الحزمة ما قبل 1.0 -- مرّر
`TraceCallOptions::default()` للسلوك القديم.

---

## ملاحظات ميدانية (الألغام التي وطأناها بالفعل)

ينبغي لأدوات الهندسة العكسية أن تخبرك بمكان دفن الجثث. هذه كلّفت وقت
تصحيح حقيقي للعثور عليها؛ وهي موثّقة
داخليًا في كلا الوكيلين، ومكرّرة هنا لأنها من النوع الذي لا تتعلمه إلا
بتتبّع عملية حقيقية فعليًا بدلًا من الوثوق ببيانات اختبار اصطناعية.

- **`Stalker.follow()` لا يفعل شيئًا بصمت إذا استدعيته من
  معالج `rpc.exports` عادي ثم استدعيت `NativeFunction`
  مباشرة.** لا خطأ، لا حدث، `blockCount` يبقى صفرًا إلى الأبد. لا
  ينشط إلا من سياق تنفيذ أصلي حقيقي — ولهذا
  يُطوّق `traceCall` الاستدعاء الفعلي داخل
  `Interceptor.attach(target, {onEnter, onLeave})` مؤقت بدلًا من استدعاء
  `Stalker.follow`/`fn()` على التوالي. إذا كنت توسّع الوكيل
  وعدد أحداثك صفر بشكل غامض، فهذا على الأرجح هو السبب.

- **حزمة `frida` المنشورة لـ Rust (`0.17.2` على crates.io) بها
  خطأ سلامة حقيقي** في `Script::handle_message`: فهي تحوّل
  `user_data` الخاص بالإشارة إلى نوع مؤشر خاطئ قبل استدعاء
  دالة من خلاله — سلوك غير معرّف لأي معالج يحمل
  حالة حقيقية، مطابقًا للمشكلة upstream
  [issue #189](https://github.com/frida/frida-rust/issues/189) (الأعراض المُبلَّغ عنها:
  انهيارات داخل عمليات ذرية، قراءات حقول مشوّهة،
  جمود على Windows). مُصلَح على `main` في الالتزام `080e8a99a5`، وليس بعد في
  إصدار crates.io — `rust/Cargo.toml` يثبّت مراجعة git مع كامل
  التبرير مضمّنًا. **تحقق من وجود إصدار `>=0.17.3` قبل أن "تعيد
  بلطف" تحويل ذلك إلى سلسلة إصدار.**

- **استدعاءان بوسائط *متطابقة* قد يُبلّغان مع ذلك عن أول تباعد
  زائف**، إذا لم يكن أول استدعاء للهدف لدالة خارجية ما (`strcmp`، أي شيء آخر يمر
  عبر PLT) قد حُلّ فيه مدخل GOT بعد. يأخذ الاستدعاء الأول مسار
  محلّل الربط الكسول؛ وكل استدعاء بعده يقفز مباشرة إلى
  العنوان المحلول. سيُبلّغ `find_first_divergence` بصدق
  عن هذا كفرق حقيقي، لأنه كذلك فعلًا — `find_divergence_regions`
  هو ما يريك أنه منطقة واحدة تعيد المزامنة فورًا، وعند
  هذه النقطة يمكنك التعرف على عنوان PLT stub والمضي قدمًا. اعتبارًا من
  v0.2.0 يمكنك أيضًا ببساطة ألا تواجه المشكلة: انظر **وضع الإحماء**.

- **استدعاءات RPC الحاجبة في `frida-rust` ليس لها مهلة أو
  كشف للجلسات الميتة.** تنفيذ `Exports::call` هو مجرد
  `rx.recv().unwrap()` على قناة داخلية — إذا خرجت العملية الهدف (بما في ذلك
  لأنك استدعيت `device.resume()` واكتمل تنفيذ `main()` الخاص بها) بينما استدعاء
  قيد التنفيذ، أو قبل أن يبدأ التالي، فإن تلك القناة لا تستقبل شيئًا أبدًا
  ويحجب الاستدعاء *إلى الأبد*، وليس بخطأ. اكتُشف مباشرةً: استدعاء
  `disassemble_block` بعد `device.resume()` علّق الثنائي التجريبي لهذا المحرك
  نفسه إلى أجل غير مسمى. نظير ذلك في `frida-python` يكشف هذا
  ويرفع `frida.InvalidOperationError: script has been destroyed`
  بنظافة بدلًا من ذلك — هذا خاص بـ Rust. الإصلاح ليس غلاف مهلة،
  بل الترتيب: كلا العرضين التوضيحيين الآن ينفذان كل استدعاء RPC (تتبّع،
  فرق، تفكيك) *قبل* استدعاء `resume()`، لأن التفكيك
  يحتاج فقط إلى بايتات الكود الثابتة والمخطّطة مسبقًا للهدف ولم
  يعتمد قط على كون العملية تعمل فعليًا. `resume()` هو
  السطر الأخير في كليهما، فقط حتى لا تُترك العملية المُنشأة
  متوقفة. إذا كنت توسّع أيًا من العرضين التوضيحيين، أبقِ الأمر على هذا النحو.

- **أخطر خطأ وُجد في هذا المشروع حتى الآن: `resync_confirmed`
  قارن نقطة إعادة مزامنة مرشحة بـ*نفسها*، لا بما
  جاء بعدها.** `a[p..p+len]` مقابل `b[bj..bj+len]` يبدأ عند
  المرشح — و`bj` لا يُوجد أبدًا إلا لأن `b[bj] == a[p]` يتحقق
  بالفعل، لذا فإن ذلك العنصر الابتدائي تطابق ذاتي تافه بحكم البناء.
  كلما انتهى أي من التتبّعين بالضبط عند المرشح، انهار `len` إلى 1
  ولم يفحص "التأكيد" سوى تلك الحشوية — فقد تُقبل إعادة مزامنة
  بصفر دليل حقيقي، في حالة المرور بكتلة واحدة بالضبط التي
  وُجد `MIN_CONFIRM` لرفضها (انظر **ضبط إعادة المزامنة** أعلاه). على
  وجه التحديد، `a=[...,0x30,0xD0]` (ينتهي عند
  المُوزِّع المشترك) مقابل `b=[...,0x31,0xD0,0xBB,0xBC,0xBD]`
  (ثلاث كتل أخرى، لم تُفحص قط، ومختلفة فعليًا) كان يُثبّت `0xD0` كدمج
  حقيقي. الدلالات المُصلَحة: انتهاء كلا التتبّعين معًا عند المرشح
  مشروع ولا يزال يؤكّد (لا شيء متبقٍ في أي منهما للاختلاف معه — دليل
  شامل، لا دليل غائب)؛ انتهاء تتبّع واحد بينما يستمر الآخر هو الخطأ أعلاه
  ويُرفض الآن. وُجد بواسطة مراجعة مستقلة، لا بواسطة
  مجموعة الاختبارات التي كانت موجودة آنذاك — الاختبارات التي كانت ستلتقطه
  موجودة الآن.

- **الاستدعاء المسبق غير المتتبَّع في وضع الإحماء شارك نفس مخزن الوسائط
  المُخصَّص مع الاستدعاء المتتبَّع الذي تلاه.** بالنسبة لوسيط `'string'`،
  أشار كلا الاستدعاءين إلى مخزن `Memory.allocUtf8String` واحد —
  فالهدف الذي يفكّ ترميز وسيطه أو يحوّله في مكانه (أمر روتيني
  للفحوصات المُشوَّشة التي وُجدت هذه الأداة لتحليلها) سيجعل
  استدعاء الإحماء غير المتتبَّع يعدّل المخزن الذي يقرأه الاستدعاء المتتبَّع
  بعد ذلك، متتبّعًا بصمت مدخلًا معدّلًا مسبقًا بدلًا من وسيطك
  الفعلي. أُصلح بإعطاء استدعاء الإحماء تخصيصه الخاص. لم يُتحقق منه
  مباشرةً مقابل هدف يعدّل نفسه تحديدًا — لم يُبنَ أي هدف لاختبار ذلك،
  قيل بصراحة لا ضمنًا.

- **استغرق الاختبار المباشر على ARM64 محاولتين عبر v0.2.2 وv0.2.3، و
  الفشلان على الطريق يستحقان التوثيق بقدر النجاح
  النهائي.** مقابل POCO F7 Ultra مُروَّت، Android 16، `arm64-v8a`:

  1. **لا تستطيع Frida الحقن في ثنائي ELF مرتبط ساكنًا على هذا
     الجهاز.** الخطوة الأولى البديهية — ترجمة الهدف الاختباري
     ساكنًا، دون الحاجة إلى NDK، دون تبعيات وقت تشغيل لدفعها — فشلت
     بشكل متطابق لكل من `spawn()` و`attach()`:
     `frida.NotSupportedError: bootstrapper crashed with signal 11`.
     انهيار attach بنفس طريقة spawn يستبعد بوابة spawn تحديدًا —
     السبب الحقيقي على الأرجح أن حقن Frida يعتمد على امتلاك الهدف
     لرابط ديناميكي لـ `dlopen()` الوكيل داخله، وهو ما لا يلمسه ثنائي
     ساكن بالكامل أبدًا. ليس خطأ في Veridiff؛ بل خاصية
     لآلية حقن Frida. أُصلح بالبناء باستخدام Android NDK بدلًا من ذلك
     (`aarch64-linux-android26-clang`، دون `-static`) — ملف تنفيذي PIE
     عادي، مرتبط ديناميكيًا مقابل `/system/bin/linker64`، والحقن
     عمل ببساطة. `examples/licensecheck.c` نفسه لم يحتج إلى أي تغييرات؛
     المترجم فقط هو ما احتاج.
  2. **`Interceptor.attach` على دالة داخل `libc.so` المُحصَّن من Bionic**
     (جُرِّب: `strcmp`) **أسقط العملية الهدف** — بشكل قابل للتكرار،
     مرتين، في محاولة ربط libc المباشر التي سبقت بناء NDK
     أعلاه. نفس آلية الربط على دالة داخل مكتبة أصلية مضمّنة
     خاصة بتطبيق عادي (جُرِّب: `base.odex` الخاص بـ Chrome Beta)
     ثُبِّتت ونجت بنظافة، ولهذا فإن التشغيل الناجح في النهاية
     يتتبّع `check_license` *الخاص بالثنائي الاختباري نفسه*، لا دالة مكتبة نظام
     يُوصل إليها من خلاله. السبب المرجّح: تحصين تدفق التحكم على ARM64
     (PAC/BTI/CFI) على `libc.so` في بناء Android حديث
     يتعارض مع الربط المضمّن — فرضية معقولة بالنظر إلى الأدلة،
     مُلتزَم بها عند عتبة ثقة أدنى من نتيجة الثنائي الساكن
     أعلاه، المؤكَّدة. لم يُعَد اختبارها منذ الالتفاف حولها، لذا تبقى
     لغمًا حيًا لأي شخص يربط مكتبات النظام تحديدًا، لا مجرد
     ملاحظة تاريخية.

  ما نجح أخيرًا، بمجرد أن أصبح الهدف بناء NDK سليمًا: `spawn()`
  مباشرةً (لا حاجة إلى حل التفاف النوم والربط لثنائي عادي
  مرتبط ديناميكيًا — ذلك الالتفاف لم يكن مطلوبًا قط إلا أثناء
  مطاردة الطرق المسدودة للثنائي الساكن وlibc المُحصَّن أعلاه)، ثم نفس
  تسلسل `trace_call`/`find_first_divergence`/`disassemble_block`
  كأي هدف آخر، مع حل قاعدة الوحدة مرة واحدة في البداية لأن Android
  يتطلب PIE. لم يحتج تصميم اختيار الجهاز في المحرك نفسه إلى أي
  تغييرات لتوجيهه نحو الهاتف خلال أي من هذا — `VeridiffEngine(device=...)` في Python
  كان بالفعل يقبل أي جهاز، وكون محرك Rust لا يملك `Device` يعني أن
  المستدعي استخدم فقط `DeviceType::USB` بدلًا من المحلي. كل عقبة حقيقية كانت في
  الثنائي الهدف وطبقة حقن Frida، لا في هذا المحرك أبدًا. وصفة البناء الكاملة
  في قسم Testing في `CLAUDE.md`.

---

## القانون

Veridiff هو محرك أساسي بمعيار مرجعي، وليس تطبيقًا. قيمته
كلها في كونه صغيرًا وسريعًا وخفيف التبعيات بما يكفي ليُضمَّن في *أي شيء* —
واجهة سطر أوامر، واجهة رسومية، إضافة Ghidra أو IDA، بوابة CI،
بوت Discord، أيًا كان ما يحتاجه الشخص التالي الذي يبني فوقه. كل تبعية أو
تجريد نسمح له بالدخول إلى النواة هو ضريبة
يدفعها كل مُدمِج واحد بعدنا، إلى الأبد — بما في ذلك
من لم يرغبوا فيه قط.

لذا القاعدة بسيطة، ونعتزم فرضها دون
استثناء:

**نرحّب، بأذرع مفتوحة، بـ:**
- تحسينات الخوارزمية الأساسية — مسح تباعد أسرع أو أكثر دقة،
  استدلال إعادة مزامنة أذكى، تقليل حقيقي في التخصيصات
  أو النسخ على المسار الساخن.
- إصلاحات الذاكرة والصحة، خاصة تلك التي وُجدت بالطريقة التي
  وُجد بها خطأ Stalker/Interceptor أعلاه: بتتبّع عملية حقيقية فعليًا
  وإثبات الإصلاح مقابل مخرجات حقيقية.
- دعم معماريات جديدة — ARM32/Thumb، MIPS، RISC-V، حيثما
  تصل Frida وCapstone بالفعل ومنطق التصنيف لدينا
  (كشف نوع التفريع، وما يأتي بعده) لا يصل بعد. x86
  وARM64 كلاهما مُتحقق منهما مباشرةً اعتبارًا من v0.2.3 (انظر **إثبات، لا وعود**)؛
  ARM32/Thumb وMIPS وRISC-V لا تزال غير مُجرَّبة.
- إصلاحات قابلية النقل للمنصات التي يتعامل معها الكود الحالي بشكل سيئ.

**لن ندمج، أبدًا، تحت أي صياغة:**
- أطر CLI. لا `argparse`، لا `clap`، لا تحليل أعلام من أي
  نوع. إذا أردت أعلامًا، فذلك غلاف، لا رقعة على هذا المستودع.
- كود GUI، كود TUI، لوحات تحكم ويب، أشرطة تقدم.
- مخرجات طرفية ملوّنة، أطر تسجيل فاخرة، مؤشرات دوّارة —
  `print()`/`eprintln!()` أو لا شيء.
- دوال "الراحة" التي توجد فقط لتوفير ثلاثة أسطر على المستدعي
  على حساب تبعية جديدة لكل من لا يحتاجها.
- صيغ ملفات الإعداد، أنظمة الإضافات، القياس عن بعد، فاحصات
  التحديث التلقائي — أي شيء يحوّل محركًا إلى تطبيق.

هذا ليس حراسة للبوابة لمجرد ذلك — إنها الطريقة الوحيدة ليبقى المحرك الأساسي
محركًا أساسيًا بدلًا من أن يصبح ببطء واجهة سطر أوامر
معينة لشخص ما مع واجهة برمجة ملحقة كفكرة لاحقة. إذا أردت
واجهة سطر أوامر، أو واجهة رسومية، أو إضافة Ghidra: **اعمل fork للمحرك، وابنِ
`veridiff-cli` أو `veridiff-gui` أو `veridiff-ida` فوقه كمشروع
خاص به، واعتمد على هذا المستودع كأي مكتبة أخرى.** نحن
نعني ذلك كدعوة، لا كصدّ — أخبرنا بوجوده
وسنربط إليه من هنا.

---

## الترخيص

Apache License 2.0 — انظر [`LICENSE`](https://github.com/veridiff/veridiff/blob/main/LICENSE). حقوق النشر 2026 Veridiff.
تنزيل الأداة