mirror of
https://github.com/burrowers/garble.git
synced 2026-04-22 23:57:14 +08:00
implement funcInfo.entryoff encryption
At linker stage, we now encrypt funcInfo.entryoff value with a simple algorithm (1 xor + 1 mul). This makes it harder to relate function metadata (e.g. name) to function itself in binary, almost without affecting performance.
This commit is contained in:
@@ -195,19 +195,30 @@ func isUpper(b byte) bool { return 'A' <= b && b <= 'Z' }
|
||||
func toLower(b byte) byte { return b + ('a' - 'A') }
|
||||
func toUpper(b byte) byte { return b - ('a' - 'A') }
|
||||
|
||||
// magicValue returns random magic value based
|
||||
// on user specified seed or the runtime package's GarbleActionID.
|
||||
func magicValue() uint32 {
|
||||
func runtimeHashWithCustomSalt(salt []byte) uint32 {
|
||||
hasher.Reset()
|
||||
if !flagSeed.present() {
|
||||
hasher.Write(cache.ListedPackages["runtime"].GarbleActionID)
|
||||
} else {
|
||||
hasher.Write(flagSeed.bytes)
|
||||
}
|
||||
hasher.Write(salt)
|
||||
sum := hasher.Sum(sumBuffer[:0])
|
||||
return binary.LittleEndian.Uint32(sum)
|
||||
}
|
||||
|
||||
// magicValue returns random magic value based
|
||||
// on user specified seed or the runtime package's GarbleActionID.
|
||||
func magicValue() uint32 {
|
||||
return runtimeHashWithCustomSalt([]byte("magic"))
|
||||
}
|
||||
|
||||
// entryOffKey returns random entry offset key
|
||||
// on user specified seed or the runtime package's GarbleActionID.
|
||||
func entryOffKey() uint32 {
|
||||
return runtimeHashWithCustomSalt([]byte("entryOffKey"))
|
||||
}
|
||||
|
||||
func hashWithPackage(pkg *listedPackage, name string) string {
|
||||
if !flagSeed.present() {
|
||||
return hashWithCustomSalt(pkg.GarbleActionID, name)
|
||||
|
||||
@@ -23,8 +23,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MagicValueEnv = "GARBLE_LINK_MAGIC"
|
||||
TinyEnv = "GARBLE_LINK_TINY"
|
||||
MagicValueEnv = "GARBLE_LINK_MAGIC"
|
||||
TinyEnv = "GARBLE_LINK_TINY"
|
||||
EntryOffKeyEnv = "GARBLE_LINK_ENTRYOFF_KEY"
|
||||
|
||||
cacheDirName = "garble"
|
||||
versionExt = ".version"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
From 99349f6e00859e1bd5c1dd14921b6b9d4aac9966 Mon Sep 17 00:00:00 2001
|
||||
From: pagran <pagran@protonmail.com>
|
||||
Date: Sat, 14 Jan 2023 21:36:16 +0100
|
||||
Subject: [PATCH 3/3] add entryOff encryption
|
||||
|
||||
---
|
||||
cmd/link/internal/ld/pcln.go | 20 ++++++++++++++++++++
|
||||
1 file changed, 20 insertions(+)
|
||||
|
||||
diff --git a/cmd/link/internal/ld/pcln.go b/cmd/link/internal/ld/pcln.go
|
||||
index ab13b15042..8e2fa09434 100644
|
||||
--- a/cmd/link/internal/ld/pcln.go
|
||||
+++ b/cmd/link/internal/ld/pcln.go
|
||||
@@ -790,6 +790,26 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym
|
||||
sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym)-gofuncBase))
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // Moving next code higher is not recommended.
|
||||
+ // Only at the end of the current function no edits between go versions
|
||||
+ garbleEntryOffKeyStr := os.Getenv("GARBLE_LINK_ENTRYOFF_KEY")
|
||||
+ if garbleEntryOffKeyStr == "" {
|
||||
+ panic("[garble] entryOff key must be set")
|
||||
+ }
|
||||
+ var garbleEntryOffKey uint32
|
||||
+ // Use fmt package instead of strconv to avoid importing a new package
|
||||
+ if _, err := fmt.Sscan(garbleEntryOffKeyStr, &garbleEntryOffKey); err != nil {
|
||||
+ panic(fmt.Errorf("[garble] invalid entryOff key %s: %v", garbleEntryOffKeyStr, err))
|
||||
+ }
|
||||
+
|
||||
+ garbleData := sb.Data()
|
||||
+ for _, off := range startLocations {
|
||||
+ entryOff := ctxt.Arch.ByteOrder.Uint32(garbleData[off:])
|
||||
+ nameOff := ctxt.Arch.ByteOrder.Uint32(garbleData[off+4:])
|
||||
+
|
||||
+ sb.SetUint32(ctxt.Arch, int64(off), entryOff^(nameOff*garbleEntryOffKey))
|
||||
+ }
|
||||
}
|
||||
|
||||
// pclntab initializes the pclntab symbol with
|
||||
--
|
||||
2.38.1.windows.1
|
||||
|
||||
@@ -449,6 +449,7 @@ func mainErr(args []string) error {
|
||||
|
||||
executablePath = modifiedLinkPath
|
||||
os.Setenv(linker.MagicValueEnv, strconv.FormatUint(uint64(magicValue()), 10))
|
||||
os.Setenv(linker.EntryOffKeyEnv, strconv.FormatUint(uint64(entryOffKey()), 10))
|
||||
if flagTiny {
|
||||
os.Setenv(linker.TinyEnv, "true")
|
||||
}
|
||||
@@ -948,6 +949,7 @@ func transformCompile(args []string) ([]string, error) {
|
||||
}
|
||||
if basename == "symtab.go" {
|
||||
updateMagicValue(file, magicValue())
|
||||
updateEntryOffset(file, entryOffKey())
|
||||
}
|
||||
}
|
||||
tf.handleDirectives(file.Comments)
|
||||
|
||||
@@ -60,6 +60,113 @@ func updateMagicValue(file *ast.File, magicValue uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var nameOffField string
|
||||
entryOffUpdated := false
|
||||
|
||||
// The funcInfo.nameoff field can be renamed between versions and for more stability
|
||||
// we dynamically extract its name from the cfuncname function.
|
||||
// Note that extractNameOff must be called before updateEntryOff.
|
||||
extractNameOff := func(node ast.Node) bool {
|
||||
indexExpr, ok := node.(*ast.IndexExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
selExpr, ok := indexExpr.Index.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
nameOffField = selExpr.Sel.Name
|
||||
return 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
|
||||
var cfuncnameFunc *ast.FuncDecl
|
||||
|
||||
for _, decl := range file.Decls {
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch funcDecl.Name.Name {
|
||||
case "entry":
|
||||
entryFunc = funcDecl
|
||||
case "cfuncname":
|
||||
cfuncnameFunc = funcDecl
|
||||
}
|
||||
if entryFunc != nil && cfuncnameFunc != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if entryFunc == nil {
|
||||
panic("entry function not found")
|
||||
}
|
||||
if cfuncnameFunc == nil {
|
||||
panic("cfuncname function not found")
|
||||
}
|
||||
|
||||
ast.Inspect(cfuncnameFunc, extractNameOff)
|
||||
if nameOffField == "" {
|
||||
panic("nameOff field 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.
|
||||
|
||||
Reference in New Issue
Block a user