Skip to content

Instantly share code, notes, and snippets.

@Anaminus
Last active January 1, 2016 19:29
Show Gist options
  • Save Anaminus/8190330 to your computer and use it in GitHub Desktop.
Save Anaminus/8190330 to your computer and use it in GitHub Desktop.
// Thanks:
// http://stackoverflow.com/questions/12396665/c-library-to-read-exe-version-from-linux#answer-12486703
package main
import (
"encoding/binary"
"errors"
"os"
)
func readWord(file *os.File, off int64) int64 {
file.Seek(off, 0)
var n uint16
binary.Read(file, binary.LittleEndian, &n)
return int64(n)
}
func readDword(file *os.File, off int64) int64 {
file.Seek(off, 0)
var n uint32
binary.Read(file, binary.LittleEndian, &n)
return int64(n)
}
func pad(x int64) int64 {
return (x + 3) & 0xFFFFFFFC
}
func equals(file *os.File, off int64, value string) bool {
b := make([]byte, len(value))
file.ReadAt(b, off)
return string(b) == value
}
func findVersion(file *os.File) (ptr int64, err error) {
if !equals(file, 0, "MZ") {
return 0, errors.New("No MZ_HEADER")
}
pe := readWord(file, 0x3C)
if !equals(file, pe, "PE") {
return 0, errors.New("No PE_HEADER")
}
coff := pe + 4
numSections := readWord(file, coff+2)
optHeaderSize := readWord(file, coff+16)
if numSections == 0 || optHeaderSize == 0 {
return 0, errors.New("No opt header")
}
optHeader := coff + 20
if readWord(file, optHeader) != 0x10b {
return 0, errors.New("no optional header magic")
}
dataDir := optHeader + 96
vaRes := readDword(file, dataDir+8*2)
secTable := optHeader + optHeaderSize
for i := 0; i < int(numSections); i++ {
sec := secTable + 40*int64(i)
if !equals(file, sec, ".rsrc") {
continue
}
vaSec := readDword(file, sec+12)
raw := readDword(file, sec+20)
resSec := raw + (vaRes - vaSec)
numNamed := readWord(file, resSec+12)
numId := readWord(file, resSec+14)
// print("CHECK ", numNamed, " ", numId, "\n")
for j := 0; j < int(numNamed+numId); j++ {
// resSec is a IMAGE_RESOURCE_DIRECTORY followed by an array of
// IMAGE_RESOURCE_DIRECTORY_ENTRY
res := resSec + 16 + 8*int64(j)
name := readDword(file, res)
if name != 16 {
continue
}
offs := readDword(file, res+4)
if offs&0x80000000 == 0 {
return 0, errors.New("no dir resource")
}
// verDir is another IMAGE_RESOURCE_DIRECTORY and
// IMAGE_RESOURCE_DIRECTORY_ENTRY array
verDir := resSec + (offs & 0x7FFFFFFF)
numNamed = readWord(file, verDir+12)
numId = readWord(file, verDir+14)
if numNamed == 0 && numId == 0 {
return 0, errors.New("resource empty")
}
res = verDir + 16
offs = readDword(file, res+4)
if offs&0x80000000 == 0 {
return 0, errors.New("no dir ressource")
}
// and yet another IMAGE_RESOURCE_DIRECTORY, etc.
verDir = resSec + (offs & 0x7FFFFFFF)
numNamed = readWord(file, verDir+12)
numId = readWord(file, verDir+14)
if numNamed == 0 && numId == 0 {
return 0, errors.New("resource empty")
}
res = verDir + 16
offs = readDword(file, res+4)
if offs&0x80000000 != 0 {
return 0, errors.New("no dir resource")
}
verDir = resSec + offs
verVa := readDword(file, verDir)
//verSize := readDword(file, verDir+4)
verPtr := raw + (verVa - vaSec)
return verPtr, nil
}
return 0, errors.New("")
}
return 0, errors.New("")
}
func recParse(file *os.File, ptr int64, offs int64, version *uint64) int64 {
len := readWord(file, ptr+offs)
offs += 2
valLen := readWord(file, ptr+offs)
offs += 2
vtype := readWord(file, ptr+offs)
offs += 2
info := []byte{}
for i := 0; i < 200; i++ {
c := readWord(file, ptr+offs)
if c == 0 {
break
}
offs += 2
info = append(info, byte(c))
}
offs = pad(offs)
if vtype != 0 { // TEXT
value := []byte{}
for i := 0; i < int(valLen); i++ {
c := readWord(file, ptr+offs)
offs += 2
value = append(value, byte(c))
}
} else {
if string(info) == "VS_VERSION_INFO" {
fixed := ptr + offs
//dwSignature := readDword(file, fixed+4)
//dwStrucVersion := readDword(file, fixed+8)
//dwFileVersionMS := readDword(file, fixed+12)
//dwFileVersionLS := readDword(file, fixed+16)
dwProductVersionMS := readDword(file, fixed+12)
dwProductVersionLS := readDword(file, fixed+16)
//dwFileFlagsMask := readDword(file, fixed+28)
//dwFileFlags := readDword(file, fixed+32)
//dwFileOS := readDword(file, fixed+36)
//dwFileType := readDword(file, fixed+40)
//dwFileSubtype := readDword(file, fixed+44)
//dwFileDateMS := readDword(file, fixed+48)
//dwFileDateLS := readDword(file, fixed+52)
*version = uint64(dwProductVersionMS<<32) | uint64(dwProductVersionLS)
}
offs += valLen
}
for offs < len {
offs = recParse(file, ptr, offs, version)
}
return pad(offs)
}
func parseVersion(file *os.File, ptr int64) (version uint64) {
recParse(file, ptr, 0, &version)
return version
}
func ReadVersion(path string) (version Version) {
file, _ := os.Open(path)
defer file.Close()
ptr, err := findVersion(file)
if err != nil {
return
}
ver := parseVersion(file, ptr)
version.Major = uint16(ver >> 0x30)
version.Minor = uint16(ver >> 0x20)
version.Release = uint16(ver >> 0x10)
version.Build = uint16(ver >> 0x00)
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment