aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2021-07-23 16:09:45 +0000
committerAleksandr Nogikh <wp32pw@gmail.com>2021-08-12 17:24:45 +0200
commit05b6b79e7cb914acc958e8ec7ef9a6abe5fe6ff1 (patch)
tree881d184f9314572ac923d0c7311963510c09ce52 /pkg
parent6972b10616d785401dea17cec890cca8916424a7 (diff)
pkg/report: add opcode decompilation functionality
Create a module responsible for converting a sequence of machine code bytes into human-readable instructions.
Diffstat (limited to 'pkg')
-rw-r--r--pkg/report/decompile.go122
-rw-r--r--pkg/report/decompile_test.go52
2 files changed, 174 insertions, 0 deletions
diff --git a/pkg/report/decompile.go b/pkg/report/decompile.go
new file mode 100644
index 000000000..ea352fc29
--- /dev/null
+++ b/pkg/report/decompile.go
@@ -0,0 +1,122 @@
+// Copyright 2021 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package report
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "regexp"
+ "strconv"
+ "time"
+
+ "github.com/google/syzkaller/pkg/osutil"
+ "github.com/google/syzkaller/sys/targets"
+)
+
+type DecompilerFlagMask uint64
+
+// Extra flags that control the flow of decompilation.
+const (
+ FlagForceArmThumbMode DecompilerFlagMask = 1 << iota
+)
+
+const objdumpCallTimeout = 10 * time.Second
+
+type DecompiledOpcode struct {
+ Offset int
+ IsBad bool
+ FullDescription string
+}
+
+// Decompiles a byte array with opcodes into human-readable descriptions.
+// Target must specify the environment from which the opcodes were taken.
+func DecompileOpcodes(rawOpcodes []byte, flags DecompilerFlagMask, target *targets.Target) ([]DecompiledOpcode, error) {
+ args, err := objdumpBuildArgs(flags, target)
+ if err != nil {
+ return nil, err
+ }
+
+ outBytes, err := objdumpExecutor(rawOpcodes, args, target)
+ if err != nil {
+ return nil, err
+ }
+
+ list := objdumpParseOutput(outBytes)
+ if len(list) == 0 && len(rawOpcodes) > 0 {
+ return nil, fmt.Errorf("no instructions found while the total size is %v bytes", len(rawOpcodes))
+ }
+ return list, nil
+}
+
+func objdumpExecutor(rawOpcodes []byte, args []string, target *targets.Target) ([]byte, error) {
+ fileName, err := osutil.TempFile("syz-opcode-decompiler")
+ if err != nil {
+ return nil, fmt.Errorf("failed to create temp file: %v", err)
+ }
+ defer os.Remove(fileName)
+
+ err = osutil.WriteFile(fileName, rawOpcodes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to write to temp file: %v", err)
+ }
+
+ return osutil.RunCmd(objdumpCallTimeout, "", target.Objdump, append(args, fileName)...)
+}
+
+// nolint: lll
+var objdumpAsmLineRegexp = regexp.MustCompile(`\s+([a-fA-F0-9]+)\:\s+((?:[a-fA-F0-9]{2,8}\s*)*[a-fA-F0-9]{2,8})\s+(.*?)\s*$`)
+
+func objdumpParseOutput(rawOutput []byte) []DecompiledOpcode {
+ ret := []DecompiledOpcode{}
+ for s := bufio.NewScanner(bytes.NewReader(rawOutput)); s.Scan(); {
+ result := objdumpAsmLineRegexp.FindStringSubmatch(string(s.Bytes()))
+ if result == nil {
+ continue
+ }
+ offset, err := strconv.ParseUint(result[1], 16, 64)
+ if err != nil {
+ continue
+ }
+ const objdumpBadCommand = "(bad)"
+ ret = append(ret, DecompiledOpcode{
+ Offset: int(offset),
+ IsBad: result[3] == objdumpBadCommand,
+ FullDescription: result[0],
+ })
+ }
+ return ret
+}
+
+func objdumpBuildArgs(flags DecompilerFlagMask, target *targets.Target) ([]string, error) {
+ // objdump won't be able to decompile a raw binary file unless we specify the exact
+ // architecture through the -m parameter.
+ ret := []string{"-b", "binary", "-D"}
+ switch target.Arch {
+ case targets.ARM64:
+ ret = append(ret, "-maarch64")
+ case targets.ARM:
+ ret = append(ret, "-marm")
+ if flags&FlagForceArmThumbMode != 0 {
+ ret = append(ret, "-M", "force-thumb")
+ }
+ case targets.I386:
+ ret = append(ret, "-mi386")
+ case targets.AMD64:
+ ret = append(ret, "-mi386", "-Mx86-64")
+ case targets.MIPS64LE:
+ ret = append(ret, "-mmips")
+ case targets.PPC64LE:
+ ret = append(ret, "-mppc")
+ case targets.S390x:
+ ret = append(ret, "-m", "s390:64-bit")
+ case targets.RiscV64:
+ ret = append(ret, "-mriscv")
+ default:
+ return nil, fmt.Errorf("cannot build objdump args for %#v", target.Arch)
+ }
+
+ return ret, nil
+}
diff --git a/pkg/report/decompile_test.go b/pkg/report/decompile_test.go
new file mode 100644
index 000000000..15843de83
--- /dev/null
+++ b/pkg/report/decompile_test.go
@@ -0,0 +1,52 @@
+// Copyright 2021 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package report
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestParseObjdumpOutput(t *testing.T) {
+ rawResponse := `
+/tmp/binary: file format binary
+
+
+Disassembly of section .data:
+
+00000000 <.data>:
+ 0: 55 push %ebp
+ 1: 53 push %ebx
+ 2: 31 c0 xor %eax,%eax
+ 4: e8 f5 bf f7 ff call 0xfff7bffe
+ 9: ff (bad)
+`
+ opcodes := objdumpParseOutput([]byte(rawResponse))
+ expected := []DecompiledOpcode{
+ {
+ Offset: 0,
+ FullDescription: " 0: 55 push %ebp",
+ },
+ {
+ Offset: 1,
+ FullDescription: " 1: 53 push %ebx",
+ },
+ {
+ Offset: 2,
+ FullDescription: " 2: 31 c0 xor %eax,%eax",
+ },
+ {
+ Offset: 4,
+ FullDescription: " 4: e8 f5 bf f7 ff call 0xfff7bffe",
+ },
+ {
+ Offset: 9,
+ IsBad: true,
+ FullDescription: " 9: ff (bad)",
+ },
+ }
+ if !reflect.DeepEqual(opcodes, expected) {
+ t.Errorf("Expected: %#v, got: %#v", expected, opcodes)
+ }
+}