support obfuscating the runtime package
mvdan opened this issue · 6 comments
We can't obfuscate a package if it's imported by a non-obfuscated package. Obfuscating the runtime package currently fails, so small chunk of std can't be obfuscated even with GOPRIVATE=*
:
$ go list -deps runtime
internal/cpu
unsafe
internal/bytealg
runtime/internal/atomic
runtime/internal/sys
runtime/internal/math
runtime
The main reason one might have to obfuscate runtime
is that, as a relatively large amount of code, it can tell an attacker what version of Go its compiled code corresponds to. This can be enough to deduce some things about how an obfuscated program will behave, or what kind of security issues it might have if its Go version is out of date.
This issue tracks what needs to be done to obfuscate runtime
properly.
It turns out this is actually more important than I thought. The runtime has more than a handful of dependencies; it actually linknames functions from many other std packages. From a quick search:
$ cd tip/src/runtime
$ sed -rn 's@//go:linkname .* (.*)\..*@\1@p' *.go | sort -u
crypto/x509/internal/macos
internal/cpu
internal/poll
internal/reflectlite
main
main.
net
os
os/signal
plugin
reflect
runtime
runtime.
runtime/debug
runtime/metrics
runtime/pprof
runtime/trace
sync
sync/atomic
syscall
syscall/js
time
Perhaps we could somehow massage the input to the linker so that the linknames are updated to point to their garbled counterparts, but that seems complex. A better solution would be to simply obfuscate the runtime package, and modify the linkname directives in place.
Yet more confusion; go:linkname
is not just about the current package depending on another. It means that the first symbol is exported as the second. This can be used to have the first name be implemented by the second, or vice versa.
In the case of runtime, it often means that some functions are implemented in the runtime and are made available to other packages. This still means that we need to fix these linkname directives in the runtime package if we want to obfuscate all those other packages, though.
93b2873 was a huge step towards this. I think we now handle all of the runtime's linknames well, and we have pretty good assertions in place to catch if we get them wrong.
A note for future self - there are some more package paths that should never be obfuscated:
$ cd src/cmd && git grep 'Path == "' compile link
compile/internal/importer/iimport.go: if pkgPath == "" {
compile/internal/inline/inl.go: if base.Ctxt.Arch.CanMergeLoads && s.Pkg.Path == "encoding/binary" {
compile/internal/logopt/log_opts.go: if slashPkgPath == "" {
compile/internal/ssa/writebarrier.go: if types.LocalPkg.Path == "runtime" && v.Type.IsUnsafePtr() {
compile/internal/staticdata/embed.go: if typ.Sym() != nil && typ.Sym().Name == "FS" && typ.Sym().Pkg.Path == "embed" {
compile/internal/types/size.go: if sym := t.Sym(); sym != nil && sym.Pkg.Path == "runtime/internal/sys" && sym.Name == "nih" {
compile/internal/types/type.go: return p.Path == "runtime"
compile/internal/types/type.go: return p.Path == "reflect"
compile/internal/walk/order.go: (fn.Pkg.Path == "internal/abi" || fn.Pkg == types.LocalPkg && base.Ctxt.Pkgpath == "internal/abi")
Because we now patch the linker anyway we could also patch the hardcoded paths.
Certainly possible, although note that we currently patch and rebuild the linker, but not the compiler, which is where a lot of these hard-coded strings are.