Skip to content

Commit 54a6712

Browse files
committed
Remove candidate cache, use go channel for generator of candidates
1 parent 534ca84 commit 54a6712

File tree

7 files changed

+478
-491
lines changed

7 files changed

+478
-491
lines changed

main.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,19 +187,16 @@ func main_impl(fileName string, printStdPkgs bool, printFilePaths bool, printTyp
187187

188188
var knownPclntabVA = uint64(0)
189189
var knownGoTextBase = uint64(0)
190+
190191
restartParseWithRealTextBase:
191-
tabs, err := file.PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase)
192+
ch_tabs, err := file.PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase)
192193
if err != nil {
193194
return ExtractMetadata{}, fmt.Errorf("failed to read pclntab: %w", err)
194195
}
195196

196-
if len(tabs) == 0 {
197-
return ExtractMetadata{}, fmt.Errorf("no pclntab candidates found")
198-
}
199-
200197
var moduleData *objfile.ModuleData = nil
201-
var finalTab *objfile.PclntabCandidate = &tabs[0]
202-
for _, tab := range tabs {
198+
var finalTab *objfile.PclntabCandidate = nil
199+
for tab := range ch_tabs {
203200
if len(versionOverride) > 0 {
204201
extractMetadata.Version = versionOverride
205202
}
@@ -256,9 +253,13 @@ restartParseWithRealTextBase:
256253
}
257254
}
258255

256+
if finalTab == nil {
257+
return ExtractMetadata{}, fmt.Errorf("no valid pclntab found")
258+
}
259+
259260
// to be sure we got the right pclntab we had to have found a moduledat as well. If we didn't, then we failed to find the pclntab (correctly) as well
260261
if moduleData == nil {
261-
return ExtractMetadata{}, fmt.Errorf("no valid pclntab or moduledata found")
262+
return ExtractMetadata{}, fmt.Errorf("no valid moduledata found")
262263
}
263264

264265
extractMetadata.ModuleMeta = *moduleData

objfile/disasm.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (e *Entry) Disasm() (*Disasm, error) {
4848
return nil, err
4949
}
5050

51-
pclns, err := e.PCLineTable("", 0, 0)
51+
ch_pclns, err := e.PCLineTable("", 0, 0)
5252
if err != nil {
5353
return nil, err
5454
}
@@ -75,10 +75,13 @@ func (e *Entry) Disasm() (*Disasm, error) {
7575
keep = append(keep, sym)
7676
}
7777
}
78+
79+
first_pclntab := <-ch_pclns
80+
7881
syms = keep
7982
d := &Disasm{
8083
syms: syms,
81-
pcln: pclns[0].ParsedPclntab,
84+
pcln: first_pclntab.ParsedPclntab,
8285
text: textBytes,
8386
textStart: textStart,
8487
textEnd: textStart + uint64(len(textBytes)),

objfile/elf.go

Lines changed: 143 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (f *elfFile) symbols() ([]Sym, error) {
8686
return syms, nil
8787
}
8888

89-
func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) {
89+
func (f *elfFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) {
9090
// 1) Locate pclntab via symbols (standard way)
9191
foundpcln := false
9292
var pclntab []byte
@@ -110,99 +110,27 @@ func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) {
110110
[]byte("\xFF\xFF\xFF\xFB\x00\x00"),
111111
}
112112

113+
var symtab []byte
114+
var symtab_err error
115+
if sect := f.elf.Section(".gosymtab"); sect != nil {
116+
symtab, symtab_err = sect.Data()
117+
}
118+
113119
// 2) if not found, byte scan for it
114120
pclntab_sigs := append(pclntab_sigs_le, pclntab_sigs_be...)
115121

116-
// candidate array for method 4 of scanning
117-
var stompedmagic_candidates []StompMagicCandidate = make([]StompMagicCandidate, 0)
118-
for _, sec := range f.elf.Sections {
119-
// first section is all zeros, skip
120-
if sec.Type == elf.SHT_NULL {
121-
continue
122-
}
123-
124-
data := f.elf.DataAfterSection(sec)
125-
if !foundpcln {
126-
// malware can split the pclntab across multiple sections, re-merge
127-
// https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
128-
matches := findAllOccurrences(data, pclntab_sigs)
129-
for _, pclntab_idx := range matches {
130-
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
131-
pclntab = data[pclntab_idx:]
132-
133-
var candidate PclntabCandidate
134-
candidate.Pclntab = pclntab
135-
136-
candidate.SecStart = uint64(sec.Addr)
137-
candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx)
138-
139-
candidates = append(candidates, candidate)
140-
// we must scan all signature for all sections. DO NOT BREAK
141-
}
142-
}
143-
} else {
144-
// 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
145-
pclntab_idx := bytes.Index(data, pclntab)
146-
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
147-
var candidate PclntabCandidate
148-
candidate.Pclntab = pclntab
149-
candidate.SecStart = uint64(sec.Addr)
150-
candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx)
151-
152-
candidates = append(candidates, candidate)
153-
}
154-
}
122+
ch_tab := make(chan PclntabCandidate)
155123

