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:
os.(*File).WriteStringprints the 15-byte prompt"Go Go License? ".- A hardcoded 40-byte string at
0x4cc9e8is loaded (ptr + len0x28). bufio.NewScanner(os.Stdin)+Scan/Textreads our input line.base64.StdEncoding.DecodedLen(40)→30,runtime.makesliceallocates the destination, thenencoding/base64.(*Encoding).Decodedecodes the embedded string (not our input) into the buffer.- The decoded length
nis compared against our input length; if they differ we jump straight to the failure message. - 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)
- equal →
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
- A not-stripped Go binary is a gift: full embedded symbols meant
nm | grep 'main\.'landed onmain.mainimmediately. Static + debug info is the friendliest reversing target there is. - Follow the data, not your input: the check base64-decoded an embedded constant and compared that to us — the key was never derived from our input, so there was nothing to brute-force or patch. Always trace which bytes actually reach the comparison.
- vaddr → file offset is the reusable move: mapping
.rodata(0x4a3000→ file0xa3000) let us pull the constant straight out of the file with no debugger. That little offset lambda recurs across these RE challenges — keep it handy. - Verify by feeding it back: piping the decoded key into the binary and getting “Correct!” is near-free proof the static read was right before calling it the flag.