Skip to content

Commit dd0c48a

Browse files
committed
Fix cant found RET offset in gotls mode. fix #502.
Fix the issue of not being able to fetch the function RET offset in the gotls model when building a Golang binary with pie mode. Signed-off-by: CFC4N <cfc4n.cs@gmail.com>
1 parent d26ad15 commit dd0c48a

File tree

8 files changed

+259
-68
lines changed

8 files changed

+259
-68
lines changed

builder/Makefile.release

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ endif
5050

5151
OUTPUT_DIR = ./bin
5252
TAR_DIR = ecapture-$(SNAPSHOT_VERSION)-linux-$(UNAME_M)
53+
TAR_DIR_NOCORE = ecapture-$(SNAPSHOT_VERSION)-linux-nocore
5354
TAR_DIR_ANDROID = ecapture-$(SNAPSHOT_VERSION)-android-$(UNAME_M)
55+
TAR_DIR_ANDROID_NOCORE = ecapture-$(SNAPSHOT_VERSION)-android-$(UNAME_M)-nocore
5456

5557
# from CLI args.
5658
RELEASE_NOTES ?= $(OUTPUT_DIR)/release_notes.txt
@@ -71,7 +73,9 @@ BUILD_DIR = build
7173
#
7274

7375
OUT_ARCHIVE := $(OUTPUT_DIR)/$(TAR_DIR).tar.gz
74-
OUT_ARCHIVE_ANDROID := $(OUTPUT_DIR)/$(TAR_DIR_ANDROID)-nocore.tar.gz
76+
OUT_ARCHIVE_NOCORE := $(OUTPUT_DIR)/$(TAR_DIR_NOCORE).tar.gz
77+
OUT_ARCHIVE_ANDROID := $(OUTPUT_DIR)/$(TAR_DIR_ANDROID).tar.gz
78+
OUT_ARCHIVE_ANDROID_NOCORE := $(OUTPUT_DIR)/$(TAR_DIR_ANDROID_NOCORE).tar.gz
7579
OUT_CHECKSUMS := $(OUTPUT_DIR)/checksum-$(SNAPSHOT_VERSION).txt
7680

7781

@@ -183,7 +187,7 @@ snapshot: \
183187
$(CMD_CP) $(OUTPUT_DIR)/ecapture $(TAR_DIR)/ecapture
184188
$(CMD_TAR) -czf $(OUT_ARCHIVE) $(TAR_DIR)
185189
cd $(OUTPUT_DIR)
186-
$(CMD_CHECKSUM) $(TAR_DIR).tar.gz > ./../$(OUT_CHECKSUMS)
190+
$(CMD_CHECKSUM) $(OUT_ARCHIVE_ANDROID) > ./../$(OUT_CHECKSUMS)
187191
cd ../
188192

189193
.PHONY: snapshot_android
@@ -198,29 +202,29 @@ snapshot_android: \
198202
$(MAKE) clean
199203
ANDROID=1 $(MAKE) nocore RELEASE_TAG=$(SNAPSHOT_VERSION)
200204
# create the tar ball and checksum files
201-
$(CMD_MKDIR) -p $(TAR_DIR_ANDROID)
202-
$(CMD_CP) LICENSE $(TAR_DIR_ANDROID)/LICENSE
203-
$(CMD_CP) CHANGELOG.md $(TAR_DIR_ANDROID)/CHANGELOG.md
204-
$(CMD_CP) README.md $(TAR_DIR_ANDROID)/README.md
205-
$(CMD_CP) README_CN.md $(TAR_DIR_ANDROID)/README_CN.md
206-
$(CMD_CP) $(OUTPUT_DIR)/ecapture $(TAR_DIR_ANDROID)/ecapture
207-
$(CMD_TAR) -czf $(OUT_ARCHIVE_ANDROID) $(TAR_DIR_ANDROID)
205+
$(CMD_MKDIR) -p $(TAR_DIR_ANDROID_NOCORE)
206+
$(CMD_CP) LICENSE $(TAR_DIR_ANDROID_NOCORE)/LICENSE
207+
$(CMD_CP) CHANGELOG.md $(TAR_DIR_ANDROID_NOCORE)/CHANGELOG.md
208+
$(CMD_CP) README.md $(TAR_DIR_ANDROID_NOCORE)/README.md
209+
$(CMD_CP) README_CN.md $(TAR_DIR_ANDROID_NOCORE)/README_CN.md
210+
$(CMD_CP) $(OUTPUT_DIR)/ecapture $(TAR_DIR_ANDROID_NOCORE)/ecapture
211+
$(CMD_TAR) -czf $(OUT_ARCHIVE_ANDROID_NOCORE) $(TAR_DIR_ANDROID_NOCORE)
208212
cd $(OUTPUT_DIR)
209-
$(CMD_CHECKSUM) $(TAR_DIR_ANDROID).tar.gz >> ./../$(OUT_CHECKSUMS)
213+
$(CMD_CHECKSUM) $(OUT_ARCHIVE_ANDROID_NOCORE) >> ./../$(OUT_CHECKSUMS)
210214
cd ../
211215