156-
// TODO this scan needs to occur in both big and little endian mode
157-
// 4) Always try this other way! Sometimes the pclntab magic is stomped as well so our byte OR symbol location fail. Byte scan for the moduledata, use that to find the pclntab instead, fix up magic with all combinations.
158-
// See the obfuscator 'garble' for an example of randomizing the pclntab magic
159-
sigResults := findModuleInitPCHeader(data, sec.Addr)
160-
for _, sigResult := range sigResults {
161-
// example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab!
162-
// 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o
163-
// 0x000000000069D0C0 ; runtime_scanstack+40B↑o ...
164-
// 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize"
165-
// 0x000000000069D0D0 F0 db 0F0h
166-
// 0x000000000069D0D1 BB db 0BBh
167-
168-
// we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later
169-
// example: reads out ptr unk_5DF6E0
170-
pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit
171-
if err == nil {
172-
stompedMagicCandidateLE := StompMagicCandidate{
173-
binary.LittleEndian.Uint64(pclntabVARaw64),
174-
sigResult.moduleDataVA,
175-
true,
176-
}
177-
stompedMagicCandidateBE := StompMagicCandidate{
178-
binary.BigEndian.Uint64(pclntabVARaw64),
179-
sigResult.moduleDataVA,
180-
false,
181-
}
182-
stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE)
183-
}
184-
185-
pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit
186-
if err == nil {
187-
stompedMagicCandidateLE := StompMagicCandidate{
188-
uint64(binary.LittleEndian.Uint32(pclntabVARaw32)),
189-
sigResult.moduleDataVA,
190-
true,
191-
}
192-
stompedMagicCandidateBE := StompMagicCandidate{
193-
uint64(binary.BigEndian.Uint32(pclntabVARaw32)),
194-
sigResult.moduleDataVA,
195-
false,
196-
}
197-
stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE)
198-
}
124+
send_tab := func(candidate *PclntabCandidate) {
125+
if symtab_err != nil {
126+
candidate.Symtab = symtab
127+
ch_tab <- *candidate
199128
}
129+
ch_tab <- *candidate
200130
}
201131

202-
// even if we found the pclntab without signature scanning it may have a stomped magic. That would break parsing later! So, let's submit new candidates
203-
// with all the possible magics to get at least one that hopefully parses correctly.
204-
patched_magic_candidates := make([]PclntabCandidate, 0)
205-
for _, candidate := range candidates {
132+
// for any candidate, patch out the magic, and send all possible magics to parse too
133+
send_patched_magic_candidates := func(candidate *PclntabCandidate) {
206134
has_some_valid_magic := false
207135
for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) {
208136
if bytes.Equal(candidate.Pclntab, magic) {
@@ -219,82 +147,154 @@ func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) {
219147

220148
new_candidate := candidate
221149
new_candidate.Pclntab = pclntab_copy
222-
patched_magic_candidates = append(patched_magic_candidates, new_candidate)
223-
candidate.Pclntab = pclntab_copy
150+
send_tab(new_candidate)
224151
}
225152
}
226153
}
227154

