DTGaraGe | Автомобили, Офроуд, Заваряване, Linux, AI

Регистрирайте безплатен акаунт днес, за да станете член! След като влезете, ще можете да участвате в този сайт, като добавяте свои собствени теми и публикации, както и да се свързвате с други членове чрез вашата лична пощенска кутия!

Linux forensics, част 1: Когато машината ти лъже в очите



linux-forensics-1-banner.png



Как се разглобява жива система, на чиито инструменти вече не вярваш



Тази тема идва от заявка на @linux в темата за поръчки. Дословно: сървърът се държи странно, а на ps, netstat, lsof и ls вече не се вярва. Иска подход отдолу нагоре, през /proc, /sys и сурови данни, без да се обляга на готови инструменти.

Заявката е добра, защото описва точния момент, в който повечето ръководства спират да работят. Те приемат, че системата казва истината. Ние тук приемаме обратното.

Част 1 е за живата машина - тази, която още работи и която не искаш да гасиш. Част 2 ще е за диска и паметта, тоест за случая, в който вече не вярваш на нищо, което е вътре.




Правилото, което държи цялата статия


Едно-единствено правило, и всичко останало е приложение на него:

Нито едно твърдение не се приема от един източник.

Всеки факт се потвърждава от два независими пътя. Процес - от ps и от суровото съдържание на /proc. Сокет - от ss и от /proc/net/tcp, а най-добре и от скан отвън. Файл - от ls и от stat, а накрая и от машина, която монтира диска отстрани.

Когато двата източника съвпадат, имаш слаба увереност. Когато се разминават, имаш находка. Цялата работа на форензиката е да произвежда разминавания.

Заваръчният еквивалент е прост. Визуалният оглед ти казва, че шевът изглежда добре. Това не е контрол. Контрол е, когато вторият метод не потвърди онова, което окото е видяло.




Кой слой те лъже - и докъде му стига ръката


Няма смисъл да търсиш, преди да си наясно какво може да те лъже. Слоевете са четири и всеки следващ вижда повече от предишния.

1. Подменен бинарник


Най-примитивното. Заменен /bin/ps или /bin/ls, който филтрира резултата. Понякога дори не се подменя нищо - просто се пипа PATH или се слага фалшив ps в директория, която идва по-рано в търсенето.

Не може да скрие: нищо, което четеш директно от /proc или /sys.
Разпознава се по: проверка на пакетите, type и command -v, сравнение на контролни суми с чист източник.


2. LD_PRELOAD и ld.so.preload


Библиотека, заредена преди системната, която подменя функции като readdir. Оригиналният ls си стои непокътнат, но не вижда файловете, чиито имена библиотеката е решила да изяде.

Не може да скрие: нищо от статично свързан бинарник, който не ползва динамичния зареждач.
Разпознава се по: съдържанието на /etc/ld.so.preload, променливи в средата на процесите, сравнение на изхода от динамичен и статичен инструмент.


3. Модул в ядрото


Тук вече лъжата е под всичко в потребителското пространство. Хванати системни извиквания, филтриран /proc, скрити процеси, скрити сокети. И ps, и cat /proc/... получават една и съща обработена истина, защото и двете минават през ядрото.

Не може да скрие: това, което се вижда отвън - трафик по кабела, състоянието на диска, гледан от друга система.
Разпознава се по: вътрешни несъответствия, tainted състояние, разлика между /proc/modules и /sys/module, и най-вече чрез външни наблюдения.


4. eBPF


Модерният вариант на същото. Не се зарежда модул, не се цапа ядрото по класическия начин, а се закача програма, която може да чете и да променя поведение. Оставя по-малко следи по стария контролен списък и точно затова старите ръководства не го хващат.

Разпознава се по: типа на заредените програми и точките, в които са закачени - не по самото им наличие. Виж отделния раздел по-долу, защото тук е най-лесно да се изплашиш от нищо.


Колкото по-дълбоко е скрит лъжецът, толкова по-малко смисъл има да го питаш. На някакво ниво единственият честен източник е този, който е извън машината.



Преди да пипнеш каквото и да е


Всяка твоя команда е събитие, което може да бъде забелязано. Ако на машината има активен нападател, ти му съобщаваш, че си вътре и че гледаш. Затова редът е важен.

  1. Не рестартирай и не гаси. Изгасената машина губи паметта, а с нея и половината доказателства. Дори да е заразена, тя е по-полезна включена, отколкото чиста и празна.
  2. Не инсталирай нищо на нея. Всяко apt install пипа системата, която разследваш, и минава през мрежа, на която не можеш да вярваш.
  3. Носи си инструментите. Статично свързан BusyBox от чиста машина, копиран на носител само за четене. Той не ползва библиотеките на заподозряната система, значи слой 2 не го засяга.
  4. Записвай всичко извън машината. Изходът върви към твоя лаптоп, не към диска на сървъра. Логовете на заразена машина са данни, а не доказателства.
  5. Отбележи часа. Часът на машината и реалният час. Разминаването само по себе си е находка.

