mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
530 lines
14 KiB
Go
530 lines
14 KiB
Go
package kernelcache
|
|
|
|
//go:generate go tool stringer -type=SubsystemStart -output mig_string.go
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/arm64-cgo/disassemble"
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-macho/types"
|
|
"github.com/blacktop/ipsw/pkg/disass"
|
|
)
|
|
|
|
type SubsystemStart uint32
|
|
|
|
const (
|
|
arcade_register_subsystem SubsystemStart = 0xC90F
|
|
catch_exc_subsystem SubsystemStart = 0x961
|
|
catch_mach_exc_subsystem SubsystemStart = 0x965
|
|
clock_subsystem SubsystemStart = 0x3E8
|
|
host_priv_subsystem SubsystemStart = 0x190
|
|
is_iokit_subsystem SubsystemStart = 0xAF0
|
|
mach_eventlink_subsystem SubsystemStart = 0xAEDA8
|
|
mach_host_subsystem SubsystemStart = 0xC8
|
|
mach_port_subsystem SubsystemStart = 0xC80
|
|
mach_vm_subsystem SubsystemStart = 0x12c0
|
|
mach_voucher_subsystem SubsystemStart = 0x1518
|
|
memory_entry_subsystem SubsystemStart = 0x1324
|
|
processor_set_subsystem SubsystemStart = 0xFA0
|
|
processor_subsystem SubsystemStart = 0xBB8
|
|
task_restartable_subsystem SubsystemStart = 0x1F40
|
|
task_subsystem SubsystemStart = 0xD48
|
|
thread_act_subsystem SubsystemStart = 0xE10
|
|
UNDReply_subsystem SubsystemStart = 0x1838
|
|
vm32_map_subsystem SubsystemStart = 0xED8
|
|
)
|
|
|
|
type MigHash struct {
|
|
Num int
|
|
KObjIdx int
|
|
KRoutine uint64 /* Kernel server routine */
|
|
KReplySize uint32 /* Size of kernel reply msg */
|
|
KReplyDescCnt uint32 /* Number of descs in kernel reply msg */
|
|
}
|
|
|
|
type MachMsgHeader struct {
|
|
MsghBits uint32
|
|
MsghSize uint32
|
|
MsghRemotePort uint32
|
|
MsghLocalPort uint32
|
|
MsghVoucherPort uint32
|
|
MsghId uint32
|
|
}
|
|
|
|
type KernRoutineDescriptor struct {
|
|
ImplRoutine uint64 /* Server work func pointer */
|
|
KStubRoutine uint64 /* Unmarshalling func pointer */
|
|
ArgC uint32 /* Number of argument words */
|
|
DescrCount uint32 /* Number complex descriptors */
|
|
ReplyDescrCount uint32 /* Number descriptors in reply */
|
|
MaxReplyMsg uint32 /* Max size for reply msg */
|
|
}
|
|
|
|
type migKernSubsystemHdr struct {
|
|
KServer uint64 /* pointer to kernel demux routine */
|
|
Start SubsystemStart /* Min routine number */
|
|
End uint32 /* Max routine number + 1 */
|
|
Maxsize uint32 /* Max reply message size */
|
|
_ uint32 // padding
|
|
Reserved uint64 /* reserved for MIG use */
|
|
|
|
}
|
|
|
|
type MigKernSubsystem struct {
|
|
migKernSubsystemHdr
|
|
Routines []KernRoutineDescriptor /* Kernel routine descriptor array */
|
|
}
|
|
|
|
func (m MigKernSubsystem) LookupRoutineName(idx int) string {
|
|
switch m.Start {
|
|
case mach_vm_subsystem:
|
|
if idx >= len(machVmSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return machVmSubsystemFuncs[idx]
|
|
}
|
|
case mach_port_subsystem:
|
|
if idx >= len(machPortSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return machPortSubsystemFuncs[idx]
|
|
}
|
|
case mach_host_subsystem:
|
|
if idx >= len(machHostSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return machHostSubsystemFuncs[idx]
|
|
}
|
|
case host_priv_subsystem:
|
|
if idx >= len(hostPrivSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return hostPrivSubsystemFuncs[idx]
|
|
}
|
|
case clock_subsystem:
|
|
if idx >= len(clockSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return clockSubsystemFuncs[idx]
|
|
}
|
|
case processor_subsystem:
|
|
if idx >= len(processorSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return processorSubsystemFuncs[idx]
|
|
}
|
|
case processor_set_subsystem:
|
|
if idx >= len(processorSetSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return processorSetSubsystemFuncs[idx]
|
|
}
|
|
case is_iokit_subsystem:
|
|
if idx >= len(isIokitSubsystemProcessorSetSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return isIokitSubsystemProcessorSetSubsystemFuncs[idx]
|
|
}
|
|
case task_subsystem:
|
|
if idx >= len(taskSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return taskSubsystemFuncs[idx]
|
|
}
|
|
case thread_act_subsystem:
|
|
if idx >= len(threadActSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return threadActSubsystemFuncs[idx]
|
|
}
|
|
case vm32_map_subsystem:
|
|
if idx >= len(vm32MapSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return vm32MapSubsystemFuncs[idx]
|
|
}
|
|
case UNDReply_subsystem:
|
|
if idx >= len(undReplySubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return undReplySubsystemFuncs[idx]
|
|
}
|
|
case mach_voucher_subsystem:
|
|
if idx >= len(machVoucherSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return machVoucherSubsystemFuncs[idx]
|
|
}
|
|
case memory_entry_subsystem:
|
|
if idx >= len(memoryEntrySubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return memoryEntrySubsystemFuncs[idx]
|
|
}
|
|
case task_restartable_subsystem:
|
|
if idx >= len(taskRestartableSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return taskRestartableSubsystemFuncs[idx]
|
|
}
|
|
case catch_exc_subsystem:
|
|
if idx >= len(catchExcSubsystemFuncs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return catchExcSubsystemFuncs[idx]
|
|
}
|
|
case catch_mach_exc_subsystem:
|
|
if idx >= len(catch_mach_exc_subsystem_Funcs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return catch_mach_exc_subsystem_Funcs[idx]
|
|
}
|
|
case arcade_register_subsystem:
|
|
if idx >= len(arcade_register_subsystem_Funcs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return arcade_register_subsystem_Funcs[idx]
|
|
}
|
|
case mach_eventlink_subsystem:
|
|
if idx >= len(mach_eventlink_subsystem_Funcs) {
|
|
return "<unknown>"
|
|
} else {
|
|
return mach_eventlink_subsystem_Funcs[idx]
|
|
}
|
|
default:
|
|
return "<unknown>"
|
|
}
|
|
}
|
|
|
|
func (m MigKernSubsystem) String() string {
|
|
var out strings.Builder
|
|
out.WriteString(fmt.Sprintf("%s: %s\t%s=%d %s=%d %s=%d\n",
|
|
colorAddr("%#x", m.KServer),
|
|
colorSubSystem(m.Start.String()),
|
|
colorField("start"), m.Start,
|
|
colorField("end"), m.End,
|
|
colorField("max_sz"), m.Maxsize,
|
|
))
|
|
for idx, r := range m.Routines {
|
|
if r.KStubRoutine == 0 {
|
|
continue // skip empty routines
|
|
}
|
|
out.WriteString(fmt.Sprintf(" %s: ", colorAddr("%#x", r.KStubRoutine)))
|
|
out.WriteString(colorBold(m.LookupRoutineName(idx)))
|
|
out.WriteString(fmt.Sprintf("\t%s=%02d %s=%#x %s=%02d %s=%d %s=%d %s=%d\n",
|
|
colorName("num"), idx+int(m.Start),
|
|
colorName("impl"), r.ImplRoutine,
|
|
colorName("argc"), r.ArgC,
|
|
colorName("descr"), r.DescrCount,
|
|
colorName("reply_descr"), r.ReplyDescrCount,
|
|
colorName("max_reply_msg"), r.MaxReplyMsg,
|
|
))
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
func getMigInitFunc(m *macho.File) (*types.Function, error) {
|
|
var ref uint64
|
|
|
|
cstrs, err := m.GetCStrings()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
found := false
|
|
for str, addr := range cstrs["__TEXT.__cstring"] {
|
|
if strings.Contains(str, "mig_e") {
|
|
ref = addr
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("failed to find 'mig_e' anchor")
|
|
}
|
|
|
|
text := m.Section("__TEXT_EXEC", "__text")
|
|
if text == nil {
|
|
return nil, fmt.Errorf("failed to find __TEXT_EXEC.__text section")
|
|
}
|
|
data, err := text.Data()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get data from __TEXT_EXEC.__text section: %v", err)
|
|
}
|
|
|
|
engine := disass.NewMachoDisass(m, &disass.Config{
|
|
Data: data,
|
|
StartAddress: text.Addr,
|
|
Quiet: true,
|
|
})
|
|
if err := engine.Triage(); err != nil {
|
|
return nil, fmt.Errorf("first pass triage failed: %v", err)
|
|
}
|
|
|
|
if ok, loc := engine.Contains(ref); ok {
|
|
migInit, err := m.GetFunctionForVMAddr(loc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get function with xref to 'mig_e' anchor: %v", err)
|
|
}
|
|
return &migInit, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to find 'mig_init' address")
|
|
}
|
|
|
|
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.Inst, idx int) (uint64, bool) {
|
|
if instr == nil || int(instr.NumOps) <= idx {
|
|
return 0, false
|
|
}
|
|
return instr.Operands[idx].Immediate, true
|
|
}
|
|
|
|
func getMigE(r *bytes.Reader, migInit *types.Function) (uint64, error) {
|
|
var migE uint64
|
|
|
|
var instrValue uint32
|
|
var decoder disassemble.Decoder
|
|
var prevInstr disassemble.Inst
|
|
var hasPrev bool
|
|
|
|
startAddr := migInit.StartAddr
|
|
|
|
for {
|
|
err := binary.Read(r, binary.LittleEndian, &instrValue)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return 0, fmt.Errorf("failed to read instruction @ %#x: %v", startAddr, err)
|
|
}
|
|
|
|
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 {
|
|
migE = imm
|
|
break
|
|
}
|
|
} 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)
|
|
if ok {
|
|
adrpImm, ok := migOperandImmediate(&prevInstr, 1)
|
|
if ok {
|
|
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 {
|
|
adrpImm += imm
|
|
} else {
|
|
validPattern = false
|
|
}
|
|
case disassemble.ARM64_ADD:
|
|
if imm, ok := migOperandImmediate(&instruction, 2); ok {
|
|
adrpImm += imm
|
|
} else {
|
|
validPattern = false
|
|
}
|
|
}
|
|
}
|
|
if validPattern {
|
|
migE = adrpImm
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// fmt.Printf("%#08x: %s\t%s\n", uint64(startAddr), disassemble.GetOpCodeByteString(instrValue), instruction)
|
|
|
|
prevInstr = instruction
|
|
hasPrev = true
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
}
|
|
|
|
if migE == 0 {
|
|
return 0, fmt.Errorf("failed to find 'mig_e' table address in mig_init")
|
|
}
|
|
|
|
return migE, nil
|
|
}
|
|
|
|
// getMigSubsystemPointers discovers the list of mig_subsystem pointers in the
|
|
// mig_e[] table by walking pointer-sized entries starting at migEAddr and
|
|
// validating that each entry looks like a reasonable mig_subsystem header
|
|
// within __DATA_CONST.__const. This avoids relying on brittle instruction
|
|
// patterns (such as CMP-immediate) in mig_init, which can change between
|
|
// kernel versions.
|
|
func getMigSubsystemPointers(m *macho.File, migEAddr uint64) ([]uint64, error) {
|
|
const maxEntries = 64 // generous upper bound for number of MIG subsystems
|
|
|
|
dataConst := m.Section("__DATA_CONST", "__const")
|
|
if dataConst == nil {
|
|
return nil, macho.ErrMachOSectionNotFound
|
|
}
|
|
dataConstData, err := dataConst.Data()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dataConstStart := dataConst.Addr
|
|
dataConstEnd := dataConstStart + uint64(len(dataConstData))
|
|
|
|
hdrSize := uint64(binary.Size(migKernSubsystemHdr{}))
|
|
|
|
var subsystems []uint64
|
|
|
|
for i := range uint64(maxEntries) {
|
|
ptr, err := m.GetPointerAtAddress(migEAddr + i*uint64(binary.Size(uint64(0))))
|
|
if err != nil {
|
|
// If we fail on the very first entry, something is wrong with migEAddr.
|
|
if i == 0 {
|
|
return nil, fmt.Errorf("failed to read mig_e entry at %#x: %v", migEAddr, err)
|
|
}
|
|
break
|
|
}
|
|
|
|
// A NULL entry is treated as the end of the table if we've already
|
|
// collected some subsystems.
|
|
if ptr == 0 {
|
|
if len(subsystems) == 0 {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
// mig_subsystem headers live in __DATA_CONST.__const.
|
|
if ptr < dataConstStart || ptr+hdrSize > dataConstEnd {
|
|
// If we haven't collected any valid entries yet, keep scanning in
|
|
// case the first few entries are something else.
|
|
if len(subsystems) == 0 {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
off := ptr - dataConstStart
|
|
r := bytes.NewReader(dataConstData[off:])
|
|
|
|
var hdr migKernSubsystemHdr
|
|
if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil {
|
|
if len(subsystems) == 0 {
|
|
return nil, fmt.Errorf("failed to read mig subsystem header at %#x: %v", ptr, err)
|
|
}
|
|
break
|
|
}
|
|
|
|
// Basic sanity checks on the header:
|
|
if hdr.Start == 0 || hdr.End <= uint32(hdr.Start) {
|
|
if len(subsystems) == 0 {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if hdr.End-uint32(hdr.Start) > 0x1000 {
|
|
if len(subsystems) == 0 {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
subsystems = append(subsystems, ptr)
|
|
}
|
|
|
|
if len(subsystems) == 0 {
|
|
return nil, fmt.Errorf("failed to infer mig subsystem table size from mig_e (addr=%#x)", migEAddr)
|
|
}
|
|
|
|
return subsystems, nil
|
|
}
|
|
|
|
func GetMigSubsystems(m *macho.File) (uint64, []MigKernSubsystem, error) {
|
|
if m.FileTOC.FileHeader.Type == types.MH_FILESET {
|
|
var err error
|
|
m, err = m.GetFileSetFileByName("com.apple.kernel")
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed to get fileset entry 'com.apple.kernel': %v", err)
|
|
}
|
|
}
|
|
|
|
migInit, err := getMigInitFunc(m)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
data, err := m.GetFunctionData(*migInit)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
migEAddr, err := getMigE(bytes.NewReader(data), migInit)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
subsystems, err := getMigSubsystemPointers(m, migEAddr)
|
|
if err != nil {
|
|
return migEAddr, nil, err
|
|
}
|
|
|
|
log.WithField("mig_kern_subsystem table", fmt.Sprintf("%#x", migEAddr)).Infof("Found")
|
|
|
|
dataConst := m.Section("__DATA_CONST", "__const")
|
|
if dataConst == nil {
|
|
return migEAddr, nil, macho.ErrMachOSectionNotFound
|
|
}
|
|
dataConstData, err := dataConst.Data()
|
|
if err != nil {
|
|
return migEAddr, nil, err
|
|
}
|
|
|
|
r := bytes.NewReader(dataConstData)
|
|
|
|
var migs []MigKernSubsystem
|
|
|
|
for i := range subsystems {
|
|
r.Seek(int64(subsystems[i]-dataConst.Addr), io.SeekStart)
|
|
|
|
var mig MigKernSubsystem
|
|
if err := binary.Read(r, binary.LittleEndian, &mig.migKernSubsystemHdr); err != nil {
|
|
return migEAddr, nil, err
|
|
}
|
|
mig.migKernSubsystemHdr.KServer = m.SlidePointer(mig.migKernSubsystemHdr.KServer)
|
|
mig.Routines = make([]KernRoutineDescriptor, mig.End-uint32(mig.Start))
|
|
if err := binary.Read(r, binary.LittleEndian, &mig.Routines); err != nil {
|
|
return migEAddr, nil, err
|
|
}
|
|
for i, routine := range mig.Routines {
|
|
routine.ImplRoutine = m.SlidePointer(routine.ImplRoutine)
|
|
routine.KStubRoutine = m.SlidePointer(routine.KStubRoutine)
|
|
mig.Routines[i] = routine
|
|
}
|
|
|
|
migs = append(migs, mig)
|
|
}
|
|
|
|
return migEAddr, migs, nil
|
|
}
|