Comments (14)
I think I implemented a fix for this in redress. We should be be able to just take the start of text address from the moduledata structure.
from gore.
Maybe the same logic can also be applied to pclntab
from gore.
I think I have an idea for how we can solve this. First we need to find the moduledata
structure. This we can do without knowing which compiler version was used to compile the binary. While we need to know the compiler version to parse the structure correctly, I think we can check offsets for expected data on the fly as long as we do it in the right order.
So we read the offset where TEXT and ETEXT should be, check if the values falls within the .text
section of the file. We can also "verify" that we read the right address by also checking for example the start of data and end of data. If the address is not in the expected range, we use another offset for other compiler versions. Once we find the right offset, we can compare it to the start address of the text section found in the header. If it's different, we use the value from the module data as the image base.
Do you have a test binary that can be used for testing the implementation?
from gore.
My testdata placed at https://github.com/Zxilly/go-testdata. But I think the offset may not a fixed value. As the golang team member said:
For a pure Go binary, we put it at the start o f the text section and text segment. But for a cgo binary, the C linker doesn't necessarily to do so. It can put some C functions in the text section before the runtime.text symbol.
The value of offset can change a lot while the devs static link some c library.
from gore.
The moduledata's text field is where Go thinks it starts. https://github.com/golang/go/blob/653abefddf2c627d5d1805d35e55c5a01eb32c6e/src/runtime/symtab.go#L320 from the runtime. The debug: https://github.com/golang/go/blob/653abefddf2c627d5d1805d35e55c5a01eb32c6e/src/debug/gosym/pclntab.go#L58
from gore.
I read some other reverse implementation12, maybe we can search moduledata based on magic number, then validate the address one by one?
Footnotes
from gore.
I would first find the pclntab based on its magic bytes. Get the virtual address of that offset. Search for the offset in the binary to find the moduledata since it is the first field in the structure.
This is pretty much how these two structures are found in PE files already.
from gore.
Validating moduledata is easy if we find it first. We can use the text&etext
addr and pclntab data for validation. For text&etext
, we can check the .text section, and for pclntab, we can verify the magic number at the start.
However, if we find pnlntab first, we have to build gosym.Table twice because we didn't get the accurate pc base for it during the first search. These are my suggestions.
from gore.
We can create a magic pair
for moduledata and pclntab magic numbers. They should pair each other in the same golang version.
from gore.
I will rewrite the PR associated with the moduledata search based implementation. Please let me know if you have any ideas.
from gore.
Validating moduledata is easy if we find it first. We can use the
text&etext
addr and pclntab data for validation. Fortext&etext
, we can check the .text section, and for pclntab, we can verify the magic number at the start.However, if we find pnlntab first, we have to build gosym.Table twice because we didn't get the accurate pc base for it during the first search. These are my suggestions.
We don't need to parse the pclntab twice, we just need the address of the structure so it can be used to find the moduledata structure. After we have the moduledata, we can parse the pclntab with the correct text start.
from gore.
Can you add more explain about Search for the offset in the binary to find the moduledata since it is the first field in the structure.
?
I found the moduledata defind in the golang source like
type moduledata struct {
sys.NotInHeap // Only in static data
pcHeader *pcHeader
funcnametab []byte
cutab []uint32
filetab []byte
pctab []byte
pclntable []byte
ftab []functab
findfunctab uintptr
minpc, maxpc uintptr
text, etext uintptr
noptrdata, enoptrdata uintptr
data, edata uintptr
bss, ebss uintptr
noptrbss, enoptrbss uintptr
covctrs, ecovctrs uintptr
end, gcdata, gcbss uintptr
types, etypes uintptr
rodata uintptr
gofunc uintptr // go.func.*
textsectmap []textsect
typelinks []int32 // offsets from types
itablinks []*itab
ptab []ptabEntry
pluginpath string
pkghashes []modulehash
// This slice records the initializing tasks that need to be
// done to start up the program. It is built by the linker.
inittasks []*initTask
modulename string
modulehashes []modulehash
hasmain uint8 // 1 if module contains the main function, 0 otherwise
gcdatamask, gcbssmask bitvector
typemap map[typeOff]*_type // offset to *_rtype in previous module
bad bool // module failed to load and should be ignored
next *moduledata
}
Is only this part persist in the binary?
text, etext uintptr
noptrdata, enoptrdata uintptr
data, edata uintptr
bss, ebss uintptr
noptrbss, enoptrbss uintptr
covctrs, ecovctrs uintptr
end, gcdata, gcbss uintptr
types, etypes uintptr
rodata uintptr
gofunc uintptr // go.func.*
from gore.
The whole structure is present in the binary. Check the different structures in the moduledata.go
file. The size is different depending on 32 vs 64 bit and Go version. The logic used to find it is:
func findModuledata(f fileHandler) ([]byte, error) {
_, secData, err := f.getSectionData(f.moduledataSection())
if err != nil {
return nil, err
}
/// Get the virtual address of the PCLNTAB.
tabAddr, _, err := f.getPCLNTABData()
if err != nil {
return nil, err
}
// Search for moduledata using the address to the PCLNTAB. The match will be hit on `*pcHeader` which is the first field.
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, &tabAddr)
if err != nil {
return nil, err
}
off := bytes.Index(secData, buf.Bytes()[:intSize32])
if off == -1 {
return nil, errors.New("could not find moduledata")
}
// TODO: Verify that hit is correct.
return secData[off : off+0x300], nil
}
For PE files gore searches the .rdata
and .text
sections for the PCLNTAB using this logic:
func searchSectionForTab(secData []byte) ([]byte, error) {
// First check for the current magic used. If this fails, it could be
// an older version. So check for the old header.
MAGIC_LOOP:
for _, magic := range [][]byte{pclntab120magic, pclntab118magic, pclntab116magic, pclntab12magic} {
off := bytes.LastIndex(secData, magic)
if off == -1 {
continue // Try other magic.
}
for off != -1 {
if off != 0 {
buf := secData[off:]
if len(buf) < 16 || buf[4] != 0 || buf[5] != 0 ||
(buf[6] != 1 && buf[6] != 2 && buf[6] != 4) || // pc quantum
(buf[7] != 4 && buf[7] != 8) { // pointer size
// Header doesn't match.
if off-1 <= 0 {
continue MAGIC_LOOP
}
off = bytes.LastIndex(secData[:off-1], magic)
continue
}
// Header match
return secData[off:], nil
}
break
}
}
return nil, ErrNoPCLNTab
}
The address of the PCLNTAB is calculated using: addr := sec.VirtualAddress + uint32(len(secData)-len(tab))
. Where tab[0] is the starting byte of the PCLNTAB. The secData is a []byte
of the section. tab
is a sub-slice of secData
starting at the PCLNTAB plus the tail of the section. len(secData)-len(tab)
calculates the PCLNTAB offset in the section. The calculated virtual address is the pcHeader
field in the moduledata structure.
I maybe can get a chance to code this up this weekend. Otherwise, if you want take a stab at it, please go ahead.
Essentially, I would add this logic to ELF and MachO files as a fallback if the named sections don't exist.
from gore.
I can port this logic to elf and macho, but before we can do that we need to merge the #77
from gore.
Related Issues (20)
- String extraction functionality
- MIPS support
- Fix the type parser
- Fix function line logic
- Don't skip built-in packages
- Use semver library for version compare
- panic: runtime error: index out of range [757869355] with length 33282560 HOT 2
- Fix map struct parsing for Go 1.7 to 1.13 HOT 1
- Extract build information if available
- GetTypes panic: runtime error: invalid memory address or nil pointer dereference. HOT 1
- support go1.18 HOT 2
- no pclntab located HOT 1
- GetGoRoot return ErrNoGoRootFound when running on windows
- Replacing extracted information HOT 3
- Confusion about LICENSE HOT 2
- external linked pie elf binary can work without .gopclntab section
- codecov/codecov-action v1 will not work after 2024 Feb 1 HOT 1
- `GoVersionCompare` can provide wrong result
- Generate of moduledata timeout HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from gore.