
Uno script per radare2 per analizzare il gopclntab per facilitare il reverse engineering di binari Go.
Nota: Questa funzionalità è stata implementata direttamente nel core di radare2 come plugin di analisi gopc
Un parser gopclntab di Go basato su radare2 per recuperare simboli di funzioni da binari Go, inclusi quelli completamente stripped. Supporta binari ELF, Mach-O e PE su versioni Go 1.2, 1.16, 1.18 e 1.20+.
Ogni binario Go 1.2+ incorpora una regione di dati chiamata gopclntab (Program Counter Line Table) che il runtime di Go utilizza per stack trace, messaggi di panic, garbage collection e supporto al debugger. È una delle fonti più preziose di informazioni sui simboli nei binari Go.
r2gopclntabParser legge questa regione attraverso radare2, analizza le strutture specifiche della versione e stampa l'elenco delle funzioni recuperate (con indirizzi, file sorgente e numeri di riga) oppure applica i nomi recuperati nella sessione aperta di radare2 come definizioni di funzioni, flag e commenti.
| Dipendenza | Versione Minima |
|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (testato su 6.0.9) |
| r2pipe | qualsiasi |
Non sono richiesti altri pacchetti Python. Lo script utilizza solo la libreria standard (struct, json, argparse, os, sys) più 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
## Riferimento CLI```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
[--apply] [--json] [--files] [--r2pipe]
| Flag | Descrizione |
|---|---|
-f FILE, --file FILE | Percorso del binario Go da analizzare. Lo script avvia la propria istanza r2. |
--r2pipe | Si collega a una sessione r2 già in esecuzione (da usare dentro la console r2). |
I flag possono essere combinati liberamente. Se non viene specificato alcun flag di output, il comportamento predefinito è stampare l'intestazione e l'elenco completo delle funzioni.
Tutti gli esempi seguenti sono stati eseguiti su un binario stripped Go 1.26 Mach-O arm64 (compilato con -ldflags="-s -w"). Il programma di test definisce main.main, main.fibonacci, main.helloWorld e main.addNumbers. Il compilatore ha inlineato helloWorld e addNumbers, quindi non appaiono nel gopclntab.
Eseguendo senza flag (o solo con -f) stampa l'intestazione analizzata seguita dalla tabella completa delle funzioni:```
$ python3 r2_gopclntab.py -f ./gotest_stripped
### ☁️ Strumenti per Hacker - MEGA Cloud Drive
Molti strumenti e risorse utilizzati da hacker etici e ricercatori di sicurezza sono disponibili su drive cloud MEGA.```
============================================================
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
Aggiunge il progresso della scansione delle sezioni, i dettagli della risoluzione di textStart e le informazioni sugli offset interni:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -v
Please provide the Markdown content to translate.```
[*] 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+
...
Filtra la lista delle funzioni per corrispondenza di sottostringa. Se esiste una corrispondenza esatta, il suo indirizzo viene stampato separatamente.
Ricerca per sottostringa per tutte le funzioni contenenti main.:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."
(empty response due to missing input)```
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)
Ricerca esatta:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -n "main.fibonacci"
Since no input was provided, there is no text to translate. Please provide the chunk content.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
[+] 1 function(s) shown (filtered from 2030 total)
[+] Exact match: main.fibonacci @ 0x1000A0BB0
Ricerca degli interni del runtime correlati al GC:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
- [Httpx](https://github.com/projectdiscovery/httpx) - Un toolkit HTTP veloce e multiuso che permette di eseguire più probe utilizzando la libreria retryablehttp. Progettato per mantenere l'affidabilità dei risultati con un numero maggiore di thread.
- [Jsubfinder](https://github.com/ThreatUnkown/jsubfinder) - Cerca pagine web alla ricerca di JavaScript e le analizza per individuare sottodomini nascosti e segreti. Supporta regex personalizzate, risultati di ricerca organizzati.
- [Knock](https://github.com/guelfoweb/knock) - Knockpy è uno strumento Python progettato per enumerare i sottodomini su un dominio target tramite una wordlist. Progettato per eseguire la scansione del trasferimento di zona DNS e anche per tentare di bypassare automaticamente il carattere jolly.
- [Lepus](https://github.com/gfek/Lepus) - Una utility per identificare i sottodomini, verificare la presenza di caratteri jolly ed eseguire ricognizione passiva per raccogliere informazioni sul target.
- [Massdns](https://github.com/blechschmidt/massdns) - Un resolver DNS stub ad alte prestazioni per ricerche massive e ricognizione (mass, da cui il nome). Supporta un'ampia gamma di tipi di record DNS e formati di output.
- [Puredns](https://github.com/d3mondev/puredns) - Strumento veloce di risoluzione dei domini e di brute force dei sottodomini in grado di filtrare accuratamente i sottodomini jolly e le voci DNS avvelenate.
- [Raptor](https://github.com/0xdea/raptor) - Strumento che esegue ricognizione DNS con diverse tecniche tra cui brute force DNS, trasferimento di zona DNS, test del carattere jolly DNS ed enumerazione dei tipi di record DNS.
- [Shuffledns](https://github.com/projectdiscovery/shuffledns) - Un wrapper scritto in Go per massdns che consente di enumerare sottodomini validi utilizzando brute force attivo, nonché di risolvere i sottodomini con gestione dei caratteri jolly e un semplice supporto input-output.
- [Subfinder](https://github.com/projectdiscovery/subfinder) - Strumento di scoperta dei sottodomini che individua sottodomini validi per i siti web. Progettato come framework passivo per essere utile per i bug bounty e sicuro per i penetration test.
- [Turbolist3r](https://github.com/alex14324/Turbolist3r) - Strumento di enumerazione dei sottodomini con funzionalità di analisi per i domini scoperti. Utilizza un'architettura client-server per l'enumerazione distribuita e supporta più sorgenti.
- [Turbolist3r](https://github.com/fleetcaptain/Turbolist3r) - Un fork dello strumento di scoperta sottodomini sublist3r che integra funzionalità di brute force dei sottodomini con risoluzione DNS e gestione migliorata dell'output.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
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)
Ricerca di fmt. (stampa della libreria standard):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."
| `-n` | Nome del listener. Se non specificato, viene utilizzato un nome generato a partire dagli argomenti. |
| `-a`, `--address` | Indirizzo del listener. Se non specificato, verrà utilizzato l'indirizzo dell'adattatore. |
| `-p`, `--port` | Porta del listener. |
| `-s`, `--server` | **IMPORTANTE:** Il dominio, l'indirizzo IPv4 o l'indirizzo IPv6 per il server HTML Follina. Per scenari avanzati, potrebbe essere l'indirizzo del redirector. |
| `-P`, `--server-port` | Porta per il server HTML Follina. Predefinita `8080`. |
| `-w`, `--wait` | Tempo di attesa per una connessione in ingresso, in secondi. Predefinito `0` secondi. |
| `-t`, `--type` | Tipo di payload personalizzato da eseguire. Predefinito `custom` (esegue uno script personalizzato codificato in base64). |
| `-d`, `--payload` | Il payload dello script personalizzato da eseguire, richiesto quando `--type` è `custom`. |
| `--http-port` | Porta su cui avviare il server HTTP. Predefinita `80`. |
| `--https-port` | Porta su cui avviare il server HTTPS. Predefinita `443`. |
| `--payload-path` | **IMPORTANTE:** Specifica un percorso del payload da eseguire. (Il percorso relativo inizia con "." esempio: `..\path\to\payload`). |
| `—no-https` | Di default, insieme al server HTTP viene avviata anche un'operazione HTTPS. Questa opzione abilita solo un server HTTP. |```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
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)
...
Ricerca di sync. (primitive di concorrenza):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
* Funziona con il framework MITRE ATT&CK®.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
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)
...
Output leggibile dalla macchina per scripting e integrazione pipeline:``` $ python3 r2_gopclntab.py -f ./gotest_stripped --json
shields.io/badge/HackTheBox-Profile-9cf?style=for-the-badge&logo=HackTheBox&logoColor=white)](https://www.hackthebox.eu/home/users/profile/12345)```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
}
Estrae tutti i percorsi dei file sorgente incorporati nel binario:``` $ python3 r2_gopclntab.py -f ./gotest_stripped --files
[*] HTTP/2 è in uso per la comunicazione
[+] Il target imposta i cookie utilizzando il cookie \x1b[1;34m__cf_bm\x1b[0m, indicativo della gestione dei bot di CloudFlare
[*] Sembra che il target sia protetto da CloudFlare. Prova a usare cloudscraper per aggirare questo.
[!] Cookie 'cf_clearance' mancante, il bypass automatico potrebbe non funzionare. Fallo manualmente...```
Source files (261):
------------------------------------------------------------
/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
<autogenerated>
/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
Scrive tutti i nomi di funzione recuperati nella sessione di radare2. Il seguente mostra un confronto prima/dopo sul binario privo di simboli.
PRIMA (analisi nativa di r2 su binario privo di simboli, senza parsing di 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 ha trovato 1913 funzioni ma non ne ha nominata nessuna (solo etichette anonime `fcn.XXXXXXXX`). La ricerca di `main.main` o `main.fibonacci` non restituisce nulla. **Applicazione dei simboli 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)
DOPO (sessione r2 con simboli gopclntab applicati):``` 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
Il disassemblaggio a `main.main` ora mostra il nome recuperato e la posizione del sorgente:```
;-- 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
Il disassemblaggio a main.fibonacci ora mostra il nome e la posizione del sorgente recuperati:```
;-- 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 ricerca per nome ora funziona nella sessione r2:```
go.main.main resolves to: 0x1000a0c20
go.main.fibonacci resolves to: 0x1000a0bb0
Il parser è stato testato contro un binario PE reale, Greenblood (greenblood_1), un campione di ransomware in Go compilato come eseguibile Windows PE32+ x86-64. I binari PE non hanno una sezione .gopclntab dedicata, quindi questo esercita il percorso di fallback di scansione dei byte magici.
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
$ cat src/sh/jarm.sh ... ... $ sh src/sh/jarm.sh google.com | grep JARM JARM: 27d40d40d00040d000042d43d0000009e9f4e9c120f97792cbd22bfb3f192 ✅ Host google.com JARM: 27d40d40d00040d000042d43d0000009e9f4e9c120f97792cbd22bfb3f192 Esempio di analisi della cronologia dei certificati in tempo reale:
[*] 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
```
Lo scanner ha trovato il gopclntab all'interno della sezione `.rdata` a `0x568C00`.
Poiché questo è un PE standard (non PIE), `textStart` è `0x401000` (non zero), quindi il valore dell'intestazione viene utilizzato direttamente per il calcolo dell'indirizzo.
| Campo | Valore |
|---|---|
| Formato | PE32+ x86-64 |
| Posizione gopclntab | `.rdata` a `0x568C00` (trovato tramite scansione magica) |
| Magic | `0xFFFFFFF1` (Go 1.20+) |
| Dimensione puntatore | 8 |
| Quantum (minLC) | 1 (x86) |
| textStart | `0x401000` (dall'intestazione) |
| Funzioni recuperate | 2596 |
| File sorgenti | 345 |
### Funzioni del malware recuperate
Ricerca del codice del malware stesso (`main.`):```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."
```
Esegui lo script `crypt.py` con il comando `python3 crypt.py` nel terminale. Assicurati di avere le dipendenze necessarie installate (`pycryptodome`, `requests`, `colorama`).```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x4D91E0 main.init (<autogenerated>: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)
```
Tutte le 43 funzioni utente sono state recuperate da un unico file sorgente in
`/root/victims/ransom/daf/enc.go`. I nomi delle funzioni rivelano immediatamente le
capacità del ransomware: gestione delle chiavi, fingerprinting della macchina,
crittografia dei file con traversal di path, posizionamento della nota di riscatto,
disabilitazione del ripristino, controllo dei privilegi, esecuzione di singola istanza
basata su mutex, enumerazione delle unità e auto-eliminazione.
### Dipendenze non standard della libreria
Estrazione dei file sorgente che non fanno parte della libreria standard di 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
```
L'unica dipendenza esterna è `golang.org/x/[email protected]` per le syscall specifiche di Windows (accesso al registro, token di sicurezza, caricamento DLL).
### Funzioni crittografiche
La ricerca di `crypto` rivela 137 funzioni legate a crittografia, tra cui:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
```
[No content to translate.]```
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)
```
Il profilo di utilizzo crittografico: AES (modalità CBC e CTR), SHA-256, SHA-512, HMAC, CSHAKE128 e DRBG (generatore deterministico di bit casuali). Ciò è coerente con ransomware che genera chiavi di crittografia per-macchina derivate da un'impronta della macchina, crittografa file con AES-CTR e utilizza HMAC per l'integrità.
### Apply Mode```
$ 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.
```
Tutte le 2596 funzioni applicate con successo, nessuna saltata.
---
## Casi d'uso per il Reverse Engineering
### 1. Triage e Identificazione
Determina istantaneamente se un binario è Go, con quale versione è stato compilato e
quali pacchetti utilizza. L'output `--files` rivela la versione del toolchain Go
(da percorsi file come `/usr/local/go/1.26.1/...`) e ogni percorso di file sorgente,
incluse le librerie di terze parti. Per il malware, questo rivela immediatamente se
il campione utilizza `crypto/tls`, `net/http`, `os/exec` o altri pacchetti di
interesse.
### 2. Recupero dei Simboli su Binari Strippati
Il caso d'uso principale. I binari Go strippati con `-ldflags="-s -w"` perdono la
tabella dei simboli, ma gopclntab sopravvive. Questo strumento ripristina ogni nome di funzione,
trasformando anonime `fcn.1000a0c20` in `main.main`. Questo si applica a
campioni di malware, sfide CTF, binari di produzione e qualsiasi eseguibile Go
strippato.
### 3. Navigazione del Runtime Go
I binari Go incorporano l'intero runtime (tipicamente da 1500 a oltre 2000 funzioni).
Senza nomi, il runtime è un muro impenetrabile di funzioni anonime.
Con i nomi, puoi individuare istantaneamente `runtime.mallocgc`, `runtime.gopanic`,
`runtime.newproc`, `runtime.gcStart` e capire cosa sta facendo il binario
in ogni punto di chiamata.
### 4. Separazione del Codice Utente dal Runtime
Cercando `main.` o il percorso del pacchetto dell'applicazione, puoi isolare
solo il codice dell'utente dal runtime. Negli esempi sopra, filtrando per
`main.` si rivelano immediatamente `main.main` e `main.fibonacci` tra 2030
funzioni totali. Puoi anche cercare per nome di pacchetto di terze parti
(es., `-n "github.com/user/repo"`) per identificare dipendenze esterne.
### 5. Contesto a Livello di Sorgente
Ogni funzione viene fornita con il percorso del file sorgente e il numero di riga di inizio. Questo
significa che puoi incrociare il disassemblaggio con il codice sorgente della libreria standard
Go (che è aperto), anche quando analizzi un binario strippato. Sapere
che una funzione inizia alla riga 733 di `runtime/mgc.go` ti permette di leggere il
sorgente originale insieme al disassemblaggio.
### 6. Pipeline e Automazione
La modalità `--json` abilita lo scripting. Invia l'output a importatori per IDA/Ghidra,
strumenti di diff, generatori di regole YARA o qualsiasi pipeline di analisi.
Ad esempio, per estrarre tutte le funzioni relative alla crittografia:```bash
python3 r2_gopclntab.py -f sample.exe --json \
| jq '.functions[] | select(.name | contains("crypto"))'
```
### 7. Flusso di lavoro interattivo con radare2
Dopo `--apply`, l'intera sessione r2 diventa navigabile per nome Go. Puoi
posizionarti sulle funzioni (`s go.main.main`), cercare nell'elenco delle
funzioni (`afl~runtime.gc`), ispezionare i riferimenti incrociati
(`axf go.main.fibonacci`) e vedere i commenti sulla posizione del codice
sorgente direttamente nell'output del disassemblatore (`pd`). Questo
trasforma r2 da un disassemblatore generico in un ambiente di analisi
consapevole di Go.
---
## Piattaforme supportate e versioni di Go
### Formati binari
| Formato | Metodo di individuazione delle sezioni | Testato |
|---|---|---|
| ELF (Linux) | Nome sezione `.gopclntab` o `.data.rel.ro.gopclntab` | Sì |
| Mach-O (macOS) | Nome sezione `__gopclntab` (all'interno del segmento `__TEXT`) | Sì |
| PE (Windows) | Scansione dei byte magici (nessuna sezione dedicata) | Sì (testato su Greenblood, un ransomware Go PE64) |
Per i binari PE e i binari ELF/Mach-O fortemente stripati privi dell'intestazione
della sezione, lo strumento ricade sulla scansione di tutte le sezioni alla
ricerca dei 4 byte magici seguiti dai byte di validazione (pad=0, ptrSize in {4,8},
minLC in {1,2,4}).
### Versioni di Go
| Magic | Versione Go | `textStart` nell'intestazione | Tipo `functab.entry` | Campo `startLine` | Stato |
|---|---|---|---|---|---|
| `0xFFFFFFFB` | 1.2 | No | `uintptr` (assoluto) | No | Supportato |
| `0xFFFFFFFA` | 1.16 | No | `uintptr` (assoluto) | No | Supportato |
| `0xFFFFFFF0` | 1.18 - 1.19 | Sì | `uint32` (relativo) | No | Supportato |
| `0xFFFFFFF1` | 1.20+ | Sì (può essere 0) | `uint32` (relativo) | Sì | Supportato |
Il magic `0xFFFFFFF1` è utilizzato da Go 1.20 fino almeno a Go 1.26.
---
## Metodologia
### Strategia di localizzazione delle sezioni
Il parser utilizza un approccio a due fasi per trovare i dati gopclntab:
**Fase 1 (ELF/Mach-O):** Interroga l'elenco delle sezioni di radare2 (`iSj`) e cerca
le sezioni denominate `.gopclntab`, `.data.rel.ro.gopclntab` o `__gopclntab`.
**Fase 2 (PE/fallback):** Se non viene trovata alcuna sezione nominata, scansiona
tutte le sezioni alla ricerca dei 4 byte magici. Ogni candidato viene validato
verificando che i byte 4-7 corrispondano allo schema atteso: due byte di padding
zero, una dimensione del puntatore valida (4 o 8) e un quanto di istruzione
valido (1, 2 o 4). Questo elimina i falsi positivi derivanti da pattern
byte coincidenti.
### textStart contro la sezione .text
In Go >= 1.18, gli indirizzi di ingresso delle funzioni nella functab sono
memorizzati come offset relativi. Il calcolo dell'indirizzo virtuale assoluto
richiede una base:```
absolute_addr = base + entryoff
```
La base viene risolta con la seguente logica:```
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
```
Il caso `textStart == 0` si verifica in Go >= 1.22 per binari Mach-O e PIE.
Quando ciò accade, i valori di `entryoff` sono relativi all'inizio della sezione `.text`,
quindi il parser interroga r2 per l'indirizzo virtuale di `.text` e lo utilizza
come base.
Ciò è stato verificato su un binario Go 1.26 Mach-O arm64:
- `textStart` nell'header: `0x0`
- vaddr della sezione `.text`: `0x100001000`
- `functab[1].entryoff`: `0x70`
- Indirizzo calcolato: `0x100001000 + 0x70 = 0x100001070`
- L'analisi nativa di r2 ha confermato `internal/abi.BoundsDecode` a `0x100001070`
### Parsing Strutturale Versione-Consapevole
Il layout della struct `_func` differisce tra Go 1.18 e Go 1.20+. L'unica
modifica è l'inserimento di un campo `startLine` di 4 byte all'offset 36 in Go
1.20+, che sposta `funcID`, `flag` e `nfuncdata` di 4 byte:
| Campo | Offset Go 1.18 | Offset Go 1.20+ |
|---|---|---|
| `entryOff` | 0 | 0 |
| `nameOff` | 4 | 4 |
| `args` | 8 | 8 |
| `cuOffset` | 32 | 32 |
| `startLine` | (assente) | 36 |
| `funcID` | 36 | 40 |
| `flag` | 37 | 41 |
| `nfuncdata` | 39 | 43 |
Il parser controlla il numero magico per determinare quale layout utilizzare.
### Decodifica dei Dati PC
Gli indici dei file sorgente e i numeri di riga sono memorizzati come "programmi dati PC" compatti
nella regione pctab. Ogni programma codifica una sequenza
di coppie `(value_delta, pc_delta)` utilizzando interi a lunghezza variabile con codifica
zig-zag per i valori con segno. Il parser decodifica questi per risolvere:
- File sorgente: `_func.pcfile` -> programma pctab -> indice file -> cutab ->
filetab -> stringa del percorso del file
- Numero di riga: `_func.pcln` -> programma pctab -> numero di riga (più
l'offset `startLine` per Go 1.20+)
---
## Limitazioni
1. **Le funzioni inline** non appaiono nella tabella delle funzioni di primo livello. Esse
sono codificate nelle strutture `FUNCDATA_InlTree` / `PCDATA_InlTreeIndex`,
che questo strumento attualmente non decodifica. Nel binario di test,
`main.helloWorld` e `main.addNumbers` sono state inline dal compilatore e quindi
non compaiono nell'output.
2. **Il supporto Go 1.2** è al meglio. Il formato Go 1.2 è sostanzialmente
diverso (nessun funcnametab separato, nessun cutab, puntatori assoluti in functab)
e si incontra raramente nella pratica.
3. **Architetture big-endian** sono gestite in linea di principio (l'endianess è
rilevata dalle informazioni binarie di radare2 e utilizzata per tutte le letture di struct)
ma non sono state testate.
4. La modalità `--apply` crea stub di funzione con `af+` che possono entrare in conflitto
con l'auto-analisi di r2. Eseguirla su una sessione pulita (prima o
invece di `aaa`) può dare risultati migliori in alcuni casi.
---
## Documentazione Aggiuntiva
La documentazione tecnica dettagliata è disponibile nella directory `documentation/`:
- [DOCUMENTATION.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/DOCUMENTATION.md) - Documentazione utente completa
con tutte le modalità di utilizzo, i formati di output e le combinazioni di flag.
- [METHODOLOGY.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/METHODOLOGY.md) - Decisioni progettuali e
algoritmi: risoluzione textStart vs .text, parsing versione-consapevole,
strategia di scansione PE, decodifica dati PC, dettagli integrazione r2.
- [GOPCLNTAB_FORMAT.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/GOPCLNTAB_FORMAT.md) - Il formato binario gopclntab
attraverso le versioni di Go: layout delle struct a livello di byte, modello di memoria,
catene di offset, codifica varint, riepilogo differenze tra versioni.
---
## Riferimenti
- Sorgente 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)
- Linker Go (scrive il formato): [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)
- Documento di progettazione tabella simboli Go 1.2: [docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o](https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub)
| Flag | Descrizione |
|---|
-l, --list | Stampa ogni funzione recuperata con il suo indirizzo. |
-n NAME, --funcname NAME | Stampa solo le funzioni il cui nome contiene NAME (corrispondenza parziale). Se esiste una corrispondenza esatta, il suo indirizzo viene stampato separatamente. |
-v, --verbose | Stampa messaggi di avanzamento, campi dell'intestazione analizzati e offset interni. |
--apply | Scrive i nomi delle funzioni recuperate nella sessione radare2 come definizioni di funzione (af+), flag nel flagspace go. e commenti con il nome Go originale e la posizione nel codice sorgente. |
--json | Invia l'intestazione e l'elenco completo delle funzioni in formato JSON su stdout. |
--files | Stampa l'elenco dei percorsi dei file sorgente estratti dalla tabella dei file. |
-h, --help | Mostra il messaggio di aiuto. |
| Metrica | r2 Nativo (senza simboli) | Dopo r2_gopclntab.py |
|---|
| Funzioni trovate | 1913 (etichette anonime fcn.XXXX) | 2030 (nomi completi con pacchetto Go) |
| Funzioni utente identificate | 0 | main.main, main.fibonacci con sorgente + riga |
| File sorgente recuperati | 0 | 261 (percorsi assoluti completi) |
| Simboli denominati | Solo stub di import C (sym.imp.mmap, ecc.) | Ogni funzione Go etichettata (go.main.main, go.runtime.gcStart, ecc.) |
| Commenti sorgente | Nessuno | In linea src: /tmp/gotest/main.go:20 nel disassemblato |
| Navigabile per nome | No | Sì (s go.main.main, afl~runtime.gc) |