Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
r2gopclntabParser — A radare2 script to parse the gopclntab to facilitate Reverse Engineering Go binaries. | Kitploit
Tools/GitHubGitHub/asherdll/r2gopclntabparser
Static AnalysisReverse EngineeringDebuggersForensicsMalware AnalysisBinary Analysis
GitHubasherdll/r2gopclntabparser

r2gopclntabParser

A radare2 script to parse the gopclntab to facilitate Reverse Engineering Go binaries.

View Repository
17205 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
# r2gopclntabParser

> Note: This feature has been implemented directly to radare2's core as [gopc analysis plugin](https://github.com/radareorg/radare2/pull/25661)

A radare2-based Go `gopclntab` parser for recovering
function symbols from Go binaries, including fully stripped ones. Supports ELF,
Mach-O, and PE binaries across Go versions 1.2, 1.16, 1.18, and 1.20+.

Every Go 1.2+ binary embeds a data region called `gopclntab` (Program Counter Line
Table) that the Go runtime uses for stack traces, panic messages, garbage
collection, and debugger support. It is one of the most valuable source of symbol
information in Go binaries.

r2gopclntabParser reads this region through radare2, parses the
version-specific structures, and either prints the recovered function list
(with addresses, source files, and line numbers) or applies the recovered names
back into the open radare2 session as function definitions, flags, and
comments.

---

## Table of Contents

- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [CLI Reference](#cli-reference)
- [Output Modes and Examples](#output-modes-and-examples)
  - [Default Mode (Header + Function List)](#default-mode-header--function-list)
  - [Verbose Mode (-v)](#verbose-mode--v)
  - [Search Mode (-n)](#search-mode--n)
  - [JSON Output (--json)](#json-output---json)
  - [Source File Listing (--files)](#source-file-listing---files)
  - [Apply Mode (--apply)](#apply-mode---apply)
- [Results Summary: Mach-O (Stripped Test Binary)](#results-summary-mach-o-stripped-test-binary)
- [PE Test: Greenblood, a Go Ransomware Binary](#pe-test-go-ransomware-binary)
- [Use Cases for Reverse Engineering](#use-cases-for-reverse-engineering)
- [Supported Platforms and Go Versions](#supported-platforms-and-go-versions)
- [Methodology](#methodology)
  - [Section Location Strategy](#section-location-strategy)
  - [textStart vs .text Section](#textstart-vs-text-section)
  - [Version-Aware Struct Parsing](#version-aware-struct-parsing)
  - [PC Data Decoding](#pc-data-decoding)
- [Limitations](#limitations)
- [Additional Documentation](#additional-documentation)
- [References](#references)

---

## Prerequisites

| Dependency | Minimum Version |
|---|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (tested on 6.0.9) |
| r2pipe | any |

No other Python packages are required. The script uses only the standard
library (`struct`, `json`, `argparse`, `os`, `sys`) plus `r2pipe`.

---

## Quick Start

```bash
# List all functions recovered from a Go binary
python3 r2_gopclntab.py -f ./mybinary -l

# Search for a specific function (substring match)
python3 r2_gopclntab.py -f ./mybinary -n main.main

# Verbose header + function list
python3 r2_gopclntab.py -f ./mybinary -v -l

# Apply recovered names into an r2 session
python3 r2_gopclntab.py -f ./mybinary --apply

# JSON output
python3 r2_gopclntab.py -f ./mybinary --json

# From within r2 (attach to running session)
#!pipe python3 r2_gopclntab.py --r2pipe --apply -v
```

---

## CLI Reference

```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
                       [--apply] [--json] [--files] [--r2pipe]
```

### Required (one of)

| Flag | Description |
|---|---|
| `-f FILE`, `--file FILE` | Path to the Go binary to analyze. The script spawns its own r2 instance. |
| `--r2pipe` | Attach to an already-running r2 session (for use inside the r2 console). |

### Optional

| Flag | Description |
|---|---|
| `-l`, `--list` | Print every recovered function with its address. |
| `-n NAME`, `--funcname NAME` | Print only functions whose name contains `NAME` (substring match). If an exact match exists, its address is printed separately. |
| `-v`, `--verbose` | Print progress messages, parsed header fields, and internal offsets. |
| `--apply` | Write recovered function names into the radare2 session as function definitions (`af+`), flags in the `go.` flagspace, and comments with the original Go name and source location. |
| `--json` | Output the header and full function list as JSON to stdout. |
| `--files` | Print the list of source file paths extracted from the file table. |
| `-h`, `--help` | Show the help message. |

Flags can be combined freely. When no output flag is given, the default
behavior is to print the header and the full function list.

---

## Output Modes and Examples

All examples below were run against a stripped Go 1.26 Mach-O arm64 binary
(built with `-ldflags="-s -w"`). The test program defines `main.main`,
`main.fibonacci`, `main.helloWorld`, and `main.addNumbers`. The compiler
inlined `helloWorld` and `addNumbers`, so they do not appear in the gopclntab.

### Default Mode (Header + Function List)

Running with no flags (or just `-f`) prints the parsed header followed by the
full function table:

```
$ python3 r2_gopclntab.py -f ./gotest_stripped
```

```
============================================================
  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
```

### Verbose Mode (-v)

Adds section scanning progress, textStart resolution details, and internal
offset information:

```
$ python3 r2_gopclntab.py -f ./gotest_stripped -v
```

```
[*] 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+
  ...
```

### Search Mode (-n)

Filters the function list by substring match. If an exact match exists, its
address is printed separately.

Substring search for all functions containing `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)
```

Exact match search:

```
$ 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
```

Searching for GC-related runtime internals:

```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
```

```
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)
```

Searching for `fmt.` (standard library printing):

```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."
```

```
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)
...
```

Searching for `sync.` (concurrency primitives):

```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
```

```
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 Output (--json)

Machine-readable output for scripting and pipeline integration:

```
$ python3 r2_gopclntab.py -f ./gotest_stripped --json
```

```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
}
```

### Source File Listing (--files)

Extracts all source file paths embedded in the binary:

```
$ python3 r2_gopclntab.py -f ./gotest_stripped --files
```

```
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 Mode (--apply)

Writes all recovered function names into the radare2 session. The following
shows a before/after comparison on the stripped binary.

**BEFORE** (r2 native analysis on stripped binary, no gopclntab parsing):

```
Functions found by r2 natively: 1913

Disassembly at 0x1000a0c20 (main.main, unnamed):

            ; CODE XREF from fcn.1000a0c20 @ 0x1000a0d14(r)
  24: fcn.1000a0c20 (int64_t arg1);
           0x1000a0c20      900b40f9       ldr x16, [x28, 0x10]
           0x1000a0c24      ff6330eb       cmp sp, x16
           0x1000a0c28      29070054       b.ls 0x1000a0d0c

Disassembly at 0x1000a0bb0 (main.fibonacci, unnamed):

  112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h);
           0x1000a0bb0      900b40f9       ldr x16, [x28, 0x10]
           0x1000a0bb4      ff6330eb       cmp sp, x16
           0x1000a0bb8      a9020054       b.ls 0x1000a0c0c
```

r2 found 1913 functions but named none of them (only anonymous `fcn.XXXXXXXX`
labels). Searching for `main.main` or `main.fibonacci` returns nothing.

**Applying gopclntab symbols:**

```
[*] 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)
```

**AFTER** (r2 session with gopclntab symbols applied):

```
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
```

Disassembly at `main.main` now shows the recovered name and source location:

```
            ;-- 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
```

Disassembly at `main.fibonacci` now shows the recovered name and source location:

```
            ;-- 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
```

Seeking by name now works in the r2 session:

```
go.main.main resolves to: 0x1000a0c20
go.main.fibonacci resolves to: 0x1000a0bb0
```

---

## Results Summary: Mach-O (Stripped Test Binary)

| Metric | r2 Native (stripped) | After r2_gopclntab.py |
|---|---|---|
| Functions found | 1913 (anonymous `fcn.XXXX` labels) | 2030 (full Go package-qualified names) |
| User functions identified | 0 | `main.main`, `main.fibonacci` with source + line |
| Source files recovered | 0 | 261 (full absolute paths) |
| Named symbols | Only C import stubs (`sym.imp.mmap`, etc.) | Every Go function labeled (`go.main.main`, `go.runtime.gcStart`, etc.) |
| Source comments | None | Inline `src: /tmp/gotest/main.go:20` in disassembly |
| Navigable by name | No | Yes (`s go.main.main`, `afl~runtime.gc`) |

---

## PE Test: Greenblood, a Go Ransomware Binary

The parser was tested against a real-world PE binary, Greenblood (`greenblood_1`), a Go
ransomware sample compiled as a PE32+ x86-64 Windows executable. PE binaries
do not have a dedicated `.gopclntab` section, so this exercises the magic-byte
scanning fallback path.

### Detection and Header

```
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
```

```
[*] 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
```

The scanner found the gopclntab inside the `.rdata` section at `0x568C00`.
Because this is a standard PE (not PIE), `textStart` is `0x401000` (non-zero),
so the header value is used directly for address computation.

| Field | Value |
|---|---|
| Format | PE32+ x86-64 |
| gopclntab location | `.rdata` at `0x568C00` (found by magic scan) |
| Magic | `0xFFFFFFF1` (Go 1.20+) |
| Pointer size | 8 |
| Quantum (minLC) | 1 (x86) |
| textStart | `0x401000` (from header) |
| Functions recovered | 2596 |
| Source files | 345 |

### Recovered Malware Functions

Searching for the malware's own code (`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)
```

All 43 user functions were recovered from a single source file at
`/root/victims/ransom/daf/enc.go`. The function names immediately reveal the
ransomware's capabilities: key management, machine fingerprinting, file
encryption with path traversal, ransom note placement, recovery disabling,
privilege checking, mutex-based single-instance enforcement, drive enumeration,
and self-deletion.

### Non-Standard-Library Dependencies

Extracting source files that are not part of the Go standard library:

```
/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
```

The only external dependency is `golang.org/x/[email protected]` for Windows-specific
syscalls (registry access, security tokens, DLL loading).

### Crypto Functions

Searching for `crypto` reveals 137 crypto-related functions, including:

```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
```

```
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)
```

The crypto usage profile: AES (CBC and CTR modes), SHA-256, SHA-512, HMAC,
CSHAKE128, and DRBG (deterministic random bit generator). This is consistent
with ransomware that generates per-machine encryption keys derived from a
machine fingerprint, encrypts files with AES-CTR, and uses HMAC for integrity.

### 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.
```

All 2596 functions applied successfully with zero skipped.

---

## Use Cases for Reverse Engineering

### 1. Triage and Identification

Instantly determine if a binary is Go, what version it was built with, and
what packages it uses. The `--files` output reveals the Go toolchain version
(from file paths like `/usr/local/go/1.26.1/...`) and every source file path,
including third-party libraries. For malware, this instantly reveals whether
the sample uses `crypto/tls`, `net/http`, `os/exec`, or other packages of
interest.

### 2. Symbol Recovery on Stripped Binaries

The core use case. Go binaries stripped with `-ldflags="-s -w"` lose their
symbol table, but gopclntab survives. This tool restores every function name,
turning anonymous `fcn.1000a0c20` back into `main.main`. This applies to
malware samples, CTF challenges, production binaries, and any stripped Go
executable.

### 3. Navigating the Go Runtime

Go binaries embed the entire runtime (typically 1500 to 2000+ functions).
Without names, the runtime is an impenetrable wall of anonymous functions.
With names, you can instantly locate `runtime.mallocgc`, `runtime.gopanic`,
`runtime.newproc`, `runtime.gcStart`, and understand what the binary is doing
at each call site.

### 4. Separating User Code from Runtime

By searching for `main.` or the application's package path, you can isolate
just the user's code from the runtime. In the examples above, filtering for
`main.` immediately reveals `main.main` and `main.fibonacci` among 2030
total functions. You can also search by third-party package names
(e.g., `-n "github.com/user/repo"`) to identify external dependencies.

### 5. Source-Level Context

Each function comes with its source file path and start line number. This
means you can cross-reference the disassembly against the Go standard library
source code (which is open), even when analyzing a stripped binary. Knowing
that a function starts at line 733 of `runtime/mgc.go` lets you read the
original source alongside the disassembly.

### 6. Pipeline and Automation

The `--json` mode enables scripting. Feed the output into IDA/Ghidra
importers, diffing tools, YARA rule generators, or any analysis pipeline.
For example, to extract all crypto-related functions:

```bash
python3 r2_gopclntab.py -f sample.exe --json \
  | jq '.functions[] | select(.name | contains("crypto"))'
```

### 7. Interactive radare2 Workflow

After `--apply`, the entire r2 session becomes navigable by Go name. You can
seek to functions (`s go.main.main`), search the function list
(`afl~runtime.gc`), inspect cross-references (`axf go.main.fibonacci`), and
see source location comments inline in disassembly output (`pd`). This
transforms r2 from a generic disassembler into a Go-aware analysis
environment.

---

## Supported Platforms and Go Versions

### Binary Formats

| Format | Section Discovery Method | Tested |
|---|---|---|
| ELF (Linux) | Section name `.gopclntab` or `.data.rel.ro.gopclntab` | Yes |
| Mach-O (macOS) | Section name `__gopclntab` (inside `__TEXT` segment) | Yes |
| PE (Windows) | Magic byte scan (no dedicated section) | Yes (tested on Greenblood, a Go ransomware PE64) |

For PE binaries and aggressively stripped ELF/Mach-O binaries that lack the
section header, the tool falls back to scanning all sections for the 4-byte
magic followed by validation bytes (pad=0, ptrSize in {4,8}, minLC in
{1,2,4}).

### Go Versions

| Magic | Go Version | `textStart` in header | `functab.entry` type | `startLine` field | Status |
|---|---|---|---|---|---|
| `0xFFFFFFFB` | 1.2 | No | `uintptr` (absolute) | No | Supported |
| `0xFFFFFFFA` | 1.16 | No | `uintptr` (absolute) | No | Supported |
| `0xFFFFFFF0` | 1.18 - 1.19 | Yes | `uint32` (relative) | No | Supported |
| `0xFFFFFFF1` | 1.20+ | Yes (may be 0) | `uint32` (relative) | Yes | Supported |

The `0xFFFFFFF1` magic is used by Go 1.20 through at least Go 1.26.

---

## Methodology

### Section Location Strategy

The parser uses a two-phase approach to find the gopclntab data:

**Phase 1 (ELF/Mach-O):** Query radare2's section list (`iSj`) and look for
sections named `.gopclntab`, `.data.rel.ro.gopclntab`, or `__gopclntab`.

**Phase 2 (PE/fallback):** If no named section is found, scan all sections
for the 4-byte magic bytes. Each candidate is validated by checking that
bytes 4-7 match the expected pattern: two zero pad bytes, a valid pointer
size (4 or 8), and a valid instruction quantum (1, 2, or 4). This
eliminates false positives from coincidental byte patterns.

### textStart vs .text Section

In Go >= 1.18, function entry points in the functab are stored as relative
offsets. Computing the absolute virtual address requires a base:

```
absolute_addr = base + entryoff
```

The base is resolved with the following logic:

```
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
```

The `textStart == 0` case occurs in Go >= 1.22 for Mach-O and PIE binaries.
When this happens, the `entryoff` values are relative to the `.text` section
start, so the parser queries r2 for the `.text` virtual address and uses that
as the base.

This was verified against a Go 1.26 Mach-O arm64 binary:
- `textStart` in the header: `0x0`
- `.text` section vaddr: `0x100001000`
- `functab[1].entryoff`: `0x70`
- Computed address: `0x100001000 + 0x70 = 0x100001070`
- r2 native analysis confirmed `internal/abi.BoundsDecode` at `0x100001070`

### Version-Aware Struct Parsing

The `_func` struct layout differs between Go 1.18 and Go 1.20+. The only
change is the insertion of a 4-byte `startLine` field at offset 36 in Go
1.20+, which shifts `funcID`, `flag`, and `nfuncdata` by 4 bytes:

| Field | Go 1.18 offset | Go 1.20+ offset |
|---|---|---|
| `entryOff` | 0 | 0 |
| `nameOff` | 4 | 4 |
| `args` | 8 | 8 |
| `cuOffset` | 32 | 32 |
| `startLine` | (absent) | 36 |
| `funcID` | 36 | 40 |
| `flag` | 37 | 41 |
| `nfuncdata` | 39 | 43 |

The parser checks the magic number to determine which layout to use.

### PC Data Decoding

Source file indices and line numbers are stored as compact "PC data programs"
in the pctab region. Each program encodes a sequence of
`(value_delta, pc_delta)` pairs using variable-length integers with zig-zag
encoding for signed values. The parser decodes these to resolve:

- Source file: `_func.pcfile` -> pctab program -> file index -> cutab ->
  filetab -> file path string
- Line number: `_func.pcln` -> pctab program -> line number (plus
  `startLine` offset for Go 1.20+)

---

## Limitations

1. **Inlined functions** do not appear in the top-level function table. They
   are encoded in `FUNCDATA_InlTree` / `PCDATA_InlTreeIndex` structures,
   which this tool does not currently decode. In the test binary,
   `main.helloWorld` and `main.addNumbers` were inlined by the compiler and
   therefore do not appear in the output.

2. **Go 1.2 support** is best-effort. The Go 1.2 format is substantially
   different (no separate funcnametab, no cutab, absolute pointers in functab)
   and is rarely encountered in practice.

3. **Big-endian** architectures are handled in principle (the endian is
   detected from radare2's binary info and used for all struct reads) but
   have not been tested.

4. The `--apply` mode creates function stubs with `af+` which may conflict
   with r2's own auto-analysis. Running it on a clean session (before or
   instead of `aaa`) may give better results in some cases.

---

## Additional Documentation

Detailed technical documentation is available in the `documentation/` directory:

- [DOCUMENTATION.md](https://github.com/asherdll/r2gopclntabparser/blob/main/documentation/DOCUMENTATION.md) - Full user documentation
  with all usage modes, output formats, and flag combinations.
- [METHODOLOGY.md](https://github.com/asherdll/r2gopclntabparser/blob/main/documentation/METHODOLOGY.md) - Design decisions and
  algorithms: textStart vs .text resolution, version-aware parsing, PE
  scanning strategy, PC data decoding, r2 integration details.
- [GOPCLNTAB_FORMAT.md](https://github.com/asherdll/r2gopclntabparser/blob/main/documentation/GOPCLNTAB_FORMAT.md) - The gopclntab
  binary format across Go versions: byte-level struct layouts, memory model,
  offset chains, varint encoding, version differences summary.

---

## References

- Go runtime source (pcHeader): [go1.20.6/src/runtime/symtab.go#L414](https://github.com/golang/go/blob/go1.20.6/src/runtime/symtab.go#L414)
- Go linker (writes the 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)
- Go 1.2 symbol table design document: [docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o](https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub)
Download Tool