-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSystemCallService.cs
More file actions
2330 lines (2005 loc) · 94.5 KB
/
SystemCallService.cs
File metadata and controls
2330 lines (2005 loc) · 94.5 KB
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright 2015 Ray Canzanese
* email: rcanzanese@gmail.com
* url: www.canzanese.com
*
* This file is part of SystemCallService.
*
* SystemCallService is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* SystemCallService is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with SystemCallService. If not, see <http://www.gnu.org/licenses/>.
*/
// TODO items
// - break more functions out of the main class
// - figure out if there is a easy way to incorporate symbol lookup
// NOTE: You must manually set / unset this variable for 32 / 64 bit builds
//#define X86
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SQLite;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.ServiceProcess;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Timers;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Session;
namespace SystemCallService
{
/// <summary>
/// Class for the service, extends the base service class. No actual functionality is here other than handling requests.
/// </summary>
public partial class SystemCallService : ServiceBase
{
private Thread _thread; // Worker thread for running the service
private SystemCallServiceLoop _scc; // System call counter object
private bool _isRunning; // Keeps track of whether it is running to handle power events
/// <summary>
/// Initialization and establishes that the SCS handles power events
/// </summary>
public SystemCallService()
{
InitializeComponent();
CanHandlePowerEvent = true;
}
/// <summary>
/// Handles hibernation events.
/// </summary>
/// <param name="powerStatus">Powerstatus passed in by SC</param>
/// <returns>true</returns>
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
{
EventLog.WriteEntry("Power Status Change -- " + powerStatus, EventLogEntryType.Warning);
if (PowerBroadcastStatus.Suspend == powerStatus)
{
if (_isRunning)
{
EventLog.WriteEntry("Stopping for suspend.", EventLogEntryType.Warning);
OnStop();
}
else
{
EventLog.WriteEntry("Tried suspending but it was already stopped.", EventLogEntryType.Warning);
}
}
// Start on resume.
else if (PowerBroadcastStatus.ResumeSuspend == powerStatus ||
PowerBroadcastStatus.ResumeAutomatic == powerStatus)
{
if (_isRunning)
{
EventLog.WriteEntry("Tried resuming, but already running.", EventLogEntryType.Warning);
}
else
{
EventLog.WriteEntry("Resuming.", EventLogEntryType.Warning);
OnStart(null);
}
}
return true;
}
/// <summary>
/// Called by SC when the service is started. Must return immediately.
/// </summary>
/// <param name="args">Passed by SC</param>
protected override void OnStart(string[] args)
{
_isRunning = true;
// Sets the event log source so we can find us the in the application log
EventLog.Source = "SystemCallService";
// Starts the worker thread that actually performs all the useful tasks
_thread = new Thread(WorkerThreadFunc)
{
Name = "Main SystemCallService Worker Thread",
IsBackground = true
};
_thread.Start();
// Prints the path to the local configuration to the app event log.
EventLog.WriteEntry(
"Local Configuration Location:" +
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath,
EventLogEntryType.Information);
}
/// <summary>
/// Called by SC when service is stopped
/// </summary>
protected override void OnStop()
{
_isRunning = false;
// Signal the thread to stop collecting data
_scc.Stop(true);
// Give the thread 10 seconds to stop gracefully. It does a lot of processing, so we are conservative with this etimate for now.
if (_thread.Join(10000)) return;
EventLog.WriteEntry("Internal error Stopping Service", EventLogEntryType.Error);
_thread.Abort();
}
/// <summary>
/// Worker thread responsible for kicking off the data collection. Exits when the data collection is signalled by OnStop to stop.
/// </summary>
private void WorkerThreadFunc()
{
// Create a new SyscallCounter object with sampling periods from settings
_scc = new SystemCallServiceLoop(EventLog);
// Continuously restart the service until IsRunning is set to false
while (_isRunning)
{
_scc.Start();
}
}
}
/// <summary>
/// Put host, process, and thread metadata in a SQLite database
/// </summary>
public class DatabaseInterface
{
//EventLog object for logging to the Application Event Log
private readonly StreamWriter _debugLog;
// Database connection
private readonly SQLiteConnection _connection;
// Everything is in one transaction
private SQLiteTransaction _transaction;
// Queries for adding processes and threads and a list of the column titles to expedite the query building.
private readonly SQLiteCommand _processAdd;
private readonly List<string> _processColumns;
private readonly SQLiteCommand _threadAdd;
private readonly List<string> _threadColumns;
private readonly SQLiteCommand _processEnd;
private readonly SQLiteCommand _threadEnd;
/// <summary>
/// Constructor
/// </summary>
/// <param name="path">Absolute path to the database file</param>
/// <param name="debugLog">Handle to debug log</param>
public DatabaseInterface(string path, StreamWriter debugLog)
{
_debugLog = debugLog;
// Create file
SQLiteConnection.CreateFile(path);
//Connect and open handle
_connection = new SQLiteConnection("Data Source=" + path + ";Version=3;");
_connection.Open();
_transaction = _connection.BeginTransaction();
// Create table for processes
using (SQLiteCommand genericCommand = _connection.CreateCommand())
{
// Create process Table
genericCommand.CommandText = "CREATE TABLE Processes (" +
Properties.SystemCallServiceSettings.Default.ProcessTableStructure + ")";
genericCommand.ExecuteNonQuery();
_processAdd = SetupObjectQuery(typeof (ProcessInfo), "Processes", out _processColumns);
// Create table for threads
genericCommand.CommandText = "CREATE TABLE Threads (" +
Properties.SystemCallServiceSettings.Default.ThreadTableStructure + ")";
genericCommand.ExecuteNonQuery();
genericCommand.Dispose();
}
// Create command for thread and process endings
_threadEnd = _connection.CreateCommand();
_threadEnd.CommandText = "UPDATE threads SET StopRelativeMsec=@StopRelativeMsec WHERE Guid=@Guid";
_threadEnd.Parameters.AddWithValue("@StopRelativeMsec", "");
_threadEnd.Parameters.AddWithValue("@Guid", "");
_processEnd = _connection.CreateCommand();
_processEnd.CommandText = "UPDATE processes SET StopRelativeMsec=@StopRelativeMsec WHERE Guid=@Guid";
_processEnd.Parameters.AddWithValue("@StopRelativeMsec", "");
_processEnd.Parameters.AddWithValue("@Guid", "");
_threadAdd = SetupObjectQuery(typeof (ThreadInfo), "Threads", out _threadColumns);
// Commit all the table creations and starts a new transaction
_transaction.Commit();
_transaction.Dispose();
_transaction = _connection.BeginTransaction();
}
/// <summary>
/// Update the thread end time.
/// </summary>
/// <param name="mSec">end time in ms</param>
/// <param name="guid">thread guid</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateThreadEnd(double mSec, string guid)
{
_threadEnd.Parameters["@StopRelativeMsec"].Value = mSec;
_threadEnd.Parameters["@Guid"].Value = guid;
_threadEnd.ExecuteNonQuery();
}
/// <summary>
/// Update the process end time.
/// </summary>
/// <param name="mSec">end time in ms</param>
/// <param name="guid">process guid</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateProcessEnd(double mSec, string guid)
{
_processEnd.Parameters["@StopRelativeMsec"].Value = mSec;
_processEnd.Parameters["@Guid"].Value = guid;
_processEnd.ExecuteNonQuery();
}
/// <summary>
/// Construct a query to use for adding properties of an object to the database.
/// </summary>
/// <param name="type">The object type that will be added</param>
/// <param name="table">The name of the table</param>
/// <param name="columns">Output the names of the columns so we don't have to look them up every time.</param>
/// <returns>The sqlite command </returns>
public SQLiteCommand SetupObjectQuery(Type type, string table, out List<string> columns)
{
columns = new List<String>();
foreach (var field in type.GetProperties())
{
if (!field.Name.Contains("Flag") && !field.Name.Contains("InsertCommand"))
// I have flags in the data structures that are only used internally and not added to database
{
columns.Add(field.Name);
}
}
SQLiteCommand command = _connection.CreateCommand();
command.CommandText = "INSERT INTO " + table + "(" + String.Join(",", columns) + ") VALUES(@" +
String.Join(",@", columns) + ")";
foreach (var column in columns)
{
command.Parameters.AddWithValue("@" + column, "");
}
return command;
}
/// <summary>
/// Add the properties of an object as a new entry into the table.
/// </summary>
/// <param name="data">The object containing data to add</param>
/// <param name="table">The name of the table</param>
/// <param name="command">The command prepared by SetupObjectQuery</param>
/// <param name="columns">The name of the columns prepared by SetupObjectQuery</param>
/// <returns>True</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AddObject(Object data, string table, SQLiteCommand command, List<string> columns)
{
foreach (var column in columns)
{
command.Parameters["@" + column].Value = data.GetType().GetProperty(column).GetValue(data, null);
}
command.ExecuteNonQuery();
return true;
}
/// <summary>
/// Add a thread to the thread table.
/// </summary>
/// <param name="thread"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AddThread(ThreadInfo thread)
{
AddObject(thread, "Threads", _threadAdd, _threadColumns);
return true;
}
/// <summary>
/// Add a process to the process table.
/// </summary>
/// <param name="process"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AddProcess(ProcessInfo process)
{
AddObject(process, "Processes", _processAdd, _processColumns);
return true;
}
/// <summary>
/// Add system information
/// </summary>
/// <param name="sysinfo">System information structure</param>
/// <returns></returns>
public bool AddSystemInformation(SystemInformation sysinfo)
{
using (var localCommand = _connection.CreateCommand())
{
//Create table
localCommand.CommandText = "CREATE TABLE SystemInfo (" +
Properties.SystemCallServiceSettings.Default.SystemInfoTableStructure + ")";
localCommand.ExecuteNonQuery();
// Setup Query
List<string> systemInfoColumns;
SQLiteCommand systemInfoAdd = SetupObjectQuery(typeof (SystemInformation), "SystemInfo",
out systemInfoColumns);
// Execute query
AddObject(sysinfo, "SystemInfo", systemInfoAdd, systemInfoColumns);
}
return true;
}
/// <summary>
/// Commit the current transaction and create a new one.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Commit()
{
try
{
_transaction.Commit();
_transaction.Dispose();
_transaction = _connection.BeginTransaction();
}
catch (Exception e)
{
_debugLog.WriteLine("Commit error: " + e);
}
}
/// <summary>
/// Commit transaction, dispose all commands, and close the connection to the database
/// </summary>
public void CloseConnection()
{
Commit();
_processAdd.Dispose();
_threadAdd.Dispose();
_processEnd.Dispose();
_threadEnd.Dispose();
try
{
_transaction.Dispose();
}
catch (Exception e)
{
_debugLog.WriteLine("CloseError: " + e);
}
try
{
_connection.Close();
_connection.Dispose();
}
catch (Exception e)
{
_debugLog.WriteLine("CloseError: " + e);
}
}
}
/// <summary>
/// Generic class for storing information about a process or thread
/// </summary>
public class Info
{
public String Guid { get; set; } // GUID to identify the data table.
public double DeletionFlag; // Indicates when object was flagged for deletion
public bool Deleted; // Indicates that that it has been deleted
public double StartRelativeMSec { get; set; } // Time the process started as RelativeMSec count
public DateTime Start { get; set; } // Time the process was started
public double StopRelativeMSec { get; set; } // Time the process started as RelativeMSec count
public SystemCallTracer Counter; // Where the tracing and counting occurs
}
/// <summary>
/// Information about active processes
/// </summary>
public class ProcessInfo : Info
{
public string Name; // Just the executable name
public int Pid { get; set; }
public long ProcessKey; // I don't know whether this is unique -- currently unused
public string CommandLine { get; set; } // Full path used to launch the process
public string ImageFileName { get; set; } // Same as executable name?
public string KernelImageFileName; // Same as executable name?
public int ParentId { get; set; } // Parent PID
public uint TotalThreads; // Total number of child threads
public uint ActiveThreads; // Number of threads currently executing
public double LastActive; // Time it was last CSWITCHED off a core.
public string Md5 { get; set; }
}
/// <summary>
/// Information about the host
/// </summary>
public class SystemInformation
{
public string NtoskrnlVersion { get; set; } // ntoskrnl.exe file version
public string NtoskrnlMd5 { get; set; } // ntoskrnl.exe file version
public int Cores { get; set; } // Number of logical processor cores
public string Hostname { get; set; } // hostname
public double HostSampling { get; set; } // System sampling period
public double ProcessSampling { get; set; } // Process sampling period
public double ThreadSampling { get; set; } // Thread Sampling period
public long HostTrace { get; set; } // System sampling period
public long ProcessTrace { get; set; } // Process sampling period
public long ThreadTrace { get; set; } // Thread Sampling period
}
/// <summary>
/// Information about active threads
/// </summary>
public class ThreadInfo : Info
{
public int Pid { get; set; } // PID to which the thread belongs
public double PidStartRelativeMSec { get; set; } // Process start time as RelativeMSec
public int Tid { get; set; }
public String ProcessGuid { get; set; } // Process GUID so we can do joins
public double LastActive; // Last time it was CSWITCHed out
public bool IsActive; // Whether it is currently active on a core
}
/// <summary>
/// Perform system call traces at the host, process, and thread levels
/// </summary>
public class SystemCallTracer
{
private readonly string _filename;
// TODO: Implement these or remove them.
private double _startTimeMs;
private StreamWriter _debugLog;
// For saving raw traces
private long _maxTrace = -1;
private FileStream _traceOutputFile;
private BinaryWriter _traceWriter;
private bool _fullTrace;
private long _traceLength;
private bool _traceFinished = true;
/// <summary>
/// Create the tracer.
/// </summary>
/// <param name="filename">Filename to store the trace</param>
/// <param name="debugLog">Debug logger</param>
public SystemCallTracer(string filename, StreamWriter debugLog)
{
_filename = filename;
_debugLog = debugLog;
}
/// <summary>
/// Initialize a trace
/// </summary>
/// <param name="maxTrace">Maximum number of system calls. Negative number indicates no limit.</param>
/// <param name="timeMs">Start time of trace in ms.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void InitializeFullTrace(long maxTrace, double timeMs)
{
_traceOutputFile = File.Open(_filename + "_trace", FileMode.Create);
_traceWriter = new BinaryWriter(_traceOutputFile);
_maxTrace = maxTrace;
_traceFinished = false;
_fullTrace = true;
_startTimeMs = timeMs;
}
/// <summary>
/// Add a system call to the trace
/// </summary>
/// <param name="syscall">system call index</param>
/// <param name="timeMs">time of the call</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(ushort syscall, double timeMs)
{
if (!_fullTrace) return;
if (_maxTrace >= 0 && _traceLength >= _maxTrace)
{
if (_traceFinished) return;
_traceFinished = true;
_traceWriter.Flush();
_traceWriter.Close();
_traceOutputFile.Close();
//DebugLog.WriteLine("TOTAL_TRACE_TIME=" + (time - StartTimeMs));
return;
}
_traceWriter.Write(syscall);
_traceLength += 1;
}
/// <summary>
/// End the trace and close the file.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Close()
{
if (_traceFinished) return;
_traceFinished = true;
_traceWriter.Flush();
_traceWriter.Close();
_traceOutputFile.Close();
}
}
/// <summary>
/// Service loop and primary interface with ETW
/// </summary>
public class SystemCallServiceLoop
{
// Regex for sanitizing the command line -- either it is quoted, contains spaces and ends with exe, or contains no spaces. This covers all cases seen on the development machine.
private readonly Regex _pathRegex = new Regex("(\"[^\"]*\"|.*?\\.exe|[^ ]*)");
// List of settings we want to print out in debug log / get from web
private readonly string[] _properties =
{
"AutoResetMs", "TracePerProcess", "TracePerThread", "MaxTraceSyscalls",
"Verbosity", "ScrubCommandLine", "AnonymizeHostname"
};
// Private copies of settings for speed. I don't know whether these are actually faster.
private readonly bool _traceHostLevel = Properties.SystemCallServiceSettings.Default.TraceHostLevel;
private readonly bool _tracePerThread = Properties.SystemCallServiceSettings.Default.TracePerThread;
private readonly bool _tracePerProcess = Properties.SystemCallServiceSettings.Default.TracePerProcess;
private readonly long _maxTraceSyscalls = Properties.SystemCallServiceSettings.Default.MaxTraceSyscalls;
private readonly int _verbosity = Properties.SystemCallServiceSettings.Default.Verbosity;
private readonly double _commitInterval = Properties.SystemCallServiceSettings.Default.CommitInterval;
private readonly bool _scrubCommandLine = Properties.SystemCallServiceSettings.Default.ScrubCommandLine;
private readonly string _dataDirectory = Properties.SystemCallServiceSettings.Default.DataDirectory;
private readonly double _processDeletionDelay = Properties.SystemCallServiceSettings.Default.ProcessDeletionDelay; //ms
private readonly double _minBackoff = Properties.SystemCallServiceSettings.Default.MinBackoff;
private double _threadDeletionDelay = Properties.SystemCallServiceSettings.Default.ThreadDeletionDelay; //ms
// Counting system call tuples
private SystemCallTracer _systemWideCounter;
// System call list
private List<string> _syscallNameList;
//Code profiling
private readonly bool _profileCode = Properties.SystemCallServiceSettings.Default.ProfileCode;
private readonly Dictionary<String, Stopwatch> _codeProfileTimers = new Dictionary<String, Stopwatch>();
private readonly Dictionary<String, ulong> _codeProfileCounters = new Dictionary<String, ulong>();
private readonly string[] _codeProfileKeys =
{
"Commit", "Syscall", "ProcessStartTable", "WriteProcessData", "ProcessDeletionQueue", "ThreadDeletionQueue",
"ContextSwitch", "ContextSwitch1", "ContextSwitch2", "ContextSwitch3", "Database.Commit",
"WriteProcessDataInsert", "CreateTable", "WriteSystemData", "ProcessStart", "ProcessStop", "ThreadStart",
"ThreadStop", "CreateCommand", "InsertCommandClone", "InsertCommandNew"
};
// Last time we checked for thread and process deletion
private double _lastDeletionCheck;
// System information
private SystemInformation _systemInfo;
// End service flag
private bool _endService;
// Timer to trigger automatic restarts
private readonly System.Timers.Timer _resetTimer;
// Database connection
private DatabaseInterface _database;
// For timing the code
private readonly Stopwatch _timer = new Stopwatch(); // Keeps track of processing lag.
// For keeping track of how much time each thread has been active since last cswitch
private double[] _lastCSwitch;
// Dict for active threads, indexed by PID then TID
private class ThreadDict : Dictionary<int, ThreadInfo>{}
private class ProcessThreadDict : Dictionary<int, ThreadDict>{};
//Queue for creating tables
private readonly Queue<Info> _tableCreations = new Queue<Info>();
// Queue for deleting processes and threads
private readonly List<ProcessInfo> _processesToDelete = new List<ProcessInfo>();
private readonly List<ThreadInfo> _threadsToDelete = new List<ThreadInfo>();
// A Dictionary of active threads by process [PID][TID]
private readonly ProcessThreadDict _activeThreadsByProcess = new ProcessThreadDict();
// Dict for unknown threads, indexed by TID
private readonly ThreadDict _unkownThreads = new ThreadDict();
// Dict for active processes, indexed by PID
private readonly Dictionary<int, ProcessInfo> _activeProcesses = new Dictionary<int, ProcessInfo>();
//EventLog object for logging to the Application Event Log
private readonly EventLog _eventLog;
// Data directory location, used for generating paths to various files
private readonly string _rootPath;
private readonly string _tempPath;
private string _dataSubFolder;
// Maps from the addresses to some set of integers so we can do array indexing quickly and easily
private int[] _ntMap;
private ulong _ntLowestAddress;
private ulong _ntMaxAddress;
// Number of logical processors
private int _logicalProcessorCount;
// File to store all of our debug messages
private StreamWriter _debugLog;
// Event tracing session
private TraceEventSession _eventTracingSession;
// Place to keep track of what process and thread on each processor core
private int[] _processorActiveThreads;
private int[] _processorActiveProcesses;
// Default values for processors. Used for convenience
private const int DefaultProcessorValue = -2;
private const int UnknownPid = -1;
private const int IdlePid = 0;
// Address of the Kenrel
private UInt64 _ntKernelAddress;
private String _ntKernelImageName;
// Track the last time the database changes were committed
private double _lastCommit = -100;
// DLL imports for the functions that we use to get the base addresses of the kernel modules.
// The precompiler directives handle the 32 bit vs. 64 distinction
[DllImport("psapi")]
private static extern bool EnumDeviceDrivers(
#if X86
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In][Out] UInt32[] ddAddresses,
#else
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In] [Out] UInt64[] ddAddresses,
#endif
UInt32 arraySizeBytes,
[MarshalAs(UnmanagedType.U4)] out UInt32 bytesNeeded
);
[DllImport("psapi")]
private static extern int GetDeviceDriverBaseName(
#if X86
UInt32 ddAddress,
#else
UInt64 ddAddress,
#endif
StringBuilder ddBaseName,
int baseNameStringSizeChars
);
/// <summary>
/// Set the tracer parameters.
/// </summary>
/// <param name="el">EventLog object</param>
public SystemCallServiceLoop(EventLog el)
{
// Keep an instance variable of the event log -- used for crash logging
_eventLog = el;
// Keep track of where this is executing from and where we store the data
_rootPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
_tempPath = Path.GetTempPath();
// Setup a timer to automatically restart logging
_resetTimer = new System.Timers.Timer {AutoReset = false};
_resetTimer.Elapsed += ResetTimerEvent;
}
/// <summary>
/// Compute the MD5 of the kernel.
/// </summary>
/// <param name="kernelFileName">Filename of kernel (varies for different versions of wWndows).</param>
/// <returns></returns>
private string ComputeMd5(string kernelFileName)
{
string md5Sum;
// Debug print MD5sum of the kernel
using (var md5 = MD5.Create())
{
using (
var stream =
File.OpenRead(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System),
kernelFileName)))
{
md5Sum = BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLower();
}
}
return md5Sum;
}
/// <summary>
/// Load the system call addresses.
/// </summary>
/// <param name="kernelFileName">Filename of the kernel</param>
/// <param name="version">Kernel version</param>
/// <param name="sensorFileName">Filename that lists desired system calls to trace</param>
/// <param name="sensorList">List of the system calls (out)</param>
/// <param name="baseAddress">Base address of kernel (out)</param>
/// <param name="maxAddress">Max address of system call (out)</param>
/// <param name="indexBase">Used as offset for GDI tracing to prevent index collisions with multiple kernel files.</param>
/// <param name="kernelBase">Base address of the kernel.</param>
/// <returns>The map from addresses to indices</returns>
private int[] LoadAddressMaps(String kernelFileName, String version, String sensorFileName,
out List<string> sensorList, out ulong baseAddress, out ulong maxAddress, int indexBase, ulong kernelBase)
{
// Load the symbol table and build a dict, where the sensor names are the keys
string[] lines;
string md5Sum = ComputeMd5(kernelFileName);
_debugLog.WriteLine("KERNEL MD5: " + md5Sum);
// This is an abuse of naming, but because they are identified by version and MD5, I am not going to differentiate among different kernel image file names
string subdirectory = "ntoskrnl.exe";
string localFilename = Path.Combine(_rootPath, Properties.SystemCallServiceSettings.Default.SymbolSubfolder,
subdirectory, version + "_" + md5Sum + ".symbols");
try
{
lines = File.ReadAllLines(localFilename);
}
catch
{
_debugLog.WriteLine("Kernel symbols not held locally. Checking online");
_debugLog.WriteLine("NtVersion: " + _systemInfo.NtoskrnlVersion);
_debugLog.WriteLine("Local Filename: " + localFilename);
_debugLog.Flush();
File.Copy(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), kernelFileName),
Path.Combine(_tempPath, Properties.SystemCallServiceSettings.Default.DataDirectory,
_dataSubFolder, version + "_" + md5Sum + ".binary"));
maxAddress = 0;
baseAddress = 0;
sensorList = null;
return null;
}
Dictionary<string, UInt64> symbolTable = new Dictionary<string, UInt64>();
// First group is the address, second group is the name;
Regex re = new Regex(@"[ ]+[0-9a-f]+[ ]+([0-9a-f]+)[ ]+:+[ ]+(.*)");
foreach (string line in lines)
{
Match match = re.Match(line);
if (match.Success)
{
symbolTable[match.Groups[2].Value] = UInt64.Parse(match.Groups[1].Value, NumberStyles.HexNumber) -
UInt64.Parse("1000000", NumberStyles.HexNumber);
// corrects dbh.exe offset default.
}
}
// Load the sensor list to figure out the offsets
lines = File.ReadAllLines(Path.Combine(_rootPath, sensorFileName));
// Creates a list of the sensor names as a list of strings and the map from the addresses to the indices
sensorList = new List<string>();
List<UInt64> temp = new List<UInt64>();
// Convert the list of addresses to a map.
foreach (string line in lines)
{
sensorList.Add(line);
try
{
var offset = symbolTable[line];
temp.Add(offset);
}
catch
{
_debugLog.WriteLine("Sensor not found in symbol table: " + line);
}
}
baseAddress = temp.Min() + kernelBase;
maxAddress = temp.Max() + kernelBase;
int[] sensorMap = new int[1 + (temp.Max() - temp.Min())];
for (int i = 0; i < sensorMap.Count(); i++)
{
sensorMap[i] = -1;
}
for (int i = 0; i < temp.Count; i++)
{
sensorMap[(temp[i] - temp.Min())] = i + indexBase;
}
// Debugging information
_debugLog.WriteLine("Base: {0:X}", kernelBase);
_debugLog.WriteLine("Min: {0:X}", temp.Min());
_debugLog.WriteLine("Max: {0:X}", temp.Max());
_debugLog.WriteLine("baseAddress: {0:X}", baseAddress);
_debugLog.WriteLine("maxAddress: {0:X}", maxAddress);
return sensorMap;
}
/// <summary>
/// Get the hostname or the anonymized identifier.
/// </summary>
/// <returns>hostname or anonymized identifier</returns>
private string GetHostname()
{
string hostname;
if (Properties.SystemCallServiceSettings.Default.AnonymizeHostname)
{
if (Properties.SystemCallServiceSettings.Default.HostIdentifier == "None")
{
Properties.SystemCallServiceSettings.Default.HostIdentifier = Path.GetRandomFileName();
Properties.SystemCallServiceSettings.Default.Save();
}
hostname = Properties.SystemCallServiceSettings.Default.HostIdentifier;
}
else
{
hostname = Environment.MachineName;
}
return hostname;
}
/// <summary>
/// Ges system information and save it in the database
/// </summary>
private void GetSystemInformation()
{
// Anonymize the hostname (if necessary) by generating random string instead and saving it in properties
string hostname = GetHostname();
// Creates object to hold system info and populates it
_systemInfo = new SystemInformation
{
Cores = Environment.ProcessorCount,
NtoskrnlVersion =
FileVersionInfo.GetVersionInfo(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), _ntKernelImageName))
.ProductVersion,
NtoskrnlMd5 = ComputeMd5(_ntKernelImageName),
Hostname = hostname,
ThreadSampling = 0,
ProcessSampling = 0,
HostSampling = 0,
ThreadTrace = 0,
ProcessTrace = 0,
HostTrace = 0
};
if (Properties.SystemCallServiceSettings.Default.TracePerThread)
{
_systemInfo.ThreadTrace = Properties.SystemCallServiceSettings.Default.MaxTraceSyscalls;
}
if (Properties.SystemCallServiceSettings.Default.TracePerProcess)
{
_systemInfo.ProcessTrace = Properties.SystemCallServiceSettings.Default.MaxTraceSyscalls;
}
if (Properties.SystemCallServiceSettings.Default.TraceHostLevel)
{
_systemInfo.HostTrace = 1;
}
// Save logical processor count because we use it during tracing
_logicalProcessorCount = _systemInfo.Cores;
// Add to database
_database.AddSystemInformation(_systemInfo);
}
/// <summary>
/// Wrapper for regular starts.
/// </summary>
/// <param name="data">data from trace event</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessStart(TraceEvent data)
{
ProcessStartHelper(data, false);
}
/// <summary>
/// Wrapper for dc starts.
/// </summary>
/// <param name="data">data from trace event</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessDcStart(TraceEvent data)
{
ProcessStartHelper(data, true);
}
/// <summary>
/// Get the full path of anexecutable image.
/// </summary>
/// <param name="fileName"></param>
/// <returns>the path</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetFullPath(string fileName)
{
// Trim surrounding quotes
string path = fileName.Trim('\"');
// If it already exists, just return it.
if (File.Exists(path))
return Path.GetFullPath(path);
// If there isn't a dot, try adding an extension
if (!path.Contains('.'))
{
path = path + ".exe";
if (File.Exists(path))
return Path.GetFullPath(path);
}
// If there is a %, try expanding environment variables
// If there isn't a dot, try adding an extension
if (path.Contains('%'))
{
path = Environment.ExpandEnvironmentVariables(path);
if (File.Exists(path))
return Path.GetFullPath(path);
}
// This appears at the beginning of some strings
if (path.Contains(@"\??\"))
{
path = path.Replace(@"\??\", "");
if (File.Exists(path))
return Path.GetFullPath(path);