-
Notifications
You must be signed in to change notification settings - Fork 270
/
ServiceCollector.cs
259 lines (227 loc) · 9.81 KB
/
ServiceCollector.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using AttackSurfaceAnalyzer.ObjectTypes;
using AttackSurfaceAnalyzer.Utils;
using Microsoft.Data.Sqlite;
using Newtonsoft.Json;
using Serilog;
using System.Text.RegularExpressions;
namespace AttackSurfaceAnalyzer.Collectors.Service
{
/// <summary>
/// Collects metadata from the local file system.
/// </summary>
public class ServiceCollector : BaseCollector
{
/// <summary>
/// A filter supplied to this function. All files must pass this filter in order to be included.
/// </summary>
//private static readonly string CREATE_SQL = "create table if not exists win_system_service (run_id text, row_key text, service_name text, display_name text, start_type text, current_state text)";
private static readonly string SQL_TRUNCATE = "delete from win_system_service where run_id = @run_id";
private static readonly string INSERT_SQL = "insert into win_system_service (run_id, row_key, service_name, display_name, start_type, current_state, serialized) values (@run_id, @row_key, @service_name, @display_name, @start_type, @current_state, @serialized)";
public ServiceCollector(string runId)
{
this.runId = runId;
}
/// <summary>
/// Determines whether the ServiceCollector can run or not.
/// </summary>
/// <returns>True iff the operating system is Windows.</returns>
public override bool CanRunOnPlatform()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
}
/// <summary>
/// Writes information about a single service to the database.
/// </summary>
/// <param name="obj">A ServiceObject to write.</param>
public void Write(ServiceObject obj)
{
_numCollected++;
var cmd = new SqliteCommand(INSERT_SQL, DatabaseManager.Connection, DatabaseManager.Transaction);
cmd.Parameters.AddWithValue("@run_id", this.runId);
cmd.Parameters.AddWithValue("@row_key", obj.GetUniqueHash());
cmd.Parameters.AddWithValue("@service_name", obj.ServiceName);
cmd.Parameters.AddWithValue("@display_name", obj.DisplayName);
cmd.Parameters.AddWithValue("@start_type", obj.StartType);
cmd.Parameters.AddWithValue("@current_state", obj.CurrentState);
cmd.Parameters.AddWithValue("@serialized", JsonConvert.SerializeObject(obj));
cmd.ExecuteNonQuery();
}
public void Truncate(string runid)
{
var cmd = new SqliteCommand(SQL_TRUNCATE, DatabaseManager.Connection, DatabaseManager.Transaction);
cmd.Parameters.AddWithValue("@run_id", runId);
}
/// <summary>
/// Executes the ServiceCollector (main entrypoint).
/// </summary>
public override void Execute()
{
Start();
if (!this.CanRunOnPlatform())
{
Log.Information(Strings.Get("Err_ServiceCollectorIncompat"));
return;
}
Truncate(runId);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// This gathers official "services" on Windows, but perhaps neglects other startup items
foreach (ServiceController service in ServiceController.GetServices())
{
var obj = new ServiceObject()
{
DisplayName = service.DisplayName,
ServiceName = service.ServiceName,
StartType = service.StartType.ToString(),
CurrentState = service.Status.ToString()
};
this.Write(obj);
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var runner = new ExternalCommandRunner();
// Get the user processes
// run "launchtl dumpstate" for the super detailed view
// However, dumpstate is difficult to parse
var result = runner.RunExternalCommand("launchctl", "list");
Dictionary<string, ServiceObject> outDict = new Dictionary<string, ServiceObject>();
foreach (var _line in result.Split('\n'))
{
// Lines are formatted like this:
// PID Exit Name
//1015 0 com.apple.appstoreagent
var _fields = _line.Split('\t');
if (_fields.Length < 3 || _fields[0].Contains("PID"))
{
continue;
}
var obj = new ServiceObject()
{
DisplayName = _fields[2],
ServiceName = _fields[2],
StartType = "Unknown",
// If we have a current PID then it is running.
CurrentState = (_fields[0].Equals("-"))?"Stopped":"Running"
};
if (!outDict.ContainsKey(obj.GetUniqueHash())){
this.Write(obj);
outDict.Add(obj.GetUniqueHash(), obj);
}
}
// Then get the system processes
result = runner.RunExternalCommand("sudo", "launchctl list");
foreach (var _line in result.Split('\n'))
{
// Lines are formatted like this, with single tab separation:
// PID Exit Name
// 1015 0 com.apple.appstoreagent
var _fields = _line.Split('\t');
if (_fields.Length < 3 || _fields[0].Contains("PID"))
{
continue;
}
var obj = new ServiceObject()
{
DisplayName = _fields[2],
ServiceName = _fields[2],
StartType = "Unknown",
// If we have a current PID then it is running.
CurrentState = (_fields[0].Equals("-")) ? "Stopped" : "Running"
};
if (!outDict.ContainsKey(obj.GetUniqueHash()))
{
this.Write(obj);
outDict.Add(obj.GetUniqueHash(), obj);
}
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var runner = new ExternalCommandRunner();
var result = runner.RunExternalCommand("systemctl", "list-units --type service");
//Split lines and remove header
var lines = result.Split('\n').Skip(1);
foreach (var _line in lines)
{
var _fields = _line.Split('\t');
if (_fields.Count() == 5)
{
var obj = new ServiceObject()
{
DisplayName = _fields[4],
ServiceName = _fields[0],
StartType = "Unknown",
CurrentState = _fields[3],
};
Write(obj);
}
}
result = runner.RunExternalCommand("ls", "/etc/init.d/ -l");
lines = result.Split('\n').Skip(1);
String pattern = @".*\s(.*)";
foreach (var _line in lines)
{
Match match = Regex.Match(_line, pattern);
GroupCollection groups = match.Groups;
var serviceName = groups[1].ToString();
var obj = new ServiceObject()
{
DisplayName = serviceName,
ServiceName = serviceName,
StartType = "Unknown",
CurrentState = "Unknown"
};
Write(obj);
}
// without systemd (maybe just CentOS)
// chkconfig --list
// BSD
// service -l
// this provides very minor amount of info
}
Stop();
}
private string underscoreToCamelCase(string name)
{
if (string.IsNullOrEmpty(name) || !name.Contains("_"))
{
return name;
}
string[] array = name.Split('_');
for (int i = 0; i < array.Length; i++)
{
string s = array[i];
string first = string.Empty;
string rest = string.Empty;
if (s.Length > 0)
{
first = Char.ToUpperInvariant(s[0]).ToString();
}
if (s.Length > 1)
{
rest = s.Substring(1).ToLowerInvariant();
}
array[i] = first + rest;
}
string newname = string.Join("", array);
if (newname.Length > 0)
{
newname = Char.ToUpperInvariant(newname[0]) + newname.Substring(1);
}
else
{
newname = name;
}
return newname;
}
}
}