chore: refactor disassembly code to use new instruction representation

- Updated disassembly functions to utilize the new `disassemble.Inst` type instead of `disassemble.Instruction`.
- Modified operand retrieval functions to accommodate the new instruction structure.
- Enhanced error handling and logging for instruction decoding failures.
- Improved JSON output for disassembly to ensure disassembly strings are preserved.
- Refactored various components across the disassembly package, including Mach-O and dyld handling, to streamline instruction processing.
- Added tests to validate the new disassembly behavior and ensure backward compatibility.
This commit is contained in:
blacktop
2026-03-09 21:13:33 -06:00
parent 368742585b
commit 27e8ec23ad
7 changed files with 293 additions and 161 deletions
+140 -82
View File
@@ -172,35 +172,86 @@ func IsBranchOp(op disassemble.Operation) bool {
}
// IsLoadLiteral returns true if the instruction is a PC-relative literal load
func IsLoadLiteral(instr *disassemble.Instruction) bool {
func IsLoadLiteral(instr *disassemble.Inst) bool {
switch instr.Operation {
case disassemble.ARM64_LDR, disassemble.ARM64_LDRSW, disassemble.ARM64_PRFM:
return len(instr.Operands) > 1 && instr.Operands[1].Class == disassemble.LABEL
return instr != nil && instr.NumOps > 1 && instr.Operands[1].Class == disassemble.LABEL
}
return false
}
func operandRegister(instr *disassemble.Instruction, idx int) (disassemble.Register, bool) {
if instr == nil || len(instr.Operands) <= idx || len(instr.Operands[idx].Registers) == 0 {
func operandRegister(instr *disassemble.Inst, idx int) (disassemble.Register, bool) {
if instr == nil || int(instr.NumOps) <= idx || instr.Operands[idx].NumRegisters == 0 {
return disassemble.REG_NONE, false
}
return instr.Operands[idx].Registers[0], true
}
func operandImmediate(instr *disassemble.Instruction, idx int) (uint64, bool) {
if instr == nil || len(instr.Operands) <= idx {
func operandImmediate(instr *disassemble.Inst, idx int) (uint64, bool) {
if instr == nil || int(instr.NumOps) <= idx {
return 0, false
}
return instr.Operands[idx].Immediate, true
}
func operandShiftValue(instr *disassemble.Instruction, idx int) (uint64, bool) {
if instr == nil || len(instr.Operands) <= idx {
func operandShiftValue(instr *disassemble.Inst, idx int) (uint64, bool) {
if instr == nil || int(instr.NumOps) <= idx {
return 0, false
}
return uint64(instr.Operands[idx].ShiftValue), true
}
func implSpecificSysReg(op *disassemble.Op) (string, bool) {
if op == nil || !op.HasImplSpec {
return "", false
}
sysReg := disassemble.SystemReg(op.ImplSpec[0])<<14 |
disassemble.SystemReg(op.ImplSpec[1])<<11 |
disassemble.SystemReg(op.ImplSpec[2])<<7 |
disassemble.SystemReg(op.ImplSpec[3])<<3 |
disassemble.SystemReg(op.ImplSpec[4])
name := sysReg.String()
if len(name) == 0 {
return "", false
}
return name, true
}
func invalidInstruction(addr uint64, raw uint32, err error) disassemble.Instruction {
return disassemble.Instruction{
Address: addr,
Raw: raw,
Encoding: 0,
Operation: 0,
Operands: nil,
SetFlags: 0,
Disassembly: fmt.Sprintf(".long\t%#x ; (%s)\n", raw, err.Error()),
}
}
func logStubDecodeContext(
instruction *disassemble.Inst,
queue *[2]disassemble.Inst,
queueValid *[2]bool,
) {
if instruction != nil {
if text, err := instruction.Disassemble(); err == nil {
log.Debugf("%#08x: %s\t%s", instruction.Address, disassemble.GetOpCodeByteString(instruction.Raw), text)
}
}
for idx := range queue {
if queueValid[idx] {
if text, err := queue[idx].Disassemble(); err == nil {
log.Debugf("%#08x: %s\t%s", queue[idx].Address, disassemble.GetOpCodeByteString(queue[idx].Raw), text)
}
}
}
}
const maxCStringCommentLen = 200
func cstringComment(d Disass, addr uint64) string {
@@ -237,8 +288,9 @@ func Disassemble(d Disass) string {
var sb strings.Builder
var instrStr string
var instrValue uint32
var results [1024]byte
var prevInstr *disassemble.Instruction
var decoder disassemble.Decoder
var prevInstr disassemble.Inst
var hasPrev bool
var instructions []disassemble.Instruction
r := bytes.NewReader(d.Data())
@@ -254,8 +306,8 @@ func Disassemble(d Disass) string {
if !d.AsJSON() {
var comment string
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
var op string
var oprs string
if instrValue == 0xfeedfacf {
@@ -272,7 +324,7 @@ func Disassemble(d Disass) string {
op = ".long"
oprs = fmt.Sprintf("%#x", instrValue)
comment = " ; (probably a jump-table)"
} else if prevInstr != nil && strings.Contains(prevInstr.Operation.String(), "braa") {
} else if hasPrev && strings.Contains(prevInstr.Operation.String(), "braa") {
break // TODO: why did I do this again?
} else if (instrValue & 0xfffffC00) == 0x00201000 {
Xr := disassemble.Register((instrValue & 0x1F) + 34)
@@ -323,7 +375,20 @@ func Disassemble(d Disass) string {
goto INCR_ADDR
}
instrStr = instruction.String()
if instrStr, err = instruction.Disassemble(); err != nil {
if d.Color() {
fmt.Fprintf(&sb, "%s: %s %s %s%s\n",
colorAddr("%#08x", uint64(startAddr)),
colorOpCodes(disassemble.GetOpCodeByteString(instrValue)),
colorOp("%-7s", ".long"),
ColorOperands(fmt.Sprintf(" %#x", instrValue)),
colorComment(fmt.Sprintf(" ; (%s)", err.Error())),
)
} else {
fmt.Fprintf(&sb, "%#08x: %s .long\t%#x ; (%s)\n", uint64(startAddr), disassemble.GetOpCodeByteString(instrValue), instrValue, err.Error())
}
goto INCR_ADDR
}
if !d.Quite() {
// check for start of a new function
@@ -369,14 +434,14 @@ func Disassemble(d Disass) string {
if instruction.Operation == disassemble.ARM64_MRS || instruction.Operation == disassemble.ARM64_MSR {
var ops []string
replaced := false
for idx, op := range instruction.Operands {
for idx := 0; idx < int(instruction.NumOps); idx++ {
op := instruction.Operands[idx]
if op.Class == disassemble.REG {
if reg, ok := operandRegister(instruction, idx); ok {
if reg, ok := operandRegister(&instruction, idx); ok {
ops = append(ops, reg.String())
}
} else if op.Class == disassemble.IMPLEMENTATION_SPECIFIC {
sysRegFix := op.ImplSpec.GetSysReg().String()
if len(sysRegFix) > 0 {
if sysRegFix, ok := implSpecificSysReg(&op); ok {
ops = append(ops, sysRegFix)
replaced = true
}
@@ -387,7 +452,8 @@ func Disassemble(d Disass) string {
}
} else if ok, loc := d.IsBranchLocation(instruction.Address); ok {
opStr := strings.TrimPrefix(instrStr, fmt.Sprintf("%s\t", instruction.Operation))
for _, operand := range instruction.Operands {
for idx := 0; idx < int(instruction.NumOps); idx++ {
operand := instruction.Operands[idx]
if operand.Class == disassemble.LABEL {
if name, ok := d.FindSymbol(uint64(operand.Immediate)); ok {
display := symbols.FormatSymbol(name, d.Demangle())
@@ -409,21 +475,21 @@ func Disassemble(d Disass) string {
}
instrStr = fmt.Sprintf("%s\t%s", instruction.Operation, opStr)
} else if instruction.Operation == disassemble.ARM64_BL || instruction.Operation == disassemble.ARM64_B {
if target, ok := operandImmediate(instruction, 0); ok {
if target, ok := operandImmediate(&instruction, 0); ok {
if name, ok := d.FindSymbol(target); ok {
display := symbols.FormatSymbol(name, d.Demangle())
instrStr = fmt.Sprintf("%s\t%s", instruction.Operation, display)
comment = appendComment(comment, cstringComment(d, target))
}
}
} else if IsLoadLiteral(instruction) {
} else if IsLoadLiteral(&instruction) {
if name, ok := d.FindSymbol(uint64(instruction.Operands[1].Immediate)); ok {
display := symbols.FormatSymbol(name, d.Demangle())
comment = fmt.Sprintf(" ; %s", display)
comment = appendComment(comment, cstringComment(d, uint64(instruction.Operands[1].Immediate)))
}
} else if instruction.Operation == disassemble.ARM64_CBZ || instruction.Operation == disassemble.ARM64_CBNZ {
if target, ok := operandImmediate(instruction, 1); ok {
if target, ok := operandImmediate(&instruction, 1); ok {
if name, ok := d.FindSymbol(target); ok {
display := symbols.FormatSymbol(name, d.Demangle())
comment = fmt.Sprintf(" ; %s", display)
@@ -432,7 +498,8 @@ func Disassemble(d Disass) string {
}
} else if instruction.Operation == disassemble.ARM64_ADR {
opStr := strings.TrimPrefix(instrStr, fmt.Sprintf("%s\t", instruction.Operation))
for _, operand := range instruction.Operands {
for idx := 0; idx < int(instruction.NumOps); idx++ {
operand := instruction.Operands[idx]
if operand.Class == disassemble.LABEL {
if name, ok := d.FindSymbol(uint64(operand.Immediate)); ok {
display := symbols.FormatSymbol(name, d.Demangle())
@@ -450,29 +517,29 @@ func Disassemble(d Disass) string {
}
}
instrStr = fmt.Sprintf("%s\t%s", instruction.Operation, opStr)
} else if (prevInstr != nil && prevInstr.Operation == disassemble.ARM64_ADRP) &&
} else if hasPrev && prevInstr.Operation == disassemble.ARM64_ADRP &&
(instruction.Operation == disassemble.ARM64_ADD ||
instruction.Operation == disassemble.ARM64_LDR ||
instruction.Operation == disassemble.ARM64_LDRB ||
instruction.Operation == disassemble.ARM64_LDRSW ||
instruction.Operation == disassemble.ARM64_STRB) {
adrpRegister, ok := operandRegister(prevInstr, 0)
adrpRegister, ok := operandRegister(&prevInstr, 0)
if ok {
adrpImm, ok := operandImmediate(prevInstr, 1)
adrpImm, ok := operandImmediate(&prevInstr, 1)
if ok {
srcRegister, ok := operandRegister(instruction, 1)
srcRegister, ok := operandRegister(&instruction, 1)
if ok {
validPattern := true
if adrpRegister == srcRegister {
switch instruction.Operation {
case disassemble.ARM64_LDR, disassemble.ARM64_LDRB, disassemble.ARM64_LDRSW, disassemble.ARM64_STRB:
if imm, ok := operandImmediate(instruction, 1); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
adrpImm += imm
} else {
validPattern = false
}
case disassemble.ARM64_ADD:
if imm, ok := operandImmediate(instruction, 2); ok {
if imm, ok := operandImmediate(&instruction, 2); ok {
adrpImm += imm
} else {
validPattern = false
@@ -545,21 +612,19 @@ func Disassemble(d Disass) string {
}
prevInstr = instruction
hasPrev = true
} else { // output as JSON
instruction, err := disassemble.Decompose(startAddr, instrValue, &results) // TODO: it would probably be valuable to add a "comment" field and capture the analysis above for JSON peeps
if err != nil {
instructions = append(instructions, disassemble.Instruction{
Address: startAddr,
Raw: instrValue,
Encoding: 0,
Operation: 0,
Operands: nil,
SetFlags: 0,
Disassembly: fmt.Sprintf(".long\t%#x ; (%s)\n", instrValue, err.Error()), // TODO: same with error enhancements above
})
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil { // TODO: it would probably be valuable to add a "comment" field and capture the analysis above for JSON peeps
instructions = append(instructions, invalidInstruction(startAddr, instrValue, err)) // TODO: same with error enhancements above
goto INCR_ADDR
}
instructions = append(instructions, *instruction)
converted := instruction.ToInstruction()
if converted.Disassembly, err = instruction.Disassemble(); err != nil {
instructions = append(instructions, invalidInstruction(startAddr, instrValue, err))
goto INCR_ADDR
}
instructions = append(instructions, *converted)
}
INCR_ADDR:
startAddr += uint64(binary.Size(uint32(0)))
@@ -661,9 +726,9 @@ func ParseStubsForMachO(m *macho.File) (map[uint64]uint64, error) {
func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, error)) (map[uint64]uint64, error) {
var instrValue uint32
var results [1024]byte
queue := make([]*disassemble.Instruction, 2)
var decoder disassemble.Decoder
var queue [2]disassemble.Inst
var queueValid [2]bool
stubs := make(map[uint64]uint64)
@@ -678,22 +743,23 @@ func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, erro
break
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
queue[1] = queue[0] // push instruction onto const length FIFO queue
queue[0] = instruction
queueValid[1] = queueValid[0]
queueValid[0] = false
continue
}
if (queue[1] != nil && queue[1].Operation == disassemble.ARM64_ADRP) &&
(queue[0] != nil && queue[0].Operation == disassemble.ARM64_ADD) &&
if queueValid[1] && queue[1].Operation == disassemble.ARM64_ADRP &&
queueValid[0] && queue[0].Operation == disassemble.ARM64_ADD &&
instruction.Operation == disassemble.ARM64_LDR {
adrpRegister, ok := operandRegister(queue[1], 0)
adrpRegister, ok := operandRegister(&queue[1], 0)
if ok {
if addRegister, ok := operandRegister(queue[0], 0); ok {
if adrpBase, ok := operandImmediate(queue[1], 1); ok {
if addImm, ok := operandImmediate(queue[0], 2); ok {
if addRegister, ok := operandRegister(&queue[0], 0); ok {
if adrpBase, ok := operandImmediate(&queue[1], 1); ok {
if addImm, ok := operandImmediate(&queue[0], 2); ok {
if adrpRegister == addRegister {
// adrp x17, 0x221baf000
stubTarget := adrpBase
@@ -701,19 +767,14 @@ func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, erro
stubTarget += addImm
stubAddr := queue[1].Address // address of beginning of stub (adrp)
// ldr x16, [x17]
if ldrRegister, ok := operandRegister(instruction, 1); ok &&
if ldrRegister, ok := operandRegister(&instruction, 1); ok &&
(addRegister == ldrRegister) { // check add reg and ldr reg are the same
if ldrImm, ok := operandImmediate(instruction, 1); ok && ldrImm != 0 { // check for immediate
if ldrImm, ok := operandImmediate(&instruction, 1); ok && ldrImm != 0 { // check for immediate
stubTarget += ldrImm
}
addr, err := readPtr(stubTarget)
if err != nil {
log.Debugf("%#08x: %s\t%s", instruction.Address, disassemble.GetOpCodeByteString(instruction.Raw), instruction) // TODO: DRY this up
for _, i := range queue {
if i != nil {
log.Debugf("%#08x: %s\t%s", i.Address, disassemble.GetOpCodeByteString(i.Raw), i)
}
}
logStubDecodeContext(&instruction, &queue, &queueValid)
return nil, fmt.Errorf("failed to read stub target pointer at %#x: %v", stubTarget, err)
}
// braa x16, x17
@@ -724,14 +785,14 @@ func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, erro
}
}
}
} else if (queue[1] != nil && queue[1].Operation == disassemble.ARM64_ADRP) &&
(queue[0] != nil && queue[0].Operation == disassemble.ARM64_ADD) &&
} else if queueValid[1] && queue[1].Operation == disassemble.ARM64_ADRP &&
queueValid[0] && queue[0].Operation == disassemble.ARM64_ADD &&
(IsBranchOp(instruction.Operation) || instruction.Operation == disassemble.ARM64_BR) {
adrpRegister, ok := operandRegister(queue[1], 0)
adrpRegister, ok := operandRegister(&queue[1], 0)
if ok {
if addRegister, ok := operandRegister(queue[0], 0); ok {
if adrpBase, ok := operandImmediate(queue[1], 1); ok {
if addImm, ok := operandImmediate(queue[0], 2); ok {
if addRegister, ok := operandRegister(&queue[0], 0); ok {
if adrpBase, ok := operandImmediate(&queue[1], 1); ok {
if addImm, ok := operandImmediate(&queue[0], 2); ok {
if adrpRegister == addRegister {
// adrp x16, 0x19089b000
stubTarget := adrpBase
@@ -746,25 +807,20 @@ func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, erro
}
}
// brk #0x1
} else if (queue[0] != nil && queue[0].Operation == disassemble.ARM64_ADRP) &&
} else if queueValid[0] && queue[0].Operation == disassemble.ARM64_ADRP &&
instruction.Operation == disassemble.ARM64_LDR {
if adrpRegister, ok := operandRegister(queue[0], 0); ok {
if adrpBase, ok := operandImmediate(queue[0], 1); ok {
if adrpRegister, ok := operandRegister(&queue[0], 0); ok {
if adrpBase, ok := operandImmediate(&queue[0], 1); ok {
// adrp x16, #0x1e3be9000
stubTarget := adrpBase // #0x1e3be9000
// ldr x16, [x16, #0x560]
if ldrRegister, ok := operandRegister(instruction, 0); ok && adrpRegister == ldrRegister {
if ldrImm, ok := operandImmediate(instruction, 1); ok {
if ldrRegister, ok := operandRegister(&instruction, 0); ok && adrpRegister == ldrRegister {
if ldrImm, ok := operandImmediate(&instruction, 1); ok {
stubTarget += ldrImm
stubAddr := queue[0].Address
addr, err := readPtr(stubTarget)
if err != nil {
log.Debugf("%#08x: %s\t%s", instruction.Address, disassemble.GetOpCodeByteString(instruction.Raw), instruction)
for _, i := range queue {
if i != nil {
log.Debugf("%#08x: %s\t%s", i.Address, disassemble.GetOpCodeByteString(i.Raw), i)
}
}
logStubDecodeContext(&instruction, &queue, &queueValid)
return nil, fmt.Errorf("failed to read stub target pointer at %#x: %v", stubTarget, err)
}
stubs[stubAddr] = addr
@@ -777,7 +833,9 @@ func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, erro
// fmt.Printf("%#08x: %s\t%s\n", instruction.Address, disassemble.GetOpCodeByteString(instrValue), instruction)
queue[1] = queue[0] // push instruction onto const length FIFO queue
queueValid[1] = queueValid[0]
queue[0] = instruction
queueValid[0] = true
startAddr += uint64(binary.Size(uint32(0)))
}
@@ -786,7 +844,7 @@ func ParseStubsASM(data []byte, begin uint64, readPtr func(uint64) (uint64, erro
func ParseHelpersASM(m *macho.File) (map[uint64]uint64, error) {
var instrValue uint32
var results [1024]byte
var decoder disassemble.Decoder
helpers := make(map[uint64]uint64)
@@ -815,8 +873,8 @@ func ParseHelpersASM(m *macho.File) (map[uint64]uint64, error) {
break
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
@@ -829,7 +887,7 @@ func ParseHelpersASM(m *macho.File) (map[uint64]uint64, error) {
continue
}
if instruction.Encoding == disassemble.ENC_BL_ONLY_BRANCH_IMM {
if target, ok := operandImmediate(instruction, 0); ok {
if target, ok := operandImmediate(&instruction, 0); ok {
helpers[stubHelperFnStart] = target
}
}
+55 -3
View File
@@ -2,12 +2,38 @@ package disass
import (
"encoding/binary"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/blacktop/arm64-cgo/disassemble"
)
type stubDisass struct {
data []byte
startAddr uint64
asJSON bool
}
func (s stubDisass) Triage() error { return nil }
func (s stubDisass) IsFunctionStart(uint64) (bool, string) { return false, "" }
func (s stubDisass) IsLocation(uint64) bool { return false }
func (s stubDisass) IsBranchLocation(uint64) (bool, uint64) { return false, 0 }
func (s stubDisass) IsData(uint64) (bool, *AddrDetails) { return false, nil }
func (s stubDisass) IsPointer(uint64) (bool, *AddrDetails) { return false, nil }
func (s stubDisass) FindSymbol(uint64) (string, bool) { return "", false }
func (s stubDisass) FindSwiftString(uint64) (string, bool) { return "", false }
func (s stubDisass) GetCString(uint64) (string, error) { return "", fmt.Errorf("no cstring") }
func (s stubDisass) Demangle() bool { return false }
func (s stubDisass) Quite() bool { return true }
func (s stubDisass) Color() bool { return false }
func (s stubDisass) AsJSON() bool { return s.asJSON }
func (s stubDisass) Data() []byte { return s.data }
func (s stubDisass) StartAddr() uint64 { return s.startAddr }
func (s stubDisass) Middle() uint64 { return 0 }
func (s stubDisass) ReadAddr(uint64) (uint64, error) { return 0, fmt.Errorf("no pointer") }
func TestParseStubsASMADRPAndLDRStubDoesNotPanicOnTrailingBR(t *testing.T) {
data := []byte{
0x10, 0x00, 0x00, 0xd0, // adrp x16, ...
@@ -40,9 +66,9 @@ func TestParseStubsASMADRPAndAddBranchParsesStub(t *testing.T) {
0x00, 0x02, 0x1f, 0xd6, // br x16
}
var results [1024]byte
adrp, err := disassemble.Decompose(begin, binary.LittleEndian.Uint32(data[0:4]), &results)
if err != nil {
var decoder disassemble.Decoder
var adrp disassemble.Inst
if err := decoder.DecomposeInto(begin, binary.LittleEndian.Uint32(data[0:4]), &adrp); err != nil {
t.Fatalf("failed to decompose adrp: %v", err)
}
want := adrp.Operands[1].Immediate + 0x8
@@ -112,3 +138,29 @@ func TestParseStubsASMMismatchedRegistersDoNotCreateStaleStubEntry(t *testing.T)
}
})
}
func TestDisassembleJSONPreservesDisassemblyString(t *testing.T) {
out := Disassemble(stubDisass{
data: []byte{0x1f, 0x20, 0x03, 0xd5}, // nop
startAddr: 0x1000,
asJSON: true,
})
var got []map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &got); err != nil {
t.Fatalf("failed to unmarshal JSON disassembly output: %v\noutput=%s", err, out)
}
if len(got) != 1 {
t.Fatalf("expected 1 instruction, got %d", len(got))
}
if addr, ok := got[0]["addr"].(float64); !ok || uint64(addr) != 0x1000 {
t.Fatalf("unexpected instruction address payload: %#v", got[0]["addr"])
}
disassText, ok := got[0]["disass"].(string)
if !ok {
t.Fatalf("expected disass string in JSON payload, got %#v", got[0]["disass"])
}
if !strings.Contains(disassText, "nop") {
t.Fatalf("expected JSON disassembly to contain nop, got %q", disassText)
}
}
+28 -22
View File
@@ -31,6 +31,7 @@ type MachoDisass struct {
a2s map[uint64]string
sinfo map[uint64]uint64
swiftstrs map[uint64]string
decoder disassemble.Decoder
}
func NewMachoDisass(f *macho.File, cfg *Config) *MachoDisass {
@@ -69,8 +70,8 @@ func (d MachoDisass) ReadAddr(addr uint64) (uint64, error) {
// Triage walks a function and analyzes all immediates
func (d *MachoDisass) Triage() error {
var instrValue uint32
var results [1024]byte
var prevInstr *disassemble.Instruction
var prevInstr disassemble.Inst
var hasPrev bool
d.tr = &Triage{
Addresses: make(map[uint64]uint64),
@@ -87,45 +88,48 @@ func (d *MachoDisass) Triage() error {
break
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if IsBranchOp(instruction.Operation) {
for _, op := range instruction.Operands {
for idx := 0; idx < int(instruction.NumOps); idx++ {
op := instruction.Operands[idx]
if op.Class == disassemble.LABEL {
d.tr.Addresses[instruction.Address] = uint64(op.Immediate)
}
}
} else if IsLoadLiteral(instruction) {
} else if IsLoadLiteral(&instruction) {
d.tr.Addresses[instruction.Address] = uint64(instruction.Operands[1].Immediate)
} else if (prevInstr != nil && prevInstr.Operation == disassemble.ARM64_ADRP) &&
} else if hasPrev && prevInstr.Operation == disassemble.ARM64_ADRP &&
(instruction.Operation == disassemble.ARM64_ADD ||
instruction.Operation == disassemble.ARM64_LDR ||
instruction.Operation == disassemble.ARM64_LDRB ||
instruction.Operation == disassemble.ARM64_LDRSW) {
adrpRegister, ok := operandRegister(prevInstr, 0)
adrpRegister, ok := operandRegister(&prevInstr, 0)
if !ok {
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
continue
}
adrpImm, ok := operandImmediate(prevInstr, 1)
adrpImm, ok := operandImmediate(&prevInstr, 1)
if !ok {
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if srcRegister, ok := operandRegister(instruction, 1); ok && adrpRegister == srcRegister {
if srcRegister, ok := operandRegister(&instruction, 1); ok && adrpRegister == srcRegister {
switch instruction.Operation {
case disassemble.ARM64_LDR, disassemble.ARM64_LDRB, disassemble.ARM64_LDRSW:
if imm, ok := operandImmediate(instruction, 1); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
adrpImm += imm
}
case disassemble.ARM64_ADD:
if imm, ok := operandImmediate(instruction, 2); ok {
if imm, ok := operandImmediate(&instruction, 2); ok {
adrpImm += imm
}
}
@@ -134,6 +138,7 @@ func (d *MachoDisass) Triage() error {
}
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
}
@@ -178,8 +183,8 @@ func (d *MachoDisass) Triage() error {
// ref - stdlib/public/core/StringObject.swift
func (d *MachoDisass) FindSwiftStrings() (out map[uint64]string, err error) {
var instrValue uint32
var results [1024]byte
var prevInstr *disassemble.Instruction
var prevInstr disassemble.Inst
var hasPrev bool
d.tr = &Triage{
Addresses: make(map[uint64]uint64),
@@ -207,15 +212,15 @@ func (d *MachoDisass) FindSwiftStrings() (out map[uint64]string, err error) {
break
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if instruction.Operation == disassemble.ARM64_MOV {
if dstRegister, ok := operandRegister(instruction, 0); ok {
if imm, ok := operandImmediate(instruction, 1); ok {
if dstRegister, ok := operandRegister(&instruction, 0); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
if reg == disassemble.REG_NONE {
strAddr = instruction.Address
reg = dstRegister
@@ -232,12 +237,12 @@ func (d *MachoDisass) FindSwiftStrings() (out map[uint64]string, err error) {
}
}
}
} else if prevInstr != nil &&
} else if hasPrev &&
((prevInstr.Operation == disassemble.ARM64_MOV && instruction.Operation == disassemble.ARM64_MOVK) ||
(prevInstr.Operation == disassemble.ARM64_MOVK && instruction.Operation == disassemble.ARM64_MOVK)) {
if dstRegister, ok := operandRegister(instruction, 0); ok {
if imm, ok := operandImmediate(instruction, 1); ok {
if shift, ok := operandShiftValue(instruction, 1); ok {
if dstRegister, ok := operandRegister(&instruction, 0); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
if shift, ok := operandShiftValue(&instruction, 1); ok {
if reg == dstRegister {
regVal += imm << shift
} else if next == dstRegister {
@@ -275,6 +280,7 @@ func (d *MachoDisass) FindSwiftStrings() (out map[uint64]string, err error) {
}
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
}
+24 -21
View File
@@ -16,10 +16,11 @@ import (
)
type DyldDisass struct {
f *File
cfg *disass.Config
tr *disass.Triage
dylibs []*CacheImage
f *File
cfg *disass.Config
tr *disass.Triage
dylibs []*CacheImage
decoder disassemble.Decoder
}
func NewDyldDisass(f *File, cfg *disass.Config) *DyldDisass {
@@ -124,15 +125,15 @@ func (d DyldDisass) IsPointer(imm uint64) (bool, *disass.AddrDetails) {
return false, nil
}
func operandRegister(instr *disassemble.Instruction, idx int) (disassemble.Register, bool) {
if instr == nil || len(instr.Operands) <= idx || len(instr.Operands[idx].Registers) == 0 {
func operandRegister(instr *disassemble.Inst, idx int) (disassemble.Register, bool) {
if instr == nil || int(instr.NumOps) <= idx || instr.Operands[idx].NumRegisters == 0 {
return disassemble.REG_NONE, false
}
return instr.Operands[idx].Registers[0], true
}
func operandImmediate(instr *disassemble.Instruction, idx int) (uint64, bool) {
if instr == nil || len(instr.Operands) <= idx {
func operandImmediate(instr *disassemble.Inst, idx int) (uint64, bool) {
if instr == nil || int(instr.NumOps) <= idx {
return 0, false
}
return instr.Operands[idx].Immediate, true
@@ -141,8 +142,8 @@ func operandImmediate(instr *disassemble.Instruction, idx int) (uint64, bool) {
// Triage walks a function and analyzes all immediates
func (d *DyldDisass) Triage() error {
var instrValue uint32
var results [1024]byte
var prevInstr *disassemble.Instruction
var prevInstr disassemble.Inst
var hasPrev bool
d.tr = &disass.Triage{
Addresses: make(map[uint64]uint64),
@@ -172,45 +173,46 @@ func (d *DyldDisass) Triage() error {
break
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if disass.IsBranchOp(instruction.Operation) {
for _, op := range instruction.Operands {
for idx := 0; idx < int(instruction.NumOps); idx++ {
op := instruction.Operands[idx]
if op.Class == disassemble.LABEL {
d.tr.Addresses[instruction.Address] = uint64(op.Immediate)
}
}
} else if disass.IsLoadLiteral(instruction) {
if imm, ok := operandImmediate(instruction, 1); ok {
} else if disass.IsLoadLiteral(&instruction) {
if imm, ok := operandImmediate(&instruction, 1); ok {
d.tr.Addresses[instruction.Address] = imm
}
} else if (prevInstr != nil && prevInstr.Operation == disassemble.ARM64_ADRP) &&
} else if hasPrev && prevInstr.Operation == disassemble.ARM64_ADRP &&
(instruction.Operation == disassemble.ARM64_ADD ||
instruction.Operation == disassemble.ARM64_LDR ||
instruction.Operation == disassemble.ARM64_LDRB ||
instruction.Operation == disassemble.ARM64_LDRSW ||
instruction.Operation == disassemble.ARM64_STRB) {
adrpRegister, ok := operandRegister(prevInstr, 0)
adrpRegister, ok := operandRegister(&prevInstr, 0)
if ok {
adrpImm, ok := operandImmediate(prevInstr, 1)
adrpImm, ok := operandImmediate(&prevInstr, 1)
if ok {
srcRegister, ok := operandRegister(instruction, 1)
srcRegister, ok := operandRegister(&instruction, 1)
if ok {
validPattern := true
if adrpRegister == srcRegister {
switch instruction.Operation {
case disassemble.ARM64_LDR, disassemble.ARM64_LDRB, disassemble.ARM64_LDRSW, disassemble.ARM64_STRB:
if imm, ok := operandImmediate(instruction, 1); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
adrpImm += imm
} else {
validPattern = false
}
case disassemble.ARM64_ADD:
if imm, ok := operandImmediate(instruction, 2); ok {
if imm, ok := operandImmediate(&instruction, 2); ok {
adrpImm += imm
} else {
validPattern = false
@@ -260,6 +262,7 @@ func (d *DyldDisass) Triage() error {
// }
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
}
+24 -13
View File
@@ -27,9 +27,18 @@ func colorOperands(operands string) string {
return operands
}
func diss(startAddr uint64, data []byte) (instruction *disassemble.Instruction) {
func printDecodeFailure(startAddr uint64, instrValue uint32, err error) {
fmt.Printf("%s: %s\t%s\t%#-18x ; (%s)\n",
colorAddr("%#08x", uint64(startAddr)),
colorOp("%-7s", ".long"),
colorOpCodes(disassemble.GetOpCodeByteString(instrValue)),
instrValue,
err.Error())
}
func diss(startAddr uint64, data []byte) {
var instrValue uint32
var results [1024]byte
var decoder disassemble.Decoder
r := bytes.NewReader(data)
@@ -40,17 +49,21 @@ func diss(startAddr uint64, data []byte) (instruction *disassemble.Instruction)
break
}
instruction, err = disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
fmt.Printf("%s: %s\t%s\t%#-18x ; (%s)\n",
colorAddr("%#08x", uint64(startAddr)),
colorOp("%-7s", ".long"),
colorOpCodes(disassemble.GetOpCodeByteString(instrValue)),
instrValue,
err.Error())
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
printDecodeFailure(startAddr, instrValue, err)
startAddr += uint64(binary.Size(uint32(0)))
continue
}
opStr := strings.TrimSpace(strings.TrimPrefix(instruction.String(), instruction.Operation.String()))
disass, err := instruction.Disassemble()
if err != nil {
printDecodeFailure(startAddr, instrValue, err)
startAddr += uint64(binary.Size(uint32(0)))
continue
}
opStr := strings.TrimSpace(strings.TrimPrefix(disass, instruction.Operation.String()))
fmt.Printf("%s: %s %s %s\n",
colorAddr("%#08x", uint64(startAddr)),
@@ -61,6 +74,4 @@ func diss(startAddr uint64, data []byte) (instruction *disassemble.Instruction)
startAddr += uint64(binary.Size(uint32(0)))
}
return
}
+4 -4
View File
@@ -156,7 +156,7 @@ func getBaseAddress(r *bytes.Reader) (uint64, error) {
var startAddr uint64 = 0
var instrValue uint32
var results [1024]byte
var decoder disassemble.Decoder
for {
err := binary.Read(r, binary.LittleEndian, &instrValue)
@@ -167,12 +167,12 @@ func getBaseAddress(r *bytes.Reader) (uint64, error) {
return 0, fmt.Errorf("failed to read instruction @ %#x: %v", startAddr, err)
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
return 0, fmt.Errorf("failed to decompose instruction @ %#x: %v", startAddr, err)
}
if disass.IsLoadLiteral(instruction) {
if disass.IsLoadLiteral(&instruction) {
if _, err := r.Seek(int64(instruction.Operands[1].Immediate), io.SeekStart); err != nil {
return 0, fmt.Errorf("failed to seek to base address: %v", err)
}
+18 -16
View File
@@ -278,15 +278,15 @@ func getMigInitFunc(m *macho.File) (*types.Function, error) {
return nil, fmt.Errorf("failed to find 'mig_init' address")
}
func migOperandRegister(instr *disassemble.Instruction, idx int) (disassemble.Register, bool) {
if instr == nil || len(instr.Operands) <= idx || len(instr.Operands[idx].Registers) == 0 {
func migOperandRegister(instr *disassemble.Inst, idx int) (disassemble.Register, bool) {
if instr == nil || int(instr.NumOps) <= idx || instr.Operands[idx].NumRegisters == 0 {
return disassemble.REG_NONE, false
}
return instr.Operands[idx].Registers[0], true
}
func migOperandImmediate(instr *disassemble.Instruction, idx int) (uint64, bool) {
if instr == nil || len(instr.Operands) <= idx {
func migOperandImmediate(instr *disassemble.Inst, idx int) (uint64, bool) {
if instr == nil || int(instr.NumOps) <= idx {
return 0, false
}
return instr.Operands[idx].Immediate, true
@@ -296,8 +296,9 @@ func getMigE(r *bytes.Reader, migInit *types.Function) (uint64, error) {
var migE uint64
var instrValue uint32
var results [1024]byte
var prevInstr *disassemble.Instruction
var decoder disassemble.Decoder
var prevInstr disassemble.Inst
var hasPrev bool
startAddr := migInit.StartAddr
@@ -310,39 +311,39 @@ func getMigE(r *bytes.Reader, migInit *types.Function) (uint64, error) {
return 0, fmt.Errorf("failed to read instruction @ %#x: %v", startAddr, err)
}
instruction, err := disassemble.Decompose(startAddr, instrValue, &results)
if err != nil {
var instruction disassemble.Inst
if err := decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if disass.IsLoadLiteral(instruction) {
if imm, ok := migOperandImmediate(instruction, 1); ok {
if disass.IsLoadLiteral(&instruction) {
if imm, ok := migOperandImmediate(&instruction, 1); ok {
migE = imm
break
}
} else if (prevInstr != nil && prevInstr.Operation == disassemble.ARM64_ADRP) &&
} else if hasPrev && prevInstr.Operation == disassemble.ARM64_ADRP &&
(instruction.Operation == disassemble.ARM64_ADD ||
instruction.Operation == disassemble.ARM64_LDR ||
instruction.Operation == disassemble.ARM64_LDRB ||
instruction.Operation == disassemble.ARM64_LDRSW) {
adrpRegister, ok := migOperandRegister(prevInstr, 0)
adrpRegister, ok := migOperandRegister(&prevInstr, 0)
if ok {
adrpImm, ok := migOperandImmediate(prevInstr, 1)
adrpImm, ok := migOperandImmediate(&prevInstr, 1)
if ok {
srcRegister, ok := migOperandRegister(instruction, 1)
srcRegister, ok := migOperandRegister(&instruction, 1)
if ok {
validPattern := true
if adrpRegister == srcRegister {
switch instruction.Operation {
case disassemble.ARM64_LDR, disassemble.ARM64_LDRB, disassemble.ARM64_LDRSW:
if imm, ok := migOperandImmediate(instruction, 1); ok {
if imm, ok := migOperandImmediate(&instruction, 1); ok {
adrpImm += imm
} else {
validPattern = false
}
case disassemble.ARM64_ADD:
if imm, ok := migOperandImmediate(instruction, 2); ok {
if imm, ok := migOperandImmediate(&instruction, 2); ok {
adrpImm += imm
} else {
validPattern = false
@@ -360,6 +361,7 @@ func getMigE(r *bytes.Reader, migInit *types.Function) (uint64, error) {
// fmt.Printf("%#08x: %s\t%s\n", uint64(startAddr), disassemble.GetOpCodeByteString(instrValue), instruction)
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
}