212216
.PHONY: publish
213217
publish: \
214218
$(OUTPUT_DIR) \
215219
$(OUT_ARCHIVE) \
216-
$(OUT_ARCHIVE_ANDROID) \
220+
$(OUT_ARCHIVE_ANDROID_NOCORE) \
217221
$(OUT_CHECKSUMS) \
218222
$(OUT_DEB_FILE) \
219223
| .check_tree \
220224
.check_$(CMD_GITHUB)
221225
#
222226
# release it!
223-
$(CMD_GITHUB) release create $(SNAPSHOT_VERSION) $(OUT_ARCHIVE) $(OUT_ARCHIVE_ANDROID) $(OUT_DEB_FILE) $(OUT_CHECKSUMS) --title "eCapture $(SNAPSHOT_VERSION)" --notes-file $(RELEASE_NOTES)
227+
$(CMD_GITHUB) release create $(SNAPSHOT_VERSION) $(OUT_ARCHIVE) $(OUT_ARCHIVE_ANDROID_NOCORE) $(OUT_DEB_FILE) $(OUT_CHECKSUMS) --title "eCapture $(SNAPSHOT_VERSION)" --notes-file $(RELEASE_NOTES)
224228

225229
.PHONY: clean
226230
clean:

user/config/config_gotls.go

Lines changed: 185 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,85 @@
1515
package config
1616

1717
import (
18+
"bytes"
19+
"debug/buildinfo"
1820
"debug/elf"
21+
"debug/gosym"
22+
"encoding/binary"
1923
"errors"
2024
"fmt"
2125
"os"
2226
"runtime"
23-
24-
"golang.org/x/arch/arm64/arm64asm"
25-
"golang.org/x/arch/x86/x86asm"
27+
"strings"
2628
)
2729

2830
const (
29-
// Arm64armInstSize via : arm64/arm64asm/decode.go:Decode() size = 4
30-
Arm64armInstSize = 4
31-
31+
// 46EE50 - 46EDB0
32+
//
3233
GoTlsReadFunc = "crypto/tls.(*Conn).Read"
34+
35+
GoTlsWriteFunc = "crypto/tls.(*Conn).writeRecordLocked"
36+
GoTlsMasterSecretFunc = "crypto/tls.(*Config).writeKeyLog"
37+
// crypto_tls._ptr_Conn.Read
38+
GoTlsReadFuncArm64 = "crypto_tls._ptr_Conn.Read"
3339
)
3440

