Skip to content
This repository has been archived by the owner on Feb 18, 2021. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
LloydLabs committed Feb 19, 2018
0 parents commit 0da5929
Show file tree
Hide file tree
Showing 271 changed files with 153,361 additions and 0 deletions.
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# elf-strings
elf-strings will programmatically read an ELF binary's string sections within a given binary. This is meant to be much like the `strings` UNIX utility, however is purpose built for ELF binaries.

This means that you can get suitable information about the strings within the binary, such as the section they reside in, the offset in the section, etc.. This utility also has the functionality to 'demangle' C++ symbols, iterate linked libraries and print basic information about the ELF.

This can prove extremely useful for quickly grabbing strings when analysing a binary.

# Output
![alt text](https://i.imgur.com/plIdQCF.png "example of demangled strings")

# Building
```
git clone https://github.com/LloydLabs/elf-strings
cd elf-strings
go build
```

# Arguments
```
-binary string
the path to the ELF you wish to parse
-demangle
demangle C++ symbols into their original source identifiers, prettify found C++ symbols (optional)
-hex
output the strings as a hexadecimal literal (optional)
-libs
show the linked libraries in the binary (optional)
-max uint
the maximum amount of strings that you wish to be output (optional)
-offset
show the offset of the string in the section (default, recommended) (default true)
-output-file string
the path of the output file that you want to output to (optional)
-output-format string
the format you want to output as (optional, plain/json/xml) (default "plain")
```

# Example

An example grabbing the strings from the `echo` utility.

```
./elf-strings --binary=/bin/echo --min=4 --max-count=10
[+] Size: 31 kB
[+] Arch: x86_64
[+] Entry point: 0x401800
[+] Class: ELFCLASS64
[+] Byte order: LittleEndian
[.dynstr+0x0]: libc.so.6
[.dynstr+0xa]: fflush
[.dynstr+0x11]: __printf_chk
[.dynstr+0x1e]: setlocale
[.dynstr+0x28]: mbrtowc
[.dynstr+0x30]: strncmp
[.dynstr+0x38]: strrchr
[.dynstr+0x40]: dcgettext
[.dynstr+0x4a]: error
[.dynstr+0x50]: __stack_chk_fail
```
102 changes: 102 additions & 0 deletions elfread.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"bytes"
"debug/elf"
"errors"
"os"
)

// ElfReader instance containing information
// about said ELF binary
type ElfReader struct {
ExecReader *elf.File
File *os.File
}

// NewELFReader will create a new instance of ElfReader
func NewELFReader(path string) (*ElfReader, error) {
var r ElfReader
var err error

r.File, err = os.OpenFile(path, os.O_RDONLY, os.ModePerm)
if err != nil {
return nil, errors.New("failed to open the file")
}

r.ExecReader, err = elf.NewFile(r.File)
if err != nil {
return nil, errors.New("failed to parse the ELF file succesfully")
}

return &r, nil
}

// ReaderParseSection will parse the ELF section and
// return an array of bytes containing the content
// of the section, using the file instance..
func (r *ElfReader) ReaderParseSection(name string) []byte {
var s *elf.Section
if s = r.ExecReader.Section(name); s == nil {
return nil
}

sectionSize := int64(s.Offset)

_, err := r.File.Seek(0, 0)
if err != nil {
return nil
}

ret, err := r.File.Seek(sectionSize, 0)
if ret != sectionSize || err != nil {
return nil
}

buf := make([]byte, s.Size)
if buf == nil {
return nil
}

_, err = r.File.Read(buf)
if err != nil {
return nil
}

return buf
}

// ReaderParseStrings will parse the strings by a null terminator
// and then place them into an [offset => string] type map
// alignment does not matter here, as when \x00 exists more than once
// it will simply be skipped.
func (r *ElfReader) ReaderParseStrings(buf []byte) map[uint64][]byte {
var slice [][]byte
if slice = bytes.Split(buf, []byte("\x00")); slice == nil {
return nil
}

strings := make(map[uint64][]byte, len(slice))
length := uint64(len(slice))

var offset uint64

for i := uint64(0); i < length; i++ {
if len(slice[i]) == 0 {
continue
}

strings[offset] = slice[i]

offset += (uint64(len(slice[i])) + 1)
}

return strings
}

// Close softly close all of the instances associated
// with the ElfReader
func (r *ElfReader) Close() {
r.ExecReader.Close()
r.File.Close()
}
189 changes: 189 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"flag"
"fmt"
"log"
"os"
"strings"

humanize "github.com/dustin/go-humanize"
"github.com/fatih/color"
"github.com/shawnsmithdev/zermelo"
)

