Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
r2gopclntabParser — Go 바이너리의 리버스 엔지니어링을 용이하게 하기 위해 gopclntab을 파싱하는 radare2 스크립트입니다. | Kitploit
도구/GitHubGitHub/asherdll/r2gopclntabparser
Static AnalysisReverse EngineeringDebuggersForensicsMalware AnalysisBinary Analysis
GitHubasherdll/r2gopclntabparser

r2gopclntabParser

Go 바이너리의 리버스 엔지니어링을 용이하게 하기 위해 gopclntab을 파싱하는 radare2 스크립트입니다.

저장소 보기
174개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

r2gopclntabParser

참고: 이 기능은 gopc 분석 플러그인으로 radare2 코어에 직접 구현되었습니다.

완전히 스트립된(stripped) 바이너리를 포함한 Go 바이너리에서 함수 심볼을 복구하기 위한 radare2 기반 Go gopclntab 파서입니다. Go 버전 1.2, 1.16, 1.18, 1.20+의 ELF, Mach‑O, PE 바이너리를 지원합니다.

모든 Go 1.2+ 바이너리에는 gopclntab(Program Counter Line Table)이라는 데이터 영역이 내장되어 있으며, Go 런타임이 스택 트레이스, 패닉 메시지, 가비지 컬렉션, 디버거 지원에 사용합니다. 이는 Go 바이너리에서 가장 가치 있는 심볼 정보 소스 중 하나입니다.

r2gopclntabParser는 radare2를 통해 이 영역을 읽고, 버전별 구조를 파싱하여 복구된 함수 목록(주소, 소스 파일, 줄 번호 포함)을 출력하거나 복구된 이름을 함수 정의, 플래그, 코멘트로 radare2 세션에 다시 적용합니다.


목차

  • 전제 조건
  • 빠른 시작
  • CLI 참조
  • 출력 모드 및 예제
    • 기본 모드 (헤더 + 함수 목록)
    • 상세 모드 (-v)
    • 검색 모드 (-n)
    • JSON 출력 (--json)
    • 소스 파일 목록 (--files)
    • 적용 모드 (--apply)
  • 결과 요약: Mach‑O (스트립된 테스트 바이너리)
  • PE 테스트: Greenblood, Go 랜섬웨어 바이너리
  • 리버스 엔지니어링 활용 사례
  • 지원 플랫폼 및 Go 버전
  • 방법론
    • 섹션 위치 전략
    • textStart vs .text 섹션
    • 버전 인식 구조체 파싱
    • PC 데이터 디코딩
  • 제한 사항
  • 추가 문서
  • 참고 자료

전제 조건

의존성최소 버전
Python 33.8+
radare25.0+ (6.0.9에서 테스트 완료)
r2pipe모든 버전

다른 Python 패키지는 필요하지 않습니다. 스크립트는 표준 라이브러리(struct, json, argparse, os, sys)와 r2pipe만 사용합니다.


빠른 시작```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

root@kitploit:~
## CLI 참조```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
                       [--apply] [--json] [--files] [--r2pipe]

필수 (하나 선택)

플래그설명
-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

root@kitploit:~
puf - 🎃 URL 및 셸코드 난독화 도구

# 🎃 P.U.F

## 겉은 금과 옥, 속은 헌 솜

P.U.F (P.U.F) 는 강력한 URL 및 셸코드 난독화 도구입니다. 다양한 고급 알고리즘을 사용하여 사람이 읽을 수 있으면서도 파악하기 어려운 난독화된 URL과 셸코드를 생성합니다.

## Features

- **Double Obfuscation U**: 이중 난독화로 은닉성을 향상시킵니다.
- **Randomness Obfuscation D**: 랜덤화 처리를 통해 공격 시그니처를 예측할 수 없게 만듭니다.
- **Multiple Encodings E**: URL 인코딩, Base64 등 다양한 인코딩 방식을 통합했습니다.
- **Highly Deceptive R**: 매우 기만적인 시각적 외관을 생성합니다.
- **Multi-Platform Adaptability**: Windows, Linux, macOS 등 다양한 환경을 지원합니다.
- **Extensive Protocol Compatibility**: HTTP, HTTPS, FTP 등 여러 프로토콜을 지원합니다.
- **Code as Art**: 난독화 결과를 일반 파일 내용으로 위장할 수 있으며, 시각적 효과는 추상 미술 작품과 같습니다.
- **Customizable Compression**: 난독화 결과를 ZIP, TAR.GZ 등의 형식으로 압축할 수 있습니다.
- **Dual Execution**: 역방향 셸코드 주입과 메모리 수준에서 기존 프로세스와의 상호 작용 제어를 지원합니다.
- **Interactive Shell Interface**: 직관적인 명령줄 셸 인터페이스를 제공합니다.

