Go Go Decompile

  • Category: rev
  • Difficulty: Easy

A Go binary (go_go_budgetmaster) asks for input on stdin and tells you whether you got it right. No source given — just the compiled binary.

Solution:

1. Decompile main.main

Loaded the binary into Binary Ninja (Go decompiler). Raw decompiler output for main.main:

004a1f80    bool main.main()

004a1f80    {
004a1f80        struct string flagb64;
004a1f8c        void* entry_r14;
004a1f8c        
004a1f8c        if (&flagb64.len <= *(uint64_t*)((char*)entry_r14 + 0x10))
004a1f8c        {
004a2267            runtime.morestack_noctxt.abi0();
004a2267            /* no return */
004a1f8c        }
004a1f8c        
004a1f9d        os.Stdout;
004a1fb0        int64_t entry_s;
004a1fb0        struct os.File* entry_f;
004a1fb0        struct bufio.Scanner* ~r0_3 = os.(*File).WriteString(entry_f, entry_s);
004a1fbc        flagb64.str = "YnJ1bm5lcntnMF9kM2MwbXAxbDNkX2cwX2Jycn0=Correct!\nThis is way better "
004a1fbc        "than Excel!\ninvalid span in heapArena for user arenabulkBarrierPreWrite: unaligned "
004a1fbc        "argumentsruntime: typeBitsBulkBarrier wi";
004a1fc1        flagb64.len = 0x28;
004a1fcd        struct os.File* os.Stdin_1 = os.Stdin;
004a1fe0        void r;
004a1fe0        struct internal/abi.Type* scanner_1;
004a1fe0        bool ~r0_2;
004a1fe0        struct bufio.Scanner* s_1;
004a1fe0        scanner_1 = bufio.NewScanner(r, ~r0_3);
004a1fe5        struct internal/abi.Type* scanner = scanner_1;
004a1fea        bool result;
004a1fea        struct bufio.Scanner* s_2;
004a1fea        result = bufio.(*Scanner).Scan(s_1, ~r0_2);
004a1fef        bool result_1 = result;
004a1fef        
004a1ff5        if (!result)
004a2001            return result;
004a2001        
004a2001        void* rax_3;
004a2001        int ~r0_1;
004a2001        int rsi;
004a2001        struct encoding/base64.Encoding* rdi;
004a2001        rax_3 = bufio.(*Scanner).Text(s_2);
004a2006        struct string input;
004a2006        input.str = rax_3;
004a200b        input.len = os.Stdin_1;
004a2018        int len = flagb64.len;
004a201d        encoding/base64.StdEncoding;
004a2024        int64_t ~r0;
004a2024        int cap_1;
004a2024        ~r0 = encoding/base64.(*Encoding).DecodedLen(rdi, rsi, ~r0_1);
004a2040        struct internal/abi.Type* rax_6;
004a2040        int128_t zmm15;
004a2040        rax_6 = runtime.makeslice(rdi, rsi, cap_1, ~r0);
004a204f        struct internal/abi.Type* var_58 = rax_6;
004a2057        int64_t ~r0_4 = ~r0;
004a205f        int64_t ~r0_5 = ~r0;
004a2067        struct []uint8 data;
004a2067        data.array = rax_6;
004a206f        data.len = ~r0;
004a2077        data.cap = ~r0;
004a207f        struct internal/godebug.runtimeStderr* str = flagb64.str;
004a2084        int len_1 = flagb64.len;
004a2084        
004a208f        if (!str)
004a2095            str = &internal/godebug.stderr;
004a2095        
004a20a6        int len_2 = len_1;
004a20ae        int len_3 = len_1;
004a20b6        int128_t var_30 = zmm15;
004a20bf        void* array = data.array;
004a20c7        encoding/base64.StdEncoding;
004a20f6        int rax_8;
004a20f6        int64_t rcx_4;
004a20f6        struct os.File* f;
004a20f6        rax_8 = encoding/base64.(*Encoding).Decode(data.cap, str);
004a2100        (uint64_t)var_30 = array;
004a2108        *(uint64_t*)((char*)&var_30 + 8) = rcx_4;
004a211a        void* array_2 = array;
004a2122        int64_t var_80 = rcx_4;
004a2134        error err;
004a2134        err.tab = array;
004a213c        err.data = rcx_4;
004a213c        
004a2147        if (array)
004a2147        {
004a214b            *(uint8_t*)array;
004a2156            int64_t var_40_1 = (*(uint64_t*)((char*)array + 0x18))();
004a215e            void* array_4 = array;
004a217a            int64_t rax_12;
004a217a            int64_t s;
004a217a            struct os.File* f_1;
004a217a            rax_12 = runtime.concatstring2(&data_4d0020);
004a217f            int64_t var_68_1 = rax_12;
004a2187            int64_t s_3 = s;
004a218f            os.Stderr;
004a21a5            f = os.(*File).WriteString(f_1, s);
004a2147        }
004a2147        
004a21b3        uint8* array_1 = data.array;
004a21bb        int cap = data.cap;
004a21bb        
004a21c6        if (cap < rax_8)
004a21c6        {
004a21cf            runtime.panicBounds();
004a2261            /* no return */
004a21c6        }
004a21c6        
004a21cf        struct []uint8 flag;
004a21cf        flag.array = array_1;
004a21d7        flag.len = rax_8;
004a21df        flag.cap = cap;
004a21e7        uint8* array_3 = array_1;
004a21ef        int var_70 = rax_8;
004a21ef        
004a21fc        if (input.len == rax_8)
004a21fc        {
004a220c            char rax_15;
004a220c            rax_15 = runtime.memequal();
004a220c            
004a2213            if (rax_15)
004a2213            {
004a2217                os.Stdout;
004a222a                return os.(*File).WriteString(f, cap);
004a2213            }
004a21fc        }
004a21fc        
004a2235        os.Stdout;
004a2248        return os.(*File).WriteString(f, cap);
004a1f80    }
2. Filter the Go noise

Most of that is runtime scaffolding — morestack_noctxt stack-growth checks, slice/string header splits, SSA temp variables (~r0, rax_N). Filtering it down to the actual logic:

func main() {
    flagb64 := "YnJ1bm5lcntnMF9kM2MwbXAxbDNkX2cwX2Jycn0=" // len = 0x28 (40 bytes)

    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
    input := scanner.Text()

    flag, err := base64.StdEncoding.Decode(flagb64)
    if len(input) == len(flag) && bytes.Equal(input, flag) {
        os.Stdout.WriteString("Correct!\n")
    } else {
        os.Stdout.WriteString("...\n")
    }
}

The decompiler concatenates the real 40-byte base64 literal with adjacent entries from Go’s read-only string table ("Correct!\nThis is way better than Excel!...", runtime panic strings) since they sit contiguously in .rodata. The flagb64.len = 0x28 field is what tells you exactly where the real string ends.

3. Confirm there’s no transformation

The rest of main.main is just: read a line from stdin, base64-decode the hardcoded constant, then len check + runtime.memequal against the input. No hashing, XOR, or per-byte scrambling — the flag is the decoded constant, and the “check” is cosmetic.

4. Decode
$ echo "YnJ1bm5lcntnMF9kM2MwbXAxbDNkX2cwX2Jycn0=" | base64 -d
brunner{g0_d3c0mp1l3d_g0_brr}

No dynamic analysis, debugger, or input crafting required — static extraction of the constant was enough.

Flag: brunner{g0_d3c0mp1l3d_g0_brr}