├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── README.md ├── canary.go ├── checksec.go ├── dyntag.go ├── nx.go ├── pie.go ├── relro.go ├── rpath.go ├── rpm-find-exec.sh └── runpath.go /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '28 0 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | checksec 2 | ======== 3 | 4 | This is a rough port of the checksec.sh script by Tobias Klein 5 | to Go. 6 | 7 | 8 | -------------------------------------------------------------------------------- /canary.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | ) 7 | 8 | const STACK_CHK = "__stack_chk_fail" 9 | 10 | func canary(file *elf.File) string { 11 | 12 | if symbols, e := file.Symbols(); e == nil { 13 | for _, sym := range symbols { 14 | if bytes.HasPrefix([]byte(sym.Name), []byte(STACK_CHK)) { 15 | return ENABLED 16 | } 17 | } 18 | } 19 | 20 | if importedSymbols, e := file.ImportedSymbols(); e == nil { 21 | for _, imp := range importedSymbols { 22 | if bytes.HasPrefix([]byte(imp.Name), []byte(STACK_CHK)) { 23 | return ENABLED 24 | } 25 | } 26 | } 27 | 28 | return DISABLED 29 | } 30 | -------------------------------------------------------------------------------- /checksec.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | "fmt" 7 | "io" 8 | "os" 9 | ) 10 | 11 | const ( 12 | INVALID = "Not an ELF binary" 13 | DISABLED = "Disabled" 14 | ENABLED = "Enabled" 15 | PARTIAL = "Partial" 16 | SEP = "," 17 | ) 18 | 19 | type checker func(file *elf.File) string 20 | 21 | var checks = []struct { 22 | name string 23 | run checker 24 | }{ 25 | {"NX", nx}, 26 | {"CANARY", canary}, 27 | {"RELRO", relro}, 28 | {"PIE", pie}, 29 | {"RPATH", rpath}, 30 | {"RUNPATH", runpath}, 31 | } 32 | 33 | func checksec(file *elf.File) { 34 | 35 | for n, check := range checks { 36 | fmt.Print(check.name, "=", check.run(file)) 37 | if n < len(checks)-1 { 38 | fmt.Print(SEP) 39 | } 40 | } 41 | fmt.Println() 42 | } 43 | 44 | func main() { 45 | 46 | // cat | ./a.out 47 | if len(os.Args) == 1 { 48 | 49 | var buf bytes.Buffer 50 | if _, err := io.Copy(&buf, os.Stdin); err != nil { 51 | fmt.Println(err) 52 | os.Exit(1) 53 | } 54 | 55 | data := buf.Bytes() 56 | if len(data) > 0 { 57 | file, e := elf.NewFile(bytes.NewReader(data)) 58 | if e != nil { 59 | fmt.Println(INVALID) 60 | os.Exit(1) 61 | } 62 | checksec(file) 63 | } 64 | 65 | // FILE [FILE]* 66 | } else { 67 | 68 | for _, arg := range os.Args[1:] { 69 | 70 | file, e := elf.Open(arg) 71 | if e != nil { 72 | fmt.Printf("%s,%s\n", arg, INVALID) 73 | 74 | } else { 75 | fmt.Print(arg, SEP) 76 | checksec(file) 77 | file.Close() 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /dyntag.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "debug/elf" 5 | "encoding/binary" 6 | "io" 7 | ) 8 | 9 | func haveDynTag(file *elf.File, tag elf.DynTag) bool { 10 | 11 | if section := file.Section(".dynamic"); section != nil { 12 | 13 | reader := io.NewSectionReader(section, 0, int64(section.Size)) 14 | switch file.Machine { 15 | 16 | case elf.EM_X86_64: 17 | for { 18 | var entry elf.Dyn64 19 | if err := binary.Read(reader, binary.LittleEndian, &entry); err != io.EOF { 20 | if elf.DynTag(entry.Tag) == tag { 21 | return true 22 | } 23 | } else { 24 | break 25 | } 26 | } 27 | 28 | case elf.EM_386: 29 | for { 30 | var entry elf.Dyn32 31 | if err := binary.Read(reader, binary.LittleEndian, &entry); err != io.EOF { 32 | if elf.DynTag(entry.Tag) == tag { 33 | return true 34 | } 35 | } else { 36 | break 37 | } 38 | } 39 | } 40 | } 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /nx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include 4 | //#include 5 | //int64_t GNU_STACK = PT_GNU_STACK; 6 | import "C" 7 | import "debug/elf" 8 | 9 | func nx(file *elf.File) string { 10 | 11 | rv := ENABLED 12 | for _, prog := range file.Progs { 13 | 14 | if int64(prog.Type) == int64(C.GNU_STACK) { 15 | if prog.Flags&elf.PF_X == elf.PF_X { 16 | rv = DISABLED 17 | } 18 | } 19 | } 20 | return rv 21 | } 22 | -------------------------------------------------------------------------------- /pie.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "debug/elf" 4 | 5 | func pie(file *elf.File) string { 6 | 7 | if file.Type == elf.ET_DYN { 8 | return ENABLED 9 | } 10 | return DISABLED 11 | } 12 | -------------------------------------------------------------------------------- /relro.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include 4 | //#include 5 | //int64_t GNU_RELRO = PT_GNU_RELRO; 6 | import "C" 7 | import "debug/elf" 8 | 9 | func relro(file *elf.File) string { 10 | 11 | haveRelro := false 12 | 13 | for _, prog := range file.Progs { 14 | if int64(prog.Type) == int64(C.GNU_RELRO) { 15 | haveRelro = true 16 | break 17 | } 18 | } 19 | 20 | if haveDynTag(file, elf.DT_BIND_NOW) && haveRelro { 21 | return ENABLED 22 | } 23 | 24 | if haveRelro { 25 | return PARTIAL 26 | } 27 | return DISABLED 28 | 29 | } 30 | -------------------------------------------------------------------------------- /rpath.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "debug/elf" 4 | 5 | func rpath(file *elf.File) string { 6 | 7 | if haveDynTag(file, elf.DT_RPATH) { 8 | return ENABLED 9 | } 10 | return DISABLED 11 | } 12 | -------------------------------------------------------------------------------- /rpm-find-exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Produces a csv list of all executable files and the packages that they belong to 3 | # including the details of whether they have been built using security hardened 4 | # options. 5 | 6 | for file in "$@"; do 7 | for data in `rpm -qp --queryformat '[%{NAME} %{FILEMODES:perms} %{FILENAMES}\n]' $file | grep -E "^.* -..x..x..x " | awk '{ print sprintf("%s:%s", $1, $3)}'| grep -v lib`; do 8 | 9 | split=`echo $data | sed -e 's/\:/ /'` 10 | set -- $split 11 | rv=`rpm2cpio $file | cpio --to-stdout -iv .$2 2>/dev/null | ./checksec` 12 | if test $? -eq 0; then 13 | echo $(basename $file),$2,$rv 14 | fi 15 | done 16 | done 17 | -------------------------------------------------------------------------------- /runpath.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "debug/elf" 4 | 5 | func runpath(file *elf.File) string { 6 | 7 | if haveDynTag(file, elf.DT_RUNPATH) { 8 | return ENABLED 9 | } 10 | return DISABLED 11 | } 12 | --------------------------------------------------------------------------------