## 설치

```bash
git clone https://github.com/hvck2vj2/puf
cd puf
pip install -r requirements.txt
sudo mv puf.py /usr/local/bin/puf
puf

사용법

P.U.F 시작:

root@kitploit:~
puf

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

root@kitploit:~
### 상세 모드 (-v)

섹션 스캔 진행률, 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+ ...

root@kitploit:~
### Search Mode (-n)

함수 목록을 부분 문자열 일치로 필터링합니다. 정확히 일치하는 항목이 있으면 해당
주소가 별도로 출력됩니다.

`main.`을 포함하는 모든 함수에 대한 부분 문자열 검색:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."

and our community. We are truly humbled by your support, enthusiasm, and contributions.

⚠️ Important Notes

🔒 Security

  • 보안을 중요하게 생각합니다. 보안 관련 문제가 있을 경우, 보안 정책을 참조하거나 직접 문의해 주세요.
  • 취약점이 해결되기 전에 공개적으로 공개하지 마십시오.

📝 Versioning

  • 이 프로젝트는 Semantic Versioning (SemVer)를 따릅니다.
  • 주요 버전 릴리스에서는 호환성이 깨지는 변경이 있을 수 있습니다.

💬 Feedback

  • 피드백과 제안은 언제나 환영합니다! 이슈를 열거나 토론을 시작해 주세요.
  • 자세한 내용은 기여 가이드라인을 확인하세요.

📚 Documentation

전체 문서, 예제 및 고급 사용법은 위키를 방문해 주세요.

🤝 Contributing

모든 분들의 기여를 환영합니다! 자세한 내용은 기여 가이드를 참조해 주세요. 버그 신고, 기능 제안, 풀 리퀘스트 제출 등 여러분의 도움에 감사드립니다.

⭐ Show Your Support

이 프로젝트가 유용하다면, GitHub에서 ⭐를 눌러 주세요! 저희 작업이 가치 있다는 것을 알게 됩니다.

📄 License

이 프로젝트는 MIT 라이선스에 따라 라이선스가 부여됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.

🙏 Acknowledgements

모든 기여자, 테스터, 사용자 여러분께 감사드립니다. 귀중한 도구와 자료를 제공해 주신 오픈 소스 커뮤니티에 특별히 감사드립니다.``` 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)

root@kitploit:~
정확한 일치 검색:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main.fibonacci"

Please provide the Markdown content to translate.``` ADDRESS FUNCTION NAME

0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)

[+] 1 function(s) shown (filtered from 2030 total)

[+] Exact match: main.fibonacci @ 0x1000A0BB0

root@kitploit:~
GC 관련 런타임 내부 검색 중:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
  • 고급 인터넷 전역 서브도메인/CIDR 스캐너: 로우 소켓 구현을 활용하여 UDP 및 TCP 프로토콜을 모두 사용해 초당 수천 개의 호스트에서 DNS 레코드를 스캔합니다.``` 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)

root@kitploit:~
`fmt.` (표준 라이브러리 출력) 검색 중:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."

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

root@kitploit:~
`sync.` 검색 중 (동시성 기본 요소):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."

(no content provided)``` 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) ...

root@kitploit:~
### 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 }

root@kitploit:~
### Source File Listing (--files)

바이너리에 포함된 모든 소스 파일 경로를 추출합니다:```
$ 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 /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

root@kitploit:~
### 적용 모드 (--apply)

복구된 모든 함수 이름을 radare2 세션에 기록합니다. 다음은 스트립된 바이너리의 전/후 비교를 보여줍니다.

**BEFORE** (스트립된 바이너리에 대한 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 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)

root@kitploit:~
**이후** (gopclntab 심볼이 적용된 r2 세션):```
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

root@kitploit:~
이제 `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

root@kitploit:~
## 결과 요약: Mach-O (스트리핑된 테스트 바이너리)

