
Ein radare2-Skript zum Parsen der gopclntab, um das Reverse Engineering von Go-Binärdateien zu erleichtern.
Hinweis: Diese Funktion wurde direkt in den Kern von radare2 als gopc analysis plugin implementiert.
Ein radare2-basierter Go gopclntab-Parser zum Wiederherstellen von Funktionssymbolen aus Go-Binaries, einschließlich vollständig gestrippter. Unterstützt ELF-, Mach-O- und PE-Binaries für Go-Versionen 1.2, 1.16, 1.18 und 1.20+.
Jedes Go 1.2+ Binary enthält einen Datenbereich namens gopclntab (Program Counter Line Table), den die Go-Laufzeit für Stack Traces, Panic-Meldungen, Garbage Collection und Debugger-Unterstützung verwendet. Es ist eine der wertvollsten Quellen für Symbolinformationen in Go-Binaries.
r2gopclntabParser liest diesen Bereich über radare2, parst die versionsspezifischen Strukturen und gibt entweder die wiederhergestellte Funktionsliste (mit Adressen, Quelldateien und Zeilennummern) aus oder wendet die wiederhergestellten Namen wieder in die offene radare2-Sitzung als Funktionsdefinitionen, Flags und Kommentare an.
| Abhängigkeit | Mindestversion |
|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (getestet mit 6.0.9) |
| r2pipe | beliebig |
Es werden keine weiteren Python-Pakete benötigt. Das Skript verwendet nur die Standardbibliothek (struct, json, argparse, os, sys) sowie r2pipe.
python3 r2_gopclntab.py -f ./mybinary -l
python3 r2_gopclntab.py -f ./mybinary -n main.main
python3 r2_gopclntab.py -f ./mybinary -v -l
python3 r2_gopclntab.py -f ./mybinary --apply
python3 r2_gopclntab.py -f ./mybinary --json
#!pipe python3 r2_gopclntab.py --r2pipe --apply -v
## CLI-Referenz```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
[--apply] [--json] [--files] [--r2pipe]
| Flag | Beschreibung |
|---|---|
-f FILE, --file FILE | Pfad zur zu analysierenden Go-Binärdatei. Das Skript startet eine eigene r2-Instanz. |
--r2pipe | An eine bereits laufende r2-Sitzung anhängen (zur Verwendung innerhalb der r2-Konsole). |
Flags können frei kombiniert werden. Wenn kein Ausgabe-Flag angegeben ist, werden standardmäßig der Header und die vollständige Funktionsliste ausgegeben.
Alle untenstehenden Beispiele wurden gegen eine gestrippte Go 1.26 Mach-O arm64-Binärdatei ausgeführt (kompiliert mit -ldflags="-s -w"). Das Testprogramm definiert main.main, main.fibonacci, main.helloWorld und main.addNumbers. Der Compiler hat helloWorld und addNumbers inline eingefügt, sodass sie nicht im gopclntab erscheinen.
Bei Ausführung ohne Flags (oder nur -f) wird der geparste Header gefolgt von der vollständigen Funktionstabelle ausgegeben:```
$ python3 r2_gopclntab.py -f ./gotest_stripped
### Projektzuordnung
[COBALT](https://github.com/MobSF/mobsfscan/tree/main/mobsfscan/rules/semgrep) enthält einen robusten Satz an Regeln für Android-Sicherheit und Datenschutz.```
============================================================
Go pclntab Header
============================================================
Magic: 0xFFFFFFF1
Go version: 1.20+
Pointer size: 8
Min LC (quantum):4
Num functions: 2030
Num files: 261
funcnameOffset: 0x48
cuOffset: 0x14D20
filetabOffset: 0x158D8
pctabOffset: 0x19A98
pclnOffset: 0x55E40
============================================================
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x100001000 go:buildid
0x100001070 internal/abi.BoundsDecode (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go:86)
0x100001150 internal/abi.NoEscape (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/escape.go:19)
0x100001160 internal/abi.Kind.String (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:143)
0x1000011E0 internal/abi.TypeOf (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:181)
0x1000011F0 internal/abi.(*Type).ExportedMethods (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:453)
...
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
0x1000A0C20 main.main (/tmp/gotest/main.go:20)
0x1000A0D20 go:textfipsstart
0x1000A0D30 go:textfipsend
[+] 2030 function(s) shown
Fügt Abschnitts-Scan-Fortschritt, textStart-Auflösungsdetails und interne Offset-Informationen hinzu:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -v
[No content provided after INPUT:]```
[*] Running radare2 analysis...
[*] Binary format: mach0, endian: little, arch: arm, bits: 64
[*] Scanning binary for gopclntab magic bytes...
[*] Scanning section '0.__TEXT.__text' (0x100001000, 0x9FD44)...
[*] Scanning section '1.__TEXT.__symbol_stub1' (0x1000A0D60, 0x2B8)...
[*] Scanning section '2.__TEXT.__rodata' (0x1000A1020, 0xACC2)...
[*] Scanning section '3.__TEXT.__gopclntab' (0x1000ABCE8, 0xA48AE)...
[*] Found magic at vaddr=0x1000ABCE8
[*] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ptrSize=8, minLC=4,
nfunc=2030, nfiles=261, textStart=0x0)
[*] textStart is 0, using .text section vaddr: 0x100001000
[*] Parsed 2030 functions
============================================================
Go pclntab Header
============================================================
Magic: 0xFFFFFFF1
Go version: 1.20+
...
Filtert die Funktionsliste durch Teilzeichenfolgen-Übereinstimmung. Wenn eine exakte Übereinstimmung existiert, wird deren Adresse separat ausgegeben.
Teilzeichenfolgensuche nach allen Funktionen, die main. enthalten:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."
Der obige Befehl scannt das Netzwerk `192.168.1.0/24` und führt Tests nur auf Port 80 durch.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x100041920 runtime.main.func2 (/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go:207)
0x10006CE30 runtime.main.func1 (/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go:174)
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
0x1000A0C20 main.main (/tmp/gotest/main.go:20)
[+] 4 function(s) shown (filtered from 2030 total)
Exakte Übereinstimmungssuche:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -n "main.fibonacci"
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
[+] 1 function(s) shown (filtered from 2030 total)
[+] Exact match: main.fibonacci @ 0x1000A0BB0
Suche nach GC-bezogenen Laufzeitinterna:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
0x10001F310 runtime.gcinit (/opt/homebrew/.../src/runtime/mgc.go:179) 0x10001F3C0 runtime.gcenable (/opt/homebrew/.../src/runtime/mgc.go:211) 0x10001F730 runtime.gcStart (/opt/homebrew/.../src/runtime/mgc.go:733) 0x10001FFE0 runtime.gcMarkDone (/opt/homebrew/.../src/runtime/mgc.go:1015) 0x100020A50 runtime.gcMarkTermination (/opt/homebrew/.../src/runtime/mgc.go:1344) 0x100021C10 runtime.gcBgMarkWorker (/opt/homebrew/.../src/runtime/mgc.go:1750) 0x1000223D0 runtime.gcMark (/opt/homebrew/.../src/runtime/mgc.go:1956) 0x1000227A0 runtime.gcSweep (/opt/homebrew/.../src/runtime/mgc.go:2049) ... 0x100076C60 runtime.gcWriteBarrier1 (/opt/homebrew/.../src/runtime/asm_arm64.s:1533)
[+] 73 function(s) shown (filtered from 2030 total)
Suche nach `fmt.` (Standardbibliothek Ausgabe):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."
0x100098AA0 fmt.(*fmt).writePadding (/opt/homebrew/.../src/fmt/format.go:66) 0x100098BF0 fmt.(*fmt).pad (/opt/homebrew/.../src/fmt/format.go:93) 0x100099590 fmt.(*fmt).fmtInteger (/opt/homebrew/.../src/fmt/format.go:197) 0x10009ADF0 fmt.Fprintf (/opt/homebrew/.../src/fmt/print.go:222) 0x10009AED0 fmt.Fprintln (/opt/homebrew/.../src/fmt/print.go:303) 0x10009D3F0 fmt.(*pp).printArg (/opt/homebrew/.../src/fmt/print.go:721) 0x10009D950 fmt.(*pp).printValue (/opt/homebrew/.../src/fmt/print.go:797) 0x10009FA60 fmt.(*pp).doPrintf (/opt/homebrew/.../src/fmt/print.go:1018) ...
Suche nach `sync.` (Nebenläufigkeits-Primitive):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
F: Wofür wird dieses Tool verwendet?
A: Dieses Tool wird zur Analyse des Netzwerkverkehrs und zur Erkennung potenzieller Sicherheitsbedrohungen verwendet.
0x10006FFE0 sync.runtime_registerPoolCleanup (/opt/homebrew/.../src/runtime/mgc.go:2150) 0x100070BA0 sync.fatal (/opt/homebrew/.../src/runtime/panic.go:1160) 0x1000714E0 sync.runtime_procPin (/opt/homebrew/.../src/runtime/proc.go:7912) 0x10007B290 internal/sync.(*Mutex).lockSlow (/opt/homebrew/.../src/internal/sync/mutex.go:95) 0x10007B570 internal/sync.(*Mutex).Unlock (/opt/homebrew/.../src/internal/sync/mutex.go:187) ...
### JSON-Ausgabe (--json)
Maschinenlesbare Ausgabe für Skript- und Pipeline-Integration:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --json
Bitte fügen Sie den zu übersetzenden Markdown-Inhalt ein.```json { "header": { "magic": "0xFFFFFFF1", "version": "1.20+", "ptrSize": 8, "minLC": 4, "nfunc": 2030, "nfiles": 261, "textStart": "0x0" }, "functions": [ { "name": "go:buildid", "addr": "0x100001000", "args": 0, "source_file": "", "start_line": 0 }, { "name": "internal/abi.BoundsDecode", "addr": "0x100001070", "args": 8, "source_file": "/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go", "start_line": 86 }, { "name": "main.fibonacci", "addr": "0x1000A0BB0", "args": 0, "source_file": "/tmp/gotest/main.go", "start_line": 13 }, { "name": "main.main", "addr": "0x1000A0C20", "args": 0, "source_file": "/tmp/gotest/main.go", "start_line": 20 } ], "num_source_files": 261 }
### Auflistung der Quelldateien (--files)
Extrahiert alle im Binary eingebetteten Quelldateipfade:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --files
/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/escape.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/cpu/cpu.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/cpu/cpu_arm64.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/mgc.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/malloc.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/panic.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/fmt/print.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/fmt/format.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/reflect/value.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/reflect/type.go /tmp/gotest/main.go ... ... and 61 more
### Anwendungsmodus (--apply)
Schreibt alle wiederhergestellten Funktionsnamen in die radare2-Sitzung. Das Folgende zeigt einen Vorher/Nachher-Vergleich an der gestrippten Binärdatei.
**BEFORE** (r2 native analysis on stripped binary, no gopclntab parsing):```
Functions found by r2 natively: 1913
Disassembly at 0x1000a0c20 (main.main, unnamed):
; CODE XREF from fcn.1000a0c20 @ 0x1000a0d14(r)
24: fcn.1000a0c20 (int64_t arg1);
0x1000a0c20 900b40f9 ldr x16, [x28, 0x10]
0x1000a0c24 ff6330eb cmp sp, x16
0x1000a0c28 29070054 b.ls 0x1000a0d0c
Disassembly at 0x1000a0bb0 (main.fibonacci, unnamed):
112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h);
0x1000a0bb0 900b40f9 ldr x16, [x28, 0x10]
0x1000a0bb4 ff6330eb cmp sp, x16
0x1000a0bb8 a9020054 b.ls 0x1000a0c0c
r2 fand 1913 Funktionen, benannte jedoch keine von ihnen (nur anonyme fcn.XXXXXXXX-Labels). Die Suche nach main.main oder main.fibonacci ergibt nichts.
Anwenden von gopclntab-Symbolen:``` [] Binary format: mach0, endian: little, arch: arm, bits: 64 [] Found magic at vaddr=0x1000ABCE8 [] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ...) [] textStart is 0, using .text section vaddr: 0x100001000 [*] Parsed 2030 functions [+] Applied 2030 function names to radare2 (0 skipped)
**NACH** (r2 session mit angewendeten gopclntab Symbolen):```
r2 function list matching "main" (after --apply):
0x100041510 0 0 runtime.main
0x100041920 0 0 runtime.main.func2
0x10006ce30 0 0 runtime.main.func1
Flags in go.* flagspace (last 20):
0x10009fa60 1 go.fmt._ptr_pp_.doPrintf
0x1000a0930 1 go.fmt._ptr_pp_.doPrintln
0x1000a0bb0 1 go.main.fibonacci
0x1000a0c20 1 go.main.main
0x1000a0d20 1 go.go:textfipsstart
0x1000a0d30 1 go.go:textfipsend
Die Disassemblierung bei main.main zeigt jetzt den wiederhergestellten Namen und den Quellort an:```
;-- go.main.main:
24: fcn.1000a0c20 (int64_t arg1);
0x1000a0c20 900b40f9 ldr x16, [x28, 0x10] ; " src: /tmp/gotest/main.go:20"
0x1000a0c24 ff6330eb cmp sp, x16
0x1000a0c28 29070054 b.ls 0x1000a0d0c
Disassemblierung bei `main.fibonacci` zeigt jetzt den wiederhergestellten Namen und die Quellposition:```
;-- go.main.fibonacci:
112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h);
0x1000a0bb0 900b40f9 ldr x16, [x28, 0x10] ; " src: /tmp/gotest/main.go:13"
0x1000a0bb4 ff6330eb cmp sp, x16
0x1000a0bb8 a9020054 b.ls 0x1000a0c0c
Das Suchen nach Namen funktioniert jetzt in der r2-Sitzung:``` go.main.main resolves to: 0x1000a0c20 go.main.fibonacci resolves to: 0x1000a0bb0
---
## Zusammenfassung der Ergebnisse: Mach-O (gestrippte Test-Binärdatei)
| Metrik | r2 nativ (gestrippt) | Nach r2_gopclntab.py |
|---|---|---|
| Gefundene Funktionen | 1913 (anonyme `fcn.XXXX`-Bezeichnungen) | 2030 (vollständige, paketqualifizierte Go-Namen) |
| Identifizierte Benutzerfunktionen | 0 | `main.main`, `main.fibonacci` mit Quelle + Zeile |
| Wiederhergestellte Quelldateien | 0 | 261 (vollständige absolute Pfade) |
| Benannte Symbole | Nur C-Import-Stubs (`sym.imp.mmap`, usw.) | Jede Go-Funktion gekennzeichnet (`go.main.main`, `go.runtime.gcStart`, usw.) |
| Quellkommentare | Keine | Inline `src: /tmp/gotest/main.go:20` in der Disassembly |
| Nach Name navigierbar | Nein | Ja (`s go.main.main`, `afl~runtime.gc`) |
---
## PE-Test: Greenblood, eine Go-Ransomware-Binärdatei
Der Parser wurde an einer echten PE-Binärdatei getestet, Greenblood (`greenblood_1`), einer Go-Ransomware-Probe, die als PE32+ x86-64 Windows-Executable kompiliert wurde. PE-Binärdateien haben keinen dedizierten `.gopclntab`-Abschnitt, daher wird hier der Magic-Byte-Scanning-Fallback-Pfad getestet.
### Erkennung und Header```
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
EINGABE:``` [] Binary format: pe, endian: little, arch: x86, bits: 64 [] Scanning binary for gopclntab magic bytes... [] Scanning section '.text' (0x401000, 0xF4000)... [] Scanning section '.rdata' (0x4F5000, 0x127000)... [] Found magic at vaddr=0x568C00 [] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ptrSize=8, minLC=1, nfunc=2596, nfiles=345, textStart=0x401000) [] textStart from header: 0x401000 [] Parsed 2596 functions
Der Scanner fand das gopclntab im Abschnitt `.rdata` bei `0x568C00`.
Da es sich um eine standardmäßige PE (nicht PIE) handelt, ist `textStart` `0x401000` (nicht Null),
daher wird der Header-Wert direkt für die Adressberechnung verwendet.
| Field | Value |
|---|---|
| Format | PE32+ x86-64 |
| gopclntab location | `.rdata` at `0x568C00` (found by magic scan) |
| Magic | `0xFFFFFFF1` (Go 1.20+) |
| Pointer size | 8 |
| Quantum (minLC) | 1 (x86) |
| textStart | `0x401000` (from header) |
| Functions recovered | 2596 |
| Source files | 345 |
### Wiederhergestellte Malware-Funktionen
Suche nach dem eigenen Code der Malware (`main.`):```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."
0x4D91E0 main.init (:1) 0x4D9200 main.map.init.0 (/root/victims/ransom/daf/enc.go:59) 0x4D92C0 main.map.init.1 (/root/victims/ransom/daf/enc.go:126) 0x4D95C0 main.NewKeyManager (/root/victims/ransom/daf/enc.go:146) 0x4D97E0 main.getMachineFingerprint (/root/victims/ransom/daf/enc.go:173) 0x4DA2C0 main.getBIOSUUID (/root/victims/ransom/daf/enc.go:249) 0x4DA3C0 main.NewEncryptionEngine (/root/victims/ransom/daf/enc.go:287) 0x4DA560 main.(*EncryptionEngine).fileWorker (/root/victims/ransom/daf/enc.go:303) 0x4DA660 main.(*EncryptionEngine).processFile (/root/victims/ransom/daf/enc.go:319) 0x4DA740 main.(*EncryptionEngine).encryptFile (/root/victims/ransom/daf/enc.go:334) 0x4DB2A0 main.(*EncryptionEngine).EncryptPath (/root/victims/ransom/daf/enc.go:446) 0x4DB620 main.(*EncryptionEngine).shouldSkipDirectory (/root/victims/ransom/daf/enc.go:495) 0x4DB7C0 main.(*EncryptionEngine).shouldEncryptFile (/root/victims/ransom/daf/enc.go:522) 0x4DB9A0 main.(*EncryptionEngine).placeRansomNote (/root/victims/ransom/daf/enc.go:560) 0x4DBB60 main.(*EncryptionEngine).recordSuccess (/root/victims/ransom/daf/enc.go:636) 0x4DBFA0 main.(*EncryptionEngine).Wait (/root/victims/ransom/daf/enc.go:664) 0x4DC3C0 main.formatBytes (/root/victims/ransom/daf/enc.go:689) 0x4DC500 main.disableRecovery (/root/victims/ransom/daf/enc.go:706) 0x4DC720 main.isAdmin (/root/victims/ransom/daf/enc.go:732) 0x4DC8C0 main.main (/root/victims/ransom/daf/enc.go:760) 0x4DD260 main.getLogicalDrives (/root/victims/ransom/daf/enc.go:863) 0x4DD4A0 main.isAlreadyRunning (/root/victims/ransom/daf/enc.go:892) 0x4DD660 main.getDesktopPath (/root/victims/ransom/daf/enc.go:911) 0x4DD780 main.removeExecutable (/root/victims/ransom/daf/enc.go:932) ...
[+] 43 function(s) shown (filtered from 2596 total)
Alle 43 Benutzerfunktionen wurden aus einer einzelnen Quelldatei unter `/root/victims/ransom/daf/enc.go` wiederhergestellt. Die Funktionsnamen offenbaren sofort die Fähigkeiten der Ransomware: Schlüsselverwaltung, Maschinen-Fingerprinting, Dateiverschlüsselung mit Pfad-Traversal, Platzieren von Lösegeldforderungen, Deaktivierung der Wiederherstellung, Privilegienprüfung, Mutex-basierte Einzelinstanz-Durchsetzung, Laufwerksaufzählung und Selbstlöschung.
### Abhängigkeiten von Nicht-Standard-Bibliotheken
Extrahieren von Quelldateien, die nicht Teil der Go-Standardbibliothek sind:```
/root/go/pkg/mod/golang.org/x/[email protected]/windows/dll_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/registry/key.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/registry/value.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/security_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/str.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/syscall.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/syscall_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/zsyscall_windows.go
/root/victims/ransom/daf/enc.go
Die einzige externe Abhängigkeit ist golang.org/x/[email protected] für Windows-spezifische Syscalls (Registry-Zugriff, Sicherheitstoken, DLL-Laden).
Die Suche nach crypto offenbart 137 Krypto-bezogene Funktionen, darunter:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
(empty response)```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x4AEC00 crypto/cipher.NewCTR (/usr/local/go/src/crypto/cipher/ctr.go:41)
0x4AF520 crypto/cipher.StreamWriter.Write (/usr/local/go/src/crypto/cipher/io.go:36)
0x4AF840 crypto/aes.NewCipher (/usr/local/go/src/crypto/aes/aes.go:36)
0x4C3760 crypto/rand.(*reader).Read (/usr/local/go/src/crypto/rand/rand.go:45)
0x4DE6C0 crypto/internal/fips140/sha256.New (.../sha256/sha256.go:138)
0x4E3EC0 crypto/internal/fips140/sha3.NewCShake128 (.../sha3/shake.go:134)
0x4EED60 crypto/internal/fips140/hmac.New (.../hmac/hmac.go:131)
0x4EF800 crypto/internal/fips140/aes.newBlock (.../aes/aes_asm.go:59)
0x4F0800 crypto/internal/fips140/aes.(*CBCEncrypter).CryptBlocks (.../aes/cbc.go:26)
0x4F0FE0 crypto/internal/fips140/aes.(*CTR).XORKeyStream (.../aes/ctr.go:41)
...
[+] 137 function(s) shown (filtered from 2596 total)
Das Kryptonutzungsprofil: AES (CBC- und CTR-Modi), SHA-256, SHA-512, HMAC, CSHAKE128 und DRBG (deterministischer Zufallsbitgenerator). Dies stimmt mit Ransomware überein, die pro Maschine generierte Verschlüsselungsschlüssel basierend auf einem Maschinenfingerabdruck erzeugt, Dateien mit AES-CTR verschlüsselt und HMAC für die Integrität verwendet.
$ python3 r2_gopclntab.py -f ./greenblood_1 --apply [+] Applied 2596 function names to radare2 (0 skipped) [+] Function names applied. Use 'afl' in r2 to see them.
Alle 2596 Funktionen erfolgreich angewendet, null übersprungen.
---
## Anwendungsfälle für Reverse Engineering
### 1. Triage und Identifikation
Sofort feststellen, ob eine Binärdatei Go ist, mit welcher Version sie erstellt wurde und welche Pakete sie verwendet. Die Ausgabe von `--files` zeigt die Go-Toolchain-Version (anhand von Dateipfaden wie `/usr/local/go/1.26.1/...`) und jeden Quellcodedateipfad, einschließlich Drittanbieter-Bibliotheken. Bei Malware wird dadurch sofort sichtbar, ob die Probe `crypto/tls`, `net/http`, `os/exec` oder andere interessante Pakete verwendet.
### 2. Symbolwiederherstellung bei gestrippten Binärdateien
Der zentrale Anwendungsfall. Go-Binärdateien, die mit `-ldflags="-s -w"` gestrippt wurden, verlieren ihre Symboltabelle, aber gopclntab bleibt erhalten. Dieses Tool stellt jeden Funktionsnamen wieder her und verwandelt anonyme `fcn.1000a0c20` zurück in `main.main`. Dies gilt für Malware-Beispiele, CTF-Herausforderungen, Produktionsbinärdateien und jede gestrippte Go-ausführbare Datei.
### 3. Navigation durch die Go-Laufzeitumgebung
Go-Binärdateien enthalten die gesamte Laufzeitumgebung (typischerweise 1500 bis 2000+ Funktionen). Ohne Namen ist die Laufzeitumgebung eine undurchdringliche Wand anonymer Funktionen. Mit Namen können Sie sofort `runtime.mallocgc`, `runtime.gopanic`, `runtime.newproc`, `runtime.gcStart` lokalisieren und verstehen, was die Binärdatei an jeder Aufrufstelle tut.
### 4. Trennung von Benutzercode und Laufzeitumgebung
Durch die Suche nach `main.` oder dem Paketpfad der Anwendung können Sie den Benutzercode von der Laufzeitumgebung isolieren. In den obigen Beispielen zeigt das Filtern nach `main.` sofort `main.main` und `main.fibonacci` unter insgesamt 2030 Funktionen. Sie können auch nach Drittanbieter-Paketnamen suchen (z.B. `-n "github.com/user/repo"`), um externe Abhängigkeiten zu identifizieren.
### 5. Kontext auf Quellcode-Ebene
Jede Funktion wird mit ihrem Quellcodedateipfad und der Startzeilennummer geliefert. Das bedeutet, dass Sie die Disassemblierung mit dem Quellcode der Go-Standardbibliothek (der offen ist) abgleichen können, selbst wenn Sie eine gestrippte Binärdatei analysieren. Zu wissen, dass eine Funktion in Zeile 733 von `runtime/mgc.go` beginnt, ermöglicht es Ihnen, den ursprünglichen Quellcode zusammen mit der Disassemblierung zu lesen.
### 6. Pipeline und Automatisierung
Der `--json`-Modus ermöglicht Skripting. Leiten Sie die Ausgabe an IDA/Ghidra-Importwerkzeuge, Diffing-Tools, YARA-Regelgeneratoren oder jede Analyse-Pipeline weiter. Zum Beispiel, um alle kryptobezogenen Funktionen zu extrahieren:```bash
python3 r2_gopclntab.py -f sample.exe --json \
| jq '.functions[] | select(.name | contains("crypto"))'
Nach --apply wird die gesamte r2-Sitzung über den Go-Namen navigierbar. Sie können zu Funktionen springen (s go.main.main), die Funktionsliste durchsuchen (afl~runtime.gc), Querverweise prüfen (axf go.main.fibonacci) und Quellortkommentare inline in der Disassemblierungsausgabe (pd) sehen. Dies verwandelt r2 von einem generischen Disassembler in eine Go-bewusste Analyseumgebung.
Für PE-Binärdateien und aggressiv gestrippte ELF/Mach-O-Binärdateien, denen der Abschnittsheader fehlt, greift das Tool auf das Scannen aller Abschnitte nach den 4-Byte-Magic-Bytes zurück, gefolgt von Validierungsbytes (pad=0, ptrSize in {4,8}, minLC in {1,2,4}).
Der Magic-Wert 0xFFFFFFF1 wird von Go 1.20 bis mindestens Go 1.26 verwendet.
Der Parser verwendet einen zweiphasigen Ansatz, um die gopclntab-Daten zu finden:
Phase 1 (ELF/Mach-O): Fragt die radare2-Abschnittsliste ab (iSj) und sucht nach Abschnitten mit den Namen .gopclntab, .data.rel.ro.gopclntab oder __gopclntab.
Phase 2 (PE/Fallback): Wenn kein benannter Abschnitt gefunden wird, werden alle Abschnitte nach den 4-Byte-Magic-Bytes durchsucht. Jeder Kandidat wird validiert, indem überprüft wird, ob die Bytes 4-7 dem erwarteten Muster entsprechen: zwei Null-Pad-Bytes, eine gültige Zeigergröße (4 oder 8) und ein gültiges Instruktionsquantum (1, 2 oder 4). Dies eliminiert Fehlalarme durch zufällige Bytemuster.
In Go >= 1.18 werden Funktionseinstiegspunkte in der functab als relative Offsets gespeichert. Die Berechnung der absoluten virtuellen Adresse erfordert eine Basis:``` absolute_addr = base + entryoff
Die Basis wird mit der folgenden Logik ermittelt:```
Is magic 0xFFFFFFFB (Go 1.2)?
YES -> base = 0 (entries are absolute addresses)
NO -> Is magic 0xFFFFFFFA (Go 1.16)?
YES -> base = .text section vaddr (entries are absolute)
NO -> (Go 1.18 / 1.20+)
Is header.textStart != 0?
YES -> base = header.textStart
NO -> base = .text section vaddr
Der Fall textStart == 0 tritt in Go >= 1.22 für Mach-O- und PIE-Binärdateien auf.
Wenn dies geschieht, sind die entryoff-Werte relativ zum Beginn des .text-Abschnitts, daher fragt der Parser r2 nach der virtuellen Adresse von .text und verwendet diese als Basis.
Dies wurde an einer Go 1.26 Mach-O arm64-Binärdatei verifiziert:
textStart im Header: 0x0.text-Abschnitt vaddr: 0x100001000functab[1].entryoff: 0x700x100001000 + 0x70 = 0x100001070internal/abi.BoundsDecode bei 0x100001070Das Layout der _func-Struktur unterscheidet sich zwischen Go 1.18 und Go 1.20+. Die einzige Änderung ist die Einfügung eines 4-Byte-startLine-Felds bei Offset 36 in Go 1.20+, wodurch funcID, flag und nfuncdata um 4 Byte verschoben werden:
Der Parser prüft die Magische Zahl, um zu bestimmen, welches Layout verwendet werden soll.
Quelldatei-Indizes und Zeilennummern werden als kompakte „PC-Daten-Programme“ im pctab-Bereich gespeichert. Jedes Programm kodiert eine Sequenz von (value_delta, pc_delta)-Paaren unter Verwendung von Ganzzahlen variabler Länge mit Zig-Zag-Kodierung für vorzeichenbehaftete Werte. Der Parser dekodiert diese, um Folgendes aufzulösen:
_func.pcfile -> pctab-Programm -> Dateiindex -> cutab -> filetab -> Dateipfad-Zeichenkette_func.pcln -> pctab-Programm -> Zeilennummer (zuzüglich startLine-Offset für Go 1.20+)Inline-Funktionen erscheinen nicht in der Funktionstabelle der obersten Ebene. Sie sind in FUNCDATA_InlTree- / PCDATA_InlTreeIndex-Strukturen kodiert, die dieses Tool derzeit nicht dekodiert. Im Test-Binärprogramm wurden main.helloWorld und main.addNumbers vom Compiler inline gesetzt und erscheinen daher nicht in der Ausgabe.
Go 1.2-Unterstützung erfolgt nach besten Kräften. Das Go 1.2-Format unterscheidet sich erheblich (kein separates funcnametab, kein cutab, absolute Zeiger in functab) und wird in der Praxis selten angetroffen.
Big-Endian-Architekturen werden prinzipiell unterstützt (die Endianness wird aus den radare2-Binärinformationen erkannt und für alle Struktur-Lesevorgänge verwendet), wurden aber nicht getestet.
Der --apply-Modus erstellt Funktions-Stubs mit af+, die mit der eigenen Autoanalyse von r2 in Konflikt geraten können. Die Ausführung in einer sauberen Sitzung (vor oder anstelle von aaa) kann in manchen Fällen bessere Ergebnisse liefern.
Detaillierte technische Dokumentation ist im Verzeichnis documentation/ verfügbar:
| Flag | Beschreibung |
|---|
-l, --list | Jede wiederhergestellte Funktion mit ihrer Adresse ausgeben. |
-n NAME, --funcname NAME | Nur Funktionen ausgeben, deren Name NAME enthält (Teilstring-Übereinstimmung). Wenn eine exakte Übereinstimmung existiert, wird ihre Adresse separat ausgegeben. |
-v, --verbose | Fortschrittsmeldungen, geparste Header-Felder und interne Offsets ausgeben. |
--apply | Wiederhergestellte Funktionsnamen in die radare2-Sitzung als Funktionsdefinitionen (af+), Flags im go.-Flagspace und Kommentare mit dem ursprünglichen Go-Namen und Quellort schreiben. |
--json | Den Header und die vollständige Funktionsliste als JSON nach stdout ausgeben. |
--files | Die Liste der aus der Dateitabelle extrahierten Quellcodedateipfade ausgeben. |
-h, --help | Hilfemeldung anzeigen. |
| Format | Methode zur Abschnittserkennung | Getestet |
|---|
| ELF (Linux) | Abschnittsname .gopclntab oder .data.rel.ro.gopclntab | Ja |
| Mach-O (macOS) | Abschnittsname __gopclntab (im Segment __TEXT) | Ja |
| PE (Windows) | Magic-Byte-Scan (kein dedizierter Abschnitt) | Ja (getestet auf Greenblood, einem Go-Ransomware-PE64) |
| Magie | Go-Version | textStart im Header | Typ von functab.entry | Feld startLine | Status |
|---|
0xFFFFFFFB | 1.2 | Nein | uintptr (absolut) | Nein | Unterstützt |
0xFFFFFFFA | 1.16 | Nein | uintptr (absolut) | Nein | Unterstützt |
0xFFFFFFF0 | 1.18 - 1.19 | Ja | uint32 (relativ) | Nein | Unterstützt |
0xFFFFFFF1 | 1.20+ | Ja (kann 0 sein) | uint32 (relativ) | Ja | Unterstützt |
| Feld | Go 1.18-Offset | Go 1.20+-Offset |
|---|
entryOff | 0 | 0 |
nameOff | 4 | 4 |
args | 8 | 8 |
cuOffset | 32 | 32 |
startLine | (fehlt) | 36 |
funcID | 36 | 40 |
flag | 37 | 41 |
nfuncdata | 39 | 43 |