mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
416 lines
10 KiB
Go
416 lines
10 KiB
Go
package kernelcache
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"compress/gzip"
|
|
_ "embed"
|
|
"encoding/binary"
|
|
"encoding/gob"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-macho/types"
|
|
"github.com/blacktop/ipsw/internal/download"
|
|
)
|
|
|
|
//go:embed data/syscall.gz
|
|
var syscallData []byte
|
|
|
|
const (
|
|
// SYS_MAXSYSCALL = 557
|
|
syscall1Pattern = "00000000000000000100000000000000"
|
|
syscall2Pattern = "0000000001000400"
|
|
syscallHeader = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sys/syscall.h"
|
|
syscallBetaHeader = "/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sys/syscall.h"
|
|
)
|
|
|
|
type returnType int32
|
|
|
|
const (
|
|
RET_NONE returnType = 0
|
|
RET_INT_T returnType = 1
|
|
RET_UINT_T returnType = 2
|
|
RET_OFF_T returnType = 3
|
|
RET_ADDR_T returnType = 4
|
|
RET_SIZE_T returnType = 5
|
|
RET_SSIZE_T returnType = 6
|
|
RET_UINT64_T returnType = 7
|
|
)
|
|
|
|
func (r returnType) String() string {
|
|
switch r {
|
|
case RET_NONE:
|
|
return "void"
|
|
case RET_INT_T:
|
|
return "int"
|
|
case RET_UINT_T:
|
|
return "uint"
|
|
case RET_OFF_T:
|
|
return "off_t"
|
|
case RET_ADDR_T:
|
|
return "addr_t"
|
|
case RET_SIZE_T:
|
|
return "size_t"
|
|
case RET_SSIZE_T:
|
|
return "ssize_t"
|
|
case RET_UINT64_T:
|
|
return "uint64_t"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
type SyscallData struct {
|
|
Names map[int]string
|
|
Table map[int]sysMaster
|
|
}
|
|
|
|
type sysMaster struct {
|
|
Audit string `json:"audit,omitempty"`
|
|
Files string `json:"files,omitempty"`
|
|
NameAndArgs string `json:"name_and_args,omitempty"`
|
|
}
|
|
|
|
type sysent struct {
|
|
Call uint64 `json:"call,omitempty"` // implementing function
|
|
Munge uint64 `json:"munge,omitempty"` // system call arguments munger for 32-bit process
|
|
ReturnType returnType `json:"return_type,omitempty"` // system call return types
|
|
NArg int16 `json:"n_arg,omitempty"` // number of args
|
|
ArgBytes uint16 `json:"arg_bytes,omitempty"` // Total size of arguments in bytes for 32-bit system calls
|
|
}
|
|
|
|
type Sysent struct {
|
|
Number int `json:"number,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
DBName string `json:"old_name,omitempty"`
|
|
Args []string `json:"args,omitempty"`
|
|
Proto string `json:"proto,omitempty"`
|
|
New bool `json:"new,omitempty"`
|
|
Old bool `json:"old,omitempty"`
|
|
sysent
|
|
}
|
|
|
|
func (s Sysent) String() string {
|
|
var args []string
|
|
var funcStr string
|
|
var extra string
|
|
|
|
if s.Name == "syscall" {
|
|
extra = colorAddr(" // indirect syscall")
|
|
}
|
|
if s.Old {
|
|
s.Name = "nosys"
|
|
extra = colorAddr(fmt.Sprintf(" // old '%s'", s.DBName))
|
|
}
|
|
|
|
// get args
|
|
if len(s.Args) == 0 || (len(s.Args) == 1 && s.Args[0] == RET_NONE.String()) {
|
|
args = append(args, "void")
|
|
} else {
|
|
for _, arg := range s.Args {
|
|
parts := strings.Split(arg, " ")
|
|
if len(parts) == 2 {
|
|
args = append(args, fmt.Sprintf("%s %s", colorType(parts[0]), (parts[1])))
|
|
} else {
|
|
args = append(args, colorType(arg))
|
|
}
|
|
}
|
|
}
|
|
|
|
funcStr = fmt.Sprintf("%s %s(%s);%s", colorType(s.ReturnType.String()), colorName(s.Name), strings.Join(args, ", "), extra)
|
|
|
|
var isNew string
|
|
if s.New && s.Name != "nosys" && s.Name != "enosys" && s.Name != "syscall" {
|
|
isNew = colorBold(" << 👀 [found NEW syscall] 👀 >>")
|
|
}
|
|
|
|
if s.Name == "nosys" || s.Name == "enosys" || s.Name == "syscall" {
|
|
s.Name = colorAddr(s.Name)
|
|
} else {
|
|
s.Name = colorBold(s.Name)
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"%d\t%s: %s\t%s=%#x\t%s=%s\t%s=%d\t%s=%d\t%s%s",
|
|
s.Number,
|
|
colorAddr("%#x", s.Call),
|
|
s.Name,
|
|
colorField("munge"), s.Munge,
|
|
colorField("ret"), s.ReturnType,
|
|
colorField("narg"), s.NArg,
|
|
colorField("bytes"), s.ArgBytes,
|
|
funcStr,
|
|
isNew)
|
|
}
|
|
|
|
func getSyscallData() (*SyscallData, error) {
|
|
var sdata SyscallData
|
|
gzr, err := gzip.NewReader(bytes.NewReader(syscallData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create gzip reader: %v", err)
|
|
}
|
|
defer gzr.Close()
|
|
// Decoding the serialized data
|
|
if err = gob.NewDecoder(gzr).Decode(&sdata); err != nil {
|
|
return nil, fmt.Errorf("failed to decode syscall data; %v", err)
|
|
}
|
|
return &sdata, nil
|
|
}
|
|
|
|
// GetSyscallTable returns a map of system call table as array of sysent structs
|
|
func GetSyscallTable(m *macho.File) ([]Sysent, error) {
|
|
var syscalls []Sysent
|
|
var sysnoAddr uint64
|
|
var maxSyscall int
|
|
|
|
srcdata, err := getSyscallData()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get embedded syscall data: %v", err)
|
|
}
|
|
|
|
sdata, err := getSyscallsData()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get embedded syscall JSON data: %v", err)
|
|
}
|
|
|
|
maxSyscall = len(sdata.BsdSyscalls)
|
|
|
|
if m.FileTOC.FileHeader.Type == types.MH_FILESET {
|
|
var err error
|
|
m, err = m.GetFileSetFileByName("com.apple.kernel")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse fileset entry com.apple.kernel; %v", err)
|
|
}
|
|
}
|
|
|
|
if sec := m.Section("__DATA_CONST", "__const"); sec != nil {
|
|
dat, err := sec.Data()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sysents := make([]sysent, maxSyscall+20)
|
|
|
|
pattern, err := hex.DecodeString(syscall2Pattern)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode pattern: %v", err)
|
|
}
|
|
|
|
if found := bytes.Index(dat, pattern); found > 0 {
|
|
found -= (binary.Size(uint64(0)) * 5) // rewind to beginning of syscall table
|
|
log.WithField("bsd_syscall_table", fmt.Sprintf("%#x", sec.Addr+uint64(found))).Infof("Found")
|
|
|
|
if err := binary.Read(bytes.NewReader(dat[found:]), binary.LittleEndian, &sysents); err != nil {
|
|
return nil, fmt.Errorf("failed to read syscalls sysent data: %v", err)
|
|
}
|
|
|
|
// fixup syscall table call/munge addresses
|
|
for idx, sc := range sysents {
|
|
isNew := false
|
|
if sc.ReturnType > RET_UINT64_T || sc.ReturnType < RET_NONE {
|
|
break
|
|
}
|
|
sysents[idx].Call = m.SlidePointer(sc.Call)
|
|
sysents[idx].Munge = m.SlidePointer(sc.Munge)
|
|
|
|
var name string
|
|
if idx == 0 {
|
|
name = "syscall"
|
|
sysnoAddr = sc.Call
|
|
} else if sc.Call == sysnoAddr {
|
|
name = "nosys"
|
|
} else {
|
|
if sysents[idx].Munge == 0 &&
|
|
sysents[idx].ReturnType == RET_INT_T &&
|
|
sysents[idx].NArg == 0 &&
|
|
sysents[idx].ArgBytes == 0 {
|
|
name = "enosys"
|
|
} else {
|
|
name = unknownTrap
|
|
}
|
|
}
|
|
|
|
// check if we have a name for this syscall in JSON data
|
|
sc, err := sdata.GetBsdSyscallByNumber(idx)
|
|
if err != nil { // not found
|
|
// check if we have a name for this syscall in xnu src
|
|
if n, ok := srcdata.Names[idx]; ok {
|
|
name = n
|
|
} else {
|
|
isNew = true
|
|
}
|
|
} else { // found
|
|
if name == "<unknown>" {
|
|
name = sc.Name
|
|
}
|
|
}
|
|
|
|
syscalls = append(syscalls, Sysent{
|
|
Number: idx,
|
|
Name: name,
|
|
DBName: sc.Name,
|
|
Args: sc.Arguments,
|
|
New: isNew,
|
|
Old: sc.Old,
|
|
sysent: sysents[idx],
|
|
})
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("failed to find begining of syscalls sysent data")
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("failed to find __DATA_CONST __const section in kernel")
|
|
}
|
|
|
|
return syscalls, nil
|
|
}
|
|
|
|
func ParseSyscallFiles(output string) error {
|
|
var err error
|
|
var syscalls SyscallData
|
|
|
|
syscalls.Names, err = ParseSyscallHeader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
syscalls.Table, err = ParseSyscallsMaster()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
buff := new(bytes.Buffer)
|
|
|
|
of, err := os.Create(output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer of.Close()
|
|
|
|
e := gob.NewEncoder(buff)
|
|
|
|
// Encoding the map
|
|
if err := e.Encode(syscalls); err != nil {
|
|
return fmt.Errorf("failed to encode syscalls map to binary: %v", err)
|
|
}
|
|
|
|
gzw := gzip.NewWriter(of)
|
|
defer gzw.Close()
|
|
|
|
if _, err = buff.WriteTo(gzw); err != nil {
|
|
return fmt.Errorf("failed to write syscalls map to gzip file: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ParseSyscallHeader() (map[int]string, error) {
|
|
f, err := os.Open(syscallBetaHeader)
|
|
if err != nil {
|
|
ff, err := os.Open(syscallHeader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open syscall header: %v", err)
|
|
}
|
|
defer ff.Close()
|
|
f = ff
|
|
} else {
|
|
defer f.Close()
|
|
}
|
|
|
|
r := bufio.NewReader(f)
|
|
|
|
syscalls := make(map[int]string)
|
|
|
|
re := regexp.MustCompile(`^#define\s+(?P<name_args>\S+)\s+(?P<num>\d+)$`)
|
|
|
|
for {
|
|
line, _, err := r.ReadLine()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("failed to read line: %v", err)
|
|
}
|
|
for _, match := range re.FindAllStringSubmatch(string(line), -1) {
|
|
num, err := strconv.Atoi(match[2])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert %s to int: %v", match[2], err)
|
|
}
|
|
syscalls[num] = strings.TrimPrefix(match[1], "SYS_")
|
|
}
|
|
}
|
|
|
|
if len(syscalls) == 0 {
|
|
return nil, fmt.Errorf("failed to parse any syscalls from syscalls.master")
|
|
}
|
|
|
|
return syscalls, nil
|
|
}
|
|
|
|
func ParseSyscallsMaster() (map[int]sysMaster, error) {
|
|
xnuTag, err := download.GetLatestTag("xnu", "", false, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query xnu github repo: %v", err)
|
|
}
|
|
|
|
resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/apple-oss-distributions/xnu/%s/bsd/kern/syscalls.master", xnuTag))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get syscalls.master: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
r := bufio.NewReader(resp.Body)
|
|
|
|
re, err := regexp.Compile(`^(?P<num>\d+)\s+(?P<audit>[A-Z0-9_]+)\s+(?P<files>[A-Z]+)\s+(?P<name_args>.+)$`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to compile regex: %v", err)
|
|
}
|
|
|
|
syscalls := make(map[int]sysMaster)
|
|
|
|
for {
|
|
line, _, err := r.ReadLine()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("failed to read line: %v", err)
|
|
}
|
|
for _, match := range re.FindAllStringSubmatch(string(line), -1) {
|
|
num, err := strconv.Atoi(match[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert %s to int: %v", match[1], err)
|
|
}
|
|
if _, ok := syscalls[num]; ok { // already have this syscall
|
|
if match[2] != "AUE_NULL" {
|
|
syscalls[num] = sysMaster{
|
|
Audit: match[2],
|
|
Files: match[3],
|
|
NameAndArgs: strings.Replace(match[4], " NO_SYSCALL_STUB", "", -1),
|
|
}
|
|
}
|
|
} else {
|
|
syscalls[num] = sysMaster{
|
|
Audit: match[2],
|
|
Files: match[3],
|
|
NameAndArgs: strings.Replace(match[4], " NO_SYSCALL_STUB", "", -1),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(syscalls) == 0 {
|
|
return nil, fmt.Errorf("failed to parse any syscalls from syscalls.master")
|
|
}
|
|
|
|
return syscalls, nil
|
|
}
|