| 지표 | r2 Native (stripped) | After 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`)에 대해 테스트되었습니다. 이는 PE32+ x86-64 Windows 실행 파일로 컴파일된 Go 랜섬웨어 샘플입니다.
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

root@kitploit:~
스캐너가 `.rdata` 섹션 내부 `0x568C00`에서 gopclntab을 발견했습니다.
표준 PE(PIE 아님)이므로 `textStart`는 `0x401000`(0이 아님)이며,
헤더 값이 주소 계산에 직접 사용됩니다.

| Field | Value |
|---|---|
| 형식 | PE32+ x86-64 |
| gopclntab 위치 | `0x568C00`의 `.rdata` (매직 스캔으로 발견) |
| 매직 | `0xFFFFFFF1` (Go 1.20+) |
| 포인터 크기 | 8 |
| 퀀텀 (minLC) | 1 (x86) |
| textStart | `0x401000` (헤더에서) |
| 복구된 함수 | 2596 |
| 소스 파일 | 345 |

### 복구된 멀웨어 함수

멀웨어 자체 코드(`main.`)를 검색 중:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."

악의적인 bash 명령이 사용자가 매크로 문서를 열 때 아무런 상호작용 없이 실행됩니다.``` ADDRESS FUNCTION NAME

0x4D91E0 main.init (:1) 0x4D9200 main.map.init.0 (/root/victims/ransom/daf/enc.go:59) 0x4D92C0 main.map.init.1 (/root/victims/ransom/daf/enc.go:126) 0x4D95C0 main.NewKeyManager (/root/victims/ransom/daf/enc.go:146) 0x4D97E0 main.getMachineFingerprint (/root/victims/ransom/daf/enc.go:173) 0x4DA2C0 main.getBIOSUUID (/root/victims/ransom/daf/enc.go:249) 0x4DA3C0 main.NewEncryptionEngine (/root/victims/ransom/daf/enc.go:287) 0x4DA560 main.(*EncryptionEngine).fileWorker (/root/victims/ransom/daf/enc.go:303) 0x4DA660 main.(*EncryptionEngine).processFile (/root/victims/ransom/daf/enc.go:319) 0x4DA740 main.(*EncryptionEngine).encryptFile (/root/victims/ransom/daf/enc.go:334) 0x4DB2A0 main.(*EncryptionEngine).EncryptPath (/root/victims/ransom/daf/enc.go:446) 0x4DB620 main.(*EncryptionEngine).shouldSkipDirectory (/root/victims/ransom/daf/enc.go:495) 0x4DB7C0 main.(*EncryptionEngine).shouldEncryptFile (/root/victims/ransom/daf/enc.go:522) 0x4DB9A0 main.(*EncryptionEngine).placeRansomNote (/root/victims/ransom/daf/enc.go:560) 0x4DBB60 main.(*EncryptionEngine).recordSuccess (/root/victims/ransom/daf/enc.go:636) 0x4DBFA0 main.(*EncryptionEngine).Wait (/root/victims/ransom/daf/enc.go:664) 0x4DC3C0 main.formatBytes (/root/victims/ransom/daf/enc.go:689) 0x4DC500 main.disableRecovery (/root/victims/ransom/daf/enc.go:706) 0x4DC720 main.isAdmin (/root/victims/ransom/daf/enc.go:732) 0x4DC8C0 main.main (/root/victims/ransom/daf/enc.go:760) 0x4DD260 main.getLogicalDrives (/root/victims/ransom/daf/enc.go:863) 0x4DD4A0 main.isAlreadyRunning (/root/victims/ransom/daf/enc.go:892) 0x4DD660 main.getDesktopPath (/root/victims/ransom/daf/enc.go:911) 0x4DD780 main.removeExecutable (/root/victims/ransom/daf/enc.go:932) ...

[+] 43 function(s) shown (filtered from 2596 total)

root@kitploit:~
총 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

유일한 외부 종속성은 Windows 특정 시스템 호출(레지스트리 액세스, 보안 토큰, DLL 로딩)을 위한 golang.org/x/[email protected]입니다.

암호화 함수

crypto 검색 결과 137개의 암호화 관련 함수가 있으며, 그중 다음을 포함합니다:``` $ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"

root@kitploit:~
INPUT:```
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.

root@kitploit:~
2596개 함수가 모두 성공적으로 적용되었으며, 건너뛴 항목이 없습니다.

---

## 리버스 엔지니어링 사용 사례

### 1. 분류 및 식별

바이너리가 Go로 작성되었는지, 어떤 버전으로 빌드되었는지, 어떤 패키지를 사용하는지 즉시 확인할 수 있습니다. `--files` 출력은 Go 툴체인 버전(예: `/usr/local/go/1.26.1/...` 같은 파일 경로로부터)과 모든 소스 파일 경로(서드파티 라이브러리 포함)를 나타냅니다. 악성코드의 경우, 샘플이 `crypto/tls`, `net/http`, `os/exec` 또는 관심 있는 다른 패키지를 사용하는지 즉시 알 수 있습니다.

