Go Go Decompile

581 Words · 2 Minutes, 38 Seconds

Challenge Name: rev_go-go-decompile

Category: Reversing

Difficulty: Easy

Author: Quack

Challenge Description

Uh oh, I’ve been crunching numbers all month in Go Go BudgetMaster, but the cleaning crew accidentally threw out my license key Post-it. Now management’s breathing down my neck about the budget.

Maybe I can use that magic dragon program our security guy keeps blabbing about at lunch?

We are handed a single binary, go_go_budgetmaster. It asks for a license key and rejects everything we type. The “magic dragon program” is the not-so-subtle hint: the dragon is Ghidra’s logo, and the title says it all — decompile the Go binary and read the key out.

Approach

Step 1 — Identify the binary

$ file go_go_budgetmaster
ELF 64-bit LSB executable, x86-64, statically linked, with debug_info, not stripped

A statically-linked Go binary, not stripped, with debug info — the friendliest possible reversing target. Go embeds full symbol names, so nm finds the interesting function immediately:

$ nm go_go_budgetmaster | grep 'main\.'
00000000004a1f80 T main.main

Running it shows the shape of the check:

$ echo test | ./go_go_budgetmaster
Go Go License? Incorrect!
Are you sure you work here?

Step 2 — Read main.main

Disassembling main.main (objdump -d --disassemble=main.main -M intel) the control flow is short and readable:

  1. os.(*File).WriteString prints the 15-byte prompt "Go Go License? ".
  2. A hardcoded 40-byte string at 0x4cc9e8 is loaded (ptr + len 0x28).
  3. bufio.NewScanner(os.Stdin) + Scan/Text reads our input line.
  4. base64.StdEncoding.DecodedLen(40)30, runtime.makeslice allocates the destination, then encoding/base64.(*Encoding).Decode decodes the embedded string (not our input) into the buffer.
  5. The decoded length n is compared against our input length; if they differ we jump straight to the failure message.
  6. If lengths match, runtime.memequal(input, decoded, n) compares the bytes.
    • equal → "Correct!\nThis is way better than Excel!\n" (0x4cca10)
    • not equal → "Incorrect!\nAre you sure you work here?\n" (0x4cc72a)

The comparison is against the plaintext base64-decode of a constant baked into the binary. So the license key isn’t computed from our input at all — it is that decoded constant. No dynamic analysis or patching required.

Step 3 — Extract and decode the constant

The .rodata section maps virtual address 0x4a3000 to file offset 0xa3000, so 0x4cc9e8 lives at file offset 0xcc9e8. Pull the 40 bytes and base64-decode:

import base64
data = open("go_go_budgetmaster","rb").read()
off = lambda v: 0xa3000 + (v - 0x4a3000)      # .rodata: vaddr 0x4a3000 -> file 0xa3000
b64 = data[off(0x4cc9e8):off(0x4cc9e8)+40]
print(b64)                                     # b'YnJ1bm5lcntnMF9kM2MwbXAxbDNkX2cwX2Jycn0='
print(base64.b64decode(b64).decode())          # brunner{g0_d3c0mp1l3d_g0_brr}

Step 4 — Verify against the binary

$ printf 'brunner{g0_d3c0mp1l3d_g0_brr}\n' | ./go_go_budgetmaster
Go Go License? Correct!
This is way better than Excel!

The decoded license key is accepted, and (being in brunner{...} format) is itself the flag.

Flag

brunner{g0_d3c0mp1l3d_g0_brr}

Reflections and Learnings


reversinggodecompilationghidrastatic-analysis

Reversing