1// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package symbol_inject
16
17import (
18	"debug/macho"
19	"encoding/binary"
20	"fmt"
21	"io"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"sort"
26	"strings"
27)
28
29func machoSymbolsFromFile(r io.ReaderAt) (*File, error) {
30	machoFile, err := macho.NewFile(r)
31	if err != nil {
32		return nil, cantParseError{err}
33	}
34
35	return extractMachoSymbols(machoFile)
36}
37
38func extractMachoSymbols(machoFile *macho.File) (*File, error) {
39	symbols := machoFile.Symtab.Syms
40	sort.SliceStable(symbols, func(i, j int) bool {
41		if symbols[i].Sect != symbols[j].Sect {
42			return symbols[i].Sect < symbols[j].Sect
43		}
44		return symbols[i].Value < symbols[j].Value
45	})
46
47	file := &File{IsMachoFile: true}
48
49	for _, section := range machoFile.Sections {
50		file.Sections = append(file.Sections, &Section{
51			Name:   section.Name,
52			Addr:   section.Addr,
53			Offset: uint64(section.Offset),
54			Size:   section.Size,
55		})
56	}
57
58	for _, symbol := range symbols {
59		if symbol.Sect > 0 {
60			section := file.Sections[symbol.Sect-1]
61			file.Symbols = append(file.Symbols, &Symbol{
62				// symbols in macho files seem to be prefixed with an underscore
63				Name: strings.TrimPrefix(symbol.Name, "_"),
64				// MachO symbol value is virtual address of the symbol, convert it to offset into the section.
65				Addr: symbol.Value - section.Addr,
66				// MachO symbols don't have size information.
67				Size:    0,
68				Section: section,
69			})
70		}
71	}
72
73	return file, nil
74}
75
76func dumpMachoSymbols(r io.ReaderAt) error {
77	machoFile, err := macho.NewFile(r)
78	if err != nil {
79		return cantParseError{err}
80	}
81
82	fmt.Println("&macho.File{")
83
84	fmt.Println("\tSections: []*macho.Section{")
85	for _, section := range machoFile.Sections {
86		fmt.Printf("\t\t&macho.Section{SectionHeader: %#v},\n", section.SectionHeader)
87	}
88	fmt.Println("\t},")
89
90	fmt.Println("\tSymtab: &macho.Symtab{")
91	fmt.Println("\t\tSyms: []macho.Symbol{")
92	for _, symbol := range machoFile.Symtab.Syms {
93		fmt.Printf("\t\t\t%#v,\n", symbol)
94	}
95	fmt.Println("\t\t},")
96	fmt.Println("\t},")
97
98	fmt.Println("}")
99
100	return nil
101}
102
103func CodeSignMachoFile(path string) error {
104	filename := filepath.Base(path)
105	cmd := exec.Command("/usr/bin/codesign", "--force", "-s", "-", "-i", filename, path)
106	if err := cmd.Run(); err != nil {
107		return err
108	}
109	return modifyCodeSignFlags(path)
110}
111
112const LC_CODE_SIGNATURE = 0x1d
113const CSSLOT_CODEDIRECTORY = 0
114
115// To make codesign not invalidated by stripping, modify codesign flags to 0x20002
116// (adhoc | linkerSigned).
117func modifyCodeSignFlags(path string) error {
118	f, err := os.OpenFile(path, os.O_RDWR, 0)
119	if err != nil {
120		return err
121	}
122	defer f.Close()
123
124	// Step 1: find code signature section.
125	machoFile, err := macho.NewFile(f)
126	if err != nil {
127		return err
128	}
129	var codeSignSectionOffset uint32 = 0
130	var codeSignSectionSize uint32 = 0
131	for _, l := range machoFile.Loads {
132		data := l.Raw()
133		cmd := machoFile.ByteOrder.Uint32(data)
134		if cmd == LC_CODE_SIGNATURE {
135			codeSignSectionOffset = machoFile.ByteOrder.Uint32(data[8:])
136			codeSignSectionSize = machoFile.ByteOrder.Uint32(data[12:])
137		}
138	}
139	if codeSignSectionOffset == 0 {
140		return fmt.Errorf("code signature section not found")
141	}
142
143	data := make([]byte, codeSignSectionSize)
144	_, err = f.ReadAt(data, int64(codeSignSectionOffset))
145	if err != nil {
146		return err
147	}
148
149	// Step 2: get flags offset.
150	blobCount := binary.BigEndian.Uint32(data[8:])
151	off := 12
152	var codeDirectoryOff uint32 = 0
153	for blobCount > 0 {
154		blobType := binary.BigEndian.Uint32(data[off:])
155		if blobType == CSSLOT_CODEDIRECTORY {
156			codeDirectoryOff = binary.BigEndian.Uint32(data[off+4:])
157			break
158		}
159		blobCount--
160		off += 8
161	}
162	if codeDirectoryOff == 0 {
163		return fmt.Errorf("no code directory in code signature section")
164	}
165	flagsOff := codeSignSectionOffset + codeDirectoryOff + 12
166
167	// Step 3: modify flags.
168	flagsData := make([]byte, 4)
169	_, err = f.ReadAt(flagsData, int64(flagsOff))
170	if err != nil {
171		return err
172	}
173	oldFlags := binary.BigEndian.Uint32(flagsData)
174	if oldFlags != 0x2 {
175		return fmt.Errorf("unexpected flags in code signature section: 0x%x", oldFlags)
176	}
177	binary.BigEndian.PutUint32(flagsData, 0x20002)
178	_, err = f.WriteAt(flagsData, int64(flagsOff))
179	return err
180}
181