### 2. 스트립된 바이너리에서 심볼 복구

핵심 사용 사례입니다. `-ldflags="-s -w"`로 스트립된 Go 바이너리는 심볼 테이블을 잃지만, gopclntab은 유지됩니다. 이 도구는 모든 함수 이름을 복원하여 익명의 `fcn.1000a0c20`을 다시 `main.main`으로 되돌립니다. 이는 악성코드 샘플, CTF 챌린지, 프로덕션 바이너리 및 모든 스트립된 Go 실행 파일에 적용됩니다.

### 3. Go 런타임 탐색

Go 바이너리는 전체 런타임(일반적으로 1500~2000개 이상의 함수)을 내장합니다. 이름이 없으면 런타임은 익명 함수의 뚫을 수 없는 벽입니다. 이름이 있으면 `runtime.mallocgc`, `runtime.gopanic`, `runtime.newproc`, `runtime.gcStart`를 즉시 찾고 각 호출 지점에서 바이너리가 무엇을 하는지 이해할 수 있습니다.

### 4. 사용자 코드와 런타임 분리

`main.` 또는 애플리케이션의 패키지 경로를 검색하여 런타임에서 사용자 코드만 분리할 수 있습니다. 위의 예에서 `main.`으로 필터링하면 총 2030개 함수 중에서 `main.main`과 `main.fibonacci`가 즉시 드러납니다. 또한 서드파티 패키지 이름(예: `-n "github.com/user/repo"`)으로 검색하여 외부 종속성을 식별할 수 있습니다.

### 5. 소스 수준 컨텍스트

각 함수는 소스 파일 경로와 시작 라인 번호를 제공합니다. 즉, 스트립된 바이너리를 분석할 때도 디스어셈블리를 Go 표준 라이브러리 소스 코드(오픈소스)와 상호 참조할 수 있습니다. 함수가 `runtime/mgc.go`의 733번째 줄에서 시작한다는 것을 알면 디스어셈블리와 함께 원본 소스를 읽을 수 있습니다.

### 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 버전

바이너리 형식

PE 바이너리 및 섹션 헤더가 없는 강력하게 스트립된 ELF/Mach-O 바이너리의 경우, 도구는 4바이트 매직 뒤에 검증 바이트(pad=0, ptrSize in {4,8}, minLC in {1,2,4})를 찾기 위해 모든 섹션을 스캔하는 방식으로 대체(fallback)합니다.

Go 버전

0xFFFFFFF1 매직은 Go 1.20부터 최소 Go 1.26까지 사용됩니다.


방법론

섹션 위치 전략

파서는 gopclntab 데이터를 찾기 위해 2단계 접근 방식을 사용합니다:

1단계 (ELF/Mach-O): radare2의 섹션 목록(iSj)을 조회하고 .gopclntab, .data.rel.ro.gopclntab 또는 __gopclntab이라는 이름의 섹션을 찾습니다.

2단계 (PE/대체): 이름이 지정된 섹션이 없으면 모든 섹션에서 4바이트 매직 바이트를 스캔합니다. 각 후보는 바이트 4-7이 예상 패턴(2개의 제로 패드 바이트, 유효한 포인터 크기(4 또는 8), 유효한 명령어 양자(1, 2 또는 4))과 일치하는지 확인하여 검증됩니다. 이렇게 하면 우연한 바이트 패턴으로 인한 오탐(false positive)이 제거됩니다.

textStart vs .text 섹션

Go >= 1.18에서 functab의 함수 진입점은 상대 오프셋으로 저장됩니다. 절대 가상 주소를 계산하려면 기준(base)이 필요합니다:``` absolute_addr = base + entryoff

root@kitploit:~
기본은 다음 논리로 결정됩니다:```
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+에서 다릅니다. 유일한 변경 사항은 Go 1.20+에서 오프셋 36에 4바이트 startLine 필드가 삽입되어 funcID, flag, nfuncdata가 4바이트씩 이동한다는 점입니다:

파서는 매직 넘버를 확인하여 사용할 레이아웃을 결정합니다.

PC 데이터 디코딩

소스 파일 인덱스와 라인 번호는 pctab 영역에 컴팩트한 "PC 데이터 프로그램"으로 저장됩니다. 각 프로그램은 부호 있는 값에 대해 지그재그 인코딩을 사용하는 가변 길이 정수로 (value_delta, pc_delta) 쌍의 시퀀스를 인코딩합니다. 파서는 이를 디코딩하여 다음을 확인합니다:

  • 소스 파일: _func.pcfile -> pctab 프로그램 -> 파일 인덱스 -> cutab -> filetab -> 파일 경로 문자열
  • 라인 번호: _func.pcln -> pctab 프로그램 -> 라인 번호 (Go 1.20+의 경우 startLine 오프셋 추가)

