mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
531 lines
14 KiB
Go
531 lines
14 KiB
Go
package ctf
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"compress/zlib"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/blacktop/go-macho"
|
|
)
|
|
|
|
const CTF_MAGIC = 0xcff1
|
|
|
|
// CTF is the Compact ANSI-C Type Format object
|
|
type CTF struct {
|
|
Header header `json:"header"`
|
|
Types map[int]Type `json:"types,omitempty"`
|
|
Globals []global `json:"globals,omitempty"`
|
|
Functions []function `json:"functions,omitempty"`
|
|
|
|
data []byte
|
|
m *macho.File
|
|
sr *io.SectionReader
|
|
}
|
|
|
|
// Parse parses the CTF data and returns an CTF object pointer
|
|
func Parse(m *macho.File) (*CTF, error) {
|
|
|
|
c := CTF{m: m}
|
|
|
|
sec := m.Section("__CTF", "__ctf")
|
|
if sec == nil {
|
|
return nil, fmt.Errorf("failed to find __CTF.__ctf section")
|
|
}
|
|
|
|
data, err := sec.Data()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read __CTF.__ctf data: %v", err)
|
|
}
|
|
|
|
c.data = data
|
|
c.sr = io.NewSectionReader(bytes.NewReader(data), 0, 1<<63-1)
|
|
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &c.Header.header_t); err != nil {
|
|
return nil, fmt.Errorf("failed to read ctf_header_t: %v", err)
|
|
}
|
|
|
|
if c.Header.Preamble.Magic != CTF_MAGIC {
|
|
return nil, fmt.Errorf("CTF magic %#x is invalid; expected %#x", c.Header.Preamble.Magic, CTF_MAGIC)
|
|
}
|
|
|
|
if c.Header.Preamble.Version < 2 || c.Header.Preamble.Version > 4 {
|
|
return nil, fmt.Errorf("CTF version %d is not supported", c.Header.Preamble.Version)
|
|
}
|
|
|
|
if (c.Header.Preamble.Flags & F_COMPRESS) != 0 {
|
|
zr, err := zlib.NewReader(c.sr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create zlib reader: %v", err)
|
|
}
|
|
var out bytes.Buffer
|
|
io.Copy(&out, zr)
|
|
c.data = out.Bytes()
|
|
c.sr = io.NewSectionReader(bytes.NewReader(out.Bytes()), 0, 1<<63-1)
|
|
}
|
|
|
|
c.Header.ParentLabel = c.getString(c.Header.ParentLabelRef)
|
|
c.Header.ParentName = c.getString(c.Header.ParentNameRef)
|
|
|
|
if err := c.GetDataTypes(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := c.GetDataObjects(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := c.GetFunctions(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &c, nil
|
|
}
|
|
|
|
func (c *CTF) getString(offset uint32) string {
|
|
|
|
// if (CTF_NAME_STID(name) != CTF_STRTAB_0) {
|
|
// return ("<< ??? - name in external strtab >>")
|
|
// }
|
|
|
|
// if (offset >= hp->cth_strlen) {
|
|
// return ("<< ??? - name exceeds strlab len >>")
|
|
// }
|
|
|
|
// if (hp->cth_stroff + offset >= cd->cd_ctflen) {
|
|
// return ("<< ??? - file truncated >>")
|
|
// }
|
|
|
|
offset = c.Header.StrOffset + (offset & MAX_NAME)
|
|
|
|
// fmt.Println(hex.Dump(c.data[offset : offset+100]))
|
|
|
|
c.sr.Seek(int64(offset), io.SeekStart)
|
|
|
|
s, err := bufio.NewReader(c.sr).ReadString('\x00')
|
|
if err != nil {
|
|
return fmt.Sprintf("failed to read string at offset %#x: %v", offset, err)
|
|
}
|
|
|
|
s = strings.Trim(s, "\x00")
|
|
|
|
if len(s) == 0 {
|
|
return "(anon)"
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (c *CTF) getGlobalSymbols() []macho.Symbol {
|
|
var fsyms []macho.Symbol
|
|
for _, sym := range c.m.Symtab.Syms {
|
|
if sym.Type.IsExternalSym() {
|
|
if sym.Type.IsDefinedInSection() {
|
|
if c.m.Sections[sym.Sect-1].Name != "__text" {
|
|
fsyms = append(fsyms, sym)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return fsyms
|
|
}
|
|
|
|
func (c *CTF) getFunctionSymbols() []macho.Symbol {
|
|
var fsyms []macho.Symbol
|
|
for _, sym := range c.m.Symtab.Syms {
|
|
if sym.Type.IsExternalSym() {
|
|
if sym.Type.IsDefinedInSection() {
|
|
if c.m.Sections[sym.Sect-1].Name == "__text" {
|
|
fsyms = append(fsyms, sym)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return fsyms
|
|
}
|
|
|
|
func (c *CTF) lookup(id int) Type {
|
|
if t, ok := c.Types[id]; ok {
|
|
return t
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDataTypes returns all the CTF data type definitions
|
|
func (c *CTF) GetDataTypes() error {
|
|
|
|
c.Types = make(map[int]Type)
|
|
|
|
r := bytes.NewReader(c.data[c.Header.TypeOffset:c.Header.StrOffset])
|
|
|
|
id := 1
|
|
|
|
for {
|
|
var t ctftype
|
|
if err := binary.Read(r, binary.LittleEndian, &t.stype); err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return fmt.Errorf("failed to read data type: %v", err)
|
|
}
|
|
|
|
// log.WithFields(log.Fields{
|
|
// "index": id,
|
|
// "kind": t.Info.Kind().String(),
|
|
// }).Debug("Parsing Type")
|
|
|
|
size := uint64(t.SizeOrType)
|
|
if t.SizeOrType == LSIZE_SENT {
|
|
if err := binary.Read(r, binary.LittleEndian, &t.LSizeHI); err != nil {
|
|
return fmt.Errorf("failed to read ctf_type_t lsizehi : %v", err)
|
|
}
|
|
if err := binary.Read(r, binary.LittleEndian, &t.LSizeLO); err != nil {
|
|
return fmt.Errorf("failed to read ctf_type_t lsizelo : %v", err)
|
|
}
|
|
size = t.LSize()
|
|
}
|
|
|
|
switch t.Info.Kind() {
|
|
case INTEGER:
|
|
var enc intEncoding
|
|
if err := binary.Read(r, binary.LittleEndian, &enc); err != nil {
|
|
return fmt.Errorf("failed to read int encoding: %v", err)
|
|
}
|
|
c.Types[id] = &Integer{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
encoding: enc,
|
|
}
|
|
case FLOAT:
|
|
var enc floatEncoding
|
|
if err := binary.Read(r, binary.LittleEndian, &enc); err != nil {
|
|
return fmt.Errorf("failed to read float encoding: %v", err)
|
|
}
|
|
c.Types[id] = &Float{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
encoding: enc,
|
|
}
|
|
case ARRAY:
|
|
var a array
|
|
if err := binary.Read(r, binary.LittleEndian, &a); err != nil {
|
|
return fmt.Errorf("failed to read array: %v", err)
|
|
}
|
|
c.Types[id] = &Array{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
array: a,
|
|
lookupFn: c.lookup,
|
|
}
|
|
case FUNCTION:
|
|
args := make([]uint32, t.Info.VarLen())
|
|
if c.Header.Preamble.Version < 4 {
|
|
argsV1 := make([]uint16, t.Info.VarLen())
|
|
if err := binary.Read(r, binary.LittleEndian, &argsV1); err != nil {
|
|
return fmt.Errorf("failed to read args: %v", err)
|
|
}
|
|
for idx, arg := range argsV1 {
|
|
args[idx] = uint32(arg)
|
|
}
|
|
} else {
|
|
if err := binary.Read(r, binary.LittleEndian, &args); err != nil {
|
|
return fmt.Errorf("failed to read args: %v", err)
|
|
}
|
|
}
|
|
c.Types[id] = &Function{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
ret: uint(t.SizeOrType),
|
|
args: args,
|
|
lookupFn: c.lookup,
|
|
}
|
|
if (t.Info.VarLen() & 1) != 0 {
|
|
if c.Header.Preamble.Version < 4 {
|
|
r.Seek(int64(binary.Size(uint16(0))), io.SeekCurrent) // alignment
|
|
} else {
|
|
r.Seek(int64(binary.Size(uint32(0))), io.SeekCurrent) // alignment
|
|
}
|
|
}
|
|
case STRUCT:
|
|
s := &Struct{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
size: size,
|
|
lookupFn: c.lookup,
|
|
}
|
|
if size >= LSTRUCT_THRESH {
|
|
lmps := make([]lmember, t.Info.VarLen())
|
|
if err := binary.Read(r, binary.LittleEndian, &lmps); err != nil {
|
|
return fmt.Errorf("failed to read lmembers: %v", err)
|
|
}
|
|
for _, lmp := range lmps {
|
|
s.Fields = append(s.Fields, Member{
|
|
parent: id,
|
|
name: c.getString(uint32(lmp.Name)),
|
|
offset: lmp.Offset(),
|
|
reference: uint(lmp.Type),
|
|
lookupFn: c.lookup,
|
|
})
|
|
}
|
|
} else {
|
|
mps := make([]member, t.Info.VarLen())
|
|
if err := binary.Read(r, binary.LittleEndian, &mps); err != nil {
|
|
return fmt.Errorf("failed to read members: %v", err)
|
|
}
|
|
for _, mp := range mps {
|
|
s.Fields = append(s.Fields, Member{
|
|
parent: id,
|
|
name: c.getString(uint32(mp.Name)),
|
|
offset: uint64(mp.Offset),
|
|
reference: uint(mp.Type),
|
|
lookupFn: c.lookup,
|
|
})
|
|
}
|
|
}
|
|
c.Types[id] = s
|
|
case UNION:
|
|
u := &Union{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
size: size,
|
|
lookupFn: c.lookup,
|
|
}
|
|
if size >= LSTRUCT_THRESH {
|
|
lmps := make([]lmember, t.Info.VarLen())
|
|
if err := binary.Read(r, binary.LittleEndian, &lmps); err != nil {
|
|
return fmt.Errorf("failed to read lmembers: %v", err)
|
|
}
|
|
for _, lmp := range lmps {
|
|
u.Fields = append(u.Fields, Member{
|
|
parent: id,
|
|
name: c.getString(uint32(lmp.Name)),
|
|
offset: lmp.Offset(),
|
|
reference: uint(lmp.Type),
|
|
lookupFn: c.lookup,
|
|
})
|
|
}
|
|
} else {
|
|
mps := make([]member, t.Info.VarLen())
|
|
if err := binary.Read(r, binary.LittleEndian, &mps); err != nil {
|
|
return fmt.Errorf("failed to read members: %v", err)
|
|
}
|
|
for _, mp := range mps {
|
|
u.Fields = append(u.Fields, Member{
|
|
parent: id,
|
|
name: c.getString(uint32(mp.Name)),
|
|
offset: uint64(mp.Offset),
|
|
reference: uint(mp.Type),
|
|
lookupFn: c.lookup,
|
|
})
|
|
}
|
|
}
|
|
c.Types[id] = u
|
|
case ENUM:
|
|
en := &Enum{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
}
|
|
enums := make([]enum, t.Info.VarLen())
|
|
if err := binary.Read(r, binary.LittleEndian, &enums); err != nil {
|
|
return fmt.Errorf("failed to read enums: %v", err)
|
|
}
|
|
for _, e := range enums {
|
|
en.Fields = append(en.Fields, enumField{
|
|
Name: c.getString(uint32(e.Name)),
|
|
Value: e.Value,
|
|
})
|
|
}
|
|
c.Types[id] = en
|
|
case FORWARD:
|
|
c.Types[id] = &Forward{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
}
|
|
case POINTER:
|
|
c.Types[id] = &Pointer{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
reference: uint(t.SizeOrType),
|
|
lookupFn: c.lookup,
|
|
}
|
|
case TYPEDEF:
|
|
c.Types[id] = &Typedef{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
reference: uint(t.SizeOrType),
|
|
lookupFn: c.lookup,
|
|
}
|
|
case VOLATILE:
|
|
c.Types[id] = &Volatile{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
reference: uint(t.SizeOrType),
|
|
lookupFn: c.lookup,
|
|
}
|
|
case CONST:
|
|
c.Types[id] = &Const{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
reference: uint(t.SizeOrType),
|
|
lookupFn: c.lookup,
|
|
}
|
|
case RESTRICT:
|
|
c.Types[id] = &Restrict{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
reference: uint(t.SizeOrType),
|
|
lookupFn: c.lookup,
|
|
}
|
|
case PTRAUTH:
|
|
var ptrauth ptrAuthData
|
|
if err := binary.Read(r, binary.LittleEndian, &ptrauth); err != nil {
|
|
return fmt.Errorf("failed to read ptr auth data: %v", err)
|
|
}
|
|
c.Types[id] = &PtrAuth{
|
|
id: id,
|
|
name: c.getString(uint32(t.Name)),
|
|
info: t.Info,
|
|
data: ptrauth,
|
|
reference: uint(t.SizeOrType),
|
|
lookupFn: c.lookup,
|
|
}
|
|
case UNKNOWN: /* hole in type id space */
|
|
default:
|
|
return fmt.Errorf("unexpected kind %d; possible name: '%s'", t.Info.Kind(), c.getString(uint32(t.Name)))
|
|
}
|
|
|
|
id++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDataObjects returns all the CTF data objects
|
|
func (c *CTF) GetDataObjects() error {
|
|
c.sr.Seek(int64(c.Header.ObjOffset), io.SeekStart)
|
|
|
|
dataSyms := c.getGlobalSymbols()
|
|
|
|
var dataObjects []uint32
|
|
if c.Header.Preamble.Version < 4 {
|
|
dataObjectsV1 := make([]uint16, (c.Header.FuncOffset-c.Header.ObjOffset)/uint32(binary.Size(uint16(0))))
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &dataObjectsV1); err != nil {
|
|
return fmt.Errorf("failed to read data objects: %v", err)
|
|
}
|
|
dataObjects = make([]uint32, len(dataObjectsV1))
|
|
for idx, dobj := range dataObjectsV1 {
|
|
dataObjects[idx] = uint32(dobj)
|
|
}
|
|
} else {
|
|
dataObjects = make([]uint32, (c.Header.FuncOffset-c.Header.ObjOffset)/uint32(binary.Size(uint32(0))))
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &dataObjects); err != nil {
|
|
return fmt.Errorf("failed to read data objects: %v", err)
|
|
}
|
|
}
|
|
|
|
if len(dataSyms) != len(dataObjects) {
|
|
return fmt.Errorf("size of global symbols does NOT match that of the CTF data objects")
|
|
}
|
|
|
|
for idx, sym := range dataSyms {
|
|
c.Globals = append(c.Globals, global{
|
|
Address: sym.Value,
|
|
Name: strings.TrimPrefix(sym.Name, "_"),
|
|
Type: c.lookup(int(dataObjects[idx])),
|
|
Reference: int(dataObjects[idx]),
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFunctions returns all the CTF function definitions
|
|
func (c *CTF) GetFunctions() error {
|
|
|
|
var inf Info
|
|
var ret uint32
|
|
|
|
c.sr.Seek(int64(c.Header.FuncOffset), io.SeekStart)
|
|
|
|
for idx, fsym := range c.getFunctionSymbols() {
|
|
if c.Header.Preamble.Version < 4 {
|
|
var i infoV1
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &i); err != nil {
|
|
return fmt.Errorf("failed to read function info: %v", err)
|
|
}
|
|
inf = i
|
|
} else {
|
|
var i info
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &i); err != nil {
|
|
return fmt.Errorf("failed to read function info: %v", err)
|
|
}
|
|
inf = i
|
|
}
|
|
|
|
if inf.Kind() == UNKNOWN && inf.VarLen() == 0 {
|
|
continue /* skip padding */
|
|
}
|
|
|
|
if inf.Kind() != FUNCTION {
|
|
return fmt.Errorf("[%d] unexpected kind -- %d", idx, inf.Kind())
|
|
}
|
|
|
|
args := make([]uint32, inf.VarLen())
|
|
if c.Header.Preamble.Version < 4 {
|
|
// get return type
|
|
var retV1 uint16
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &retV1); err != nil {
|
|
return fmt.Errorf("failed to read return type: %v", err)
|
|
}
|
|
ret = uint32(retV1)
|
|
// get arg types
|
|
argsV1 := make([]uint16, inf.VarLen())
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &argsV1); err != nil {
|
|
return fmt.Errorf("failed to read args: %v", err)
|
|
}
|
|
for idx, arg := range argsV1 {
|
|
args[idx] = uint32(arg)
|
|
}
|
|
} else {
|
|
// get return type
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &ret); err != nil {
|
|
return fmt.Errorf("failed to read return type: %v", err)
|
|
}
|
|
// get arg types
|
|
if err := binary.Read(c.sr, binary.LittleEndian, &args); err != nil {
|
|
return fmt.Errorf("failed to read args: %v", err)
|
|
}
|
|
}
|
|
|
|
f := function{
|
|
Address: fsym.Value,
|
|
Name: strings.TrimPrefix(fsym.Name, "_"), // Lop off omnipresent underscore to match DWARF convention
|
|
Return: c.lookup(int(ret)),
|
|
}
|
|
|
|
for _, arg := range args {
|
|
f.Arguments = append(f.Arguments, c.lookup(int(arg)))
|
|
}
|
|
|
|
c.Functions = append(c.Functions, f)
|
|
}
|
|
|
|
return nil
|
|
}
|