1+ using Moonlight . App . ApiClients . Daemon . Resources ;
2+ using Moonlight . App . Database . Entities ;
3+ using Moonlight . App . Events ;
4+ using Moonlight . App . Exceptions ;
5+ using Moonlight . App . Helpers ;
6+ using Moonlight . App . Models . Misc ;
7+ using Moonlight . App . Repositories ;
8+
9+ namespace Moonlight . App . Services . Background ;
10+
11+ public class MalwareScanService
12+ {
13+ private Repository < Server > ServerRepository ;
14+ private Repository < Node > NodeRepository ;
15+ private NodeService NodeService ;
16+ private ServerService ServerService ;
17+
18+ private readonly EventSystem Event ;
19+ private readonly IServiceScopeFactory ServiceScopeFactory ;
20+
21+ public bool IsRunning { get ; private set ; }
22+ public readonly Dictionary < Server , MalwareScanResult [ ] > ScanResults ;
23+ public string Status { get ; private set ; } = "N/A" ;
24+
25+ public MalwareScanService ( IServiceScopeFactory serviceScopeFactory , EventSystem eventSystem )
26+ {
27+ ServiceScopeFactory = serviceScopeFactory ;
28+ Event = eventSystem ;
29+
30+ ScanResults = new ( ) ;
31+ }
32+
33+ public Task Start ( )
34+ {
35+ if ( IsRunning )
36+ throw new DisplayException ( "Malware scan is already running" ) ;
37+
38+ Task . Run ( Run ) ;
39+
40+ return Task . CompletedTask ;
41+ }
42+
43+ private async Task Run ( )
44+ {
45+ IsRunning = true ;
46+ Status = "Clearing last results" ;
47+ await Event . Emit ( "malwareScan.status" , IsRunning ) ;
48+
49+ lock ( ScanResults )
50+ {
51+ ScanResults . Clear ( ) ;
52+ }
53+
54+ await Event . Emit ( "malwareScan.result" ) ;
55+
56+ using var scope = ServiceScopeFactory . CreateScope ( ) ;
57+
58+ // Load services from di scope
59+ NodeRepository = scope . ServiceProvider . GetRequiredService < Repository < Node > > ( ) ;
60+ ServerRepository = scope . ServiceProvider . GetRequiredService < Repository < Server > > ( ) ;
61+ NodeService = scope . ServiceProvider . GetRequiredService < NodeService > ( ) ;
62+ ServerService = scope . ServiceProvider . GetRequiredService < ServerService > ( ) ;
63+
64+ var nodes = NodeRepository . Get ( ) . ToArray ( ) ;
65+ var containers = new List < Container > ( ) ;
66+
67+ // Fetch and summarize all running containers from all nodes
68+ Logger . Verbose ( "Fetching and summarizing all running containers from all nodes" ) ;
69+
70+ Status = "Fetching and summarizing all running containers from all nodes" ;
71+ await Event . Emit ( "malwareScan.status" , IsRunning ) ;
72+
73+ foreach ( var node in nodes )
74+ {
75+ var metrics = await NodeService . GetDockerMetrics ( node ) ;
76+
77+ foreach ( var container in metrics . Containers )
78+ {
79+ containers . Add ( container ) ;
80+ }
81+ }
82+
83+ var containerServerMapped = new Dictionary < Server , Container > ( ) ;
84+
85+ // Map all the containers to their corresponding server if existing
86+ Logger . Verbose ( "Mapping all the containers to their corresponding server if existing" ) ;
87+
88+ Status = "Mapping all the containers to their corresponding server if existing" ;
89+ await Event . Emit ( "malwareScan.status" , IsRunning ) ;
90+
91+ foreach ( var container in containers )
92+ {
93+ if ( Guid . TryParse ( container . Name , out Guid uuid ) )
94+ {
95+ var server = ServerRepository
96+ . Get ( )
97+ . FirstOrDefault ( x => x . Uuid == uuid ) ;
98+
99+ if ( server == null )
100+ continue ;
101+
102+ containerServerMapped . Add ( server , container ) ;
103+ }
104+ }
105+
106+ // Perform scan
107+ var resultsMapped = new Dictionary < Server , MalwareScanResult [ ] > ( ) ;
108+ foreach ( var mapping in containerServerMapped )
109+ {
110+ Logger . Verbose ( $ "Scanning server { mapping . Key . Name } for malware") ;
111+
112+ Status = $ "Scanning server { mapping . Key . Name } for malware";
113+ await Event . Emit ( "malwareScan.status" , IsRunning ) ;
114+
115+ var results = await PerformScanOnServer ( mapping . Key , mapping . Value ) ;
116+
117+ if ( results . Any ( ) )
118+ {
119+ resultsMapped . Add ( mapping . Key , results ) ;
120+ Logger . Verbose ( $ "{ results . Length } findings on server { mapping . Key . Name } ") ;
121+ }
122+ }
123+
124+ Logger . Verbose ( $ "Scan complete. Detected { resultsMapped . Count } servers with findings") ;
125+
126+ IsRunning = false ;
127+ Status = $ "Scan complete. Detected { resultsMapped . Count } servers with findings";
128+ await Event . Emit ( "malwareScan.status" , IsRunning ) ;
129+
130+ lock ( ScanResults )
131+ {
132+ foreach ( var mapping in resultsMapped )
133+ {
134+ ScanResults . Add ( mapping . Key , mapping . Value ) ;
135+ }
136+ }
137+
138+ await Event . Emit ( "malwareScan.result" ) ;
139+ }
140+
141+ private async Task < MalwareScanResult [ ] > PerformScanOnServer ( Server server , Container container )
142+ {
143+ var results = new List < MalwareScanResult > ( ) ;
144+
145+ // TODO: Move scans to an universal format / api
146+
147+ // Define scans here
148+
149+ async Task ScanSelfBot ( )
150+ {
151+ var access = await ServerService . CreateFileAccess ( server , null ! ) ;
152+ var fileElements = await access . Ls ( ) ;
153+
154+ if ( fileElements . Any ( x => x . Name == "tokens.txt" ) )
155+ {
156+ results . Add ( new ( )
157+ {
158+ Title = "Found SelfBot" ,
159+ Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot" ,
160+ Author = "Marcel Baumgartner"
161+ } ) ;
162+ }
163+ }
164+
165+ async Task ScanFakePlayerPlugins ( )
166+ {
167+ var access = await ServerService . CreateFileAccess ( server , null ! ) ;
168+ var fileElements = await access . Ls ( ) ;
169+
170+ if ( fileElements . Any ( x => ! x . IsFile && x . Name == "plugins" ) ) // Check for plugins folder
171+ {
172+ await access . Cd ( "plugins" ) ;
173+ fileElements = await access . Ls ( ) ;
174+
175+ foreach ( var fileElement in fileElements )
176+ {
177+ if ( fileElement . Name . ToLower ( ) . Contains ( "fake" ) )
178+ {
179+ results . Add ( new ( )
180+ {
181+ Title = "Fake player plugin" ,
182+ Description = $ "Suspicious plugin file: { fileElement . Name } ",
183+ Author = "Marcel Baumgartner"
184+ } ) ;
185+ }
186+ }
187+ }
188+ }
189+
190+ // Execute scans
191+ await ScanSelfBot ( ) ;
192+ await ScanFakePlayerPlugins ( ) ;
193+
194+ return results . ToArray ( ) ;
195+ }
196+ }
0 commit comments