제한 사항

  1. 인라인 함수는 최상위 함수 테이블에 나타나지 않습니다. 이들은 FUNCDATA_InlTree / PCDATA_InlTreeIndex 구조체에 인코딩되어 있으며, 이 도구는 현재 이를 디코딩하지 않습니다. 테스트 바이너리에서 main.helloWorld와 main.addNumbers는 컴파일러에 의해 인라인 처리되었으므로 출력에 나타나지 않습니다.

  2. Go 1.2 지원은 최선을 다합니다. Go 1.2 형식은 상당히 다르며(별도의 funcnametab 없음, cutab 없음, functab의 절대 포인터) 실제로는 거의 마주치지 않습니다.

  3. 빅 엔디안 아키텍처는 원칙적으로 처리되지만(엔디안은 radare2의 바이너리 정보에서 감지되어 모든 구조체 읽기에 사용됨) 테스트되지 않았습니다.

  4. --apply 모드는 af+로 함수 스텁을 생성하며, 이는 r2 자체의 자동 분석과 충돌할 수 있습니다. 일부 경우에는 깨끗한 세션에서(aaa 전이나 대신) 실행하는 것이 더 나은 결과를 제공할 수 있습니다.


추가 문서

자세한 기술 문서는 documentation/ 디렉토리에서 확인할 수 있습니다:

  • DOCUMENTATION.md - 모든 사용 모드, 출력 형식 및 플래그 조합을 포함한 전체 사용자 문서.
  • METHODOLOGY.md - 설계 결정 및 알고리즘: textStart 대 .text 해결, 버전 인식 파싱, PE 스캐닝 전략, PC 데이터 디코딩, r2 통합 세부 정보.
  • GOPCLNTAB_FORMAT.md - Go 버전별 gopclntab 바이너리 형식: 바이트 수준 구조체 레이아웃, 메모리 모델, 오프셋 체인, varint 인코딩, 버전 차이 요약.

참고 자료

  • Go 런타임 소스 (pcHeader): go1.20.6/src/runtime/symtab.go#L414
  • Go 링커 (포맷 작성): go1.20.6/src/cmd/link/internal/ld/pcln.go
  • Mandiant - Golang Internals Symbol Recovery: mandiant.com/resources/blog/golang-internals-symbol-recovery
  • Go 1.2 심볼 테이블 설계 문서: docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o
도구 다운로드
플래그설명
-l, --list복구된 모든 함수와 해당 주소를 출력합니다.
-n NAME, --funcname NAME이름에 NAME을 포함하는 함수만 출력합니다 (부분 문자열 일치). 정확히 일치하는 이름이 있으면 해당 주소가 별도로 출력됩니다.
-v, --verbose진행 메시지, 구문 분석된 헤더 필드 및 내부 오프셋을 출력합니다.
--apply복구된 함수 이름을 radare2 세션에 함수 정의(af+), go. 플래그스페이스 플래그, 원래 Go 이름 및 소스 위치가 포함된 주석으로 기록합니다.
--json헤더와 전체 함수 목록을 JSON 형식으로 표준 출력에 출력합니다.
--files파일 테이블에서 추출된 소스 파일 경로 목록을 출력합니다.
-h, --help도움말 메시지를 표시합니다.
형식섹션 검색 방법테스트됨
ELF (Linux)섹션 이름 .gopclntab 또는 .data.rel.ro.gopclntab예
Mach-O (macOS)섹션 이름 __gopclntab (__TEXT 세그먼트 내부)예
PE (Windows)매직 바이트 스캔 (전용 섹션 없음)예 (Greenblood, Go 랜섬웨어 PE64에서 테스트됨)
MagicGo 버전헤더 내 textStartfunctab.entry 타입startLine 필드상태
0xFFFFFFFB1.2아니오uintptr (절대)아니오지원됨
0xFFFFFFFA1.16아니오uintptr (절대)아니오지원됨
0xFFFFFFF01.18 - 1.19예uint32 (상대)아니오지원됨
0xFFFFFFF11.20+예 (0일 수 있음)uint32 (상대)예지원됨
필드Go 1.18 오프셋Go 1.20+ 오프셋
entryOff00
nameOff44
args88
cuOffset3232
startLine(없음)36
funcID3640
flag3741
nfuncdata3943