var (
demangleOpt = flag.Bool("demangle", false, "demangle C++ symbols into their original source identifiers, prettify found C++ symbols (optional)")
hexOpt = flag.Bool("hex", false, "output the strings as a hexadecimal literal (optional)")
offsetOpt = flag.Bool("offset", true, "show the offset of the string in the section (default, recommended)")
binaryOpt = flag.String("binary", "", "the path to the ELF you wish to parse")
formatOpt = flag.String("output-format", "plain", "the format you want to output as (optional, plain/json/xml)")
outputOpt = flag.String("output-file", "", "the path of the output file that you want to output to (optional)")
maxOpt = flag.Uint64("max-count", 0, "the maximum amount of strings that you wish to be output (optional)")
libOpt = flag.Bool("libs", false, "show the linked libraries in the binary (optional)")
infoOpt = flag.Bool("no-info", false, "don't show any information about the binary")
minOpt = flag.Uint64("min", 0, "the minimum length of the string")
colorOpt = flag.Bool("no-color", false, "disable color output in the results")
trimOpt = flag.Bool("no-trim", false, "disable triming whitespace and trailing newlines")
humanOpt = flag.Bool("no-human", false, "don't validate that its a human readable string, this could increase the amount of junk.")
)

// ReadSection is the main logic here
// it combines all of the modules, etc.
func ReadSection(reader *ElfReader, section string) {
var err error
var writer *OutWriter
var count uint64

sect := reader.ReaderParseSection(section)

if *outputOpt != "" {
writer, err = NewOutWriter(*outputOpt, OutParseTypeStr(*formatOpt))
if err != nil {
log.Fatal(err.Error())
}
}

if sect != nil {
nodes := reader.ReaderParseStrings(sect)

// Since maps in Go are unsorted, we're going to have to make
// a slice of keys, then iterate over this and just use the index
// from the map.
keys := make([]uint64, len(nodes))
for k, _ := range nodes {
keys = append(keys, k)
}

err = zermelo.Sort(keys)
if err != nil {
return
}

keys = UtilUniqueSlice(keys)

for _, off := range keys {
if *maxOpt != 0 {
if count == *maxOpt {
break
}
}

str := string(nodes[off])
if uint64(len(str)) < *minOpt {
continue
}

if !*humanOpt {
if !UtilIsNice(str) {
continue
}
}

str = strings.TrimSpace(str)

if !*trimOpt {
bad := []string{"\n", "\r"}
for _, char := range bad {
str = strings.Replace(str, char, "", -1)
}
}

if *demangleOpt {
demangled, err := UtilDemangle(&str)
if err == nil {
str = demangled
}
}

if *hexOpt {
str = UtilConvHex(str)
}

if *offsetOpt {
if os.Getenv("NO_COLOR") != "" || *colorOpt {
fmt.Printf("[%s+%#x]: %s\n",
section,
off,
str)
} else {
fmt.Printf("[%s%s]: %s\n",
color.BlueString(section),
color.GreenString("+%#x", off),
str)
}
} else {
fmt.Println(str)
}

if writer != nil {
writer.WriteResult(str, off)
}

count++
}
}
}

// ReadBasic will read the basic information
// about the ELF
func ReadBasic(reader *ElfReader) {
stat, err := reader.File.Stat()
if err != nil {
return
}

size := humanize.Bytes(uint64(stat.Size()))

fmt.Printf(
"[+] Size: %s\n"+
"[+] Arch: %s\n"+
"[+] Entry point: %#x\n"+
"[+] Class: %s\n"+
"[+] Byte order: %s\n",
size,
UtilConvertMachine(reader.ExecReader.Machine),
reader.ExecReader.Entry,
reader.ExecReader.Class.String(),
reader.ExecReader.ByteOrder.String(),
)

if *libOpt {
fmt.Println("[+] Libraries:")
libs, err := reader.ExecReader.ImportedLibraries()
if err == nil {
for _, lib := range libs {
fmt.Printf("\t [!] %s\n", lib)
}
}
}

fmt.Println(strings.Repeat("-", 16))
}

// main is the entrypoint for this program
func main() {
flag.Parse()

if *binaryOpt == "" {
flag.PrintDefaults()
return
}

r, err := NewELFReader(*binaryOpt)
if err != nil {
log.Fatal(err.Error())
}

defer r.Close()

ReadBasic(r)

sections := []string{".dynstr", ".rodata", ".rdata",
".strtab", ".comment", ".note",
".stab", ".stabstr", ".note.ABI-tag", ".note.gnu.build-id"}

for _, section := range sections {
ReadSection(r, section)
}
}
Loading

0 comments on commit 0da5929

Please sign in to comment.