burrowers / garble

Obfuscate Go builds
BSD 3-Clause "New" or "Revised" License
3.73k stars 239 forks source link

Feature proposal: new subcommand for computing obfuscation map #849

Open fbbdev opened 2 months ago

fbbdev commented 2 months ago

Rationale

At present, garble provides a reverse subcommand that enables consumers to map obfuscated identifiers back to their plain counterparts as needed.

There are cases where this is not enough for devtools that might want to offer support for garbled builds. This is especially true for tools that provide interoperation between go and other languages (more on a concrete use case below).

What such tools might need is actually the ability to map plain identifiers to their obfuscated counterparts – i.e. perform obfuscation – matching exactly the behaviour of the installed garble binary. This is not an easy problem especially considering that garble has some sophisticated logic for deciding which objects get obfuscated, and which do not.

Proposal

I propose therefore the addition of a new subcommand, tentatively named garble map, with the following usage:

garble [garble flags] map [build flags] packages...

The command should output a JSON object with one key per obfuscated package – including all packages given on the command line and their transitive dependencies, kinda like go list -deps – with the following shape (but other approaches will do as long as they provide equivalent information):

{
    "PLAIN_PACKAGE_PATH_1": {
        "path": "OBFUSCATED_PACKAGE_PATH_1",
        "objects": {
            "TYPES_OBJECT_PATH_1": "OBFUSCATED_NAME_1",
            // ...
            "TYPES_OBJECT_PATH_n": "OBFUSCATED_NAME_n"
        }
    },
    // ...
    "PLAIN_PACKAGE_PATH_n": { /* ... */ },
}

Here, "TYPES_OBJECT_PATH_n": "OBFUSCATED_NAME_n" represents one entry for each obfuscated, externally visible go/types.Object in the package. Keys are object paths as computed by the facilities at golang.org/x/tools/go/types/objectpath. Objects that are not obfuscated, or don't have a path (i.e. they are not part of the package's public API) should not appear in the map.

Security considerations

Accidentally leaking the output of garble map would defeat obfuscation ~for all exported APIs~ for all public functions and public/private type names, so it carries a higher risk than garble reverse. Consumers should take extra care when integrating garble map with their development workflows.

That risk can be mitigated by feeding the output of garble map directly into other tools through shell pipelines, without writing it to the filesystem.

Concrete use case

Recently I have been working as an independent contributor on a full rewrite of the binding generator for Wails v3.

Wails is a project that enables developers to write desktop apps using Go and web technologies. Like many similar projects, it aims to support seamless interoperation between the Go backend and the JS frontend.

To this end, they provide an RPC system based upon encoding/json and a binding generator that parses Go code and outputs JS glue code. Specifically, developers register a list of named types as RPC "Services"; a hash is then computed for each method based on package path, type name and method name, and used to identify methods in remote call requests.

This obviously breaks down for garbled builds. Project owner @leaanthony sought advice some time ago at #826 and @lu4p suggested using the reflect.ValueOf trick; that however is not enough in this case, because it is only going to stop obfuscation of fields, not of the type name or package path.

There are workarounds, but they all involve increased boilerplate and upkeep efforts for application developers, and/or degraded obfuscation.

Not to speak of the fact that the reflect.ValueOf trick cannot cross package boundaries, and therefore hurts composability a lot. People who may need or want to use complex type hierarchies in their RPC API are going to experience severe limitations.

From Wails' perspective, the best option is clearly to provide transparent support for garbled builds, which requires some cooperation on garble's part. I devised and implemented the garble map approach described above as a minimal means for them to achieve this end.

It's true that the generated JS glue code could be used (although not without some effort) to partly reverse obfuscation, but Wails users would be made well aware of that and advised to obfuscate JS code as well (which they probably already do).

fbbdev commented 2 months ago

I opened a PR with a draft implementation for this proposal at #850 Technical details are discussed over there.

fbbdev commented 2 months ago

After thinking further about it, I believe the impact of leaking garble map output would be somewhat smaller than I thought initially. The path encoding by x/tools/go/types/objectpath uses names for top-level objects only (i.e. types and functions) hence it shouldn't be usable to deobfuscate field/method names.