
دليل حول كيفية كتابة قواعد YARA سريعة وصديقة للذاكرة
كتابة قواعد YARA فعّالة أمر أساسي للحفاظ على سرعة ودقة الأداء عند الفحص. يوفّر هذا الدليل مبادئ رئيسية وأفضل الممارسات التي تساعدك على تحسين قواعدك، وتقليل الحسابات غير الضرورية، وتجنّب الأخطاء الشائعة. يتضمن الدليل رؤى من خبراء في المجال، منهم Victor M. Alvarez وWXS، ومساهمات من مجتمع YARA.
يوفر هذا القسم ملخصًا موجزًا لأفضل ممارسات أداء YARA. للحصول على شروحات مفصّلة وأمثلة، راجع الدليل الكامل أدناه.
"فكّر في YARA كعملية من خطوتين: أولاً، البحث عن جميع الأنماط المدرجة في السلاسل، وثانيًا، تقييم الشروط. لا يمكنك استخدام شروط جيدة الصياغة للتعويض عن سلاسل سيئة الاختيار."
— Wesley Shields
يتبع YARA أربع خطوات رئيسية عند فحص ملف:
يبحث YARA عن السلاسل أولاً، مما يجعل اختيار السلاسل العامل الأكثر أهمية في كفاءة القاعدة.
✅ أفضل الممارسات للسلاسل:
\x00\x00\x00\x00 يظهر بشكل متكرر جدًا.nocase بحذر – فهو يولّد عددًا هائلًا من Variations البحث.يقوم YARA بتقييم الشروط تسلسليًا ويتوقف عند أول فشل.
✅ أفضل الممارسات للشروط:
filesize < X) قبل الشروط المكلفة.for all i in (1..filesize) غير فعّال).@) بدلاً من التعبيرات النمطية لفحص التسلسل.⚠ ملاحظة: شروط التعبيرات النمطية لا تخضع للاختصار المنطقي ويتم تقييمها دائمًا في النهاية.
الوحدات مثل pe أو elf أو magic يجب أن تحلل الملف بالكامل قبل التقييم، مما يزيد من زمن الفحص.
✅ بدائل:
pe.is_pe، استخدم uint16(0) == 0x5A4D لتحديد ملفات PE.التطابقات المفرطة تبطئ الفحص وقد تؤدي إلى أخطاء "too many matches".
✅ إصلاح التطابقات غير الفعّالة:
.* أو .+ أو {x,} بدون حد أعلى.أعدّ @herrcore فيديو تعليميًا مفيدًا يغطي الموضوعات التي تمت مناقشتها في دليل الأداء هذا.
مقدمة إلى YARA - كتابة قواعد YARA فعّالة
لفهم أين وكيف يمكن تحسين أداء YARA بشكل أفضل، من المفيد فهم عملية الفحص. هي مقسمة بشكل أساسي إلى 4 خطوات سيتم شرحها بشكل مبسّط جدًا باستخدام قاعدة الأمثلة هذه:
import "math"
rule example_php_webshell_rule
{
meta:
description = "Just an example php webshell rule"
date = "2021/02/16"
strings:
$php_tag = "<?php"
$input1 = "GET"
$input2 = "POST"
$payload = /assert[\t ]{0,100}\(/
condition:
filesize < 20KB and
$php_tag and
$payload and
any of ( $input* ) and
math.entropy(500, filesize-500) >= 5
}
تحدث هذه الخطوة قبل الفحص الفعلي. سيبحث YARA عن ما يسمى atoms (ذرات) في سلاسل البحث لتغذية آلية Aho-Corasick. التفاصيل موضحة في فصل الذرات (atoms)، لكن يكفي الآن معرفة أن طولها يبلغ 4 بايت كحد أقصى، وأن YARA يختارها بذكاء لتجنّب كثرة التطابقات. في مثالنا، قد يختار YARA الذرات الأربع التالية:
<?phGETPOSTsser (من assert)هنا يبدأ الفحص. سيتم تنفيذ الخطوات 2-4 على جميع الملفات. سيبحث YARA في كل ملف عن الذرات الأربع المعرّفة أعلاه باستخدام شجرة بادئات تُسمى آلية Aho-Corasick. يتم تمرير أي تطابقات إلى محرك البايت كود.
إذا كان هناك مثلًا تطابق على sser، فسيتحقق YARA مما إذا كانت مسبوقة بحرف a ويستمر بحرف t. إذا كان ذلك صحيحًا، سيتابع مع التعبير النمطي [\t ]{0,100}\(. بهذا الأسلوب الذكي، يتجنب YARA استخدام محرك تعبيرات نمطية بطيء على الملفات الكاملة، ويختار فقط أجزاء معينة لفحصها عن قرب.
بعد اكتمال جميع مطابقة الأنماط، يتم التحقق من الشروط.
لدى YARA آلية تحسين أخرى لتنفيذ فحص math.entropy المُكلف من قاعدة المثال فقط إذا كانت الشروط الأربعة التي تسبقه مستوفاة. شرح ذلك بمزيد من التفصيل في فصل الشروط والتقييم الاختصاري
إذا كانت الشروط مستوفاة، يتم الإبلاغ عن تطابق. يستمر الفحص مع الملف التالي في الخطوة 2.
يستخرج YARA من السلاسل سلاسل فرعية قصيرة يصل طولها إلى 4 بايت تُسمى "ذرات" (atoms). يمكن استخراج هذه الذرات من أي مكان داخل السلسلة، ويبحث YARA عن هذه الذرات أثناء فحص الملف، وإذا وجد واحدة منها فإنه يتحقق من أن السلسلة تطابق بالفعل.
على سبيل المثال، تأمل هذه السلاسل:
/abc.*cde/
=> الذرات المحتملة هي abc وcde، يمكن استخدام أي منهما. حاليًا يُفضَّل ذرة abc لأن كلاهما بنفس الجودة وهي الأولى بينهما.
/(one|two)three/
=> الذرات المحتملة هي one وtwo وthre وhree. يمكننا البحث عن thre (أو hree) بمفردها، أو عن كل من one وtwo. الذرة thre مفضّلة لأنها ستؤدي إلى تطابقات محتملة أقل من one وtwo (لأنهما أقصر) ولا تحتوي على حرف e مكرر (كلما كان الحرف أكثر تميزًا كان أفضل).
يبذل YARA قصارى جهده لاختيار أفضل الذرات من كل سلسلة، على سبيل المثال:
{ 00 00 00 00 [1-4] 01 02 03 04 }
=> هنا يستخدم YARA الذرة 01 02 03 04، لأن 00 00 00 00 شائعة جدًا
{ 01 02 [1-4] 01 02 03 04 }
=> 01 02 03 04 مفضّلة على 01 02 لأنها أطول
لذا، النقطة المهمة هي أن السلاسل يجب أن تحتوي على ذرات جيدة. هذه سلاسل سيئة لأنها تحتوي على ذرات قصيرة جدًا أو شائعة جدًا:
{00 00 00 00 [1-2] FF FF [1-2] 00 00 00 00}
{AB [1-2] 03 21 [1-2] 01 02}
/a.*b/
/a(c|d)/
أسوأ السلاسل هي تلك التي لا تحتوي على أي ذرات على الإطلاق، مثل:
/\w.*\d/
/[0-9]+\n/
هذه التعبيرات النمطية لا تحتوي على أي سلسلة فرعية ثابتة يمكن استخدامها كذرة، لذلك يجب تقييمها عند كل إزاحة في الملف لمعرفة ما إذا كانت تطابق هناك.
توصية جيدة أخرى هي تجنّب الحلقات (loops) ذات التكرارات الكثيرة جدًا، خاصة إذا كانت العبارة داخل الحلقة معقدة جدًا، على سبيل المثال:
strings:
$a = {00 00}
condition:
for all i in (1..#a) : (@a[i] < 10000)
هذه القاعدة فيها مشكلتان. الأولى أن السلسلة $a شائعة جدًا، والثانية أنه بسبب شيوع $a فإن #a يمكن أن يكون كبيرًا جدًا وقد يتم تقييمه آلاف المرات.
هذا الشرط الآخر غير فعّال أيضًا لأن عدد التكرارات يعتمد على حجم الملف، والذي يمكن أن يكون كبيرًا جدًا أيضًا:
for all i in (1..filesize) : ($a at i)
تجنّب استخدام وحدة "magic" غير المتوفرة على منصة Windows. استخدام وحدة "magic" يبطئ الفحص لكنه يوفر تطابقات دقيقة.
تعريف ترويسة GIF المخصصة:
rule gif_1 {
condition:
(uint32be(0) == 0x47494638 and uint16be(4) == 0x3961) or
(uint32be(0) == 0x47494638 and uint16be(4) == 0x3761)
}
استخدام وحدة "magic":
import "magic"
rule gif_2 {
condition:
magic.mime_type() == "image/gif"
}
تجنّب تعريف سلاسل قصيرة جدًا. أي سلسلة أقل من 4 بايت ستظهر غالبًا في الكثير من الملفات أو كمحتوى موحّد في ملف مُعامل بعملية XOR.
بعض السلاسل طويلة بما يكفي ولكن لا ينبغي استخدامها لسبب مختلف - وهو التوحّد. هذه بعض الأمثلة على سلاسل لا ينبغي استخدامها لأنها قد تسبب تطابقات كثيرة جدًا في الملفات.
$s1 = "22222222222222222222222222222222222222222222222222222222222222"
$s2 = "\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20" // wide formatted spaces
ستبدو رسالة الخطأ كما يلي:
error scanning yara-killer.dat: string "$mz" in rule "shitty_mz" caused too many matches
حاول جعل تعريفات السلاسل محددة قدر الإمكان. تجنّب خاصية "nocase" إذا أمكن، لأنه سيتم توليد والبحث عن العديد من الذرات (استهلاك أعلى للذاكرة، وتكرارات أكثر). تذكر، في غياب المعدِّلات، يُفترض "ascii" افتراضيًا. التركيبات الممكنة هي:
منخفض (LOW) - يتم توليد ذرة واحدة فقط
$s1 = "cmd.exe" // (ascii only)
$s2 = "cmd.exe" ascii // (ascii only, same as $s1)
$s3 = "cmd.exe" wide // (UTF-16 only)
$s4 = "cmd.exe" ascii wide // (both ascii and UTF-16) two atoms will be generated
$s5 = { 63 6d 64 2e 65 78 65 } // ascii char code in hex
مرتفع (HIGH) - سيتم توليد جميع مجموعات الأحرف الكبيرة والصغيرة للبايتات الأربعة التي يختارها YARA كـذرات
$s5 = "cmd.exe" nocase (all different cases, e.g. "Cmd.", "cMd.", "cmD." ..)
إذا كنت تريد مطابقة أوامر برمجية نصية، تحقق أولاً مما إذا كانت اللغة حساسة لحالة الأحرف أم لا (مثل php، وWindows batch) قبل استخدام nocase. إذا كنت تحتاج فقط إلى حالة أحرف مختلفة لحرف أو حرفين، فمن الأفضل استخدام تعبير نمطي، مثل:
$re = /[Pp]assword/
كن حذرًا عند التعامل مع البدائل (alternation) مثل:
$re = /(a|b)cde/
$hex = {C7 C3 00 (31 | 33)}
هذه السلاسل تولّد ذرات قصيرة يمكن أن تبطئ الفحص. في الحالات التي يكون فيها عدد المتغيرات صغيرًا، يُنصح بكتابة السلسلة بشكل منفصل:
$re1 = /acde/
$re2 = /bcde/
$hex1 = {C7 C3 00 31}
$hex2 = {C7 C3 00 33}
استخدم التعبيرات النمطية فقط عند الضرورة. التعبير النمطي أبطأ بطبيعته من مطابقة السلاسل العادية ويستهلك كمية كبيرة من الذاكرة. لا تستخدمها إذا كانت السلاسل الست عشرية مع القفزات وأحرف البدل يمكن أن تحل المشكلة.
إذا اضطررت لاستخدام التعبيرات النمطية، فتجنّب .* الجشعة وحتى معدّلات الكم المترددة .*?. وبدلاً من ذلك استخدم أرقامًا دقيقة مثل .{1,30} أو حتى .{1,3000}. أيضًا، لا تنسَ الحد الأعلى (تجنّب مثل .{2,}).
عند استخدام معدّلات الكم، يمكن أن تحدث حالتان:
إذا كانت بداية التعبير النمطي مثبتة على موضع واحد واللاحقة فقط هي التي يمكن أن تختلف، فسوف يطابق YARA أطول تطابق ممكن. في حالات مثل .* و.+ أو .{2,}، يمكن أن يؤدي ذلك إلى سلاسل كبيرة وإبطاء الفحص.
إذا كانت هناك بدايات أكثر可能性 للتعبير النمطي، فسوف يطابق YARA جميعها.
$re1 = /Tom.{0,2}/ // will find Tomxx in "Tomxx"
$re2 = /.{0,2}Tom/ // will find Tom, xTom, xxTom in "xxTom"
عدد التطابقات القصيرة يمكن أن يتجاوز الحد بسهولة ويسبب خطأ "too many matches".
المثال التالي هو التعبير النمطي لعنوان بريد إلكتروني. عند استخدام [-a-z0-9._%+] مع معدّلات الكم، سيطابق YARA العنوان الواحد عدة مرات، وهذا ليس مثاليًا. في هذه الحالة، يُنصح بإيجاد مجموعة فرعية صغيرة بشكل معقول من العناوين توفر معلومات كافية للتحليل.
استخدم
/[-a-z0-9._%+]@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
أو
/@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
تجنّب
/[-a-z0-9._%+]*@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
/[-a-z0-9._%+]+@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
/[-a-z0-9._%+]{x,y}@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
إذا أردت التأكد من أن exec متبوعًا بـ /bin/sh مثلًا، يمكنك استخدام الإزاحات التي يوفرها الرمز @. هذه هي النسخة البطيئة بالتعبير النمطي:
$ = /exec.*\/bin\/sh/
هذه هي الطريقة الأسرع باستخدام الإزاحات:
strings:
$exec = "exec"
$sh = "/bin/sh"
conditions:
$exec and $sh and
@exec < @sh
حاول أيضًا تضمين تسلسلات طويلة من السلاسل التي يمكن أن تكون بمثابة مراسي (anchors) في عملية المطابقة. مرة أخرى، كلما كان أطول كان أفضل.
سيئ
$s1 = /http:\/\/[.]*\.hta/ // greedy [.]*
أفضل
$s1 = /http:\/\/[a-z0-9\.\/]{3,70}\.hta/ // better, with an the upper bound
الأفضل
$s1 = /mshta\.exe http:\/\/[a-z0-9\.\/]{3,70}\.hta/
أخطاء كثرة التطابقات سببها سلاسل عامة جدًا تتكرر كثيرًا في الإدخال، أو أن YARA يطابق نفس المثيل عدة مرات.
بطء الفحص سببه سلاسل تولّد ذرات قصيرة جدًا، أو لا تولّد أي ذرات على الإطلاق. ونتيجة لذلك، يستخدم YARA خوارزمية مطابقة أنماط ساذجة، مما يسبب التباطؤ.
كلا المشكلتين يمكن، في بعض الحالات، إصلاحهما باتباع هذه الخطوات:
.* و.+ و.*?x{14,}لاحظ، في الفصل التالي الشروط والتقييم الاختصاري، تم ذكر بعض النصائح للشروط. ومع ذلك، فإن التغييرات فيها لن تحل مشكلات كثرة التطابقات وبطء الفحص.
حاول كتابة عبارات الشروط بحيث توضع العناصر الأكثر احتمالًا لأن تكون "False" أولاً. يتم تقييم الشرط من اليسار إلى اليمين. كلما أسرع المحرك في تحديد أن القاعدة غير مستوفاة، كلما أسرع في تخطي القاعدة الحالية وتقييم التالية. تحسين السرعة الناتج عن هذا الترتيب لعبارات الشروط يعتمد على الفرق في دورات المعالجة اللازمة لتنفيذ كل عبارة. إذا كانت جميع العبارات متساوية التكلفة تقريبًا، فإن إعادة ترتيبها لا تسبب تحسنًا ملحوظًا. إذا كان يمكن تنفيذ إحدى العبارات بسرعة كبيرة، يُنصح بوضعها أولاً لتجنّب تقييم العبارة المكلفة في الحالات التي تكون فيها العبارة الأولى FALSE.
تغيير الترتيب في العبارة التالية لا يسبب تحسنًا كبيرًا:
$string1 and $string2 and uint16(0) == 0x5A4D
ومع ذلك، إذا كان وقت تنفيذ العبارات مختلفًا جدًا، فإن إعادة الترتيب من أجل تفعيل الاختصار المنطقي سيحسن سرعة الفحص بشكل كبير:
بطيء
// EXPENSIVE and CHEAP
math.entropy(0, filesize) > 7.0 and uint16(0) == 0x5A4D
سريع
// CHEAP and EXPENSIVE
uint16(0) == 0x5A4D and math.entropy(0, filesize) > 7.0
التقييم الاختصاري تم تقديمه للمساعدة في تحسين العبارات المكلفة، خاصة عبارات "for". بعض الأشخاص كانوا يستخدمون شروطًا مثل المثال التالي:
strings:
$mz = "MZ"
...
condition:
$mz at 0 and for all i in (1..filesize) : ( whatever )
لأن حجم الملف يمكن أن يكون رقمًا كبيرًا جدًا، يمكن تنفيذ "whatever" عدة مرات، مما يبطئ التنفيذ. الآن، مع التقييم الاختصاري، سيتم تنفيذ عبارة "for" فقط إذا تحقق الجزء الأول من الشرط، لذا ستكون هذه القاعدة بطيئة فقط لملفات MZ. تحسين إضافي يمكن أن يكون:
$mz at 0 and filesize < 100KB and for all i in (1..filesize) : ( whatever )
بهذه الطريقة يتم تعيين حد أعلى لعدد التكرارات.
من الإصدار 3.10، تم أيضًا تحسين حلقات النطاقات الصحيحة:
for all i in (0..100): (false)
for any i in (0..100): (true)
كلا الحلقتين ستتوقفان عن التكرار بعد أول مرة.
للأسف هذا لا يعمل مع التعبيرات النمطية لأنها جميعًا تُغذى مبدئيًا إلى محرك مطابقة السلاسل. المثال التالي سيبطئ البحث لأي ملف وليس فقط لتلك التي حجمها أصغر من 200 بايت:
strings:
$expensive_regex = /\$[a-z0-9_]+\(/ nocase
conditions:
filesize < 200 and
$expensive_regex
هذا "التقييم الاختصاري" يتم تطبيقه منذ إصدار YARA 3.4.
أي بيانات في قسم البيانات الوصفية (metadata) يتم قراءتها في الذاكرة RAM بواسطة YARA. (يمكنك اختبار ذلك بسهولة بإدراج 100,000 تجزئة (hash) في قاعدة والتحقق من استخدام الذاكرة في فحص YARA قبلها وبعدها). بالطبع لا تريد إزالة البيانات الوصفية بشكل دائم من القواعد، لكن إذا كنت تعاني من نقص في الذاكرة، يمكنك إزالة بعض الأجزاء غير الضرورية منها في سير عملك مباشرة قبل الفحص.