parent
462f60a307
commit
077d02d43a
@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If math/rand.Seed() is not called, the generator behaves as if seeded by rand.Seed(1),
|
||||||
|
// so the generator is deterministic.
|
||||||
|
|
||||||
|
// genAesKey generates a 128bit AES Key
|
||||||
|
func genAesKey() []byte {
|
||||||
|
return genRandBytes(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// genAesKey generates a 128bit nonce
|
||||||
|
func genNonce() []byte {
|
||||||
|
return genRandBytes(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// genRandBytes return a random []byte with the length of size
|
||||||
|
func genRandBytes(size int) []byte {
|
||||||
|
buffer := make([]byte, size)
|
||||||
|
rand.Read(buffer) // error is always nil so save to ignore
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// encAes encrypt data with AesKey in AES gcm mode
|
||||||
|
func encAes(data []byte, AesKey []byte) ([]byte, error) {
|
||||||
|
block, _ := aes.NewCipher(AesKey)
|
||||||
|
nonce := genNonce()
|
||||||
|
|
||||||
|
aesgcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := aesgcm.Seal(nil, nonce, data, nil)
|
||||||
|
encData := append(nonce, ciphertext...)
|
||||||
|
return encData, nil
|
||||||
|
}
|
@ -0,0 +1,324 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func obfuscateLiterals(files []*ast.File) []*ast.File {
|
||||||
|
|
||||||
|
pre := func(cursor *astutil.Cursor) bool {
|
||||||
|
t, ok := cursor.Node().(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// constants are not possibly if we want to obfuscate literals, therfore
|
||||||
|
// remove all constants and replace them by variables
|
||||||
|
if t.Tok == token.CONST {
|
||||||
|
t.Tok = token.VAR
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
key = genAesKey()
|
||||||
|
fset = token.NewFileSet()
|
||||||
|
addedToPkg bool // we only want to inject the code and imports once
|
||||||
|
)
|
||||||
|
|
||||||
|
post := func(cursor *astutil.Cursor) bool {
|
||||||
|
|
||||||
|
switch x := cursor.Node().(type) {
|
||||||
|
case *ast.File:
|
||||||
|
if !addedToPkg {
|
||||||
|
x.Decls = append(x.Decls, funcStmt)
|
||||||
|
x.Decls = append(x.Decls, keyStmt(key))
|
||||||
|
|
||||||
|
if x.Imports == nil {
|
||||||
|
var newDecls = []ast.Decl{
|
||||||
|
cryptoAesImportSpec,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, decl := range x.Decls {
|
||||||
|
newDecls = append(newDecls, decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.Decls = newDecls
|
||||||
|
} else {
|
||||||
|
astutil.AddImport(fset, x, "crypto/aes")
|
||||||
|
astutil.AddImport(fset, x, "crypto/cipher")
|
||||||
|
}
|
||||||
|
|
||||||
|
addedToPkg = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case *ast.BasicLit:
|
||||||
|
if !(cursor.Name() == "Values" || cursor.Name() == "Rhs" || cursor.Name() == "Value" || cursor.Name() == "Args") {
|
||||||
|
return true // we don't want to obfuscate imports etc.
|
||||||
|
}
|
||||||
|
if x.Kind != token.STRING {
|
||||||
|
return true // TODO: garble literals other than strings
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.Unquote(x.Value)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[Fatal]: Could not unqote string", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := encAes([]byte(value), key)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
log.Fatalln("[Fatal]: Could not encrypt string:", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.Replace(ciphertextStmt(ciphertext))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
file = astutil.Apply(file, pre, post).(*ast.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// ast definitions for injection
|
||||||
|
var (
|
||||||
|
aesCipherStmt = &ast.AssignStmt{
|
||||||
|
Lhs: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "block"},
|
||||||
|
&ast.Ident{Name: "err"},
|
||||||
|
},
|
||||||
|
Tok: token.DEFINE,
|
||||||
|
Rhs: []ast.Expr{
|
||||||
|
&ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{Name: "aes"},
|
||||||
|
Sel: &ast.Ident{Name: "NewCipher"},
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "garbleKey"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
aesGcmCipherStmt = &ast.AssignStmt{
|
||||||
|
Lhs: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "aesgcm"},
|
||||||
|
&ast.Ident{Name: "err"},
|
||||||
|
},
|
||||||
|
Tok: token.DEFINE,
|
||||||
|
Rhs: []ast.Expr{
|
||||||
|
&ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{Name: "cipher"},
|
||||||
|
Sel: &ast.Ident{Name: "NewGCM"},
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "block"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintextStmt = &ast.AssignStmt{
|
||||||
|
Lhs: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "plaintext"},
|
||||||
|
&ast.Ident{Name: "err"},
|
||||||
|
},
|
||||||
|
Tok: token.DEFINE,
|
||||||
|
Rhs: []ast.Expr{
|
||||||
|
&ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{Name: "aesgcm"},
|
||||||
|
Sel: &ast.Ident{Name: "Open"},
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "nil"},
|
||||||
|
&ast.SliceExpr{
|
||||||
|
X: &ast.Ident{Name: "ciphertext"},
|
||||||
|
High: &ast.BasicLit{
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: "12",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ast.SliceExpr{
|
||||||
|
X: &ast.Ident{Name: "ciphertext"},
|
||||||
|
Low: &ast.BasicLit{
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: "12",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ast.Ident{Name: "nil"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
returnStmt = &ast.ReturnStmt{
|
||||||
|
Results: []ast.Expr{
|
||||||
|
&ast.CallExpr{
|
||||||
|
Fun: &ast.Ident{Name: "string"},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.Ident{Name: "plaintext"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func decErrStmt() *ast.IfStmt {
|
||||||
|
return &ast.IfStmt{
|
||||||
|
Cond: &ast.BinaryExpr{
|
||||||
|
X: &ast.Ident{Name: "err"},
|
||||||
|
Op: token.NEQ,
|
||||||
|
Y: &ast.Ident{Name: "nil"},
|
||||||
|
},
|
||||||
|
Body: &ast.BlockStmt{
|
||||||
|
List: []ast.Stmt{
|
||||||
|
&ast.ExprStmt{
|
||||||
|
X: &ast.CallExpr{
|
||||||
|
Fun: &ast.Ident{Name: "panic"},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.BinaryExpr{
|
||||||
|
X: &ast.BasicLit{
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: `"[garble] Literal couldn't be decrypted: "`,
|
||||||
|
},
|
||||||
|
Op: token.ADD,
|
||||||
|
Y: &ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{Name: "err"},
|
||||||
|
Sel: &ast.Ident{Name: "Error"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcStmt = &ast.FuncDecl{
|
||||||
|
Name: &ast.Ident{Name: "garbleDecrypt"},
|
||||||
|
Type: &ast.FuncType{
|
||||||
|
Params: &ast.FieldList{
|
||||||
|
List: []*ast.Field{
|
||||||
|
{
|
||||||
|
Names: []*ast.Ident{{Name: "ciphertext"}},
|
||||||
|
Type: &ast.ArrayType{
|
||||||
|
Elt: &ast.Ident{Name: "byte"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Results: &ast.FieldList{
|
||||||
|
List: []*ast.Field{
|
||||||
|
{
|
||||||
|
Type: &ast.Ident{Name: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Body: &ast.BlockStmt{
|
||||||
|
List: []ast.Stmt{
|
||||||
|
aesCipherStmt,
|
||||||
|
decErrStmt(),
|
||||||
|
aesGcmCipherStmt,
|
||||||
|
decErrStmt(),
|
||||||
|
plaintextStmt,
|
||||||
|
decErrStmt(),
|
||||||
|
returnStmt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func ciphertextStmt(ciphertext []byte) *ast.CallExpr {
|
||||||
|
ciphertextLit := byteToByteLit(ciphertext)
|
||||||
|
|
||||||
|
return &ast.CallExpr{
|
||||||
|
Fun: &ast.Ident{Name: "garbleDecrypt"},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
ciphertextLit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteToByteLit(buffer []byte) *ast.CallExpr {
|
||||||
|
hexstr := hex.EncodeToString(buffer)
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
b.WriteString(`"`)
|
||||||
|
for i := 0; i < len(hexstr); i += 2 {
|
||||||
|
b.WriteString("\\x" + hexstr[i:i+2])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(`"`)
|
||||||
|
|
||||||
|
return &ast.CallExpr{
|
||||||
|
Fun: &ast.ArrayType{
|
||||||
|
Elt: &ast.Ident{Name: "byte"},
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.BasicLit{
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: b.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyStmt(key []byte) (decl *ast.GenDecl) {
|
||||||
|
keyLit := byteToByteLit(key)
|
||||||
|
|
||||||
|
decl = &ast.GenDecl{
|
||||||
|
Tok: token.VAR,
|
||||||
|
Specs: []ast.Spec{
|
||||||
|
&ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{
|
||||||
|
{Name: "garbleKey"},
|
||||||
|
},
|
||||||
|
Values: []ast.Expr{
|
||||||
|
keyLit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var cryptoAesImportSpec = &ast.GenDecl{
|
||||||
|
Tok: token.IMPORT,
|
||||||
|
Specs: []ast.Spec{
|
||||||
|
&ast.ImportSpec{
|
||||||
|
Path: &ast.BasicLit{
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: `"crypto/aes"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ast.ImportSpec{
|
||||||
|
Path: &ast.BasicLit{
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: `"crypto/cipher"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
garble -literals build main.go
|
||||||
|
exec ./main
|
||||||
|
cmp stdout main.stdout
|
||||||
|
! binsubstr main$exe 'Lorem' 'ipsum' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate'
|
||||||
|
|
||||||
|
[short] stop # checking that the build is reproducible is slow
|
||||||
|
|
||||||
|
# Also check that the binary is reproducible.
|
||||||
|
cp main$exe main_old$exe
|
||||||
|
rm main$exe
|
||||||
|
garble -literals build main.go
|
||||||
|
bincmp main$exe main_old$exe
|
||||||
|
|
||||||
|
-- main.go --
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type strucTest struct {
|
||||||
|
field string
|
||||||
|
anotherfield string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cnst = "Lorem"
|
||||||
|
multiline = `First Line
|
||||||
|
Second Line`
|
||||||
|
)
|
||||||
|
|
||||||
|
var variable = "ipsum"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
empty := ""
|
||||||
|
|
||||||
|
localVar := "dolor"
|
||||||
|
|
||||||
|
reassign := "first assign"
|
||||||
|
reassign = "second assign"
|
||||||
|
|
||||||
|
fmt.Println(cnst)
|
||||||
|
fmt.Println(multiline)
|
||||||
|
fmt.Println(variable)
|
||||||
|
fmt.Println(localVar)
|
||||||
|
fmt.Println(reassign)
|
||||||
|
fmt.Println(empty)
|
||||||
|
|
||||||
|
x := strucTest{
|
||||||
|
field: "to obfuscate",
|
||||||
|
anotherfield: "also obfuscate",
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(x.field)
|
||||||
|
fmt.Println(x.anotherfield)
|
||||||
|
|
||||||
|
testMap := map[string]string{"map key": "map value"}
|
||||||
|
fmt.Println(testMap["map key"])
|
||||||
|
|
||||||
|
fmt.Println("another literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
-- main.stdout --
|
||||||
|
Lorem
|
||||||
|
First Line
|
||||||
|
Second Line
|
||||||
|
ipsum
|
||||||
|
dolor
|
||||||
|
second assign
|
||||||
|
|
||||||
|
to obfuscate
|
||||||
|
also obfuscate
|
||||||
|
map value
|
||||||
|
another literal
|
Loading…
Reference in New Issue