← Back to Writeups
CUSTOMEasyReversing

abominate_language

XESXOR9/10/20263 min read
#reversing#custom#easy#cgo_call_tracing#gopclntab_recovery#static_disassembly#string_reconstruction#linux

abominate_language

Platform: spbctf | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-06-24 | Status: Solved Techniques: cgo_call_tracing, gopclntab_recovery, static_disassembly, string_reconstruction

Summary

Task: stripped Go+cgo ELF that reads a string and prints OK! only for the correct flag. Solution: statically disassemble main.main in radare2 (gopclntab gives symbols), read the ordered per-character lea rbx string references that the checker concatenates, and reconstruct the expected input.

abominate_language — SPbCTF (rev-kids20.forkbomb.ru)

Description

App reads a string, and will print 'OK!' if it's flag

Language hint from the organizers: "C+Go" — i.e. a cgo binary mixing C and Go. The goal is to recover the exact string the program accepts.

Source: rev-kids20.forkbomb.ru (SPbCTF kids rev track). Binary: https://rev-kids20.forkbomb.ru/files/rev/re5/abominate_language

Recon (file / strings / symbols)

text
$ file abominate_language
ELF 64-bit LSB executable, x86-64, dynamically linked, stripped
Go build ID "a54840cebb9ead1c5a7564cf8a7fccba626c6188"

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Although the binary is stripped, it is a Go executable, so its function table (gopclntab) lets tools recover Go symbol names. The cgo footprint is visible in the symbol/string data:

  • _cgo_2d05b70430d8_Cfunc_myprint, x_cgo_* — the C side glue.
  • Go-side wrappers: main._Cfunc_CString, main._Cfunc_myprint, main._Ctype_char, main._Ctype_void.

Relevant recovered Go functions:

AddressSymbol
0x401460main._Cfunc_CString
0x401550main._Cfunc_myprint
0x402020main.init
0x4015f0main.main (size 2604 B)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The presence of main._Cfunc_* confirms the "C+Go" (cgo) hint: the Go side builds the success message and hands it to a C function for printing.

Static analysis with radare2

The ELF could not be executed locally (macOS: exec format error), so it was solved purely statically. Analyze and disassemble main.main:

bash
r2 -2 -q -e scr.color=0 \
   -c 'aa; s 0x004015f0; af; pdf' \
   abominate_language

main.main reads the user input, then compares it byte-by-byte against an expected string. The expected string is not stored as one contiguous blob. Instead, the Go fmt-style code assembles it character-by-character: each piece is referenced with a lea rbx, [<addr>] instruction pointing at a small .rodata string entry (mostly single characters, plus a few multi-char fragments). radare2 annotates each lea with the string content it points to. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

How the flag string is reconstructed

Reading the lea rbx, ... references in execution order spells out the expected input:

text
0x0040173b lea rbx, [0x004b3458] ; "f"
0x00401791 lea rbx, [0x004b37f8] ; "l"
0x004017e7 lea rbx, [0x004b2fd8] ; "a"
0x0040183d lea rbx, [0x004b3578] ; "g"
0x00401893 lea rbx, [0x004b43a0] ; "{"
0x004018e9 lea rbx, [0x004b2820] ; "G"
0x0040193f lea rbx, [0x004b2b28] ; "O"
0x00401995 lea rbx, [0x004b2f78] ; "_"
0x004019eb lea rbx, [0x004b36e8] ; "i"
0x00401a41 lea rbx, [0x004b2c88] ; "S"
0x00401a97 lea rbx, [0x004b2d80] ; "T"
0x00401aed lea rbx, [0x004b3358] ; "e"
0x00401b43 lea rbx, [0x004b3cd0] ; "r"
0x00401b99 lea rbx, [0x004b3cd0] ; "r"
0x00401bef lea rbx, [0x004b36e8] ; "i"
0x00401c45 lea rbx, [0x004b30c8] ; "b"
0x00401c9b lea rbx, [0x004b37f8] ; "l"
0x00401cf1 lea rbx, [0x004b3358] ; "e"
0x00401d47 lea rbx, str._lan      ; "_lan"
0x00401d9d lea rbx, [0x004b3660] ; "gua"
0x00401df3 lea rbx, [0x004b35e8] ; "ge"
0x00401e49 lea rbx, [0x004b43a8] ; "}"

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Concatenating the fragments in order:

text
f l a g { G O _ i S T e r r i b l e  + "_lan" + "gua" + "ge" + }
= flag{REDACTED}

Note that several addresses repeat (r→0x004b3cd0 twice, i→0x004b36e8, e→0x004b3358, l→0x004b37f8), which is just .rodata string deduplication — the same character entry is referenced multiple times.

The cgo print path

Once every byte matches, main.main takes the success branch and invokes the cgo bridge:

text
call 0x401460   ; main._Cfunc_CString  — convert Go string -> C char*
call 0x401550   ; main._Cfunc_myprint  — C side prints "OK!"

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

So the Go side converts its message into a C string via CString, then calls the C function myprint (the _cgo_..._Cfunc_myprint glue) which prints "OK!".

The whole challenge is a pun: cgo ("abomination") mixes C and Go, and the flag itself jokes that Go is a terrible language. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