mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
529 lines
12 KiB
Go
529 lines
12 KiB
Go
package cpp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"slices"
|
|
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-macho/pkg/fixupchains"
|
|
"github.com/blacktop/go-macho/types"
|
|
)
|
|
|
|
func getMetaHasBoundedTail(data []byte, start int) bool {
|
|
if start < 0 || start+4 > len(data) {
|
|
return false
|
|
}
|
|
limit := min(start+5*4, len(data))
|
|
sawCall := false
|
|
for off := start; off+4 <= limit; off += 4 {
|
|
raw := binary.LittleEndian.Uint32(data[off : off+4])
|
|
switch {
|
|
case raw == 0xd65f03c0 || raw == 0xd65f0fff:
|
|
return true
|
|
case (raw & 0xfc000000) == 0x14000000:
|
|
return true
|
|
case (raw & 0xfc000000) == 0x94000000:
|
|
if sawCall {
|
|
return false
|
|
}
|
|
sawCall = true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Scanner) findClassGetMetaClassCandidates(m *macho.File, metaPtr uint64) []uint64 {
|
|
if metaPtr == 0 {
|
|
return nil
|
|
}
|
|
key := fileAddrKey{file: m, addr: metaPtr}
|
|
if cached, ok := s.getMetaCands[key]; ok {
|
|
return cached
|
|
}
|
|
seen := make(map[uint64]struct{})
|
|
out := make([]uint64, 0, 2)
|
|
addResults := func(results map[uint64][]uint64) {
|
|
for _, addr := range results[metaPtr] {
|
|
if addr == 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[addr]; ok {
|
|
continue
|
|
}
|
|
seen[addr] = struct{}{}
|
|
out = append(out, addr)
|
|
}
|
|
}
|
|
if m != nil {
|
|
addResults(s.scanForGetMetaClassFunctions(m))
|
|
}
|
|
if s.root != nil && s.root != m {
|
|
addResults(s.scanForGetMetaClassFunctions(s.root))
|
|
}
|
|
s.getMetaCands[key] = out
|
|
return out
|
|
}
|
|
|
|
func (s *Scanner) scanForGetMetaClassFunctions(m *macho.File) map[uint64][]uint64 {
|
|
if results, ok := s.getMetaMap[m]; ok {
|
|
return results
|
|
}
|
|
|
|
results := make(map[uint64][]uint64)
|
|
addCandidate := func(metaAddr uint64, funcAddr uint64) {
|
|
if !validKernelPointer(metaAddr) || funcAddr == 0 {
|
|
return
|
|
}
|
|
list := results[metaAddr]
|
|
if slices.Contains(list, funcAddr) {
|
|
return
|
|
}
|
|
results[metaAddr] = append(list, funcAddr)
|
|
}
|
|
|
|
for _, sec := range m.Sections {
|
|
if sec.Name != "__text" {
|
|
continue
|
|
}
|
|
if sec.Seg != "__TEXT_EXEC" && sec.Seg != "__TEXT" {
|
|
continue
|
|
}
|
|
data, err := s.readSectionData(m, sec)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
base := sec.Addr
|
|
for i := 0; i+12 <= len(data); i += 4 {
|
|
instrAddr := base + uint64(i)
|
|
raw0 := binary.LittleEndian.Uint32(data[i : i+4])
|
|
raw1 := binary.LittleEndian.Uint32(data[i+4 : i+8])
|
|
raw2 := binary.LittleEndian.Uint32(data[i+8 : i+12])
|
|
|
|
var metaclassAddr uint64
|
|
funcAddr := instrAddr
|
|
if i >= 4 {
|
|
rawPrev := binary.LittleEndian.Uint32(data[i-4 : i])
|
|
if (rawPrev & 0xfffff01f) == 0xd503201f {
|
|
funcAddr = instrAddr - 4
|
|
}
|
|
}
|
|
|
|
isADRP := (raw0 & 0x9f000000) == 0x90000000
|
|
isADR := (raw0 & 0x9f000000) == 0x10000000
|
|
isLDR := (raw0&0x3f000000) == 0x18000000 || (raw0&0x3f000000) == 0x58000000
|
|
|
|
if isADRP {
|
|
isADD := (raw1 & 0x1f800000) == 0x11000000
|
|
if isADD && getMetaHasBoundedTail(data, i+8) {
|
|
adrpRd := raw0 & 0x1f
|
|
addRn := (raw1 >> 5) & 0x1f
|
|
addRd := raw1 & 0x1f
|
|
if adrpRd == addRn && addRd == 0 {
|
|
immhi := int64((raw0 >> 5) & 0x7ffff)
|
|
immlo := int64((raw0 >> 29) & 0x3)
|
|
offset := (immhi << 2) | immlo
|
|
if offset&(1<<20) != 0 {
|
|
offset |= ^int64((1 << 21) - 1)
|
|
}
|
|
pc := instrAddr &^ 0xfff
|
|
addImm := uint64((raw1 >> 10) & 0xfff)
|
|
metaclassAddr = uint64(int64(pc)+(offset<<12)) + addImm
|
|
}
|
|
}
|
|
}
|
|
|
|
if metaclassAddr == 0 && isADR {
|
|
adrRd := raw0 & 0x1f
|
|
isRET1 := raw1 == 0xd65f03c0 || raw1 == 0xd65f0fff
|
|
isNOP := raw1 == 0xd503201f
|
|
isRET2 := raw2 == 0xd65f03c0 || raw2 == 0xd65f0fff
|
|
if adrRd == 0 && (isRET1 || (isNOP && isRET2) || getMetaHasBoundedTail(data, i+4)) {
|
|
immhi := int64((raw0 >> 5) & 0x7ffff)
|
|
immlo := int64((raw0 >> 29) & 0x3)
|
|
offset := (immhi << 2) | immlo
|
|
if offset&(1<<20) != 0 {
|
|
offset |= ^int64((1 << 21) - 1)
|
|
}
|
|
metaclassAddr = uint64(int64(instrAddr) + offset)
|
|
}
|
|
}
|
|
|
|
if metaclassAddr == 0 && isLDR {
|
|
ldrRd := raw0 & 0x1f
|
|
isRET := raw1 == 0xd65f03c0
|
|
isBR := isBranchRegisterRaw(raw1)
|
|
if ldrRd == 0 && (isRET || isBR) {
|
|
imm19 := int64((raw0 >> 5) & 0x7ffff)
|
|
if imm19&(1<<18) != 0 {
|
|
imm19 |= ^int64((1 << 19) - 1)
|
|
}
|
|
literalAddr := uint64(int64(instrAddr) + (imm19 << 2))
|
|
if ptr, ok := s.resolvePointerAtReason(m, literalAddr, pointerReasonGetMetaLiteral); ok {
|
|
metaclassAddr = ptr
|
|
}
|
|
}
|
|
}
|
|
|
|
addCandidate(metaclassAddr, funcAddr)
|
|
}
|
|
}
|
|
|
|
s.getMetaMap[m] = results
|
|
for _, sec := range m.Sections {
|
|
if sec.Name == "__text" {
|
|
delete(s.sectionData, sectionKey{file: m, addr: sec.Addr})
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (s *Scanner) buildPointerIndex(m *macho.File) map[uint64][]uint64 {
|
|
if s.pointerBuilt[m] {
|
|
return s.pointerIndex[m]
|
|
}
|
|
|
|
if s.root != nil && s.root.FileHeader.Type == types.MH_FILESET {
|
|
s.seedRootFixupIndexes()
|
|
}
|
|
index, fwd := s.ensurePointerMaps(m)
|
|
|
|
if m == nil {
|
|
return index
|
|
}
|
|
addFixupPointers := func() {
|
|
if !m.HasDyldChainedFixups() {
|
|
return
|
|
}
|
|
dcf, err := m.DyldChainedFixups()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if _, err := dcf.Parse(); err != nil {
|
|
return
|
|
}
|
|
vmOwner := m
|
|
if s.root != nil {
|
|
vmOwner = s.root
|
|
}
|
|
for _, start := range dcf.Starts {
|
|
for _, fx := range start.Fixups {
|
|
slotAddr, err := vmOwner.GetVMAddress(fx.Offset())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if s.fileForVMAddr(slotAddr) != m {
|
|
continue
|
|
}
|
|
if _, seen := fwd[slotAddr]; seen {
|
|
continue
|
|
}
|
|
ptr := m.SlidePointer(fx.Raw())
|
|
if !validKernelPointer(ptr) {
|
|
continue
|
|
}
|
|
index[ptr] = append(index[ptr], slotAddr)
|
|
fwd[slotAddr] = ptr
|
|
}
|
|
}
|
|
}
|
|
addSectionPointers := func(sec *types.Section) {
|
|
if sec == nil || sec.Size < 8 {
|
|
return
|
|
}
|
|
data, err := s.readSectionData(m, sec)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ownerDecoder := newSectionPointerDecoder(m, sec, sec)
|
|
|
|
for off := 0; off+8 <= len(data); off += 8 {
|
|
raw := binary.LittleEndian.Uint64(data[off : off+8])
|
|
if raw == 0 {
|
|
continue
|
|
}
|
|
addr := sec.Addr + uint64(off)
|
|
if _, seen := fwd[addr]; seen {
|
|
continue
|
|
}
|
|
if ptr, ok := ownerDecoder.decode(raw, off); ok && ptr != 0 {
|
|
index[ptr] = append(index[ptr], addr)
|
|
fwd[addr] = ptr
|
|
}
|
|
}
|
|
}
|
|
|
|
addFixupPointers()
|
|
for _, sec := range m.Sections {
|
|
if sec == nil || sec.Size < 8 {
|
|
continue
|
|
}
|
|
if sec.Seg == "__TEXT" || sec.Seg == "__TEXT_EXEC" {
|
|
continue
|
|
}
|
|
if sec.Name == "__bss" || sec.Name == "__common" {
|
|
continue
|
|
}
|
|
addSectionPointers(sec)
|
|
}
|
|
|
|
s.pointerIndex[m] = index
|
|
s.forwardPointers[m] = fwd
|
|
s.pointerBuilt[m] = true
|
|
s.stats.pointerIndexEntries += len(index)
|
|
return index
|
|
}
|
|
|
|
func (s *Scanner) seedRootFixupIndexes() {
|
|
if s.rootFixupsSeeded || s.root == nil || !s.root.HasDyldChainedFixups() {
|
|
return
|
|
}
|
|
dcf, err := s.root.DyldChainedFixups()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if _, err := dcf.Parse(); err != nil {
|
|
return
|
|
}
|
|
|
|
for _, start := range dcf.Starts {
|
|
for _, fx := range start.Fixups {
|
|
slotAddr, err := s.root.GetVMAddress(fx.Offset())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
owner := s.fileForVMAddr(slotAddr)
|
|
if owner == nil {
|
|
continue
|
|
}
|
|
ptr := s.root.SlidePointer(fx.Raw())
|
|
if !validKernelPointer(ptr) {
|
|
continue
|
|
}
|
|
index, fwd := s.ensurePointerMaps(owner)
|
|
if _, seen := fwd[slotAddr]; seen {
|
|
continue
|
|
}
|
|
index[ptr] = append(index[ptr], slotAddr)
|
|
fwd[slotAddr] = ptr
|
|
}
|
|
}
|
|
s.rootFixupsSeeded = true
|
|
}
|
|
|
|
func (s *Scanner) ensurePointerMaps(m *macho.File) (map[uint64][]uint64, map[uint64]uint64) {
|
|
index := s.pointerIndex[m]
|
|
if index == nil {
|
|
index = make(map[uint64][]uint64)
|
|
s.pointerIndex[m] = index
|
|
}
|
|
fwd := s.forwardPointers[m]
|
|
if fwd == nil {
|
|
fwd = make(map[uint64]uint64)
|
|
s.forwardPointers[m] = fwd
|
|
}
|
|
return index, fwd
|
|
}
|
|
|
|
type sectionPointerDecoder struct {
|
|
file *macho.File
|
|
dcf *fixupchains.DyldChainedFixups
|
|
offsetBase uint64
|
|
base uint64
|
|
preferred uint64
|
|
}
|
|
|
|
func newSectionPointerDecoder(file *macho.File, targetSec, mappedSec *types.Section) sectionPointerDecoder {
|
|
if file == nil || targetSec == nil || mappedSec == nil {
|
|
return sectionPointerDecoder{}
|
|
}
|
|
offsetBase := uint64(mappedSec.Offset)
|
|
if mappedSec != targetSec {
|
|
offsetBase += targetSec.Addr - mappedSec.Addr
|
|
}
|
|
decoder := sectionPointerDecoder{
|
|
file: file,
|
|
offsetBase: offsetBase,
|
|
base: file.GetBaseAddress(),
|
|
}
|
|
if text := file.Segment("__TEXT"); text != nil {
|
|
decoder.preferred = text.Addr
|
|
}
|
|
if file.HasDyldChainedFixups() {
|
|
if dcf, err := file.DyldChainedFixups(); err == nil {
|
|
decoder.dcf = dcf
|
|
}
|
|
}
|
|
return decoder
|
|
}
|
|
|
|
func (d sectionPointerDecoder) decode(raw uint64, relOff int) (uint64, bool) {
|
|
if d.file == nil || raw == 0 {
|
|
return 0, false
|
|
}
|
|
if d.dcf != nil {
|
|
fileOffset := d.offsetBase + uint64(relOff)
|
|
if target, err := d.dcf.RebaseRaw(fileOffset, raw, d.base); err == nil {
|
|
ptr := target + d.preferred
|
|
if validKernelPointer(ptr) {
|
|
return ptr, true
|
|
}
|
|
}
|
|
}
|
|
ptr := d.file.SlidePointer(raw)
|
|
if validKernelPointer(ptr) {
|
|
return ptr, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (s *Scanner) inferVtableFromPointer(m *macho.File, ptrLoc uint64) (uint64, int, bool) {
|
|
if ptrLoc < 16 {
|
|
return 0, 0, false
|
|
}
|
|
sec := m.FindSectionForVMAddr(ptrLoc)
|
|
if sec == nil || ptrLoc < sec.Addr+16 {
|
|
return 0, 0, false
|
|
}
|
|
data, err := s.readSectionData(m, sec)
|
|
if err != nil {
|
|
return 0, 0, false
|
|
}
|
|
if ptrLoc >= sec.Addr+uint64(len(data)) {
|
|
return 0, 0, false
|
|
}
|
|
|
|
zerosSeen := 0
|
|
var headerAddr uint64
|
|
for step := 1; step <= 256; step++ {
|
|
addr := ptrLoc - uint64(step*8)
|
|
if addr < sec.Addr {
|
|
break
|
|
}
|
|
off := int(addr - sec.Addr)
|
|
if off < 0 || off+8 > len(data) {
|
|
break
|
|
}
|
|
val := binary.LittleEndian.Uint64(data[off : off+8])
|
|
if val == 0 {
|
|
zerosSeen++
|
|
if zerosSeen == 2 {
|
|
headerAddr = addr
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
zerosSeen = 0
|
|
}
|
|
if headerAddr == 0 {
|
|
return 0, 0, false
|
|
}
|
|
|
|
vtableAddr := headerAddr + 16
|
|
if ptrLoc < vtableAddr {
|
|
return 0, 0, false
|
|
}
|
|
offset := ptrLoc - vtableAddr
|
|
if offset%8 != 0 {
|
|
return 0, 0, false
|
|
}
|
|
return vtableAddr, int(offset / 8), true
|
|
}
|
|
|
|
func (s *Scanner) findVtableViaGetMetaClass(m *macho.File, metaVtableAddr uint64, getMetaClassAddr uint64) uint64 {
|
|
index := s.buildPointerIndex(m)
|
|
locs := index[getMetaClassAddr]
|
|
if len(locs) == 0 {
|
|
if fn, err := s.functionForAddr(m, getMetaClassAddr); err == nil {
|
|
locs = index[fn.StartAddr]
|
|
}
|
|
}
|
|
|
|
var best uint64
|
|
ambiguous := false
|
|
for _, loc := range locs {
|
|
vtableAddr, slot, ok := s.inferVtableFromPointer(m, loc)
|
|
if !ok || slot != s.getMetaClassIndex {
|
|
continue
|
|
}
|
|
if vtableAddr == metaVtableAddr || vtableAddr == metaVtableAddr+16 {
|
|
continue
|
|
}
|
|
if !s.validateVtableCandidate(m, vtableAddr, getMetaClassAddr) {
|
|
continue
|
|
}
|
|
if best == 0 {
|
|
best = vtableAddr
|
|
continue
|
|
}
|
|
if vtableAddr != best {
|
|
ambiguous = true
|
|
}
|
|
}
|
|
if ambiguous {
|
|
return 0
|
|
}
|
|
return best
|
|
}
|
|
|
|
func (s *Scanner) validateVtableCandidate(m *macho.File, vtableAddr uint64, getMetaClassAddr uint64) bool {
|
|
ptr, ok := s.resolvePointerAtReason(m, vtableAddr+uint64(s.getMetaClassIndex*8), pointerReasonGetMetaLiteral)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if ptr == getMetaClassAddr {
|
|
return true
|
|
}
|
|
if fn, err := s.functionForAddr(m, getMetaClassAddr); err == nil && ptr == fn.StartAddr {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Scanner) recoverVtableNearMeta(m *macho.File, metaVtableAddr uint64, getMetaCandidates []uint64) uint64 {
|
|
if m == nil || metaVtableAddr == 0 || len(getMetaCandidates) == 0 {
|
|
return 0
|
|
}
|
|
sec := m.FindSectionForVMAddr(metaVtableAddr)
|
|
if sec == nil {
|
|
return 0
|
|
}
|
|
start := max(metaVtableAddr, sec.Addr+16)
|
|
lowerBound := sec.Addr + 16
|
|
upperBound := sec.Addr + sec.Size
|
|
const maxRadius = 0x2000
|
|
|
|
check := func(addr uint64) uint64 {
|
|
if addr < lowerBound || addr+8 > upperBound {
|
|
return 0
|
|
}
|
|
if addr == metaVtableAddr || addr == metaVtableAddr+16 {
|
|
return 0
|
|
}
|
|
if !s.looksLikeVtableStart(m, addr) {
|
|
return 0
|
|
}
|
|
for _, cand := range getMetaCandidates {
|
|
if s.validateVtableCandidate(m, addr, cand) {
|
|
return addr
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
for delta := uint64(8); delta <= maxRadius; delta += 8 {
|
|
if start >= lowerBound+delta {
|
|
if vt := check(start - delta); vt != 0 {
|
|
return vt
|
|
}
|
|
}
|
|
if start+delta < upperBound {
|
|
if vt := check(start + delta); vt != 0 {
|
|
return vt
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|