去找吧!我把所有資料都藏在那了 - 通向 Section 航道的旅程
前言
「我要成為海賊王!」
這句話大家應該不陌生吧。寫程式有時候就像在 偉大航道上尋寶。
這次的寶藏不是什麼「One Piece」,而是──把設定檔藏在 ELF Binary 裡的秘密空間。
最近剛好遇到一個需求:想把資料包進程式裡,方便部署,但又不想讓人隨便亂改。
就像在跟同學們玩「海上捉迷藏」一樣,所以筆者決定把設定藏進一個 神秘的 section,然後用小工具去 patch。
於是乎本篇就誕生了。
核心概念
在 ELF(Linux)或 PE(Windows) binary 世界裡,有很多「島嶼」──每個 section 就是一個島。
我們要找的就是那個能保存寶藏的島,例如 .rodata
或自訂的 .mydata
。
這些島有兩個特色:
- 不能執行(safe!不怕炸掉程式);
- 但可以讀出內容。
於是我們的計畫就像「把寶藏藏在島上」一樣:
- 在編譯時留下一個固定大小的空間(像是預留寶箱的大小);
- 寫一個小 patch 工具,就能在航行途中把新的寶物塞進去(但記住──大小不能超過原本的寶箱)。
建立自訂 Section
首先,我們要在 C 世界裡留一個「寶箱」。
1/*
2const char mydata[1024] __attribute__((section(".mydata"), used)) = {0};
3*/
4
5import "C"
這裡透過 __attribute__((section(".mydata")))
指定了寶藏島的座標,大小固定 1024 bytes。
之後 Go 就能透過 CGO 拿到這個位置。
在 Go 世界開啟寶藏
我們要有個船員(函式),能幫忙把藏在島上的寶物讀出來:
1// ===== app/main.go =====
2package main
3
4/*
5const char mydata[1024] __attribute__((section(".mydata"), used)) = {0};
6*/
7import "C"
8
9import (
10 "fmt"
11 "unsafe"
12)
13
14func read() []byte {
15 return C.GoBytes(unsafe.Pointer(&C.mydata[0]), 1024)
16}
17
18func main() {
19 fmt.Println("mydata:", string(read()))
20}
C.mydata
是陣列,要用&C.mydata[0]
才能拿到指標;unsafe
需要引入。
Patch 工具:羅盤與鑰匙
接著我們需要一個「航海士」──工具程式,能找到 .mydata
的位置並塞進新的資料。
1// ===== tools/patcher.go =====
2package main
3
4import (
5 "debug/elf"
6 "flag"
7 "io"
8 "os"
9)
10
11func main() {
12 binPath := flag.String("bin", "", "path to target binary")
13 newCfg := flag.String("config", "", "path to new config file")
14 flag.Parse()
15
16 // 打開寶藏地圖(binary)
17 f, err := os.OpenFile(*binPath, os.O_RDWR, 0)
18 if err != nil {
19 panic(err)
20 }
21 defer f.Close()
22
23 ef, err := elf.NewFile(f)
24 if err != nil {
25 panic(err)
26 }
27
28 cfg, err := os.ReadFile(*newCfg)
29 if err != nil {
30 panic(err)
31 }
32
33 for _, sec := range ef.Sections {
34 if sec.Name == ".mydata" {
35 if uint64(len(cfg)) > sec.Size {
36 panic("new config too big for treasure chest")
37 }
38 // 移動到正確位置
39 f.Seek(int64(sec.Offset), io.SeekStart)
40 // 蓋掉舊的寶藏
41 f.Write(cfg)
42 break
43 }
44 }
45}
sec.Data()
只是回傳內容,不能直接 patch;- 必須用
f.Seek(sec.Offset)
定位,再f.Write()
蓋掉; - 記得檢查長度,避免寶藏放不下。
執行流程
1go build -o myapp ./app
2go build -o patcher ./tools/patcher.go
3
4# 放新寶藏
5./patcher -bin=myapp -config=new_config.json
6
7# 驗證
8./myapp
小提醒
Segmentation Fault:如果 build 出來不是 ELF(例如 macOS 預設是 Mach-O),
debug/elf
就會爆炸。要加上:1GOOS=linux GOARCH=amd64 go build -o myapp ./app
長度限制:寶藏箱(section)大小固定,超過就會炸。
Alignment:要對齊,否則可能會在別的島上亂挖洞。
Windows:PE + code signing 可能會失效,要額外注意。
小結
這趟航海之旅,我們學到怎麼把設定藏在 Binary 的 section 裡,就像 Roger 在偉大航道終點留下了寶藏一樣。
這種方式讓程式既能單檔部署,又能後續動態調整設定。 但就像航海王世界的規則一樣,出航一定有風險在。 同學們在實作、使用的同時也要小心才不會不小心就踩到「陷阱」。