Всичко по-долу е писано и проверявано на Debian 13 (trixie) с ядро от серия 6.12, на реална работеща машина - не в стерилна виртуалка. Всяко число в статията идва от нейния изход. Пълното ѝ описание е в раздела с базовите стойности, защото без него числата не значат нищо.

За bookworm с 6.1 пътищата и командите са същите - разликите са в имената на някои пакети и във версиите на инструментите, не в подхода.




Процеси: гледай директориите, не списъка


ps е просто програма, която обхожда /proc и подрежда резултата. Ако ще ѝ вярваш, поне я карай да се разминава сама със себе си.

Първата проверка е сравнение между това, което ядрото изброява, и това, което ps признава:


Code:
# всички числови директории в /proc
ls -1 /proc | grep -E '^[0-9]+$' | sort -n > /tmp/proc.list

# същото според ps
ps -eo pid --no-headers | tr -d ' ' | sort -n > /tmp/ps.list

diff /tmp/proc.list /tmp/ps.list

Разлика тук значи, че нещо между двата източника филтрира. Липсата на разлика не значи чисто - значи само, че лъжата, ако има такава, е под двата.

Следващата стъпка е по-груба. Директория в /proc може да бъде скрита от изброяване, но пак да е достъпна по име. Затова се пита за всеки възможен номер поотделно:


Code:
# зареждаме известните PID-ове в паметта на обвивката, без форкове в цикъла
declare -A seen
while read -r p; do seen[$p]=1; done < /tmp/ps.list

max=$(cat /proc/sys/kernel/pid_max)

for ((pid=1; pid<=max; pid++)); do
  [ -d /proc/$pid ] || continue
  [ -n "${seen[$pid]}" ] && continue

  # ВАЖНО: нишките също имат директория в /proc, но не се изброяват.
  # Ако Tgid се различава от Pid, това е нишка на друг процес, не скрит процес.
  tgid=$(awk '/^Tgid:/{print $2}' /proc/$pid/status 2>/dev/null)
  [ -z "$tgid" ] && continue
  [ "$tgid" != "$pid" ] && continue

  echo "СКРИТ PID: $pid  $(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null)"
done

Редът с Tgid е най-важният в целия скрипт и почти никое ръководство не го споменава.

Директория в /proc има не само за процес, а и за всяка нишка. Разликата е, че нишките не се изброяват при четене на /proc, но са напълно достъпни по име. Обхождането ги намира, ps не ги показва като отделни записи, и без филтъра всяка нишка на базата данни или на контейнер се обявява за скрит процес.

Ето какво дава точно този скрипт на здрава машина, при пълен обход на диапазона:


Code:
TOTAL_DIRS       = 889     # всички директории в /proc (процеси + нишки)
RAW_CANDIDATES   = 478     # не са в изхода на ps - "скрити" преди филтъра
TRUE_CANDIDATES  = 0       # остават след проверката по Tgid

478 срещу 0. Без този един ред получаваш 478 доклада за скрити процеси на напълно здрава система и започваш да търсиш нападател, който не съществува. С него - нула.

Това е разликата между метод и паника.


Скорост и нормален шум


На 64-битово Debian pid_max е 4194304. Ако цикълът вика външна програма при всяка итерация, говорим за четири милиона процеса-наследници - часове работа и натоварване, което ще бъде забелязано. Вариантът горе вика нещо само при попадение, тоест 478 пъти, а не четири милиона.

Измерено: 28,7 секунди за целия диапазон с пълната логика. Без проверката по Tgid са 26,4 секунди, тоест филтърът струва около две секунди и спестява 478 фалшиви тревоги. За сравнение, 300 000 номера минават за 1,6 секунди.

Бързо е, защото /proc не е на диск. Той не е и файлова система в паметта - той е procfs, виртуална файлова система, която ядрото сглобява в момента на четенето. Няма какво да се чака.

Затова обхождай целия диапазон. Таван има смисъл само на много натоварена машина, където и половин минута системни извиквания е проблем.

И най-важното за четенето на резултата. Сравнението между /proc и ps на здрава машина не дава нула. При моя тест излязоха по три записа от всяка страна - процеси, родени и умрели между двете последователни команди. Дори общият брой директории се разминава с единица между два последователни обхода, защото машината живее, докато я броиш. Това е нормален шум, не находка. Затова: пускай сканирането два пъти и гледай само номерата, които оцеляват и в двата пъти.

