
Un script radare2 pour parser le gopclntab afin de faciliter la rétro-ingénierie des binaires Go.
Note : Cette fonctionnalité a été implémentée directement dans le cœur de radare2 sous la forme du plugin d'analyse gopc
Un analyseur gopclntab basé sur radare2 pour récupérer les symboles de fonctions de binaires Go, y compris ceux entièrement strippés. Prend en charge les binaires ELF, Mach-O et PE pour les versions Go 1.2, 1.16, 1.18 et 1.20+.
Chaque binaire Go 1.2+ intègre une région de données appelée gopclntab (Program Counter Line Table) que le runtime Go utilise pour les traces de pile, les messages de panique, le ramasse-miettes et le support du débogueur. C'est l'une des sources d'information les plus précieuses sur les symboles dans les binaires Go.
r2gopclntabParser lit cette région via radare2, analyse les structures spécifiques à la version, et soit affiche la liste des fonctions récupérées (avec adresses, fichiers sources et numéros de ligne), soit applique les noms récupérés dans la session radare2 ouverte en tant que définitions de fonctions, flags et commentaires.
| Dépendance | Version minimale |
|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (testé sur 6.0.9) |
| r2pipe | toute |
Aucun autre paquet Python n'est requis. Le script utilise uniquement la bibliothèque standard (struct, json, argparse, os, sys) ainsi que 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
---
## Référence CLI```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
[--apply] [--json] [--files] [--r2pipe]
| Flag | Description |
|---|---|
-f FILE, --file FILE | Chemin vers le binaire Go à analyser. Le script lance sa propre instance r2. |
--r2pipe | Se connecter à une session r2 déjà en cours (pour une utilisation dans la console r2). |
Les flags peuvent être combinés librement. Lorsqu'aucun flag de sortie n'est donné, le comportement par défaut est d'afficher l'en-tête et la liste complète des fonctions.
Tous les exemples ci-dessous ont été exécutés sur un binaire Go 1.26 Mach-O arm64 élagué (construit avec -ldflags="-s -w"). Le programme de test définit main.main, main.fibonacci, main.helloWorld et main.addNumbers. Le compilateur a intégré helloWorld et addNumbers, donc ils n'apparaissent pas dans le gopclntab.
En exécutant sans flag (ou juste -f), on obtient l'en-tête analysé suivi de la table complète des fonctions :```
$ python3 r2_gopclntab.py -f ./gotest_stripped
.
To be precise, I'll just not write anything. However, the API might require a message. I'll send an empty string.```
============================================================
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
Ajoute la progression de l'analyse des sections, les détails de résolution de textStart et les informations de décalage interne :``` $ python3 r2_gopclntab.py -f ./gotest_stripped -v
### Documentation
Pour des instructions d'utilisation complètes et la documentation, consultez : [NetDoc Documentation](https://github.com/```
[*] 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+
...
Filtre la liste des fonctions par correspondance de sous-chaîne. Si une correspondance exacte existe, son adresse est imprimée séparément.
Recherche de sous-chaîne pour toutes les fonctions contenant main. :```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."
VERSION = "2.0"
DESCRIPTION = "Find vulnerabilities easily."
[](https://github.com/example/issues)
- [Fonctionnalités](#features)
- [Installation](#installation)
- [Utilisation](#usage)
- [Exemples](#examples)
## Fonctionnalités
- **Analyse rapide** avec multithreading.
- **Rapports complets** aux formats HTML et PDF.
- **Interface en ligne de commande conviviale** avec sortie colorée.
## Installation
```bash
pip install vulnscanner
vulnscanner scan --target example.com --output report.html
Cet outil est conçu pour les équipes rouges et les chercheurs en sécurité.
Pour commencer, exécutez la commande suivante :
vulnscanner --help
Pour plus de détails, consultez la documentation.
Remarque : Cet outil nécessite Python 3.8 ou supérieur.
Si vous rencontrez des problèmes, veuillez ouvrir un problème sur GitHub.
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)
Recherche de correspondance exacte :```
$ 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
Recherche des internes du runtime liés au GC :```
$ 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)
Recherche de `fmt.` (impression de la bibliothèque standard):```
$ 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) ...
Recherche de `sync.` (primitives de concurrence):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
|
calc.exe```
ADDRESS FUNCTION NAME0x10006FFE0 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) ...
### Sortie JSON (--json)
Sortie lisible par machine pour l'intégration de scripts et de pipelines :```
$ python3 r2_gopclntab.py -f ./gotest_stripped --json
alert
!! Ceci est un travail en cours - loin d'être terminé !!```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
}
### Liste des fichiers sources (--files)
Extrait tous les chemins des fichiers sources intégrés dans le binaire :```
$ 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
### Apply Mode (--apply)
Écrit tous les noms de fonctions récupérés dans la session radare2. Ce qui suit montre une comparaison avant/après sur le binaire dépouillé.
**AVANT** (analyse native r2 sur binaire dépouillé, sans analyse gopclntab) :```
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 a trouvé 1913 fonctions mais n'en a nommé aucune (seulement des étiquettes anonymes fcn.XXXXXXXX). Chercher main.main ou main.fibonacci ne renvoie rien.
Application des symboles gopclntab :``` [] 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)
**APRÈS** (session r2 avec symboles gopclntab appliqués):```
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
Le désassemblage au niveau de main.main montre maintenant le nom et l'emplacement source récupérés :```
;-- 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
Le désassemblage à `main.fibonacci` montre désormais le nom et l'emplacement source récupérés:```
;-- 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
La recherche par nom fonctionne désormais dans la session r2 :``` go.main.main resolves to: 0x1000a0c20 go.main.fibonacci resolves to: 0x1000a0bb0
---
## Résumé des résultats : Mach-O (binaire de test ébarbé)
| Métrique | r2 natif (ébarbé) | Après r2_gopclntab.py |
|---|---|---|
| Fonctions trouvées | 1913 (étiquettes anonymes `fcn.XXXX`) | 2030 (noms complets qualifiés par paquet Go) |
| Fonctions utilisateur identifiées | 0 | `main.main`, `main.fibonacci` avec source + ligne |
| Fichiers source récupérés | 0 | 261 (chemins absolus complets) |
| Symboles nommés | Uniquement les stubs d'importation C (`sym.imp.mmap`, etc.) | Chaque fonction Go étiquetée (`go.main.main`, `go.runtime.gcStart`, etc.) |
| Commentaires source | Aucun | Dans le désassemblage, en ligne `src: /tmp/gotest/main.go:20` |
| Navigable par nom | Non | Oui (`s go.main.main`, `afl~runtime.gc`) |
---
## Test PE : Greenblood, un binaire ransomware Go
L'analyseur a été testé avec un binaire PE réel, Greenblood (`greenblood_1`), un
échantillon de ransomware Go compilé en tant qu'exécutable Windows PE32+ x86-64.
Les binaires PE n'ont pas de section `.gopclntab` dédiée, ce qui met en œuvre le
chemin de repli par balayage des octets magiques.
### Détection et en-tête```
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
No content provided for translation.``` [] 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
Le scanner a trouvé le gopclntab dans la section `.rdata` à `0x568C00`.
Comme il s'agit d'un PE standard (pas PIE), `textStart` est `0x401000` (non nul),
donc la valeur de l'en-tête est utilisée directement pour le calcul d'adresse.
| Champ | Valeur |
|---|---|
| Format | PE32+ x86-64 |
| gopclntab location | `.rdata` à `0x568C00` (trouvé par magic scan) |
| Magic | `0xFFFFFFF1` (Go 1.20+) |
| Pointer size | 8 |
| Quantum (minLC) | 1 (x86) |
| textStart | `0x401000` (depuis l'en-tête) |
| Functions recovered | 2596 |
| Source files | 345 |
### Fonctions du malware récupérées
Recherche du code propre au malware (`main.`) :```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."
Une fois la construction générée, le jeu commence ; le processus est que l'application web (kibana:5601) envoie des alertes aléatoires à la passerelle (gateway:30443) et à la console (console:3000) tout en affichant le scénario total déployé et une table des matières de tous les scénarios disponibles. Après la configuration initiale, les seules options qui doivent être configurées pour chaque utilisateur sont les variables d'environnement Docker suivantes :
ES_USER: par défaut adminES_PASSWORD: par défaut adminKIBANA_URL: par défaut http://kibana:5601KIBANA_USER: par défaut adminKIBANA_PASSWORD: par défaut adminCLOUD_ID: par défaut noneAIOPS_PLUGIN_URL: par défaut http://caddy:8080APISERVER_PORT: par défaut 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)
Toutes les 43 fonctions utilisateur ont été récupérées à partir d'un seul fichier source situé à
`/root/victims/ransom/daf/enc.go`. Les noms des fonctions révèlent immédiatement les
capacités du rançongiciel : gestion des clés, empreinte de la machine, chiffrement
des fichiers avec traversée de chemin, placement de la note de rançon, désactivation des
restaurations, vérification des privilèges, limitation à une seule instance via mutex,
énumération des lecteurs et auto-suppression.
### Dépendances non issues de la bibliothèque standard
Extraction des fichiers sources qui ne font pas partie de la bibliothèque standard Go :```
/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
La seule dépendance externe est golang.org/x/[email protected] pour les appels système spécifiques à Windows (accès au registre, jetons de sécurité, chargement de DLL).
La recherche de crypto révèle 137 fonctions liées à la cryptographie, notamment :```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
- 🪟 [**pivotnacci**](https://github.com/blackarrowsec/pivotnacci): Outil qui permet de faire un tunnel socks vers un serveur HTTP, modélisant les agents en tant que machines à états.
Utilisateurs de FreeBSD
- Si vous avez installé via le port git :
```bash
sudo pkg upgrade git
sudo pkg update
sudo pkg upgrade
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)
Le profil d'utilisation cryptographique : AES (modes CBC et CTR), SHA-256, SHA-512, HMAC, CSHAKE128 et DRBG (générateur de bits aléatoires déterministe). Ceci est cohérent avec les ransomwares qui génèrent des clés de chiffrement par machine dérivées d'une empreinte de la machine, chiffrent les fichiers avec AES-CTR et utilisent HMAC pour l'intégrité.
### Mode d'Application```
$ 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.
Les 2596 fonctions ont été appliquées avec succès, sans aucune omission.
Déterminez instantanément si un binaire est écrit en Go, avec quelle version il a été compilé et quels packages il utilise. La sortie --files révèle la version de la toolchain Go (à partir de chemins comme /usr/local/go/1.26.1/...) et tous les chemins des fichiers sources, y compris les bibliothèques tierces. Pour les malwares, cela révèle immédiatement si l'échantillon utilise crypto/tls, net/http, os/exec ou d'autres packages d'intérêt.
Le cas d'usage central. Les binaires Go stripés avec -ldflags="-s -w" perdent leur table de symboles, mais gopclntab survit. Cet outil restaure chaque nom de fonction, transformant les fonctions anonymes fcn.1000a0c20 en main.main. Cela s'applique aux échantillons de malwares, aux défis CTF, aux binaires de production et à tout exécutable Go stripé.
Les binaires Go embarquent l'intégralité du runtime (généralement 1500 à 2000+ fonctions). Sans noms, le runtime est un mur impénétrable de fonctions anonymes. Avec des noms, vous pouvez localiser instantanément runtime.mallocgc, runtime.gopanic, runtime.newproc, runtime.gcStart et comprendre ce que fait le binaire à chaque point d'appel.
En recherchant main. ou le chemin du package de l'application, vous pouvez isoler uniquement le code utilisateur du runtime. Dans les exemples ci-dessus, filtrer sur main. révèle immédiatement main.main et main.fibonacci parmi 2030 fonctions au total. Vous pouvez également rechercher par noms de packages tiers (par exemple -n "github.com/user/repo") pour identifier les dépendances externes.
Chaque fonction est accompagnée de son chemin de fichier source et du numéro de ligne de début. Cela signifie que vous pouvez recouper le désassemblage avec le code source de la bibliothèque standard Go (qui est ouverte), même lors de l'analyse d'un binaire stripé. Savoir qu'une fonction commence à la ligne 733 de runtime/mgc.go vous permet de lire le code source original en parallèle du désassemblage.
Le mode --json permet l'écriture de scripts. Injectez la sortie dans des importateurs IDA/Ghidra, des outils de comparaison, des générateurs de règles YARA ou toute pipeline d'analyse. Par exemple, pour extraire toutes les fonctions liées à la cryptographie :```bash
python3 r2_gopclntab.py -f sample.exe --json
| jq '.functions[] | select(.name | contains("crypto"))'
### 7. Workflow interactif radare2
Après `--apply`, la session r2 entière devient navigable par nom Go. Vous pouvez vous rendre à des fonctions (`s go.main.main`), chercher dans la liste des fonctions (`afl~runtime.gc`), inspecter les références croisées (`axf go.main.fibonacci`), et voir les commentaires d'emplacement source en ligne dans le résultat du désassemblage (`pd`). Cela transforme r2 d'un désassembleur générique en un environnement d'analyse sensible à Go.
---
## Plateformes supportées et versions de Go
### Formats binaires
| Format | Méthode de découverte de section | Testé |
|---|---|---|
| ELF (Linux) | Nom de section `.gopclntab` ou `.data.rel.ro.gopclntab` | Oui |
| Mach-O (macOS) | Nom de section `__gopclntab` (à l'intérieur du segment `__TEXT`) | Oui |
| PE (Windows) | Scan par octets magiques (pas de section dédiée) | Oui (testé sur Greenblood, un ransomware Go PE64) |
Pour les binaires PE et les binaires ELF/Mach-O agressivement stripés qui manquent l'en-tête de section, l'outil se rabat sur le scan de toutes les sections pour les 4 octets magiques suivis des octets de validation (pad=0, ptrSize dans {4,8}, minLC dans {1,2,4}).
### Versions de Go
| Magique | Version de Go | `textStart` dans l'en-tête | Type de `functab.entry` | Champ `startLine` | Statut |
|---|---|---|---|---|---|
| `0xFFFFFFFB` | 1.2 | Non | `uintptr` (absolu) | Non | Supporté |
| `0xFFFFFFFA` | 1.16 | Non | `uintptr` (absolu) | Non | Supporté |
| `0xFFFFFFF0` | 1.18 - 1.19 | Oui | `uint32` (relatif) | Non | Supporté |
| `0xFFFFFFF1` | 1.20+ | Oui (peut être 0) | `uint32` (relatif) | Oui | Supporté |
La magie `0xFFFFFFF1` est utilisée par Go 1.20 jusqu'à au moins Go 1.26.
---
## Méthodologie
### Stratégie de localisation de section
L'analyseur utilise une approche en deux phases pour trouver les données gopclntab :
**Phase 1 (ELF/Mach-O) :** Interroger la liste des sections de radare2 (`iSj`) et rechercher les sections nommées `.gopclntab`, `.data.rel.ro.gopclntab` ou `__gopclntab`.
**Phase 2 (PE/fallback) :** Si aucune section nommée n'est trouvée, scanner toutes les sections pour les 4 octets magiques. Chaque candidat est validé en vérifiant que les octets 4-7 correspondent au motif attendu : deux octets de padding zéro, une taille de pointeur valide (4 ou 8) et un quantum d'instruction valide (1, 2 ou 4). Cela élimine les faux positifs provenant de motifs d'octets fortuits.
### textStart par rapport à la section .text
À partir de Go >= 1.18, les points d'entrée des fonctions dans le functab sont stockés comme des décalages relatifs. Le calcul de l'adresse virtuelle absolue nécessite une base :```
absolute_addr = base + entryoff
La base est résolue avec la logique suivante :``` 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
Le cas `textStart == 0` se produit dans Go >= 1.22 pour les binaires Mach-O et PIE.
Lorsque cela se produit, les valeurs `entryoff` sont relatives au début de la section `.text`,
donc l'analyseur interroge r2 pour l'adresse virtuelle de `.text` et l'utilise comme base.
Cela a été vérifié sur un binaire Go 1.26 Mach-O arm64 :
- `textStart` dans l'en-tête : `0x0`
- `.text` section vaddr : `0x100001000`
- `functab[1].entryoff` : `0x70`
- Adresse calculée : `0x100001000 + 0x70 = 0x100001070`
- L'analyse native de r2 a confirmé `internal/abi.BoundsDecode` à l'adresse `0x100001070`
### Analyse de structure adaptée à la version
La disposition de la structure `_func` diffère entre Go 1.18 et Go 1.20+. La seule
modification est l'insertion d'un champ `startLine` de 4 octets au décalage 36 dans Go
1.20+, ce qui décale `funcID`, `flag` et `nfuncdata` de 4 octets :
| Champ | Décalage Go 1.18 | Décalage Go 1.20+ |
|---|---|---|
| `entryOff` | 0 | 0 |
| `nameOff` | 4 | 4 |
| `args` | 8 | 8 |
| `cuOffset` | 32 | 32 |
| `startLine` | (absent) | 36 |
| `funcID` | 36 | 40 |
| `flag` | 37 | 41 |
| `nfuncdata` | 39 | 43 |
L'analyseur vérifie le nombre magique pour déterminer quelle disposition utiliser.
### Décodage des données PC
Les indices de fichiers source et les numéros de ligne sont stockés sous forme de
« programmes de données PC » compacts dans la région pctab. Chaque programme encode une
séquence de paires `(value_delta, pc_delta)` en utilisant des entiers de longueur variable
avec un encodage zig-zag pour les valeurs signées. L'analyseur décode ceux-ci pour résoudre :
- Fichier source : `_func.pcfile` -> programme pctab -> index de fichier -> cutab ->
filetab -> chaîne de chemin de fichier
- Numéro de ligne : `_func.pcln` -> programme pctab -> numéro de ligne (plus
le décalage `startLine` pour Go 1.20+)
---
## Limitations
1. **Les fonctions inline** n'apparaissent pas dans la table des fonctions de premier
niveau. Elles sont encodées dans les structures `FUNCDATA_InlTree` / `PCDATA_InlTreeIndex`,
que cet outil ne décode pas actuellement. Dans le binaire de test,
`main.helloWorld` et `main.addNumbers` ont été inline par le compilateur et
n'apparaissent donc pas dans la sortie.
2. **Le support Go 1.2** est fourni au mieux. Le format Go 1.2 est sensiblement
différent (pas de funcnametab séparé, pas de cutab, pointeurs absolus dans functab)
et est rarement rencontré en pratique.
3. **Les architectures big-endian** sont gérées en principe (l'endianness est
détectée à partir des informations binaires de radare2 et utilisée pour toutes les
lectures de structures) mais n'ont pas été testées.
4. Le mode `--apply` crée des stubs de fonctions avec `af+` qui peuvent entrer
en conflit avec l'auto-analyse de r2. L'exécuter sur une session propre (avant ou
à la place de `aaa`) peut donner de meilleurs résultats dans certains cas.
---
## Documentation complémentaire
Une documentation technique détaillée est disponible dans le répertoire `documentation/` :
- [DOCUMENTATION.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/DOCUMENTATION.md) - Documentation complète de l'utilisateur avec tous les modes d'utilisation, formats de sortie et combinaisons de drapeaux.
- [METHODOLOGY.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/METHODOLOGY.md) - Décisions de conception et algorithmes : résolution textStart vs .text, analyse adaptée à la version, stratégie de numérisation PE, décodage des données PC, détails d'intégration r2.
- [GOPCLNTAB_FORMAT.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/GOPCLNTAB_FORMAT.md) - Le format binaire gopclntab à travers les versions de Go : dispositions de structure au niveau octet, modèle mémoire, chaînes de décalages, encodage varint, résumé des différences de version.
---
## Références
- Source du runtime Go (pcHeader) : [go1.20.6/src/runtime/symtab.go#L414](https://github.com/golang/go/blob/go1.20.6/src/runtime/symtab.go#L414)
- Éditeur de liens Go (écrit le format) : [go1.20.6/src/cmd/link/internal/ld/pcln.go](https://github.com/golang/go/blob/go1.20.6/src/cmd/link/internal/ld/pcln.go)
- Mandiant - Golang Internals Symbol Recovery : [mandiant.com/resources/blog/golang-internals-symbol-recovery](https://www.mandiant.com/resources/blog/golang-internals-symbol-recovery)
- Document de conception de la table des symboles Go 1.2 : [docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o](https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub)
| Flag | Description |
|---|
-l, --list | Afficher chaque fonction récupérée avec son adresse. |
-n NAME, --funcname NAME | Afficher uniquement les fonctions dont le nom contient NAME (correspondance de sous-chaîne). Si une correspondance exacte existe, son adresse est affichée séparément. |
-v, --verbose | Afficher les messages de progression, les champs d'en-tête analysés et les décalages internes. |
--apply | Écrire les noms de fonctions récupérés dans la session radare2 en tant que définitions de fonctions (af+), flags dans l'espace de flags go., et commentaires avec le nom Go d'origine et l'emplacement source. |
--json | Sortir l'en-tête et la liste complète des fonctions au format JSON sur stdout. |
--files | Afficher la liste des chemins de fichiers sources extraits de la table des fichiers. |
-h, --help | Afficher le message d'aide. |
30445APISERVER_HOST: par défaut https://gateway.herodotus.devMAX_CONCURRENT_API_CALLS: par défaut 100AIOPS_CONSOLE_TROUBLESHOOTING_MODE: par défaut false