mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
Add dyld softlink/import/disassembly helpers, sandbox reach and diff support, richer WebKit IPC metadata, and expanded IOKit method recovery. Fix MOVK softlink address reconstruction and avoid cross-case argument-state poisoning while scanning switch-dispatched user-client methods.
342 lines
11 KiB
Go
342 lines
11 KiB
Go
package iokit
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
type dispatchEntry struct {
|
|
object uint64
|
|
function uint64
|
|
adjustor uint64
|
|
scalarInputCount uint32
|
|
structInputSize uint32
|
|
scalarOutputCount uint32
|
|
structOutputSize uint32
|
|
flags int64
|
|
checkEntitlementAddr uint64
|
|
checkEntitlement string
|
|
count0 uint64
|
|
count1 uint64
|
|
}
|
|
|
|
func (a *analyzer) dispatchRecords(info *classInfo, analysis methodAnalysis) ([]Record, error) {
|
|
if analysis.kind == DispatchExternalMethod2022 && analysis.stride != dispatchSize2022 {
|
|
return nil, fmt.Errorf("IOExternalMethodDispatch2022 layout mismatch: stride=%d assumed=%d class=%s", analysis.stride, dispatchSize2022, info.Name)
|
|
}
|
|
if len(analysis.selectedEntries) > 0 {
|
|
return a.selectedDispatchRecords(info, analysis), nil
|
|
}
|
|
if analysis.note == "bounds_unknown" || analysis.count <= 0 {
|
|
return []Record{a.unresolvedMethodRecord(info, analysis, -1, "bounds_unknown")}, nil
|
|
}
|
|
bases := dispatchTableBases(analysis)
|
|
records := make([]Record, 0, len(bases)*analysis.count)
|
|
for tableIndex, tableBase := range bases {
|
|
tableAnalysis := analysis
|
|
tableAnalysis.arrayBase = tableBase
|
|
if analysis.note == "conditional_array" {
|
|
tableAnalysis.note = ""
|
|
}
|
|
for selector := 0; selector < analysis.count; selector++ {
|
|
rec := a.dispatchRecord(info, tableAnalysis, selector)
|
|
if analysis.note == "conditional_array" {
|
|
addConditionalTableExtra(rec.Extra, tableIndex, tableBase)
|
|
}
|
|
records = append(records, rec)
|
|
}
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func (a *analyzer) selectedDispatchRecords(info *classInfo, analysis methodAnalysis) []Record {
|
|
selectors := make([]int, 0, len(analysis.selectedEntries))
|
|
for selector := range analysis.selectedEntries {
|
|
selectors = append(selectors, selector)
|
|
}
|
|
sort.Ints(selectors)
|
|
records := make([]Record, 0, len(selectors))
|
|
for _, selector := range selectors {
|
|
entryAnalysis := analysis
|
|
entryAnalysis.arrayBase = analysis.selectedEntries[selector]
|
|
rec := a.dispatchRecord(info, entryAnalysis, 0)
|
|
rec.Selector = selector
|
|
rec.Extra["slice_notes"] = appendNote(rec.Extra["slice_notes"], "selected_entry")
|
|
records = append(records, rec)
|
|
}
|
|
return records
|
|
}
|
|
|
|
func dispatchTableBases(analysis methodAnalysis) []uint64 {
|
|
if analysis.note == "conditional_array" && len(analysis.arrayBases) > 0 {
|
|
return analysis.arrayBases
|
|
}
|
|
return []uint64{analysis.arrayBase}
|
|
}
|
|
|
|
func addConditionalTableExtra(extra map[string]string, tableIndex int, tableBase uint64) {
|
|
if extra == nil {
|
|
return
|
|
}
|
|
extra["table"] = fmt.Sprintf("table_%d", tableIndex)
|
|
extra["table_base"] = hexAddr(tableBase)
|
|
extra["slice_notes"] = appendNote(extra["slice_notes"], "conditional_array")
|
|
}
|
|
|
|
func (a *analyzer) dispatchRecord(info *classInfo, analysis methodAnalysis, selector int) Record {
|
|
entry, note := a.readDispatchEntry(analysis, selector)
|
|
rec := Record{
|
|
Kind: KindMethod,
|
|
Class: info.Name,
|
|
Bundle: info.Bundle,
|
|
Selector: selector,
|
|
MethodAddr: hexAddr(entry.function),
|
|
MethodSymbol: a.symbolName(entry.function),
|
|
DispatchKind: analysis.kind,
|
|
ScalarInputCount: int64(entry.scalarInputCount),
|
|
ScalarOutputCount: int64(entry.scalarOutputCount),
|
|
StructInputSize: int64(entry.structInputSize),
|
|
StructOutputSize: int64(entry.structOutputSize),
|
|
Flags: entry.flags,
|
|
Resolved: entry.function != 0 && note == "",
|
|
Extra: map[string]string{},
|
|
}
|
|
if note != "" {
|
|
rec.ScalarInputCount = -1
|
|
rec.ScalarOutputCount = -1
|
|
rec.StructInputSize = -1
|
|
rec.StructOutputSize = -1
|
|
rec.Flags = -1
|
|
rec.Extra["slice_notes"] = note
|
|
}
|
|
if entry.checkEntitlement != "" {
|
|
rec.Extra["check_entitlement"] = entry.checkEntitlement
|
|
} else if entry.checkEntitlementAddr != 0 {
|
|
rec.Extra["check_entitlement_addr"] = hexAddr(entry.checkEntitlementAddr)
|
|
}
|
|
return rec
|
|
}
|
|
|
|
func (a *analyzer) legacyDispatchRecords(info *classInfo, analysis methodAnalysis) ([]Record, error) {
|
|
if analysis.stride != dispatchSizeLegacy {
|
|
return nil, fmt.Errorf("IOExternalMethod layout mismatch: stride=%d assumed=%d class=%s", analysis.stride, dispatchSizeLegacy, info.Name)
|
|
}
|
|
if analysis.note == "bounds_unknown" || analysis.count <= 0 {
|
|
return []Record{a.unresolvedMethodRecord(info, analysis, -1, "bounds_unknown")}, nil
|
|
}
|
|
records := make([]Record, 0, analysis.count)
|
|
for selector := 0; selector < analysis.count; selector++ {
|
|
records = append(records, a.legacyDispatchRecord(info, analysis, selector))
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func (a *analyzer) legacyDispatchRecord(info *classInfo, analysis methodAnalysis, selector int) Record {
|
|
entry, note := a.readLegacyDispatchEntry(analysis, selector)
|
|
scalarIn, scalarOut, structIn, structOut, countNote := legacyCountsForFlags(entry.flags, entry.count0, entry.count1)
|
|
note = appendNote(note, countNote)
|
|
rec := Record{
|
|
Kind: KindMethod,
|
|
Class: info.Name,
|
|
Bundle: info.Bundle,
|
|
Selector: selector,
|
|
MethodAddr: hexAddr(entry.function),
|
|
MethodSymbol: a.symbolName(entry.function),
|
|
DispatchKind: DispatchExternalMethodLegacy,
|
|
ScalarInputCount: scalarIn,
|
|
ScalarOutputCount: scalarOut,
|
|
StructInputSize: structIn,
|
|
StructOutputSize: structOut,
|
|
Flags: entry.flags,
|
|
Resolved: entry.function != 0 && note == "",
|
|
Extra: map[string]string{},
|
|
}
|
|
if entry.object != 0 {
|
|
rec.Extra["target_addr"] = hexAddr(entry.object)
|
|
}
|
|
if entry.adjustor != 0 {
|
|
rec.Extra["method_adjustor"] = hexAddr(entry.adjustor)
|
|
}
|
|
if note != "" {
|
|
rec.ScalarInputCount = -1
|
|
rec.ScalarOutputCount = -1
|
|
rec.StructInputSize = -1
|
|
rec.StructOutputSize = -1
|
|
rec.Flags = -1
|
|
rec.Extra["slice_notes"] = note
|
|
}
|
|
return rec
|
|
}
|
|
|
|
func (a *analyzer) readLegacyDispatchEntry(analysis methodAnalysis, selector int) (dispatchEntry, string) {
|
|
addr := analysis.arrayBase + uint64(selector)*analysis.stride
|
|
entry := dispatchEntry{}
|
|
if object, ok := a.scanner.ReadPointerAt(analysis.owner, addr); ok {
|
|
entry.object = object
|
|
}
|
|
function, ok := a.scanner.ReadPointerAt(analysis.owner, addr+8)
|
|
if !ok {
|
|
if raw, err := a.scanner.ReadUint64At(analysis.owner, addr+8); err == nil {
|
|
function = raw
|
|
}
|
|
}
|
|
if function == 0 {
|
|
return entry, "indirect"
|
|
}
|
|
entry.function = function
|
|
if adjustor, err := a.scanner.ReadUint64At(analysis.owner, addr+16); err == nil {
|
|
entry.adjustor = adjustor
|
|
}
|
|
flags, err := a.scanner.ReadUint32At(analysis.owner, addr+24)
|
|
if err != nil {
|
|
return entry, "indirect"
|
|
}
|
|
entry.flags = int64(flags)
|
|
count0, err := a.scanner.ReadUint64At(analysis.owner, addr+32)
|
|
if err != nil {
|
|
return entry, "indirect"
|
|
}
|
|
entry.count0 = count0
|
|
count1, err := a.scanner.ReadUint64At(analysis.owner, addr+40)
|
|
if err != nil {
|
|
return entry, "indirect"
|
|
}
|
|
entry.count1 = count1
|
|
return entry, ""
|
|
}
|
|
|
|
func legacyCountsForFlags(flags int64, count0, count1 uint64) (int64, int64, int64, int64, string) {
|
|
var scalarIn, scalarOut, structIn, structOut int64
|
|
switch uint32(flags) & 0xf {
|
|
case 0:
|
|
scalarIn = legacyCountToInt64(count0)
|
|
scalarOut = legacyCountToInt64(count1)
|
|
case 2:
|
|
scalarIn = legacyCountToInt64(count0)
|
|
structOut = legacyCountToInt64(count1)
|
|
case 3:
|
|
structIn = legacyCountToInt64(count0)
|
|
structOut = legacyCountToInt64(count1)
|
|
case 4:
|
|
scalarIn = legacyCountToInt64(count0)
|
|
structIn = legacyCountToInt64(count1)
|
|
default:
|
|
return -1, -1, -1, -1, "legacy_flags_unknown"
|
|
}
|
|
if scalarIn < 0 || scalarOut < 0 || structIn < 0 || structOut < 0 {
|
|
return -1, -1, -1, -1, "legacy_count_overflow"
|
|
}
|
|
return scalarIn, scalarOut, structIn, structOut, ""
|
|
}
|
|
|
|
func legacyCountToInt64(count uint64) int64 {
|
|
const maxInt64Uint64 = uint64(1<<63 - 1)
|
|
if count > maxInt64Uint64 {
|
|
return -1
|
|
}
|
|
return int64(count)
|
|
}
|
|
|
|
func (a *analyzer) readDispatchEntry(analysis methodAnalysis, selector int) (dispatchEntry, string) {
|
|
addr := analysis.arrayBase + uint64(selector)*analysis.stride
|
|
function, ok := a.scanner.ReadPointerAt(analysis.owner, addr)
|
|
if !ok || function == 0 {
|
|
return dispatchEntry{}, "indirect"
|
|
}
|
|
scalarIn, err := a.scanner.ReadUint32At(analysis.owner, addr+8)
|
|
if err != nil {
|
|
return dispatchEntry{function: function}, "indirect"
|
|
}
|
|
structIn, err := a.scanner.ReadUint32At(analysis.owner, addr+12)
|
|
if err != nil {
|
|
return dispatchEntry{function: function}, "indirect"
|
|
}
|
|
scalarOut, err := a.scanner.ReadUint32At(analysis.owner, addr+16)
|
|
if err != nil {
|
|
return dispatchEntry{function: function}, "indirect"
|
|
}
|
|
structOut, err := a.scanner.ReadUint32At(analysis.owner, addr+20)
|
|
if err != nil {
|
|
return dispatchEntry{function: function}, "indirect"
|
|
}
|
|
entry := dispatchEntry{
|
|
function: function,
|
|
scalarInputCount: scalarIn,
|
|
structInputSize: structIn,
|
|
scalarOutputCount: scalarOut,
|
|
structOutputSize: structOut,
|
|
flags: 0,
|
|
}
|
|
if analysis.kind == DispatchExternalMethod2022 {
|
|
flags, err := a.scanner.ReadUint32At(analysis.owner, addr+24)
|
|
if err != nil {
|
|
return entry, "indirect"
|
|
}
|
|
entry.flags = int64(flags & 0xff)
|
|
if entitlement, ok := a.scanner.ReadPointerAt(analysis.owner, addr+32); ok {
|
|
entry.checkEntitlementAddr = entitlement
|
|
if str, err := a.scanner.ReadCStringAt(analysis.owner, entitlement); err == nil && validLiteralString(str) {
|
|
entry.checkEntitlement = str
|
|
}
|
|
}
|
|
}
|
|
return entry, ""
|
|
}
|
|
|
|
func (a *analyzer) unresolvedMethodRecord(info *classInfo, analysis methodAnalysis, selector int, note string) Record {
|
|
return Record{
|
|
Kind: KindMethod,
|
|
Class: info.Name,
|
|
Bundle: info.Bundle,
|
|
Selector: selector,
|
|
MethodAddr: "0x0",
|
|
MethodSymbol: "",
|
|
DispatchKind: analysis.kind,
|
|
ScalarInputCount: -1,
|
|
ScalarOutputCount: -1,
|
|
StructInputSize: -1,
|
|
StructOutputSize: -1,
|
|
Flags: -1,
|
|
Resolved: false,
|
|
Extra: map[string]string{"slice_notes": note},
|
|
}
|
|
}
|
|
|
|
func (a *analyzer) switchRecords(info *classInfo, analysis methodAnalysis) []Record {
|
|
count := analysis.count
|
|
if count <= 0 || count > maxSelectorCount {
|
|
return []Record{a.unresolvedMethodRecord(info, analysis, -1, "bounds_unknown")}
|
|
}
|
|
records := make([]Record, 0, count)
|
|
for selector := range count {
|
|
extra := map[string]string{"slice_notes": "switch"}
|
|
var methodAddr uint64
|
|
if caseInfo, ok := analysis.switchCases[selector]; ok {
|
|
methodAddr = caseInfo.methodAddr
|
|
if caseInfo.readsStructureInput {
|
|
extra["reads_structure_input"] = "true"
|
|
}
|
|
if caseInfo.readsStructureInputSize {
|
|
extra["reads_structure_input_size"] = "true"
|
|
}
|
|
}
|
|
records = append(records, Record{
|
|
Kind: KindMethod,
|
|
Class: info.Name,
|
|
Bundle: info.Bundle,
|
|
Selector: selector,
|
|
MethodAddr: hexAddr(methodAddr),
|
|
MethodSymbol: a.symbolName(methodAddr),
|
|
DispatchKind: DispatchSwitch,
|
|
ScalarInputCount: -1,
|
|
ScalarOutputCount: -1,
|
|
StructInputSize: -1,
|
|
StructOutputSize: -1,
|
|
Flags: -1,
|
|
Resolved: methodAddr != 0,
|
|
Extra: extra,
|
|
})
|
|
}
|
|
return records
|
|
}
|