3541
var (
36-
ErrorGoBINNotFound = errors.New("The executable program (compiled by Golang) was not found")
37-
ErrorSymbolNotFound = errors.New("symbol not found")
38-
ErrorNoRetFound = errors.New("no RET instructions found")
42+
ErrorGoBINNotFound = errors.New("The executable program (compiled by Golang) was not found")
43+
ErrorSymbolEmpty = errors.New("symbol is empty")
44+
ErrorSymbolNotFound = errors.New("symbol not found")
45+
ErrorSymbolNotFoundFromTable = errors.New("symbol not found from table")
46+
ErrorNoRetFound = errors.New("no RET instructions found")
47+
ErrorNoRetFoundFromSymTabFun = errors.New("no RET instructions found from golang symbol table with Fun")
3948
)
4049

50+
// From go/src/debug/gosym/pclntab.go
51+
const (
52+
go12magic = 0xfffffffb
53+
go116magic = 0xfffffffa
54+
go118magic = 0xfffffff0
55+
go120magic = 0xfffffff1
56+
)
57+
58+
// Select the magic number based on the Go version
59+
func magicNumber(goVersion string) []byte {
60+
bs := make([]byte, 4)
61+
var magic uint32
62+
if strings.Compare(goVersion, "go1.20") >= 0 {
63+
magic = go120magic
64+
} else if strings.Compare(goVersion, "go1.18") >= 0 {
65+
magic = go118magic
66+
} else if strings.Compare(goVersion, "go1.16") >= 0 {
67+
magic = go116magic
68+
} else {
69+
magic = go12magic
70+
}
71+
binary.LittleEndian.PutUint32(bs, magic)
72+
return bs
73+
}
74+
75+
type FuncOffsets struct {
76+
Start uint64
77+
Returns []uint64
78+
}
79+
4180
// GoTLSConfig represents configuration for Go SSL probe
4281
type GoTLSConfig struct {
4382
eConfig
44-
Path string `json:"path"` // golang application path to binary built with Go toolchain.
45-
PcapFile string `json:"pcapFile"` // pcapFile the raw packets to file rather than parsing and printing them out.
46-
KeylogFile string `json:"keylogFile"` // keylogFile The file stores SSL/TLS keys, and eCapture captures these keys during encrypted traffic communication and saves them to the file.
47-
Model string `json:"model"` // model such as : text, pcapng/pcap, key/keylog.
48-
Ifname string `json:"ifName"` // (TC Classifier) Interface name on which the probe will be attached.
49-
PcapFilter string `json:"pcapFilter"` // pcap filter
50-
goElfArch string //
51-
goElf *elf.File //
52-
ReadTlsAddrs []int
83+
Path string `json:"path"` // golang application path to binary built with Go toolchain.
84+
PcapFile string `json:"pcapFile"` // pcapFile the raw packets to file rather than parsing and printing them out.
85+
KeylogFile string `json:"keylogFile"` // keylogFile The file stores SSL/TLS keys, and eCapture captures these keys during encrypted traffic communication and saves them to the file.
86+
Model string `json:"model"` // model such as : text, pcapng/pcap, key/keylog.
87+
Ifname string `json:"ifName"` // (TC Classifier) Interface name on which the probe will be attached.
88+
PcapFilter string `json:"pcapFilter"` // pcap filter
89+
goElfArch string //
90+
goElf *elf.File //
91+
buildinfo *buildinfo.BuildInfo
92+
ReadTlsAddrs []int
93+
GoTlsWriteAddr uint64
94+
GoTlsMasterSecretAddr uint64
95+
IsPieBuildMode bool
96+
goSymTab *gosym.Table
5397
}
5498

5599
// NewGoTLSConfig creates a new config for Go SSL
@@ -74,6 +118,12 @@ func (gc *GoTLSConfig) Check() error {
74118
return err
75119
}
76120

