Skip to content

Commit 9d43df3

Browse files
korylprinceKory Prince
authored and
Kory Prince
committed
add alt_system_info table
1 parent 6ba5c46 commit 9d43df3

File tree

3 files changed

+447
-0
lines changed

3 files changed

+447
-0
lines changed

main.go

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"runtime"
88
"time"
99

10+
"github.com/macadmins/osquery-extension/tables/alt_system_info"
1011
"github.com/macadmins/osquery-extension/tables/chromeuserprofiles"
1112
"github.com/macadmins/osquery-extension/tables/fileline"
1213
"github.com/macadmins/osquery-extension/tables/filevaultusers"
@@ -100,6 +101,11 @@ func main() {
100101
return wifi_network.WifiNetworkGenerate(ctx, queryContext, *flSocketPath)
101102
},
102103
),
104+
table.NewPlugin("alt_system_info", alt_system_info.AltSystemInfoColumns(),
105+
func(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
106+
return alt_system_info.AltSystemInfoGenerate(ctx, queryContext, *flSocketPath)
107+
},
108+
),
103109
}
104110
plugins = append(plugins, darwinPlugins...)
105111
}
+324
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
package alt_system_info
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"context"
7+
"fmt"
8+
"strings"
9+
"sync"
10+
"time"
11+
12+
"github.com/groob/plist"
13+
"github.com/macadmins/osquery-extension/pkg/utils"
14+
"github.com/osquery/osquery-go"
15+
"github.com/osquery/osquery-go/plugin/table"
16+
"golang.org/x/sync/errgroup"
17+
)
18+
19+
func GetCPUType(cmder utils.CmdRunner) (string, error) {
20+
buf, err := cmder.RunCmd("machine")
21+
if err != nil {
22+
return "", fmt.Errorf("could not run machine command: %w", err)
23+
}
24+
return strings.TrimSpace(string(buf)), nil
25+
}
26+
27+
type IORegData struct {
28+
UUID string
29+
HardwareVendor string
30+
HardwareModel string
31+
HardwareVersion string
32+
HardwareSerial string
33+
}
34+
35+
func GetIORegData(cmder utils.CmdRunner) (*IORegData, error) {
36+
type data struct {
37+
Children []*struct {
38+
UUID string `plist:"IOPlatformUUID"`
39+
HardwareVendor []byte `plist:"manufacturer"`
40+
HardwareModel []byte `plist:"model"`
41+
HardwareVersion []byte `plist:"version"`
42+
HardwareSerial string `plist:"IOPlatformSerialNumber"`
43+
} `plist:"IORegistryEntryChildren"`
44+
}
45+
46+
buf, err := cmder.RunCmd("ioreg", "-d2", "-c", "IOPlatformExpertDevice", "-a")
47+
if err != nil {
48+
return nil, fmt.Errorf("could not run ioreg command: %w", err)
49+
}
50+
51+
d := new(data)
52+
if err := plist.Unmarshal(buf, d); err != nil {
53+
return nil, fmt.Errorf("could not unmarshal plist: %w", err)
54+
}
55+
56+
if len(d.Children) == 0 {
57+
return nil, fmt.Errorf("no children found in IORegistryEntryChildren")
58+
}
59+
60+
return &IORegData{
61+
UUID: d.Children[0].UUID,
62+
HardwareVendor: strings.TrimRight(string(d.Children[0].HardwareVendor), "\x00"),
63+
HardwareModel: strings.TrimRight(string(d.Children[0].HardwareModel), "\x00"),
64+
HardwareVersion: strings.TrimRight(string(d.Children[0].HardwareVersion), "\x00"),
65+
HardwareSerial: d.Children[0].HardwareSerial,
66+
}, nil
67+
}
68+
69+
type SysctlData struct {
70+
CPUBrand string
71+
CPUPhysicalCores string
72+
CPULogicalCores string
73+
PhysicalMemory string
74+
}
75+
76+
func GetSysctlData(cmder utils.CmdRunner) (*SysctlData, error) {
77+
keys := []string{
78+
"machdep.cpu.brand_string",
79+
"machdep.cpu.core_count",
80+
"machdep.cpu.thread_count",
81+
"hw.memsize",
82+
}
83+
84+
buf, err := cmder.RunCmd("sysctl", keys...)
85+
if err != nil {
86+
return nil, fmt.Errorf("could not run sysctl command: %w", err)
87+
}
88+
89+
s := bufio.NewScanner(bytes.NewReader(buf))
90+
data := new(SysctlData)
91+
for s.Scan() {
92+
line := s.Text()
93+
parts := strings.Split(line, ":")
94+
if len(parts) != 2 {
95+
continue
96+
}
97+
switch strings.TrimSpace(parts[0]) {
98+
case "machdep.cpu.brand_string":
99+
data.CPUBrand = strings.TrimSpace(parts[1])
100+
case "machdep.cpu.core_count":
101+
data.CPUPhysicalCores = strings.TrimSpace(parts[1])
102+
case "machdep.cpu.thread_count":
103+
data.CPULogicalCores = strings.TrimSpace(parts[1])
104+
case "hw.memsize":
105+
data.PhysicalMemory = strings.TrimSpace(parts[1])
106+
}
107+
}
108+
return data, nil
109+
}
110+
111+
type HostData struct {
112+
Hostname string
113+
ComputerName string
114+
LocalHostname string
115+
}
116+
117+
func GetHostData(cmder utils.CmdRunner) (*HostData, error) {
118+
data := new(HostData)
119+
120+
var wg errgroup.Group
121+
wg.Go(func() error {
122+
buf, err := cmder.RunCmd("hostname")
123+
if err != nil {
124+
return fmt.Errorf("could not run hostname command: %w", err)
125+
}
126+
data.Hostname = strings.TrimSpace(string(buf))
127+
return nil
128+
})
129+
130+
wg.Go(func() error {
131+
buf, err := cmder.RunCmd("scutil", "--get", "ComputerName")
132+
if err != nil {
133+
return fmt.Errorf("could not run scutil --get ComputerName command: %w", err)
134+
}
135+
data.ComputerName = strings.TrimSpace(string(buf))
136+
return nil
137+
})
138+
139+
wg.Go(func() error {
140+
buf, err := cmder.RunCmd("scutil", "--get", "LocalHostName")
141+
if err != nil {
142+
return fmt.Errorf("could not run scutil --get LocalHostname command: %w", err)
143+
}
144+
data.LocalHostname = strings.TrimSpace(string(buf))
145+
return nil
146+
})
147+
148+
return data, wg.Wait()
149+
}
150+
151+
func AltSystemInfoColumns() []table.ColumnDefinition {
152+
return []table.ColumnDefinition{
153+
table.TextColumn("hostname"),
154+
table.TextColumn("uuid"),
155+
table.TextColumn("cpu_type"),
156+
table.TextColumn("cpu_subtype"),
157+
table.TextColumn("cpu_brand"),
158+
table.IntegerColumn("cpu_physical_cores"),
159+
table.IntegerColumn("cpu_logical_cores"),
160+
table.IntegerColumn("cpu_sockets"),
161+
table.BigIntColumn("cpu_microcode"),
162+
table.TextColumn("physical_memory"),
163+
table.TextColumn("hardware_vendor"),
164+
table.TextColumn("hardware_model"),
165+
table.TextColumn("hardware_version"),
166+
table.TextColumn("hardware_serial"),
167+
table.TextColumn("board_vendor"),
168+
table.TextColumn("board_model"),
169+
table.TextColumn("board_version"),
170+
table.TextColumn("board_serial"),
171+
table.TextColumn("computer_name"),
172+
table.TextColumn("local_hostname"),
173+
}
174+
}
175+
176+
// IsMacOS150 returns true if the host is running macOS 15.0
177+
func IsMacOS150(client utils.OsqueryClient) (bool, error) {
178+
versionQuery := "select * from os_version where name = 'macOS' and major = '15' and minor = 0;"
179+
180+
resp, err := client.QueryRows(versionQuery)
181+
if err != nil {
182+
return false, err
183+
}
184+
185+
return len(resp) > 0, nil
186+
}
187+
188+
// Fallback returns the fields from the system_info table
189+
func Fallback(client utils.OsqueryClient) ([]map[string]string, error) {
190+
infoQuery := "select * from system_info;"
191+
192+
resp, err := client.QueryRow(infoQuery)
193+
if err != nil {
194+
return nil, err
195+
}
196+
return []map[string]string{resp}, nil
197+
}
198+
199+
type AltSystemInfoCache struct {
200+
IsMacOS15 *bool
201+
CPUType string
202+
IORegData *IORegData
203+
SysctlData *SysctlData
204+
HostData *HostData
205+
lastHost time.Time
206+
mu sync.Mutex
207+
}
208+
209+
// nolint:gochecknoglobals
210+
var altSystemInfoCache AltSystemInfoCache
211+
212+
// AltSystemInfoGenerate returns system information about the host, mirroring osquery's builtin system_info table.
213+
// Most data is cached forever because it never changes. Hostname data is cached for 5 minutes.
214+
func AltSystemInfoGenerate(ctx context.Context, queryContext table.QueryContext, socketPath string) ([]map[string]string, error) {
215+
altSystemInfoCache.mu.Lock()
216+
defer altSystemInfoCache.mu.Unlock()
217+
218+
// If not macOS 15, fallback to system_info table
219+
if altSystemInfoCache.IsMacOS15 != nil && !*altSystemInfoCache.IsMacOS15 {
220+
osqueryClient, err := osquery.NewClient(socketPath, 10*time.Second)
221+
if err != nil {
222+
return nil, fmt.Errorf("could not create osquery client: %w", err)
223+
}
224+
defer osqueryClient.Close()
225+
226+
return Fallback(osqueryClient)
227+
}
228+
229+
if altSystemInfoCache.IsMacOS15 == nil {
230+
// this is the first time we're running this query, so check if we're on macOS 15.0
231+
osqueryClient, err := osquery.NewClient(socketPath, 10*time.Second)
232+
if err != nil {
233+
return nil, fmt.Errorf("could not create osquery client: %w", err)
234+
}
235+
defer osqueryClient.Close()
236+
237+
isMacOS15, err := IsMacOS150(osqueryClient)
238+
if err != nil {
239+
return nil, fmt.Errorf("could not determine if host is running macOS 15.0: %w", err)
240+
}
241+
242+
altSystemInfoCache.IsMacOS15 = &isMacOS15
243+
244+
// if the host is not running macOS 15.0, fallback to the system_info table
245+
if !isMacOS15 {
246+
return Fallback(osqueryClient)
247+
}
248+
}
249+
250+
runner := utils.NewRunner()
251+
252+
var wg errgroup.Group
253+
if altSystemInfoCache.CPUType == "" {
254+
wg.Go(func() error {
255+
var err error
256+
altSystemInfoCache.CPUType, err = GetCPUType(runner.Runner)
257+
if err != nil {
258+
return fmt.Errorf("could not get cpu type: %w", err)
259+
}
260+
return nil
261+
})
262+
}
263+
264+
if altSystemInfoCache.IORegData == nil {
265+
wg.Go(func() error {
266+
var err error
267+
altSystemInfoCache.IORegData, err = GetIORegData(runner.Runner)
268+
if err != nil {
269+
return fmt.Errorf("could not get ioreg data: %w", err)
270+
}
271+
return nil
272+
})
273+
}
274+
275+
if altSystemInfoCache.SysctlData == nil {
276+
wg.Go(func() error {
277+
var err error
278+
altSystemInfoCache.SysctlData, err = GetSysctlData(runner.Runner)
279+
if err != nil {
280+
return fmt.Errorf("could not get sysctl data: %w", err)
281+
}
282+
return nil
283+
})
284+
}
285+
286+
if time.Since(altSystemInfoCache.lastHost) > 5*time.Minute {
287+
wg.Go(func() error {
288+
var err error
289+
altSystemInfoCache.HostData, err = GetHostData(runner.Runner)
290+
if err != nil {
291+
return fmt.Errorf("could not get host data: %w", err)
292+
}
293+
return nil
294+
})
295+
296+
}
297+
298+
if err := wg.Wait(); err != nil {
299+
return nil, err
300+
}
301+
302+
return []map[string]string{{
303+
"hostname": altSystemInfoCache.HostData.Hostname,
304+
"uuid": altSystemInfoCache.IORegData.UUID,
305+
"cpu_type": altSystemInfoCache.CPUType,
306+
"cpu_subtype": "", // always empty
307+
"cpu_brand": altSystemInfoCache.SysctlData.CPUBrand,
308+
"cpu_physical_cores": altSystemInfoCache.SysctlData.CPUPhysicalCores,
309+
"cpu_logical_cores": altSystemInfoCache.SysctlData.CPULogicalCores,
310+
"cpu_sockets": "", // always empty
311+
"cpu_microcode": "", // always empty
312+
"physical_memory": altSystemInfoCache.SysctlData.PhysicalMemory,
313+
"hardware_vendor": altSystemInfoCache.IORegData.HardwareVendor,
314+
"hardware_model": altSystemInfoCache.IORegData.HardwareModel,
315+
"hardware_version": altSystemInfoCache.IORegData.HardwareVersion,
316+
"hardware_serial": altSystemInfoCache.IORegData.HardwareSerial,
317+
"board_vendor": "", // always empty
318+
"board_model": "", // always empty
319+
"board_version": "", // always empty
320+
"board_serial": "", // always empty
321+
"computer_name": altSystemInfoCache.HostData.ComputerName,
322+
"local_hostname": altSystemInfoCache.HostData.LocalHostname,
323+
}}, nil
324+
}

0 commit comments

Comments
 (0)