5
\$\begingroup\$

I've made a small script to compile Go code with Python.

The result is not something I'm happy with, because:

  • It seems overly complicated
  • I'm worried the nested if-else with f string are unreadable
  • Am I using the subprocess module the correct way?

Any review is welcome.

#!/usr/bin/env python3

import argparse
import subprocess
import os

ARCHS = {
    "32": "386",
    "64": "amd64"
}

BUILDTYPES = ["exe", "dll"]

def parse_arguments():
    parser = argparse.ArgumentParser(usage='%(prog)s [options] <buildtype> <architecture>',
                                     description='Go builder',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''
Python program to build go payloads

Examples:
    ./builder exe 32 --stripped
    ./builder dll 64 --upx --stripped
    ./builder dll 32
''')
    parser.add_argument("buildtype", help="The type to build the paylopad with", type=str)
    parser.add_argument("architecture", help="The architecture to build the paylopad with", type=str, default="32")
    parser.add_argument("--stripped", "-s", help="Strip the payload of symbols", action="store_true", default=False)
    parser.add_argument("--upx", "-u", help="Pack the payload with upx", action="store_true", default=False)
    arguments = parser.parse_args()

    if not arguments.buildtype.lower() in BUILDTYPES:
        parser.error(f"{arguments.buildtype} is not a valid buildtype can only be {', '.join(BUILDTYPES)}")

    if not arguments.architecture.lower() in ARCHS:
        parser.error(f"{arguments.architecture} is not a valid architecture can only be {', '.join(ARCHS.keys())}")
    arguments.architecture = ARCHS[arguments.architecture]

    return arguments

def build_go(**kwargs):
    enviroment = {
        **os.environ,
        "GOOS": "windows",
        "GOARCH": f"{kwargs['architecture']}"
    }
    if kwargs['buildtype'].lower() == "dll":
        enviroment["CGO_ENABLED"] = "1"
        enviroment["CC"] = f"{'i686' if kwargs['architecture'] == '386' else 'x86_64'}-w64-mingw32-gcc"

    build_type = f"go build -buildmode=c-shared" if kwargs["buildtype"].lower() == "dll" else "go build -tags exe"
    stripped = "-s -w" if kwargs['stripped'] else ""
    builder = f'''{build_type} -o GOLoader.{kwargs['buildtype']} -ldflags "-H=windowsgui {stripped}"'''
    subprocess.check_output(builder, shell=True, env=enviroment)

    if kwargs["stripped"]:
        stripped = f"strip GOLoader.{kwargs['buildtype']}"
        subprocess.check_output(stripped, shell=True)

    if kwargs["upx"]:
        upx = f"upx -f GOLoader.{kwargs['buildtype']} -9 --ultra-brute -o GOLoader.upx.{kwargs['buildtype']}"
        subprocess.check_output(upx, shell=True)

if __name__ == '__main__':
    args = parse_arguments()
    build_go(**vars(args))
\$\endgroup\$
  • \$\begingroup\$ I would write it using go, so it does not depend upon any runtime dependencies. \$\endgroup\$ – mh-cbon Nov 19 '19 at 9:50
3
\$\begingroup\$

Some minor notes.

I love the ternary operator and f strings, but...

Here are three ways to express the same thing. There is the original way, a DRY version of the original way, and a more traditional if/else. Which is easier to read and understand what is going on?

# variant 1
build_type = f"go build -buildmode=c-shared" if kwargs["buildtype"].lower() == "dll" else "go build -tags exe"

# variant 2
build_type = f"go build {'-buildmode=c-shared' if kwargs['buildtype'].lower() == 'dll' else '-tags exe'}"

# variant 3
if kwargs["buildtype"].lower() == "dll":
    build_type = "go build -buildmode=c-shared"
else:
    build_type = "go build -tags exe"

In this case I would argue that the third variant is MUCH easier to understand what the distinction is between the two build types. EG: What question is being asked (buildtype) and what is the difference in the result based on the answer.

Environment has a "n" after the "o"

This:

enviroment["CGO_ENABLED"] = "1"

should likely be:

environment["CGO_ENABLED"] = "1"    

Click is the thing.

Not sure this is immediately relevant as a code review, but...

I much prefer click to argparse. In this simple case there is not likely a huge advantage for Click, but there is little downside. And if you get comfortable with click and the size of your project increases, I think Click can be quite advantageous.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.