mirror of
https://github.com/burrowers/garble.git
synced 2025-12-24 12:58:05 +08:00
https://go.dev/cl/466095 lightly refactored the runtime in a way that broke extractNameOff. In particular, the code func cfuncname(f funcInfo) *byte { if !f.valid() || f.nameOff == 0 { return nil } return &f.datap.funcnametab[f.nameOff] } func funcname(f funcInfo) string { return gostringnocopy(cfuncname(f)) } is now simply func funcname(f funcInfo) string { if !f.valid() { return "" } return f.datap.funcName(f.nameOff) } Since extractNameOff looked for the func named cfuncname, and looked for the nameOff selector inside an index expression, all of that code no longer worked properly. It all existed to find the name of the field, nameOff, so that we would automatically adapt if upstream renames it. Unsurprisingly, the code using the field got refactored first. It doesn't seem like the extra code on our part is helping us, and assuming the name of the field works for all Go versions, so do that instead. If upstream does rename the field in the future, the obfuscated Go builds will start failing in an obvious way. If or when that comes to pass, we can change our constant string.
272 lines
7.0 KiB
Go
272 lines
7.0 KiB
Go
// Copyright (c) 2020, The Garble Authors.
|
|
// See LICENSE for licensing information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"strconv"
|
|
"strings"
|
|
|
|
ah "mvdan.cc/garble/internal/asthelper"
|
|
)
|
|
|
|
// updateMagicValue updates hardcoded value of hdr.magic
|
|
// when verifying header in symtab.go
|
|
func updateMagicValue(file *ast.File, magicValue uint32) {
|
|
magicUpdated := false
|
|
|
|
// Find `hdr.magic != 0xfffffff?` in symtab.go and update to random magicValue
|
|
updateMagic := func(node ast.Node) bool {
|
|
binExpr, ok := node.(*ast.BinaryExpr)
|
|
if !ok || binExpr.Op != token.NEQ {
|
|
return true
|
|
}
|
|
|
|
selectorExpr, ok := binExpr.X.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
if ident, ok := selectorExpr.X.(*ast.Ident); !ok || ident.Name != "hdr" {
|
|
return true
|
|
}
|
|
if selectorExpr.Sel.Name != "magic" {
|
|
return true
|
|
}
|
|
|
|
if _, ok := binExpr.Y.(*ast.BasicLit); !ok {
|
|
return true
|
|
}
|
|
binExpr.Y = &ast.BasicLit{
|
|
Kind: token.INT,
|
|
Value: strconv.FormatUint(uint64(magicValue), 10),
|
|
}
|
|
magicUpdated = true
|
|
return false
|
|
}
|
|
|
|
for _, decl := range file.Decls {
|
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
|
if ok && funcDecl.Name.Name == "moduledataverify1" {
|
|
ast.Inspect(funcDecl, updateMagic)
|
|
break
|
|
}
|
|
}
|
|
|
|
if !magicUpdated {
|
|
panic("magic value not updated")
|
|
}
|
|
}
|
|
|
|
// updateEntryOffset adds xor encryption for funcInfo.entryoff
|
|
// Encryption algorithm contains 1 xor and 1 multiply operations and is not cryptographically strong.
|
|
// Its goal, without slowing down program performance (reflection, stacktrace),
|
|
// is to make it difficult to determine relations between function metadata and function itself in a binary file.
|
|
// Difficulty of decryption is based on the difficulty of finding a small (probably inlined) entry() function without obvious patterns.
|
|
func updateEntryOffset(file *ast.File, entryOffKey uint32) {
|
|
// Note that this field could be renamed in future Go versions.
|
|
const nameOffField = "nameOff"
|
|
entryOffUpdated := false
|
|
|
|
// During linker stage we encrypt funcInfo.entryoff using a random number and funcInfo.nameOff,
|
|
// for correct program functioning we must decrypt funcInfo.entryoff at any access to it.
|
|
// In runtime package all references to funcInfo.entryOff are made through one method entry():
|
|
// func (f funcInfo) entry() uintptr {
|
|
// return f.datap.textAddr(f.entryoff)
|
|
// }
|
|
// It is enough to inject decryption into entry() method for program to start working transparently with encrypted value of funcInfo.entryOff:
|
|
// func (f funcInfo) entry() uintptr {
|
|
// return f.datap.textAddr(f.entryoff ^ (uint32(f.nameOff) * <random int>))
|
|
// }
|
|
updateEntryOff := func(node ast.Node) bool {
|
|
callExpr, ok := node.(*ast.CallExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
textSelExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
if !ok || textSelExpr.Sel.Name != "textAddr" {
|
|
return true
|
|
}
|
|
|
|
selExpr, ok := callExpr.Args[0].(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
callExpr.Args[0] = &ast.BinaryExpr{
|
|
X: selExpr,
|
|
Op: token.XOR,
|
|
Y: &ast.ParenExpr{X: &ast.BinaryExpr{
|
|
X: ah.CallExpr(ast.NewIdent("uint32"), &ast.SelectorExpr{
|
|
X: selExpr.X,
|
|
Sel: ast.NewIdent(nameOffField),
|
|
}),
|
|
Op: token.MUL,
|
|
Y: &ast.BasicLit{
|
|
Kind: token.INT,
|
|
Value: strconv.FormatUint(uint64(entryOffKey), 10),
|
|
},
|
|
}},
|
|
}
|
|
entryOffUpdated = true
|
|
return false
|
|
}
|
|
|
|
var entryFunc *ast.FuncDecl
|
|
for _, decl := range file.Decls {
|
|
decl, ok := decl.(*ast.FuncDecl)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if decl.Name.Name == "entry" {
|
|
entryFunc = decl
|
|
break
|
|
}
|
|
}
|
|
if entryFunc == nil {
|
|
panic("entry function not found")
|
|
}
|
|
|
|
ast.Inspect(entryFunc, updateEntryOff)
|
|
if !entryOffUpdated {
|
|
panic("entryOff not found")
|
|
}
|
|
}
|
|
|
|
// stripRuntime removes unnecessary code from the runtime,
|
|
// such as panic and fatal error printing, and code that
|
|
// prints trace/debug info of the runtime.
|
|
func stripRuntime(basename string, file *ast.File) {
|
|
stripPrints := func(node ast.Node) bool {
|
|
call, ok := node.(*ast.CallExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
id, ok := call.Fun.(*ast.Ident)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
switch id.Name {
|
|
case "print", "println":
|
|
id.Name = "hidePrint"
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
for _, decl := range file.Decls {
|
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
switch basename {
|
|
case "error.go":
|
|
// only used in panics
|
|
switch funcDecl.Name.Name {
|
|
case "printany", "printanycustomtype":
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "mgcscavenge.go":
|
|
// used in tracing the scavenger
|
|
if funcDecl.Name.Name == "printScavTrace" {
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "mprof.go":
|
|
// remove all functions that print debug/tracing info
|
|
// of the runtime
|
|
if strings.HasPrefix(funcDecl.Name.Name, "trace") {
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "panic.go":
|
|
// used for printing panics
|
|
switch funcDecl.Name.Name {
|
|
case "preprintpanics", "printpanics":
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "print.go":
|
|
// only used in tracebacks
|
|
if funcDecl.Name.Name == "hexdumpWords" {
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "proc.go":
|
|
// used in tracing the scheduler
|
|
if funcDecl.Name.Name == "schedtrace" {
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "runtime1.go":
|
|
usesEnv := func(node ast.Node) bool {
|
|
seen := false
|
|
ast.Inspect(node, func(node ast.Node) bool {
|
|
ident, ok := node.(*ast.Ident)
|
|
if ok && ident.Name == "gogetenv" {
|
|
seen = true
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return seen
|
|
}
|
|
filenames:
|
|
switch funcDecl.Name.Name {
|
|
case "parsedebugvars":
|
|
// keep defaults for GODEBUG cgocheck and invalidptr,
|
|
// remove code that reads GODEBUG via gogetenv
|
|
for i, stmt := range funcDecl.Body.List {
|
|
if usesEnv(stmt) {
|
|
funcDecl.Body.List = funcDecl.Body.List[:i]
|
|
break filenames
|
|
}
|
|
}
|
|
panic("did not see any gogetenv call in parsedebugvars")
|
|
case "setTraceback":
|
|
// tracebacks are completely hidden, no
|
|
// sense keeping this function
|
|
funcDecl.Body.List = nil
|
|
}
|
|
case "traceback.go":
|
|
// only used for printing tracebacks
|
|
switch funcDecl.Name.Name {
|
|
case "tracebackdefers", "printcreatedby", "printcreatedby1", "traceback", "tracebacktrap", "traceback1", "printAncestorTraceback",
|
|
"printAncestorTracebackFuncInfo", "goroutineheader", "tracebackothers", "tracebackHexdump", "printCgoTraceback":
|
|
funcDecl.Body.List = nil
|
|
case "printOneCgoTraceback":
|
|
funcDecl.Body = ah.BlockStmt(ah.ReturnStmt(ah.IntLit(0)))
|
|
default:
|
|
if strings.HasPrefix(funcDecl.Name.Name, "print") {
|
|
funcDecl.Body.List = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if basename == "print.go" {
|
|
file.Decls = append(file.Decls, hidePrintDecl)
|
|
return
|
|
}
|
|
|
|
// replace all 'print' and 'println' statements in
|
|
// the runtime with an empty func, which will be
|
|
// optimized out by the compiler
|
|
ast.Inspect(file, stripPrints)
|
|
}
|
|
|
|
var hidePrintDecl = &ast.FuncDecl{
|
|
Name: ast.NewIdent("hidePrint"),
|
|
Type: &ast.FuncType{Params: &ast.FieldList{
|
|
List: []*ast.Field{{
|
|
Names: []*ast.Ident{{Name: "args"}},
|
|
Type: &ast.Ellipsis{Elt: &ast.InterfaceType{
|
|
Methods: &ast.FieldList{},
|
|
}},
|
|
}},
|
|
}},
|
|
Body: &ast.BlockStmt{},
|
|
}
|