Skip to content

Commit 2503dcc

Browse files
committed
feat(process): Enrich processes with integrity level and token elevation info
1 parent 77889f1 commit 2503dcc

File tree

8 files changed

+406
-49
lines changed

8 files changed

+406
-49
lines changed

pkg/alertsender/alert_test.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,17 @@ func TestAlertString(t *testing.T) {
5252
Name: "CreateProcess",
5353
PID: 1023,
5454
PS: &pstypes.PS{
55-
Name: "svchost.exe",
56-
Cmdline: "C:\\Windows\\System32\\svchost.exe",
57-
Ppid: 345,
58-
Username: "SYSTEM",
59-
Domain: "NT AUTHORITY",
60-
SID: "S-1-5-18",
55+
Name: "svchost.exe",
56+
Cmdline: "C:\\Windows\\System32\\svchost.exe",
57+
Ppid: 345,
58+
Username: "SYSTEM",
59+
Domain: "NT AUTHORITY",
60+
SID: "S-1-5-18",
61+
TokenIntegrityLevel: "HIGH",
6162
},
6263
}}),
6364
true,
64-
"Credential discovery via VaultCmd.exe\n\nSuspicious vault enumeration via VaultCmd tool\n\nSeverity: low\n\nSystem event involved in this alert:\n\n\tEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tHost: \n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC\n\t\tParameters: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe\n \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tAncestors: \n\t\n",
65+
"Credential discovery via VaultCmd.exe\n\nSuspicious vault enumeration via VaultCmd tool\n\nSeverity: low\n\nSystem event involved in this alert:\n\n\tEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tHost: \n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC\n\t\tParameters: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe\n \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tIntegrity level: HIGH\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tAncestors: \n\t\n",
6566
},
6667
{
6768
NewAlertWithEvents("Credential discovery via VaultCmd.exe", "", nil, Normal, []*event.Event{{
@@ -73,16 +74,17 @@ func TestAlertString(t *testing.T) {
7374
Name: "CreateProcess",
7475
PID: 1023,
7576
PS: &pstypes.PS{
76-
Name: "svchost.exe",
77-
Cmdline: "C:\\Windows\\System32\\svchost.exe",
78-
Ppid: 345,
79-
Username: "SYSTEM",
80-
Domain: "NT AUTHORITY",
81-
SID: "S-1-5-18",
77+
Name: "svchost.exe",
78+
Cmdline: "C:\\Windows\\System32\\svchost.exe",
79+
Ppid: 345,
80+
Username: "SYSTEM",
81+
Domain: "NT AUTHORITY",
82+
SID: "S-1-5-18",
83+
TokenIntegrityLevel: "HIGH",
8284
},
8385
}}),
8486
true,
85-
"Credential discovery via VaultCmd.exe\n\nSeverity: low\n\nSystem event involved in this alert:\n\n\tEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tHost: \n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC\n\t\tParameters: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe\n \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tAncestors: \n\t\n",
87+
"Credential discovery via VaultCmd.exe\n\nSeverity: low\n\nSystem event involved in this alert:\n\n\tEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tHost: \n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC\n\t\tParameters: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe\n \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tIntegrity level: HIGH\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tAncestors: \n\t\n",
8688
},
8789
}
8890

pkg/event/enum.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,16 @@ var DNSResponseCodes = ParamEnum{
114114
uint32(windows.ERROR_INVALID_PARAMETER): "INVALID",
115115
uint32(windows.DNS_INFO_NO_RECORDS): "NXDOMAIN",
116116
}
117+
118+
const (
119+
TokenElevationTypeDefault uint32 = iota + 1
120+
TokenElevationTypeFull
121+
TokenElevationTypeLimited
122+
)
123+
124+
// PsTokenElevationTypes describes process token elevation types
125+
var PsTokenElevationTypes = ParamEnum{
126+
TokenElevationTypeDefault: "DEFAULT",
127+
TokenElevationTypeFull: "FULL",
128+
TokenElevationTypeLimited: "LIMITED",
129+
}

pkg/event/params/params_windows.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ const (
5858
ExitStatus = "exit_status"
5959
// StartTime field denotes the process start time.
6060
StartTime = "start_time"
61+
// ProcessIntegrityLevel field denotes the process integrity level.
62+
ProcessIntegrityLevel = "integrity_level"
63+
// ProcessTokenElevationType field designates the process token elevation type.
64+
ProcessTokenElevationType = "token_elevation_type"
65+
// ProcessTokenIsElevated field designates if the process token is elevated.
66+
ProcessTokenIsElevated = "token_is_elevated"
6167

6268
// DesiredAccess field denotes the access rights for different kernel objects such as processes or threads.
6369
DesiredAccess = "desired_access"

pkg/ps/snapshotter_windows.go

Lines changed: 119 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var (
5353
moduleCount = expvar.NewInt("process.module.count")
5454
mmapCount = expvar.NewInt("process.mmap.count")
5555
pebReadErrors = expvar.NewInt("process.peb.read.errors")
56+
processEnrichments = expvar.NewInt("process.enrichments")
5657
)
5758

5859
type snapshotter struct {
@@ -146,6 +147,7 @@ func (s *snapshotter) Write(e *event.Event) error {
146147
s.mu.Lock()
147148
defer s.mu.Unlock()
148149
processCount.Add(1)
150+
149151
pid, err := e.Params.GetPid()
150152
if err != nil {
151153
return err
@@ -154,8 +156,45 @@ func (s *snapshotter) Write(e *event.Event) error {
154156
if err != nil {
155157
return err
156158
}
159+
157160
proc, err := s.newProcState(pid, ppid, e)
158-
s.procs[pid] = proc
161+
if ps := s.procs[pid]; ps == nil && (e.IsCreateProcessInternal() || e.IsProcessRundownInternal()) {
162+
// only modify the state if there is no process derived from the NT kernel logger process events
163+
s.procs[pid] = proc
164+
} else if ps, ok := s.procs[pid]; ok && (e.IsCreateProcessInternal() || e.IsProcessRundownInternal()) {
165+
// process state derived from the core kernel events exists - enrich it
166+
ps.TokenIntegrityLevel = proc.TokenIntegrityLevel
167+
ps.TokenElevationType = proc.TokenElevationType
168+
ps.IsTokenElevated = proc.IsTokenElevated
169+
if len(proc.Exe) > len(ps.Exe) {
170+
// prefer full executable path
171+
ps.Exe = proc.Exe
172+
}
173+
s.procs[pid] = ps
174+
} else if ps, ok := s.procs[pid]; ok && (e.IsCreateProcess() || e.IsProcessRundown()) && ps.TokenIntegrityLevel != "" {
175+
// enrich the existing process state with the newly arrived NT kernel logger process events
176+
// but obtain the integrity level and executable path from the previous proc state
177+
processEnrichments.Add(1)
178+
proc.TokenIntegrityLevel = ps.TokenIntegrityLevel
179+
proc.TokenElevationType = ps.TokenElevationType
180+
proc.IsTokenElevated = ps.IsTokenElevated
181+
182+
if len(ps.Exe) > len(proc.Exe) {
183+
// prefer full executable path
184+
proc.Exe = ps.Exe
185+
e.AppendParam(params.Exe, params.Path, ps.Exe)
186+
}
187+
188+
e.AppendParam(params.ProcessIntegrityLevel, params.AnsiString, ps.TokenIntegrityLevel)
189+
e.AppendParam(params.ProcessTokenElevationType, params.AnsiString, ps.TokenElevationType)
190+
e.AppendParam(params.ProcessTokenIsElevated, params.Bool, ps.IsTokenElevated)
191+
192+
s.procs[pid] = proc
193+
} else {
194+
// in all other cases append the process state
195+
s.procs[pid] = proc
196+
}
197+
159198
// adjust the process which is generating
160199
// the event. For `CreateProcess` events
161200
// the process context is scoped to the
@@ -165,9 +204,10 @@ func (s *snapshotter) Write(e *event.Event) error {
165204
// snapshot state
166205
if e.IsProcessRundown() {
167206
e.PS = proc
168-
} else {
207+
} else if !e.IsProcessRundownInternal() && !e.IsCreateProcessInternal() {
169208
e.PS = s.procs[e.PID]
170209
}
210+
171211
return err
172212
}
173213

@@ -317,6 +357,23 @@ func (s *snapshotter) Close() error {
317357
}
318358

319359
func (s *snapshotter) newProcState(pid, ppid uint32, e *event.Event) (*pstypes.PS, error) {
360+
if e.IsCreateProcessInternal() || e.IsProcessRundownInternal() {
361+
proc := &pstypes.PS{
362+
PID: pid,
363+
Ppid: ppid,
364+
Exe: e.GetParamAsString(params.Exe),
365+
TokenIntegrityLevel: e.GetParamAsString(params.ProcessIntegrityLevel),
366+
TokenElevationType: e.GetParamAsString(params.ProcessTokenElevationType),
367+
IsTokenElevated: e.Params.TryGetBool(params.ProcessTokenIsElevated),
368+
Threads: make(map[uint32]pstypes.Thread),
369+
Modules: make([]pstypes.Module, 0),
370+
Handles: make([]htypes.Handle, 0),
371+
Mmaps: make([]pstypes.Mmap, 0),
372+
}
373+
374+
return proc, nil
375+
}
376+
320377
proc := pstypes.New(
321378
pid,
322379
ppid,
@@ -369,12 +426,38 @@ func (s *snapshotter) newProcState(pid, ppid uint32, e *event.Event) (*pstypes.P
369426
access := uint32(windows.PROCESS_QUERY_INFORMATION | windows.PROCESS_VM_READ)
370427
process, err := windows.OpenProcess(access, false, pid)
371428
if err != nil {
372-
return proc, nil
429+
process, err = windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, pid)
430+
if err != nil {
431+
return proc, nil
432+
}
373433
}
374434
//nolint:errcheck
375435
defer windows.CloseHandle(process)
376436

377-
// read PEB
437+
// query token attributes if not enriched by internal event
438+
if s.procs[pid] == nil {
439+
var token windows.Token
440+
err = windows.OpenProcessToken(process, windows.TOKEN_QUERY, &token)
441+
if err != nil {
442+
goto readPEB
443+
}
444+
defer token.Close()
445+
446+
// get process token integrity level
447+
tokenMandatoryLabel, err := sys.GetProcessTokenInformation[windows.Tokenmandatorylabel](token, windows.TokenIntegrityLevel)
448+
if err != nil {
449+
goto readPEB
450+
}
451+
452+
proc.TokenIntegrityLevel = sys.RidToString(tokenMandatoryLabel.Label.Sid)
453+
proc.IsTokenElevated = token.IsElevated()
454+
455+
e.AppendParam(params.ProcessIntegrityLevel, params.AnsiString, proc.TokenIntegrityLevel)
456+
e.AppendParam(params.ProcessTokenIsElevated, params.Bool, proc.IsTokenElevated)
457+
}
458+
459+
readPEB:
460+
// read PEB (Process Environment Block)
378461
peb, err := ReadPEB(process)
379462
if err != nil {
380463
pebReadErrors.Add(1)
@@ -525,27 +608,52 @@ func (s *snapshotter) Find(pid uint32) (bool, *pstypes.PS) {
525608
}
526609
proc.StartTime = time.Unix(0, ct.Nanoseconds())
527610

611+
// get process creation attributes
612+
var isWOW64 bool
613+
if err := windows.IsWow64Process(process, &isWOW64); err == nil && isWOW64 {
614+
proc.IsWOW64 = true
615+
}
616+
if isPackaged, err := sys.IsProcessPackaged(process); err == nil && isPackaged {
617+
proc.IsPackaged = true
618+
}
619+
if prot, err := sys.QueryInformationProcess[sys.PsProtection](process, sys.ProcessProtectionInformation); err == nil && prot != nil {
620+
proc.IsProtected = prot.IsProtected()
621+
}
622+
528623
// get process token attributes
529624
var token windows.Token
625+
var tokenUser *windows.Tokenuser
626+
var tokenMandatoryLabel *windows.Tokenmandatorylabel
627+
530628
err = windows.OpenProcessToken(process, windows.TOKEN_QUERY, &token)
531629
if err != nil {
532-
return false, proc
630+
goto readPEB
533631
}
534632
defer token.Close()
535-
usr, err := token.GetTokenUser()
633+
tokenUser, err = token.GetTokenUser()
536634
if err != nil {
537-
return false, proc
635+
goto readPEB
636+
}
637+
proc.SID = tokenUser.User.Sid.String()
638+
proc.Username, proc.Domain, _, _ = tokenUser.User.Sid.LookupAccount("")
639+
640+
// get process token integrity level
641+
tokenMandatoryLabel, err = sys.GetProcessTokenInformation[windows.Tokenmandatorylabel](token, windows.TokenIntegrityLevel)
642+
if err != nil {
643+
goto readPEB
538644
}
539-
proc.SID = usr.User.Sid.String()
540-
proc.Username, proc.Domain, _, _ = usr.User.Sid.LookupAccount("")
645+
646+
proc.TokenIntegrityLevel = sys.RidToString(tokenMandatoryLabel.Label.Sid)
647+
proc.IsTokenElevated = token.IsElevated()
541648

542649
// retrieve process handles
543650
proc.Handles, err = s.hsnap.FindHandles(pid)
544651
if err != nil {
545-
return false, proc
652+
goto readPEB
546653
}
547654

548-
// read PEB
655+
readPEB:
656+
// read PEB (Process Environment Block)
549657
peb, err := ReadPEB(process)
550658
if err != nil {
551659
pebReadErrors.Add(1)
@@ -556,18 +664,6 @@ func (s *snapshotter) Find(pid uint32) (bool, *pstypes.PS) {
556664
proc.SessionID = peb.GetSessionID()
557665
proc.Cwd = peb.GetCurrentWorkingDirectory()
558666

559-
// get process creation attributes
560-
var isWOW64 bool
561-
if err := windows.IsWow64Process(process, &isWOW64); err == nil && isWOW64 {
562-
proc.IsWOW64 = true
563-
}
564-
if isPackaged, err := sys.IsProcessPackaged(process); err == nil && isPackaged {
565-
proc.IsPackaged = true
566-
}
567-
if prot, err := sys.QueryInformationProcess[sys.PsProtection](process, sys.ProcessProtectionInformation); err == nil && prot != nil {
568-
proc.IsProtected = prot.IsProtected()
569-
}
570-
571667
return false, proc
572668
}
573669

0 commit comments

Comments
 (0)