Commit Graph

117 Commits (2e2bd09b5e420455f51f7e1e1cbe46a11a6a7cf3)

Author SHA1 Message Date
Daniel Martí c0731921c2
rewrite go:linkname directives with garbled names (#200)
If code includes a linkname directive pointing at a name in an imported
package, like:

	//go:linkname localName importedpackage.RemoteName
	func localName()

We should rewrite the comment to replace "RemoteName" with its
obfuscated counterpart, if the package in question was obfuscated and
that name was as well.

We already had some code to handle linkname directives, but only to
ensure that "localName" was never obfuscated. This behavior is kept, to
ensure that the directive applies to the right name. In the future, we
could instead rewrite "localName" in the directive, like we do with
"RemoteName".

Add plenty of tests, too. The linkname directive used to be tested in
imports.txt and syntax.txt, but that was hard to maintain as each file
tested different edge cases.

Now that we have build caching, adding one extra testscript file isn't a
big problem anymoree. Add linkname.txt, which is self-explanatory. The
other two scripts also get a bit less complex.

Fixes #197.
4 years ago
Daniel Martí 4f65e6f99c
testdata: avoid messing with the host's mod cache (#198)
It turns out that the modules we include in testdata/mod via
txtar-addmod don't result in the same h1 hash that one gets when using
proxy.golang.org.

As proof:

	$ go clean -modcache && go get -d rsc.io/quote@v1.5.2 && go test -short
	[...]
	--- FAIL: TestScripts/imports (0.06s)
		> garble build -tags buildtag
		go list error: exit status 1: verifying rsc.io/quote@v1.5.2: checksum mismatch
		    downloaded: h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
		    go.sum:     h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=

The added comment explains the situation in detail.

For now, simply work around the issue by not sharing GOMODCACHE with the
host.
4 years ago
Daniel Martí c9deff810b
obfuscate fewer std packages (#196)
Previously, we were never obfuscating runtime and its direct
dependencies. Unfortunately, due to linkname, the runtime package is
actually closely related to dozens of other std packages as well.

Until we can obfuscate the runtime and properly support go:linkname
directives, obfuscating fewer std packages is a better outcome than
breaking and not producing any obfuscated code at all.

The added test case is building runtime/pprof, which used to cause
failures:

	# runtime/pprof
	/go/src/runtime/pprof/label.go:27:21: undefined: context.Context
	/go/src/runtime/pprof/label.go:59:21: undefined: context.Context
	/go/src/runtime/pprof/label.go:93:16: undefined: context.Context
	/go/src/runtime/pprof/label.go:101:20: undefined: context.Context

The net package was also very close to obfuscating properly thanks to
this change, so its test is now run as well. The only other remaining
fix was to not obfuscate fields on cgo types, since those aren't
obfuscated at the moment.

The map is pretty long, but it's only a temporary solution and the
command to obtain the list again is included. Never obfuscating the
entire std library is also an option, but it's a bit unnecessary.

Fixes #134.
4 years ago
lu4p cf290b8e6d
Share data between processes via a shared file. (#192)
Previously garble heavily used env vars to share data between processes.
This also makes it easy to share complex data between processes.

The complexity of main.go is considerably reduced.
4 years ago
Andrew LeFevre 8c03afee95
Use latest Binject/debug version to support importmap directives, fixes #146 (#189)
* Use latest Binject/debug version to support importmap directives in the importcfg file

* Uncomment line in goprivate testscript to test ImportMap

* Fixed issue where a package in specified in importmap would be hashed differently in a package that imported it, due to the mapping of import paths.

Also commented out the 'net' import in the goprivate testscript (again) due to cgo compile errors
4 years ago
Daniel Martí 3e7416ee9e
add test case for ImportMap support (#186)
We also update the "original types importer" to support ImportMap.

The test now gets further along, no longer getting stuck on "path not
found in listed packages". Instead, we get stuck on:

	error parsing importcfg: <...>/importcfg:2: unknown directive "importmap"

This bug has been filed at https://github.com/Binject/debug/issues/17.
Until it's fixed, we can't really proceed on #146, so the net import in
the test file (which triggers this case) is commented out for now.

Updates #146.
4 years ago
Daniel Martí 39372a8c9b testdata: don't let tests rely on rewriting mod files
In Go 1.15, if a dependency is required but not listed in go.mod/go.sum,
it's resolved and added automatically.

This is changing in 1.16. From that release, one will have to explicitly
update the mod files via 'go mod tidy' or 'go get'.

To get ahead of the curve, start using -mod=readonly to get the same
behavior in 1.15, and fix all existing tests.

The only tests that failed were imports.txt and syntax.txt, the only
ones to require other modules. But since we're here, let's add the 'go'
line to all go.mod files as well.
4 years ago
Daniel Martí 4e79bfc01a
warn when a public package imports a private package (#182)
That is, a package that is built without obfuscation imports an
obfuscated package. This will result in confusing compilation error
messages, because the importer can't find the exported names from the
imported package by their non-obfuscated names:

	> ! garble build ./importer
	[stderr]
	# test/main/importer
	importer/importer.go:5:9: undefined: imported.Name
	exit status 2

Instead, detect this bad input case and provide a nice error:

	public package "test/main/importer" can't depend on obfuscated package "test/main/imported" (matched via GOPRIVATE="test/main/imported")

For now, this is by design. It also makes little sense for a public
package to import an obfuscated package in general, because the public
package would have to leak details about the private package's API and
behavior.

While at it, fix a quirk where we thought the unsafe package could be
private. It can't be, because the runtime package is always public and
it imports the runtime package:

	public package "internal/bytealg" can't depend on obfuscated package "unsafe" (matched via GOPRIVATE="*")

Instead of trying to obfuscate "unsafe" and doing nothing, simply add it
to the neverPrivate list, which is also a better name than
"privateBlacklist" (for #169).

Fixes #164.

Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
Andrew LeFevre bca460e36e
Print interfaces/pointers when -tiny is used (#170)
fixes #167
4 years ago
Daniel Martí 7ad246c6fe
testdata: make tiny.txt simpler and faster (#168)
By avoiding the fmt import, we save some work: 'go test -run Script/tiny'
goes down from 0.8s to 0.5s even with a warm build cache.

Since the output changes between runs, we use stderr grep lines instead
of cmp. This way we can also check that the "oh noes" panic is entirely
hidden in the tiny mode.
4 years ago
Daniel Martí 95501f0fcd
testdata: avoid unnecessary deps in init.txt (#166)
We can use println instead of fmt.Println. Similarly, we can avoid
strings.Join by just appending bytes to a []byte.

This is less important now that we have build caching, but it still
helps to do less work overall and link smaller binaries.

Reduces 'go test -run Script/init' from 0.5s to 0.3s on my laptop.

Also, properly format the Go in that file, since the space indentation
wasn't noticed during code review. We might want to enforce gofmt in Go
files within txtar files if this keeps happening.
4 years ago
Andrew LeFevre 6cf1eb6d49
keep init funcs in original order (#165) 4 years ago
Andrew LeFevre 1fc990dcf8
Fix bug where structs would get garbled in some packages but not in others (#161)
* fix bug where structs would get garbled in some packages but not in others

* only check if struct/field was not defined in current package

* fix a related bug when two objects share the same name in the same package and one is garbled but the other one is not

* renamed parameter for clarity
4 years ago
Andrew LeFevre 047aa254e2
properly remove all filenames when -tiny is passed (#160)
* properly remove all filenames when -tiny is passed

* document filename symbol removal
4 years ago
Andrew LeFevre 0e0a9fc594
Allow struct fields to be garbled, fixes #48 (#159)
* Allow struct fields to be garbled, fixes #48

* fix syntax test script

* simplified code according to review
4 years ago
pagran 803c1d9439
Store obfuscated sources in object files (#158)
Now the flag "-debugdir" does not trigger a full recompilation.
Obfuscated source files are saved to object files and are extracted during linking.
4 years ago
Daniel Martí b823b07443 testdata: avoid 'go build' with -short in literals.txt
Use a static main.stderr file, like in the other tests. This means we
don't need to always start the test with a 'go build', and the output is
also obvious by just reading the txtar file.

We can also move generate-literals to a later stage, so that 'go test
-short' needs to do even less work.

'go test -short -run Script/literals' drops from ~0.4s to ~0.2s on my
laptop.

Finally, make the printing of byte lists not use trailing spaces, so
that the txtar file itself doesn't have trailing whitespace in its lines
either.

Fixes #103.
4 years ago
Daniel Martí 1e19d136c7 testdata: make syntax.txt pass when offline
The test intended to use an extra module to be obfuscated, rsc.io/quote,
which we were bundling in the local proxy as well. Unfortunately, the
use of GOPRIVATE also meant that we did not actually fetch the module
from the proxy, and we would instead do a full roundtrip to the internet
to "git clone" the actual upstream repository.

To prevent that roundtrip, instead use a locally replaced module. This
fits the syntax.txt test too, since it's one more edge case that we want
to make sure works well with garble. Since rsc.io/quote is used in
another test, simply make up our own tiny module.

Reduces a 'go test -run Syntax/syntax' run with warm cache from ~5s to
~0.5s, thanks to removing the multiple roundtrips. A warm 'go test' run
still sits at ~6s, since we still need that much CPU time in total.

While at it, fix a staticcheck warning and fix inconsistent indentation
in a couple of tests.
4 years ago
Daniel Martí 2a0ac434fb
initial support for build caching (#142)
As per the discussion in https://github.com/golang/go/issues/41145, it
turns out that we don't need special support for build caching in
-toolexec. We can simply modify the behavior of "[...]/compile -V=full"
and "[...]/link -V=full" so that they include garble's own version and
options in the printed build ID.

The part of the build ID that matters is the last, since it's the
"content ID" which is used to work out whether there is a need to redo
the action (build) or not. Since cmd/go parses the last word in the
output as "buildID=...", we simply add "+garble buildID=_/_/_/${hash}".
The slashes let us imitate a full binary build ID, but we assume that
the other components such as the action ID are not necessary, since the
only reader here is cmd/go and it only consumes the content ID.

The reported content ID includes the tool's original content ID,
garble's own content ID from the built binary, and the garble options
which modify how we obfuscate code. If any of the three changes, we
should use a different build cache key. GOPRIVATE also affects caching,
since a different GOPRIVATE value means that we might have to garble a
different set of packages.

Include tests, which mainly check that 'garble build -v' prints package
lines when we expect to always need to rebuild packages, and that it
prints nothing when we should be reusing the build cache even when the
built binary is missing.

After this change, 'go test' on Go 1.15.2 stabilizes at about 8s on my
machine, whereas it used to be at around 25s before.
4 years ago
pagran ea4a01df87
More correct comments transformation (#152)
More correct comments transformation was implemented.

Added processing of //go:linkname localname [importpath.name] directive, now localname is not renamed. This is safe and does not cause a name disclosure because the functions marked //linkname do not have a name in the resulting binary.

Added cgo directives support

Fixed filename leak protection for cgo

Part of #149
4 years ago
pagran 991fbb042b
avoid potential short name collisions
Fix for bug when a conflict occurred between generated short names
and local variables/functions/types/structs.

The already existing names are collected and if the generated short name
already exists, the package counter is increased until a free name is found.

Part of #149.
4 years ago
pagran 434df0476d
fixed comments cleaning
Added cleanup of the Comment field.
In some cases, the appearance of a comment in a random place
may break the compilation (e.g. cgo and runtime package).

This is safe because the Comment field cannot contain any directives.

Part of #149.
4 years ago
Daniel Martí c3bee46a26 testdata: use the debugdir flag less often
In tiny.txt, we already check line numbers via stderr, so there's no
need to do that via -debugdir.

In syntax.txt, we only really care about what names remain in the
binary, not the names which remain in the source but don't affect the
binary.

These changes are important because -debugdir adds a non-trivial amount
of work, which will impede build caching once that feature lands. We
will likely make -debugdir support build caching eventually, but for
now, this preliminary change will make 'go test' much faster with build
caching.

And of course, the tests get simpler, which is nice.
4 years ago
pagran 00c1d5b11d
add test for Go version checking (#140)
Add tests for Go version checking

Fix panic if go version has invalid format

Fixes: #121
Co-authored-by: Daniel Martí <mvdan@mvdan.cc>
4 years ago
pagran 90fa325da7
Rewrite renaming logic for private names and reduce length of public names (#135)
1. Now private names are obfuscated based on the counter in scope of the package.
2. The length of public names is reduced to 4 bytes.
4 years ago
Andrew LeFevre 0d182a3dbd remove unnecessary data from runtime if -tiny is passed
Fixes #127. Saves an additional ~1-2% binary size in my testing.
4 years ago
Andrew LeFevre d679944408
Strip all filename and position info when -tiny is passed (#128)
Co-authored-by: pagran <pagran@protonmail.com>
Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
Daniel Martí 805c895d59 set up an AUTHORS file to attribute copyright
Many files were missing copyright, so also add a short script to add the
missing lines with the current year, and run it.

The AUTHORS file is also self-explanatory. Contributors can add
themselves there, or we can simply update it from time to time via
git-shortlog.

Since we have two scripts now, set up a directory for them.
4 years ago
lu4p d8d784639f
Validate the user provided seed. (#126)
Also allow base64 seeds without padding.

Fixes #123.
4 years ago
Daniel Martí f764467e9b all: update the docs a bit
Rework the features section in the README, leaving optional features at
the end of the list. Simplify the caveats list, too; the build cache and
exported field/method bits only need one point each. Overall, the
section was far too wordy for little reason.

Also redo the help text a bit. There's now a line to briefly introduce
the tool, as well as a link to the README with all the details. Finally,
the flags have shorter and more consistent help strings.

While at it, remove two unused global vars as spotted by staticcheck.
4 years ago
Andrew LeFevre c8d61c772f
Garble imports and package paths in GOPRIVATE (#116)
Finally, finally this is done. This allows import paths to be obfuscated by modifying
object/archive files and garbling import paths contained within. The bulk of the
code that makes parsing and writing Go object/archive files possible lives at
https://github.com/Binject/debug/tree/master/goobj2, which I wrote as well.

I have tested by garbling and checking for import paths via strings and grep
(in order of difficulty) https://github.com/lu4p/binclude, garble itself, and
https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck.

This only supports object/archive files produced from the Go 1.15 compiler.
The object file format changed at 1.15, and 1.14 and earlier is not supported.

Fixes #13.
4 years ago
Andrew LeFevre 30df5e9bbd
Disable plugin test (#120)
* disable plugin test for now, add note to README

* add link to issue in README

* fix README link to issue
4 years ago
pagran bd46c29380
add blacklist for runtime std packages 4 years ago
lu4p 388ff7d1a4
remove buggy number literal obfuscation
Also remove boolean literal obfuscation.
4 years ago
Daniel Martí d3af58b558 complain when GOPRIVATE matches no packages
This is important, because it would mean that we would obfuscate
nothing. At best, it would be confusing; at worst, it could mislead
the user into thinking the binary is obfuscated.

Fixes #20.
Updates #108.
4 years ago
Daniel Martí 81b4c49702 move the "missing -trimpath" test to the slow group (#106)
This shouldn't break often, so it doesn't need to be covered by 'go test
-short'. Moreover, it's still a relatively expensive step, since we end
up reaching package compilation.
4 years ago
Daniel Martí 511779d8ff testdata: set GOPRIVATE in all but two tests (#104)
basic.txt just builds main.go without a module. Similarly, we leave
imports.txt without a GOPRIVATE, to test the 'go list -m' fallback.

For all other tests, explicitly set GOPRIVATE, to avoid two exec calls -
both 'go env GOPRIVATE' as well as 'go list -m'. Each of those calls
takes in the order of 10ms, so saving ~26 exec calls should easily add
to 200-300ms saved from 'go test -short'.
4 years ago
lu4p ea51e78283 Check that all files use LF line endings in CI 4 years ago
Daniel Martí 75e904f6d4
various minor cleanups and fixes (#99)
Error strings should never be capitalized.

A binsubstr line in one of the tests was duplicate and thus useless.

Remove duplicate or trailing spaces in test scripts.

Finally, add a TODO for an optimization I just spotted.
4 years ago
pagran 2735555ab2
Update filename and add line number obfuscation (#94)
Fixes  #2.

Line numbers are now obfuscated, via `//line` comments.
Filenames are now obfuscated via `//line` comments, instead of changing the actual filename.
New flag `-tiny` to reduce the binary size, at the cost of reversibility.
4 years ago
lu4p 7df14ad860 Fix reflect detection if -literals is passed.
Fixes #93.

The second typecheck lead to the creation of different type objects,
which didn't match the types in the blacklist anymore.

It turns out we don't need the second typecheck,
therfore it is now removed.
4 years ago
Daniel Martí cdac2cd3d6 testdata: avoid fmt in the implement test script
Like other tests, importing fmt results in quite a lot of extra work,
due to the lack of build caching.

In this particular test, we wanted fmt.Println so that T.String would be
called in an indirect way, without defining or referencing Stringer
interface in the main package.

We can do that by rolling our own "tinyfmt" package in a dozen or so
lines of code.

Below is how 'go test -short -vet=off -run Script/implement' is
affected, measured via benchcmd and benchstat:

	name                   old time/op         new time/op         delta
	GoTestScriptImplement          3.67s ± 9%          2.65s ±11%  -27.68%  (p=0.008 n=5+5)

	name                   old user-time/op    new user-time/op    delta
	GoTestScriptImplement          8.18s ± 4%          4.55s ± 9%  -44.35%  (p=0.008 n=5+5)

	name                   old sys-time/op     new sys-time/op     delta
	GoTestScriptImplement          1.27s ±12%          0.71s ±13%  -44.07%  (p=0.008 n=5+5)

	name                   old peak-RSS-bytes  new peak-RSS-bytes  delta
	GoTestScriptImplement          145MB ± 1%          145MB ± 2%     ~     (p=1.000 n=5+5)

All in all, we shave about one full second. It doesn't seem to affect
the total 'go test -short' noticeably, but every little bit counts.
4 years ago
pagran 28adbaa73b
Randomize operator (xor, add, subtract) on all obfuscators (#90)
Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
pagran 2eba744530
Add XorSeed obfuscator (#86)
Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
Daniel Martí aa9767b0d2 add a regression test for #82
The test case we had didn't have a realistic-looking module path with a
dot, so we hadn't noticed the bug with IndexByte.

Fix that. We verified that the new test fails if we undo the fix.
4 years ago
Daniel Martí d0e01478f0 keep build flags when calling 'go list'
Otherwise any build flags like -tags won't be used, and we might easily
end up with errors or incorrect packages.

The common case with -tags is covered by one of the integration test
scripts. On top of that, we add a table-driven unit test to cover all
edge cases, since there are many we can do quickly in a unit test.

Fixes #82.
4 years ago
Daniel Martí 7fe0bf4787 simplify the main code flow somewhat
We don't really care about tools other than "compile" and "link". Stop
trying to keep a complete list.

Use "if err := f(); err != nil {" where it makes sense.

Simplify some declarations, and use a better variable name than "fW".
4 years ago
pagran 9c25f4c2b2
Add xorShuffle obfuscator (#85)
* Refactoring

* Rename Xor2 to XorShuffle
4 years ago
pagran c51e08ef37
Add split obfuscator (#81) 4 years ago
Daniel Martí 65461aabce reuse a single 'go list -json -export -deps' call
Instead of doing a 'go list' call every time we need to fetch a
dependency's export file, we now do a single 'go list' call before the
build begins. With the '-deps' flag, it gives us all the dependency
packages recursively.

We store that data in the gob format in a temporary file, and share it
with the future garble sub-processes via an env var.

This required lazy parsing of flags for the 'build' and 'test' commands,
since now we need to run 'go list' with the same package pattern
arguments.

Fixes #63.
4 years ago