228-
if len(patched_magic_candidates) > 0 {
229-
candidates = patched_magic_candidates
155+
send_stomped_magic_candidate := func(stompedMagicCandidate *StompMagicCandidate) {
156+
for _, sec := range f.elf.Sections {
157+
data := f.elf.DataAfterSection(sec)
158+
pclntab_va_candidate := stompedMagicCandidate.PclntabVa
159+
160+
// use data length as some binaries have invalid section length
161+
if pclntab_va_candidate >= sec.Addr && pclntab_va_candidate < (sec.Addr+sec.Size) && pclntab_va_candidate < (sec.Addr+uint64(len(data))) {
162+
sec_offset := pclntab_va_candidate - sec.Addr
163+
pclntab = data[sec_offset:]
164+
165+
if stompedMagicCandidate.LittleEndian {
166+
for _, magicLE := range pclntab_sigs_le {
167+
pclntab_copy := make([]byte, len(pclntab))
168+
copy(pclntab_copy, pclntab)
169+
copy(pclntab_copy, magicLE)
170+
171+
var candidate PclntabCandidate
172+
candidate.StompMagicCandidateMeta = stompedMagicCandidate
173+
candidate.Pclntab = pclntab_copy
174+
candidate.SecStart = uint64(sec.Addr)
175+
candidate.PclntabVA = pclntab_va_candidate
176+
177+
send_tab(&candidate)
178+
}
179+
} else {
180+
for _, magicBE := range pclntab_sigs_be {
181+
pclntab_copy := make([]byte, len(pclntab))
182+
copy(pclntab_copy, pclntab)
183+
copy(pclntab_copy, magicBE)
184+
185+
var candidate PclntabCandidate
186+
candidate.StompMagicCandidateMeta = stompedMagicCandidate
187+
candidate.Pclntab = pclntab_copy
188+
candidate.SecStart = uint64(sec.Addr)
189+
candidate.PclntabVA = pclntab_va_candidate
190+
191+
send_tab(&candidate)
192+
}
193+
}
194+
}
195+
}
230196
}
231197

232-
if len(stompedmagic_candidates) != 0 {
198+
go func() {
199+
defer close(ch_tab)
200+
233201
for _, sec := range f.elf.Sections {
202+
// first section is all zeros, skip
203+
if sec.Type == elf.SHT_NULL {
204+
continue
205+
}
206+
234207
data := f.elf.DataAfterSection(sec)
235-
for _, stompedMagicCandidate := range stompedmagic_candidates {
236-
pclntab_va_candidate := stompedMagicCandidate.PclntabVa
237-
238-
// use data length as some binaries have invalid section length
239-
if pclntab_va_candidate >= sec.Addr && pclntab_va_candidate < (sec.Addr+sec.Size) && pclntab_va_candidate < (sec.Addr+uint64(len(data))) {
240-
sec_offset := pclntab_va_candidate - sec.Addr
241-
pclntab = data[sec_offset:]
242-
243-
if stompedMagicCandidate.LittleEndian {
244-
for _, magicLE := range pclntab_sigs_le {
245-
pclntab_copy := make([]byte, len(pclntab))
246-
copy(pclntab_copy, pclntab)
247-
copy(pclntab_copy, magicLE)
248-
249-
var candidate PclntabCandidate
250-
candidate.StompMagicCandidateMeta = &stompedMagicCandidate
251-
candidate.Pclntab = pclntab_copy
252-
candidate.SecStart = uint64(sec.Addr)
253-
candidate.PclntabVA = pclntab_va_candidate
254-
255-
candidates = append(candidates, candidate)
256-
}
257-
} else {
258-
for _, magicBE := range pclntab_sigs_be {
259-
pclntab_copy := make([]byte, len(pclntab))
260-
copy(pclntab_copy, pclntab)
261-
copy(pclntab_copy, magicBE)
262-
263-
var candidate PclntabCandidate
264-
candidate.StompMagicCandidateMeta = &stompedMagicCandidate
265-
candidate.Pclntab = pclntab_copy
266-
candidate.SecStart = uint64(sec.Addr)
267-
candidate.PclntabVA = pclntab_va_candidate
268-
269-
candidates = append(candidates, candidate)
270-
}
208+
if !foundpcln {
209+
// malware can split the pclntab across multiple sections, re-merge
210+
// https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
211+
matches := findAllOccurrences(data, pclntab_sigs)
212+
for _, pclntab_idx := range matches {
213+
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
214+
pclntab = data[pclntab_idx:]
215+
216+
var candidate PclntabCandidate
217+
candidate.Pclntab = pclntab
218+
219+
candidate.SecStart = uint64(sec.Addr)
220+
candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx)
221+
send_patched_magic_candidates(&candidate)
222+
223+
send_tab(&candidate)
224+
// we must scan all signature for all sections. DO NOT BREAK
271225
}
272226
}
227+
} else {
228+
// 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
229+
pclntab_idx := bytes.Index(data, pclntab)
230+
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
231+
var candidate PclntabCandidate
232+
candidate.Pclntab = pclntab
233+
candidate.SecStart = uint64(sec.Addr)
234+
candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx)
235+
236+
send_patched_magic_candidates(&candidate)
237+
send_tab(&candidate)
238+
}
239+
}
240+
241+
// 4) Always try this other way! Sometimes the pclntab magic is stomped as well so our byte OR symbol location fail. Byte scan for the moduledata, use that to find the pclntab instead, fix up magic with all combinations.
242+
// See the obfuscator 'garble' for an example of randomizing the pclntab magic
243+
sigResults := findModuleInitPCHeader(data, sec.Addr)
244+
for _, sigResult := range sigResults {
245+
// example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab!
246+
// 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o
247+
// 0x000000000069D0C0 ; runtime_scanstack+40B↑o ...
248+
// 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize"
249+
// 0x000000000069D0D0 F0 db 0F0h
250+
// 0x000000000069D0D1 BB db 0BBh
251+
252+
// we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later
253+
// example: reads out ptr unk_5DF6E0
254+
pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit
255+
if err == nil {
256+
stompedMagicCandidateLE := StompMagicCandidate{
257+
binary.LittleEndian.Uint64(pclntabVARaw64),
258+
sigResult.moduleDataVA,
259+
true,
260+
}
261+
stompedMagicCandidateBE := StompMagicCandidate{
262+
binary.BigEndian.Uint64(pclntabVARaw64),
263+
sigResult.moduleDataVA,
264+
false,
265+
}
266+
send_stomped_magic_candidate(&stompedMagicCandidateBE)
267+
send_stomped_magic_candidate(&stompedMagicCandidateLE)
268+
}
269+
270+
pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit
271+
if err == nil {
272+
stompedMagicCandidateLE := StompMagicCandidate{
273+
uint64(binary.LittleEndian.Uint32(pclntabVARaw32)),
274+
sigResult.moduleDataVA,
275+
true,
276+
}
277+
stompedMagicCandidateBE := StompMagicCandidate{
278+
uint64(binary.BigEndian.Uint32(pclntabVARaw32)),
279+
sigResult.moduleDataVA,
280+
false,
281+
}
282+
send_stomped_magic_candidate(&stompedMagicCandidateBE)
283+
send_stomped_magic_candidate(&stompedMagicCandidateLE)
284+
}
273285
}
274286
}
275-
}
287+
}()
276288

277-
return candidates, nil
289+
return ch_tab, nil
278290
}
279291

280-
func (f *elfFile) pcln() (candidates []PclntabCandidate, err error) {
292+
func (f *elfFile) pcln() (candidates <-chan PclntabCandidate, err error) {
281293
candidates, err = f.pcln_scan()
282294
if err != nil {
283295
return nil, err
284296
}
285297

286-
// 4) symtab is completely optional, but try to find it
287-
var symtab []byte
288-
if sect := f.elf.Section(".gosymtab"); sect != nil {
289-
symtab, err = sect.Data()
290-
}
291-
292-
if err == nil {
293-
for _, c := range candidates {
294-
c.Symtab = symtab
295-
}
296-
}
297-
298298
return candidates, nil
299299
}
300300

0 commit comments

Comments
 (0)