Compare commits

...
Author SHA1 Message Date
moloch-- 41dc54d736 Implement mount for darwin 2026-01-29 22:22:59 -08:00
moloch-- 8a0694cb38 Add mount on darwin 2026-01-29 22:08:28 -08:00
moloch-- 52c0618bc3 Add os version implementations for linux/darwin 2026-01-29 21:50:16 -08:00
moloch-- e2e1912628 Add search to edit cmd 2026-01-29 21:01:16 -08:00
moloch-- 36794f1b07 Implement skip to line number 2026-01-29 20:50:11 -08:00
moloch-- ccd8f75b03 v1.6.9 2026-01-29 20:36:45 -08:00
9 changed files with 798 additions and 36 deletions

No files matched your search

+1
View File
@@ -42,3 +42,4 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
+357 -28
View File
@@ -2,7 +2,9 @@ package edit
import (
"fmt"
"strconv"
"strings"
"unicode"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters"
@@ -37,30 +39,33 @@ type editorResult struct {
}
type editorModel struct {
lines [][]rune
row int
col int
top int
left int
width int
height int
mode editorMode
command string
pending rune
dirty bool
filename string
showLineNumbers bool
syntaxName string
lexer chroma.Lexer
formatter chroma.Formatter
style *chroma.Style
highlighted []string
highlightOn bool
highlightDirty bool
action exitAction
forceQuit bool
message string
clearMessage bool
lines [][]rune
row int
col int
top int
left int
width int
height int
mode editorMode
command string
commandPrefix rune
pending rune
dirty bool
filename string
showLineNumbers bool
syntaxName string
lexer chroma.Lexer
formatter chroma.Formatter
style *chroma.Style
highlighted []string
highlightOn bool
highlightDirty bool
lastSearch string
lastSearchForward bool
action exitAction
forceQuit bool
message string
clearMessage bool
}
var (
@@ -76,6 +81,7 @@ func newEditorModel(content, filename string, lexer chroma.Lexer, syntaxName str
mode: modeNormal,
filename: filename,
showLineNumbers: showLineNumbers,
commandPrefix: ':',
}
model.setSyntax(lexer, syntaxName)
return model
@@ -175,10 +181,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case tea.KeyEsc:
m.mode = modeNormal
m.command = ""
m.commandPrefix = ':'
case tea.KeyEnter:
m.executeCommand(strings.TrimSpace(m.command))
if m.commandPrefix == '/' {
m.executeSearch(strings.TrimSpace(m.command))
} else {
m.executeCommand(strings.TrimSpace(m.command))
}
m.command = ""
m.mode = modeNormal
m.commandPrefix = ':'
if m.action != actionNone {
return m, tea.Quit
}
@@ -276,6 +288,14 @@ func (m *editorModel) handleNormalRune(r rune) bool {
m.moveUp()
case 'l':
m.moveRight()
case 'w':
m.moveWordForward()
case 'b':
m.moveWordBackward()
case 'e':
m.moveWordEnd()
case '^':
m.moveLineFirstNonWhitespace()
case '0':
m.moveLineStart()
case '$':
@@ -303,15 +323,31 @@ func (m *editorModel) handleNormalRune(r rune) bool {
m.pending = 'g'
case 'G':
m.gotoBottom()
case 'n':
m.searchNext(true)
case 'N':
m.searchNext(false)
case ':':
m.mode = modeCommand
m.command = ""
m.commandPrefix = ':'
case '/':
m.mode = modeCommand
m.command = ""
m.commandPrefix = '/'
}
return false
}
func (m *editorModel) executeCommand(cmd string) {
if isDigits(cmd) {
target, err := strconv.Atoi(cmd)
if err == nil {
m.gotoLine(target)
return
}
}
switch strings.ToLower(cmd) {
case "":
return
@@ -408,15 +444,19 @@ func (m *editorModel) statusLine() string {
func (m *editorModel) commandLine() string {
if m.mode == modeCommand {
return ":" + m.command
prefix := ":"
if m.commandPrefix == '/' {
prefix = "/"
}
return prefix + m.command
}
if m.message != "" {
return m.message
}
if m.mode == modeInsert {
return "ESC: normal :wq save+quit :q quit :n line numbers"
return "ESC: normal / search :wq save+quit :q quit :n line numbers :<num> goto line"
}
return "i: insert h/j/k/l: move :wq save+quit :q quit :n line numbers"
return "i: insert h/j/k/l: move / search :wq save+quit :q quit :n line numbers :<num> goto line"
}
func (m *editorModel) content() string {
@@ -551,6 +591,18 @@ func (m *editorModel) moveLineStart() {
m.col = 0
}
func (m *editorModel) moveLineFirstNonWhitespace() {
line := m.lines[m.row]
col := 0
for col < len(line) {
if line[col] != ' ' && line[col] != '\t' {
break
}
col++
}
m.col = col
}
func (m *editorModel) moveLineEnd() {
m.col = len(m.lines[m.row])
}
@@ -570,6 +622,115 @@ func (m *editorModel) gotoBottom() {
m.clampCol()
}
func (m *editorModel) gotoLine(lineNumber int) {
if lineNumber < 1 {
lineNumber = 1
}
if len(m.lines) == 0 {
m.row = 0
m.col = 0
return
}
if lineNumber > len(m.lines) {
lineNumber = len(m.lines)
}
m.row = lineNumber - 1
m.clampCol()
m.ensureCursorVisible()
}
func (m *editorModel) moveWordForward() {
row := m.row
col := m.col
for {
if row >= len(m.lines) {
return
}
line := m.lines[row]
if col >= len(line) {
row++
col = 0
continue
}
if isWordChar(line[col]) {
for col < len(line) && isWordChar(line[col]) {
col++
}
}
for col < len(line) && !isWordChar(line[col]) {
col++
}
if col < len(line) {
m.row = row
m.col = col
return
}
row++
col = 0
}
}
func (m *editorModel) moveWordBackward() {
row := m.row
col := m.col - 1
for {
if row < 0 {
return
}
line := m.lines[row]
if col >= len(line) {
col = len(line) - 1
}
for col >= 0 && (col >= len(line) || !isWordChar(line[col])) {
col--
}
for col >= 0 && isWordChar(line[col]) {
col--
}
if col+1 >= 0 && col+1 < len(line) {
m.row = row
m.col = col + 1
return
}
row--
if row >= 0 {
col = len(m.lines[row]) - 1
}
}
}
func (m *editorModel) moveWordEnd() {
row := m.row
col := m.col
for {
if row >= len(m.lines) {
return
}
line := m.lines[row]
if col >= len(line) {
row++
col = 0
continue
}
if isWordChar(line[col]) {
for col+1 < len(line) && isWordChar(line[col+1]) {
col++
}
m.row = row
m.col = col
return
}
for col < len(line) && !isWordChar(line[col]) {
col++
}
if col < len(line) {
continue
}
row++
col = 0
}
}
func (m *editorModel) clampCol() {
lineLen := len(m.lines[m.row])
if m.col > lineLen {
@@ -763,6 +924,104 @@ func (m *editorModel) applyCursor(rendered, highlighted string, line []rune, sta
return left + cursorStyle.Render(string(line[cursor])) + right
}
func (m *editorModel) executeSearch(pattern string) {
if pattern == "" {
if m.lastSearch == "" {
return
}
pattern = m.lastSearch
}
m.lastSearch = pattern
m.lastSearchForward = true
if !m.searchNext(true) {
m.message = fmt.Sprintf("Pattern not found: %s", pattern)
m.clearMessage = true
}
}
func (m *editorModel) searchNext(forward bool) bool {
pattern := m.lastSearch
if pattern == "" {
m.message = "No previous search"
m.clearMessage = true
return false
}
row, col, ok := m.findMatch(pattern, forward)
if !ok {
m.message = fmt.Sprintf("Pattern not found: %s", pattern)
m.clearMessage = true
return false
}
m.row = row
m.col = col
m.lastSearchForward = forward
m.ensureCursorVisible()
return true
}
func (m *editorModel) findMatch(pattern string, forward bool) (int, int, bool) {
if pattern == "" {
return 0, 0, false
}
pat := []rune(pattern)
if forward {
return m.findForward(pat)
}
return m.findBackward(pat)
}
func (m *editorModel) findForward(pattern []rune) (int, int, bool) {
row := m.row
col := m.col + 1
for r := row; r < len(m.lines); r++ {
line := m.lines[r]
start := 0
if r == row {
start = col
}
if idx := indexOfRunes(line, pattern, start); idx >= 0 {
return r, idx, true
}
}
for r := 0; r <= row && r < len(m.lines); r++ {
line := m.lines[r]
end := len(line)
if r == row {
end = min(end, col)
}
if idx := indexOfRunes(line, pattern, 0); idx >= 0 && idx < end {
return r, idx, true
}
}
return 0, 0, false
}
func (m *editorModel) findBackward(pattern []rune) (int, int, bool) {
row := m.row
col := m.col - 1
for r := row; r >= 0; r-- {
line := m.lines[r]
end := len(line)
if r == row {
end = col + 1
}
if idx := lastIndexOfRunes(line, pattern, end); idx >= 0 {
return r, idx, true
}
}
for r := len(m.lines) - 1; r >= row && r >= 0; r-- {
line := m.lines[r]
end := len(line)
if r == row {
end = len(line)
}
if idx := lastIndexOfRunes(line, pattern, end); idx >= 0 {
return r, idx, true
}
}
return 0, 0, false
}
func splitLines(content string) [][]rune {
parts := strings.Split(content, "\n")
if len(parts) == 0 {
@@ -783,6 +1042,76 @@ func popRune(value string) string {
return string(runes[:len(runes)-1])
}
func isDigits(value string) bool {
if value == "" {
return false
}
for _, r := range value {
if r < '0' || r > '9' {
return false
}
}
return true
}
func isWordChar(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}
func indexOfRunes(haystack, needle []rune, start int) int {
if len(needle) == 0 {
return -1
}
if start < 0 {
start = 0
}
if start >= len(haystack) {
return -1
}
for i := start; i+len(needle) <= len(haystack); i++ {
match := true
for j := range needle {
if haystack[i+j] != needle[j] {
match = false
break
}
}
if match {
return i
}
}
return -1
}
func lastIndexOfRunes(haystack, needle []rune, end int) int {
if len(needle) == 0 {
return -1
}
if end > len(haystack) {
end = len(haystack)
}
for i := end - len(needle); i >= 0; i-- {
match := true
for j := range needle {
if haystack[i+j] != needle[j] {
match = false
break
}
}
if match {
return i
}
}
return -1
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func baseName(path string) string {
if path == "" {
return ""
@@ -25,6 +25,7 @@ import (
"syscall"
"github.com/bishopfox/sliver/implant/sliver/extension"
"github.com/bishopfox/sliver/implant/sliver/mount"
"github.com/bishopfox/sliver/protobuf/commonpb"
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
@@ -56,6 +57,7 @@ var (
pb.MsgNetstatReq: netstatHandler,
pb.MsgSideloadReq: sideloadHandler,
pb.MsgMountReq: mountHandler,
pb.MsgReconfigureReq: reconfigureHandler,
pb.MsgSSHCommandReq: runSSHCommandHandler,
@@ -154,6 +156,27 @@ func listExtensionsHandler(data []byte, resp RPCResponse) {
resp(data, err)
}
func mountHandler(data []byte, resp RPCResponse) {
mountReq := &pb.MountReq{}
err := proto.Unmarshal(data, mountReq)
if err != nil {
return
}
mountData, err := mount.GetMountInformation()
mountResp := &pb.Mount{
Info: mountData,
Response: &commonpb.Response{},
}
if err != nil {
mountResp.Response.Err = err.Error()
}
data, err = proto.Marshal(mountResp)
resp(data, err)
}
func getUid(fileInfo os.FileInfo) string {
uid := int32(fileInfo.Sys().(*syscall.Stat_t).Uid)
uid_str := strconv.FormatUint(uint64(uid), 10)
+166
View File
@@ -0,0 +1,166 @@
//go:build darwin
package mount
/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"strings"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"golang.org/x/sys/unix"
)
func getString(input []byte) string {
ver := string(input)
if i := strings.Index(ver, "\x00"); i != -1 {
ver = ver[:i]
}
return ver
}
func formatMountFlags(flags uint32) string {
opts := make([]string, 0, 12)
if flags&unix.MNT_RDONLY != 0 {
opts = append(opts, "ro")
} else {
opts = append(opts, "rw")
}
if flags&unix.MNT_NOSUID != 0 {
opts = append(opts, "nosuid")
}
if flags&unix.MNT_NODEV != 0 {
opts = append(opts, "nodev")
}
if flags&unix.MNT_NOEXEC != 0 {
opts = append(opts, "noexec")
}
if flags&unix.MNT_SYNCHRONOUS != 0 {
opts = append(opts, "sync")
}
if flags&unix.MNT_ASYNC != 0 {
opts = append(opts, "async")
}
if flags&unix.MNT_NOATIME != 0 {
opts = append(opts, "noatime")
}
if flags&unix.MNT_JOURNALED != 0 {
opts = append(opts, "journaled")
}
if flags&unix.MNT_QUOTA != 0 {
opts = append(opts, "quota")
}
if flags&unix.MNT_UNION != 0 {
opts = append(opts, "union")
}
if flags&unix.MNT_AUTOMOUNTED != 0 {
opts = append(opts, "automounted")
}
if flags&unix.MNT_REMOVABLE != 0 {
opts = append(opts, "removable")
}
if flags&unix.MNT_DONTBROWSE != 0 {
opts = append(opts, "nobrowse")
}
if flags&unix.MNT_IGNORE_OWNERSHIP != 0 {
opts = append(opts, "ignore-ownership")
}
if flags&unix.MNT_LOCAL != 0 {
opts = append(opts, "local")
}
if flags&unix.MNT_ROOTFS != 0 {
opts = append(opts, "rootfs")
}
if flags&unix.MNT_SNAPSHOT != 0 {
opts = append(opts, "snapshot")
}
if flags&unix.MNT_CPROTECT != 0 {
opts = append(opts, "cprotect")
}
if flags&unix.MNT_NOUSERXATTR != 0 {
opts = append(opts, "nouserxattr")
}
if flags&unix.MNT_QUARANTINE != 0 {
opts = append(opts, "quarantine")
}
if flags&unix.MNT_UNKNOWNPERMISSIONS != 0 {
opts = append(opts, "unknown-permissions")
}
if flags&unix.MNT_DEFWRITE != 0 {
opts = append(opts, "defwrite")
}
if flags&unix.MNT_DOVOLFS != 0 {
opts = append(opts, "volfs")
}
if flags&unix.MNT_NOBLOCK != 0 {
opts = append(opts, "noblock")
}
if flags&unix.MNT_RELOAD != 0 {
opts = append(opts, "reload")
}
if flags&unix.MNT_UPDATE != 0 {
opts = append(opts, "update")
}
if flags&unix.MNT_EXPORTED != 0 {
opts = append(opts, "exported")
}
if flags&unix.MNT_MULTILABEL != 0 {
opts = append(opts, "multilabel")
}
if flags&unix.MNT_STRICTATIME != 0 {
opts = append(opts, "strictatime")
}
if flags&unix.MNT_FORCE != 0 {
opts = append(opts, "force")
}
return strings.Join(opts, ",")
}
func GetMountInformation() ([]*sliverpb.MountInfo, error) {
mountInfo := make([]*sliverpb.MountInfo, 0)
count, err := unix.Getfsstat(nil, unix.MNT_NOWAIT)
if err != nil || count == 0 {
return mountInfo, err
}
stats := make([]unix.Statfs_t, count)
count, err = unix.Getfsstat(stats, unix.MNT_NOWAIT)
if err != nil {
return mountInfo, err
}
stats = stats[:count]
for _, stat := range stats {
var mountData sliverpb.MountInfo
mountData.VolumeName = getString(stat.Mntfromname[:])
mountData.MountPoint = getString(stat.Mntonname[:])
mountData.Label = "/"
mountData.VolumeType = getString(stat.Fstypename[:])
mountData.MountOptions = formatMountFlags(stat.Flags)
mountData.TotalSpace = stat.Blocks * uint64(stat.Bsize)
mountData.FreeSpace = stat.Bavail * uint64(stat.Bsize)
mountData.UsedSpace = (stat.Blocks - stat.Bfree) * uint64(stat.Bsize)
mountInfo = append(mountInfo, &mountData)
}
return mountInfo, nil
}
+19 -1
View File
@@ -1,7 +1,25 @@
// +linux
//go:build linux
package mount
/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bufio"
"os"
+2 -3
View File
@@ -1,5 +1,6 @@
//go:build windows
// +build windows
package mount
/*
Sliver Implant Framework
@@ -19,8 +20,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package mount
import (
"errors"
"fmt"
+1 -1
View File
@@ -1,4 +1,4 @@
// +darwin
//go:build darwin
package procdump
+124 -1
View File
@@ -18,6 +18,129 @@ package version
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
func GetVersion() string {
import (
"encoding/xml"
"errors"
"fmt"
//{{if .Config.Debug}}
"log"
//{{end}}
"os"
"strings"
"golang.org/x/sys/unix"
)
type plist struct {
XMLName xml.Name `xml:"plist"`
Dict dict `xml:"dict"`
}
type dict struct {
Key []string `xml:"key"`
String []string `xml:"string"`
}
func getString(input []byte) string {
ver := string(input)
if i := strings.Index(ver, "\x00"); i != -1 {
ver = ver[:i]
}
return ver
}
func readOSRelease() string {
paths := []string{
"/System/Library/CoreServices/SystemVersion.plist",
"/System/Library/CoreServices/ServerVersion.plist",
}
for _, path := range paths {
file, err := os.Open(path)
if err != nil {
continue
}
properties, err := parsePlistFile(file)
_ = file.Close()
if err != nil {
//{{if .Config.Debug}}
log.Printf("error parsing %s: %v", path, err)
//{{end}}
continue
}
if osRelease := buildOSRelease(properties); osRelease != "" {
return osRelease
}
}
return ""
}
func parsePlistFile(file *os.File) (map[string]string, error) {
var v plist
if err := xml.NewDecoder(file).Decode(&v); err != nil {
return nil, err
}
if len(v.Dict.Key) != len(v.Dict.String) {
return nil, errors.New("invalid plist content")
}
properties := make(map[string]string, len(v.Dict.Key))
for i, key := range v.Dict.Key {
properties[key] = v.Dict.String[i]
}
return properties, nil
}
func buildOSRelease(properties map[string]string) string {
productName := properties["ProductName"]
productVersion := properties["ProductVersion"]
productBuildVersion := properties["ProductBuildVersion"]
if productName == "" || productVersion == "" || productBuildVersion == "" {
return ""
}
return fmt.Sprintf("%s %s (%s)", productName, productVersion, productBuildVersion)
}
func normalizeArch(arch string) string {
arch = strings.TrimSpace(arch)
if arch == "" {
return ""
}
switch strings.ToLower(arch) {
case "amd64":
return "x86_64"
case "i386", "i486", "i586", "i686", "x86":
return "x86"
default:
return arch
}
}
func formatDetailedVersion(osRelease, kernel, arch string) string {
parts := make([]string, 0, 3)
if osRelease != "" {
parts = append(parts, osRelease)
}
if kernel != "" {
parts = append(parts, fmt.Sprintf("kernel %s", kernel))
}
if arch != "" {
parts = append(parts, arch)
}
return strings.Join(parts, " ")
}
func GetVersion() string {
osRelease := readOSRelease()
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
//{{if .Config.Debug}}
log.Printf("error getting OS version: %v", err)
//{{end}}
return osRelease
}
if osRelease == "" {
osRelease = getString(uname.Sysname[:])
}
kernel := getString(uname.Release[:])
arch := normalizeArch(getString(uname.Machine[:]))
return formatDetailedVersion(osRelease, kernel, arch)
}
+105 -2
View File
@@ -22,7 +22,10 @@ package version
import (
"fmt"
//{{if .Config.Debug}}
"log"
//{{end}}
"os"
"strings"
"syscall"
)
@@ -39,11 +42,111 @@ func getString(input [65]int8) string {
return ver
}
func readOSRelease() string {
paths := []string{"/etc/os-release", "/usr/lib/os-release"}
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
continue
}
return buildOSRelease(parseOSRelease(string(data)))
}
return ""
}
func parseOSRelease(content string) map[string]string {
values := make(map[string]string)
for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok || key == "" {
continue
}
key = strings.TrimSpace(key)
value = unescape(unquote(strings.TrimSpace(value)))
values[key] = value
}
return values
}
func unquote(s string) string {
if len(s) < 2 {
return s
}
if (s[0] == '"' || s[0] == '\'') && s[0] == s[len(s)-1] {
return s[1 : len(s)-1]
}
return s
}
func unescape(s string) string {
return strings.NewReplacer(
`\$`, `$`,
`\"`, `"`,
`\'`, `'`,
`\\`, `\`,
"\\`", "`",
).Replace(s)
}
func buildOSRelease(values map[string]string) string {
name := values["NAME"]
version := values["VERSION"]
if version == "" {
version = values["VERSION_ID"]
}
if name != "" && version != "" {
return fmt.Sprintf("%s %s", name, version)
}
return values["PRETTY_NAME"]
}
func normalizeArch(arch string) string {
arch = strings.TrimSpace(arch)
if arch == "" {
return ""
}
switch strings.ToLower(arch) {
case "amd64":
return "x86_64"
case "i386", "i486", "i586", "i686", "x86":
return "x86"
default:
return arch
}
}
func formatDetailedVersion(osRelease, kernel, arch string) string {
parts := make([]string, 0, 3)
if osRelease != "" {
parts = append(parts, osRelease)
}
if kernel != "" {
parts = append(parts, fmt.Sprintf("kernel %s", kernel))
}
if arch != "" {
parts = append(parts, arch)
}
return strings.Join(parts, " ")
}
// GetVersion returns the os version information
func GetVersion() string {
osRelease := readOSRelease()
var uname syscall.Utsname
if err := syscall.Uname(&uname); err != nil {
log.Fatal(err)
//{{if .Config.Debug}}
log.Printf("error getting OS version: %v", err)
//{{end}}
return osRelease
}
return fmt.Sprintf("%s %s %s", getString(uname.Sysname), getString(uname.Nodename), getString(uname.Release))
if osRelease == "" {
osRelease = getString(uname.Sysname)
}
kernel := getString(uname.Release)
arch := normalizeArch(getString(uname.Machine))
return formatDetailedVersion(osRelease, kernel, arch)
}