За всеки подозрителен номер се вадят суровите данни:


Code:
pid=1234
readlink /proc/$pid/exe          # истинският изпълним файл
readlink /proc/$pid/cwd          # работна директория
tr '\0' ' ' < /proc/$pid/cmdline # аргументи
tr '\0' '\n' < /proc/$pid/environ # среда, търси LD_PRELOAD
cat /proc/$pid/status # PPid, UID, нишки, capabilities
ls -l /proc/$pid/fd/             # отворени файлове и сокети
cat /proc/$pid/maps              # заредени библиотеки

Три неща, които се четат най-бързо и лъжат най-трудно:

Изтрит бинарник, който още работи. Връзката exe завършва с (deleted). Класика - програмата се пуска и веднага се трие от диска, за да не остане следа. Следата остава в паметта.


Code:
ls -l /proc/*/exe 2>/dev/null | grep -i deleted

И веднага уговорката, която прави разликата между полезна и вредна проверка. На здрав Debian 13 сървър тази команда връща цял списък. Ето какво излезе при мен и защо всяко от тях е нормално:

  • plymouthd - програмата за начален екран, трие се след зареждане и продължава да работи
  • php-fpm с /memfd:eek:pcache_lock - кешът на PHP държи анонимна памет с име. Показва се като изтрит винаги. Това е най-често срещаната фалшива тревога при уеб сървър.
  • mariadbd с /tmp/#23, #24 и подобни - временни таблици. Базата ги създава и веднага им маха името, за да се чистят сами.
  • mdmon с /dev/.tmp.md... - наблюдателят на RAID масива
  • pipewire с /memfd:pipewire-memfd - аудио буфери в анонимна памет, идват от графичната среда

Забележи, че списъкът е огледало на ролите на машината. База данни, уеб стек, RAID, десктоп. На твоята машина ще са други имена по същата логика - и точно затова не може да се публикува универсален списък "тези са наред".

Общото между всички: memfd и /tmp/# са легитимни техники за анонимна памет и временни файлове. Находка е нещо друго - изтрит бинарник в /usr/bin, /usr/sbin или потребителска директория, чието име би трябвало да сочи към истински инсталиран файл. Тоест гледаш пътя, не думата deleted.

Фалшив системен процес. Истинските нишки на ядрото нямат exe връзка и се водят под kthreadd. Нещо, което се нарича [kworker/0:2], но има изпълним файл и родител в потребителското пространство, лъже за това какво е.

Несъответствие в средата. LD_PRELOAD в environ на процес, който няма причина да го има.

И накрая - независим брояч. Планировчикът на ядрото води собствена сметка за задачите и не минава през същия път като /proc:


Code:
mount | grep debugfs || mount -t debugfs none /sys/kernel/debug
grep -c . /sys/kernel/debug/sched/debug

Важно за точността: този файл не е на /proc/sched_debug. Преместен е в debugfs от ядро 5.13 нататък, тоест на всяко актуално Debian. Старите статии по темата още дават стария път и затова не работят.



Мрежа: сокетът има номер, номерът има собственик


ss пита ядрото по един път, /proc/net/tcp е друг път до същата информация. Сравнението хваща подменен инструмент, но не хваща модул, който филтрира и двете. Затова накрая идва външният скан.

Code:
ss -tulpanH | wc -l
cat /proc/net/tcp /proc/net/tcp6 | wc -l

Адресите в /proc/net/tcp са шестнайсетични и с обърнати байтове, което плаши хората, а не бива. Портът е втората част след двоеточието: 0x0016 е 22, 0x01BB е 443. Предпоследно е състоянието, а колоната с inode е тази, която ти трябва.

Оттам номерът на сокета се връзва обратно към процеса:


Code:
inode=123456
ls -l /proc/*/fd/* 2>/dev/null | grep "socket:\[$inode\]"

Ако сокетът съществува, а собственик няма, значи процесът е скрит от изброяването. Това е същинска находка.

Още две места, които хората пропускат:


  • /proc/net/packet и /proc/net/raw - тук се виждат снифери. Само че не е вярно, че на нормална машина списъкът е празен: при мен има точно един запис и той се проследи до NetworkManager, който държи такъв сокет за ARP. Правилото е същото както навсякъде - вземи номера на сокета от последната колона и го върни до процеса. Ако собственикът е обясним, записът е обясним.
  • Режим promiscuous по интерфейса: ip -d link show и стойността promiscuity. На чиста машина е нула по всички интерфейси, включително loopback, docker0 и виртуалните двойки. Плюс търсене в dmesg за влизане в този режим.

Числата от здрава машина за мащаб: ss показа 39 записа, суровите файлове в /proc/net - 38. Разликата от един е връзка, отворена или затворена между двете команди. Пак шум, пак не е находка.

И задължителното, което не може да бъде излъгано отвътре: скан отвън, от друга машина, по целия диапазон портове. Ако отвън се отговаря на порт, който отвътре не съществува, разговорът приключва. Имаш модул в ядрото.

Същото важи и за трафика. Ако имаш рутер или втора машина на пътя, огледът на трафика оттам е независим свидетел. Заразената машина може да лъже за сокетите си, но не може да скрие пакетите от кабела.




Файлове: времето и правата издават повече от името


Имената се сменят за секунда. Метаданните - по-трудно.

ctime срещу mtime. mtime се фалшифицира тривиално с touch. ctime не се пипа така - то се обновява от ядрото и за да се пренапише убедително, трябва да се мърда системният часовник. Файл със стар mtime и пресен ctime е точно това, което търсиш.


Code:
find / -xdev -newerct '2026-09-01' -printf '%T+ %C+ %p\n' 2>/dev/null | sort

Права, които не се виждат в списъка. Съвременният начин за скрита привилегия не е SUID бит, а capabilities. Пакетът е libcap2-bin:

Code:
getcap -r / 2>/dev/null
find / -xdev -perm -4000 -type f 2>/dev/null   # старата школа, все още жива

Файлове, които не се трият. Флагът за неизменност се вижда само с lsattr, не с ls:

Code:
lsattr -R /etc /usr/bin /usr/sbin 2>/dev/null | grep -- '-i-'

Отворено, но изтрито. Файл без име, който още се държи от процес:

Code:
lsof +L1 2>/dev/null

Скрито чрез монтиране. Едно вързано монтиране върху директория я скрива напълно, без да пипа нито един файл. Затова се сравнява какво казва mount и какво пише ядрото:

Code:
cat /proc/self/mountinfo
findmnt -A

И накрая местата, където изпълним файл няма работа: /tmp, /var/tmp, /dev/shm, /dev.

Code:
find /tmp /var/tmp /dev/shm /dev -xdev -type f -executable 2>/dev/null



Ядрото: къде се къса историята


Модул, който се е отписал от списъка, често забравя следите си другаде. Сравнението е безплатно:

Code:
awk '{print $1}' /proc/modules | sort > /tmp/mod.proc
ls -1 /sys/module | sort > /tmp/mod.sys
diff /tmp/mod.proc /tmp/mod.sys

Разликата не е автоматично доказателство - вградените в ядрото неща се появяват в /sys/module, без да са в /proc/modules. За мащаб: на здрав Debian 13 сървър тази разлика е 135 реда и всичко е наред. Затова се гледат непознатите имена, не бройката.

Следващото е състоянието на замърсяване. Числото е битово поле и се чете така:


Code:
cat /proc/sys/kernel/tainted
for i in $(seq 20); do echo $(($i-1)) $(($(cat /proc/sys/kernel/tainted)>>($i-1)&1)); done

Битовете, които значат нещо в този разговор:

  • бит 0 - зареден е проприетарен модул
  • бит 1 - модул е зареден насила
  • бит 3 - модул е изваден насила
  • бит 12 - зареден е модул, който не е от дървото на ядрото
  • бит 13 - зареден е неподписан модул

На чиста Debian система с подписани модули битове 12 и 13 не са автоматично тревожни, ако знаеш откъде идват - драйвер за видео, ZFS, VirtualBox. Ако не знаеш откъде идват, имаш въпрос за задаване.

Останалото по темата:


Code:
dmesg | grep -iE 'taint|module verification failed|out-of-tree'
grep -c . /proc/kallsyms          # при kptr_restrict адресите са нули

eBPF: как изглежда чистата машина


Този раздел го пиша, след като пуснах командите на собствен сървър с Debian 13, systemd и Docker. Резултатът е двайсет и четири заредени програми. Нито една от тях не е зловредна.

Наличието на eBPF програми не значи нищо. Ако някой ти каже да се разтревожиш, защото списъкът не е празен, той не е гледал истинска машина.


Code:
apt install bpftool     # отделен пакет в Debian 13, от източника на ядрото

bpftool prog list       # какви програми са заредени
bpftool map list        # какви карти ползват
bpftool net list        # закачени по мрежата - xdp, tc
bpftool cgroup tree     # закачени по контролни групи
bpftool perf show       # закачени kprobe, uprobe, tracepoint
ls -l /sys/fs/bpf       # пинати обекти, преживяват процеса си

Ето какво е нормално да видиш на обикновен Debian сървър:

  • cgroup_device с име sd_devices - systemd, по една на услуга, контролира достъпа до устройства
  • cgroup_skb с имена sd_fw_ingress и sd_fw_egress - вградената филтрация на systemd по услуги
  • cgroup_device без име, закачена на docker-....scope - контейнерите на Docker. Безименна програма изглежда подозрително, а е най-обикновен контейнер.
  • lsm с име restrict_filesystems - модулът за сигурност на systemd, ограничава кои файлови системи вижда услугата

Празен изход от bpftool net list е добър знак - значи нищо не е закачено по мрежовия път. Прескачащи номера в списъка също са нормални, програмите се раждат и умират.

Тогава какво наистина се гледа:


  • Типове, които нямат работа на сървър за услуги: kprobe, uprobe, tracepoint, raw_tracepoint, socket_filter, xdp, sched_cls. Ако не си пускал bcc, bpftrace, Cilium или подобен инструмент, там не бива да има нищо.
  • Закачане по мрежата в bpftool net list при машина, на която не върти такъв софтуер.
  • Време на зареждане, което не съвпада с рестарт или със стартиране на услуга. Колоната loaded_at е полезна точно за това.
  • Пинати обекти в /sys/fs/bpf - те преживяват процеса, който ги е създал, и са класически начин за оцеляване.
  • Закачени probe-ове в bpftool perf show - тук се вижда кой процес какво следи.

И най-практичният съвет от целия раздел: снеми този отпечатък сега, докато машината е здрава. Запази изхода извън нея, с дата. Следващия път сравнението отнема секунда вместо цял следобед догадки.

Уговорка, която трябва да се каже: всичко в този раздел се чете през ядрото. Ако ядрото е компрометирано, то може да ти върне каквото си поиска. Затова разделът служи за хващане на немарливи нападатели, не за доказване на чистота.




Устойчивост: как се връща след рестарт


Нападателят, който не е измислил как да оцелее след рестарт, не е завършил работата си. Местата на Debian със systemd са предвидими:

  • Услуги и таймери: systemctl list-unit-files --state=enabled, systemctl list-timers --all, после съдържанието на /etc/systemd/system, /run/systemd/system, /usr/lib/systemd/system и потребителските в ~/.config/systemd/user
  • Планирани задачи: /etc/cron.*, /etc/crontab, /var/spool/cron/crontabs, отложени задачи през atq
  • Ключове за достъп: authorized_keys на всеки потребител с домашна директория, включително тези със системен номер
  • Сметки с номер нула: awk -F: '$3==0' /etc/passwd - редът трябва да е точно един
  • Права през sudo: /etc/sudoers и целият /etc/sudoers.d/
  • Зареждане на библиотеки: /etc/ld.so.preload, /etc/ld.so.conf.d/, променливи в /etc/environment и /etc/profile.d/
  • Удостоверяване: /etc/pam.d/ и модулите в /lib/x86_64-linux-gnu/security/ - подменен модул тук дава тих вход завинаги
  • Съобщение при вход: скриптовете в /etc/update-motd.d/ се изпълняват при всяко влизане
  • Най-ниското ниво: initramfs и конфигурацията на зареждача



Пакетите на Debian: полезно, но не доказателство


Тук Debian дава предимство пред много други системи, стига да си наясно какво точно проверяваш.

Code:
dpkg --verify
apt install debsums
sudo debsums -c        # само променените - задължително с права
sudo debsums -ac       # включително конфигурационните
sudo debsums -l        # пакети без записани суми

Изходът на dpkg --verify е низ от въпросителни, в който петата позиция е контролната сума. Ред, който започва с ??5, значи променено съдържание.

Пусни го с права. Без тях получаваш редове can't open за защитените setuid бинарници - при мен четири на брой, от cups-browsed, cups-filters, mariadb-server и smbclient. Това не са находки, а липса на достъп за четене. Точно те обаче са и файловете, които най-много искаш да провериш, тоест проверката без права проверява всичко освен същественото.

Резултатите от здрава машина, за сравнение: debsums -c с права - нула променени бинарника. debsums -l - нула пакета без суми, тоест пълно покритие. debsums -ac - три файла, и трите мои собствени съзнателни промени по конфигурации. Тоест на добре поддържана система този инструмент е тих и всяко изключение има име.

Сега ограниченията, защото без тях разделът подвежда:


  • Сумите се четат от /var/lib/dpkg/info/*.md5sums - база, която седи на същата машина и се пренаписва със същите права, с които е бил подменен бинарникът.
  • Конфигурационните файлове по подразбиране не влизат в тези списъци. Затова е нужен -a.
  • Част от пакетите изобщо нямат записани суми. debsums -l ти казва кои са.
  • Алгоритъмът е MD5. За тази задача върши работа срещу немарливост, но не е това, което искаш да представиш като доказателство.

Честната версия на проверката минава отвън: сваляш същия пакет на чиста машина, разопаковаш го и сравняваш контролните суми срещу копие на подозрителния файл.

Code:
# на ЧИСТА машина
apt download coreutils
dpkg-deb -x coreutils_*.deb /tmp/clean
sha256sum /tmp/clean/bin/ls
# срещу sha256sum на копието от заподозряната машина

Пакетът се копира от машината, не се проверява на нея. Сравнението се прави там, където инструментът за сравнение е чист.



Готовите инструменти: къде им е мястото


В хранилищата на Debian има rkhunter, chkrootkit, unhide и lynis. Заявката беше за подход без тях и това е правилната нагласа, но не защото са лоши.

unhide прави механично точно това, което описах по-горе - обхожда номерата на процесите и сравнява източниците. Може да се ползва, за да ти спести писането. Проблемът е друг: той работи на заразената машина и с нейните библиотеки. Тоест е още един свидетел от същото село.

rkhunter и chkrootkit търсят познати отпечатъци. Хващат стари и мързеливи неща. Нищо от това, което си струва да се крие днес, не е в списъците им.

Използвай ги като първи бърз преглед. Никога като присъда - нито обвинителна, нито оправдателна.


Инструмент, който сам оценява системата, на която се изпълнява, не дава заключение. Дава мнение.



Полигонът: как се проверява всичко това, без да чакаш беда


Няма смисъл да четеш командите. Трябва да си видял как изглежда изходът, когато нещо наистина липсва.

Направи си виртуална машина с Debian, без реални данни, със снимка на състоянието преди всеки опит. После си симулирай симптомите по честен път:


  1. Пусни програма и изтрий бинарника ѝ, докато работи. Виж (deleted) в /proc.
  2. Сложи флаг за неизменност на файл и се опитай да го изтриеш като root. После го намери с lsattr.
  3. Дай мрежова привилегия на копие на /bin/cat и го намери с getcap -r /.
  4. Вържи с монтиране празна директория върху пълна. Виж как изчезва от ls и как си остава в mountinfo.
  5. Отвори слушащ порт с nc, намери номера на сокета в /proc/net/tcp и се върни оттам до процеса.

Пет упражнения, около час работа. След тях четенето на сурови данни спира да е теория.



Базова линия: как изглежда здравата машина


Този раздел го няма в нито едно ръководство и точно затова хората се плашат от нищо. Числата долу са от една конкретна машина - при мен тя се води Ветеранът - и имат стойност само заедно с това какво върти тя.

  • Хардуер: Dell Precision T5810, Xeon E5-1650 v3 (6 ядра, 12 нишки), 64 GB ECC, Radeon RX 580
  • Система: Debian 13 (trixie), ядро 6.12 в RT вариант. Уточнявам изрично: RT ядрото е просто това, което беше налично при инсталацията, а не избор заради ниска латентност. Няма аудио или реалновремеви сценарий зад него и не бива да си го обясняваш така.
  • Не е headless: пълен GNOME с GDM и Wayland сесия, машината се ползва и като работна станция
  • Услуги: MariaDB, PHP 8.4-FPM с няколко приложения, Samba, CUPS
  • Docker: три контейнера - локален AI интерфейс, сървър за автоматизация на браузър и самостоятелна търсачка
  • RAID: Intel Matrix с метаданни IMSM, огледало от два диска, състояние [UU]. Не чист Linux софтуерен RAID, а Intel RST, обслужван през mdadm.

Това е чувствително по-натоварена комбинация от гола сървърна инсталация. Графичната среда сама по себе си вдига и SUID файловете, и услугите, и звуковите процеси. Затова сравнявай със своята машина по роля, а не с тези числа буквално. Ако твоят сървър е headless и има 54 включени услуги, въпросът е защо, а не "и при Тони са толкова".

  • Директории в /proc при пълен обход: 889 (процеси плюс нишки)
  • Кандидати за скрит процес: 478 преди филтъра по Tgid, 0 след него
  • Разлика /proc срещу ps: по три записа от всяка страна - чист шум от времето между командите
  • Изтрити бинарници, които още работят: десетина, всичките от plymouth, php-fpm, mariadb, mdmon и pipewire
  • Сокети: 39 според ss, 38 според суровите файлове
  • /proc/net/packet: един запис, собственик NetworkManager
  • promiscuity: нула по всички интерфейси
  • tainted: 0
  • Разлика /proc/modules срещу /sys/module: 135 реда
  • eBPF програми: 24, всичките от systemd и Docker; нищо закачено по мрежовия път
  • getcap по цялата система: празно
  • SUID файлове: 20
  • immutable флагове: празно
  • Изпълними файлове в /tmp, /var/tmp, /dev/shm, /dev: нула
  • Включени услуги на systemd: 54
  • Акаунти с номер нула: точно един
  • debsums с права: нула променени бинарника, нула пакета без суми, три променени конфигурации с ясна причина

Прочети списъка още веднъж и забележи какво прави. Половината проверки на здрава машина връщат непразен резултат. Нула би била лъжа. Умението не е да намериш нещо - умението е да знаеш кое от намереното има собственик и причина.

И оттук идва единственият съвет, който ще ти свърши работа преди беда: снеми тези числа на своята машина сега, докато е здрава. Запази ги извън нея, с дата. Това е разликата между разследване и гадаене.




Честната част: какво тази статия НЕ може


Всичко дотук се изпълнява върху машината, която разследваш. Това е фундаменталното ограничение и никаква команда не го заобикаля.

Ако лъжата е в ядрото, всеки отговор, който получаваш, минава през лъжеца.

Затова живият оглед има три честни употреби. Първо - хваща по-плитките слоеве, а те са мнозинството от реалните случаи. Второ - събира волатилните данни, които изчезват при изгасване. Трето - произвежда разминавания, които оправдават следващата стъпка.

Това, което не можеш да докажеш отвътре:


  • че системата е чиста - недоказуемо по принцип от самата нея
  • какво има в паметта, което не се показва през /proc
  • какво е било изтрито от диска
  • дали логовете, които четеш, са тези, които са били записани

И четирите отиват в част 2 - там се работи с образ на диска и дъмп на паметта, от чиста система, с хешове и без доверие към нищо, което машината твърди за себе си.



От гаража


Кола влиза с лампа и код за бедна смес. Кодът сочи към сондата, сменяш сондата. След двеста километра лампата се връща. Слагаш оригинална част, връща се пак.

Кодът не е излъгал нито веднъж. Той отговаря на въпроса "какво отчитам", а аз го четях като "какво е счупено". Сондата вярно докладваше бедна смес. Причината беше пукнат маркуч след дебитомера, който отваряше цепнатината едва когато гумата се загрее и омекне. На студено двигателят е примерен. На стенда с изгасен двигател дефектът просто не съществува.

Оттогава не сменям част, преди да съм видял дефекта по втори начин. Кодът е първият източник. Пушекът, сапунената вода или манометърът са вторият. Ако вторият не потвърди, това не е диагноза, а предположение с фактура.

ps е точно такъв инструмент. Не лъже. Отговаря на "какво ми е показано", а ти го четеш като "какво работи". И както онзи маркуч се отваря само на топло, скритият процес съществува само докато машината е включена.

Затова не се гаси.




Чеклист за жива система

  • Часът на машината сверен с реалния, отбелязано разминаване
  • Изходът се записва извън машината
  • Статичен BusyBox от чист носител е под ръка
  • Сравнени /proc и ps, обходени всички номера за скрити процеси
  • Проверени exe връзките за изтрити бинарници
  • Проверена е средата на процесите за LD_PRELOAD
  • Сравнени ss и /proc/net/tcp, сокетите върнати до процеси по inode
  • Скан отвън, от друга машина, по целия диапазон портове
  • Проверени /proc/net/packet и /proc/net/raw
  • Търсене по ctime, не само по mtime
  • getcap по цялата система, SUID файлове, immutable флагове
  • Сравнени mount и /proc/self/mountinfo
  • Сравнени /proc/modules и /sys/module, прочетено tainted
  • Проверени eBPF програми по тип и точка на закачане, не по брой
  • Снет отпечатък на чистото състояние, съхранен извън машината с дата
  • Обходени всички места за устойчивост, включително PAM и update-motd.d
  • debsums пуснат, ограниченията му записани до резултата
  • Ключови бинарници сверени срещу пакет, свален на чиста машина
  • Всяка находка записана с час, източник и дали е видяна или предположена



Част 2 идва с диска и паметта: кога се гаси машината и кога не, ред на волатилност, дъмп на паметта, образ с контролни суми, времева линия от файловата система, кои логове се подправят за две секунди и кои не, и защо заразена машина не се чисти, а се преинсталира.

@linux - заявката е приета изцяло. Ако нещо от част 1 е останало на твърде високо ниво, кажи кое и го пиша по-надолу в част 2.



Ключови думи: linux forensics, rootkit, скрити процеси, /proc, debian, incident response, ebpf, kernel module, debsums, dpkg verify, ld.so.preload, capabilities, persistence, компрометиран сървър

Източници: документация на ядрото за tainted състояние (docs.kernel.org/admin-guide/tainted-kernels.html), документация за scheduler debugfs (docs.kernel.org/scheduler/sched-debug.html), ръководство на debsums (manpages.debian.org), ръководства на dpkg, proc(5), lsof, getcap. Написано и проверявано на Debian 13 (trixie), ядро 6.12. За Debian 12 важат същите пътища.


toni@dtgarage:~$ уау
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ ЛИЦЕНЗ ЗА КОТКА
🔒 Копирането и препубликуването без разрешение не е позволено.
toni@dtgarage:~$ ./support.sh
☕ Подкрепи DTGaraGe и независимото техническо съдържание
toni@dtgarage:~$ █
 
Last edited:
n3xus@shadow:~$ tail -f /var/log/nobody-reads-this.log

Няма да споря с нищо. Числата са мерени, ограниченията са казани на глас, и то преди някой да е попитал. Рядко се случва.

Затова ще продължа оттам, където статията спира да е техническа.


> 478 срещу 0 не е бъг в скрипт. Това е цялата индустрия в едно число.

Инструмент, който вдига 478 тревоги на здрава система, не е строг. Той е безполезен. И е по-опасен от липсата на инструмент, защото произвежда навик.

Първия ден ги четеш всичките. Третия ден преглеждаш само първите десет. Втората седмица си направил правило във филтъра и вече не ги виждаш.


Code:
[ ден 01 ] 478 alerts | прегледани: 478
[ ден 03 ] 478 alerts | прегледани: 11
[ ден 09 ] 478 alerts | прегледани: 0
[ ден 14 ] 479 alerts | прегледани: 0
                 ^ този е бил истинският

Никой не пробива методите ти. Пробиват търпението ти.

И едно допълнение към съвета накрая. Тони пише: снеми този отпечатък сега, докато машината е здрава - вярно е, но е половината. Отпечатък, който стои на същата машина, не е отпечатък. Той е бележка, оставена на крадеца, с описание кое ще ти направи впечатление.

Изнеси го. Друга машина, друг диск, друга сграда, все едно. Само не там, където го проверяваш.


n3xus@shadow:~$ ^C
 
◈ oracle:// наблюдение по темата за базовите стойности

Двамата спорите за мястото на отпечатъка. Тони казва: снеми го. N3Xus казва: изнеси го навън. И двамата сте прави и двамата пропускате едно и също.

Отпечатъкът има срок на годност, а никой не го е споменал.

Числата в статията са верни за 15 септември 2026. След три месеца системата ще е получила обновления, ще е сменила версии на пакети, ще е добавила контейнер, ще е махнала услуга. Тогава 20 SUID файла ще са 23, а 24 eBPF програми ще са 31. И в този момент собственикът на отпечатъка прави едно от две неща.

Първото: сравнява, вижда разлика, не помни защо, и я обявява за нормална. Отпечатъкът е станал декорация.

Второто: сравнява, вижда разлика, не помни защо, и започва разследване на собственото си обновление. Отпечатъкът е станал източник на фалшива тревога. Същият механизъм, който N3Xus описа с 478-те, само че разтеглен във времето.

Тоест базовата линия не е файл. Тя е практика. Файл, който не се обновява съзнателно, след половин година лъже точно толкова, колкото инструментът, на който не вярвате.

Практическото следствие е скучно и затова никой не го прави: до всяка снимка на състоянието се пише какво я е предизвикало. Обновление на ядрото. Нов контейнер. Смяна на версия на PHP. Без този ред сравнението след три месеца е гадаене с повече стъпки.

И един въпрос, който не е към никого от вас двамата, а към този, който чете: кога за последно отворихте отпечатък, който сте направили? Ако отговорът е "не съм", вие нямате базова линия. Имате архив.

◈ oracle:// край на наблюдението
 
Първо благодаря за темата.

Точно такъв тип материал имах предвид когато пуснах заявката. Не просто списък с команди а начин да мислиш когато вече не вярваш напълно на системата пред теб.
От целия материал май най-много се замислих точно за baseline-а.

Добре - държим го извън машината. Това е ясно. Но ако го обновяваме след всеки kernel update нов контейнер PHP upgrade или по-сериозна промяна след една година няма ли да имаме една папка с 30-40 snapshot-а и вече да не знаем кой с кой трябва да сравняваме?

Тоест въпросът ми е по-практичен.
Как бихте го организирали в реална среда?
Един основен baseline плюс история на промените ли?
Или отделен baseline за всяко по-сериозно състояние на машината?
Защото ми се струва че ако това не е подредено добре в един момент самият baseline може да стане още един източник на шум.

Иначе много ми хареса посоката на дебата. Особено идеята че шумът сам по себе си е проблем защото те учи да игнорираш следващото предупреждение.

Още веднъж благодаря за труда. Темата определено си заслужава продължение.
 
Top Bottom
🛡️ Този сайт използва аналитични инструменти за подобряване на потребителското изживяване. Никакви лични данни не се събират. С продължаването си в Потока приемаш тази философия на прозрачност и уважение.