diff --git a/asm.go b/asm.go index e30e5a1..16a2a4e 100644 --- a/asm.go +++ b/asm.go @@ -1,13 +1,13 @@ -package sploit; +package sploit import ( - "errors" - "github.com/knightsc/gapstone" - "fmt" - "io/ioutil" - "os/exec" - "os" - log "github.com/sirupsen/logrus" + "errors" + "fmt" + "github.com/knightsc/gapstone" + log "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "os/exec" ) var sourcePath = "/tmp/prog.S" @@ -16,201 +16,201 @@ var blobPath = "/tmp/prog.bin" // Asm complies assembly instructions to a byte slice containing machine code func Asm(processor *Processor, code string) ([]byte, error) { - prefix, err := getToolchainPrefix(processor) - if err != nil { - return nil, err - } - - err = createSourceFile(processor, code) - if err != nil { - return nil, err - } - - err = buildProgram(processor, prefix) - if err != nil { - return nil, err - } - - opcodes, err := dumpText(prefix) - os.Remove(sourcePath) - os.Remove(objectPath) - os.Remove(blobPath) - return opcodes, err + prefix, err := getToolchainPrefix(processor) + if err != nil { + return nil, err + } + + err = createSourceFile(processor, code) + if err != nil { + return nil, err + } + + err = buildProgram(processor, prefix) + if err != nil { + return nil, err + } + + opcodes, err := dumpText(prefix) + os.Remove(sourcePath) + os.Remove(objectPath) + os.Remove(blobPath) + return opcodes, err } // Disasm disassembles a supplied byte slice and returns a string containing the assembly instructions func Disasm(address uint64, code []byte, processor *Processor) (string, error) { - arch := getCapstoneArch(processor) - mode := getCapstoneMode(processor) - return disasm(code, address, arch, mode, false) + arch := getCapstoneArch(processor) + mode := getCapstoneMode(processor) + return disasm(code, address, arch, mode, false) } func createSourceFile(processor *Processor, code string) error { - srcCode := ".section .text\n.global _start\n" - if processor.Architecture == ArchI386 || processor.Architecture == ArchX8664 || processor.Architecture == ArchIA64 { - srcCode += ".intel_syntax noprefix" - } - srcCode += "\n\n_start:\n" + code - err := ioutil.WriteFile(sourcePath, []byte(srcCode), 0644) - if err != nil { - return err - } - - return nil + srcCode := ".section .text\n.global _start\n" + if processor.Architecture == ArchI386 || processor.Architecture == ArchX8664 || processor.Architecture == ArchIA64 { + srcCode += ".intel_syntax noprefix" + } + srcCode += "\n\n_start:\n" + code + err := ioutil.WriteFile(sourcePath, []byte(srcCode), 0644) + if err != nil { + return err + } + + return nil } -func buildProgram(processor *Processor, prefix string) (error) { - compilerExe, err := exec.LookPath(prefix + "gcc") - if err != nil { - return err - } - - // Construct compile command arguments - args := []string{compilerExe, "-c", "-fpic"} - if processor.Architecture == ArchI386 { - args = append(args, "-m32") - } - - args = append(args, sourcePath) - args = append(args, "-o") - args = append(args, objectPath) - - cmdCompile := &exec.Cmd { - Path: compilerExe, - Args: args, - Stdout: os.Stdout, - Stderr: os.Stdout, - } - - return cmdCompile.Run() +func buildProgram(processor *Processor, prefix string) error { + compilerExe, err := exec.LookPath(prefix + "gcc") + if err != nil { + return err + } + + // Construct compile command arguments + args := []string{compilerExe, "-c", "-fpic"} + if processor.Architecture == ArchI386 { + args = append(args, "-m32") + } + + args = append(args, sourcePath) + args = append(args, "-o") + args = append(args, objectPath) + + cmdCompile := &exec.Cmd{ + Path: compilerExe, + Args: args, + Stdout: os.Stdout, + Stderr: os.Stdout, + } + + return cmdCompile.Run() } func dumpText(prefix string) ([]byte, error) { - objcopyExe, err := exec.LookPath(prefix + "objcopy") - if err != nil { - return nil, err - } - - args := []string{objcopyExe, "-O", "binary", "--only-section=.text", objectPath, blobPath} - cmdObjcopy := &exec.Cmd { - Path: objcopyExe, - Args: args, - Stdout: os.Stdout, - Stderr: os.Stdout, - } - - // Objcopy the .text section to a binary file - err = cmdObjcopy.Run() - if err != nil { - return nil, err - } - - // Open the objcopy'd blob - f, err := os.Open(blobPath) - if err != nil { - return nil, err - } - defer f.Close() - - // Read the entire thing - opcodes, err := ioutil.ReadAll(f) - if err != nil { - return nil, err - } - - return opcodes, nil + objcopyExe, err := exec.LookPath(prefix + "objcopy") + if err != nil { + return nil, err + } + + args := []string{objcopyExe, "-O", "binary", "--only-section=.text", objectPath, blobPath} + cmdObjcopy := &exec.Cmd{ + Path: objcopyExe, + Args: args, + Stdout: os.Stdout, + Stderr: os.Stdout, + } + + // Objcopy the .text section to a binary file + err = cmdObjcopy.Run() + if err != nil { + return nil, err + } + + // Open the objcopy'd blob + f, err := os.Open(blobPath) + if err != nil { + return nil, err + } + defer f.Close() + + // Read the entire thing + opcodes, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + return opcodes, nil } func getToolchainPrefix(processor *Processor) (string, error) { - switch processor.Architecture { - case ArchX8664: - return "x86_64-linux-gnu-", nil - case ArchI386: - return "x86_64-linux-gnu-", nil - case ArchARM: - if processor.Endian == LittleEndian { - return "arm-linux-gnueabi-", nil - } - return "arm-linux-gnueabihf-", nil - case ArchAARCH64: - return "aarch64-linux-gnu-", nil - case ArchPPC: - return "powerpc-linux-gnu-", nil - case ArchMIPS: - if processor.Endian == LittleEndian { - return "mipsel-linux-gnu-", nil - } - return "mips-linux-gnu-", nil - case ArchIA64: - return "x86_64-linux-gnu-", nil - default: - return "", errors.New("Unsupported architecture") - } + switch processor.Architecture { + case ArchX8664: + return "x86_64-linux-gnu-", nil + case ArchI386: + return "x86_64-linux-gnu-", nil + case ArchARM: + if processor.Endian == LittleEndian { + return "arm-linux-gnueabi-", nil + } + return "arm-linux-gnueabihf-", nil + case ArchAARCH64: + return "aarch64-linux-gnu-", nil + case ArchPPC: + return "powerpc-linux-gnu-", nil + case ArchMIPS: + if processor.Endian == LittleEndian { + return "mipsel-linux-gnu-", nil + } + return "mips-linux-gnu-", nil + case ArchIA64: + return "x86_64-linux-gnu-", nil + default: + return "", errors.New("Unsupported architecture") + } } -func disasm(data []byte, address uint64, arch int, mode int, isROP bool)(string, error) { - engine, err := gapstone.New(arch, mode) - if err != nil { - return "", err - } - - insns, err := engine.Disasm(data, address, 0) - if err != nil { - return "", err - } - - insnsStr := "" - for i := 0; i < len(insns); i++ { - if isROP { - insnsStr += fmt.Sprintf("%s %s", insns[i].Mnemonic, insns[i].OpStr) - if i+1 != len(insns) { - insnsStr += " ; " - } - } else { - insnsStr += fmt.Sprintf("%08x: %s %s\n", insns[i].Address, insns[i].Mnemonic, insns[i].OpStr) - } - } - - log.Debugf(insnsStr) - return insnsStr, nil +func disasm(data []byte, address uint64, arch int, mode int, isROP bool) (string, error) { + engine, err := gapstone.New(arch, mode) + if err != nil { + return "", err + } + + insns, err := engine.Disasm(data, address, 0) + if err != nil { + return "", err + } + + insnsStr := "" + for i := 0; i < len(insns); i++ { + if isROP { + insnsStr += fmt.Sprintf("%s %s", insns[i].Mnemonic, insns[i].OpStr) + if i+1 != len(insns) { + insnsStr += " ; " + } + } else { + insnsStr += fmt.Sprintf("%08x: %s %s\n", insns[i].Address, insns[i].Mnemonic, insns[i].OpStr) + } + } + + log.Debugf(insnsStr) + return insnsStr, nil } func getCapstoneArch(processor *Processor) int { - archs := map[Architecture]int { - ArchX8664 : gapstone.CS_ARCH_X86, - ArchI386 : gapstone.CS_ARCH_X86, - ArchARM : gapstone.CS_ARCH_ARM, - ArchAARCH64 : gapstone.CS_ARCH_ARM64, - ArchPPC : gapstone.CS_ARCH_PPC, - ArchMIPS : gapstone.CS_ARCH_MIPS, - ArchIA64 : gapstone.CS_ARCH_X86, - } - - return archs[processor.Architecture] + archs := map[Architecture]int{ + ArchX8664: gapstone.CS_ARCH_X86, + ArchI386: gapstone.CS_ARCH_X86, + ArchARM: gapstone.CS_ARCH_ARM, + ArchAARCH64: gapstone.CS_ARCH_ARM64, + ArchPPC: gapstone.CS_ARCH_PPC, + ArchMIPS: gapstone.CS_ARCH_MIPS, + ArchIA64: gapstone.CS_ARCH_X86, + } + + return archs[processor.Architecture] } func getCapstoneMode(processor *Processor) int { - modes := map[Architecture]int { - ArchX8664 : gapstone.CS_MODE_64, - ArchI386 : gapstone.CS_MODE_32, - ArchARM : gapstone.CS_MODE_32, - ArchAARCH64 : gapstone.CS_MODE_64, - ArchPPC : gapstone.CS_MODE_32, - ArchMIPS : gapstone.CS_MODE_32, - ArchIA64 : gapstone.CS_MODE_64, - } - mode := modes[processor.Architecture] - if processor.Endian == BigEndian { - mode |= gapstone.CS_MODE_BIG_ENDIAN - } else { - mode |= gapstone.CS_MODE_LITTLE_ENDIAN - } - - return mode + modes := map[Architecture]int{ + ArchX8664: gapstone.CS_MODE_64, + ArchI386: gapstone.CS_MODE_32, + ArchARM: gapstone.CS_MODE_32, + ArchAARCH64: gapstone.CS_MODE_64, + ArchPPC: gapstone.CS_MODE_32, + ArchMIPS: gapstone.CS_MODE_32, + ArchIA64: gapstone.CS_MODE_64, + } + mode := modes[processor.Architecture] + if processor.Endian == BigEndian { + mode |= gapstone.CS_MODE_BIG_ENDIAN + } else { + mode |= gapstone.CS_MODE_LITTLE_ENDIAN + } + + return mode } func disasmGadget(address uint64, code []byte, processor *Processor) (string, error) { - arch := getCapstoneArch(processor) - mode := getCapstoneMode(processor) - return disasm(code, address, arch, mode, true) + arch := getCapstoneArch(processor) + mode := getCapstoneMode(processor) + return disasm(code, address, arch, mode, true) } diff --git a/asm_test.go b/asm_test.go index 1e10efc..31529c7 100644 --- a/asm_test.go +++ b/asm_test.go @@ -1,64 +1,64 @@ -package sploit; +package sploit import ( - "testing" - "encoding/hex" + "encoding/hex" + "testing" ) func TestDisasm(t *testing.T) { - t.Logf("Testing disassembly (%s)...", elfFile) - addr := uint64(0x1135) - n := 32 - e, _ := NewELF(elfFile) - disasm, err := e.Disasm(addr, n) - if err != nil { - t.Fatal(err) - } + t.Logf("Testing disassembly (%s)...", elfFile) + addr := uint64(0x1135) + n := 32 + e, _ := NewELF(elfFile) + disasm, err := e.Disasm(addr, n) + if err != nil { + t.Fatal(err) + } - expected := "00001135: push rbp\n" + - "00001136: mov rbp, rsp\n" + - "00001139: sub rsp, 0x10\n" + - "0000113d: mov dword ptr [rbp - 4], edi\n" + - "00001140: mov qword ptr [rbp - 0x10], rsi\n" + - "00001144: lea rdi, [rip + 0xeb9]\n" + - "0000114b: call 0x1030\n" + - "00001150: mov eax, 0\n" + expected := "00001135: push rbp\n" + + "00001136: mov rbp, rsp\n" + + "00001139: sub rsp, 0x10\n" + + "0000113d: mov dword ptr [rbp - 4], edi\n" + + "00001140: mov qword ptr [rbp - 0x10], rsi\n" + + "00001144: lea rdi, [rip + 0xeb9]\n" + + "0000114b: call 0x1030\n" + + "00001150: mov eax, 0\n" - if disasm != expected { - t.Fatal("Disassembly does not match expected") - } - t.Logf("Successfully disassembled %v bytes at vaddr:0x%08x:", n, addr) - t.Log("\n" + disasm) + if disasm != expected { + t.Fatal("Disassembly does not match expected") + } + t.Logf("Successfully disassembled %v bytes at vaddr:0x%08x:", n, addr) + t.Log("\n" + disasm) } func TestAsmX8664(t *testing.T) { - code := "mov rdi, 1337\nmov rsi, 1337\nmov rdx, 1337\nmov rcx, 1337\nnop\n" - t.Logf("Testing assembly of following x86-64 instructions:\n%s", code) - processor := &Processor { - Architecture: ArchX8664, - Endian: LittleEndian, - } + code := "mov rdi, 1337\nmov rsi, 1337\nmov rdx, 1337\nmov rcx, 1337\nnop\n" + t.Logf("Testing assembly of following x86-64 instructions:\n%s", code) + processor := &Processor{ + Architecture: ArchX8664, + Endian: LittleEndian, + } - opcodes, err := Asm(processor, code) - if err != nil { - t.Fatal(err) - } + opcodes, err := Asm(processor, code) + if err != nil { + t.Fatal(err) + } - t.Logf("Assembly code compiled to %v bytes:\n%s", len(opcodes), hex.Dump(opcodes)) + t.Logf("Assembly code compiled to %v bytes:\n%s", len(opcodes), hex.Dump(opcodes)) } func TestAsmARM(t *testing.T) { - code := "mov r2, r1\nmov r3, r4\nmov r5, r6\n" - t.Logf("Testing assembly of following ARM instructions:\n%s", code) - processor := &Processor { - Architecture: ArchARM, - Endian: LittleEndian, - } + code := "mov r2, r1\nmov r3, r4\nmov r5, r6\n" + t.Logf("Testing assembly of following ARM instructions:\n%s", code) + processor := &Processor{ + Architecture: ArchARM, + Endian: LittleEndian, + } - opcodes, err := Asm(processor, code) - if err != nil { - t.Fatal(err) - } + opcodes, err := Asm(processor, code) + if err != nil { + t.Fatal(err) + } - t.Logf("Assembly code compiled to %v bytes:\n%s", len(opcodes), hex.Dump(opcodes)) + t.Logf("Assembly code compiled to %v bytes:\n%s", len(opcodes), hex.Dump(opcodes)) } diff --git a/elf.go b/elf.go index 4a4b2e5..f046ef0 100644 --- a/elf.go +++ b/elf.go @@ -1,322 +1,322 @@ -package sploit; +package sploit import ( - log "github.com/sirupsen/logrus" - "debug/elf" - "errors" - "bytes" - "encoding/binary" + "bytes" + "debug/elf" + "encoding/binary" + "errors" + log "github.com/sirupsen/logrus" ) // ELF is a struct that contains methods for operating on an ELF file type ELF struct { - E *elf.File - Processor *Processor - PIE bool - Mitigations *Mitigations + E *elf.File + Processor *Processor + PIE bool + Mitigations *Mitigations } // NewELF loads a ELF file from disk and initializes the ELF struct func NewELF(filename string) (*ELF, error) { - e, err := elf.Open(filename) - if err != nil { - return nil, err - } - - processor, err := getArchInfo(e) - if err != nil { - return nil, err - } - - isPIE := (e.Type == elf.ET_DYN) - mitigations, err := checkMitigations(e) - if err != nil { - return nil, err - } - - log.Debugf( - "Machine Type : %s\n" + - "Endian : %s\n" + - "PIE : %v\n" + - "Stack Canary : %v\n" + - "NX : %v\n", - e.Machine, processor.Endian, isPIE, mitigations.Canary, mitigations.NX, - ) - - return &ELF{ - E: e, - Processor: processor, - PIE: isPIE, - Mitigations: mitigations, - }, nil + e, err := elf.Open(filename) + if err != nil { + return nil, err + } + + processor, err := getArchInfo(e) + if err != nil { + return nil, err + } + + isPIE := (e.Type == elf.ET_DYN) + mitigations, err := checkMitigations(e) + if err != nil { + return nil, err + } + + log.Debugf( + "Machine Type : %s\n"+ + "Endian : %s\n"+ + "PIE : %v\n"+ + "Stack Canary : %v\n"+ + "NX : %v\n", + e.Machine, processor.Endian, isPIE, mitigations.Canary, mitigations.NX, + ) + + return &ELF{ + E: e, + Processor: processor, + PIE: isPIE, + Mitigations: mitigations, + }, nil } // BSS is an ELF method that returns the virtual address of the specified offset into the .bss section -func (e *ELF)BSS(offset uint64)(uint64, error) { - section := e.E.Section(".bss") - if section == nil { - return 0, errors.New("No .bss section") - } +func (e *ELF) BSS(offset uint64) (uint64, error) { + section := e.E.Section(".bss") + if section == nil { + return 0, errors.New("No .bss section") + } - if offset >= section.Size { - return 0, errors.New("Offset exceeds end of .bss") - } + if offset >= section.Size { + return 0, errors.New("Offset exceeds end of .bss") + } - return section.Addr+offset, nil + return section.Addr + offset, nil } // Read is an ELF method that returns a slice of bytes read from the ELF at the specified virtual address -func (e *ELF)Read(address uint64, nBytes int)([]byte, error) { - s, err := getVASegment(e.E, address) - if err != nil { - return nil, err - } - - offset := address - s.Vaddr - if s.Filesz - offset < uint64(nBytes) { - nBytes = int(s.Filesz - offset) - } - - buf := make([]byte, nBytes) - _, err = s.ReadAt(buf, int64(offset)) - if err != nil { - return nil, err - } - - return buf, nil +func (e *ELF) Read(address uint64, nBytes int) ([]byte, error) { + s, err := getVASegment(e.E, address) + if err != nil { + return nil, err + } + + offset := address - s.Vaddr + if s.Filesz-offset < uint64(nBytes) { + nBytes = int(s.Filesz - offset) + } + + buf := make([]byte, nBytes) + _, err = s.ReadAt(buf, int64(offset)) + if err != nil { + return nil, err + } + + return buf, nil } // Read8 is an ELF method that reads 8 bits from the ELF at the specified address and returns the data as a uint8 -func (e *ELF)Read8(address uint64)(uint8, error) { - b, err := e.readIntBytes(address, 1) - if err != nil { - return 0, err - } +func (e *ELF) Read8(address uint64) (uint8, error) { + b, err := e.readIntBytes(address, 1) + if err != nil { + return 0, err + } - return b[0], nil + return b[0], nil } // Read16LE is an ELF method that reads 16 bits from the ELF at the specified address and returns a Uint16 in little endian byte order -func (e *ELF)Read16LE(address uint64)(uint16, error) { - b, err := e.readIntBytes(address, 2) - if err != nil { - return 0, err - } +func (e *ELF) Read16LE(address uint64) (uint16, error) { + b, err := e.readIntBytes(address, 2) + if err != nil { + return 0, err + } - return binary.LittleEndian.Uint16(b), nil + return binary.LittleEndian.Uint16(b), nil } // Read16BE is an ELF method that reads 16 bits from the ELF at the specified address and returns a Uint16 in big endian byte order -func (e *ELF)Read16BE(address uint64)(uint16, error) { - b, err := e.readIntBytes(address, 2) - if err != nil { - return 0, err - } +func (e *ELF) Read16BE(address uint64) (uint16, error) { + b, err := e.readIntBytes(address, 2) + if err != nil { + return 0, err + } - return binary.BigEndian.Uint16(b), nil + return binary.BigEndian.Uint16(b), nil } // Read32LE is an ELF method that reads 32 bits from the ELF at the specified address and returns a Uint32 in little endian byte order -func (e *ELF)Read32LE(address uint64)(uint32, error) { - b, err := e.readIntBytes(address, 4) - if err != nil { - return 0, err - } +func (e *ELF) Read32LE(address uint64) (uint32, error) { + b, err := e.readIntBytes(address, 4) + if err != nil { + return 0, err + } - return binary.LittleEndian.Uint32(b), nil + return binary.LittleEndian.Uint32(b), nil } // Read32BE is an ELF method that reads 32 bits from the ELF at the specified address and returns a Uint32 in big endian byte order -func (e *ELF)Read32BE(address uint64)(uint32, error) { - b, err := e.readIntBytes(address, 4) - if err != nil { - return 0, err - } +func (e *ELF) Read32BE(address uint64) (uint32, error) { + b, err := e.readIntBytes(address, 4) + if err != nil { + return 0, err + } - return binary.BigEndian.Uint32(b), nil + return binary.BigEndian.Uint32(b), nil } // Read64LE is an ELF method that reads 64 bits from the ELF at the specified address and returns a Uint64 in little endian byte order -func (e *ELF)Read64LE(address uint64)(uint64, error) { - b, err := e.readIntBytes(address, 8) - if err != nil { - return 0, err - } +func (e *ELF) Read64LE(address uint64) (uint64, error) { + b, err := e.readIntBytes(address, 8) + if err != nil { + return 0, err + } - return binary.LittleEndian.Uint64(b), nil + return binary.LittleEndian.Uint64(b), nil } // Read64BE is an ELF method that reads 64 bits from the ELF at the specified address and returns a Uint64 in big endian byte order -func (e *ELF)Read64BE(address uint64)(uint64, error) { - b, err := e.readIntBytes(address, 8) - if err != nil { - return 0, err - } +func (e *ELF) Read64BE(address uint64) (uint64, error) { + b, err := e.readIntBytes(address, 8) + if err != nil { + return 0, err + } - return binary.BigEndian.Uint64(b), nil + return binary.BigEndian.Uint64(b), nil } // Disasm is an ELF method that disassembles code at the specified virtual address and returns a string containing assembly instructions -func (e *ELF)Disasm(address uint64, nBytes int)(string, error) { - data, err := e.Read(address, nBytes) - if err != nil { - return "", err - } - - arch := getCapstoneArch(e.Processor) - mode := getCapstoneMode(e.Processor) - return disasm(data, address, arch, mode, false) +func (e *ELF) Disasm(address uint64, nBytes int) (string, error) { + data, err := e.Read(address, nBytes) + if err != nil { + return "", err + } + + arch := getCapstoneArch(e.Processor) + mode := getCapstoneMode(e.Processor) + return disasm(data, address, arch, mode, false) } // ROP is an ELF method that locates all ROP gadgets in the ELF's executable segments and returns a ROP object -func (e *ELF)ROP() (*ROP, error) { - file := e.E - gadgets := ROP{} - for i := 0; i < len(file.Progs); i++ { - // Check if segment is executable - if file.Progs[i].Flags & elf.PF_X == 0 { - continue - } - - // Segment is executable, read segment data - data, err := e.Read(file.Progs[i].Vaddr, int(file.Progs[i].Filesz)) - if err != nil { - return nil, err - } - - // Search for gadgets in data - gadgetsSeg, err := findGadgets(e.Processor, data, file.Progs[i].Vaddr) - if err != nil { - return nil, err - } - - gadgets = append(gadgets, gadgetsSeg...) - } - - return &gadgets, nil +func (e *ELF) ROP() (*ROP, error) { + file := e.E + gadgets := ROP{} + for i := 0; i < len(file.Progs); i++ { + // Check if segment is executable + if file.Progs[i].Flags&elf.PF_X == 0 { + continue + } + + // Segment is executable, read segment data + data, err := e.Read(file.Progs[i].Vaddr, int(file.Progs[i].Filesz)) + if err != nil { + return nil, err + } + + // Search for gadgets in data + gadgetsSeg, err := findGadgets(e.Processor, data, file.Progs[i].Vaddr) + if err != nil { + return nil, err + } + + gadgets = append(gadgets, gadgetsSeg...) + } + + return &gadgets, nil } // GetSignatureVAddrs is an ELF method that searches for the specified sequence of bytes in all segments -func (e *ELF)GetSignatureVAddrs(signature []byte) ([]uint64, error) { - return e.getSignatureVAddrs(signature, false) +func (e *ELF) GetSignatureVAddrs(signature []byte) ([]uint64, error) { + return e.getSignatureVAddrs(signature, false) } // GetOpcodeVAddrs is an ELF method that searches for the specified sequence of bytes in executable segments only -func (e *ELF)GetOpcodeVAddrs(signature []byte) ([]uint64, error) { - return e.getSignatureVAddrs(signature, true) +func (e *ELF) GetOpcodeVAddrs(signature []byte) ([]uint64, error) { + return e.getSignatureVAddrs(signature, true) } -func (e *ELF)getSignatureVAddrs(signature []byte, exeOnly bool) ([]uint64, error) { - file := e.E - vaddrs := []uint64{} - for i := 0; i < len(file.Progs); i++ { - if exeOnly { - if file.Progs[i].Flags & elf.PF_X == 0 { - continue - } - } - - data, err := e.Read(file.Progs[i].Vaddr, int(file.Progs[i].Filesz)) - if err != nil { - return nil, errors.New("Failed to read from segment") - } - - // Search for byte signature in segment - n := 0 - for { - idx := bytes.Index(data[n:], signature) - if idx == -1 { - break - } - - vaddrs = append(vaddrs, file.Progs[i].Vaddr+uint64(n)+uint64(idx)) - n += idx + 1 - } - } - - return vaddrs, nil +func (e *ELF) getSignatureVAddrs(signature []byte, exeOnly bool) ([]uint64, error) { + file := e.E + vaddrs := []uint64{} + for i := 0; i < len(file.Progs); i++ { + if exeOnly { + if file.Progs[i].Flags&elf.PF_X == 0 { + continue + } + } + + data, err := e.Read(file.Progs[i].Vaddr, int(file.Progs[i].Filesz)) + if err != nil { + return nil, errors.New("Failed to read from segment") + } + + // Search for byte signature in segment + n := 0 + for { + idx := bytes.Index(data[n:], signature) + if idx == -1 { + break + } + + vaddrs = append(vaddrs, file.Progs[i].Vaddr+uint64(n)+uint64(idx)) + n += idx + 1 + } + } + + return vaddrs, nil } -func getVASegment(e *elf.File, address uint64)(*elf.Prog, error) { - for i := 0; i < len(e.Progs); i++ { - s := e.Progs[i] - start := s.Vaddr - end := s.Vaddr + s.Filesz +func getVASegment(e *elf.File, address uint64) (*elf.Prog, error) { + for i := 0; i < len(e.Progs); i++ { + s := e.Progs[i] + start := s.Vaddr + end := s.Vaddr + s.Filesz - if address >= start && address < end { - return s, nil - } - } + if address >= start && address < end { + return s, nil + } + } - return nil, errors.New("Address is not in range of a ELF section") + return nil, errors.New("Address is not in range of a ELF section") } func getArchInfo(e *elf.File) (*Processor, error) { - supported := map[elf.Machine]Architecture { - elf.EM_X86_64 : ArchX8664, - elf.EM_386 : ArchI386, - elf.EM_ARM : ArchARM, - elf.EM_AARCH64 : ArchAARCH64, - elf.EM_PPC : ArchPPC, - elf.EM_MIPS : ArchMIPS, - elf.EM_IA_64 : ArchIA64, - } - - endian := LittleEndian - if e.Data == elf.ELFDATA2MSB { - endian = BigEndian - } - - if arch, ok := supported[e.Machine]; ok { - return &Processor { - Architecture: arch, - Endian: endian, - }, nil - } - return nil, errors.New("Unsupported machine type") + supported := map[elf.Machine]Architecture{ + elf.EM_X86_64: ArchX8664, + elf.EM_386: ArchI386, + elf.EM_ARM: ArchARM, + elf.EM_AARCH64: ArchAARCH64, + elf.EM_PPC: ArchPPC, + elf.EM_MIPS: ArchMIPS, + elf.EM_IA_64: ArchIA64, + } + + endian := LittleEndian + if e.Data == elf.ELFDATA2MSB { + endian = BigEndian + } + + if arch, ok := supported[e.Machine]; ok { + return &Processor{ + Architecture: arch, + Endian: endian, + }, nil + } + return nil, errors.New("Unsupported machine type") } func checkMitigations(e *elf.File) (*Mitigations, error) { - // Check if there's a stack canary - symbols, err := e.Symbols() - if err != nil { - return nil, err - } - - canary := false - for _, symbol := range symbols { - if symbol.Name == "__stack_chk_fail" { - canary = true - break - } - } - - // Check for executable stack (NX) - nx := false - for _, prog := range e.Progs { - if uint32(prog.Type) == uint32(0x6474e551) { // PT_GNU_STACK - if (uint32(prog.Flags) & uint32(elf.PF_X)) == 0 { - nx = true - break - } - } - } - - return &Mitigations { - Canary: canary, - NX: nx, - }, nil + // Check if there's a stack canary + symbols, err := e.Symbols() + if err != nil { + return nil, err + } + + canary := false + for _, symbol := range symbols { + if symbol.Name == "__stack_chk_fail" { + canary = true + break + } + } + + // Check for executable stack (NX) + nx := false + for _, prog := range e.Progs { + if uint32(prog.Type) == uint32(0x6474e551) { // PT_GNU_STACK + if (uint32(prog.Flags) & uint32(elf.PF_X)) == 0 { + nx = true + break + } + } + } + + return &Mitigations{ + Canary: canary, + NX: nx, + }, nil } -func (e *ELF)readIntBytes(address uint64, width int)([]byte, error) { - b, err := e.Read(address, width) - if err != nil { - return nil, err - } +func (e *ELF) readIntBytes(address uint64, width int) ([]byte, error) { + b, err := e.Read(address, width) + if err != nil { + return nil, err + } - if len(b) != width { - return nil, errors.New("Read truncated do to end of segment") - } + if len(b) != width { + return nil, errors.New("Read truncated do to end of segment") + } - return b, nil + return b, nil } diff --git a/elf_test.go b/elf_test.go index 0c90c8c..ec4a4c0 100644 --- a/elf_test.go +++ b/elf_test.go @@ -1,176 +1,176 @@ -package sploit; +package sploit import ( - "testing" - "strconv" - "encoding/hex" + "encoding/hex" + "strconv" + "testing" ) var elfFile = "test/prog1.x86_64" func TestNewELF(t *testing.T) { - t.Logf("Testing ELF processing (%s)...", elfFile) - e, err := NewELF(elfFile) - if err != nil { - t.Fatalf("NewElf returned error: %s", err) - } - - if e.Processor.Architecture != ArchX8664 { - t.Fatal("Machine type != x86-64") - } - t.Log("Processor: X86_64") - - if e.Processor.Endian != LittleEndian { - t.Fatal("Endian != little") - } - t.Log("Endianess: little") - - if e.PIE != true { - t.Fatal("PIE != true") - } - t.Log("PIE: " + strconv.FormatBool(e.PIE)) - - if e.Mitigations.NX != true { - t.Fatal("NX != true") - } - t.Log("NX: " + strconv.FormatBool(e.Mitigations.NX)) - - if e.Mitigations.Canary != false { - t.Fatal("Canary != false") - } - t.Log("Canary: " + strconv.FormatBool(e.Mitigations.Canary)) + t.Logf("Testing ELF processing (%s)...", elfFile) + e, err := NewELF(elfFile) + if err != nil { + t.Fatalf("NewElf returned error: %s", err) + } + + if e.Processor.Architecture != ArchX8664 { + t.Fatal("Machine type != x86-64") + } + t.Log("Processor: X86_64") + + if e.Processor.Endian != LittleEndian { + t.Fatal("Endian != little") + } + t.Log("Endianess: little") + + if e.PIE != true { + t.Fatal("PIE != true") + } + t.Log("PIE: " + strconv.FormatBool(e.PIE)) + + if e.Mitigations.NX != true { + t.Fatal("NX != true") + } + t.Log("NX: " + strconv.FormatBool(e.Mitigations.NX)) + + if e.Mitigations.Canary != false { + t.Fatal("Canary != false") + } + t.Log("Canary: " + strconv.FormatBool(e.Mitigations.Canary)) } func TestELFBSS(t *testing.T) { - t.Logf("Testing .bss section addressing (%s)...", elfFile) - e, _ := NewELF(elfFile) - addr, err := e.BSS(4) - if err != nil { - t.Fatal("Error computing bss offset addr") - } - - if addr != 0x4034 { - t.Fatal("BSS offset addr != 0x4034") - } - t.Logf(".bss+4 == 0x%08x", addr) + t.Logf("Testing .bss section addressing (%s)...", elfFile) + e, _ := NewELF(elfFile) + addr, err := e.BSS(4) + if err != nil { + t.Fatal("Error computing bss offset addr") + } + + if addr != 0x4034 { + t.Fatal("BSS offset addr != 0x4034") + } + t.Logf(".bss+4 == 0x%08x", addr) } func TestELFRead(t *testing.T) { - t.Logf("Testing ELF vaddr reads (%s)...", elfFile) - e, _ := NewELF(elfFile) - readSize := 6 - addr := uint64(0x2004) - data, err := e.Read(addr, 6) - if err != nil { - t.Fatal(err) - } - - if string(data) != "lolwut" { - t.Fatal("Read data does not match expected") - } - t.Logf("Read %v bytes from vaddr:0x%08x:\n%s", readSize, addr, hex.Dump(data)) + t.Logf("Testing ELF vaddr reads (%s)...", elfFile) + e, _ := NewELF(elfFile) + readSize := 6 + addr := uint64(0x2004) + data, err := e.Read(addr, 6) + if err != nil { + t.Fatal(err) + } + + if string(data) != "lolwut" { + t.Fatal("Read data does not match expected") + } + t.Logf("Read %v bytes from vaddr:0x%08x:\n%s", readSize, addr, hex.Dump(data)) } func TestELFGetSignatureVAddrs(t *testing.T) { - t.Logf("Testing ELF binary signature search") - e, _ := NewELF(elfFile) - vaddrs, err := e.GetSignatureVAddrs([]byte("lolwut")) - if err != nil { - t.Fatal(err) - } - - if vaddrs[0] != 0x2004 { - t.Fatal("Signature vaddr != 0x2004") - } + t.Logf("Testing ELF binary signature search") + e, _ := NewELF(elfFile) + vaddrs, err := e.GetSignatureVAddrs([]byte("lolwut")) + if err != nil { + t.Fatal(err) + } + + if vaddrs[0] != 0x2004 { + t.Fatal("Signature vaddr != 0x2004") + } } func TestELFGetOpcodeVAddrs(t *testing.T) { - leaRDI := []byte{0x48, 0x8d, 0x3d, 0xb9, 0x0e, 0x00, 0x00} - e, _ := NewELF(elfFile) - vaddrs, err := e.GetOpcodeVAddrs(leaRDI) - if err != nil { - t.Fatal(err) - } - - if vaddrs[0] != 0x1144 { - t.Fatal("Opcode vaddr != 0x1144") - } + leaRDI := []byte{0x48, 0x8d, 0x3d, 0xb9, 0x0e, 0x00, 0x00} + e, _ := NewELF(elfFile) + vaddrs, err := e.GetOpcodeVAddrs(leaRDI) + if err != nil { + t.Fatal(err) + } + + if vaddrs[0] != 0x1144 { + t.Fatal("Opcode vaddr != 0x1144") + } } func TestRead8(t *testing.T) { - e, _ := NewELF(elfFile) - i8, err := e.Read16LE(0x2c4) - if err != nil { - t.Fatal(err) - } - - if i8 != 0x04 { - t.Fatal("Uint8 != 0x04") - } + e, _ := NewELF(elfFile) + i8, err := e.Read16LE(0x2c4) + if err != nil { + t.Fatal(err) + } + + if i8 != 0x04 { + t.Fatal("Uint8 != 0x04") + } } func TestRead16(t *testing.T) { - t.Logf("Testing 16-bit integer reads (%s)...", elfFile) - e, _ := NewELF(elfFile) - i16, err := e.Read16LE(0x2c4) - if err != nil { - t.Fatal(err) - } - - if i16 != 0x0004 { - t.Fatal("Little endian uint16 != 0x0004") - } - - i16, err = e.Read16BE(0x2c4) - if err != nil { - t.Fatal(err) - } - - if i16 != 0x0400 { - t.Fatal("Big endian uint16 != 0x0400") - } + t.Logf("Testing 16-bit integer reads (%s)...", elfFile) + e, _ := NewELF(elfFile) + i16, err := e.Read16LE(0x2c4) + if err != nil { + t.Fatal(err) + } + + if i16 != 0x0004 { + t.Fatal("Little endian uint16 != 0x0004") + } + + i16, err = e.Read16BE(0x2c4) + if err != nil { + t.Fatal(err) + } + + if i16 != 0x0400 { + t.Fatal("Big endian uint16 != 0x0400") + } } func TestRead32(t *testing.T) { - t.Logf("Testing 32-bit integer reads (%s)...", elfFile) - e, _ := NewELF(elfFile) - i32, err := e.Read32LE(0x2d0) - if err != nil { - t.Fatal(err) - } - - if i32 != 0x00554e47 { - t.Fatal("Little endian uint32 != 0x00554e47") - } - - i32, err = e.Read32BE(0x2d0) - if err != nil { - t.Fatal(err) - } - - if i32 != 0x474e5500 { - t.Fatal("Big endian uint32 != 0x474e5500") - } + t.Logf("Testing 32-bit integer reads (%s)...", elfFile) + e, _ := NewELF(elfFile) + i32, err := e.Read32LE(0x2d0) + if err != nil { + t.Fatal(err) + } + + if i32 != 0x00554e47 { + t.Fatal("Little endian uint32 != 0x00554e47") + } + + i32, err = e.Read32BE(0x2d0) + if err != nil { + t.Fatal(err) + } + + if i32 != 0x474e5500 { + t.Fatal("Big endian uint32 != 0x474e5500") + } } func TestRead64(t *testing.T) { - t.Logf("Testing 64-bit integer reads (%s)...", elfFile) - e, _ := NewELF(elfFile) - i64, err := e.Read64LE(0x2f0) - if err != nil { - t.Fatal(err) - } - - if i64 != 0x3e95a14400554e47 { - t.Fatal("Little endian uint64 != 0x3e95a14400554e47") - } - - i64, err = e.Read64BE(0x2f0) - if err != nil { - t.Fatal(err) - } - - if i64 != 0x474e550044a1953e { - t.Fatal("Big endian uint64 != 0x474e550044a1953e") - } + t.Logf("Testing 64-bit integer reads (%s)...", elfFile) + e, _ := NewELF(elfFile) + i64, err := e.Read64LE(0x2f0) + if err != nil { + t.Fatal(err) + } + + if i64 != 0x3e95a14400554e47 { + t.Fatal("Little endian uint64 != 0x3e95a14400554e47") + } + + i64, err = e.Read64BE(0x2f0) + if err != nil { + t.Fatal(err) + } + + if i64 != 0x474e550044a1953e { + t.Fatal("Big endian uint64 != 0x474e550044a1953e") + } } diff --git a/remote.go b/remote.go index 0d686ad..7dcd040 100644 --- a/remote.go +++ b/remote.go @@ -1,146 +1,146 @@ -package sploit; - -import( - "net" - "strconv" - "bufio" - "io" - "errors" - "bytes" - "fmt" +package sploit + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "net" + "strconv" ) // Remote is an interface for communicating over IP type Remote struct { - HostPort string - Host string - Port uint16 - C net.Conn + HostPort string + Host string + Port uint16 + C net.Conn } // NewRemote connects to the specified remote host and returns an initialized Remote instance func NewRemote(protocol string, hostPort string) (*Remote, error) { - host, portString, err := net.SplitHostPort(hostPort) - if err != nil { - return nil, err - } - - port, err := strconv.ParseUint(portString, 10, 16) - if err != nil { - return nil, err - } - - c, err := net.Dial(protocol, hostPort) - if err != nil { - return nil, err - } - - return &Remote{ - HostPort: hostPort, - Host: host, - Port: uint16(port), - C: c, - }, nil + host, portString, err := net.SplitHostPort(hostPort) + if err != nil { + return nil, err + } + + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return nil, err + } + + c, err := net.Dial(protocol, hostPort) + if err != nil { + return nil, err + } + + return &Remote{ + HostPort: hostPort, + Host: host, + Port: uint16(port), + C: c, + }, nil } // Close is a Remote method for closing the connection if it's active -func (r *Remote)Close() { - if r.C != nil { - r.C.Close() - } +func (r *Remote) Close() { + if r.C != nil { + r.C.Close() + } } // RecvUntil is a Remote method for receiving data over IP until the specified sequence of bytes is detected -func (r *Remote)RecvUntil(needle []byte, drop bool)([]byte, error) { - data := make([]byte, len(needle)) - b := bufio.NewReader(r.C) - - // Read needle-size - n, err := io.ReadFull(b, data) - if err != nil { - return nil, err - } - - // Make sure it read the entire size of the needle - if n != len(needle) { - return nil, errors.New("RecvUntil truncated") - } - - // Compare needle and received data and continue to read a byte at a time - idx := 0 - for { - if bytes.Compare(data[idx:idx+len(needle)], needle) == 0 { - if drop == true { - return data[0:len(data)-len(needle)], nil - } - return data, nil - } - - byt, err := b.ReadByte() - if err != nil { - return nil, err - } - - data = append(data, byt) - idx++ - } +func (r *Remote) RecvUntil(needle []byte, drop bool) ([]byte, error) { + data := make([]byte, len(needle)) + b := bufio.NewReader(r.C) + + // Read needle-size + n, err := io.ReadFull(b, data) + if err != nil { + return nil, err + } + + // Make sure it read the entire size of the needle + if n != len(needle) { + return nil, errors.New("RecvUntil truncated") + } + + // Compare needle and received data and continue to read a byte at a time + idx := 0 + for { + if bytes.Compare(data[idx:idx+len(needle)], needle) == 0 { + if drop == true { + return data[0 : len(data)-len(needle)], nil + } + return data, nil + } + + byt, err := b.ReadByte() + if err != nil { + return nil, err + } + + data = append(data, byt) + idx++ + } } // RecvLine is a Remote method for receiving data over IP until a newline delimiter is detected -func (r *Remote)RecvLine()([]byte, error) { - return r.RecvUntil([]byte("\n"), true) +func (r *Remote) RecvLine() ([]byte, error) { + return r.RecvUntil([]byte("\n"), true) } // RecvN is a Remote method for receiving a specified number of bytes -func (r *Remote)RecvN(n int)([]byte, error) { - b := make([]byte, n) - rn, err := r.C.Read(b) - if err != nil { - return nil, err - } - - if rn != n { - return nil, errors.New("RecvN truncated") - } - - return b, nil +func (r *Remote) RecvN(n int) ([]byte, error) { + b := make([]byte, n) + rn, err := r.C.Read(b) + if err != nil { + return nil, err + } + + if rn != n { + return nil, errors.New("RecvN truncated") + } + + return b, nil } // Send is a Remote method for sending data over IP -func (r *Remote)Send(data []byte)(int, error) { - return r.C.Write(data) +func (r *Remote) Send(data []byte) (int, error) { + return r.C.Write(data) } // SendLine is a Remote method for sending data with a trailing newline character -func (r *Remote)SendLine(line []byte)(int, error) { - line = append(line, '\n') - return r.C.Write(line) +func (r *Remote) SendLine(line []byte) (int, error) { + line = append(line, '\n') + return r.C.Write(line) } // Interactive is a Remote method that allows the user to interact with a remote process manually -func (r *Remote)Interactive() error { - go func() { - for { - data, err := r.RecvN(1) - if err != nil { - break; - } - - fmt.Printf("%c", data[0]) - } - }() - - for { - var line string - fmt.Scanln(&line) - if line == "_quit" { - fmt.Println("Exiting..."); - return nil; - } - - _, err := r.SendLine([]byte(line)) - if err != nil { - return err; - } - } +func (r *Remote) Interactive() error { + go func() { + for { + data, err := r.RecvN(1) + if err != nil { + break + } + + fmt.Printf("%c", data[0]) + } + }() + + for { + var line string + fmt.Scanln(&line) + if line == "_quit" { + fmt.Println("Exiting...") + return nil + } + + _, err := r.SendLine([]byte(line)) + if err != nil { + return err + } + } } diff --git a/remote_test.go b/remote_test.go index 543b746..76f2f19 100644 --- a/remote_test.go +++ b/remote_test.go @@ -1,161 +1,160 @@ -package sploit; +package sploit -import( - "net" - "time" - "testing" +import ( + "net" + "testing" + "time" ) func TestRecvLine(t *testing.T) { - go func() { - time.Sleep(500 * time.Millisecond) - r, err := NewRemote("tcp", "127.0.0.1:8000") - if err != nil { - t.Fatal(err) - } - defer r.Close() - - data, err := r.RecvLine() - if err != nil { - t.Fatal(err) - } - - if string(data) != "lolwut" { - t.Fatal("Received line != lolwut") - } - }() - - l, err := net.Listen("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer l.Close() - - conn, err := l.Accept() - if err != nil { - return - } - defer conn.Close() - conn.Write([]byte("lolwut\n")) + go func() { + time.Sleep(500 * time.Millisecond) + r, err := NewRemote("tcp", "127.0.0.1:8000") + if err != nil { + t.Fatal(err) + } + defer r.Close() + + data, err := r.RecvLine() + if err != nil { + t.Fatal(err) + } + + if string(data) != "lolwut" { + t.Fatal("Received line != lolwut") + } + }() + + l, err := net.Listen("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + conn, err := l.Accept() + if err != nil { + return + } + defer conn.Close() + conn.Write([]byte("lolwut\n")) } func TestRecvUntil(t *testing.T) { - go func() { - time.Sleep(500 * time.Millisecond) - r, err := NewRemote("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer r.Close() - - data, err := r.RecvUntil([]byte("cmd>"), true) - if err != nil { - t.Fatal(err) - } - - if string(data) != "lolwut\n" { - t.Fatal("Received data != lolwut") - } - }() - - l, err := net.Listen("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer l.Close() - - conn, err := l.Accept() - if err != nil { - return - } - defer conn.Close() - conn.Write([]byte("lolwut\n")) - conn.Write([]byte("cmd>")) + go func() { + time.Sleep(500 * time.Millisecond) + r, err := NewRemote("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer r.Close() + + data, err := r.RecvUntil([]byte("cmd>"), true) + if err != nil { + t.Fatal(err) + } + + if string(data) != "lolwut\n" { + t.Fatal("Received data != lolwut") + } + }() + + l, err := net.Listen("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + conn, err := l.Accept() + if err != nil { + return + } + defer conn.Close() + conn.Write([]byte("lolwut\n")) + conn.Write([]byte("cmd>")) } func TestRecvN(t *testing.T) { - go func() { - time.Sleep(500 * time.Millisecond) - r, err := NewRemote("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer r.Close() - - data, err := r.RecvN(6) - if err != nil { - t.Fatal(err) - } - - if string(data) != "lolwut" { - t.Fatal("RecvN data != lolwut") - } - }() - - l, err := net.Listen("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer l.Close() - - conn, err := l.Accept() - if err != nil { - return - } - defer conn.Close() - conn.Write([]byte("lolwut")) + go func() { + time.Sleep(500 * time.Millisecond) + r, err := NewRemote("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer r.Close() + + data, err := r.RecvN(6) + if err != nil { + t.Fatal(err) + } + + if string(data) != "lolwut" { + t.Fatal("RecvN data != lolwut") + } + }() + + l, err := net.Listen("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + conn, err := l.Accept() + if err != nil { + return + } + defer conn.Close() + conn.Write([]byte("lolwut")) } func TestSend(t *testing.T) { - go func() { - time.Sleep(500 * time.Millisecond) - r, err := NewRemote("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer r.Close() - - _, err = r.Send([]byte("Send test")) - if err != nil { - t.Fatal(err) - } - - _, err = r.SendLine([]byte("SendLine test")) - if err != nil { - t.Fatal(err) - } - }() - - l, err := net.Listen("tcp", ":8000") - if err != nil { - t.Fatal(err) - } - defer l.Close() - - conn, err := l.Accept() - if err != nil { - return - } - defer conn.Close() - - - b := make([]byte, 9) - _, err = conn.Read(b) - if err != nil { - t.Fatal(err) - } - - if string(b) != "Send test" { - t.Fatal("Send test data != expected") - } - - b = make([]byte, 14) - _, err = conn.Read(b) - if err != nil { - t.Fatal(err) - } - - if string(b) != "SendLine test\n" { - t.Fatal("SendLine test data != expected") - } + go func() { + time.Sleep(500 * time.Millisecond) + r, err := NewRemote("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer r.Close() + + _, err = r.Send([]byte("Send test")) + if err != nil { + t.Fatal(err) + } + + _, err = r.SendLine([]byte("SendLine test")) + if err != nil { + t.Fatal(err) + } + }() + + l, err := net.Listen("tcp", ":8000") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + conn, err := l.Accept() + if err != nil { + return + } + defer conn.Close() + + b := make([]byte, 9) + _, err = conn.Read(b) + if err != nil { + t.Fatal(err) + } + + if string(b) != "Send test" { + t.Fatal("Send test data != expected") + } + + b = make([]byte, 14) + _, err = conn.Read(b) + if err != nil { + t.Fatal(err) + } + + if string(b) != "SendLine test\n" { + t.Fatal("SendLine test data != expected") + } } diff --git a/rop.go b/rop.go index 63464c3..db17ddb 100644 --- a/rop.go +++ b/rop.go @@ -1,103 +1,103 @@ -package sploit; +package sploit -import( - "errors" - "strings" - "regexp" - "fmt" +import ( + "errors" + "fmt" + "regexp" + "strings" ) // Gadget stores information about a ROP gadgets including the address, instructions, and opcode bytes type Gadget struct { - Address uint64 - Instrs string - Opcode []byte + Address uint64 + Instrs string + Opcode []byte } // ROP is a interface for working with ROP gadgets type ROP []*Gadget // Dump is a ROP method that locates and prints ROP gadgets contained in the ELF file to stdout -func (r *ROP)Dump() { - for _, gadget := range []*Gadget(*r) { - fmt.Printf("0x%08x: %v\n", gadget.Address, gadget.Instrs) - } +func (r *ROP) Dump() { + for _, gadget := range []*Gadget(*r) { + fmt.Printf("0x%08x: %v\n", gadget.Address, gadget.Instrs) + } } // InstrSearch is a ROP method that returns a ROP object containing gadgets with a sub-string match to the user-defined regex -func (r *ROP)InstrSearch(regex string)(ROP, error) { - re, err := regexp.Compile(regex) - if err != nil { - return nil, err - } - - matchGadgets := ROP{} - for _, gadget := range []*Gadget(*r) { - if re.FindAllString(gadget.Instrs, 1) != nil { - matchGadgets = append(matchGadgets, gadget) - } - } - - return matchGadgets, nil +func (r *ROP) InstrSearch(regex string) (ROP, error) { + re, err := regexp.Compile(regex) + if err != nil { + return nil, err + } + + matchGadgets := ROP{} + for _, gadget := range []*Gadget(*r) { + if re.FindAllString(gadget.Instrs, 1) != nil { + matchGadgets = append(matchGadgets, gadget) + } + } + + return matchGadgets, nil } func disasmInstrsFromRet(processor *Processor, data []byte, index int, address uint64) ([]*Gadget, error) { - stop := index - 15 - if stop < 0 { - stop = 0 - } - - gadgets := []*Gadget{} - for i := index-1; i > stop; i-- { - instr, err := disasmGadget(address+uint64(i), data[i:index+1], processor) - if err != nil { - continue - } - - if strings.Contains(instr, "leave") || - !strings.HasSuffix(strings.TrimSpace(instr), "ret") || - strings.Count(instr, "ret") > 1 { - continue - } - - gadgets = append( - gadgets, - &Gadget { - Address: address+uint64(i), - Instrs: instr, - Opcode: data[i:index+1], - }, - ) - } - - return gadgets, nil + stop := index - 15 + if stop < 0 { + stop = 0 + } + + gadgets := []*Gadget{} + for i := index - 1; i > stop; i-- { + instr, err := disasmGadget(address+uint64(i), data[i:index+1], processor) + if err != nil { + continue + } + + if strings.Contains(instr, "leave") || + !strings.HasSuffix(strings.TrimSpace(instr), "ret") || + strings.Count(instr, "ret") > 1 { + continue + } + + gadgets = append( + gadgets, + &Gadget{ + Address: address + uint64(i), + Instrs: instr, + Opcode: data[i : index+1], + }, + ) + } + + return gadgets, nil } func findGadgetsIntel(processor *Processor, data []byte, address uint64) ([]*Gadget, error) { - gadgets := []*Gadget{} - for i := 0; i < len(data); i++ { - if data[i] == 0xc3 || data[i] == 0xcb { - gadgetsRet, err := disasmInstrsFromRet(processor, data, i, address) - if err != nil { - return nil, err - } - - gadgets = append(gadgets, gadgetsRet...) - } - } - - return gadgets, nil + gadgets := []*Gadget{} + for i := 0; i < len(data); i++ { + if data[i] == 0xc3 || data[i] == 0xcb { + gadgetsRet, err := disasmInstrsFromRet(processor, data, i, address) + if err != nil { + return nil, err + } + + gadgets = append(gadgets, gadgetsRet...) + } + } + + return gadgets, nil } func findGadgets(processor *Processor, data []byte, address uint64) ([]*Gadget, error) { - switch (processor.Architecture) { - case ArchX8664: - return findGadgetsIntel(processor, data, address) - case ArchIA64: - return findGadgetsIntel(processor, data, address) - case ArchI386: - return findGadgetsIntel(processor, data, address) - default: - return nil, errors.New("ROP interface currently only supports Intel binaries") - } + switch processor.Architecture { + case ArchX8664: + return findGadgetsIntel(processor, data, address) + case ArchIA64: + return findGadgetsIntel(processor, data, address) + case ArchI386: + return findGadgetsIntel(processor, data, address) + default: + return nil, errors.New("ROP interface currently only supports Intel binaries") + } } diff --git a/rop_test.go b/rop_test.go index d406f38..3b3efbd 100644 --- a/rop_test.go +++ b/rop_test.go @@ -1,43 +1,43 @@ -package sploit; +package sploit import ( - "testing" - "bytes" + "bytes" + "testing" ) func TestROPDump(t *testing.T) { - t.Logf("Testing ROP gadget dump (%s)...", elfFile) - e, _ := NewELF(elfFile) - r, err := e.ROP() - if err != nil { - t.Fatal(err) - } - r.Dump() + t.Logf("Testing ROP gadget dump (%s)...", elfFile) + e, _ := NewELF(elfFile) + r, err := e.ROP() + if err != nil { + t.Fatal(err) + } + r.Dump() } func TestROPInstrSearch(t *testing.T) { - t.Logf("Testing ROP gadget search (%s)...", elfFile) - e, _ := NewELF(elfFile) - r, _ := e.ROP() - gadgets, err := r.InstrSearch(".*") - if err != nil { - t.Fatal(err) - } + t.Logf("Testing ROP gadget search (%s)...", elfFile) + e, _ := NewELF(elfFile) + r, _ := e.ROP() + gadgets, err := r.InstrSearch(".*") + if err != nil { + t.Fatal(err) + } - if len(gadgets) != 63 { - t.Fatal("Number of gadgets for wildcard match != 63") - } + if len(gadgets) != 63 { + t.Fatal("Number of gadgets for wildcard match != 63") + } - gadgets, err = r.InstrSearch("add rsp, 8 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret") - if err != nil { - t.Fatal(err) - } + gadgets, err = r.InstrSearch("add rsp, 8 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret") + if err != nil { + t.Fatal(err) + } - if len(gadgets) != 1 || gadgets[0].Address != 0x11ae { - t.Fatal("Single gadget search did not return gadget at 0x11ae") - } + if len(gadgets) != 1 || gadgets[0].Address != 0x11ae { + t.Fatal("Single gadget search did not return gadget at 0x11ae") + } - if bytes.Compare(gadgets[0].Opcode, []byte{0x48, 0x83, 0xc4, 0x08, 0x5b, 0x5d, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0xc3}) != 0 { - t.Fatal("Gadget machine code does not match expected") - } + if bytes.Compare(gadgets[0].Opcode, []byte{0x48, 0x83, 0xc4, 0x08, 0x5b, 0x5d, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0xc3}) != 0 { + t.Fatal("Gadget machine code does not match expected") + } } diff --git a/sploit.go b/sploit.go index 939a8cd..2c60f62 100644 --- a/sploit.go +++ b/sploit.go @@ -1,16 +1,18 @@ -package sploit; +package sploit -import( - "fmt" +import ( + "fmt" ) // FileFormat represents the type of file under analysis type FileFormat uint16 // PEFile represents Microsoft PE file format -const PEFile = 0 +const PEFile = 0 + // ELFFile represents Unix ELF file format -const ELFFile = 1 +const ELFFile = 1 + // UnknownFile indicates that the file format is unsupported const UnknownFile = 2 @@ -19,23 +21,29 @@ type Architecture uint16 // ArchX8664 indicates Intel x86-64 ISA const ArchX8664 = 0 + // ArchI386 - Intel x86 const ArchI386 = 1 + // ArchARM - ARM (32-bit) const ArchARM = 2 + // ArchAARCH64 - ARM (64-bit) const ArchAARCH64 = 3 + // ArchPPC - PowerPC const ArchPPC = 4 + // ArchMIPS - MIPS const ArchMIPS = 5 + // ArchIA64 - Intel Itanium const ArchIA64 = 6 // Processor is a struct that represents a binary's machine type type Processor struct { - Architecture Architecture - Endian Endian + Architecture Architecture + Endian Endian } // Endian is a integer type that represents the byte order of a binary @@ -43,23 +51,23 @@ type Endian int // LittleEndian - little endian byte order const LittleEndian Endian = 0 + // BigEndian - big endian byte order const BigEndian Endian = 1 func (e Endian) String() string { - switch e { - case LittleEndian: - return "little" - case BigEndian: - return "big" - default: - return fmt.Sprintf("%d", int(e)) - } + switch e { + case LittleEndian: + return "little" + case BigEndian: + return "big" + default: + return fmt.Sprintf("%d", int(e)) + } } // Mitigations is used to store information on exploit mitigations detected while loading the binary type Mitigations struct { - Canary bool - NX bool + Canary bool + NX bool } - diff --git a/struct.go b/struct.go index e9182d6..abac0f6 100644 --- a/struct.go +++ b/struct.go @@ -1,77 +1,77 @@ -package sploit; +package sploit -import( - "encoding/binary" +import ( + "encoding/binary" ) // PackUint64LE packs a uint64 into a byte slice in little endian format func PackUint64LE(i uint64) []byte { - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b, i) - return b + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, i) + return b } // PackUint32LE packs a uint32 into a byte slice in little endian format func PackUint32LE(i uint32) []byte { - b := make([]byte, 4) - binary.LittleEndian.PutUint32(b, i) - return b + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, i) + return b } // PackUint16LE packs a uint16 into a byte slice in little endian format func PackUint16LE(i uint16) []byte { - b := make([]byte, 2) - binary.LittleEndian.PutUint16(b, i) - return b + b := make([]byte, 2) + binary.LittleEndian.PutUint16(b, i) + return b } // PackUint64BE packs a uint64 into a byte slice in big endian format func PackUint64BE(i uint64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, i) - return b + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, i) + return b } // PackUint32BE packs a uint32 into a byte slice in big endian format func PackUint32BE(i uint32) []byte { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, i) - return b + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, i) + return b } // PackUint16BE packs a uint16 into a byte slice in big endian format func PackUint16BE(i uint16) []byte { - b := make([]byte, 2) - binary.BigEndian.PutUint16(b, i) - return b + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, i) + return b } // UnpackUint64LE unpacks a byte slice in little endian format into a uint64 func UnpackUint64LE(b []byte) uint64 { - return binary.LittleEndian.Uint64(b) + return binary.LittleEndian.Uint64(b) } // UnpackUint32LE unpacks a byte slice in little endian format into a uint32 func UnpackUint32LE(b []byte) uint32 { - return binary.LittleEndian.Uint32(b) + return binary.LittleEndian.Uint32(b) } // UnpackUint16LE unpacks a byte slice in little endian format into a uint16 func UnpackUint16LE(b []byte) uint16 { - return binary.LittleEndian.Uint16(b) + return binary.LittleEndian.Uint16(b) } // UnpackUint64BE unpacks a byte slice in big endian format into a uint64 func UnpackUint64BE(b []byte) uint64 { - return binary.BigEndian.Uint64(b) + return binary.BigEndian.Uint64(b) } // UnpackUint32BE unpacks a byte slice in big endian format into a uint32 func UnpackUint32BE(b []byte) uint32 { - return binary.BigEndian.Uint32(b) + return binary.BigEndian.Uint32(b) } // UnpackUint16BE unpacks a byte slice in big endian format into a uint16 func UnpackUint16BE(b []byte) uint16 { - return binary.BigEndian.Uint16(b) + return binary.BigEndian.Uint16(b) } diff --git a/struct_test.go b/struct_test.go index 6660b1e..2d6d381 100644 --- a/struct_test.go +++ b/struct_test.go @@ -1,78 +1,78 @@ -package sploit; +package sploit import ( - "testing" - "bytes" + "bytes" + "testing" ) func TestPackUint64LE(t *testing.T) { - if bytes.Compare(PackUint64LE(0xf00bdeadbeeff00b), []byte{0x0b, 0xf0, 0xef, 0xbe, 0xad, 0xde, 0x0b, 0xf0}) != 0 { - t.Fatal("Return bytes != expected") - } + if bytes.Compare(PackUint64LE(0xf00bdeadbeeff00b), []byte{0x0b, 0xf0, 0xef, 0xbe, 0xad, 0xde, 0x0b, 0xf0}) != 0 { + t.Fatal("Return bytes != expected") + } } func TestPackUint32LE(t *testing.T) { - if bytes.Compare(PackUint32LE(0xf00bdead), []byte{0xad, 0xde, 0x0b, 0xf0}) != 0 { - t.Fatal("Return bytes != expected") - } + if bytes.Compare(PackUint32LE(0xf00bdead), []byte{0xad, 0xde, 0x0b, 0xf0}) != 0 { + t.Fatal("Return bytes != expected") + } } func TestPackUint16LE(t *testing.T) { - if bytes.Compare(PackUint16LE(0xf00b), []byte{0x0b, 0xf0}) != 0 { - t.Fatal("Return bytes != expected") - } + if bytes.Compare(PackUint16LE(0xf00b), []byte{0x0b, 0xf0}) != 0 { + t.Fatal("Return bytes != expected") + } } func TestPackUint64BE(t *testing.T) { - if bytes.Compare(PackUint64BE(0xf00bdeadbeeff00b), []byte{0xf0, 0x0b, 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0b}) != 0 { - t.Fatal("Return bytes != expected") - } + if bytes.Compare(PackUint64BE(0xf00bdeadbeeff00b), []byte{0xf0, 0x0b, 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0b}) != 0 { + t.Fatal("Return bytes != expected") + } } func TestPackUint32BE(t *testing.T) { - if bytes.Compare(PackUint32BE(0xf00bdead), []byte{0xf0, 0x0b, 0xde, 0xad}) != 0 { - t.Fatal("Return bytes != expected") - } + if bytes.Compare(PackUint32BE(0xf00bdead), []byte{0xf0, 0x0b, 0xde, 0xad}) != 0 { + t.Fatal("Return bytes != expected") + } } func TestPackUint16BE(t *testing.T) { - if bytes.Compare(PackUint16BE(0xf00b), []byte{0xf0, 0x0b}) != 0 { - t.Fatal("Return bytes != expected") - } + if bytes.Compare(PackUint16BE(0xf00b), []byte{0xf0, 0x0b}) != 0 { + t.Fatal("Return bytes != expected") + } } func TestUnpackUint64LE(t *testing.T) { - if UnpackUint64LE([]byte{0x0b, 0xf0, 0xef, 0xbe, 0xad, 0xde, 0x0b, 0xf0}) != 0xf00bdeadbeeff00b { - t.Fatal("Return value != expected") - } + if UnpackUint64LE([]byte{0x0b, 0xf0, 0xef, 0xbe, 0xad, 0xde, 0x0b, 0xf0}) != 0xf00bdeadbeeff00b { + t.Fatal("Return value != expected") + } } func TestUnpackUint32LE(t *testing.T) { - if UnpackUint32LE([]byte{0x0b, 0xf0, 0xef, 0xbe}) != 0xbeeff00b { - t.Fatal("Return value != expected") - } + if UnpackUint32LE([]byte{0x0b, 0xf0, 0xef, 0xbe}) != 0xbeeff00b { + t.Fatal("Return value != expected") + } } func TestUnpackUint16LE(t *testing.T) { - if UnpackUint16LE([]byte{0x0b, 0xf0}) != 0xf00b { - t.Fatal("Return value != expected") - } + if UnpackUint16LE([]byte{0x0b, 0xf0}) != 0xf00b { + t.Fatal("Return value != expected") + } } func TestUnpackUint64BE(t *testing.T) { - if UnpackUint64BE([]byte{0xf0, 0x0b, 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0b}) != 0xf00bdeadbeeff00b { - t.Fatal("Return value != expected") - } + if UnpackUint64BE([]byte{0xf0, 0x0b, 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0b}) != 0xf00bdeadbeeff00b { + t.Fatal("Return value != expected") + } } func TestUnpackUint32BE(t *testing.T) { - if UnpackUint32BE([]byte{0xf0, 0x0b, 0xde, 0xad}) != 0xf00bdead { - t.Fatal("Return value != expected") - } + if UnpackUint32BE([]byte{0xf0, 0x0b, 0xde, 0xad}) != 0xf00bdead { + t.Fatal("Return value != expected") + } } func TestUnpackUint16BE(t *testing.T) { - if UnpackUint16BE([]byte{0xf0, 0x0b}) != 0xf00b { - t.Fatal("Return value != expected") - } + if UnpackUint16BE([]byte{0xf0, 0x0b}) != 0xf00b { + t.Fatal("Return value != expected") + } }