From ee107cdb3591e95546f16435552b237c23ce8c7f Mon Sep 17 00:00:00 2001 From: Brian Davis Date: Wed, 17 Apr 2024 20:28:38 -0400 Subject: [PATCH] Feat: add golang checksec intial commit - add golang checksec initial commit - add readme notes around switch to golang Signed-off-by: Brian Davis --- .goreleaser.yml | 35 +++++++ .pre-commit-config.yaml | 9 +- Dockerfile | 22 ++++- LICENSE.txt => LICENSE | 0 Makefile | 6 +- README.md | 29 ++++-- cmd/dir.go | 33 +++++++ cmd/file.go | 25 +++++ cmd/fortifyFile.go | 58 ++++++++++++ cmd/fortifyProc.go | 30 ++++++ cmd/kernel.go | 40 ++++++++ cmd/proc.go | 35 +++++++ cmd/procAll.go | 40 ++++++++ cmd/procLibs.go | 30 ++++++ cmd/root.go | 28 ++++++ compare_list.txt | 4 + go.mod | 33 +++++++ go.sum | 96 +++++++++++++++++++ main.go | 7 ++ pkg/checksec/canary.go | 53 +++++++++++ pkg/checksec/fortify.go | 179 ++++++++++++++++++++++++++++++++++++ pkg/checksec/kernel.go | 145 +++++++++++++++++++++++++++++ pkg/checksec/nx.go | 25 +++++ pkg/checksec/pie.go | 23 +++++ pkg/checksec/relro.go | 52 +++++++++++ pkg/checksec/rpath.go | 32 +++++++ pkg/checksec/runpath.go | 32 +++++++ pkg/checksec/symbols.go | 32 +++++++ pkg/checksec/sysctl.go | 66 +++++++++++++ pkg/utils/checks.go | 81 ++++++++++++++++ pkg/utils/filePrinter.go | 122 ++++++++++++++++++++++++ pkg/utils/files.go | 122 ++++++++++++++++++++++++ pkg/utils/fortifyPrinter.go | 100 ++++++++++++++++++++ pkg/utils/kernelPrinter.go | 86 +++++++++++++++++ pkg/utils/utils.go | 43 +++++++++ 35 files changed, 1740 insertions(+), 13 deletions(-) create mode 100644 .goreleaser.yml rename LICENSE.txt => LICENSE (100%) create mode 100644 cmd/dir.go create mode 100644 cmd/file.go create mode 100644 cmd/fortifyFile.go create mode 100644 cmd/fortifyProc.go create mode 100644 cmd/kernel.go create mode 100644 cmd/proc.go create mode 100644 cmd/procAll.go create mode 100644 cmd/procLibs.go create mode 100644 cmd/root.go create mode 100644 compare_list.txt create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/checksec/canary.go create mode 100644 pkg/checksec/fortify.go create mode 100644 pkg/checksec/kernel.go create mode 100644 pkg/checksec/nx.go create mode 100644 pkg/checksec/pie.go create mode 100644 pkg/checksec/relro.go create mode 100644 pkg/checksec/rpath.go create mode 100644 pkg/checksec/runpath.go create mode 100644 pkg/checksec/symbols.go create mode 100644 pkg/checksec/sysctl.go create mode 100644 pkg/utils/checks.go create mode 100644 pkg/utils/filePrinter.go create mode 100644 pkg/utils/files.go create mode 100644 pkg/utils/fortifyPrinter.go create mode 100644 pkg/utils/kernelPrinter.go create mode 100644 pkg/utils/utils.go diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..94a720d --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,35 @@ +project_name: checksec + +builds: + - id: linux + binary: checksec + main: ./main.go + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + #- amd64 + - arm64 + + - id: darwin + binary: checksec + main: ./main.go + env: + - CGO_ENABLED=0 + goos: + - darwin + goarch: + #- amd64 + - arm64 + + # - id: windows + # binary: checksec + # main: ./main.go + # goos: + # - windows + # goarch: + # - amd64 + # - arm64 + # ldflags: + # - -buildmode=exe diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54a1960..d719c08 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,9 +32,14 @@ repos: hooks: - id: forbid-crlf - id: remove-crlf - - id: remove-tabs - - id: forbid-tabs - repo: https://github.com/sirosen/fix-smartquotes rev: 0.2.0 hooks: - id: fix-smartquotes +- repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.1 + hooks: + - id: go-fmt + - id: go-vet + # - id: go-lint + - id: go-mod-tidy diff --git a/Dockerfile b/Dockerfile index 1c16e3c..07c6981 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,20 @@ -FROM photon:3.0 +FROM ubuntu:22.04 COPY checksec /bin/ -RUN tdnf clean all && tdnf remove -y toybox && tdnf upgrade -y && \ - tdnf install -y coreutils util-linux sed tar texinfo procps-ng grep findutils gzip file which awk binutils && \ - chmod +x /bin/checksec + +RUN apt-get update && apt-get -y -q upgrade && DEBIAN_FRONTEND=noninteractive apt-get -y -q install \ + bc bison flex build-essential ccache git file \ + libncurses-dev libssl-dev u-boot-tools wget \ + xz-utils vim xfce4 libxml2-utils python3 python3-pip jq \ + gcc clang && apt-get clean \ + pip3 install --upgrade pip && pip3 install setuptools && \ + pip3 install demjson3 && mkdir -p /zig && \ + wget https://ziglang.org/builds/zig-linux-$(uname -m)-0.12.0-dev.3667+77abd3a96.tar.xz && \ + tar xf zig-linux-$(uname -m)-0.12.0-dev.3667+77abd3a96.tar.xz -C /zig --strip-components=1 && \ + rm -rf zig-linux-$(uname -m)-0.12.0-dev.3667+77abd3a96.tar.xz && \ + chmod +x /bin/checksec + +COPY . /root +WORKDIR /root + +COPY dist/linux_linux_arm64/checksec /bin/checksec-go diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/Makefile b/Makefile index 5d45524..3cceebd 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,10 @@ test: ./tests/test-checksec.sh .PHONY: compose-test -compose-test: +compose-test: go docker-compose build docker-compose run + +.PHONY: go +go: + goreleaser build --snapshot --clean diff --git a/README.md b/README.md index 772c7fa..0bfedcd 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,34 @@ checksec ======== +**Bash version entering feature freeze** + Checksec is a bash script to check the properties of executables (like PIE, RELRO, Canaries, ASLR, Fortify Source). It has been originally written by Tobias Klein and the original source is available here: http://www.trapkit.de/tools/checksec.html + Updates ------- - ** MAJOR UPDATES ** 2.1.0 - - Changed structure to be more modular and switched to getopts so options can be in any order. e.g. format=json can be at the end now, however. - - All options now require `--$option=$value` instead of `--$option $value` - - --extended option now includes clang CFI and safe stack checks - - Last Update: 2024-06-08 + ** Version 2.7.x should be the last version of checksec in bash + Version 3.x will be released as a golang static binary + Checksec was originally released with 1.0 in early 2009 and has been used for validating binary checks of Linux systems for over a decade. Over time as more checks were supported and Linux distributions have changed, this has brought more dependencies into checksec. Adding more and more dependenies to be able to check the security flags of files, it not an ideal solution for systems with minor dependencies including embedded systems, distroless containers, and cross platform checks. + - Feature partial between the bash version and the golang version will be mostly supported. + - Adding support for yaml output + - Removing support for CSV + - JSON and XML will still both be supported + - Much faster results. When checking 694 files in a directory + - bash: real 0m10.348s + - golang: real 0m0.691s + - Adds recursive directory support + TODO: + [] Fix Partial RELRO + [] Add fortify file function results + [] Add fortifyPorc + [] Add ProcLibs + [] Add selinux checks + [] Add additional kernel flag checks + [] Update and Validate all current tests + [] Enable golint validation For OSX ------- diff --git a/cmd/dir.go b/cmd/dir.go new file mode 100644 index 0000000..5b0ed2e --- /dev/null +++ b/cmd/dir.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "checksec/pkg/utils" + + "github.com/spf13/cobra" +) + +// dirCmd represents the dir command +var dirCmd = &cobra.Command{ + Use: "dir", + Short: "check all files in a directory", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + dir := args[0] + recursive, _ := cmd.Flags().GetBool("recursive") + utils.CheckDirExists(dir) + var Elements []interface{} + var ElementColors []interface{} + for _, file := range utils.GetAllFilesFromDir(dir, recursive) { + data, color := utils.RunFileChecks(file) + Elements = append(Elements, data...) + ElementColors = append(ElementColors, color...) + } + utils.FilePrinter(outputFormat, Elements, ElementColors) + + }, +} + +func init() { + rootCmd.AddCommand(dirCmd) + dirCmd.Flags().BoolP("recursive", "r", false, "Enable recursive through the directories") +} diff --git a/cmd/file.go b/cmd/file.go new file mode 100644 index 0000000..fbcfd47 --- /dev/null +++ b/cmd/file.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "checksec/pkg/utils" + + "github.com/spf13/cobra" +) + +// fileCmd represents the file command +var fileCmd = &cobra.Command{ + Use: "file", + Short: "Check a single binary file", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + file := args[0] + + utils.CheckElfExists(file) + data, color := utils.RunFileChecks(file) + utils.FilePrinter(outputFormat, data, color) + }, +} + +func init() { + rootCmd.AddCommand(fileCmd) +} diff --git a/cmd/fortifyFile.go b/cmd/fortifyFile.go new file mode 100644 index 0000000..b2c201d --- /dev/null +++ b/cmd/fortifyFile.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "checksec/pkg/checksec" + "checksec/pkg/utils" + + "github.com/spf13/cobra" +) + +// fortifyFileCmd represents the fortifyFile command +var fortifyFileCmd = &cobra.Command{ + Use: "fortifyFile", + Short: "Check Fortify for binary file", + Run: func(cmd *cobra.Command, args []string) { + file := args[0] + + utils.CheckElfExists(file) + binary := utils.GetBinary(file) + fortify := checksec.Fortify(file, binary) + output := []interface{}{ + map[string]interface{}{ + "name": file, + "checks": map[string]interface{}{ + "fortify_source": fortify.Output, + "fortified": fortify.Fortified, + "fortifyable": fortify.Fortifiable, + "noFortify": fortify.NoFortify, + "libcSupport": fortify.LibcSupport, + "numLibcFunc": fortify.NumLibcFunc, + "numFileFunc": fortify.NumFileFunc, + }, + }, + } + color := []interface{}{ + map[string]interface{}{ + "name": file, + "checks": map[string]interface{}{ + "fortified": fortify.Fortified, + "fortifiedColor": "unset", + "noFortify": fortify.NoFortify, + "fortifyable": fortify.Fortifiable, + "fortifyableColor": "unset", + "fortify_source": fortify.Output, + "fortify_sourceColor": fortify.Color, + "libcSupport": fortify.LibcSupport, + "libcSupportColor": fortify.LibcSupportColor, + "numLibcFunc": fortify.NumLibcFunc, + "numFileFunc": fortify.NumFileFunc, + }, + }, + } + utils.FortifyPrinter(outputFormat, output, color) + }, +} + +func init() { + rootCmd.AddCommand(fortifyFileCmd) +} diff --git a/cmd/fortifyProc.go b/cmd/fortifyProc.go new file mode 100644 index 0000000..e730c62 --- /dev/null +++ b/cmd/fortifyProc.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// fortifyProcCmd represents the fortifyProc command +var fortifyProcCmd = &cobra.Command{ + Use: "fortifyProc", + Short: "Check Fortify for running process", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("fortifyProc called") + }, +} + +func init() { + rootCmd.AddCommand(fortifyProcCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // fortifyProcCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // fortifyProcCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/kernel.go b/cmd/kernel.go new file mode 100644 index 0000000..75024b5 --- /dev/null +++ b/cmd/kernel.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "checksec/pkg/utils" + + "github.com/spf13/cobra" +) + +// kernelCmd represents the kernel command +var kernelCmd = &cobra.Command{ + Use: "kernel", + Short: "Check kernel security flags", + Run: func(cmd *cobra.Command, args []string) { + var configFile string + if len(args) > 0 { + configFile = args[0] + } else { + configFile = "/proc/config.gz" + } + + utils.CheckFileExists(configFile) + + kernel, kernelColors := utils.ParseKernel(configFile) + utils.KernelPrinter(outputFormat, kernel, kernelColors) + }, +} + +func init() { + rootCmd.AddCommand(kernelCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // kernelCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // kernelCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/proc.go b/cmd/proc.go new file mode 100644 index 0000000..d20120b --- /dev/null +++ b/cmd/proc.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "checksec/pkg/utils" + "fmt" + "os" + + "path/filepath" + + "github.com/spf13/cobra" +) + +// procCmd represents the proc command +var procCmd = &cobra.Command{ + Use: "proc", + Short: "Check a file of a running process", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + proc := args[0] + + file, err := os.Readlink(filepath.Join("/proc", proc, "exe")) + if err != nil { + fmt.Printf("Error: Pid %s not found", proc) + os.Exit(1) + } + + utils.CheckElfExists(file) + data, color := utils.RunFileChecks(file) + utils.FilePrinter(outputFormat, data, color) + }, +} + +func init() { + rootCmd.AddCommand(procCmd) +} diff --git a/cmd/procAll.go b/cmd/procAll.go new file mode 100644 index 0000000..f366c8d --- /dev/null +++ b/cmd/procAll.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "checksec/pkg/utils" + "fmt" + "os" + "path/filepath" + + "github.com/shirou/gopsutil/v3/process" + "github.com/spf13/cobra" +) + +// procAllCmd represents the procAll command +var procAllCmd = &cobra.Command{ + Use: "procAll", + Short: "Check all running processes", + Run: func(cmd *cobra.Command, args []string) { + + var Elements []interface{} + var ElementColors []interface{} + processes, _ := process.Processes() + for _, process := range processes { + proc := process.Pid + filePath := filepath.Join("/proc", fmt.Sprint(proc), "exe") + file, err := os.Readlink(filePath) + if err != nil { + fmt.Printf("Error: Pid %d not found", proc) + os.Exit(1) + } + data, color := utils.RunFileChecks(file) + Elements = append(Elements, data...) + ElementColors = append(ElementColors, color...) + } + utils.FilePrinter(outputFormat, Elements, ElementColors) + }, +} + +func init() { + rootCmd.AddCommand(procAllCmd) +} diff --git a/cmd/procLibs.go b/cmd/procLibs.go new file mode 100644 index 0000000..351ffc3 --- /dev/null +++ b/cmd/procLibs.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// procLibsCmd represents the procLibs command +var procLibsCmd = &cobra.Command{ + Use: "procLibs", + Short: "check process libraries", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("procLibs called") + }, +} + +func init() { + rootCmd.AddCommand(procLibsCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // procLibsCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // procLibsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..27b1a7a --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +var ( + outputFormat string +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "checksec", + Short: "A binary scanning security tool", + Long: `A tool used to quickly survey mitigation technologies in use by processes on a Linux system.`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table", "Output format (table, xml, json or yaml)") + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/compare_list.txt b/compare_list.txt new file mode 100644 index 0000000..299cd1c --- /dev/null +++ b/compare_list.txt @@ -0,0 +1,4 @@ +compare list + +/usr/bin/aarch64-linux-gnu-gcov-11 +/usr/bin/systemd-notify diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6119286 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module checksec + +go 1.21.4 + +require ( + github.com/fatih/color v1.16.0 + github.com/lorenzosaino/go-sysctl v0.3.1 + github.com/shirou/gopsutil/v3 v3.24.3 + github.com/spf13/cobra v1.8.0 + github.com/u-root/u-root v0.14.0 + sigs.k8s.io/yaml v1.4.0 +) + +require ( + github.com/BurntSushi/toml v1.1.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/tools v0.18.0 // indirect + honnef.co/go/tools v0.3.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c3e848a --- /dev/null +++ b/go.sum @@ -0,0 +1,96 @@ +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/lorenzosaino/go-sysctl v0.3.1 h1:3phX80tdITw2fJjZlwbXQnDWs4S30beNcMbw0cn0HtY= +github.com/lorenzosaino/go-sysctl v0.3.1/go.mod h1:5grcsBRpspKknNS1qzt1eIeRDLrhpKZAtz8Fcuvs1Rc= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= +github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg= +github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d h1:+W8Qf4iJtMGKkyAygcKohjxTk4JPsL9DpzApJ22m5Ic= +golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= +honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5c4eea6 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "checksec/cmd" + +func main() { + cmd.Execute() +} diff --git a/pkg/checksec/canary.go b/pkg/checksec/canary.go new file mode 100644 index 0000000..54b12ec --- /dev/null +++ b/pkg/checksec/canary.go @@ -0,0 +1,53 @@ +package checksec + +import ( + "bytes" + "debug/elf" + "fmt" + "os" +) + +// canary struct +type canary struct { + Output string + Color string +} + +// StackChk to check for stack_chk_fail value +const StackChk = "__stack_chk_fail" + +// Canary - Check for canary bits +func Canary(name string) *canary { + // To get the dynamic values + // Open the ELF binary file + file, err := elf.Open(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + res := canary{} + if symbols, err := file.Symbols(); err == nil { + for _, symbol := range symbols { + if bytes.HasPrefix([]byte(symbol.Name), []byte(StackChk)) { + res.Output = "Canary Found" + res.Color = "green" + return &res + } + } + } + + if importedSymbols, err := file.ImportedSymbols(); err == nil { + for _, imp := range importedSymbols { + if bytes.HasPrefix([]byte(imp.Name), []byte(StackChk)) { + res.Output = "Canary Found" + res.Color = "green" + return &res + } + } + } + + res.Output = "No Canary Found" + res.Color = "red" + return &res +} diff --git a/pkg/checksec/fortify.go b/pkg/checksec/fortify.go new file mode 100644 index 0000000..d9fb714 --- /dev/null +++ b/pkg/checksec/fortify.go @@ -0,0 +1,179 @@ +package checksec + +import ( + "debug/elf" + "fmt" + "os" + "sort" + "strconv" + "strings" + + uroot "github.com/u-root/u-root/pkg/ldd" +) + +type fortify struct { + Output string + Color string + Fortifiable string + Fortified string + NoFortify string + LibcSupport string + LibcSupportColor string + NumLibcFunc string + NumFileFunc string +} + +func Fortify(name string, binary *elf.File) *fortify { + res := fortify{} + var chkFuncLibs []string + var funcLibs []string + var fileFunc []string + // limit to only checks that can actually be foritifed + // https://github.com/gcc-mirror/gcc/blob/master/gcc/builtins.def#L1112 + supportedFuncs := []string{"__memcpy_chk", "__memmove_chk", "__mempcpy_chk", "__memset_chk", "__stpcpy_chk", "__stpncpy_chk", "__strcat_chk", "__strcpy_chk", "__strncat_chk", "__strncpy_chk", "__snprintf_chk", "__sprintf_chk", "__vsnprintf_chk", "__vsprintf_chk", "__fprintf_chk", "__printf_chk", "__vfprintf_chk", "__vprintf_chk"} + sort.Strings(supportedFuncs) + checked := 0 + total := 0 + + ldd := GetLdd(name) + + if ldd == "none" { + res.Output = "N/A" + res.Color = "green" + res.Fortified = "0" + res.Fortifiable = "0" + res.LibcSupport = "N/A" + return &res + } else if ldd == "unk" { + res.Output = "N/A" + res.Color = "unset" + res.Fortified = "0" + res.Fortifiable = "0" + res.LibcSupport = "N/A" + return &res + } + + libc, err := elf.Open(ldd) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer libc.Close() + + libcDynSymbols, err := libc.DynamicSymbols() + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + // Iterate through dynamic symbols and print their information + for _, sym := range libcDynSymbols { + if strings.HasPrefix(sym.Name, "__") && strings.HasSuffix(sym.Name, "_chk") { + if isInSlice(sym.Name, supportedFuncs) { + chkFuncLibs = append(chkFuncLibs, strings.Trim(sym.Name, "__")) + funcLibs = append(funcLibs, strings.Trim(strings.Trim(sym.Name, "__"), "_chk")) + } + } + } + + if len(chkFuncLibs) > 0 { + res.LibcSupport = "Yes" + res.LibcSupportColor = "green" + res.NumLibcFunc = strconv.Itoa(len(chkFuncLibs)) + } else { + res.LibcSupport = "No" + res.LibcSupportColor = "red" + res.NumLibcFunc = "0" + } + + file, err := elf.Open(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + + dynSymbols, err := file.DynamicSymbols() + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + // Iterate through dynamic symbols and print their information + for _, sym := range dynSymbols { + fileFunc = append(fileFunc, strings.Trim(sym.Name, "__")) + } + + sort.Strings(chkFuncLibs) + sort.Strings(funcLibs) + sort.Strings(fileFunc) + + for _, item := range chkFuncLibs { + if isInSlice(item, fileFunc) { + checked++ + } + } + + total = checked + for _, item := range funcLibs { + if isInSlice(item, fileFunc) { + total++ + } + } + + if checked > 0 { + res.Output = "Yes" + res.Color = "green" + res.Fortified = strconv.Itoa(checked) + res.Fortifiable = strconv.Itoa(total) + res.NoFortify = strconv.Itoa(total - checked) + res.NumFileFunc = strconv.Itoa(len(dynSymbols)) + return &res + } else { + res.Output = "No" + res.Color = "red" + res.Fortified = strconv.Itoa(checked) + res.Fortifiable = strconv.Itoa(total) + res.NoFortify = strconv.Itoa(total - checked) + res.NumFileFunc = strconv.Itoa(len(dynSymbols)) + return &res + } + +} + +func GetLdd(filename string) string { + dynamic := false + file, err := elf.Open(filename) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + for _, prog := range file.Progs { + if prog.Type == elf.PT_DYNAMIC { + dynamic = true + } + } + + files, _ := uroot.FList(filename) + if dynamic && len(files) == 0 { + fmt.Println("Warning: Dynamic Binary found but missing libc. Fortify results will be skipped") + return "unk" + } + + for _, libc := range files { + if strings.Contains(libc, "libc.") { + return libc + } + } + return "none" +} + +func isInSlice(item string, slice []string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} diff --git a/pkg/checksec/kernel.go b/pkg/checksec/kernel.go new file mode 100644 index 0000000..9c7fb58 --- /dev/null +++ b/pkg/checksec/kernel.go @@ -0,0 +1,145 @@ +package checksec + +import ( + "bufio" + "compress/gzip" + "fmt" + "io" + "log" + "os" + "strings" +) + +func KernelConfig(name string) ([]interface{}, []interface{}) { + var Results []interface{} + var ColorResults []interface{} + kernelChecks := []map[string]interface{}{ + {"name": "CONFIG_COMPAT_BRK", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Kernel Heap Randomization"}}, + {"name": "CONFIG_STACKPROTECTOR", "values": map[string]string{"arch": "all", "expect": "is not set", "desc": "Stack Protector"}}, + {"name": "CONFIG_STACKPROTECTOR_STRONG", "values": map[string]string{"arch": "all", "expect": "is not set", "desc": "Stack Protector Strong"}}, + {"name": "CONFIG_CC_STACKPROTECTOR", "values": map[string]string{"arch": "all", "expect": "y", "desc": "GCC Stack Protector"}}, + {"name": "CONFIG_CC_STACKPROTECTOR_REGULAR", "values": map[string]string{"arch": "all", "expect": "y", "desc": "GCC Stack Protector Regular"}}, + {"name": "CONFIG_CC_STACKPROTECTOR_AUTO", "values": map[string]string{"arch": "all", "expect": "y", "desc": "GCC Stack Protector Auto"}}, + {"name": "CONFIG_CC_STACKPROTECTOR_STRONG", "values": map[string]string{"arch": "all", "expect": "y", "desc": "GCC Stack Protector Strong"}}, + {"name": "CONFIG_GCC_PLUGIN_STRUCTLEAK", "values": map[string]string{"arch": "all", "expect": "y", "desc": "GCC structleak plugin"}}, + {"name": "CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL", "values": map[string]string{"arch": "all", "expect": "y", "desc": "GCC structleak by ref plugin"}}, + {"name": "CONFIG_SLAB_FREELIST_RANDOM", "values": map[string]string{"arch": "all", "expect": "y", "desc": "SLAB freelist randomization"}}, + {"name": "CPU_SW_DOMAIN_PAN", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Use CPU domains"}}, + {"name": "CONFIG_VMAP_STACK", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Virtually-mapped kernel stack"}}, + {"name": "CONFIG_STRICT_DEVMEM", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Restrict /dev/mem access"}}, + {"name": "CONFIG_STRICT_KERNEL_RWX", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Restrict Kernel RWX"}}, + {"name": "CONFIG_STRICT_MODULE_RWX", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Restrict Module RWX"}}, + {"name": "CONFIG_IO_STRICT_DEVMEM", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Restrict I/O access to /dev/mem"}}, + {"name": "CONFIG_REFCOUNT_FULL", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Full reference count validation"}}, + {"name": "CONFIG_HARDENED_USERCOPY", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Hardened Usercopy"}}, + {"name": "CONFIG_FORTIFY_SOURCE", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Harden str/mem functions"}}, + {"name": "CONFIG_DEVKMEM", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Restrict /dev/kmem access"}}, + {"name": "CONFIG_DEBUG_STRICT_USER_COPY_CHECKS", "values": map[string]string{"arch": "amd", "expect": "y", "desc": "Strict user copy checks"}}, + {"name": "CONFIG_RANDOMIZE_BASE", "values": map[string]string{"arch": "amd", "expect": "y", "desc": "Address space layout randomization"}}, + {"name": "CONFIG_ARM_KERNMEM_PERMS", "values": map[string]string{"arch": "arm", "expect": "y", "desc": "Restrict kernel memory permissions"}}, + {"name": "CONFIG_DEBUG_ALIGN_RODATA", "values": map[string]string{"arch": "all", "expect": "y", "desc": "Make rodata strictly non-excutable"}}, + {"name": "CONFIG_UNMAP_KERNEL_AT_EL0", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Unmap kernel in userspace (KAISER)"}}, + {"name": "CONFIG_HARDEN_BRANCH_PREDICTOR", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Harden branch predictor"}}, + {"name": "CONFIG_HARDEN_EL2_VECTORS", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Harden EL2 vector mapping"}}, + {"name": "CONFIG_ARM64_SSBD", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Speculative store bypass disable"}}, + {"name": "CONFIG_ARM64_SW_TTBR0_PAN", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Emulate privileged access never"}}, + {"name": "CONFIG_RANDOMIZE_BASE", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Randomize address of kernel image"}}, + {"name": "CONFIG_RANDOMIZE_MODULE_REGION_FULL", "values": map[string]string{"arch": "arm64", "expect": "y", "desc": "Randomize module region over 4GB"}}, + {"name": "CONFIG_SECURITY_SELINUX", "values": map[string]string{"arch": "all", "expect": "y", "desc": "SELinux Kernel Flag"}}, + } + + data, err := parseKernelConfig(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + for configKey, configVal := range data { + for _, k := range kernelChecks { + var res []interface{} + var colors []interface{} + var output string + var color string + if k["name"] == configKey { + values := k["values"].(map[string]string) + if values["expect"] == configVal { + output = "Enabled" + color = "green" + } else { + output = "Disabled" + color = "red" + } + res = []interface{}{ + map[string]interface{}{ + "name": k["name"], + "value": output, + "desc": values["desc"], + "type": "Kernel Config", + }, + } + colors = []interface{}{ + map[string]interface{}{ + "name": k["name"], + "value": output, + "color": color, + "desc": values["desc"], + "type": "Kernel Config", + }, + } + } + Results = append(Results, res...) + ColorResults = append(ColorResults, colors...) + + } + } + return Results, ColorResults +} + +func parseKernelConfig(filename string) (map[string]string, error) { + stat, err := os.Stat(filename) + var bytes []byte + if err != nil { + return nil, err + } + if !stat.Mode().IsRegular() { + return nil, fmt.Errorf("Not a file: %s", filename) + } + if strings.HasSuffix(filename, ".gz") { + file, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + defer file.Close() + reader, err := gzip.NewReader(file) + if err != nil { + log.Fatal(err) + } + defer reader.Close() + + bytes, err = io.ReadAll(reader) + if err != nil { + log.Fatal(err) + } + } else { + bytes, err = os.ReadFile(filename) + if err != nil { + return nil, err + } + } + + options := make(map[string]string) + + scanner := bufio.NewScanner(strings.NewReader(string(bytes))) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "CONFIG_") { + split := strings.Split(line, "=") + options[split[0]] = strings.TrimPrefix(line, fmt.Sprintf("%s=", split[0])) + } else if strings.HasPrefix(line, "# CONFIG_") && strings.HasSuffix(scanner.Text(), "is not set") { + opt := strings.TrimPrefix(line, "# ") + opt = strings.TrimSuffix(opt, " is not set") + options[opt] = "is not set" + } + } + + return options, nil +} diff --git a/pkg/checksec/nx.go b/pkg/checksec/nx.go new file mode 100644 index 0000000..4de4a13 --- /dev/null +++ b/pkg/checksec/nx.go @@ -0,0 +1,25 @@ +package checksec + +import ( + "debug/elf" +) + +type nx struct { + Output string + Color string +} + +func NX(name string, binary *elf.File) *nx { + res := nx{} + for _, p := range binary.Progs { + if p.Type == elf.PT_GNU_STACK && p.Flags&elf.PF_X == 0 { + res.Color = "green" + res.Output = "NX enabled" + return &res + } + } + + res.Color = "red" + res.Output = "NX disabled" + return &res +} diff --git a/pkg/checksec/pie.go b/pkg/checksec/pie.go new file mode 100644 index 0000000..e51d76c --- /dev/null +++ b/pkg/checksec/pie.go @@ -0,0 +1,23 @@ +package checksec + +import ( + "debug/elf" +) + +type pie struct { + Output string + Color string +} + +func PIE(name string, binary *elf.File) *pie { + res := pie{} + if binary.Type == elf.ET_DYN { + res.Color = "green" + res.Output = "PIE Enabled" + return &res + } + + res.Color = "red" + res.Output = "PIE Disabled" + return &res +} diff --git a/pkg/checksec/relro.go b/pkg/checksec/relro.go new file mode 100644 index 0000000..c353ab8 --- /dev/null +++ b/pkg/checksec/relro.go @@ -0,0 +1,52 @@ +package checksec + +import ( + "debug/elf" + "fmt" + "os" +) + +type relro struct { + Output string + Color string +} + +func RELRO(name string) *relro { + res := relro{} + relroHeader := false + bindNow := false + + // To get the dynamic values + // Open the ELF binary file + file, err := elf.Open(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + + bind, _ := file.DynValue(24) + if len(bind) > 0 && bind[0] == 0 { + bindNow = true + } + + for _, prog := range file.Progs { + if prog.Type == elf.PT_GNU_RELRO { + relroHeader = true + } + } + + if bindNow == true { + res.Color = "green" + res.Output = "Full RELRO" + return &res + } else if relroHeader == true { + res.Color = "yellow" + res.Output = "Partial RELRO" + return &res + } else { + res.Color = "red" + res.Output = "No RELRO" + return &res + } +} diff --git a/pkg/checksec/rpath.go b/pkg/checksec/rpath.go new file mode 100644 index 0000000..ab81b54 --- /dev/null +++ b/pkg/checksec/rpath.go @@ -0,0 +1,32 @@ +package checksec + +import ( + "debug/elf" + "fmt" + "os" +) + +type rpath struct { + Output string + Color string +} + +func RPATH(name string) *rpath { + res := rpath{} + file, err := elf.Open(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + + rpath, _ := file.DynValue(15) + if len(rpath) == 0 { + res.Output = "No RPATH" + res.Color = "green" + } else { + res.Output = "RPATH" + res.Color = "red" + } + return &res +} diff --git a/pkg/checksec/runpath.go b/pkg/checksec/runpath.go new file mode 100644 index 0000000..8b294dc --- /dev/null +++ b/pkg/checksec/runpath.go @@ -0,0 +1,32 @@ +package checksec + +import ( + "debug/elf" + "fmt" + "os" +) + +type runpath struct { + Output string + Color string +} + +func RUNPATH(name string) *runpath { + res := runpath{} + file, err := elf.Open(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + + runpath, _ := file.DynValue(29) + if len(runpath) == 0 { + res.Output = "No RUNPATH" + res.Color = "green" + } else { + res.Output = "RUNPATH" + res.Color = "red" + } + return &res +} diff --git a/pkg/checksec/symbols.go b/pkg/checksec/symbols.go new file mode 100644 index 0000000..782d262 --- /dev/null +++ b/pkg/checksec/symbols.go @@ -0,0 +1,32 @@ +package checksec + +import ( + "debug/elf" + "fmt" + "os" +) + +type symbols struct { + Output string + Color string +} + +func SYMBOLS(name string) *symbols { + res := symbols{} + file, err := elf.Open(name) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + defer file.Close() + + symbols, _ := file.Symbols() + if len(symbols) == 0 { + res.Output = "No Symbols" + res.Color = "green" + } else { + res.Output = fmt.Sprintf("%d symbols", len(symbols)) + res.Color = "red" + } + return &res +} diff --git a/pkg/checksec/sysctl.go b/pkg/checksec/sysctl.go new file mode 100644 index 0000000..d3421a5 --- /dev/null +++ b/pkg/checksec/sysctl.go @@ -0,0 +1,66 @@ +package checksec + +import ( + "runtime" + + "github.com/lorenzosaino/go-sysctl" +) + +func SysctlCheck() ([]interface{}, []interface{}) { + var Results []interface{} + var ColorResults []interface{} + + sysctlChecks := []map[string]interface{}{ + {"name": "fs.protected_symlinks", "desc": "Protected symlinks", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Enabled", "color": "green"}}}, + {"name": "fs.protected_hardlinks", "desc": "Protected hardlinks", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Enabled", "color": "green"}}}, + {"name": "net.ipv4.conf.all.rp_filter", "desc": "Ipv4 reverse path filtering", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Enabled", "color": "green"}}}, + {"name": "kernel.yama.ptrace_scope", "desc": "YAMA", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Enabled", "color": "green"}}}, + {"name": "kernel.exec-shield", "desc": "Exec Shield", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Enabled", "color": "green"}}}, + {"name": "kernel.randomize_va_space", "desc": "Vanilla Kernel ASLR", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Partial", "color": "yellow"}, "2": {"res": "Enabled", "color": "green"}}}, + {"name": "fs.protected_fifos", "desc": "Protected fifos", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Partial", "color": "yellow"}, "2": {"res": "Enabled", "color": "green"}}}, + {"name": "fs.protected_regular", "desc": "Protected regular", "values": map[string]map[string]string{"0": {"res": "Disabled", "color": "red"}, "1": {"res": "Partial", "color": "yellow"}, "2": {"res": "Enabled", "color": "green"}}}, + } + + for _, s := range sysctlChecks { + var res []interface{} + var colors []interface{} + var output string + var color string + check, _ := sysctl.Get(s["name"].(string)) + + values := s["values"].(map[string]map[string]string) + + if len(check) == 0 { + if runtime.GOOS == "linux" { + output = "Unknown" + } else { + output = "N/A" + } + color = "italic" + } else { + output = values[check]["res"] + color = values[check]["color"] + } + res = []interface{}{ + map[string]interface{}{ + "name": s["name"], + "value": output, + "desc": s["desc"], + "type": "Sysctl", + }, + } + colors = []interface{}{ + map[string]interface{}{ + "name": s["name"], + "value": output, + "color": color, + "desc": s["desc"], + "type": "Sysctl", + }, + } + Results = append(Results, res...) + ColorResults = append(ColorResults, colors...) + } + + return Results, ColorResults +} diff --git a/pkg/utils/checks.go b/pkg/utils/checks.go new file mode 100644 index 0000000..1146391 --- /dev/null +++ b/pkg/utils/checks.go @@ -0,0 +1,81 @@ +package utils + +import ( + "checksec/pkg/checksec" + "reflect" +) + +// RunFileChecks - Run the file checks +func RunFileChecks(filename string) ([]interface{}, []interface{}) { + + binary := GetBinary(filename) + relro := checksec.RELRO(filename) + canary := checksec.Canary(filename) + nx := checksec.NX(filename, binary) + pie := checksec.PIE(filename, binary) + rpath := checksec.RPATH(filename) + runpath := checksec.RUNPATH(filename) + symbols := checksec.SYMBOLS(filename) + fortify := checksec.Fortify(filename, binary) + + data := []interface{}{ + map[string]interface{}{ + "name": filename, + "checks": map[string]interface{}{ + "relro": relro.Output, + "canary": canary.Output, + "nx": nx.Output, + "pie": pie.Output, + "rpath": rpath.Output, + "runpath": runpath.Output, + "symbols": symbols.Output, + "fortify_source": fortify.Output, + "fortified": fortify.Fortified, + "fortifyable": fortify.Fortifiable, + }, + }, + } + + color := []interface{}{ + map[string]interface{}{ + "name": filename, + "checks": map[string]interface{}{ + "canary": canary.Output, + "canaryColor": canary.Color, + "fortified": fortify.Fortified, + "fortifiedColor": "unset", + "fortifyable": fortify.Fortifiable, + "fortifyableColor": "unset", + "fortify_source": fortify.Output, + "fortify_sourceColor": fortify.Color, + "nx": nx.Output, + "nxColor": nx.Color, + "pie": pie.Output, + "pieColor": pie.Color, + "relro": relro.Output, + "relroColor": relro.Color, + "rpath": rpath.Output, + "rpathColor": rpath.Color, + "runpath": runpath.Output, + "runpathColor": runpath.Color, + "symbols": symbols.Output, + "symbolsColor": symbols.Color, + }, + }, + } + + return data, color +} + +// ParseKernel - Parses the kernel config and runs the checks +func ParseKernel(filename string) (any, any) { + + kernelCheckResults, kernelCheckResultsColors := checksec.KernelConfig(filename) + sysctlCheckResults, sysctlCheckResultsColors := checksec.SysctlCheck() + + data := reflect.AppendSlice(reflect.ValueOf(kernelCheckResults), reflect.ValueOf(sysctlCheckResults)).Interface() + dataColors := reflect.AppendSlice(reflect.ValueOf(kernelCheckResultsColors), reflect.ValueOf(sysctlCheckResultsColors)).Interface() + + return data, dataColors + +} diff --git a/pkg/utils/filePrinter.go b/pkg/utils/filePrinter.go new file mode 100644 index 0000000..41788db --- /dev/null +++ b/pkg/utils/filePrinter.go @@ -0,0 +1,122 @@ +package utils + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "log" + + "sigs.k8s.io/yaml" +) + +type SecurityCheck struct { + Name string `json:"name"` + Checks struct { + Canary string `json:"canary"` + Fortified string `json:"fortified"` + FortifyAble string `json:"fortifyable"` + FortifySource string `json:"fortify_source"` + NX string `json:"nx"` + PIE string `json:"pie"` + Relro string `json:"relro"` + RPath string `json:"rpath"` + RunPath string `json:"runpath"` + Symbols string `json:"symbols"` + } `json:"checks"` +} + +type SecurityCheckColor struct { + Name string `json:"name"` + Checks struct { + Canary string `json:"canary"` + CanaryColor string `json:"canaryColor"` + Fortified string `json:"fortified"` + FortifyAble string `json:"fortifyable"` + FortifySource string `json:"fortify_source"` + FortifySourceColor string `json:"fortify_sourceColor"` + NX string `json:"nx"` + NXColor string `json:"nxColor"` + PIE string `json:"pie"` + PIEColor string `json:"pieColor"` + Relro string `json:"relro"` + RelroColor string `json:"relroColor"` + RPath string `json:"rpath"` + RPathColor string `json:"rpathColor"` + RunPath string `json:"runpath"` + RunPathColor string `json:"runpathColor"` + Symbols string `json:"symbols"` + SymbolsColor string `json:"symbolsColor"` + } `json:"checks"` +} + +func FilePrinter(outputFormat string, data interface{}, colors interface{}) { + + formatted, err := json.MarshalIndent(data, "", " ") + if err != nil { + fmt.Printf("err: %v\n", err) + } + formattedcolor, err := json.MarshalIndent(colors, "", " ") + if err != nil { + fmt.Printf("err: %v\n", err) + } + var securityChecks []SecurityCheck + // Unmarshal JSON data + if err := json.Unmarshal([]byte(formatted), &securityChecks); err != nil { + fmt.Println("Error:", err) + return + } + + if outputFormat == "yaml" { + yamlResponse, err := yaml.JSONToYAML(formatted) + if err != nil { + fmt.Printf("err: %v\n", err) + } + fmt.Println(string(yamlResponse)) + } else if outputFormat == "json" { + fmt.Println(string(formatted)) + } else if outputFormat == "xml" { + xmlData, err := xml.MarshalIndent(securityChecks, "", " ") + if err != nil { + log.Fatal(err) + } + fmt.Println(string(xmlData)) + } else { + PrintLogo() + var securityChecksColors []SecurityCheckColor + + // Unmarshal JSON data + if err = json.Unmarshal([]byte(formattedcolor), &securityChecksColors); err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Printf("%-24s%-26s%-22s%-24s%-19s%-21s%-24s%-19s%-20s%-25s%-40s\n", + colorPrinter("RELRO", "unset"), + colorPrinter("Stack Canary", "unset"), + colorPrinter("NX", "unset"), + colorPrinter("PIE", "unset"), + colorPrinter("RPATH", "unset"), + colorPrinter("RUNPATH", "unset"), + colorPrinter("Symbols", "unset"), + colorPrinter("FORTIFY", "unset"), + colorPrinter("Fortified", "unset"), + colorPrinter("Fortifiable", "unset"), + colorPrinter("Name", "unset"), + ) + for _, check := range securityChecksColors { + fmt.Printf("%-25s%-27s%-23s%-25s%-20s%-22s%-25s%-20s%-20s%-25s%-40s\n", + colorPrinter(check.Checks.Relro, check.Checks.RelroColor), + colorPrinter(check.Checks.Canary, check.Checks.CanaryColor), + colorPrinter(check.Checks.NX, check.Checks.NXColor), + colorPrinter(check.Checks.PIE, check.Checks.PIEColor), + colorPrinter(check.Checks.RPath, check.Checks.RPathColor), + colorPrinter(check.Checks.RunPath, check.Checks.RunPathColor), + colorPrinter(check.Checks.Symbols, check.Checks.SymbolsColor), + colorPrinter(check.Checks.FortifySource, check.Checks.FortifySourceColor), + colorPrinter(check.Checks.Fortified, "unset"), + colorPrinter(check.Checks.FortifyAble, "unset"), + colorPrinter(check.Name, "unset"), + ) + } + } +} diff --git a/pkg/utils/files.go b/pkg/utils/files.go new file mode 100644 index 0000000..e43bd89 --- /dev/null +++ b/pkg/utils/files.go @@ -0,0 +1,122 @@ +package utils + +import ( + "debug/elf" + "fmt" + "io/fs" + "log" + "os" + "path/filepath" +) + +// CheckElfExists - Check if file exists and is an Elf file +func CheckElfExists(fileName string) bool { + if !CheckFileExists(fileName) { + fmt.Println("File not found:", fileName) + os.Exit(1) + } + if !CheckIfElf(fileName) { + fmt.Println("File is not an ELF file:", fileName) + os.Exit(1) + } + + return true +} + +// CheckIfElf - Check if the file is an Elf file +func CheckIfElf(fileName string) bool { + _, err := elf.Open(fileName) + if err != nil { + return false + } + + return true +} + +// CheckDirExists - Check if the directory exists +func CheckDirExists(dirName string) bool { + dirInfo, err := os.Stat(dirName) + if err != nil { + if os.IsNotExist(err) { + fmt.Println("Directory not found:", dirName) + os.Exit(1) + } else { + fmt.Println("An error occurred:", err) + os.Exit(1) + } + } + + if !dirInfo.IsDir() { + fmt.Printf("%s is not a Directory", dirName) + os.Exit(1) + } + + return true +} + +// CheckFileExists - check if the file exists +func CheckFileExists(fileName string) bool { + _, err := os.Stat(fileName) + if err != nil { + if os.IsNotExist(err) { + fmt.Println("File not found:", fileName) + os.Exit(1) + } else { + fmt.Println("An error occurred:", err) + os.Exit(1) + } + } + + return true +} + +// GetAllFilesFromDir - get the list of all elf files from a directory (or recursively) +func GetAllFilesFromDir(dirName string, recursive bool) []string { + var results []string + var fileList []string + + if recursive { + filepath.WalkDir(dirName, func(path string, file fs.DirEntry, err error) error { + if err != nil { + return err + } + if !file.IsDir() && CheckIfElf(path) && file.Type().IsRegular() { + results = append(results, path) + } + + return nil + }) + } else { + fileList, _ = filepath.Glob(fmt.Sprintf("%s/*", dirName)) + for _, j := range fileList { + dirInfo, _ := os.Stat(j) + if j != "." && !dirInfo.IsDir() && CheckIfElf(j) { + results = append(results, j) + } + } + + } + + if len(results) == 0 { + log.Fatalf("Error: No binary files found in %s", dirName) + } + return results +} + +// GetBinary - Return the binary details +func GetBinary(fileName string) *elf.File { + + binary, err := elf.Open(fileName) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer func(f *elf.File) { + err := f.Close() + if err != nil { + log.Fatal(err) + } + }(binary) + + return binary +} diff --git a/pkg/utils/fortifyPrinter.go b/pkg/utils/fortifyPrinter.go new file mode 100644 index 0000000..6d9b129 --- /dev/null +++ b/pkg/utils/fortifyPrinter.go @@ -0,0 +1,100 @@ +package utils + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "log" + + "sigs.k8s.io/yaml" +) + +// FortifyCheck struct for non-colored data, +// keeping a dedicated struct allows conversion without removing the colors +type FortifyCheck struct { + Name string `json:"name"` + Checks struct { + Fortified string `json:"fortified"` + FortifyAble string `json:"fortifyable"` + FortifySource string `json:"fortify_source"` + NoFortify string `json:"noFortify"` + LibcSupport string `json:"libcSupport"` + NumLibcFunc string `json:"numLibcFunc"` + NumFileFunc string `json:"numFileFunc"` + } `json:"checks"` +} + +// FortifyCheckColor struct for colored data +type FortifyCheckColor struct { + Name string `json:"name"` + Checks struct { + Fortified string `json:"fortified"` + FortifyAble string `json:"fortifyable"` + FortifySource string `json:"fortify_source"` + FortifySourceColor string `json:"fortify_sourceColor"` + NoFortify string `json:"noFortify"` + LibcSupport string `json:"libcSupport"` + LibcSupportColor string `json:"libcSupportColor"` + NumLibcFunc string `json:"numLibcFunc"` + NumFileFunc string `json:"numFileFunc"` + } `json:"checks"` +} + +// FortifyPrinter - Print the output from FortifyFile function +func FortifyPrinter(outputFormat string, data interface{}, colors interface{}) { + + formatted, err := json.MarshalIndent(data, "", " ") + if err != nil { + fmt.Printf("err: %v\n", err) + } + formattedcolor, err := json.MarshalIndent(colors, "", " ") + if err != nil { + fmt.Printf("err: %v\n", err) + } + var fortifyChecks []FortifyCheck + // Unmarshal JSON data + if err := json.Unmarshal([]byte(formatted), &fortifyChecks); err != nil { + fmt.Println("Error:", err) + return + } + + if outputFormat == "yaml" { + yamlResponse, err := yaml.JSONToYAML(formatted) + if err != nil { + fmt.Printf("err: %v\n", err) + } + fmt.Println(string(yamlResponse)) + } else if outputFormat == "json" { + fmt.Println(string(formatted)) + } else if outputFormat == "xml" { + xmlData, err := xml.MarshalIndent(fortifyChecks, "", " ") + if err != nil { + log.Fatal(err) + } + fmt.Println(string(xmlData)) + } else { + PrintLogo() + var fortifyChecksColors []FortifyCheckColor + + // Unmarshal JSON data + if err = json.Unmarshal([]byte(formattedcolor), &fortifyChecksColors); err != nil { + fmt.Println("Error:", err) + return + } + + for _, check := range fortifyChecksColors { + fmt.Printf("* FORTIFY_SOURCE support available (libc): %s\n", colorPrinter(check.Checks.LibcSupport, check.Checks.LibcSupportColor)) + fmt.Printf("* Binary compiled with FORTIFY_SOURCE support: %s\n\n", colorPrinter(check.Checks.FortifySource, check.Checks.FortifySourceColor)) + fmt.Println("------ EXECUTABLE-FILE ------- | -------- LIBC --------") + fmt.Println("Fortifiable library functions | Checked function names") + // TODO: add function breakdown + fmt.Println("Coming Soon") + fmt.Printf("\n%s\n", colorPrinter("SUMMARY", "green")) + fmt.Printf("* Number of checked functions in libc : %s\n", colorPrinter(check.Checks.NumLibcFunc, "unset")) + fmt.Printf("* Total number of library functions in the executable: %s\n", colorPrinter(check.Checks.NumFileFunc, "unset")) + fmt.Printf("* Number of Fortifiable functions in the executable : %s\n", colorPrinter(check.Checks.FortifyAble, "unset")) + fmt.Printf("* Number of checked functions in the executable : %s\n", colorPrinter(check.Checks.Fortified, "green")) + fmt.Printf("* Number of unchecked functions in the executable : %s\n", colorPrinter(check.Checks.NoFortify, "red")) + } + } +} diff --git a/pkg/utils/kernelPrinter.go b/pkg/utils/kernelPrinter.go new file mode 100644 index 0000000..3d97b65 --- /dev/null +++ b/pkg/utils/kernelPrinter.go @@ -0,0 +1,86 @@ +package utils + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "log" + + "sigs.k8s.io/yaml" +) + +type KernelCheckColor struct { + Name string `json:"name"` + Description string `json:"desc"` + Color string `json:"color"` + Value string `json:"value"` + CheckType string `json:"type"` +} + +type KernelCheck struct { + Name string `json:"name"` + Description string `json:"desc"` + Value string `json:"value"` + CheckType string `json:"type"` +} + +func KernelPrinter(outputFormat string, kernel any, kernelColors any) { + + formattedKernel, err := json.MarshalIndent(kernel, "", " ") + if err != nil { + fmt.Printf("err: %v\n", err) + } + + formattedKernelColors, err := json.MarshalIndent(kernelColors, "", " ") + if err != nil { + fmt.Printf("err: %v\n", err) + } + + var KernelCheck []KernelCheck + // Unmarshal JSON data + if err := json.Unmarshal([]byte(formattedKernel), &KernelCheck); err != nil { + fmt.Println("Error:", err) + return + } + + var KernelCheckColor []KernelCheckColor + // Unmarshal JSON data + if err := json.Unmarshal([]byte(formattedKernelColors), &KernelCheckColor); err != nil { + fmt.Println("Error:", err) + return + } + + if outputFormat == "yaml" { + yamlResponse, err := yaml.JSONToYAML(formattedKernel) + if err != nil { + fmt.Printf("err: %v\n", err) + } + fmt.Println(string(yamlResponse)) + } else if outputFormat == "json" { + fmt.Println(string(formattedKernel)) + } else if outputFormat == "xml" { + xmlData, err := xml.MarshalIndent(KernelCheck, "", " ") + if err != nil { + log.Fatal(err) + } + fmt.Println(string(xmlData)) + } else { + PrintLogo() + fmt.Println("Kernel configs only print what is supported by the specific kernel/kernel config") + fmt.Printf("%-70s%-25s%-30s%-30s\n", + colorPrinter("Description", "unset"), + colorPrinter("Value", "unset"), + colorPrinter("Check Type", "unset"), + colorPrinter("Config Key", "unset"), + ) + for _, check := range KernelCheckColor { + fmt.Printf("%-70s%-26s%-30s%-30s\n", + colorPrinter(check.Description, "unset"), + colorPrinter(check.Value, check.Color), + colorPrinter(check.CheckType, "unset"), + colorPrinter(check.Name, "unset"), + ) + } + } + +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..b4e5865 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,43 @@ +package utils + +import ( + "github.com/fatih/color" +) + +func PrintLogo() { + Red := color.New(color.FgHiRed, color.Bold) + asciiLogo := ` +_______ _______ _______ _ _______ _______ _______ +( ____ \|\ /|( ____ \( ____ \| \ /\( ____ \( ____ \( ____ \ +| ( \/| ) ( || ( \/| ( \/| \ / /| ( \/| ( \/| ( \/ +| | | (___) || (__ | | | (_/ / | (_____ | (__ | | +| | | ___ || __) | | | _ ( (_____ )| __) | | +| | | ( ) || ( | | | ( \ \ ) || ( | | +| (____/\| ) ( || (____/\| (____/\| / \ \/\____) || (____/\| (____/\ +(_______/|/ \|(_______/(_______/|_/ \/\_______)(_______/(_______/ +` + Red.Println(asciiLogo) +} + +func colorPrinter(result string, resultColor string) string { + unset := color.New(color.Reset).SprintFunc() + italic := color.New(color.Italic).SprintfFunc() + red := color.New(color.FgRed).SprintFunc() + green := color.New(color.FgGreen).SprintFunc() + yellow := color.New(color.FgYellow).SprintFunc() + blue := color.New(color.FgBlue).SprintFunc() + + if resultColor == "green" { + return green(result) + } else if resultColor == "red" { + return red(result) + } else if resultColor == "yellow" { + return yellow(result) + } else if resultColor == "blue" { + return blue(result) + } else if resultColor == "italic" { + return italic(result) + } else { + return unset(result) + } +}