Skip to content

Commit

Permalink
Add mach-o support (#1)
Browse files Browse the repository at this point in the history
* Add mach-o support

* Update readme
  • Loading branch information
liamg authored Nov 25, 2021
1 parent 14a9145 commit d48e6b4
Show file tree
Hide file tree
Showing 13 changed files with 306 additions and 22 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Analyse binaries for missing security features, information disclosure and more.

:construction: Extrude is in the early stages of development, and currently only supports ELF binaries.
:construction: Extrude is in the early stages of development, and currently only supports ELF and MachO binaries. PE (Windows) binaries will be supported soon.

![Screenshot](screenshot.png)

Expand All @@ -27,9 +27,31 @@ You can optionally run extrude with docker via:
docker run -v `pwd`:/blah -it ghcr.io/liamg/extrude /blah/targetfile
```

## Supported Checks

### ELF

- PIE
- RELRO
- BIND NOW
- Fortified Source
- Stack Canary
- NX Stack

### MachO

- PIE
- Stack Canary
- NX Stack
- NX Heap
- ARC

### Windows

_Coming soon..._

## TODO

- Add support for Mach-o
- Add support for PE
- Add secret scanning
- Detect packers
Expand Down
7 changes: 1 addition & 6 deletions pkg/format/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,10 @@ var definitions = []definition{
},
},
{
format: MachO32,
format: MachO,
signatures: [][]byte{
{0xfe, 0xed, 0xfa, 0xce},
{0xce, 0xfa, 0xed, 0xfe},
},
},
{
format: MachO64,
signatures: [][]byte{
{0xfe, 0xed, 0xfa, 0xcf},
{0xcf, 0xfa, 0xed, 0xfe},
},
Expand Down
15 changes: 5 additions & 10 deletions pkg/format/formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@ type Format uint8
const (
Unknown Format = iota
ELF
MachO32
MachO64
MachO
PE
)

func (f Format) Short() string {
switch f {
case ELF:
return "ELF"
case MachO32:
return "Mach-O 32"
case MachO64:
return "Mach-O 64"
case MachO:
return "Mach-O"
case PE:
return "PE"
}
Expand All @@ -30,10 +27,8 @@ func (f Format) Long() string {
switch f {
case ELF:
return "Executable and Linkable Format"
case MachO32:
return "32-bit Mach Object File"
case MachO64:
return "64-bit Mach Object File"
case MachO:
return "Mach Object File"
case PE:
return "Portable Executable"
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/format/sniff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ func TestFormatSniffing(t *testing.T) {
}{
{
content: []byte{0xfe, 0xed, 0xfa, 0xce},
expected: MachO32,
expected: MachO,
},
{
content: []byte{0xce, 0xfa, 0xed, 0xfe},
expected: MachO32,
expected: MachO,
},
{
content: []byte{0xfe, 0xed, 0xfa, 0xcf},
expected: MachO64,
expected: MachO,
},
{
content: []byte{0xcf, 0xfa, 0xed, 0xfe},
expected: MachO64,
expected: MachO,
},
{
content: []byte{0x7f, 'E', 'L', 'F'},
Expand Down
19 changes: 19 additions & 0 deletions pkg/parser/macho/analyse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package macho

import "github.com/liamg/extrude/pkg/parser/macho/hardening"

func (m *Metadata) analyse() error {

if m.fat != nil {
var coreAttr hardening.Attributes
for _, arch := range m.fat.Arches {
attr := hardening.IdentifyAttributes(arch.File)
coreAttr = coreAttr.Merge(attr)
}
m.Hardening = coreAttr
} else {
m.Hardening = hardening.IdentifyAttributes(m.thin)
}

return nil
}
18 changes: 18 additions & 0 deletions pkg/parser/macho/hardening/arc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hardening

import (
"debug/macho"
)

func checkAutomaticReferenceCounting(f *macho.File) bool {
symbols, err := f.ImportedSymbols()
if err != nil {
return false
}
for _, imp := range symbols {
if imp == "_objc_release" {
return true
}
}
return false
}
12 changes: 12 additions & 0 deletions pkg/parser/macho/hardening/encryption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package hardening

import "debug/macho"

const (
EncInfo32 = 0x21
EncInfo64 = 0x2c
)

func checkEncrypted(f *macho.File) bool {
return f.Symtab.Cmd&EncInfo32 > 0 || f.Symtab.Cmd&EncInfo64 > 0
}
42 changes: 42 additions & 0 deletions pkg/parser/macho/hardening/hardening.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package hardening

import "debug/macho"

type Attributes struct {
init bool
PositionIndependentExecutable bool
StackExecutionNotAllowed bool
HeapExecutionNotAllowed bool
StackProtected bool
AutomaticReferenceCounting bool
Encrypted bool
}

func (a Attributes) Merge(b Attributes) Attributes {
if !a.init {
return b
}
if !b.init {
return a
}
return Attributes{
PositionIndependentExecutable: a.PositionIndependentExecutable && b.PositionIndependentExecutable,
StackExecutionNotAllowed: a.StackExecutionNotAllowed && b.StackExecutionNotAllowed,
HeapExecutionNotAllowed: a.HeapExecutionNotAllowed && b.HeapExecutionNotAllowed,
StackProtected: a.StackProtected && b.StackProtected,
AutomaticReferenceCounting: a.AutomaticReferenceCounting && b.AutomaticReferenceCounting,
Encrypted: a.Encrypted && b.Encrypted,
}
}

func IdentifyAttributes(f *macho.File) Attributes {
return Attributes{
init: true,
PositionIndependentExecutable: f.Flags&macho.FlagPIE > 0,
StackExecutionNotAllowed: f.Flags&macho.FlagAllowStackExecution == 0,
HeapExecutionNotAllowed: f.Flags&macho.FlagNoHeapExecution > 0,
StackProtected: checkStackProtected(f),
AutomaticReferenceCounting: checkAutomaticReferenceCounting(f),
Encrypted: checkEncrypted(f),
}
}
18 changes: 18 additions & 0 deletions pkg/parser/macho/hardening/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hardening

import (
"debug/macho"
)

func checkStackProtected(f *macho.File) bool {
symbols, err := f.ImportedSymbols()
if err != nil {
return false
}
for _, imp := range symbols {
if imp == "___stack_chk_fail" || imp == "___stack_chk_guard" {
return true
}
}
return false
}
25 changes: 25 additions & 0 deletions pkg/parser/macho/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package macho

import (
"debug/macho"

"github.com/liamg/extrude/pkg/format"
"github.com/liamg/extrude/pkg/parser/macho/hardening"
)

type Metadata struct {
File struct {
Path string
Name string
Format format.Format
}
Hardening hardening.Attributes
thin *macho.File
fat *macho.FatFile
Notes []Note
}

type Note struct {
Heading string
Content string
}
46 changes: 46 additions & 0 deletions pkg/parser/macho/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package macho

import (
"debug/macho"
"io"
"path/filepath"

"github.com/liamg/extrude/pkg/format"
"github.com/liamg/extrude/pkg/report"
)

type parser struct{}

func New() *parser {
return &parser{}
}

func (*parser) Parse(r io.ReaderAt, path string, format format.Format) (report.Reporter, error) {

var metadata Metadata

metadata.File.Path = path
metadata.File.Name = filepath.Base(path)
metadata.File.Format = format

fat, err := macho.NewFatFile(r)
if err != nil {
if err != macho.ErrNotFat {
return nil, err
}
thin, err := macho.NewFile(r)
if err != nil {
return nil, err
}
defer func() { _ = thin.Close() }()
metadata.thin = thin
} else {
defer func() { _ = fat.Close() }()
metadata.fat = fat
}

if err := metadata.analyse(); err != nil {
return nil, err
}
return &metadata, nil
}
90 changes: 90 additions & 0 deletions pkg/parser/macho/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package macho

import (
"strings"

"github.com/liamg/extrude/pkg/report"
)

func (m *Metadata) CreateReport() (report.Report, error) {
rep := report.New()

overview := report.NewSection("Overview")

overview.AddKeyValue("File", m.File.Path)
overview.AddKeyValue("Format", m.File.Format.String())

if m.fat != nil {
overview.AddKeyValue("Universal", "Yes (Fat)")
var arches []string
for _, arch := range m.fat.Arches {
arches = append(arches, arch.Cpu.String())
}
overview.AddKeyValue("Architectures", strings.Join(arches, ", "))
} else {
overview.AddKeyValue("Univeral", "No (Thin)")
overview.AddKeyValue("Type", m.thin.Type.String())
overview.AddKeyValue("Architecture", m.thin.Cpu.String())
}

rep.AddSection(overview)

security := report.NewSection("Security Features")

security.AddTest(
"Position Independent Executable",
boolToResult(m.Hardening.PositionIndependentExecutable),
`A PIE binary and all of its dependencies are loaded into random locations within virtual memory each time the application is executed. This makes Return Oriented Programming (ROP) attacks much more difficult to execute reliably.`,
)

security.AddTest(
"Stack Canary",
boolToResult(m.Hardening.StackProtected),
`A "canary" value is pushed onto the stack immediately after the function return pointer. The canary value is then checked before the function returns; if it has changed, the program will abort. This makes buffer overflow attacks much more difficult to carry out.`,
)

security.AddTest(
"Non-Executable Stack",
boolToResult(m.Hardening.StackExecutionNotAllowed),
`Preventing the stack from being executable means that malicious code injected onto the stack cannot be run.`,
)

security.AddTest(
"Non-Executable Heap",
boolToResult(m.Hardening.HeapExecutionNotAllowed),
`Preventing the heap from being executable means that malicious code written to the heap cannot be run.`,
)

security.AddTest(
"Automatic Reference Counting",
boolToResult(m.Hardening.AutomaticReferenceCounting),
`ARC is a runtime memory safety mechanism which keeps track of objects and frees them once they are no longer referenced.`,
)

/* In progress...
security.AddTest(
"Encryption",
boolToResult(m.Hardening.Encrypted),
``,
)
*/

rep.AddSection(security)

if len(m.Notes) > 0 {
notes := report.NewSection("Other Findings")
for _, note := range m.Notes {
notes.AddTest(note.Heading, report.Warning, note.Content)
}
rep.AddSection(notes)
}

return rep, nil
}

func boolToResult(in bool) report.Result {
if in {
return report.Pass
}
return report.Fail
}
Loading

0 comments on commit d48e6b4

Please sign in to comment.