mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
873 lines
21 KiB
Go
873 lines
21 KiB
Go
package xref
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"strings"
|
|
|
|
"github.com/blacktop/arm64-cgo/disassemble"
|
|
)
|
|
|
|
const DefaultMaxInstructions = 32
|
|
|
|
type MemoryReader interface {
|
|
ReadPointer(addr uint64) (uint64, error)
|
|
}
|
|
|
|
type Mode uint8
|
|
|
|
const (
|
|
ModeReferences Mode = iota
|
|
ModeCalls
|
|
)
|
|
|
|
type ValueKind uint8
|
|
|
|
const (
|
|
ValueUnknown ValueKind = iota
|
|
ValueParam
|
|
ValueAddr
|
|
ValueImm
|
|
)
|
|
|
|
type RegisterValue struct {
|
|
Kind ValueKind
|
|
Addr uint64
|
|
Note string
|
|
}
|
|
|
|
func (v RegisterValue) KnownAddress() bool {
|
|
return v.Kind == ValueAddr || v.Kind == ValueImm
|
|
}
|
|
|
|
func (v RegisterValue) UnresolvedNote() string {
|
|
if v.Note != "" {
|
|
return v.Note
|
|
}
|
|
if v.Kind == ValueParam {
|
|
return "param"
|
|
}
|
|
return "indirect"
|
|
}
|
|
|
|
type RegisterState [31]RegisterValue
|
|
|
|
func (s RegisterState) Register(reg int) (RegisterValue, string) {
|
|
if reg < 0 || reg >= len(s) {
|
|
return RegisterValue{}, "indirect"
|
|
}
|
|
val := s[reg]
|
|
if val.Kind == ValueParam {
|
|
return val, "param"
|
|
}
|
|
if !val.KnownAddress() {
|
|
return val, val.UnresolvedNote()
|
|
}
|
|
return val, ""
|
|
}
|
|
|
|
type Instruction struct {
|
|
disassemble.Inst
|
|
}
|
|
|
|
type TargetSet map[uint64]struct{}
|
|
|
|
func NewTargetSet(addrs ...uint64) TargetSet {
|
|
targets := make(TargetSet, len(addrs))
|
|
for _, addr := range addrs {
|
|
targets.Add(addr)
|
|
}
|
|
return targets
|
|
}
|
|
|
|
func (s TargetSet) Add(addr uint64) {
|
|
if addr != 0 {
|
|
s[addr] = struct{}{}
|
|
}
|
|
}
|
|
|
|
func (s TargetSet) Has(addr uint64) bool {
|
|
_, ok := s[addr]
|
|
return ok
|
|
}
|
|
|
|
type Options struct {
|
|
Targets TargetSet
|
|
Reader MemoryReader
|
|
Mode Mode
|
|
ResolveIndirect bool
|
|
MaxInstructions int
|
|
FirstMatchOnly bool
|
|
}
|
|
|
|
type Result struct {
|
|
Address uint64
|
|
Target uint64
|
|
Index int
|
|
Inst disassemble.Inst
|
|
State RegisterState
|
|
}
|
|
|
|
type Scanner struct {
|
|
decoder disassemble.Decoder
|
|
current disassemble.Inst
|
|
previous disassemble.Inst
|
|
instructions []Instruction
|
|
}
|
|
|
|
func ScanFunction(data []byte, start uint64, opts Options) []Result {
|
|
if opts.Mode == ModeReferences && !opts.ResolveIndirect {
|
|
var scanner Scanner
|
|
return scanner.ScanFunction(data, start, opts)
|
|
}
|
|
return ScanInstructions(Decode(data, start), opts)
|
|
}
|
|
|
|
func ScanFirstFunction(data []byte, start uint64, opts Options) (Result, bool) {
|
|
var scanner Scanner
|
|
return scanner.ScanFirstFunction(data, start, opts)
|
|
}
|
|
|
|
func (s *Scanner) ScanFunction(data []byte, start uint64, opts Options) []Result {
|
|
if opts.Mode == ModeReferences && !opts.ResolveIndirect {
|
|
return s.scanReferenceBytes(data, start, opts)
|
|
}
|
|
return ScanInstructions(s.Decode(data, start), opts)
|
|
}
|
|
|
|
func (s *Scanner) Decode(data []byte, start uint64) []Instruction {
|
|
s.instructions = decodeInto(s.instructions, data, start, &s.decoder)
|
|
return s.instructions
|
|
}
|
|
|
|
func (s *Scanner) ScanFirstFunction(data []byte, start uint64, opts Options) (Result, bool) {
|
|
if opts.Mode == ModeReferences && !opts.ResolveIndirect {
|
|
return s.scanFirstReferenceBytes(data, start, opts)
|
|
}
|
|
opts.FirstMatchOnly = true
|
|
results := s.ScanFunction(data, start, opts)
|
|
if len(results) == 0 {
|
|
return Result{}, false
|
|
}
|
|
return results[0], true
|
|
}
|
|
|
|
func ScanInstructions(instrs []Instruction, opts Options) []Result {
|
|
if len(opts.Targets) == 0 {
|
|
return nil
|
|
}
|
|
if opts.Mode == ModeCalls {
|
|
return scanCalls(instrs, opts)
|
|
}
|
|
return scanReferences(instrs, opts)
|
|
}
|
|
|
|
func Decode(data []byte, start uint64) []Instruction {
|
|
var decoder disassemble.Decoder
|
|
return decodeInto(nil, data, start, &decoder)
|
|
}
|
|
|
|
func DecodeInto(dst []Instruction, data []byte, start uint64) []Instruction {
|
|
var decoder disassemble.Decoder
|
|
return decodeInto(dst, data, start, &decoder)
|
|
}
|
|
|
|
func decodeInto(dst []Instruction, data []byte, start uint64, decoder *disassemble.Decoder) []Instruction {
|
|
out := dst[:0]
|
|
if cap(out) < len(data)/4 {
|
|
out = make([]Instruction, 0, len(data)/4)
|
|
}
|
|
for off := 0; off+4 <= len(data); off += 4 {
|
|
raw := binary.LittleEndian.Uint32(data[off : off+4])
|
|
addr := start + uint64(off)
|
|
var inst disassemble.Inst
|
|
if err := decoder.DecomposeInto(addr, raw, &inst); err == nil {
|
|
out = append(out, Instruction{Inst: inst})
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func MayContainDirectCallTarget(data []byte, start uint64, targets TargetSet) bool {
|
|
for off := 0; off+4 <= len(data); off += 4 {
|
|
raw := binary.LittleEndian.Uint32(data[off : off+4])
|
|
target, ok := DirectBranchTarget(raw, start+uint64(off))
|
|
if ok && targets.Has(target) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func DirectBranchTarget(raw uint32, pc uint64) (uint64, bool) {
|
|
switch raw & 0xfc000000 {
|
|
case 0x14000000, 0x94000000:
|
|
default:
|
|
return 0, false
|
|
}
|
|
imm := int64(raw & 0x03ffffff)
|
|
if imm&(1<<25) != 0 {
|
|
imm |= ^int64(0x03ffffff)
|
|
}
|
|
return uint64(int64(pc) + (imm << 2)), true
|
|
}
|
|
|
|
func scanCalls(instrs []Instruction, opts Options) []Result {
|
|
var results []Result
|
|
for idx := range instrs {
|
|
inst := &instrs[idx].Inst
|
|
if target, ok := directCallTarget(inst); ok {
|
|
if opts.Targets.Has(target) {
|
|
results = append(results, Result{
|
|
Address: inst.Address,
|
|
Target: target,
|
|
Index: idx,
|
|
Inst: *inst,
|
|
State: StateBefore(instrs, idx, opts.Reader, opts.MaxInstructions),
|
|
})
|
|
if opts.FirstMatchOnly {
|
|
return results
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if !opts.ResolveIndirect || !isIndirectBranch(inst.Operation) {
|
|
continue
|
|
}
|
|
state := StateBefore(instrs, idx, opts.Reader, opts.MaxInstructions)
|
|
if target, ok := indirectTarget(inst, state); ok && opts.Targets.Has(target) {
|
|
results = append(results, Result{
|
|
Address: inst.Address,
|
|
Target: target,
|
|
Index: idx,
|
|
Inst: *inst,
|
|
State: state,
|
|
})
|
|
if opts.FirstMatchOnly {
|
|
return results
|
|
}
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func scanReferences(instrs []Instruction, opts Options) []Result {
|
|
var results []Result
|
|
for idx := range instrs {
|
|
inst := &instrs[idx].Inst
|
|
if target, ok := referenceTarget(instrs, idx); ok && opts.Targets.Has(target) {
|
|
results = append(results, Result{
|
|
Address: inst.Address,
|
|
Target: target,
|
|
Index: idx,
|
|
Inst: *inst,
|
|
})
|
|
if opts.FirstMatchOnly {
|
|
return results
|
|
}
|
|
continue
|
|
}
|
|
if !opts.ResolveIndirect || !isIndirectBranch(inst.Operation) {
|
|
continue
|
|
}
|
|
state := StateBefore(instrs, idx, opts.Reader, opts.MaxInstructions)
|
|
if target, ok := indirectTarget(inst, state); ok && opts.Targets.Has(target) {
|
|
results = append(results, Result{
|
|
Address: inst.Address,
|
|
Target: target,
|
|
Index: idx,
|
|
Inst: *inst,
|
|
State: state,
|
|
})
|
|
if opts.FirstMatchOnly {
|
|
return results
|
|
}
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (s *Scanner) scanReferenceBytes(data []byte, start uint64, opts Options) []Result {
|
|
if len(opts.Targets) == 0 {
|
|
return nil
|
|
}
|
|
if opts.FirstMatchOnly {
|
|
result, ok := s.scanFirstReferenceBytes(data, start, opts)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return []Result{result}
|
|
}
|
|
hasPrev := false
|
|
var results []Result
|
|
for off := 0; off+4 <= len(data); off += 4 {
|
|
raw := binary.LittleEndian.Uint32(data[off : off+4])
|
|
addr := start + uint64(off)
|
|
if err := s.decoder.DecomposeInto(addr, raw, &s.current); err != nil {
|
|
continue
|
|
}
|
|
if target, ok := referenceTargetWithPrev(&s.current, &s.previous, hasPrev); ok && opts.Targets.Has(target) {
|
|
results = append(results, Result{
|
|
Address: s.current.Address,
|
|
Target: target,
|
|
Index: off / 4,
|
|
Inst: s.current,
|
|
})
|
|
}
|
|
s.previous = s.current
|
|
hasPrev = true
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (s *Scanner) scanFirstReferenceBytes(data []byte, start uint64, opts Options) (Result, bool) {
|
|
if len(opts.Targets) == 0 {
|
|
return Result{}, false
|
|
}
|
|
hasPrev := false
|
|
for off := 0; off+4 <= len(data); off += 4 {
|
|
raw := binary.LittleEndian.Uint32(data[off : off+4])
|
|
addr := start + uint64(off)
|
|
if err := s.decoder.DecomposeInto(addr, raw, &s.current); err != nil {
|
|
continue
|
|
}
|
|
if target, ok := referenceTargetWithPrev(&s.current, &s.previous, hasPrev); ok && opts.Targets.Has(target) {
|
|
return Result{
|
|
Address: s.current.Address,
|
|
Target: target,
|
|
Index: off / 4,
|
|
Inst: s.current,
|
|
}, true
|
|
}
|
|
s.previous = s.current
|
|
hasPrev = true
|
|
}
|
|
return Result{}, false
|
|
}
|
|
|
|
func StateBefore(instrs []Instruction, callIndex int, mem MemoryReader, maxInstructions int) RegisterState {
|
|
var state RegisterState
|
|
for idx := range 8 {
|
|
state[idx] = RegisterValue{Kind: ValueParam, Note: "param"}
|
|
}
|
|
window := maxInstructions
|
|
if window <= 0 {
|
|
window = DefaultMaxInstructions
|
|
}
|
|
start := max(callIndex-window, 0)
|
|
for idx := start; idx < callIndex; idx++ {
|
|
applyInstruction(&instrs[idx].Inst, state[:], mem)
|
|
}
|
|
return state
|
|
}
|
|
|
|
func directCallTarget(inst *disassemble.Inst) (uint64, bool) {
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_BL, disassemble.ARM64_B:
|
|
return LabelTarget(inst)
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func referenceTarget(instrs []Instruction, idx int) (uint64, bool) {
|
|
inst := &instrs[idx].Inst
|
|
if idx == 0 {
|
|
return referenceTargetWithPrev(inst, nil, false)
|
|
}
|
|
return referenceTargetWithPrev(inst, &instrs[idx-1].Inst, true)
|
|
}
|
|
|
|
func referenceTargetWithPrev(inst *disassemble.Inst, prev *disassemble.Inst, hasPrev bool) (uint64, bool) {
|
|
if isBranchReferenceOp(inst.Operation) {
|
|
return LabelTarget(inst)
|
|
}
|
|
if isLoadLiteral(inst) {
|
|
return OperandImm(inst, 1)
|
|
}
|
|
if !hasPrev || prev == nil {
|
|
return 0, false
|
|
}
|
|
if prev.Operation != disassemble.ARM64_ADRP {
|
|
return 0, false
|
|
}
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_ADD, disassemble.ARM64_LDR, disassemble.ARM64_LDRB,
|
|
disassemble.ARM64_LDRSW, disassemble.ARM64_STRB:
|
|
default:
|
|
return 0, false
|
|
}
|
|
adrpReg, ok := OperandReg(prev, 0)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
adrpImm, ok := OperandImm(prev, 1)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
srcReg, ok := OperandReg(inst, 1)
|
|
if !ok || adrpReg != srcReg {
|
|
return 0, false
|
|
}
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_ADD:
|
|
imm, ok := OperandImm(inst, 2)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
return adrpImm + imm, true
|
|
default:
|
|
imm, ok := MemoryOffset(inst, 1)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
return adrpImm + imm, true
|
|
}
|
|
}
|
|
|
|
func indirectTarget(inst *disassemble.Inst, state RegisterState) (uint64, bool) {
|
|
reg, ok := OperandReg(inst, 0)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
idx, ok := RegIndex(reg)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
val := state[idx]
|
|
if val.KnownAddress() && val.Addr != 0 {
|
|
return val.Addr, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func applyInstruction(inst *disassemble.Inst, state []RegisterValue, mem MemoryReader) {
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_ADR, disassemble.ARM64_ADRP:
|
|
if rd, ok := destRegIndex(inst); ok {
|
|
if imm, ok := OperandImm(inst, 1); ok {
|
|
state[rd] = RegisterValue{Kind: ValueAddr, Addr: imm}
|
|
}
|
|
}
|
|
case disassemble.ARM64_ADD:
|
|
applyAdd(inst, state)
|
|
case disassemble.ARM64_LDR, disassemble.ARM64_LDUR:
|
|
applyLoad(inst, state, mem)
|
|
case disassemble.ARM64_LDNP, disassemble.ARM64_LDP, disassemble.ARM64_LDPSW:
|
|
clearOperandDest(inst, state, 0, "indirect")
|
|
clearOperandDest(inst, state, 1, "indirect")
|
|
case disassemble.ARM64_MOV:
|
|
applyMove(inst, state)
|
|
case disassemble.ARM64_MOVZ:
|
|
if rd, ok := destRegIndex(inst); ok {
|
|
if imm, ok := OperandImm(inst, 1); ok {
|
|
state[rd] = RegisterValue{Kind: ValueImm, Addr: imm}
|
|
}
|
|
}
|
|
case disassemble.ARM64_MOVK:
|
|
applyMoveKeep(inst, state)
|
|
case disassemble.ARM64_ORR:
|
|
if !applyORRMove(inst, state) {
|
|
clearDest(inst, state, "indirect")
|
|
}
|
|
default:
|
|
if isCall(inst.Operation) {
|
|
clearVolatile(state)
|
|
return
|
|
}
|
|
if shouldClearDestination(inst) {
|
|
clearDest(inst, state, "indirect")
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyAdd(inst *disassemble.Inst, state []RegisterValue) {
|
|
rd, ok := destRegIndex(inst)
|
|
if !ok {
|
|
return
|
|
}
|
|
rn, ok := OperandRegIndex(inst, 1)
|
|
if !ok {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
return
|
|
}
|
|
imm, ok := OperandImm(inst, 2)
|
|
if !ok {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
return
|
|
}
|
|
base := state[rn]
|
|
if base.KnownAddress() {
|
|
state[rd] = RegisterValue{Kind: base.Kind, Addr: base.Addr + imm}
|
|
return
|
|
}
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: base.UnresolvedNote()}
|
|
}
|
|
|
|
func applyLoad(inst *disassemble.Inst, state []RegisterValue, mem MemoryReader) {
|
|
rd, ok := destRegIndex(inst)
|
|
if !ok {
|
|
return
|
|
}
|
|
if target, ok := LabelTarget(inst); ok {
|
|
state[rd] = readPointerValue(mem, target)
|
|
return
|
|
}
|
|
baseReg, ok := OperandRegIndex(inst, 1)
|
|
if !ok {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
return
|
|
}
|
|
base := state[baseReg]
|
|
if !base.KnownAddress() {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: base.UnresolvedNote()}
|
|
return
|
|
}
|
|
access, ok := memoryAccess(inst, 1)
|
|
if !ok {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
return
|
|
}
|
|
state[rd] = readPointerValue(mem, base.Addr+access.readOffset)
|
|
if access.writeback && baseReg != rd {
|
|
state[baseReg] = RegisterValue{Kind: base.Kind, Addr: base.Addr + access.writeOffset}
|
|
}
|
|
}
|
|
|
|
func readPointerValue(mem MemoryReader, addr uint64) RegisterValue {
|
|
if mem == nil {
|
|
return RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
}
|
|
ptr, err := mem.ReadPointer(addr)
|
|
if err != nil || ptr == 0 {
|
|
return RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
}
|
|
return RegisterValue{Kind: ValueAddr, Addr: ptr}
|
|
}
|
|
|
|
func applyMove(inst *disassemble.Inst, state []RegisterValue) {
|
|
rd, ok := destRegIndex(inst)
|
|
if !ok {
|
|
return
|
|
}
|
|
if rn, ok := OperandRegIndex(inst, 1); ok {
|
|
state[rd] = state[rn]
|
|
return
|
|
}
|
|
if imm, ok := OperandImm(inst, 1); ok {
|
|
state[rd] = RegisterValue{Kind: ValueImm, Addr: imm}
|
|
return
|
|
}
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
}
|
|
|
|
func applyMoveKeep(inst *disassemble.Inst, state []RegisterValue) {
|
|
rd, ok := destRegIndex(inst)
|
|
if !ok {
|
|
return
|
|
}
|
|
imm, ok := OperandImm(inst, 1)
|
|
if !ok {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
return
|
|
}
|
|
prev := state[rd]
|
|
if !prev.KnownAddress() {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: prev.UnresolvedNote()}
|
|
return
|
|
}
|
|
shift := uint64(0)
|
|
if inst.NumOps > 1 && inst.Operands[1].ShiftValueUsed {
|
|
shift = uint64(inst.Operands[1].ShiftValue)
|
|
}
|
|
mask := uint64(0xffff) << shift
|
|
state[rd] = RegisterValue{Kind: prev.Kind, Addr: (prev.Addr &^ mask) | (imm & mask)}
|
|
}
|
|
|
|
func applyORRMove(inst *disassemble.Inst, state []RegisterValue) bool {
|
|
rd, ok := destRegIndex(inst)
|
|
if !ok {
|
|
return false
|
|
}
|
|
rn, rnOK := OperandReg(inst, 1)
|
|
rm, rmOK := OperandReg(inst, 2)
|
|
if rnOK && rmOK && isZeroReg(rn) {
|
|
if idx, ok := RegIndex(rm); ok {
|
|
state[rd] = state[idx]
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func StubPointerSlotsFromInstructions(instrs []Instruction) map[uint64]uint64 {
|
|
slots := make(map[uint64]uint64)
|
|
for idx := range instrs {
|
|
if stub, slot, ok := stubPointerSlot(instrs, idx); ok {
|
|
slots[stub] = slot
|
|
}
|
|
}
|
|
return slots
|
|
}
|
|
|
|
func stubPointerSlot(instrs []Instruction, idx int) (uint64, uint64, bool) {
|
|
if stub, slot, ok := adrpAddLDRStubSlot(instrs, idx); ok {
|
|
return stub, slot, true
|
|
}
|
|
return adrpLDRStubSlot(instrs, idx)
|
|
}
|
|
|
|
func adrpAddLDRStubSlot(instrs []Instruction, idx int) (uint64, uint64, bool) {
|
|
if idx < 2 {
|
|
return 0, 0, false
|
|
}
|
|
adrp := &instrs[idx-2].Inst
|
|
add := &instrs[idx-1].Inst
|
|
ldr := &instrs[idx].Inst
|
|
if adrp.Operation != disassemble.ARM64_ADRP ||
|
|
add.Operation != disassemble.ARM64_ADD ||
|
|
ldr.Operation != disassemble.ARM64_LDR {
|
|
return 0, 0, false
|
|
}
|
|
adrpReg, ok := OperandReg(adrp, 0)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
addDst, ok := OperandReg(add, 0)
|
|
if !ok || addDst != adrpReg {
|
|
return 0, 0, false
|
|
}
|
|
addBase, ok := OperandReg(add, 1)
|
|
if !ok || addBase != adrpReg {
|
|
return 0, 0, false
|
|
}
|
|
ldrBase, ok := OperandReg(ldr, 1)
|
|
if !ok || ldrBase != addDst {
|
|
return 0, 0, false
|
|
}
|
|
base, ok := OperandImm(adrp, 1)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
addImm, ok := OperandImm(add, 2)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
ldrImm, ok := MemoryOffset(ldr, 1)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
return adrp.Address, base + addImm + ldrImm, true
|
|
}
|
|
|
|
func adrpLDRStubSlot(instrs []Instruction, idx int) (uint64, uint64, bool) {
|
|
if idx < 1 {
|
|
return 0, 0, false
|
|
}
|
|
adrp := &instrs[idx-1].Inst
|
|
ldr := &instrs[idx].Inst
|
|
if adrp.Operation != disassemble.ARM64_ADRP || ldr.Operation != disassemble.ARM64_LDR {
|
|
return 0, 0, false
|
|
}
|
|
adrpReg, ok := OperandReg(adrp, 0)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
ldrDst, ok := OperandReg(ldr, 0)
|
|
if !ok || ldrDst != adrpReg {
|
|
return 0, 0, false
|
|
}
|
|
ldrBase, ok := OperandReg(ldr, 1)
|
|
if !ok || ldrBase != adrpReg {
|
|
return 0, 0, false
|
|
}
|
|
base, ok := OperandImm(adrp, 1)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
ldrImm, ok := MemoryOffset(ldr, 1)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
return adrp.Address, base + ldrImm, true
|
|
}
|
|
|
|
func LabelTarget(inst *disassemble.Inst) (uint64, bool) {
|
|
for idx := 0; idx < int(inst.NumOps); idx++ {
|
|
op := &inst.Operands[idx]
|
|
if op.Class == disassemble.LABEL {
|
|
return op.GetImmediate(), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func OperandReg(inst *disassemble.Inst, idx int) (disassemble.Register, bool) {
|
|
if inst == nil || int(inst.NumOps) <= idx || inst.Operands[idx].NumRegisters == 0 {
|
|
return disassemble.REG_NONE, false
|
|
}
|
|
return inst.Operands[idx].Registers[0], true
|
|
}
|
|
|
|
func OperandRegIndex(inst *disassemble.Inst, idx int) (int, bool) {
|
|
reg, ok := OperandReg(inst, idx)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
return RegIndex(reg)
|
|
}
|
|
|
|
func OperandImm(inst *disassemble.Inst, idx int) (uint64, bool) {
|
|
if inst == nil || int(inst.NumOps) <= idx {
|
|
return 0, false
|
|
}
|
|
op := &inst.Operands[idx]
|
|
switch op.Class {
|
|
case disassemble.IMM32, disassemble.IMM64, disassemble.STR_IMM,
|
|
disassemble.MEM_PRE_IDX, disassemble.MEM_POST_IDX, disassemble.MEM_OFFSET,
|
|
disassemble.LABEL:
|
|
return op.GetImmediate(), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
type memoryAccessInfo struct {
|
|
readOffset uint64
|
|
writeOffset uint64
|
|
writeback bool
|
|
}
|
|
|
|
func MemoryOffset(inst *disassemble.Inst, idx int) (uint64, bool) {
|
|
access, ok := memoryAccess(inst, idx)
|
|
return access.readOffset, ok
|
|
}
|
|
|
|
func MemoryAccess(inst *disassemble.Inst, idx int) (uint64, uint64, bool, bool) {
|
|
access, ok := memoryAccess(inst, idx)
|
|
if !ok {
|
|
return 0, 0, false, false
|
|
}
|
|
return access.readOffset, access.writeOffset, access.writeback, true
|
|
}
|
|
|
|
func memoryAccess(inst *disassemble.Inst, idx int) (memoryAccessInfo, bool) {
|
|
if inst == nil || int(inst.NumOps) <= idx {
|
|
return memoryAccessInfo{}, false
|
|
}
|
|
op := &inst.Operands[idx]
|
|
switch op.Class {
|
|
case disassemble.MEM_OFFSET:
|
|
return memoryAccessInfo{readOffset: op.GetImmediate()}, true
|
|
case disassemble.MEM_PRE_IDX:
|
|
imm := op.GetImmediate()
|
|
return memoryAccessInfo{readOffset: imm, writeOffset: imm, writeback: true}, true
|
|
case disassemble.MEM_POST_IDX:
|
|
return memoryAccessInfo{writeOffset: op.GetImmediate(), writeback: true}, true
|
|
default:
|
|
return memoryAccessInfo{}, false
|
|
}
|
|
}
|
|
|
|
func RegIndex(reg disassemble.Register) (int, bool) {
|
|
switch {
|
|
case reg >= disassemble.REG_X0 && reg <= disassemble.REG_X30:
|
|
return int(reg - disassemble.REG_X0), true
|
|
case reg >= disassemble.REG_W0 && reg <= disassemble.REG_W30:
|
|
return int(reg - disassemble.REG_W0), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func destRegIndex(inst *disassemble.Inst) (int, bool) {
|
|
return OperandRegIndex(inst, 0)
|
|
}
|
|
|
|
func isZeroReg(reg disassemble.Register) bool {
|
|
return reg == disassemble.REG_XZR || reg == disassemble.REG_WZR
|
|
}
|
|
|
|
func clearDest(inst *disassemble.Inst, state []RegisterValue, note string) {
|
|
clearOperandDest(inst, state, 0, note)
|
|
}
|
|
|
|
func clearOperandDest(inst *disassemble.Inst, state []RegisterValue, operand int, note string) {
|
|
if rd, ok := OperandRegIndex(inst, operand); ok {
|
|
state[rd] = RegisterValue{Kind: ValueUnknown, Note: note}
|
|
}
|
|
}
|
|
|
|
func clearVolatile(state []RegisterValue) {
|
|
for idx := 0; idx <= 17 && idx < len(state); idx++ {
|
|
state[idx] = RegisterValue{Kind: ValueUnknown, Note: "indirect"}
|
|
}
|
|
}
|
|
|
|
func isCall(op disassemble.Operation) bool {
|
|
switch op {
|
|
case disassemble.ARM64_BL, disassemble.ARM64_BLR,
|
|
disassemble.ARM64_BLRAA, disassemble.ARM64_BLRAAZ,
|
|
disassemble.ARM64_BLRAB, disassemble.ARM64_BLRABZ:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isIndirectBranch(op disassemble.Operation) bool {
|
|
switch op {
|
|
case disassemble.ARM64_BLR, disassemble.ARM64_BLRAA, disassemble.ARM64_BLRAAZ,
|
|
disassemble.ARM64_BLRAB, disassemble.ARM64_BLRABZ,
|
|
disassemble.ARM64_BR, disassemble.ARM64_BRAA, disassemble.ARM64_BRAAZ,
|
|
disassemble.ARM64_BRAB, disassemble.ARM64_BRABZ:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isBranchReferenceOp(op disassemble.Operation) bool {
|
|
switch op {
|
|
case disassemble.ARM64_B, disassemble.ARM64_BL,
|
|
disassemble.ARM64_CBZ, disassemble.ARM64_CBNZ,
|
|
disassemble.ARM64_TBZ, disassemble.ARM64_TBNZ,
|
|
disassemble.ARM64_B_EQ, disassemble.ARM64_B_NE,
|
|
disassemble.ARM64_B_CS, disassemble.ARM64_B_CC,
|
|
disassemble.ARM64_B_MI, disassemble.ARM64_B_PL,
|
|
disassemble.ARM64_B_VS, disassemble.ARM64_B_VC,
|
|
disassemble.ARM64_B_HI, disassemble.ARM64_B_LS,
|
|
disassemble.ARM64_B_GE, disassemble.ARM64_B_LT,
|
|
disassemble.ARM64_B_GT, disassemble.ARM64_B_LE,
|
|
disassemble.ARM64_B_AL, disassemble.ARM64_B_NV:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isLoadLiteral(inst *disassemble.Inst) bool {
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_LDR, disassemble.ARM64_LDRSW, disassemble.ARM64_PRFM:
|
|
return inst != nil && inst.NumOps > 1 && inst.Operands[1].Class == disassemble.LABEL
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func shouldClearDestination(inst *disassemble.Inst) bool {
|
|
if inst == nil || inst.NumOps == 0 {
|
|
return false
|
|
}
|
|
if _, ok := destRegIndex(inst); !ok {
|
|
return false
|
|
}
|
|
op := strings.ToLower(inst.Operation.String())
|
|
if strings.HasPrefix(op, "st") || strings.HasPrefix(op, "b.") || op == "b" || op == "br" || op == "ret" {
|
|
return false
|
|
}
|
|
if isBranchReferenceOp(inst.Operation) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|