121+
// Read the build information of the Go application
122+
gc.buildinfo, err = buildinfo.ReadFile(gc.Path)
123+
if err != nil {
124+
return err
125+
}
126+
77127
var goElf *elf.File
78128
goElf, err = elf.Open(gc.Path)
79129
if err != nil {
@@ -102,10 +152,56 @@ func (gc *GoTLSConfig) Check() error {
102152
}
103153
gc.goElfArch = goElfArch
104154
gc.goElf = goElf
105-
gc.ReadTlsAddrs, err = gc.findRetOffsets(GoTlsReadFunc)
155+
// If built with PIE and stripped, gopclntab is
156+
// unlabeled and nested under .data.rel.ro.
157+
for _, bs := range gc.buildinfo.Settings {
158+
if bs.Key == "-buildmode" {
159+
if bs.Value == "pie" {
160+
gc.IsPieBuildMode = true
161+
}
162+
break
163+
}
164+
}
165+
if gc.IsPieBuildMode {
166+
gc.goSymTab, err = gc.ReadTable()
167+
if err != nil {
168+
return err
169+
}
170+
fun := gc.goSymTab.LookupFunc(GoTlsWriteFunc)
171+
if fun != nil {
172+
gc.GoTlsWriteAddr = fun.Entry
173+
}
174+
fun = gc.goSymTab.LookupFunc(GoTlsMasterSecretFunc)
175+
if fun != nil {
176+
gc.GoTlsMasterSecretAddr = fun.Entry
177+
}
178+
gc.ReadTlsAddrs, err = gc.findRetOffsetsPie(GoTlsReadFunc)
179+
if err != nil {
180+
return err
181+
}
182+
} else {
183+
gc.ReadTlsAddrs, err = gc.findRetOffsets(GoTlsReadFunc)
184+
if err != nil {
185+
return err
186+
}
187+
}
106188
return err
107189
}
108190

191+
func (gc *GoTLSConfig) findRetOffsetsPie(symbolName string) ([]int, error) {
192+
var offsets []int
193+
var err error
194+
195+
fun := gc.goSymTab.LookupFunc(symbolName)
196+
if fun == nil {
197+
return nil, ErrorSymbolNotFoundFromTable
198+
}
199+
200+
//fmt.Printf("found in %s, entry:%x, end:%x , end-entry:%x\n", fun.Name, fun.Entry, fun.End, fun.End-fun.Entry)
201+
offsets, err = gc.findFuncOffsetBySymfun(fun, gc.goElf)
202+
return offsets, err
203+
}
204+
109205
// FindRetOffsets searches for the addresses of all RET instructions within
110206
// the instruction set associated with the specified symbol in an ELF program.
111207
// It is used for mounting uretprobe programs for Golang programs,
@@ -125,7 +221,7 @@ func (gc *GoTLSConfig) findRetOffsets(symbolName string) ([]int, error) {
125221
}
126222

127223
if len(allSymbs) == 0 {
128-
return nil, fmt.Errorf("no symbols found")
224+
return nil, ErrorSymbolEmpty
129225
}
130226

131227
var found bool
@@ -163,30 +259,6 @@ func (gc *GoTLSConfig) findRetOffsets(symbolName string) ([]int, error) {
163259
return offsets, nil
164260
}
165261

