зеркало из https://github.com/golang/scratch.git
cherry/codesign: add an ad-hoc code signing tool
Add an ad-hoc code signing tool, trying to do what darwin linker does. It seems to make the "codesign" command line tool happy. Change-Id: I643368ea5cbcfbb933c83ff9ced46820542f3681 Reviewed-on: https://go-review.googlesource.com/c/scratch/+/271866 Trust: Cherry Zhang <cherryyz@google.com> Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
Родитель
98e28abad7
Коммит
7c13a5e659
|
@ -0,0 +1,356 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче