
Скрипт для radare2, который парсит gopclntab для облегчения реверс-инжиниринга Go-бинарных файлов.
Примечание: Эта функция была напрямую реализована в ядре radare2 как плагин анализа gopc
Парсер gopclntab на основе radare2 для восстановления символов функций из Go-бинарников, включая полностью лишённые символьной информации. Поддерживает ELF, Mach-O и PE бинарники для версий Go 1.2, 1.16, 1.18 и 1.20+.
Каждый Go-бинарник версии 1.2+ содержит область данных, называемую gopclntab (таблица строк программы и счётчика команд), которую рантайм Go использует для стековых трасс, сообщений паники, сборки мусора и поддержки отладчика. Это один из наиболее ценных источников информации о символах в Go-бинарниках.
r2gopclntabParser считывает эту область через radare2, анализирует версионно-зависимые структуры и либо выводит восстановленный список функций (с адресами, исходными файлами и номерами строк), либо применяет восстановленные имена обратно в открытую сессию radare2 в качестве определений функций, флагов и комментариев.
| Зависимость | Минимальная версия |
|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (протестировано на 6.0.9) |
| r2pipe | любая |
Других пакетов Python не требуется. Скрипт использует только стандартную библиотеку (struct, json, argparse, os, sys) и 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```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
[--apply] [--json] [--files] [--r2pipe]
| Flag | Description |
|---|---|
-f FILE, --file FILE | Путь к Go-бинарному файлу для анализа. Скрипт запускает собственный экземпляр r2. |
--r2pipe | Подключиться к уже запущенной сессии r2 (для использования внутри консоли r2). |
Флаги можно свободно комбинировать. Если не указан флаг вывода, поведение по умолчанию — вывод заголовка и полного списка функций.
Все примеры ниже были запущены на обезображенном бинарном файле Go 1.26 Mach-O arm64
(собранном с -ldflags="-s -w"). Тестовая программа определяет main.main,
main.fibonacci, main.helloWorld и main.addNumbers. Компилятор
встроил helloWorld и addNumbers, поэтому они не появляются в gopclntab.
Запуск без флагов (или только с -f) выводит разобранный заголовок, а затем
полную таблицу функций:```
$ python3 r2_gopclntab.py -f ./gotest_stripped
[Вики](https://github.com/ainrm/OffensivePipelineCD/wiki) • [Установка](https://github.com/ainrm/OffensivePipelineCD/wiki/Installation) • [Загрузки](https://github.com/ainrm/OffensivePipelineCD/wiki/Downloads) • [Плагины](https://github.com/ainrm/OffensivePipelineCD/wiki/Development)```
============================================================
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
Добавляет ход сканирования разделов, детали разрешения textStart и информацию о внутренних смещениях:``` $ 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+
...
Фильтрует список функций по совпадению подстроки. Если найдено точное совпадение, его адрес выводится отдельно.
Поиск подстроки для всех функций, содержащих main.:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."
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)
```
Точный поиск совпадений:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main.fibonacci"
```
Ввод:```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
[+] 1 function(s) shown (filtered from 2030 total)
[+] Exact match: main.fibonacci @ 0x1000A0BB0
```
Поиск внутренних механизмов среды выполнения, связанных с GC:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
```
Все авторские права на этот инструмент принадлежат оригинальному автору [John Hammond](https://github.com/JohnHammond), который создал `msf-vs-mimikatz`. В этом репозитории мы улучшили исходную концепцию, добавив больше сигнатур антивируса из Microsoft Defender, а также новые типы полезной нагрузки и в целом лучшую среду разработки с `Python.````
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)
```
Поиск `fmt.` (печать из стандартной библиотеки):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."
```
Помните, что вы должны ввести свои учетные данные в **accounts.json**. Мы также включили `accounts_example.json` в качестве примера настройки файла. Обратите внимание, что при настройке учетных записей для Microsoft Teams GraphRunner поддерживает отправку сообщений через Teams, а также чтение сообщений, но поддерживается только делегированная аутентификация.
Разрабатываются новые модули как для Teams, так и для Azure, и ожидается, что инструмент будет расти.
### Использование по умолчанию
Во-первых, если вы еще этого не сделали, вам потребуется установить **Microsoft Graph PowerShell SDK**, инструкции по установке можно найти [здесь](https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation?view=graph-powershell-1.0).
Войдите в Graph Runner, выполнив команду `Connect-Graph` и введя свои учетные данные. Если вы хотите войти с помощью приложения или субъекта-службы (Service Principal), используйте модуль `Connect-ClientSecret`.
Модули разделены на две категории: `Azure` и `MicrosoftTeams`. Импортируйте модули в зависимости от ваших потребностей:```
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)
...
```
Поиск `sync.` (примитивы синхронизации):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
```
MD5, SHA1, SHA256.
- Время автоматически генерируется **`pipal`** в формате `2020-05-21 18:33:59`.
### Вывод
Результаты pipal будут записаны в каталог `./pipal_output` в файл `HASH.output`.
Для быстрого просмотра результатов через `les`, `bat` (или `cat`):
```bash
les ./pipal_output/*.output
```
```bash
bat ./pipal_output/*.output
```
> **Примечание**: При повторном запуске с тем же файлом слов в качестве входных данных будет запрошено подтверждение на перезапись результатов pipal `[y/n]`. Если вы откажетесь от перезаписи, можно продолжить использовать предыдущие результаты.
> **Совет**: Чтобы увидеть результаты pipal, не дожидаясь завершения hashcat, вы можете запустить `pipal` непосредственно на potfile, используя:
> ```bash
> pipal potfile [другие параметры]
> ```
> Этот подход удобен, если вам нужен только статистический анализ без накладных расходов на вычисления hashcat.```
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)
...
```
### Вывод в формате JSON (--json)
Машиночитаемый вывод для интеграции со скриптами и конвейерами:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --json
```
Please provide the Markdown content to translate.```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
}
```
### Список исходных файлов (--files)
Извлекает все пути к исходным файлам, внедрённые в бинарный файл:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --files
```
как строки (по умолчанию), или как обобщённые примитивные **инструкции**, а также блоки (в зависимости от параметров в `parser.cpp` и предпочтений инструкций, жестко закодированных для других бэкендов в `asm.cpp`). Сохраните созданный файл как `asm.bin` на диске в текущей директории.```
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
```
### Режим применения (--apply)
Записывает все восстановленные имена функций в сессию radare2. Ниже показано сравнение до/после для бинарного файла с удалёнными символами.
**ДО** (родной анализ r2 бинарного файла с удалёнными символами, без парсинга 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 нашёл 1913 функций, но не назвал ни одну из них (только анонимные метки `fcn.XXXXXXXX`). Поиск `main.main` или `main.fibonacci` ничего не возвращает.
**Применяя символы 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)
```
**ПОСЛЕ** (сессия r2 с примененными символами gopclntab):```
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
```
Дизассемблирование в `main.main` теперь показывает восстановленное имя и исходное местоположение:```
;-- 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
```
Дизассемблирование в `main.fibonacci` теперь показывает восстановленное имя и исходное расположение:```
;-- 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
```
Поиск по имени теперь работает в сессии r2:```
go.main.main resolves to: 0x1000a0c20
go.main.fibonacci resolves to: 0x1000a0bb0
```
---
## Сводка результатов: Mach-O (очищенный тестовый бинарник)
| Метрика | r2 Native (без символов) | После r2_gopclntab.py |
|---|---|---|
| Найдено функций | 1913 (анонимные метки `fcn.XXXX`) | 2030 (полные имена с указанием пакета Go) |
| Идентифицировано пользовательских функций | 0 | `main.main`, `main.fibonacci` с исходным кодом и строкой |
| Восстановлено исходных файлов | 0 | 261 (полные абсолютные пути) |
| Именованные символы | Только заглушки импорта C (`sym.imp.mmap` и т.д.) | Каждая функция Go помечена (`go.main.main`, `go.runtime.gcStart` и т.д.) |
| Комментарии к исходному коду | Нет | Встроенный `src: /tmp/gotest/main.go:20` в дизассемблере |
| Навигация по имени | Нет | Да (`s go.main.main`, `afl~runtime.gc`) |
---
## PE-тест: Greenblood, бинарник программы-вымогателя на Go
Парсер был протестирован на реальном PE-бинарнике Greenblood (`greenblood_1`), образце программы-вымогателя на Go, скомпилированном как PE32+ x86-64 исполняемый файл Windows. PE-бинарники не имеют выделенного раздела `.gopclntab`, поэтому здесь задействуется резервный путь сканирования магических байтов.
### Обнаружение и заголовок```
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
```
Please provide the Markdown content to translate.```
[*] 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
```
Сканер обнаружил gopclntab внутри секции `.rdata` по адресу `0x568C00`.
Поскольку это стандартный PE (не PIE), `textStart` равен `0x401000` (ненулевое значение),
поэтому значение заголовка используется непосредственно для вычисления адреса.
| Поле | Значение |
|---|---|
| Формат | PE32+ x86-64 |
| Расположение gopclntab | `.rdata` по адресу `0x568C00` (найдено магическим сканированием) |
| Магическое число | `0xFFFFFFF1` (Go 1.20+) |
| Размер указателя | 8 |
| Квант (minLC) | 1 (x86) |
| textStart | `0x401000` (из заголовка) |
| Восстановлено функций | 2596 |
| Исходные файлы | 345 |
### Восстановленные функции вредоносного ПО
Поиск собственного кода вредоносного ПО (`main.`):```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."
```
## Основные функции
Хуки позволяют подключаться к различным событиям жизненного цикла, а асинхронные хуки — к асинхронным событиям.```
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)
```
Все 43 пользовательские функции были восстановлены из одного исходного файла по адресу
`/root/victims/ransom/daf/enc.go`. Названия функций сразу раскрывают
возможности вымогателя: управление ключами, снятие отпечатков машины, шифрование
файлов с обходом каталогов, размещение записки о выкупе, отключение восстановления,
проверка привилегий, обеспечение единственного экземпляра на основе мьютекса, перечисление дисков
и самоудаление.
### Нестандартные зависимости библиотек
Извлечение исходных файлов, не входящих в стандартную библиотеку 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
```
Единственная внешняя зависимость — `golang.org/x/[email protected]` для системных вызовов, специфичных для Windows (доступ к реестру, маркеры безопасности, загрузка DLL).
### Криптографические функции
Поиск по `crypto` выявляет 137 функций, связанных с криптографией, включая:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
```
| Tool Name | Description | Link |
|-----------|-------------|------|
| Loki | Простой сканер IOC и реагирования на инциденты | [](https://github.com/Neo23x0/Loki) |
| Local Exploit Suggester | Инструмент аудита повышения привилегий в Linux | [](https://github.com/AlessandroZ/BeRoot) |
| Lorg | Инструмент для расширенного логирования HTTP-ответов | [](https://github.com/jensvoid/lorg) |
| LyncSniper | Инструмент для тестирования на проникновение Lync/Skype for Business | [](https://github.com/nyxgeek/lync-sniper) |
| Lynis | Инструмент аудита безопасности для Linux, macOS и UNIX-систем | [](https://github.com/CISOfy/lynis) |
| Machinae | Инструмент для сбора разведданных из публичных сайтов/API | [](https://github.com/HurricaneLabs/machinae) |
| MacQRL | Фреймворк для быстрого реагирования macOS и Linux / живого реагирования | [](https://github.com/themitten/macqrl) |
| MailSniper | Инструмент для поиска по электронной почте в среде Microsoft Exchange по заданным терминам | [](https://github.com/dafthack/MailSniper) |
| malboxes | Создаёт виртуальные машины Windows для анализа вредоносного ПО | [](https://github.com/beahive/malboxes) |
| Maligner | Инструмент для обфускации вредоносного ПО Android | [](https://github.com/geekspeed/maligner) |```
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)
```
Профиль использования криптографии: AES (режимы CBC и CTR), SHA-256, SHA-512, HMAC, CSHAKE128 и DRBG (детерминированный генератор случайных битов). Это согласуется с программами-вымогателями, которые генерируют уникальные для каждого устройства ключи шифрования на основе отпечатка устройства, шифруют файлы с помощью AES-CTR и используют HMAC для целостности.
### Режим применения```
$ 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.
```
Все 2596 функций успешно применены, ни одна не пропущена.
---
## Сценарии использования для обратной разработки
### 1. Триаж и идентификация
Мгновенно определите, является ли бинарный файл Go-приложением, с какой версией он был собран и какие пакеты использует. Вывод с флагом `--files` показывает версию инструментария Go (из путей вида `/usr/local/go/1.26.1/...`) и все пути исходных файлов, включая сторонние библиотеки. Для вредоносного ПО это сразу показывает, использует ли образец `crypto/tls`, `net/http`, `os/exec` или другие интересующие пакеты.
### 2. Восстановление символов в обрезанных бинарных файлах
Основной сценарий использования. Go-бинарные файлы, обрезанные с помощью `-ldflags="-s -w"`, теряют таблицу символов, но `gopclntab` сохраняется. Этот инструмент восстанавливает имена всех функций, превращая анонимные `fcn.1000a0c20` обратно в `main.main`. Это применимо к образцам вредоносного ПО, задачам CTF, промышленным бинарным файлам и любым обрезанным исполняемым файлам Go.
### 3. Навигация по среде выполнения Go
Go-бинарные файлы включают в себя всю среду выполнения (обычно от 1500 до 2000+ функций). Без имён среда выполнения — это непроницаемая стена анонимных функций. С именами вы можете мгновенно найти `runtime.mallocgc`, `runtime.gopanic`, `runtime.newproc`, `runtime.gcStart` и понять, что делает бинарный файл в каждой точке вызова.
### 4. Отделение пользовательского кода от среды выполнения
Поиск по `main.` или пути пакета приложения позволяет изолировать только пользовательский код от среды выполнения. В приведённых выше примерах фильтрация по `main.` сразу выявляет `main.main` и `main.fibonacci` среди 2030 функций. Также можно искать по именам сторонних пакетов (например, `-n "github.com/user/repo"`), чтобы определить внешние зависимости.
### 5. Контекст на уровне исходного кода
Каждая функция сопровождается путём к исходному файлу и номером начальной строки. Это означает, что вы можете сопоставить дизассемблированный код с исходным кодом стандартной библиотеки Go (которая открыта), даже при анализе обрезанного бинарного файла. Знание того, что функция начинается со строки 733 в `runtime/mgc.go`, позволяет читать оригинальный исходный код вместе с дизассемблированным.
### 6. Конвейеры и автоматизация
Режим `--json` позволяет создавать сценарии. Передавайте вывод в импортёры IDA/Ghidra, инструменты сравнения, генераторы правил YARA или любой аналитический конвейер. Например, для извлечения всех криптографических функций:```bash
python3 r2_gopclntab.py -f sample.exe --json \
| jq '.functions[] | select(.name | contains("crypto"))'
```
### 7. Интерактивный рабочий процесс radare2
После `--apply` вся сессия r2 становится навигационной по именам Go. Вы можете
переходить к функциям (`s go.main.main`), искать в списке функций
(`afl~runtime.gc`), просматривать перекрёстные ссылки (`axf go.main.fibonacci`) и
видеть комментарии с расположением исходного кода прямо в выводе дизассемблера (`pd`). Это
превращает r2 из обычного дизассемблера в среду анализа, понимающую Go.
---
## Поддерживаемые платформы и версии Go
### Бинарные форматы
| Формат | Метод обнаружения секции | Протестировано |
|---|---|---|
| ELF (Linux) | Имя секции `.gopclntab` или `.data.rel.ro.gopclntab` | Да |
| Mach-O (macOS) | Имя секции `__gopclntab` (внутри сегмента `__TEXT`) | Да |
| PE (Windows) | Сканирование по магическим байтам (нет выделенной секции) | Да (протестировано на Greenblood, Go-шифровальщик PE64) |
Для PE-бинарников и агрессивно обрезанных ELF/Mach-O, у которых отсутствует
заголовок секции, инструмент переключается на сканирование всех секций на наличие 4-байтовой
магии с последующими проверочными байтами (pad=0, ptrSize в {4,8}, minLC в
{1,2,4}).
### Версии Go
| Магия | Версия Go | `textStart` в заголовке | Тип `functab.entry` | Поле `startLine` | Статус |
|---|---|---|---|---|---|
| `0xFFFFFFFB` | 1.2 | Нет | `uintptr` (абсолютный) | Нет | Поддерживается |
| `0xFFFFFFFA` | 1.16 | Нет | `uintptr` (абсолютный) | Нет | Поддерживается |
| `0xFFFFFFF0` | 1.18 – 1.19 | Да | `uint32` (относительный) | Нет | Поддерживается |
| `0xFFFFFFF1` | 1.20+ | Да (может быть 0) | `uint32` (относительный) | Да | Поддерживается |
Магия `0xFFFFFFF1` используется начиная с Go 1.20 и как минимум до Go 1.26.
---
## Методология
### Стратегия поиска секции
Парсер использует двухфазный подход для поиска данных gopclntab:
**Фаза 1 (ELF/Mach-O):** Запросить список секций radare2 (`iSj`) и найти
секции с именами `.gopclntab`, `.data.rel.ro.gopclntab` или `__gopclntab`.
**Фаза 2 (PE/резервный):** Если именованная секция не найдена, просканировать все секции
на наличие 4-байтовых магических байтов. Каждый кандидат проверяется по тому, что
байты 4-7 соответствуют ожидаемому шаблону: два нулевых байта заполнения, допустимый размер
указателя (4 или 8) и допустимый квант инструкции (1, 2 или 4). Это
исключает ложные срабатывания из-за случайных совпадений байтов.
### textStart в сравнении с секцией .text
В Go >= 1.18 точки входа функций в functab хранятся как относительные
смещения. Для вычисления абсолютного виртуального адреса требуется база:```
absolute_addr = base + entryoff
```
База разрешается следующей логикой:```
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
```
Случай `textStart == 0` возникает в Go >= 1.22 для бинарных файлов Mach-O и PIE.
В этом случае значения `entryoff` задаются относительно начала секции `.text`, поэтому анализатор запрашивает у r2 виртуальный адрес `.text` и использует его в качестве базы.
Это было проверено на бинарном файле Go 1.26 Mach-O arm64:
- `textStart` в заголовке: `0x0`
- `.text` vaddr секции: `0x100001000`
- `functab[1].entryoff`: `0x70`
- Вычисленный адрес: `0x100001000 + 0x70 = 0x100001070`
- Нативный анализ r2 подтвердил `internal/abi.BoundsDecode` по адресу `0x100001070`
### Разбор структур с учетом версии
Макет структуры `_func` различается между Go 1.18 и Go 1.20+. Единственное изменение – вставка 4-байтового поля `startLine` со смещением 36 в Go 1.20+, что сдвигает `funcID`, `flag` и `nfuncdata` на 4 байта:
| Поле | Смещение в Go 1.18 | Смещение в Go 1.20+ |
|---|---|---|
| `entryOff` | 0 | 0 |
| `nameOff` | 4 | 4 |
| `args` | 8 | 8 |
| `cuOffset` | 32 | 32 |
| `startLine` | (отсутствует) | 36 |
| `funcID` | 36 | 40 |
| `flag` | 37 | 41 |
| `nfuncdata` | 39 | 43 |
Анализатор проверяет магическое число, чтобы определить, какой макет использовать.
### Декодирование PC-данных
Индексы исходных файлов и номера строк хранятся в виде компактных "PC-программ" в области pctab. Каждая программа кодирует последовательность пар `(value_delta, pc_delta)` с использованием целых чисел переменной длины и зигзагообразного кодирования для знаковых значений. Анализатор декодирует их для определения:
- Исходный файл: `_func.pcfile` -> программа pctab -> индекс файла -> cutab -> filetab -> строка пути к файлу
- Номер строки: `_func.pcln` -> программа pctab -> номер строки (плюс смещение `startLine` для Go 1.20+)
---
## Ограничения
1. **Встраиваемые (inlined) функции** не отображаются в таблице функций верхнего уровня. Они закодированы в структурах `FUNCDATA_InlTree` / `PCDATA_InlTreeIndex`, которые данный инструмент пока не декодирует. В тестовом бинарном файле `main.helloWorld` и `main.addNumbers` были встроены компилятором и поэтому не отображаются в выводе.
2. **Поддержка Go 1.2** реализована по мере возможностей. Формат Go 1.2 существенно отличается (нет отдельной funcnametab, нет cutab, абсолютные указатели в functab) и на практике встречается редко.
3. **Архитектуры с big-endian** поддерживаются в принципе (порядок байтов определяется из информации radare2 и используется для всех чтений структур), но не были протестированы.
4. Режим `--apply` создаёт заглушки функций с помощью `af+`, что может конфликтовать с автоанализом r2. Запуск на чистом сеансе (до `aaa` или вместо него) может дать лучшие результаты в некоторых случаях.
---
## Дополнительная документация
Подробная техническая документация доступна в каталоге `documentation/`:
- [DOCUMENTATION.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/DOCUMENTATION.md) – Полная пользовательская документация со всеми режимами использования, форматами вывода и комбинациями флагов.
- [METHODOLOGY.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/METHODOLOGY.md) – Проектные решения и алгоритмы: разрешение textStart vs .text, разбор с учётом версии, стратегия сканирования PE, декодирование PC-данных, детали интеграции с r2.
- [GOPCLNTAB_FORMAT.md](https://github.com/asherdll/r2gopclntabparser/blob/HEAD/documentation/GOPCLNTAB_FORMAT.md) – Двоичный формат gopclntab в разных версиях Go: побайтовые макеты структур, модель памяти, цепочки смещений, кодирование varint, сводка различий по версиям.
---
## Ссылки
- Исходный код 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)
- Линковщик Go (записывает формат): [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: [mandiant.com/resources/blog/golang-internals-symbol-recovery](https://www.mandiant.com/resources/blog/golang-internals-symbol-recovery)
- Дизайн-документ таблицы символов Go 1.2: [docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o](https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub)
| Flag | Description |
|---|
-l, --list | Вывести каждую восстановленную функцию с её адресом. |
-n NAME, --funcname NAME | Вывести только функции, имя которых содержит NAME (поиск по подстроке). Если существует точное совпадение, его адрес выводится отдельно. |
-v, --verbose | Выводить сообщения о ходе работы, разобранные поля заголовка и внутренние смещения. |
--apply | Записать восстановленные имена функций в сессию radare2 как определения функций (af+), флаги в пространстве флагов go. и комментарии с исходным именем Go и местоположением в исходном коде. |
--json | Вывести заголовок и полный список функций в формате JSON в stdout. |
--files | Вывести список путей к исходным файлам, извлечённым из таблицы файлов. |
-h, --help | Показать справочное сообщение. |