166-
// decodeInstruction Decode into assembly instructions and identify the RET instruction to return the offset.
167-
func (gc *GoTLSConfig) decodeInstruction(instHex []byte) ([]int, error) {
168-
var offsets []int
169-
for i := 0; i < len(instHex); {
170-
if gc.goElfArch == "amd64" {
171-
inst, err := x86asm.Decode(instHex[i:], 64)
172-
if err != nil {
173-
return nil, err
174-
}
175-
if inst.Op == x86asm.RET {
176-
offsets = append(offsets, i)
177-
}
178-
i += inst.Len
179-
} else {
180-
inst, _ := arm64asm.Decode(instHex[i:]) // Why ignore error: https://github.com/gojue/ecapture/pull/506
181-
if inst.Op == arm64asm.RET {
182-
offsets = append(offsets, i)
183-
}
184-
i += Arm64armInstSize
185-
}
186-
}
187-
return offsets, nil
188-
}
189-
190262
func (gc *GoTLSConfig) checkModel() (string, error) {
191263
var m string
192264
var e error
@@ -204,3 +276,72 @@ func (gc *GoTLSConfig) checkModel() (string, error) {
204276
}
205277
return m, e
206278
}
279+
280+
func (gc *GoTLSConfig) ReadTable() (*gosym.Table, error) {
281+
sectionLabel := ".data.rel.ro"
282+
section := gc.goElf.Section(sectionLabel)
283+
if section == nil {
284+
// binary may be built with -pie
285+
sectionLabel = ".gopclntab"
286+
section = gc.goElf.Section(sectionLabel)
287+
if section == nil {
288+
return nil, fmt.Errorf("could not read section %s from %s ", sectionLabel, gc.Path)
289+
}
290+
}
291+
tableData, err := section.Data()
292+
if err != nil {
293+
return nil, fmt.Errorf("found section but could not read %s from %s ", sectionLabel, gc.Path)
294+
}
295+
296+
// Find .gopclntab by magic number even if there is no section label
297+
magic := magicNumber(gc.buildinfo.GoVersion)
298+
pclntabIndex := bytes.Index(tableData, magic)
299+
//fmt.Printf("buildinfo :%v, magic:%x, pclntabIndex:%d offset:%x , section:%v \n", gc.buildinfo, magic, pclntabIndex, section.Offset, section)
300+
if pclntabIndex < 0 {
301+
return nil, fmt.Errorf("could not find magic number in %s ", gc.Path)
302+
}
303+
tableData = tableData[pclntabIndex:]
304+
addr := gc.goElf.Section(".text").Addr
305+
lineTable := gosym.NewLineTable(tableData, addr)
306+
symTable, err := gosym.NewTable([]byte{}, lineTable)
307+
if err != nil {
308+
return nil, ErrorSymbolNotFoundFromTable
309+
}
310+
return symTable, nil
311+
}
312+
313+
func (gc *GoTLSConfig) findFuncOffsetBySymfun(f *gosym.Func, elfF *elf.File) ([]int, error) {
314+
var offsets []int
315+
var err error
316+
317+
for _, prog := range elfF.Progs {
318+
if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 {
319+
continue
320+
}
321+
// For more info on this calculation: stackoverflow.com/a/40249502
322+
/*
323+
ida : start : 46EE50, end : 46F258 , len :0x408
324+
elf info: start : 46ed30, end : , len: 0x3B0, 0x58
325+
326+
*/
327+
if prog.Vaddr <= f.Value && f.Value < (prog.Vaddr+prog.Memsz) {
328+
funcLen := f.End - f.Entry
329+
fmt.Printf("name :%s, f.Value:%x, f.Entry:%x, f.End:%x, funcLen:%X \n ", f.Name, f.Value, f.Entry, f.End, funcLen)
330+
data := make([]byte, funcLen)
331+
_, err = prog.ReadAt(data, int64(f.Value-prog.Vaddr))
332+
if err != nil {
333+
return offsets, fmt.Errorf("finding function return: %w", err)
334+
}
335+
336+
offsets, err = gc.decodeInstruction(data)
337+
if err != nil {
338+
return offsets, fmt.Errorf("finding function return: %w", err)
339+
}
340+
for i, offset := range offsets {
341+
offsets[i] = int(f.Entry) + offset
342+
}
343+
return offsets, nil
344+
}
345+
}
346+
return offsets, ErrorNoRetFoundFromSymTabFun
347+
}

user/config/go_instructions_amd64.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package config
2+
3+
import (
4+
"golang.org/x/arch/x86/x86asm"
5+
)
6+
7+
// decodeInstruction Decode into assembly instructions and identify the RET instruction to return the offset.
8+
func (gc *GoTLSConfig) decodeInstruction(instHex []byte) ([]int, error) {
9+
var offsets []int
10+
for i := 0; i < len(instHex); {
11+
inst, err := x86asm.Decode(instHex[i:], 64)
12+
if err != nil {
13+
return nil, err
14+
}
15+
if inst.Op == x86asm.RET {
16+
offsets = append(offsets, i)
17+
}
18+
i += inst.Len
19+
}
20+
return offsets, nil
21+
}

0 commit comments

Comments
 (0)