From 54a6712945cd980dcf720c162c1347e14f078155 Mon Sep 17 00:00:00 2001 From: Stephen Eckels Date: Sat, 19 Oct 2024 14:21:17 -0400 Subject: [PATCH] Remove candidate cache, use go channel for generator of candidates --- main.go | 17 +-- objfile/disasm.go | 7 +- objfile/elf.go | 286 ++++++++++++++++++++++---------------------- objfile/goobj.go | 4 +- objfile/macho.go | 280 +++++++++++++++++++++---------------------- objfile/objfile.go | 84 ++++++------- objfile/pe.go | 291 ++++++++++++++++++++++----------------------- 7 files changed, 478 insertions(+), 491 deletions(-) diff --git a/main.go b/main.go index 7eac803..2c57f60 100644 --- a/main.go +++ b/main.go @@ -187,19 +187,16 @@ func main_impl(fileName string, printStdPkgs bool, printFilePaths bool, printTyp var knownPclntabVA = uint64(0) var knownGoTextBase = uint64(0) + restartParseWithRealTextBase: - tabs, err := file.PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase) + ch_tabs, err := file.PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase) if err != nil { return ExtractMetadata{}, fmt.Errorf("failed to read pclntab: %w", err) } - if len(tabs) == 0 { - return ExtractMetadata{}, fmt.Errorf("no pclntab candidates found") - } - var moduleData *objfile.ModuleData = nil - var finalTab *objfile.PclntabCandidate = &tabs[0] - for _, tab := range tabs { + var finalTab *objfile.PclntabCandidate = nil + for tab := range ch_tabs { if len(versionOverride) > 0 { extractMetadata.Version = versionOverride } @@ -256,9 +253,13 @@ restartParseWithRealTextBase: } } + if finalTab == nil { + return ExtractMetadata{}, fmt.Errorf("no valid pclntab found") + } + // 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 if moduleData == nil { - return ExtractMetadata{}, fmt.Errorf("no valid pclntab or moduledata found") + return ExtractMetadata{}, fmt.Errorf("no valid moduledata found") } extractMetadata.ModuleMeta = *moduleData diff --git a/objfile/disasm.go b/objfile/disasm.go index 7641ac4..e32e941 100644 --- a/objfile/disasm.go +++ b/objfile/disasm.go @@ -48,7 +48,7 @@ func (e *Entry) Disasm() (*Disasm, error) { return nil, err } - pclns, err := e.PCLineTable("", 0, 0) + ch_pclns, err := e.PCLineTable("", 0, 0) if err != nil { return nil, err } @@ -75,10 +75,13 @@ func (e *Entry) Disasm() (*Disasm, error) { keep = append(keep, sym) } } + + first_pclntab := <-ch_pclns + syms = keep d := &Disasm{ syms: syms, - pcln: pclns[0].ParsedPclntab, + pcln: first_pclntab.ParsedPclntab, text: textBytes, textStart: textStart, textEnd: textStart + uint64(len(textBytes)), diff --git a/objfile/elf.go b/objfile/elf.go index 3777e00..47b4d3b 100644 --- a/objfile/elf.go +++ b/objfile/elf.go @@ -86,7 +86,7 @@ func (f *elfFile) symbols() ([]Sym, error) { return syms, nil } -func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) { +func (f *elfFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) { // 1) Locate pclntab via symbols (standard way) foundpcln := false var pclntab []byte @@ -110,99 +110,27 @@ func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) { []byte("\xFF\xFF\xFF\xFB\x00\x00"), } + var symtab []byte + var symtab_err error + if sect := f.elf.Section(".gosymtab"); sect != nil { + symtab, symtab_err = sect.Data() + } + // 2) if not found, byte scan for it pclntab_sigs := append(pclntab_sigs_le, pclntab_sigs_be...) - // candidate array for method 4 of scanning - var stompedmagic_candidates []StompMagicCandidate = make([]StompMagicCandidate, 0) - for _, sec := range f.elf.Sections { - // first section is all zeros, skip - if sec.Type == elf.SHT_NULL { - continue - } - - data := f.elf.DataAfterSection(sec) - if !foundpcln { - // malware can split the pclntab across multiple sections, re-merge - // https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172 - matches := findAllOccurrences(data, pclntab_sigs) - for _, pclntab_idx := range matches { - if pclntab_idx != -1 && pclntab_idx < int(sec.Size) { - pclntab = data[pclntab_idx:] - - var candidate PclntabCandidate - candidate.Pclntab = pclntab - - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) - - candidates = append(candidates, candidate) - // we must scan all signature for all sections. DO NOT BREAK - } - } - } else { - // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things) - pclntab_idx := bytes.Index(data, pclntab) - if pclntab_idx != -1 && pclntab_idx < int(sec.Size) { - var candidate PclntabCandidate - candidate.Pclntab = pclntab - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) - - candidates = append(candidates, candidate) - } - } + ch_tab := make(chan PclntabCandidate) - // TODO this scan needs to occur in both big and little endian mode - // 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. - // See the obfuscator 'garble' for an example of randomizing the pclntab magic - sigResults := findModuleInitPCHeader(data, sec.Addr) - for _, sigResult := range sigResults { - // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab! - // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o - // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ... - // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize" - // 0x000000000069D0D0 F0 db 0F0h - // 0x000000000069D0D1 BB db 0BBh - - // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later - // example: reads out ptr unk_5DF6E0 - pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit - if err == nil { - stompedMagicCandidateLE := StompMagicCandidate{ - binary.LittleEndian.Uint64(pclntabVARaw64), - sigResult.moduleDataVA, - true, - } - stompedMagicCandidateBE := StompMagicCandidate{ - binary.BigEndian.Uint64(pclntabVARaw64), - sigResult.moduleDataVA, - false, - } - stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE) - } - - pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit - if err == nil { - stompedMagicCandidateLE := StompMagicCandidate{ - uint64(binary.LittleEndian.Uint32(pclntabVARaw32)), - sigResult.moduleDataVA, - true, - } - stompedMagicCandidateBE := StompMagicCandidate{ - uint64(binary.BigEndian.Uint32(pclntabVARaw32)), - sigResult.moduleDataVA, - false, - } - stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE) - } + send_tab := func(candidate *PclntabCandidate) { + if symtab_err != nil { + candidate.Symtab = symtab + ch_tab <- *candidate } + ch_tab <- *candidate } - // 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 - // with all the possible magics to get at least one that hopefully parses correctly. - patched_magic_candidates := make([]PclntabCandidate, 0) - for _, candidate := range candidates { + // for any candidate, patch out the magic, and send all possible magics to parse too + send_patched_magic_candidates := func(candidate *PclntabCandidate) { has_some_valid_magic := false for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) { if bytes.Equal(candidate.Pclntab, magic) { @@ -219,82 +147,154 @@ func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) { new_candidate := candidate new_candidate.Pclntab = pclntab_copy - patched_magic_candidates = append(patched_magic_candidates, new_candidate) - candidate.Pclntab = pclntab_copy + send_tab(new_candidate) } } } - if len(patched_magic_candidates) > 0 { - candidates = patched_magic_candidates + send_stomped_magic_candidate := func(stompedMagicCandidate *StompMagicCandidate) { + for _, sec := range f.elf.Sections { + data := f.elf.DataAfterSection(sec) + pclntab_va_candidate := stompedMagicCandidate.PclntabVa + + // use data length as some binaries have invalid section length + if pclntab_va_candidate >= sec.Addr && pclntab_va_candidate < (sec.Addr+sec.Size) && pclntab_va_candidate < (sec.Addr+uint64(len(data))) { + sec_offset := pclntab_va_candidate - sec.Addr + pclntab = data[sec_offset:] + + if stompedMagicCandidate.LittleEndian { + for _, magicLE := range pclntab_sigs_le { + pclntab_copy := make([]byte, len(pclntab)) + copy(pclntab_copy, pclntab) + copy(pclntab_copy, magicLE) + + var candidate PclntabCandidate + candidate.StompMagicCandidateMeta = stompedMagicCandidate + candidate.Pclntab = pclntab_copy + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = pclntab_va_candidate + + send_tab(&candidate) + } + } else { + for _, magicBE := range pclntab_sigs_be { + pclntab_copy := make([]byte, len(pclntab)) + copy(pclntab_copy, pclntab) + copy(pclntab_copy, magicBE) + + var candidate PclntabCandidate + candidate.StompMagicCandidateMeta = stompedMagicCandidate + candidate.Pclntab = pclntab_copy + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = pclntab_va_candidate + + send_tab(&candidate) + } + } + } + } } - if len(stompedmagic_candidates) != 0 { + go func() { + defer close(ch_tab) + for _, sec := range f.elf.Sections { + // first section is all zeros, skip + if sec.Type == elf.SHT_NULL { + continue + } + data := f.elf.DataAfterSection(sec) - for _, stompedMagicCandidate := range stompedmagic_candidates { - pclntab_va_candidate := stompedMagicCandidate.PclntabVa - - // use data length as some binaries have invalid section length - if pclntab_va_candidate >= sec.Addr && pclntab_va_candidate < (sec.Addr+sec.Size) && pclntab_va_candidate < (sec.Addr+uint64(len(data))) { - sec_offset := pclntab_va_candidate - sec.Addr - pclntab = data[sec_offset:] - - if stompedMagicCandidate.LittleEndian { - for _, magicLE := range pclntab_sigs_le { - pclntab_copy := make([]byte, len(pclntab)) - copy(pclntab_copy, pclntab) - copy(pclntab_copy, magicLE) - - var candidate PclntabCandidate - candidate.StompMagicCandidateMeta = &stompedMagicCandidate - candidate.Pclntab = pclntab_copy - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = pclntab_va_candidate - - candidates = append(candidates, candidate) - } - } else { - for _, magicBE := range pclntab_sigs_be { - pclntab_copy := make([]byte, len(pclntab)) - copy(pclntab_copy, pclntab) - copy(pclntab_copy, magicBE) - - var candidate PclntabCandidate - candidate.StompMagicCandidateMeta = &stompedMagicCandidate - candidate.Pclntab = pclntab_copy - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = pclntab_va_candidate - - candidates = append(candidates, candidate) - } + if !foundpcln { + // malware can split the pclntab across multiple sections, re-merge + // https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172 + matches := findAllOccurrences(data, pclntab_sigs) + for _, pclntab_idx := range matches { + if pclntab_idx != -1 && pclntab_idx < int(sec.Size) { + pclntab = data[pclntab_idx:] + + var candidate PclntabCandidate + candidate.Pclntab = pclntab + + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + send_patched_magic_candidates(&candidate) + + send_tab(&candidate) + // we must scan all signature for all sections. DO NOT BREAK } } + } else { + // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things) + pclntab_idx := bytes.Index(data, pclntab) + if pclntab_idx != -1 && pclntab_idx < int(sec.Size) { + var candidate PclntabCandidate + candidate.Pclntab = pclntab + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + + send_patched_magic_candidates(&candidate) + send_tab(&candidate) + } + } + + // 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. + // See the obfuscator 'garble' for an example of randomizing the pclntab magic + sigResults := findModuleInitPCHeader(data, sec.Addr) + for _, sigResult := range sigResults { + // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab! + // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o + // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ... + // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize" + // 0x000000000069D0D0 F0 db 0F0h + // 0x000000000069D0D1 BB db 0BBh + + // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later + // example: reads out ptr unk_5DF6E0 + pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit + if err == nil { + stompedMagicCandidateLE := StompMagicCandidate{ + binary.LittleEndian.Uint64(pclntabVARaw64), + sigResult.moduleDataVA, + true, + } + stompedMagicCandidateBE := StompMagicCandidate{ + binary.BigEndian.Uint64(pclntabVARaw64), + sigResult.moduleDataVA, + false, + } + send_stomped_magic_candidate(&stompedMagicCandidateBE) + send_stomped_magic_candidate(&stompedMagicCandidateLE) + } + + pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit + if err == nil { + stompedMagicCandidateLE := StompMagicCandidate{ + uint64(binary.LittleEndian.Uint32(pclntabVARaw32)), + sigResult.moduleDataVA, + true, + } + stompedMagicCandidateBE := StompMagicCandidate{ + uint64(binary.BigEndian.Uint32(pclntabVARaw32)), + sigResult.moduleDataVA, + false, + } + send_stomped_magic_candidate(&stompedMagicCandidateBE) + send_stomped_magic_candidate(&stompedMagicCandidateLE) + } } } - } + }() - return candidates, nil + return ch_tab, nil } -func (f *elfFile) pcln() (candidates []PclntabCandidate, err error) { +func (f *elfFile) pcln() (candidates <-chan PclntabCandidate, err error) { candidates, err = f.pcln_scan() if err != nil { return nil, err } - // 4) symtab is completely optional, but try to find it - var symtab []byte - if sect := f.elf.Section(".gosymtab"); sect != nil { - symtab, err = sect.Data() - } - - if err == nil { - for _, c := range candidates { - c.Symtab = symtab - } - } - return candidates, nil } diff --git a/objfile/goobj.go b/objfile/goobj.go index 4bf55b6..a30401a 100644 --- a/objfile/goobj.go +++ b/objfile/goobj.go @@ -227,11 +227,11 @@ func (f *goobjFile) symbols() ([]Sym, error) { return syms, nil } -func (f *goobjFile) pcln_scan() (candidates []PclntabCandidate, err error) { +func (f *goobjFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) { return nil, fmt.Errorf("pcln not available in go object file") } -func (f *goobjFile) pcln() (candidates []PclntabCandidate, err error) { +func (f *goobjFile) pcln() (candidates <-chan PclntabCandidate, err error) { // Should never be called. We implement Liner below, callers // should use that instead. return nil, fmt.Errorf("pcln not available in go object file") diff --git a/objfile/macho.go b/objfile/macho.go index 2f81e0b..b236c8d 100644 --- a/objfile/macho.go +++ b/objfile/macho.go @@ -108,7 +108,7 @@ func (f *machoFile) symbols() ([]Sym, error) { return syms, nil } -func (f *machoFile) pcln_scan() (candidates []PclntabCandidate, err error) { +func (f *machoFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) { // 1) Locate pclntab via symbols (standard way) foundpcln := false var pclntab []byte @@ -132,97 +132,25 @@ func (f *machoFile) pcln_scan() (candidates []PclntabCandidate, err error) { []byte("\xFF\xFF\xFF\xFB\x00\x00"), } - // 2) if not found, byte scan for it - pclntab_sigs := append(pclntab_sigs_le, pclntab_sigs_be...) - - // candidate array for method 4 of scanning - var stompedmagic_candidates []StompMagicCandidate = make([]StompMagicCandidate, 0) + var symtab []byte + var symtab_err error + if sect := f.macho.Section("__gosymtab"); sect != nil { + symtab, symtab_err = sect.Data() + } // 2) if not found, byte scan for it - for _, sec := range f.macho.Sections { - // malware can split the pclntab across multiple sections, re-merge - data := f.macho.DataAfterSection(sec) - - if !foundpcln { - matches := findAllOccurrences(data, pclntab_sigs) - for _, pclntab_idx := range matches { - if pclntab_idx != -1 && pclntab_idx < int(sec.Size) { - pclntab = data[pclntab_idx:] - - var candidate PclntabCandidate - candidate.Pclntab = pclntab - - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) - - candidates = append(candidates, candidate) - // we must scan all signature for all sections. DO NOT BREAK - } - } - } else { - // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things) - pclntab_idx := bytes.Index(data, pclntab) - if pclntab_idx != -1 { - var candidate PclntabCandidate - candidate.Pclntab = pclntab - - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + pclntab_sigs := append(pclntab_sigs_le, pclntab_sigs_be...) + ch_tab := make(chan PclntabCandidate) - candidates = append(candidates, candidate) - } - } - - // TODO this scan needs to occur in both big and little endian mode - // 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. - // See the obfuscator 'garble' for an example of randomizing the pclntab magic - sigResults := findModuleInitPCHeader(data, sec.Addr) - for _, sigResult := range sigResults { - // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab! - // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o - // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ... - // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize" - // 0x000000000069D0D0 F0 db 0F0h - // 0x000000000069D0D1 BB db 0BBh - - // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later - // example: reads out ptr unk_5DF6E0 - pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit - if err == nil { - stompedMagicCandidateLE := StompMagicCandidate{ - binary.LittleEndian.Uint64(pclntabVARaw64), - sigResult.moduleDataVA, - true, - } - stompedMagicCandidateBE := StompMagicCandidate{ - binary.BigEndian.Uint64(pclntabVARaw64), - sigResult.moduleDataVA, - false, - } - stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE) - } - - pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit - if err == nil { - stompedMagicCandidateLE := StompMagicCandidate{ - uint64(binary.LittleEndian.Uint32(pclntabVARaw32)), - sigResult.moduleDataVA, - true, - } - stompedMagicCandidateBE := StompMagicCandidate{ - uint64(binary.BigEndian.Uint32(pclntabVARaw32)), - sigResult.moduleDataVA, - false, - } - stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE) - } + send_tab := func(candidate *PclntabCandidate) { + if symtab_err != nil { + candidate.Symtab = symtab + ch_tab <- *candidate } + ch_tab <- *candidate } - // 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 - // with all the possible magics to get at least one that hopefully parses correctly. - patched_magic_candidates := make([]PclntabCandidate, 0) - for _, candidate := range candidates { + send_patched_magic_candidates := func(candidate *PclntabCandidate) { has_some_valid_magic := false for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) { if bytes.Equal(candidate.Pclntab, magic) { @@ -239,82 +167,150 @@ func (f *machoFile) pcln_scan() (candidates []PclntabCandidate, err error) { new_candidate := candidate new_candidate.Pclntab = pclntab_copy - patched_magic_candidates = append(patched_magic_candidates, new_candidate) - candidate.Pclntab = pclntab_copy + send_tab(new_candidate) } } } - if len(patched_magic_candidates) > 0 { - candidates = patched_magic_candidates - } - - if len(stompedmagic_candidates) != 0 { + send_stomped_magic_candidate := func(stompedMagicCandidate *StompMagicCandidate) { for _, sec := range f.macho.Sections { // malware can split the pclntab across multiple sections, re-merge data := f.macho.DataAfterSection(sec) - for _, stompedMagicCandidate := range stompedmagic_candidates { - pclntab_va_candidate := stompedMagicCandidate.PclntabVa - - if pclntab_va_candidate >= sec.Addr && pclntab_va_candidate < (sec.Addr+sec.Size) && pclntab_va_candidate < (sec.Addr+uint64(len(data))) { - sec_offset := pclntab_va_candidate - sec.Addr - pclntab = data[sec_offset:] - - if stompedMagicCandidate.LittleEndian { - for _, magicLE := range pclntab_sigs_le { - pclntab_copy := make([]byte, len(pclntab)) - copy(pclntab_copy, pclntab) - copy(pclntab_copy, magicLE) - - var candidate PclntabCandidate - candidate.StompMagicCandidateMeta = &stompedMagicCandidate - candidate.Pclntab = pclntab_copy - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = pclntab_va_candidate - - candidates = append(candidates, candidate) - } - } else { - for _, magicBE := range pclntab_sigs_be { - pclntab_copy := make([]byte, len(pclntab)) - copy(pclntab_copy, pclntab) - copy(pclntab_copy, magicBE) - - var candidate PclntabCandidate - candidate.StompMagicCandidateMeta = &stompedMagicCandidate - candidate.Pclntab = pclntab_copy - candidate.SecStart = uint64(sec.Addr) - candidate.PclntabVA = pclntab_va_candidate - - candidates = append(candidates, candidate) - } + pclntab_va_candidate := stompedMagicCandidate.PclntabVa + + if pclntab_va_candidate >= sec.Addr && pclntab_va_candidate < (sec.Addr+sec.Size) && pclntab_va_candidate < (sec.Addr+uint64(len(data))) { + sec_offset := pclntab_va_candidate - sec.Addr + pclntab = data[sec_offset:] + + if stompedMagicCandidate.LittleEndian { + for _, magicLE := range pclntab_sigs_le { + pclntab_copy := make([]byte, len(pclntab)) + copy(pclntab_copy, pclntab) + copy(pclntab_copy, magicLE) + + var candidate PclntabCandidate + candidate.StompMagicCandidateMeta = stompedMagicCandidate + candidate.Pclntab = pclntab_copy + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = pclntab_va_candidate + + send_tab(&candidate) + } + } else { + for _, magicBE := range pclntab_sigs_be { + pclntab_copy := make([]byte, len(pclntab)) + copy(pclntab_copy, pclntab) + copy(pclntab_copy, magicBE) + + var candidate PclntabCandidate + candidate.StompMagicCandidateMeta = stompedMagicCandidate + candidate.Pclntab = pclntab_copy + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = pclntab_va_candidate + + send_tab(&candidate) } } } + } } - return candidates, nil + // 2) if not found, byte scan for it + go func() { + defer close(ch_tab) + for _, sec := range f.macho.Sections { + // malware can split the pclntab across multiple sections, re-merge + data := f.macho.DataAfterSection(sec) + + if !foundpcln { + matches := findAllOccurrences(data, pclntab_sigs) + for _, pclntab_idx := range matches { + if pclntab_idx != -1 && pclntab_idx < int(sec.Size) { + pclntab = data[pclntab_idx:] + + var candidate PclntabCandidate + candidate.Pclntab = pclntab + + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + + send_patched_magic_candidates(&candidate) + send_tab(&candidate) + // we must scan all signature for all sections. DO NOT BREAK + } + } + } else { + // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things) + pclntab_idx := bytes.Index(data, pclntab) + if pclntab_idx != -1 { + var candidate PclntabCandidate + candidate.Pclntab = pclntab + + candidate.SecStart = uint64(sec.Addr) + candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + + send_patched_magic_candidates(&candidate) + send_tab(&candidate) + } + } + + // 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. + // See the obfuscator 'garble' for an example of randomizing the pclntab magic + sigResults := findModuleInitPCHeader(data, sec.Addr) + for _, sigResult := range sigResults { + // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab! + // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o + // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ... + // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize" + // 0x000000000069D0D0 F0 db 0F0h + // 0x000000000069D0D1 BB db 0BBh + + // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later + // example: reads out ptr unk_5DF6E0 + pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit + if err == nil { + stompedMagicCandidateLE := StompMagicCandidate{ + binary.LittleEndian.Uint64(pclntabVARaw64), + sigResult.moduleDataVA, + true, + } + stompedMagicCandidateBE := StompMagicCandidate{ + binary.BigEndian.Uint64(pclntabVARaw64), + sigResult.moduleDataVA, + false, + } + send_stomped_magic_candidate(&stompedMagicCandidateBE) + send_stomped_magic_candidate(&stompedMagicCandidateLE) + } + + pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit + if err == nil { + stompedMagicCandidateLE := StompMagicCandidate{ + uint64(binary.LittleEndian.Uint32(pclntabVARaw32)), + sigResult.moduleDataVA, + true, + } + stompedMagicCandidateBE := StompMagicCandidate{ + uint64(binary.BigEndian.Uint32(pclntabVARaw32)), + sigResult.moduleDataVA, + false, + } + send_stomped_magic_candidate(&stompedMagicCandidateBE) + send_stomped_magic_candidate(&stompedMagicCandidateLE) + } + } + } + }() + return ch_tab, nil } -func (f *machoFile) pcln() (candidates []PclntabCandidate, err error) { +func (f *machoFile) pcln() (candidates <-chan PclntabCandidate, err error) { candidates, err = f.pcln_scan() if err != nil { return nil, err } - // 4) symtab is completely optional, but try to find it - var symtab []byte - if sect := f.macho.Section("__gosymtab"); sect != nil { - symtab, err = sect.Data() - } - - if err == nil { - for _, c := range candidates { - c.Symtab = symtab - } - } - return candidates, nil } diff --git a/objfile/objfile.go b/objfile/objfile.go index dfb5ba1..59a8630 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -47,8 +47,8 @@ type ModuleDataCandidate struct { type rawFile interface { symbols() (syms []Sym, err error) - pcln() (candidates []PclntabCandidate, err error) - pcln_scan() (candidates []PclntabCandidate, err error) + pcln() (candidates <-chan PclntabCandidate, err error) + pcln_scan() (candidates <-chan PclntabCandidate, err error) moduledata_scan(pclntabVA uint64, is64bit bool, littleendian bool, ignorelist []uint64) (candidate *ModuleDataCandidate, err error) read_memory(VA uint64, size uint64) (data []byte, err error) text() (textStart uint64, text []byte, err error) @@ -64,9 +64,8 @@ type File struct { } type Entry struct { - name string - raw rawFile - pclnCandidates []PclntabCandidate + name string + raw rawFile } // A Sym is a symbol defined in an executable file. @@ -129,7 +128,7 @@ func (f *File) Symbols() ([]Sym, error) { } // previously : func (f *File) PCLineTable() (Liner, error) { -func (f *File) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) ([]PclntabCandidate, error) { +func (f *File) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) (<-chan PclntabCandidate, error) { return f.entries[0].PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase) } @@ -211,7 +210,7 @@ func findAllOccurrences(data []byte, searches [][]byte) []int { } // previously: func (e *Entry) PCLineTable() (Liner, error) -func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) ([]PclntabCandidate, error) { +func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) (<-chan PclntabCandidate, error) { // If the raw file implements Liner directly, use that. // Currently, only Go intermediate objects and archives (goobj) use this path. @@ -223,50 +222,43 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known // Otherwise, read the pcln tables and build a Liner out of that. // https://github.com/golang/go/blob/89f687d6dbc11613f715d1644b4983905293dd33/src/debug/gosym/pclntab.go#L169 // https://github.com/golang/go/issues/42954 - if e.pclnCandidates == nil { - candidates, err := e.raw.pcln() - if err != nil { - return nil, err - } - e.pclnCandidates = candidates + ch_tab, err := e.raw.pcln() + if err != nil { + return nil, err } - candidates := e.pclnCandidates - - var finalCandidates []PclntabCandidate - var atLeastOneGood bool = false - for _, candidate := range candidates { - /* See https://github.com/mandiant/GoReSym/pull/11 - Locating the .text base is not safe by name due to packers which mangle names. We also have to consider CGO - which appears to update the base with an 'adjusted' one to add some shim code. So, PCLineTable - get called first with the candidate.SecStart just to find symbols, just so we can find the moduledata. - Then, we invoke it again with a 'known' text base, which is found by reading data held in the moduledata. - That is, we do all this parsing twice, on purpose, to be resiliant, we have better info on round 2. - */ - if knownGoTextBase != 0 { - candidate.SecStart = knownGoTextBase - } - // using this VA a moduledata was successfully found, this time around we can avoid re-parsing known bad pclntab candidates - if knownPclntabVA != 0 && candidate.PclntabVA != knownPclntabVA { - continue - } - - parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart), versionOverride) - if err != nil || parsedTable.Go12line == nil { - continue - } + ch := make(chan PclntabCandidate) + + go func() { + defer close(ch) + for candidate := range ch_tab { + /* See https://github.com/mandiant/GoReSym/pull/11 + Locating the .text base is not safe by name due to packers which mangle names. We also have to consider CGO + which appears to update the base with an 'adjusted' one to add some shim code. So, PCLineTable + get called first with the candidate.SecStart just to find symbols, just so we can find the moduledata. + Then, we invoke it again with a 'known' text base, which is found by reading data held in the moduledata. + That is, we do all this parsing twice, on purpose, to be resiliant, we have better info on round 2. + */ + if knownGoTextBase != 0 { + candidate.SecStart = knownGoTextBase + } - // the first good one happens to be correct more often than the last - candidate.ParsedPclntab = parsedTable - finalCandidates = append(finalCandidates, candidate) - atLeastOneGood = true - } + // using this VA a moduledata was successfully found, this time around we can avoid re-parsing known bad pclntab candidates + if knownPclntabVA != 0 && candidate.PclntabVA != knownPclntabVA { + continue + } - if atLeastOneGood { - return finalCandidates, nil - } + parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart), versionOverride) + if err != nil || parsedTable.Go12line == nil { + continue + } - return finalCandidates, fmt.Errorf("failed to locate pclntab") + // the first good one happens to be correct more often than the last + candidate.ParsedPclntab = parsedTable + ch <- candidate + } + }() + return ch, nil } func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version string, is64bit bool, littleendian bool) (secStart uint64, moduleData *ModuleData, err error) { diff --git a/objfile/pe.go b/objfile/pe.go index 6b4b144..b10c068 100644 --- a/objfile/pe.go +++ b/objfile/pe.go @@ -127,7 +127,7 @@ func (f *peFile) symbols() ([]Sym, error) { return syms, nil } -func (f *peFile) pcln_scan() (candidates []PclntabCandidate, err error) { +func (f *peFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) { var imageBase uint64 switch oh := f.pe.OptionalHeader.(type) { case *pe.OptionalHeader32: @@ -167,97 +167,24 @@ func (f *peFile) pcln_scan() (candidates []PclntabCandidate, err error) { []byte("\xFF\xFF\xFF\xFB\x00\x00"), } - // 2) if not found, byte scan for it - pclntab_sigs := append(pclntab_sigs_le, pclntab_sigs_be...) - - // candidate array for method 4 of scanning - var stompedmagic_candidates []StompMagicCandidate = make([]StompMagicCandidate, 0) + symtab, symtab_err := loadPETable(f.pe, "runtime.symtab", "runtime.esymtab") + if symtab_err != nil { + symtab, symtab_err = loadPETable(f.pe, "symtab", "esymtab") + } // 2) if not found, byte scan for it - for _, sec := range f.pe.Sections { - // malware can split the pclntab across multiple sections, re-merge - data := f.pe.DataAfterSection(sec) - - if !foundpcln { - matches := findAllOccurrences(data, pclntab_sigs) - for _, pclntab_idx := range matches { - if pclntab_idx != -1 { - pclntab = data[pclntab_idx:] - - var candidate PclntabCandidate - candidate.Pclntab = pclntab - - candidate.SecStart = imageBase + uint64(sec.VirtualAddress) - candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) - - candidates = append(candidates, candidate) - // we must scan all signature for all sections. DO NOT BREAK - } - } - } else { - // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things) - pclntab_idx := bytes.Index(data, pclntab) - if pclntab_idx != -1 { - var candidate PclntabCandidate - candidate.Pclntab = pclntab - - candidate.SecStart = imageBase + uint64(sec.VirtualAddress) - candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) - - candidates = append(candidates, candidate) - } - } - - // TODO this scan needs to occur in both big and little endian mode - // 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. - // See the obfuscator 'garble' for an example of randomizing the pclntab magic - sigResults := findModuleInitPCHeader(data, uint64(sec.VirtualAddress)+imageBase) - for _, sigResult := range sigResults { - // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab! - // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o - // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ... - // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize" - // 0x000000000069D0D0 F0 db 0F0h - // 0x000000000069D0D1 BB db 0BBh - - // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later - // example: reads out ptr unk_5DF6E0 - pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit - if err == nil { - stompedMagicCandidateLE := StompMagicCandidate{ - binary.LittleEndian.Uint64(pclntabVARaw64), - sigResult.moduleDataVA, - true, - } - stompedMagicCandidateBE := StompMagicCandidate{ - binary.BigEndian.Uint64(pclntabVARaw64), - sigResult.moduleDataVA, - false, - } - stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE) - } + pclntab_sigs := append(pclntab_sigs_le, pclntab_sigs_be...) + ch_tab := make(chan PclntabCandidate) - pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit - if err == nil { - stompedMagicCandidateLE := StompMagicCandidate{ - uint64(binary.LittleEndian.Uint32(pclntabVARaw32)), - sigResult.moduleDataVA, - true, - } - stompedMagicCandidateBE := StompMagicCandidate{ - uint64(binary.BigEndian.Uint32(pclntabVARaw32)), - sigResult.moduleDataVA, - false, - } - stompedmagic_candidates = append(stompedmagic_candidates, stompedMagicCandidateLE, stompedMagicCandidateBE) - } + send_tab := func(candidate *PclntabCandidate) { + if symtab_err != nil { + candidate.Symtab = symtab + ch_tab <- *candidate } + ch_tab <- *candidate } - // 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 - // with all the possible magics to get at least one that hopefully parses correctly. - patched_magic_candidates := make([]PclntabCandidate, 0) - for _, candidate := range candidates { + send_patched_magic_candidates := func(candidate *PclntabCandidate) { has_some_valid_magic := false for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) { if bytes.Equal(candidate.Pclntab, magic) { @@ -274,88 +201,156 @@ func (f *peFile) pcln_scan() (candidates []PclntabCandidate, err error) { new_candidate := candidate new_candidate.Pclntab = pclntab_copy - patched_magic_candidates = append(patched_magic_candidates, new_candidate) - candidate.Pclntab = pclntab_copy + send_tab(new_candidate) } } } - if len(patched_magic_candidates) > 0 { - candidates = patched_magic_candidates - } - - // 4.1) Take the pclntab stomped candidates, and read the pclntab data at each location. Usually the BIG/LITTLE endian pointers that are invalid are filtered out here - if len(stompedmagic_candidates) != 0 { + send_stomped_magic_candidate := func(stompedMagicCandidate *StompMagicCandidate) { for _, sec := range f.pe.Sections { // malware can split the pclntab across multiple sections, re-merge data := f.pe.DataAfterSection(sec) - for _, stompedMagicCandidate := range stompedmagic_candidates { - pclntab_va_candidate := stompedMagicCandidate.PclntabVa - - // We must ensure our pointer starts within the first section of the data returned by DataAfterSection so that we use the right base address - if pclntab_va_candidate >= (imageBase+uint64(sec.VirtualAddress)) && pclntab_va_candidate < (imageBase+uint64(sec.VirtualAddress)+uint64(sec.Size)) && pclntab_va_candidate < (imageBase+uint64(sec.VirtualAddress)+uint64(len(data))) { - sec_offset := pclntab_va_candidate - (imageBase + uint64(sec.VirtualAddress)) - pclntab = data[sec_offset:] - - if stompedMagicCandidate.LittleEndian { - for _, magicLE := range pclntab_sigs_le { - // Make a copy of the pclntab with each magic possible. For when the magic is intentionally corrupted - // Parsing will fail at some later point for the magics that don't match the version, filtering out that candidate - pclntab_copy := make([]byte, len(pclntab)) - copy(pclntab_copy, pclntab) - copy(pclntab_copy, magicLE) - - var candidate PclntabCandidate - candidate.StompMagicCandidateMeta = &stompedMagicCandidate - candidate.Pclntab = pclntab_copy - candidate.SecStart = imageBase + uint64(sec.VirtualAddress) - candidate.PclntabVA = pclntab_va_candidate - - candidates = append(candidates, candidate) - } - } else { - for _, magicBE := range pclntab_sigs_be { - // Make a copy of the pclntab with each magic possible. For when the magic is intentionally corrupted - // Parsing will fail at some later point for the magics that don't match the version, filtering out that candidate - pclntab_copy := make([]byte, len(pclntab)) - copy(pclntab_copy, pclntab) - copy(pclntab_copy, magicBE) - - var candidate PclntabCandidate - candidate.StompMagicCandidateMeta = &stompedMagicCandidate - candidate.Pclntab = pclntab_copy - candidate.SecStart = imageBase + uint64(sec.VirtualAddress) - candidate.PclntabVA = pclntab_va_candidate - - candidates = append(candidates, candidate) - } + pclntab_va_candidate := stompedMagicCandidate.PclntabVa + + // We must ensure our pointer starts within the first section of the data returned by DataAfterSection so that we use the right base address + if pclntab_va_candidate >= (imageBase+uint64(sec.VirtualAddress)) && pclntab_va_candidate < (imageBase+uint64(sec.VirtualAddress)+uint64(sec.Size)) && pclntab_va_candidate < (imageBase+uint64(sec.VirtualAddress)+uint64(len(data))) { + sec_offset := pclntab_va_candidate - (imageBase + uint64(sec.VirtualAddress)) + pclntab = data[sec_offset:] + + if stompedMagicCandidate.LittleEndian { + for _, magicLE := range pclntab_sigs_le { + // Make a copy of the pclntab with each magic possible. For when the magic is intentionally corrupted + // Parsing will fail at some later point for the magics that don't match the version, filtering out that candidate + pclntab_copy := make([]byte, len(pclntab)) + copy(pclntab_copy, pclntab) + copy(pclntab_copy, magicLE) + + var candidate PclntabCandidate + candidate.StompMagicCandidateMeta = stompedMagicCandidate + candidate.Pclntab = pclntab_copy + candidate.SecStart = imageBase + uint64(sec.VirtualAddress) + candidate.PclntabVA = pclntab_va_candidate + + send_tab(&candidate) + } + } else { + for _, magicBE := range pclntab_sigs_be { + // Make a copy of the pclntab with each magic possible. For when the magic is intentionally corrupted + // Parsing will fail at some later point for the magics that don't match the version, filtering out that candidate + pclntab_copy := make([]byte, len(pclntab)) + copy(pclntab_copy, pclntab) + copy(pclntab_copy, magicBE) + + var candidate PclntabCandidate + candidate.StompMagicCandidateMeta = stompedMagicCandidate + candidate.Pclntab = pclntab_copy + candidate.SecStart = imageBase + uint64(sec.VirtualAddress) + candidate.PclntabVA = pclntab_va_candidate + + send_tab(&candidate) } } } } } - return candidates, nil + go func() { + defer close(ch_tab) + + // 2) if not found, byte scan for it + for _, sec := range f.pe.Sections { + // malware can split the pclntab across multiple sections, re-merge + data := f.pe.DataAfterSection(sec) + + if !foundpcln { + matches := findAllOccurrences(data, pclntab_sigs) + for _, pclntab_idx := range matches { + if pclntab_idx != -1 { + pclntab = data[pclntab_idx:] + + var candidate PclntabCandidate + candidate.Pclntab = pclntab + + candidate.SecStart = imageBase + uint64(sec.VirtualAddress) + candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + + send_patched_magic_candidates(&candidate) + send_tab(&candidate) + // we must scan all signature for all sections. DO NOT BREAK + } + } + } else { + // 3) if we found it earlier, figure out which section base to return (might be wrong for packed things) + pclntab_idx := bytes.Index(data, pclntab) + if pclntab_idx != -1 { + var candidate PclntabCandidate + candidate.Pclntab = pclntab + + candidate.SecStart = imageBase + uint64(sec.VirtualAddress) + candidate.PclntabVA = candidate.SecStart + uint64(pclntab_idx) + + send_patched_magic_candidates(&candidate) + send_tab(&candidate) + } + } + + // TODO this scan needs to occur in both big and little endian mode + // 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. + // See the obfuscator 'garble' for an example of randomizing the pclntab magic + sigResults := findModuleInitPCHeader(data, uint64(sec.VirtualAddress)+imageBase) + for _, sigResult := range sigResults { + // example: off_69D0C0 is the moduleData we found via our scan, the first ptr unk_5DF6E0, is the pclntab! + // 0x000000000069D0C0 E0 F6 5D 00 00 00 00 00 off_69D0C0 dq offset unk_5DF6E0 ; DATA XREF: runtime_SetFinalizer+119↑o + // 0x000000000069D0C0 ; runtime_scanstack+40B↑o ... + // 0x000000000069D0C8 40 F7 5D 00 00 00 00 00 dq offset aInternalCpuIni ; "internal/cpu.Initialize" + // 0x000000000069D0D0 F0 db 0F0h + // 0x000000000069D0D1 BB db 0BBh + + // we don't know the endianess or arch, so we submit all combinations as candidates and sort them out later + // example: reads out ptr unk_5DF6E0 + pclntabVARaw64, err := f.read_memory(sigResult.moduleDataVA, 8) // assume 64bit + if err == nil { + stompedMagicCandidateLE := StompMagicCandidate{ + binary.LittleEndian.Uint64(pclntabVARaw64), + sigResult.moduleDataVA, + true, + } + stompedMagicCandidateBE := StompMagicCandidate{ + binary.BigEndian.Uint64(pclntabVARaw64), + sigResult.moduleDataVA, + false, + } + send_stomped_magic_candidate(&stompedMagicCandidateBE) + send_stomped_magic_candidate(&stompedMagicCandidateLE) + } + + pclntabVARaw32, err := f.read_memory(sigResult.moduleDataVA, 4) // assume 32bit + if err == nil { + stompedMagicCandidateLE := StompMagicCandidate{ + uint64(binary.LittleEndian.Uint32(pclntabVARaw32)), + sigResult.moduleDataVA, + true, + } + stompedMagicCandidateBE := StompMagicCandidate{ + uint64(binary.BigEndian.Uint32(pclntabVARaw32)), + sigResult.moduleDataVA, + false, + } + send_stomped_magic_candidate(&stompedMagicCandidateBE) + send_stomped_magic_candidate(&stompedMagicCandidateLE) + } + } + } + }() + return ch_tab, nil } -func (f *peFile) pcln() (candidates []PclntabCandidate, err error) { +func (f *peFile) pcln() (candidates <-chan PclntabCandidate, err error) { candidates, err = f.pcln_scan() if err != nil { return nil, err } - // 4) symtab is completely optional, but try to find it - var symtab []byte - if symtab, err = loadPETable(f.pe, "runtime.symtab", "runtime.esymtab"); err != nil { - symtab, err = loadPETable(f.pe, "symtab", "esymtab") - } - - if err == nil { - for _, c := range candidates { - c.Symtab = symtab - } - } - return candidates, nil }