|
|
|
env GOPRIVATE=test/main
|
|
|
|
|
|
|
|
garble -literals build
|
|
|
|
exec ./main$exe
|
|
|
|
cmp stderr main.stderr
|
|
|
|
|
|
|
|
binsubstr main$exe 'Skip this block' 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
|
obfuscate all variable names, even local ones (#420)
In the added test case, "garble -literals build" would fail:
--- FAIL: TestScripts/literals (8.29s)
testscript.go:397:
> env GOPRIVATE=test/main
> garble -literals build
[stderr]
# test/main
Usz1FmFm.go:1: cannot call non-function string (type int), declared at Usz1FmFm.go:1
Usz1FmFm.go:1: string is not a type
Usz1FmFm.go:1: cannot call non-function append (type int), declared at Usz1FmFm.go:1
That is, for input code such as:
var append int
println("foo")
_ = append
We'd end up with obfuscated code like:
var append int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = append
Which would then break, as the code is shadowing the "append" builtin.
To work around this, always obfuscate variable names, so we end up with:
var mwu1xuNz int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = mwu1xuNz
This change shouldn't make the quality of our obfuscation stronger,
as local variable names do not currently end up in Go binaries.
However, this does make garble more consistent in treating identifiers,
and it completely avoids any issues related to shadowing builtins.
Moreover, this also paves the way for publishing obfuscated source code,
such as #369.
Fixes #417.
3 years ago
|
|
|
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'obfuscated with shadowed builtins' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String'
|
|
|
|
|
|
|
|
[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
|
|
|
|
bincmp main$exe main_old$exe
|
|
|
|
|
|
|
|
# Check that the program works as expected without garble.
|
|
|
|
go build
|
|
|
|
exec ./main$exe
|
|
|
|
cmp stderr main.stderr
|
|
|
|
binsubstr main$exe 'Lorem' 'dolor' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String'
|
|
|
|
|
do not try to obfuscate huge literals (#204)
It's common for asset bundling code generators to produce huge literals,
for example in strings. Our literal obfuscators are meant for relatively
small string-like literals that a human would write, such as URLs, file
paths, and English text.
I ran some quick experiments, and it seems like "garble build -literals"
appears to hang trying to obfuscate literals starting at 5-20KiB. It's
not really hung; it's just doing a lot of busy work obfuscating those
literals. The code it produces is also far from ideal, so it also takes
some time to finally compile.
The generated code also led to crashes. For example, using "garble build
-literals -tiny" on a package containing literals of over a megabyte,
our use of asthelper to remove comments and shuffle line numbers could
run out of stack memory.
This all points in one direction: we never designed "-literals" to deal
with large sizes. Set a source-code-size limit of 2KiB.
We alter the literals.txt test as well, to include a few 128KiB string
literals. Before this fix, "go test" would seemingly hang on that test
for over a minute (I did not wait any longer). With the fix, those large
literals are not obfuscated, so the test ends in its usual 1-3s.
As said in the const comment, I don't believe any of this is a big
problem. Come Go 1.16, most developers should stop using asset-bundling
code generators and use go:embed instead. If we wanted to somehow
obfuscate those, it would be an entirely separate feature.
And, if someone wants to work on obfuscating truly large literals for
any reason, we need good tests and benchmarks to ensure garble does not
consume CPU for minutes or run out of memory.
I also simplified the generate-literals test command. The only argument
that matters to the script is the filename, since it's used later on.
Fixes #178.
4 years ago
|
|
|
# Generate and write random literals into a separate file.
|
|
|
|
# Some of them will be huge; assuming that we don't try to obfuscate them, the
|
|
|
|
# test should generally run in under a second. If this test hangs for over ten
|
|
|
|
# seconds, it means we're trying to obfuscate them.
|
|
|
|
generate-literals extra_literals.go
|
|
|
|
|
make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.
First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:
# go/constant
O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
FkhHJCfm.go:1: undefined: alzLJ5Fd.Word
This edge case is easy to reproduce, so a test case is added to
literals.txt.
The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:
../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )
This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.
Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.
For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.
Fixes #285, hopefully for good this time.
4 years ago
|
|
|
garble -literals -debugdir=debug1 build
|
|
|
|
exec ./main$exe
|
|
|
|
cmp stderr main.stderr
|
|
|
|
|
make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.
First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:
# go/constant
O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
FkhHJCfm.go:1: undefined: alzLJ5Fd.Word
This edge case is easy to reproduce, so a test case is added to
literals.txt.
The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:
../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )
This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.
Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.
For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.
Fixes #285, hopefully for good this time.
4 years ago
|
|
|
# Check obfuscators.
|
|
|
|
|
|
|
|
# Xor obfuscator. Detect a[i] = a[i] (^|-|+) b[i]
|
refactor "current package" with TOOLEXEC_IMPORTPATH (#266)
Now that we've dropped support for Go 1.15.x, we can finally rely on
this environment variable for toolexec calls, present in Go 1.16.
Before, we had hacky ways of trying to figure out the current package's
import path, mostly from the -p flag. The biggest rough edge there was
that, for main packages, that was simply the package name, and not its
full import path.
To work around that, we had a restriction on a single main package, so
we could work around that issue. That restriction is now gone.
The new code is simpler, especially because we can set curPkg in a
single place for all toolexec transform funcs.
Since we can always rely on curPkg not being nil now, we can also start
reusing listedPackage.Private and avoid the majority of repeated calls
to isPrivate. The function is cheap, but still not free.
isPrivate itself can also get simpler. We no longer have to worry about
the "main" edge case. Plus, the sanity check for invalid package paths
is now unnecessary; we only got malformed paths from goobj2, and we now
require exact matches with the ImportPath field from "go list -json".
Another effect of clearing up the "main" edge case is that -debugdir now
uses the right directory for main packages. We also start using
consistent debugdir paths in the tests, for the sake of being easier to
read and maintain.
Finally, note that commandReverse did not need the extra call to "go
list -toolexec", as the "shared" call stored in the cache is enough. We
still call toolexecCmd to get said cache, which should probably be
simplified in a future PR.
While at it, replace the use of the "-std" compiler flag with the
Standard field from "go list -json".
4 years ago
|
|
|
grep '^\s+\w+\[\w+\] = \w+\[\w+\] [\^\-+] \w+$' debug1/test/main/extra_literals.go
|
|
|
|
|
|
|
|
# Swap obfuscator. Detect [...]byte|uint16|uint32|uint64{...}
|
refactor "current package" with TOOLEXEC_IMPORTPATH (#266)
Now that we've dropped support for Go 1.15.x, we can finally rely on
this environment variable for toolexec calls, present in Go 1.16.
Before, we had hacky ways of trying to figure out the current package's
import path, mostly from the -p flag. The biggest rough edge there was
that, for main packages, that was simply the package name, and not its
full import path.
To work around that, we had a restriction on a single main package, so
we could work around that issue. That restriction is now gone.
The new code is simpler, especially because we can set curPkg in a
single place for all toolexec transform funcs.
Since we can always rely on curPkg not being nil now, we can also start
reusing listedPackage.Private and avoid the majority of repeated calls
to isPrivate. The function is cheap, but still not free.
isPrivate itself can also get simpler. We no longer have to worry about
the "main" edge case. Plus, the sanity check for invalid package paths
is now unnecessary; we only got malformed paths from goobj2, and we now
require exact matches with the ImportPath field from "go list -json".
Another effect of clearing up the "main" edge case is that -debugdir now
uses the right directory for main packages. We also start using
consistent debugdir paths in the tests, for the sake of being easier to
read and maintain.
Finally, note that commandReverse did not need the extra call to "go
list -toolexec", as the "shared" call stored in the cache is enough. We
still call toolexecCmd to get said cache, which should probably be
simplified in a future PR.
While at it, replace the use of the "-std" compiler flag with the
Standard field from "go list -json".
4 years ago
|
|
|
grep '^\s+\w+ := \[\.{3}\](byte|uint16|uint32|uint64)\{[0-9\s,]+\}$' debug1/test/main/extra_literals.go
|
|
|
|
|
|
|
|
# Split obfuscator. Detect decryptKey ^= i * counter
|
refactor "current package" with TOOLEXEC_IMPORTPATH (#266)
Now that we've dropped support for Go 1.15.x, we can finally rely on
this environment variable for toolexec calls, present in Go 1.16.
Before, we had hacky ways of trying to figure out the current package's
import path, mostly from the -p flag. The biggest rough edge there was
that, for main packages, that was simply the package name, and not its
full import path.
To work around that, we had a restriction on a single main package, so
we could work around that issue. That restriction is now gone.
The new code is simpler, especially because we can set curPkg in a
single place for all toolexec transform funcs.
Since we can always rely on curPkg not being nil now, we can also start
reusing listedPackage.Private and avoid the majority of repeated calls
to isPrivate. The function is cheap, but still not free.
isPrivate itself can also get simpler. We no longer have to worry about
the "main" edge case. Plus, the sanity check for invalid package paths
is now unnecessary; we only got malformed paths from goobj2, and we now
require exact matches with the ImportPath field from "go list -json".
Another effect of clearing up the "main" edge case is that -debugdir now
uses the right directory for main packages. We also start using
consistent debugdir paths in the tests, for the sake of being easier to
read and maintain.
Finally, note that commandReverse did not need the extra call to "go
list -toolexec", as the "shared" call stored in the cache is enough. We
still call toolexecCmd to get said cache, which should probably be
simplified in a future PR.
While at it, replace the use of the "-std" compiler flag with the
Standard field from "go list -json".
4 years ago
|
|
|
grep '^\s+\w+ \^= \w+ \* \w+$' debug1/test/main/extra_literals.go
|
|
|
|
|
|
|
|
# XorShuffle obfuscator. Detect data = append(data, x (^|-|+) y...).
|
|
|
|
# Note that the line obfuscator adds an inline comment before the call.
|
|
|
|
grep '^\s+\w+ = .*\bappend\(\w+,(\s+\w+\[\d+\][\^\-+]\w+\[\d+\],?)+\)$' debug1/test/main/extra_literals.go
|
|
|
|
|
|
|
|
# XorSeed obfuscator. Detect type decFunc func(byte) decFunc
|
refactor "current package" with TOOLEXEC_IMPORTPATH (#266)
Now that we've dropped support for Go 1.15.x, we can finally rely on
this environment variable for toolexec calls, present in Go 1.16.
Before, we had hacky ways of trying to figure out the current package's
import path, mostly from the -p flag. The biggest rough edge there was
that, for main packages, that was simply the package name, and not its
full import path.
To work around that, we had a restriction on a single main package, so
we could work around that issue. That restriction is now gone.
The new code is simpler, especially because we can set curPkg in a
single place for all toolexec transform funcs.
Since we can always rely on curPkg not being nil now, we can also start
reusing listedPackage.Private and avoid the majority of repeated calls
to isPrivate. The function is cheap, but still not free.
isPrivate itself can also get simpler. We no longer have to worry about
the "main" edge case. Plus, the sanity check for invalid package paths
is now unnecessary; we only got malformed paths from goobj2, and we now
require exact matches with the ImportPath field from "go list -json".
Another effect of clearing up the "main" edge case is that -debugdir now
uses the right directory for main packages. We also start using
consistent debugdir paths in the tests, for the sake of being easier to
read and maintain.
Finally, note that commandReverse did not need the extra call to "go
list -toolexec", as the "shared" call stored in the cache is enough. We
still call toolexecCmd to get said cache, which should probably be
simplified in a future PR.
While at it, replace the use of the "-std" compiler flag with the
Standard field from "go list -json".
4 years ago
|
|
|
grep '^\s+type \w+ func\(byte\) \w+$' debug1/test/main/extra_literals.go
|
|
|
|
|
make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.
First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:
# go/constant
O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
FkhHJCfm.go:1: undefined: alzLJ5Fd.Word
This edge case is easy to reproduce, so a test case is added to
literals.txt.
The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:
../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )
This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.
Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.
For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.
Fixes #285, hopefully for good this time.
4 years ago
|
|
|
# Finally, sanity check that we can build all of std with -literals.
|
|
|
|
# Analogous to goprivate.txt.
|
|
|
|
env GOPRIVATE='*'
|
|
|
|
garble -literals build std
|
|
|
|
|
|
|
|
-- go.mod --
|
|
|
|
module test/main
|
|
|
|
|
|
|
|
go 1.17
|
|
|
|
-- main.go --
|
|
|
|
package main
|
|
|
|
|
make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.
First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:
# go/constant
O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
FkhHJCfm.go:1: undefined: alzLJ5Fd.Word
This edge case is easy to reproduce, so a test case is added to
literals.txt.
The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:
../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )
This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.
Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.
For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.
Fixes #285, hopefully for good this time.
4 years ago
|
|
|
import "test/main/imported"
|
|
|
|
|
|
|
|
type structTest struct {
|
|
|
|
field string
|
|
|
|
anotherField string
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
cnst string = "Lorem"
|
|
|
|
multiline string = `First Line
|
|
|
|
Second Line`
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
i = 1
|
|
|
|
boolean = true
|
|
|
|
|
|
|
|
skip1 = "Skip this block"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
foo = iota
|
|
|
|
bar
|
|
|
|
|
|
|
|
skip2 = "also skip this"
|
|
|
|
)
|
|
|
|
|
|
|
|
// We used to conver this to a var in an attempt of obfuscating the literal.
|
|
|
|
// That would break the iota, which only works inside const declarations.
|
|
|
|
// We only obfuscate constant declarations with string values, anyway.
|
|
|
|
const fooTyped uint64 = 1 << iota
|
|
|
|
|
|
|
|
const arrayLen = 4
|
|
|
|
|
|
|
|
var array [arrayLen]byte
|
|
|
|
|
|
|
|
type typeAlias [arrayLen]byte
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
empty := ""
|
|
|
|
|
|
|
|
localVar := "dolor"
|
|
|
|
|
|
|
|
reassign := "first assign"
|
|
|
|
reassign = "second assign"
|
|
|
|
|
|
|
|
add := "total" + " string"
|
|
|
|
|
|
|
|
println(cnst, boolean)
|
|
|
|
println(multiline, add)
|
|
|
|
println(localVar)
|
|
|
|
println(reassign)
|
|
|
|
println(empty)
|
|
|
|
|
|
|
|
x := structTest{
|
|
|
|
field: "to obfuscate",
|
|
|
|
anotherField: "also obfuscate",
|
|
|
|
}
|
|
|
|
|
|
|
|
lambda := func() string {
|
|
|
|
return "😅 😅"
|
|
|
|
}()
|
|
|
|
println(lambda)
|
|
|
|
|
|
|
|
println(x.field, x.anotherField)
|
|
|
|
|
|
|
|
testMap := map[string]string{"map key": "map value"}
|
|
|
|
testMap["map key"] = "new value"
|
|
|
|
println(testMap["map key"])
|
|
|
|
println("another literal")
|
|
|
|
println(skip1, skip2)
|
|
|
|
println(i, foo, bar)
|
|
|
|
typedTest()
|
|
|
|
constantTest()
|
|
|
|
byteTest()
|
obfuscate all variable names, even local ones (#420)
In the added test case, "garble -literals build" would fail:
--- FAIL: TestScripts/literals (8.29s)
testscript.go:397:
> env GOPRIVATE=test/main
> garble -literals build
[stderr]
# test/main
Usz1FmFm.go:1: cannot call non-function string (type int), declared at Usz1FmFm.go:1
Usz1FmFm.go:1: string is not a type
Usz1FmFm.go:1: cannot call non-function append (type int), declared at Usz1FmFm.go:1
That is, for input code such as:
var append int
println("foo")
_ = append
We'd end up with obfuscated code like:
var append int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = append
Which would then break, as the code is shadowing the "append" builtin.
To work around this, always obfuscate variable names, so we end up with:
var mwu1xuNz int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = mwu1xuNz
This change shouldn't make the quality of our obfuscation stronger,
as local variable names do not currently end up in Go binaries.
However, this does make garble more consistent in treating identifiers,
and it completely avoids any issues related to shadowing builtins.
Moreover, this also paves the way for publishing obfuscated source code,
such as #369.
Fixes #417.
3 years ago
|
|
|
shadowTest()
|
|
|
|
|
|
|
|
strArray := [2]string{"1: literal in", "an array"}
|
|
|
|
println(strArray[0], strArray[1])
|
|
|
|
strSlice := []string{"2: literal in", "a slice"}
|
|
|
|
println(strSlice[0], strSlice[1])
|
|
|
|
emptyStrSlice := []string{""}
|
|
|
|
print(emptyStrSlice[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
type stringType string
|
|
|
|
|
|
|
|
type stringTypeStruct struct {
|
|
|
|
str string
|
|
|
|
strType stringType
|
|
|
|
}
|
|
|
|
|
|
|
|
// typedTest types defined from string broke previously
|
|
|
|
func typedTest() {
|
|
|
|
const skipUntypedConst = "skip untyped const"
|
|
|
|
stringTypeFunc(skipUntypedConst)
|
|
|
|
|
|
|
|
const skipTypedConst stringType = "skip typed const" // skip
|
|
|
|
var skipTypedVar stringType = "skip typed var" // skip
|
|
|
|
|
|
|
|
var skipTypedVarAssign stringType
|
|
|
|
skipTypedVarAssign = "skip typed var assign" // skip
|
|
|
|
|
|
|
|
println(skipTypedConst, skipTypedVar, skipTypedVarAssign)
|
|
|
|
|
|
|
|
y := stringTypeStruct{
|
|
|
|
str: "stringTypeField String", // obfuscate
|
|
|
|
strType: "stringTypeField strType", // skip
|
|
|
|
}
|
|
|
|
println(y.str, y.strType)
|
|
|
|
|
|
|
|
z := func(s stringType) stringType {
|
|
|
|
return "stringType lambda func return" // skip
|
|
|
|
}("lambda call") // skip
|
|
|
|
println(z)
|
|
|
|
|
|
|
|
testMap1 := map[string]stringType{"testMap1 key": "testMap1 value"} // skip
|
|
|
|
testMap1["testMap1 key"] = "testMap1 new value" // skip
|
|
|
|
|
|
|
|
testMap2 := map[stringType]string{"testMap2 key": "testMap2 value"} // skip key
|
|
|
|
testMap2["testMap2 key"] = "testMap2 new value" // skip key
|
|
|
|
|
|
|
|
testMap3 := map[stringType]stringType{"testMap3 key": "testMap3 value"} // skip
|
|
|
|
testMap3["testMap3 key"] = "testMap3 new value" // skip
|
|
|
|
|
|
|
|
println(stringTypeFunc("stringType func param")) // skip
|
|
|
|
}
|
|
|
|
|
|
|
|
// constantTest tests that string constants which need to be constant are skipped
|
|
|
|
func constantTest() {
|
|
|
|
const a = "foo" // skip
|
|
|
|
const length = len(a)
|
|
|
|
|
|
|
|
const b = "bar" // skip
|
|
|
|
type T [len(b)]byte
|
|
|
|
|
|
|
|
const c = "foo" // skip
|
|
|
|
var _ [len(c)]byte
|
|
|
|
|
|
|
|
const d = "foo" // skip
|
|
|
|
var arr = [5]string{len(d): "foo"}
|
|
|
|
for _, elm := range arr {
|
|
|
|
if elm != "" {
|
|
|
|
println(elm)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const e = "foo" // skip
|
|
|
|
var slice = []string{len(e): "foo"}
|
|
|
|
for _, elm := range slice {
|
|
|
|
if elm != "" {
|
|
|
|
println(elm)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const f = "foo" // skip
|
|
|
|
const i = length + len(f)
|
|
|
|
println(length, i)
|
make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.
First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:
# go/constant
O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
FkhHJCfm.go:1: undefined: alzLJ5Fd.Word
This edge case is easy to reproduce, so a test case is added to
literals.txt.
The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:
../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )
This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.
Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.
For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.
Fixes #285, hopefully for good this time.
4 years ago
|
|
|
|
|
|
|
// We should still obfuscate ImportedType here.
|
|
|
|
// Otherwise, the build will fail,
|
|
|
|
// as the name was obfuscated in the original package.
|
|
|
|
const impType = imported.ImportedType(3)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This only tests that we don't break byte slices.
|
|
|
|
// It was manually verified that they do get obfuscated,
|
|
|
|
// The original bytes don't seem to show up in the binary,
|
|
|
|
// meaning that we can't test for them via binsubstr.
|
|
|
|
// We should figure out a way to test for the byte sequences.
|
|
|
|
// For now, we manually tested these when they got added.
|
|
|
|
func byteTest() {
|
|
|
|
a := []byte{12, 13}
|
|
|
|
for _, elm := range a {
|
|
|
|
print(elm, ",")
|
|
|
|
}
|
|
|
|
println()
|
|
|
|
var b = []byte{12, 13}
|
|
|
|
for _, elm := range b {
|
|
|
|
print(elm, ",")
|
|
|
|
}
|
|
|
|
println()
|
|
|
|
|
|
|
|
var c = [2]byte{12, 13}
|
|
|
|
for _, elm := range c {
|
|
|
|
print(elm, ",")
|
|
|
|
}
|
|
|
|
println()
|
|
|
|
|
|
|
|
d := func() [4]byte {
|
|
|
|
return [4]byte{12, 13}
|
|
|
|
}()
|
|
|
|
for _, elm := range d {
|
|
|
|
print(elm, ",")
|
|
|
|
}
|
|
|
|
println()
|
|
|
|
|
|
|
|
e := []byte{0x43, 11_1, 0b01101101, 'p', 'l', 'e', 'x'}
|
|
|
|
println(string(e))
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringTypeFunc(s stringType) stringType {
|
|
|
|
println(s)
|
|
|
|
return "stringType return" // skip
|
|
|
|
}
|
|
|
|
|
obfuscate all variable names, even local ones (#420)
In the added test case, "garble -literals build" would fail:
--- FAIL: TestScripts/literals (8.29s)
testscript.go:397:
> env GOPRIVATE=test/main
> garble -literals build
[stderr]
# test/main
Usz1FmFm.go:1: cannot call non-function string (type int), declared at Usz1FmFm.go:1
Usz1FmFm.go:1: string is not a type
Usz1FmFm.go:1: cannot call non-function append (type int), declared at Usz1FmFm.go:1
That is, for input code such as:
var append int
println("foo")
_ = append
We'd end up with obfuscated code like:
var append int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = append
Which would then break, as the code is shadowing the "append" builtin.
To work around this, always obfuscate variable names, so we end up with:
var mwu1xuNz int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = mwu1xuNz
This change shouldn't make the quality of our obfuscation stronger,
as local variable names do not currently end up in Go binaries.
However, this does make garble more consistent in treating identifiers,
and it completely avoids any issues related to shadowing builtins.
Moreover, this also paves the way for publishing obfuscated source code,
such as #369.
Fixes #417.
3 years ago
|
|
|
|
|
|
|
// obfuscating this broke before
|
|
|
|
const (
|
|
|
|
iota0 uint8 = iota
|
|
|
|
iota1
|
|
|
|
)
|
obfuscate all variable names, even local ones (#420)
In the added test case, "garble -literals build" would fail:
--- FAIL: TestScripts/literals (8.29s)
testscript.go:397:
> env GOPRIVATE=test/main
> garble -literals build
[stderr]
# test/main
Usz1FmFm.go:1: cannot call non-function string (type int), declared at Usz1FmFm.go:1
Usz1FmFm.go:1: string is not a type
Usz1FmFm.go:1: cannot call non-function append (type int), declared at Usz1FmFm.go:1
That is, for input code such as:
var append int
println("foo")
_ = append
We'd end up with obfuscated code like:
var append int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = append
Which would then break, as the code is shadowing the "append" builtin.
To work around this, always obfuscate variable names, so we end up with:
var mwu1xuNz int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = mwu1xuNz
This change shouldn't make the quality of our obfuscation stronger,
as local variable names do not currently end up in Go binaries.
However, this does make garble more consistent in treating identifiers,
and it completely avoids any issues related to shadowing builtins.
Moreover, this also paves the way for publishing obfuscated source code,
such as #369.
Fixes #417.
3 years ago
|
|
|
|
|
|
|
// Our inserted code used to break due to the shadowed builtins.
|
|
|
|
// The name "fnc" is used as a func var name in garble's inserted code.
|
|
|
|
func shadowTest() {
|
|
|
|
{
|
|
|
|
var append, bool, string, fnc int
|
|
|
|
_, _, _, _ = append, bool, string, fnc
|
|
|
|
|
|
|
|
println("obfuscated with shadowed builtins (vars)")
|
|
|
|
}
|
|
|
|
{
|
|
|
|
type append int
|
|
|
|
type bool int
|
|
|
|
type string int
|
|
|
|
type fnc int
|
|
|
|
|
|
|
|
println("obfuscated with shadowed builtins (types)")
|
|
|
|
}
|
|
|
|
}
|
make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.
First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:
# go/constant
O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
FkhHJCfm.go:1: undefined: alzLJ5Fd.Word
This edge case is easy to reproduce, so a test case is added to
literals.txt.
The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:
../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )
This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.
Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.
For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.
Fixes #285, hopefully for good this time.
4 years ago
|
|
|
-- imported/imported.go --
|
|
|
|
package imported
|
|
|
|
|
|
|
|
type ImportedType int
|
set positions when using cursor.Replace
The regular obfuscation process simply modifies some simple nodes, such
as identifiers and strings. In those cases, we modify the nodes
in-place, meaning that their positions remain the same. This hasn't
caused any problems.
Literal obfuscation is trickier. Since we replace one expression with an
entirely different one, we use cursor.Replace. The new expression is
entirely made up on the spot, so it lacks position information.
This was causing problems. For example, in the added test input:
> garble -literals build
[stderr]
# test/main
dgcm4t6w.go:3: misplaced compiler directive
dgcm4t6w.go:4: misplaced compiler directive
dgcm4t6w.go:3: misplaced compiler directive
dgcm4t6w.go:6: misplaced compiler directive
dgcm4t6w.go:7: misplaced compiler directive
dgcm4t6w.go:3: misplaced compiler directive
dgcm4t6w.go:9: misplaced compiler directive
dgcm4t6w.go:3: misplaced compiler directive
dgcm4t6w.go:3: too many errors
The build errors are because we'd move the compiler directives, which
makes the compiler unhappy as they must be directly followed by a
function declaration.
The root cause there seems to be that, since the replacement nodes lack
position information, go/printer would try to estimate its printing
position by adding to the last known position. Since -literals adds
code, this would result in the printer position increasing rapidly, and
potentially printing directive comments earlier than needed.
For now, making the replacement nodes have the same position as the
original node seems to stop go/printer from making this mistake.
It's possible that this workaround won't be bulletproof forever, but it
works well for now, and I don't see a simpler workaround right now.
It would be possible to use fancier mechanisms like go/ast.CommentMap or
dave/dst, but those are a significant amount of added complexity as well.
Fixes #285.
4 years ago
|
|
|
-- directives.go --
|
|
|
|
// If we misplace any of the directives below,
|
|
|
|
// cmd/compile will complain with "misplaced compiler directive".
|
|
|
|
//
|
|
|
|
// We use many literals and functions, mixing different types,
|
|
|
|
// so that it's more likely that bugs will be caught.
|
|
|
|
package main
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str0() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str1() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str2() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str3() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str4() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func arr0() { println(len([...]byte{0, 1, 2})) }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func arr1() { println(len([...]byte{0, 1, 2})) }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func slc0() { println([]byte{0, 1, 2}) }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func slc1() { println([]byte{0, 1, 2}) }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str5() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str6() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str7() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str8() { println("foo") }
|
|
|
|
|
|
|
|
//go:noinline
|
|
|
|
func str9() { println("foo") }
|
|
|
|
|
|
|
|
-- main.stderr --
|
|
|
|
Lorem true
|
|
|
|
First Line
|
|
|
|
Second Line total string
|
|
|
|
dolor
|
|
|
|
second assign
|
|
|
|
|
|
|
|
😅 😅
|
|
|
|
to obfuscate also obfuscate
|
|
|
|
new value
|
|
|
|
another literal
|
|
|
|
Skip this block also skip this
|
|
|
|
1 0 1
|
|
|
|
skip untyped const
|
|
|
|
skip typed const skip typed var skip typed var assign
|
|
|
|
stringTypeField String stringTypeField strType
|
|
|
|
stringType lambda func return
|
|
|
|
stringType func param
|
|
|
|
stringType return
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
3 6
|
|
|
|
12,13,
|
|
|
|
12,13,
|
|
|
|
12,13,
|
|
|
|
12,13,0,0,
|
|
|
|
Complex
|
obfuscate all variable names, even local ones (#420)
In the added test case, "garble -literals build" would fail:
--- FAIL: TestScripts/literals (8.29s)
testscript.go:397:
> env GOPRIVATE=test/main
> garble -literals build
[stderr]
# test/main
Usz1FmFm.go:1: cannot call non-function string (type int), declared at Usz1FmFm.go:1
Usz1FmFm.go:1: string is not a type
Usz1FmFm.go:1: cannot call non-function append (type int), declared at Usz1FmFm.go:1
That is, for input code such as:
var append int
println("foo")
_ = append
We'd end up with obfuscated code like:
var append int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = append
Which would then break, as the code is shadowing the "append" builtin.
To work around this, always obfuscate variable names, so we end up with:
var mwu1xuNz int
println(func() string {
// obfuscation...
x = append(x, ...)
// obfuscation...
return string(x)
})
_ = mwu1xuNz
This change shouldn't make the quality of our obfuscation stronger,
as local variable names do not currently end up in Go binaries.
However, this does make garble more consistent in treating identifiers,
and it completely avoids any issues related to shadowing builtins.
Moreover, this also paves the way for publishing obfuscated source code,
such as #369.
Fixes #417.
3 years ago
|
|
|
obfuscated with shadowed builtins (vars)
|
|
|
|
obfuscated with shadowed builtins (types)
|
|
|
|
1: literal in an array
|
|
|
|
2: literal in a slice
|