зеркало из https://github.com/golang/scratch.git
357 строки
9.6 KiB
Go
357 строки
9.6 KiB
Go
// Copyright 2020 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// This programs does ad-hoc code signing fo Mach-O files.
|
|
// It tries to do what darwin linker does.
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"debug/macho"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
pageSizeBits = 12
|
|
pageSize = 1 << pageSizeBits
|
|
)
|
|
|
|
const LC_CODE_SIGNATURE = 0x1d
|
|
|
|
const fileHeaderSize64 = 8 * 4
|
|
|
|
const (
|
|
CSMAGIC_REQUIREMENT = 0xfade0c00 // single Requirement blob
|
|
CSMAGIC_REQUIREMENTS = 0xfade0c01 // Requirements vector (internal requirements)
|
|
CSMAGIC_CODEDIRECTORY = 0xfade0c02 // CodeDirectory blob
|
|
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // embedded form of signature data
|
|
CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // multi-arch collection of embedded signatures
|
|
|
|
CSSLOT_CODEDIRECTORY = 0 // slot index for CodeDirectory
|
|
)
|
|
|
|
const (
|
|
kSecCodeSignatureNoHash = 0 // null value
|
|
kSecCodeSignatureHashSHA1 = 1 // SHA-1
|
|
kSecCodeSignatureHashSHA256 = 2 // SHA-256
|
|
kSecCodeSignatureHashSHA256Truncated = 3 // SHA-256 truncated to first 20 bytes
|
|
kSecCodeSignatureHashSHA384 = 4 // SHA-384
|
|
kSecCodeSignatureHashSHA512 = 5 // SHA-512
|
|
)
|
|
|
|
const (
|
|
CS_EXECSEG_MAIN_BINARY = 0x1 // executable segment denotes main binary
|
|
CS_EXECSEG_ALLOW_UNSIGNED = 0x10 // allow unsigned pages (for debugging)
|
|
CS_EXECSEG_DEBUGGER = 0x20 // main binary is debugger
|
|
CS_EXECSEG_JIT = 0x40 // JIT enabled
|
|
CS_EXECSEG_SKIP_LV = 0x80 // skip library validation
|
|
CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // can bless cdhash for execution
|
|
CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // can execute blessed cdhash
|
|
)
|
|
|
|
type Blob struct {
|
|
typ uint32 // type of entry
|
|
offset uint32 // offset of entry
|
|
// data follows
|
|
}
|
|
|
|
func (b *Blob) put(out []byte) []byte {
|
|
out = put32be(out, b.typ)
|
|
out = put32be(out, b.offset)
|
|
return out
|
|
}
|
|
|
|
type SuperBlob struct {
|
|
magic uint32 // magic number
|
|
length uint32 // total length of SuperBlob
|
|
count uint32 // number of index entries following
|
|
// blobs []Blob
|
|
}
|
|
|
|
func (s *SuperBlob) put(out []byte) []byte {
|
|
out = put32be(out, s.magic)
|
|
out = put32be(out, s.length)
|
|
out = put32be(out, s.count)
|
|
return out
|
|
}
|
|
|
|
type CodeDirectory struct {
|
|
magic uint32 // magic number (CSMAGIC_CODEDIRECTORY)
|
|
length uint32 // total length of CodeDirectory blob
|
|
version uint32 // compatibility version
|
|
flags uint32 // setup and mode flags
|
|
hashOffset uint32 // offset of hash slot element at index zero
|
|
identOffset uint32 // offset of identifier string
|
|
nSpecialSlots uint32 // number of special hash slots
|
|
nCodeSlots uint32 // number of ordinary (code) hash slots
|
|
codeLimit uint32 // limit to main image signature range
|
|
hashSize uint8 // size of each hash in bytes
|
|
hashType uint8 // type of hash (cdHashType* constants)
|
|
_pad1 uint8 // unused (must be zero)
|
|
pageSize uint8 // log2(page size in bytes); 0 => infinite
|
|
_pad2 uint32 // unused (must be zero)
|
|
scatterOffset uint32
|
|
teamOffset uint32
|
|
_pad3 uint32
|
|
codeLimit64 uint64
|
|
execSegBase uint64
|
|
execSegLimit uint64
|
|
execSegFlags uint64
|
|
// data follows
|
|
}
|
|
|
|
func (c *CodeDirectory) put(out []byte) []byte {
|
|
out = put32be(out, c.magic)
|
|
out = put32be(out, c.length)
|
|
out = put32be(out, c.version)
|
|
out = put32be(out, c.flags)
|
|
out = put32be(out, c.hashOffset)
|
|
out = put32be(out, c.identOffset)
|
|
out = put32be(out, c.nSpecialSlots)
|
|
out = put32be(out, c.nCodeSlots)
|
|
out = put32be(out, c.codeLimit)
|
|
out = put8(out, c.hashSize)
|
|
out = put8(out, c.hashType)
|
|
out = put8(out, c._pad1)
|
|
out = put8(out, c.pageSize)
|
|
out = put32be(out, c._pad2)
|
|
out = put32be(out, c.scatterOffset)
|
|
out = put32be(out, c.teamOffset)
|
|
out = put32be(out, c._pad3)
|
|
out = put64be(out, c.codeLimit64)
|
|
out = put64be(out, c.execSegBase)
|
|
out = put64be(out, c.execSegLimit)
|
|
out = put64be(out, c.execSegFlags)
|
|
return out
|
|
}
|
|
|
|
type linkeditDataCmd struct {
|
|
cmd uint32
|
|
cmdsize uint32 // sizeof(struct linkedit_data_command)
|
|
dataoff uint32 // file offset of data in __LINKEDIT segment
|
|
datasize uint32 // file size of data in __LINKEDIT segment
|
|
}
|
|
|
|
func (l *linkeditDataCmd) put(out []byte) []byte {
|
|
// load command is little endian
|
|
out = put32le(out, l.cmd)
|
|
out = put32le(out, l.cmdsize)
|
|
out = put32le(out, l.dataoff)
|
|
out = put32le(out, l.datasize)
|
|
return out
|
|
}
|
|
|
|
func get32le(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
|
func put32le(b []byte, x uint32) []byte { binary.LittleEndian.PutUint32(b, x); return b[4:] }
|
|
func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
|
|
func put64le(b []byte, x uint64) []byte { binary.LittleEndian.PutUint64(b, x); return b[8:] }
|
|
func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
|
|
func put8(b []byte, x uint8) []byte { b[0] = x; return b[1:] }
|
|
func puts(b, s []byte) []byte { n := copy(b, s); return b[n:] }
|
|
|
|
// round x up to a multiple of n. n must be a power of 2.
|
|
func roundUp(x, n int) int { return (x + n - 1) &^ (n - 1) }
|
|
|
|
const verbose = false
|
|
|
|
func main() {
|
|
if len(os.Args) != 2 {
|
|
fmt.Println("usage: codesign <binary>")
|
|
os.Exit(1)
|
|
}
|
|
|
|
fname := os.Args[1]
|
|
f, err := os.OpenFile(fname, os.O_RDWR, 0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
mf, err := macho.NewFile(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if mf.Magic != macho.Magic64 {
|
|
panic("not 64-bit")
|
|
}
|
|
if mf.ByteOrder != binary.LittleEndian {
|
|
panic("not little endian")
|
|
}
|
|
|
|
// find existing LC_CODE_SIGNATURE and __LINKEDIT segment
|
|
var sigOff, sigSz, linkeditOff int
|
|
var linkeditSeg, textSeg *macho.Segment
|
|
loadOff := fileHeaderSize64
|
|
for _, l := range mf.Loads {
|
|
data := l.Raw()
|
|
cmd, sz := get32le(data), get32le(data[4:])
|
|
if cmd == LC_CODE_SIGNATURE {
|
|
sigOff = int(get32le(data[8:]))
|
|
sigSz = int(get32le(data[12:]))
|
|
}
|
|
if seg, ok := l.(*macho.Segment); ok {
|
|
switch seg.Name {
|
|
case "__LINKEDIT":
|
|
linkeditSeg = seg
|
|
linkeditOff = loadOff
|
|
case "__TEXT":
|
|
textSeg = seg
|
|
}
|
|
}
|
|
loadOff += int(sz)
|
|
}
|
|
|
|
if sigOff == 0 {
|
|
st, err := f.Stat()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
sigOff = int(st.Size())
|
|
sigOff = roundUp(sigOff, 16) // round up to 16 bytes ???
|
|
err = f.Truncate(int64(sigOff))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// compute sizes
|
|
id := "a.out\000"
|
|
nhashes := (sigOff + pageSize - 1) / pageSize
|
|
idOff := int(unsafe.Sizeof(CodeDirectory{}))
|
|
hashOff := idOff + len(id)
|
|
cdirSz := hashOff + nhashes*sha256.Size
|
|
sz := int(unsafe.Sizeof(SuperBlob{})+unsafe.Sizeof(Blob{})) + cdirSz
|
|
if sigSz != 0 && sz != sigSz {
|
|
println(sz, sigSz)
|
|
panic("LC_CODE_SIGNATURE exists but with a different size. already signed?")
|
|
}
|
|
|
|
if sigSz == 0 { // LC_CODE_SIGNATURE does not exist. Add one.
|
|
csCmdSz := int(unsafe.Sizeof(linkeditDataCmd{}))
|
|
csCmd := linkeditDataCmd{
|
|
cmd: LC_CODE_SIGNATURE,
|
|
cmdsize: uint32(csCmdSz),
|
|
dataoff: uint32(sigOff),
|
|
datasize: uint32(sz),
|
|
}
|
|
if loadOff+csCmdSz > int(mf.Sections[0].Offset) {
|
|
panic("no space for adding LC_CODE_SIGNATURE")
|
|
}
|
|
out := make([]byte, csCmdSz)
|
|
csCmd.put(out)
|
|
_, err = f.WriteAt(out, int64(loadOff))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// fix up header: update Ncmd and Cmdsz
|
|
var tmp [8]byte
|
|
put32le(tmp[:4], mf.FileHeader.Ncmd+1)
|
|
_, err = f.WriteAt(tmp[:4], int64(unsafe.Offsetof(mf.FileHeader.Ncmd)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
put32le(tmp[:4], mf.FileHeader.Cmdsz+uint32(csCmdSz))
|
|
_, err = f.WriteAt(tmp[:4], int64(unsafe.Offsetof(mf.FileHeader.Cmdsz)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// fix up LINKEDIT segment: update Memsz and Filesz
|
|
segSz := sigOff + sz - int(linkeditSeg.Offset)
|
|
put64le(tmp[:8], uint64(roundUp(segSz, 0x4000))) // round up to physical page size
|
|
_, err = f.WriteAt(tmp[:8], int64(linkeditOff)+int64(unsafe.Offsetof(macho.Segment64{}.Memsz)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
put64le(tmp[:8], uint64(segSz))
|
|
_, err = f.WriteAt(tmp[:8], int64(linkeditOff)+int64(unsafe.Offsetof(macho.Segment64{}.Filesz)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// emit blob headers
|
|
sb := SuperBlob{
|
|
magic: CSMAGIC_EMBEDDED_SIGNATURE,
|
|
length: uint32(sz),
|
|
count: 1,
|
|
}
|
|
blob := Blob{
|
|
typ: CSSLOT_CODEDIRECTORY,
|
|
offset: uint32(unsafe.Sizeof(SuperBlob{}) + unsafe.Sizeof(Blob{})),
|
|
}
|
|
cdir := CodeDirectory{
|
|
magic: CSMAGIC_CODEDIRECTORY,
|
|
length: uint32(sz) - uint32(unsafe.Sizeof(SuperBlob{})+unsafe.Sizeof(Blob{})),
|
|
version: 0x20400,
|
|
flags: 0x20002, // adhoc | linkerSigned
|
|
hashOffset: uint32(hashOff),
|
|
identOffset: uint32(idOff),
|
|
nCodeSlots: uint32(nhashes),
|
|
codeLimit: uint32(sigOff),
|
|
hashSize: sha256.Size,
|
|
hashType: kSecCodeSignatureHashSHA256,
|
|
pageSize: uint8(pageSizeBits),
|
|
execSegBase: textSeg.Offset,
|
|
execSegLimit: textSeg.Filesz,
|
|
}
|
|
if mf.Type == macho.TypeExec {
|
|
cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY
|
|
}
|
|
|
|
out := make([]byte, sz)
|
|
outp := out
|
|
|
|
outp = sb.put(outp)
|
|
outp = blob.put(outp)
|
|
outp = cdir.put(outp)
|
|
outp = puts(outp, []byte(id))
|
|
|
|
// emit hashes
|
|
_, err = f.Seek(0, os.SEEK_SET)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var buf [pageSize]byte
|
|
fileOff := 0
|
|
for fileOff < sigOff {
|
|
n, err := io.ReadFull(f, buf[:])
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil && err != io.ErrUnexpectedEOF {
|
|
panic(err)
|
|
}
|
|
if fileOff+n > sigOff {
|
|
n = sigOff - fileOff
|
|
}
|
|
h := sha256.New()
|
|
h.Write(buf[:n])
|
|
b := h.Sum(nil)
|
|
outp = puts(outp, b[:])
|
|
fileOff += n
|
|
}
|
|
|
|
if verbose {
|
|
for i := 0; i < len(out); i += 16 {
|
|
end := i + 16
|
|
if end > len(out) {
|
|
end = len(out)
|
|
}
|
|
fmt.Printf("% x\n", out[i:end])
|
|
}
|
|
}
|
|
|
|
_, err = f.WriteAt(out, int64(sigOff))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|