mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
9c861ee7cd
Update all disassembly code to use new cgo-based decoder API with stack-allocated instruction structs instead of heap-allocated pointers. Add instruction filtering to skip decoding operations that register tracking doesn't care about, avoiding expensive CGo calls. Key changes: - Replace Decompose with DecomposeInto using stack allocation - Add mayBeTrackedInstruction filter for common tracked ops - Introduce helper functions for safe operand/register access - Fix metaclass pointer index to use caller index for efficiency - Remove root file special-casing in pointer index builder - Add comprehensive unit tests for tracking options and helpers
969 lines
24 KiB
Go
969 lines
24 KiB
Go
package cpp
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/blacktop/arm64-cgo/disassemble"
|
|
"github.com/blacktop/go-macho"
|
|
)
|
|
|
|
type microTag uint16
|
|
|
|
const (
|
|
microTagBL microTag = 1 << iota
|
|
microTagB
|
|
microTagRET
|
|
microTagADRP
|
|
microTagADR
|
|
microTagStoreToX0
|
|
microTagX16Candidate
|
|
)
|
|
|
|
type microPlan struct {
|
|
anchorBLOffsets []int
|
|
branchEventOffsets []int
|
|
retOffsets []int
|
|
x16CandidateOffsets []int
|
|
storeToX0Offsets []int
|
|
maxOffset int
|
|
|
|
tags []microTag
|
|
targets []uint64
|
|
}
|
|
|
|
type microState struct {
|
|
owner *macho.File
|
|
funcStart uint64
|
|
pc uint64
|
|
sp uint64
|
|
|
|
regs [31]uint64
|
|
regKnown [31]bool
|
|
regBase [31]uint64
|
|
regLoadAddr [31]uint64
|
|
spills [4]trackedSpill
|
|
stackSlots map[uint64]uint64
|
|
|
|
x16Candidate uint64
|
|
}
|
|
|
|
type microMemAccess struct {
|
|
addr uint64
|
|
baseIdx int
|
|
baseIsSP bool
|
|
writeBack bool
|
|
writeBackAfter bool
|
|
newBase uint64
|
|
}
|
|
|
|
func newMicroState(owner *macho.File, funcStart uint64) *microState {
|
|
return µState{
|
|
owner: owner,
|
|
funcStart: funcStart,
|
|
sp: 0x7fff00000000,
|
|
}
|
|
}
|
|
|
|
func (s *microState) GetX(reg int) uint64 {
|
|
if reg < 0 || reg >= len(s.regs) {
|
|
return 0
|
|
}
|
|
return s.regs[reg]
|
|
}
|
|
|
|
func (s *microState) SetX(reg int, value uint64) {
|
|
if reg < 0 || reg >= len(s.regs) {
|
|
return
|
|
}
|
|
s.regs[reg] = value
|
|
s.regKnown[reg] = true
|
|
s.regBase[reg] = 0
|
|
s.regLoadAddr[reg] = 0
|
|
}
|
|
|
|
func (s *microState) GetSP() uint64 {
|
|
return s.sp
|
|
}
|
|
|
|
func (s *microState) ReadUint64(addr uint64) (uint64, error) {
|
|
if value, ok := s.stackSlots[addr]; ok {
|
|
return value, nil
|
|
}
|
|
return 0, fmt.Errorf("address %#x not mapped in micro stack", addr)
|
|
}
|
|
|
|
func (s *microState) writeStack(addr uint64, value uint64) {
|
|
if s.stackSlots == nil {
|
|
s.stackSlots = make(map[uint64]uint64, 8)
|
|
}
|
|
s.stackSlots[addr] = value
|
|
}
|
|
|
|
func (s *microState) clearTracked(idx int) {
|
|
if idx < 0 || idx >= len(s.regs) {
|
|
return
|
|
}
|
|
s.regs[idx] = 0
|
|
s.regKnown[idx] = false
|
|
s.regBase[idx] = 0
|
|
s.regLoadAddr[idx] = 0
|
|
}
|
|
|
|
func (s *microState) copyTracked(dst, src int) {
|
|
if dst < 0 || dst >= len(s.regs) || src < 0 || src >= len(s.regs) {
|
|
return
|
|
}
|
|
s.regs[dst] = s.regs[src]
|
|
s.regKnown[dst] = s.regKnown[src]
|
|
s.regBase[dst] = s.regBase[src]
|
|
s.regLoadAddr[dst] = s.regLoadAddr[src]
|
|
}
|
|
|
|
func (s *microState) setKnownValue(idx int, value uint64) {
|
|
if idx < 0 || idx >= len(s.regs) {
|
|
return
|
|
}
|
|
s.regs[idx] = value
|
|
s.regKnown[idx] = true
|
|
s.regBase[idx] = 0
|
|
s.regLoadAddr[idx] = 0
|
|
}
|
|
|
|
func (s *microState) setKnownBase(idx int, value uint64) {
|
|
if idx < 0 || idx >= len(s.regs) {
|
|
return
|
|
}
|
|
s.regs[idx] = value
|
|
s.regKnown[idx] = true
|
|
s.regBase[idx] = value
|
|
s.regLoadAddr[idx] = 0
|
|
}
|
|
|
|
func recoveredTrackedValue(state *microState, reg int, allowBase bool) uint64 {
|
|
value := recoverSpilledRegister(state, &state.spills, reg)
|
|
if value != 0 || (reg >= 0 && reg < len(state.regKnown) && state.regKnown[reg]) {
|
|
return value
|
|
}
|
|
if allowBase && reg >= 0 && reg < len(state.regBase) && state.regBase[reg] != 0 {
|
|
return state.regBase[reg]
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func isReturnInstruction(raw uint32) bool {
|
|
switch raw {
|
|
case 0xd65f03c0, 0xd65f0fff, 0xd65f0bff:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isCallRegisterRaw(raw uint32) bool {
|
|
return (raw & 0xfffffc1f) == 0xd63f0000
|
|
}
|
|
|
|
func isStoreToX0Raw(raw uint32) bool {
|
|
switch {
|
|
case raw&0xffffffe0 == 0xf9000000:
|
|
return true // STR Xt, [x0]
|
|
case raw&0xffffffe0 == 0xf8000000:
|
|
return true // STUR Xt, [x0]
|
|
case (raw & 0xffc003e0) == 0xa9000000:
|
|
return true // STP Xt1, Xt2, [x0]
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isPotentialX16Write(raw uint32) bool {
|
|
switch {
|
|
case (raw & 0x9f00001f) == 0x90000010:
|
|
return true // ADRP X16, ...
|
|
case (raw & 0x9f00001f) == 0x10000010:
|
|
return true // ADR X16, ...
|
|
case (raw & 0xff8003ff) == 0x91000210:
|
|
return true // ADD X16, X16, #imm (best-effort immediate form)
|
|
case (raw & 0xfffffc1f) == 0xf9400210:
|
|
return true // LDR X16, [X16, #imm]
|
|
case (raw & 0xfffffc1f) == 0xf8400210:
|
|
return true // LDUR X16, [X16, #imm]
|
|
case (raw & 0xfffffc1f) == 0xaa000010:
|
|
return true // MOV/ORR into X16
|
|
case (raw & 0xff80001f) == 0xd2800010:
|
|
return true // MOVZ X16, #imm
|
|
case (raw & 0xff80001f) == 0x92800010:
|
|
return true // MOVN X16, #imm
|
|
case (raw & 0xff80001f) == 0xf2800010:
|
|
return true // MOVK X16, #imm
|
|
default:
|
|
return isPACLikeX16(raw)
|
|
}
|
|
}
|
|
|
|
func isPACLikeX16(raw uint32) bool {
|
|
switch {
|
|
case raw == 0xd503211f, raw == 0xd503215f:
|
|
return true // PACIA1716 / PACIB1716
|
|
case (raw & 0xfffffc1f) == 0xdac10010,
|
|
(raw & 0xfffffc1f) == 0xdac10410,
|
|
(raw & 0xfffffc1f) == 0xdac10810,
|
|
(raw & 0xfffffc1f) == 0xdac10c10:
|
|
return true // PACIA/PACIB/PACDA/PACDB X16, Xn
|
|
case raw == 0xdac123f0, raw == 0xdac127f0:
|
|
return true // PACIZA/PACIZB X16
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isPACOperation(op disassemble.Operation) bool {
|
|
switch op {
|
|
case disassemble.ARM64_PACDA,
|
|
disassemble.ARM64_PACIA,
|
|
disassemble.ARM64_PACIA1716,
|
|
disassemble.ARM64_PACIA171615,
|
|
disassemble.ARM64_PACIASP,
|
|
disassemble.ARM64_PACIASPPC,
|
|
disassemble.ARM64_PACIAZ,
|
|
disassemble.ARM64_PACDB,
|
|
disassemble.ARM64_PACDZA,
|
|
disassemble.ARM64_PACDZB,
|
|
disassemble.ARM64_PACIB,
|
|
disassemble.ARM64_PACIB1716,
|
|
disassemble.ARM64_PACIB171615,
|
|
disassemble.ARM64_PACIBSP,
|
|
disassemble.ARM64_PACIBSPPC,
|
|
disassemble.ARM64_PACIBZ,
|
|
disassemble.ARM64_PACIZA,
|
|
disassemble.ARM64_PACIZB:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isCallLikeOperation(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 isConditionalBranchOperation(op disassemble.Operation) bool {
|
|
switch op {
|
|
case disassemble.ARM64_CBZ,
|
|
disassemble.ARM64_CBNZ,
|
|
disassemble.ARM64_TBZ,
|
|
disassemble.ARM64_TBNZ,
|
|
disassemble.ARM64_B_AL,
|
|
disassemble.ARM64_B_CC,
|
|
disassemble.ARM64_B_CS,
|
|
disassemble.ARM64_B_EQ,
|
|
disassemble.ARM64_B_GE,
|
|
disassemble.ARM64_B_GT,
|
|
disassemble.ARM64_B_HI,
|
|
disassemble.ARM64_B_LE,
|
|
disassemble.ARM64_B_LS,
|
|
disassemble.ARM64_B_LT,
|
|
disassemble.ARM64_B_MI,
|
|
disassemble.ARM64_B_NE,
|
|
disassemble.ARM64_B_NV,
|
|
disassemble.ARM64_B_PL,
|
|
disassemble.ARM64_B_VC,
|
|
disassemble.ARM64_B_VS:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func is32BitReg(reg disassemble.Register) bool {
|
|
return reg >= disassemble.REG_W0 && reg <= disassemble.REG_W30
|
|
}
|
|
|
|
func normalizeRegWrite(reg disassemble.Register, value uint64) uint64 {
|
|
if is32BitReg(reg) {
|
|
return uint64(uint32(value))
|
|
}
|
|
return value
|
|
}
|
|
|
|
func buildMicroPlan(start uint64, data []byte, isAnchor func(uint64) bool, maxOffset int) microPlan {
|
|
count := len(data) / 4
|
|
plan := microPlan{
|
|
tags: make([]microTag, count),
|
|
targets: make([]uint64, count),
|
|
}
|
|
for off := 0; off+4 <= len(data); off += 4 {
|
|
raw := readUint32At(data, off)
|
|
idx := off / 4
|
|
switch {
|
|
case (raw & 0x9f000000) == 0x90000000:
|
|
plan.tags[idx] |= microTagADRP
|
|
case (raw & 0x9f000000) == 0x10000000:
|
|
plan.tags[idx] |= microTagADR
|
|
}
|
|
if target, ok := decodeBLTarget(start+uint64(off), raw); ok {
|
|
plan.tags[idx] |= microTagBL
|
|
plan.targets[idx] = target
|
|
plan.branchEventOffsets = append(plan.branchEventOffsets, off)
|
|
if isAnchor != nil && isAnchor(target) {
|
|
plan.anchorBLOffsets = append(plan.anchorBLOffsets, off)
|
|
}
|
|
} else if target, ok := decodeBTarget(start+uint64(off), raw); ok {
|
|
plan.tags[idx] |= microTagB
|
|
plan.targets[idx] = target
|
|
plan.branchEventOffsets = append(plan.branchEventOffsets, off)
|
|
} else if isCallRegisterRaw(raw) {
|
|
plan.branchEventOffsets = append(plan.branchEventOffsets, off)
|
|
}
|
|
if isReturnInstruction(raw) {
|
|
plan.tags[idx] |= microTagRET
|
|
plan.retOffsets = append(plan.retOffsets, off)
|
|
}
|
|
if isPotentialX16Write(raw) {
|
|
plan.tags[idx] |= microTagX16Candidate
|
|
plan.x16CandidateOffsets = append(plan.x16CandidateOffsets, off)
|
|
}
|
|
if isStoreToX0Raw(raw) {
|
|
plan.tags[idx] |= microTagStoreToX0
|
|
plan.storeToX0Offsets = append(plan.storeToX0Offsets, off)
|
|
}
|
|
}
|
|
plan.maxOffset = len(data) - 4
|
|
if maxOffset >= 0 && maxOffset < plan.maxOffset {
|
|
plan.maxOffset = maxOffset
|
|
}
|
|
if plan.maxOffset < 0 {
|
|
plan.maxOffset = 0
|
|
}
|
|
return plan
|
|
}
|
|
|
|
func branchTargetFromState(state *microState, inst *disassemble.Inst) (uint64, bool) {
|
|
if inst == nil || operandCount(inst) == 0 {
|
|
return 0, false
|
|
}
|
|
reg, ok := operandRegister(&inst.Operands[0], 0)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_BR,
|
|
disassemble.ARM64_BRAA,
|
|
disassemble.ARM64_BRAAZ,
|
|
disassemble.ARM64_BRAB,
|
|
disassemble.ARM64_BRABZ:
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
return state.GetX(idx), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func localBranchOffset(funcStart uint64, dataLen int, maxOffset int, target uint64) (int, bool) {
|
|
if target < funcStart || target >= funcStart+uint64(dataLen) {
|
|
return 0, false
|
|
}
|
|
off := int(target - funcStart)
|
|
if off < 0 || off > maxOffset || off%4 != 0 {
|
|
return 0, false
|
|
}
|
|
return off, true
|
|
}
|
|
|
|
func (s *microState) memoryAccess(op *disassemble.Op) (microMemAccess, bool) {
|
|
if op == nil {
|
|
return microMemAccess{}, false
|
|
}
|
|
if op.Class == disassemble.LABEL {
|
|
return microMemAccess{addr: op.GetImmediate()}, true
|
|
}
|
|
baseReg, ok := operandRegister(op, 0)
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
|
|
var base uint64
|
|
access := microMemAccess{baseIdx: -1}
|
|
switch baseReg {
|
|
case disassemble.REG_SP:
|
|
base = s.sp
|
|
access.baseIsSP = true
|
|
default:
|
|
idx, ok := registerToIndex(baseReg)
|
|
if !ok || idx >= len(s.regs) {
|
|
return microMemAccess{}, false
|
|
}
|
|
base = s.regs[idx]
|
|
access.baseIdx = idx
|
|
}
|
|
|
|
switch op.Class {
|
|
case disassemble.MEM_OFFSET:
|
|
addr, ok := addSignedOffset(base, int64(op.GetImmediate()))
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
access.addr = addr
|
|
return access, true
|
|
case disassemble.MEM_PRE_IDX:
|
|
addr, ok := addSignedOffset(base, int64(op.GetImmediate()))
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
access.addr = addr
|
|
access.newBase = addr
|
|
access.writeBack = true
|
|
return access, true
|
|
case disassemble.MEM_POST_IDX:
|
|
newBase, ok := addSignedOffset(base, int64(op.GetImmediate()))
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
access.addr = base
|
|
access.newBase = newBase
|
|
access.writeBack = true
|
|
access.writeBackAfter = true
|
|
return access, true
|
|
default:
|
|
return microMemAccess{}, false
|
|
}
|
|
}
|
|
|
|
func (s *microState) applyMemWriteBack(access microMemAccess) {
|
|
if !access.writeBack {
|
|
return
|
|
}
|
|
if access.baseIsSP {
|
|
s.sp = access.newBase
|
|
return
|
|
}
|
|
if access.baseIdx >= 0 {
|
|
s.setKnownValue(access.baseIdx, access.newBase)
|
|
}
|
|
}
|
|
|
|
func (s *microState) resetCallEvidence() {
|
|
s.regLoadAddr = [31]uint64{}
|
|
s.spills = [4]trackedSpill{}
|
|
s.x16Candidate = 0
|
|
}
|
|
|
|
func (s *microState) classifyStore(inst *disassemble.Inst) (microMemAccess, [2]int, int, bool) {
|
|
var src [2]int
|
|
for i := range src {
|
|
src[i] = -1
|
|
}
|
|
if inst == nil {
|
|
return microMemAccess{}, src, 0, false
|
|
}
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_STR, disassemble.ARM64_STUR:
|
|
reg, ok := operandRegister(&inst.Operands[0], 0)
|
|
if operandCount(inst) < 2 || !ok {
|
|
return microMemAccess{}, src, 0, false
|
|
}
|
|
access, ok := s.memoryAccess(&inst.Operands[1])
|
|
if !ok {
|
|
return microMemAccess{}, src, 0, false
|
|
}
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
src[0] = idx
|
|
return access, src, 1, true
|
|
}
|
|
case disassemble.ARM64_STP:
|
|
if operandCount(inst) < 3 {
|
|
return microMemAccess{}, src, 0, false
|
|
}
|
|
access, ok := s.memoryAccess(&inst.Operands[2])
|
|
if !ok {
|
|
return microMemAccess{}, src, 0, false
|
|
}
|
|
count := 0
|
|
if reg, ok := operandRegister(&inst.Operands[0], 0); ok {
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
src[0] = idx
|
|
count++
|
|
}
|
|
}
|
|
if reg, ok := operandRegister(&inst.Operands[1], 0); ok {
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
src[1] = idx
|
|
count++
|
|
}
|
|
}
|
|
return access, src, count, count > 0
|
|
}
|
|
return microMemAccess{}, src, 0, false
|
|
}
|
|
|
|
func (s *Scanner) loadValueAt(owner *macho.File, state *microState, addr uint64) (uint64, bool) {
|
|
if value, ok := state.stackSlots[addr]; ok {
|
|
return value, true
|
|
}
|
|
if ptr, ok := s.resolvePointerAt(owner, addr); ok {
|
|
return ptr, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (s *Scanner) trackedLoadAddress(state *microState, op *disassemble.Op) (microMemAccess, bool) {
|
|
if op == nil {
|
|
return microMemAccess{}, false
|
|
}
|
|
if op.Class == disassemble.LABEL {
|
|
return microMemAccess{addr: op.GetImmediate()}, true
|
|
}
|
|
baseReg, ok := operandRegister(op, 0)
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
|
|
switch baseReg {
|
|
case disassemble.REG_SP:
|
|
return state.memoryAccess(op)
|
|
}
|
|
|
|
baseIdx, ok := registerToIndex(baseReg)
|
|
if !ok || baseIdx >= len(state.regBase) || state.regBase[baseIdx] == 0 {
|
|
return microMemAccess{}, false
|
|
}
|
|
|
|
base := state.regBase[baseIdx]
|
|
access := microMemAccess{baseIdx: baseIdx}
|
|
switch op.Class {
|
|
case disassemble.MEM_OFFSET:
|
|
addr, ok := addSignedOffset(base, int64(op.GetImmediate()))
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
access.addr = addr
|
|
return access, true
|
|
case disassemble.MEM_PRE_IDX:
|
|
addr, ok := addSignedOffset(base, int64(op.GetImmediate()))
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
access.addr = addr
|
|
access.newBase = addr
|
|
access.writeBack = true
|
|
return access, true
|
|
case disassemble.MEM_POST_IDX:
|
|
newBase, ok := addSignedOffset(base, int64(op.GetImmediate()))
|
|
if !ok {
|
|
return microMemAccess{}, false
|
|
}
|
|
access.addr = base
|
|
access.newBase = newBase
|
|
access.writeBack = true
|
|
access.writeBackAfter = true
|
|
return access, true
|
|
default:
|
|
return microMemAccess{}, false
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroInstruction(state *microState, inst *disassemble.Inst) {
|
|
if inst == nil {
|
|
return
|
|
}
|
|
|
|
captureRegisterSpills(state, inst, &state.spills)
|
|
handled := true
|
|
|
|
switch inst.Operation {
|
|
case disassemble.ARM64_ADRP:
|
|
if reg, ok := operandRegister(&inst.Operands[0], 0); operandCount(inst) > 0 && ok {
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
if addr, ok := decodeADRPImmediate(inst.Address, inst.Raw); ok {
|
|
s.stateWriteAddress(state, idx, addr)
|
|
} else {
|
|
state.clearTracked(idx)
|
|
}
|
|
}
|
|
}
|
|
case disassemble.ARM64_ADR:
|
|
if reg, ok := operandRegister(&inst.Operands[0], 0); operandCount(inst) > 1 && ok {
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
s.stateWriteAddress(state, idx, inst.Operands[1].GetImmediate())
|
|
}
|
|
}
|
|
case disassemble.ARM64_ADD:
|
|
s.applyMicroAdd(state, inst)
|
|
case disassemble.ARM64_SUB:
|
|
s.applyMicroSub(state, inst)
|
|
case disassemble.ARM64_MOV:
|
|
s.applyMicroMov(state, inst)
|
|
case disassemble.ARM64_MOVZ:
|
|
s.applyMicroMovWide(state, inst, false, false)
|
|
case disassemble.ARM64_MOVN:
|
|
s.applyMicroMovWide(state, inst, true, false)
|
|
case disassemble.ARM64_MOVK:
|
|
s.applyMicroMovWide(state, inst, false, true)
|
|
case disassemble.ARM64_ORR:
|
|
s.applyMicroOrr(state, inst)
|
|
case disassemble.ARM64_LDR, disassemble.ARM64_LDUR:
|
|
s.applyMicroLoad(state, inst, false)
|
|
case disassemble.ARM64_LDP:
|
|
s.applyMicroLoad(state, inst, true)
|
|
case disassemble.ARM64_STR, disassemble.ARM64_STUR:
|
|
s.applyMicroStore(state, inst, false)
|
|
case disassemble.ARM64_STP:
|
|
s.applyMicroStore(state, inst, true)
|
|
default:
|
|
handled = false
|
|
}
|
|
|
|
if !handled {
|
|
s.invalidateUnsupportedDestinations(state, inst)
|
|
}
|
|
if isPACOperation(inst.Operation) && operandCount(inst) > 0 && operandHasRegister(&inst.Operands[0], disassemble.REG_X16) {
|
|
if validKernelPointer(state.GetX(16)) {
|
|
state.x16Candidate = state.GetX(16)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) invalidateUnsupportedDestinations(state *microState, inst *disassemble.Inst) {
|
|
if inst == nil || operandCount(inst) == 0 {
|
|
return
|
|
}
|
|
op := inst.Operands[0]
|
|
switch op.Class {
|
|
case disassemble.MEM_OFFSET, disassemble.MEM_PRE_IDX, disassemble.MEM_POST_IDX, disassemble.LABEL:
|
|
return
|
|
}
|
|
for idx := 0; idx < int(op.NumRegisters); idx++ {
|
|
reg := op.Registers[idx]
|
|
if reg == disassemble.REG_SP {
|
|
state.sp = 0
|
|
continue
|
|
}
|
|
if idx, ok := registerToIndex(reg); ok {
|
|
state.clearTracked(idx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) stateWriteAddress(state *microState, idx int, value uint64) {
|
|
if idx < 0 || idx >= len(state.regs) {
|
|
return
|
|
}
|
|
state.setKnownBase(idx, value)
|
|
if idx == 16 && validKernelPointer(value) {
|
|
state.x16Candidate = value
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroAdd(state *microState, inst *disassemble.Inst) {
|
|
dstReg, dstOK := operandRegister(&inst.Operands[0], 0)
|
|
srcReg, srcOK := operandRegister(&inst.Operands[1], 0)
|
|
if operandCount(inst) < 3 || !dstOK || !srcOK {
|
|
return
|
|
}
|
|
dstIdx, dstOK := registerToIndex(dstReg)
|
|
srcIdx, srcOK := registerToIndex(srcReg)
|
|
if !dstOK || !srcOK {
|
|
return
|
|
}
|
|
imm := inst.Operands[2].GetImmediate()
|
|
if dstReg == disassemble.REG_SP && srcReg == disassemble.REG_SP {
|
|
state.sp += imm
|
|
return
|
|
}
|
|
srcBase := state.regBase[srcIdx]
|
|
srcLoadAddr := state.regLoadAddr[srcIdx]
|
|
srcValue := state.regs[srcIdx]
|
|
srcKnown := state.regKnown[srcIdx]
|
|
state.clearTracked(dstIdx)
|
|
if is32BitReg(dstReg) {
|
|
if srcKnown {
|
|
if value, ok := addSignedOffset(srcValue, int64(imm)); ok {
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, value))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
if srcBase != 0 {
|
|
if value, ok := addSignedOffset(srcBase, int64(imm)); ok {
|
|
s.stateWriteAddress(state, dstIdx, value)
|
|
return
|
|
}
|
|
}
|
|
if srcLoadAddr != 0 {
|
|
if addr, ok := addSignedOffset(srcLoadAddr, int64(imm)); ok {
|
|
state.regLoadAddr[dstIdx] = addr
|
|
return
|
|
}
|
|
}
|
|
if srcKnown {
|
|
if value, ok := addSignedOffset(srcValue, int64(imm)); ok {
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, value))
|
|
if dstIdx == 16 && validKernelPointer(value) {
|
|
state.x16Candidate = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroSub(state *microState, inst *disassemble.Inst) {
|
|
dstReg, dstRegOK := operandRegister(&inst.Operands[0], 0)
|
|
srcReg, srcRegOK := operandRegister(&inst.Operands[1], 0)
|
|
if operandCount(inst) < 3 || !dstRegOK || !srcRegOK {
|
|
return
|
|
}
|
|
imm := inst.Operands[2].GetImmediate()
|
|
if dstReg == disassemble.REG_SP && srcReg == disassemble.REG_SP {
|
|
state.sp -= imm
|
|
return
|
|
}
|
|
dstIdx, dstOK := registerToIndex(dstReg)
|
|
srcIdx, srcOK := registerToIndex(srcReg)
|
|
if !dstOK || !srcOK {
|
|
return
|
|
}
|
|
state.clearTracked(dstIdx)
|
|
if srcValue := state.regs[srcIdx]; state.regKnown[srcIdx] && srcValue >= imm {
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, srcValue-imm))
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroMov(state *microState, inst *disassemble.Inst) {
|
|
dstReg, dstRegOK := operandRegister(&inst.Operands[0], 0)
|
|
if operandCount(inst) < 2 || !dstRegOK {
|
|
return
|
|
}
|
|
dstIdx, dstOK := registerToIndex(dstReg)
|
|
if !dstOK {
|
|
return
|
|
}
|
|
state.clearTracked(dstIdx)
|
|
if srcReg, ok := operandRegister(&inst.Operands[1], 0); ok {
|
|
srcIdx, srcOK := registerToIndex(srcReg)
|
|
if srcOK {
|
|
if is32BitReg(dstReg) {
|
|
if state.regKnown[srcIdx] {
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, state.regs[srcIdx]))
|
|
}
|
|
} else {
|
|
state.copyTracked(dstIdx, srcIdx)
|
|
}
|
|
if dstIdx == 16 && validKernelPointer(state.regs[dstIdx]) {
|
|
state.x16Candidate = state.regs[dstIdx]
|
|
}
|
|
}
|
|
return
|
|
}
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, inst.Operands[1].GetImmediate()))
|
|
}
|
|
|
|
func (s *Scanner) applyMicroMovWide(state *microState, inst *disassemble.Inst, invert bool, keep bool) {
|
|
dstReg, dstRegOK := operandRegister(&inst.Operands[0], 0)
|
|
if operandCount(inst) < 2 || !dstRegOK {
|
|
return
|
|
}
|
|
dstIdx, dstOK := registerToIndex(dstReg)
|
|
if !dstOK {
|
|
return
|
|
}
|
|
imm := inst.Operands[1].GetImmediate()
|
|
prevKnown := state.regKnown[dstIdx]
|
|
value := imm
|
|
if invert {
|
|
value = ^imm
|
|
}
|
|
if keep {
|
|
if !prevKnown {
|
|
state.clearTracked(dstIdx)
|
|
return
|
|
}
|
|
shift := uint32(0)
|
|
if inst.Operands[1].ShiftValueUsed {
|
|
shift = inst.Operands[1].ShiftValue
|
|
}
|
|
mask := uint64(0xffff) << shift
|
|
value = (state.regs[dstIdx] & ^mask) | (imm & mask)
|
|
}
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, value))
|
|
if dstIdx == 16 && validKernelPointer(value) {
|
|
state.x16Candidate = value
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroOrr(state *microState, inst *disassemble.Inst) {
|
|
dstReg, dstRegOK := operandRegister(&inst.Operands[0], 0)
|
|
if operandCount(inst) < 3 || !dstRegOK {
|
|
return
|
|
}
|
|
dstIdx, dstOK := registerToIndex(dstReg)
|
|
if !dstOK {
|
|
return
|
|
}
|
|
state.clearTracked(dstIdx)
|
|
switch {
|
|
case operandRegisterCount(&inst.Operands[1]) > 0 && operandRegisterCount(&inst.Operands[2]) > 0:
|
|
reg1, _ := operandRegister(&inst.Operands[1], 0)
|
|
reg2, _ := operandRegister(&inst.Operands[2], 0)
|
|
if reg1 == disassemble.REG_XZR || reg1 == disassemble.REG_WZR {
|
|
srcIdx, ok := registerToIndex(reg2)
|
|
if ok {
|
|
if is32BitReg(dstReg) {
|
|
if state.regKnown[srcIdx] {
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, state.regs[srcIdx]))
|
|
}
|
|
} else {
|
|
state.copyTracked(dstIdx, srcIdx)
|
|
}
|
|
}
|
|
} else if reg2 == disassemble.REG_XZR || reg2 == disassemble.REG_WZR {
|
|
srcIdx, ok := registerToIndex(reg1)
|
|
if ok {
|
|
if is32BitReg(dstReg) {
|
|
if state.regKnown[srcIdx] {
|
|
state.setKnownValue(dstIdx, normalizeRegWrite(dstReg, state.regs[srcIdx]))
|
|
}
|
|
} else {
|
|
state.copyTracked(dstIdx, srcIdx)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if dstIdx == 16 && validKernelPointer(state.regs[dstIdx]) {
|
|
state.x16Candidate = state.regs[dstIdx]
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroLoad(state *microState, inst *disassemble.Inst, pair bool) {
|
|
if !pair {
|
|
dstReg, ok := operandRegister(&inst.Operands[0], 0)
|
|
if operandCount(inst) < 2 || !ok {
|
|
return
|
|
}
|
|
dstIdx, ok := registerToIndex(dstReg)
|
|
if !ok {
|
|
return
|
|
}
|
|
access, ok := s.trackedLoadAddress(state, &inst.Operands[1])
|
|
if !ok {
|
|
state.clearTracked(dstIdx)
|
|
return
|
|
}
|
|
state.clearTracked(dstIdx)
|
|
if access.writeBack && !access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
state.regLoadAddr[dstIdx] = access.addr
|
|
value, known := s.loadValueAt(state.owner, state, access.addr)
|
|
state.regs[dstIdx] = normalizeRegWrite(dstReg, value)
|
|
state.regKnown[dstIdx] = known
|
|
if is32BitReg(dstReg) || !known {
|
|
state.regLoadAddr[dstIdx] = 0
|
|
}
|
|
if access.writeBack && access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
if dstIdx == 16 && validKernelPointer(value) {
|
|
state.x16Candidate = value
|
|
}
|
|
return
|
|
}
|
|
|
|
if operandCount(inst) < 3 {
|
|
return
|
|
}
|
|
access, ok := s.trackedLoadAddress(state, &inst.Operands[2])
|
|
if !ok {
|
|
return
|
|
}
|
|
if access.writeBack && !access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
for i := range 2 {
|
|
dstReg, ok := operandRegister(&inst.Operands[i], 0)
|
|
if !ok {
|
|
continue
|
|
}
|
|
dstIdx, ok := registerToIndex(dstReg)
|
|
if !ok {
|
|
continue
|
|
}
|
|
state.clearTracked(dstIdx)
|
|
addr := access.addr + uint64(i*8)
|
|
state.regLoadAddr[dstIdx] = addr
|
|
value, known := s.loadValueAt(state.owner, state, addr)
|
|
state.regs[dstIdx] = normalizeRegWrite(dstReg, value)
|
|
state.regKnown[dstIdx] = known
|
|
if is32BitReg(dstReg) || !known {
|
|
state.regLoadAddr[dstIdx] = 0
|
|
}
|
|
if dstIdx == 16 && validKernelPointer(value) {
|
|
state.x16Candidate = value
|
|
}
|
|
}
|
|
if access.writeBack && access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) applyMicroStore(state *microState, inst *disassemble.Inst, pair bool) {
|
|
if !pair {
|
|
srcReg, ok := operandRegister(&inst.Operands[0], 0)
|
|
if operandCount(inst) < 2 || !ok {
|
|
return
|
|
}
|
|
access, ok := state.memoryAccess(&inst.Operands[1])
|
|
if !ok {
|
|
return
|
|
}
|
|
if access.writeBack && !access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
if access.baseIsSP {
|
|
srcIdx, ok := registerToIndex(srcReg)
|
|
if ok {
|
|
state.writeStack(access.addr, state.regs[srcIdx])
|
|
}
|
|
}
|
|
if access.writeBack && access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
return
|
|
}
|
|
|
|
if operandCount(inst) < 3 {
|
|
return
|
|
}
|
|
access, ok := state.memoryAccess(&inst.Operands[2])
|
|
if !ok {
|
|
return
|
|
}
|
|
if access.writeBack && !access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
if access.baseIsSP {
|
|
for i := range 2 {
|
|
srcReg, ok := operandRegister(&inst.Operands[i], 0)
|
|
if !ok {
|
|
continue
|
|
}
|
|
srcIdx, ok := registerToIndex(srcReg)
|
|
if !ok {
|
|
continue
|
|
}
|
|
state.writeStack(access.addr+uint64(i*8), state.regs[srcIdx])
|
|
}
|
|
}
|
|
if access.writeBack && access.writeBackAfter {
|
|
state.applyMemWriteBack(access)
|
|
}
|
|
}
|