Add ability to create a git repository from a BG3 install

Add library to parse version info from a windows PE executable
There are issues with go-git, will change to a wrapper library
This commit is contained in:
lordwelch 2021-02-15 21:47:38 -08:00
parent 59f8bdcc54
commit b5d007c865
15 changed files with 930 additions and 17 deletions

View File

@ -0,0 +1,19 @@
package main
import (
"github.com/jdferrell3/peinfo-go/peinfo"
)
func getPEFileVersion(filepath string) string {
f, err := peinfo.Initialize(filepath, false, "", false)
if err != nil {
panic(err)
}
defer f.OSFile.Close()
defer f.PEFile.Close()
v, _, err := f.GetVersionInfo()
if err != nil {
panic(err)
}
return v["FileVersion"]
}

View File

@ -1,15 +1,21 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/go-git/go-git/v5"
"github.com/hashicorp/go-multierror"
"git.narnian.us/lordwelch/lsgo/gog"
ie "git.narnian.us/lordwelch/lsgo/internal/exec"
"golang.org/x/sys/windows/registry"
)
@ -19,26 +25,202 @@ var (
initFlags struct {
BG3Path string
divinePath string
Repo string
}
)
func initRepository(arguments []string) {
const ignore = `# ignores all binary files
*.wem
*.dds
*.png
*.jpg
*.gr2
*.bin
*.patch
*.bshd
*.tga
*.ttf
*.fnt
Assets_pak/Public/Shared/Assets/Characters/_Anims/Humans/_Female/HUM_F_Rig/HUM_F_Rig_DFLT_SPL_Somatic_MimeTalk_01
Assets_pak/Public/Shared/Assets/Characters/_Models/_Creatures/Beholder/BEH_Spectator_A_GM_DDS
Assets_pak/Public/Shared/Assets/Consumables/CONS_GEN_Food_Turkey_ABC/CONS_GEN_Food_Turkey_ABC.ma
Assets_pak/Public/Shared/Assets/Decoration/Harbour/DEC_Harbour_Shell_ABC/DEC_Harbour_Shell_Venus_A
*.bnk
*.pak
*.data
*.osi
*.cur
*.gtp
*.gts
*.ffxanim
*.ffxactor
*.ffxbones
*.bk2
`
type BG3Repo struct {
git *git.Repository
gitPath string
gog gog.GOGalaxy
BG3Path string
divinePath string
Empty bool
}
func openRepository(path string) (BG3Repo, error) {
var (
BG3Path, divinePath string
repo *git.Repository
err error
b BG3Repo
err error
)
BG3Path, divinePath = preRequisites()
if BG3Path == "" {
b.gitPath = path
b.BG3Path, b.divinePath = preRequisites()
if b.BG3Path == "" {
panic("Could not locate Baldur's Gate 3")
}
fmt.Println(divinePath)
panic("Baldur's Gate 3 located " + BG3Path)
repo, err = git.PlainInit(".", false)
b.git, err = git.PlainOpen(b.gitPath)
if err != nil {
panic(err)
b.Empty = true
}
repo.Fetch(nil)
return b, nil
}
func (bgr *BG3Repo) Init() error {
var err error
if bgr.Empty || bgr.git == nil {
bgr.git, err = git.PlainInit(bgr.gitPath, false)
if err == git.ErrRepositoryAlreadyExists {
bgr.git, err = git.PlainOpen(bgr.gitPath)
if err != nil {
return err
}
}
}
if citer, err := bgr.git.CommitObjects(); err == nil {
if _, err := citer.Next(); err == nil {
// Repo contains commits, nothing to do
return nil
}
}
return bgr.AddNewVersion()
}
func (bgr *BG3Repo) RetrieveGOGalaxy() error {
var err error
bgr.gog, err = gog.RetrieveGOGInfo("1456460669")
if err != nil {
return err
}
return nil
}
func (bgr *BG3Repo) AddNewVersion() error {
var (
version string
changelog gog.Change
err error
)
version = getPEFileVersion(filepath.Join(bgr.BG3Path, "bin/bg3.exe"))
err = bgr.RetrieveGOGalaxy()
if err != nil {
return err
}
changelog, err = gog.ParseChangelog(bgr.gog.Changelog, bgr.gog.Title)
if err != nil {
return err
}
versionChanges := changelog.FindChange(version)
if versionChanges == nil {
return fmt.Errorf("no version information found for %q", version)
}
// TODO: Replace with custom errors
t, err := bgr.git.Worktree()
if err != nil {
return err
}
var stat git.Status
stat, err = t.Status()
if err != nil {
return err
}
if !stat.IsClean() {
return errors.New("git working directory must be clean before adding a new version, please save your work:\n" + stat.String())
}
if _, err := t.Filesystem.Stat(".gitignore"); err != nil {
gitignore, err := t.Filesystem.Create(".gitignore")
if err != nil {
return err
}
_, err = gitignore.Write([]byte(ignore))
if err != nil {
return err
}
gitignore.Close()
}
t.Add(".gitignore")
wg := &sync.WaitGroup{}
mut := &sync.Mutex{}
errs := new(multierror.Error)
paths := []string{}
err = filepath.Walk(filepath.Join(bgr.BG3Path, "Data"), func(path string, info os.FileInfo, err error) error {
rpath := strings.TrimPrefix(path, filepath.Join(bgr.BG3Path, "Data"))
fmt.Println("bg3 ", filepath.Join(bgr.BG3Path, "Data"))
fmt.Println("path ", path)
fmt.Println("rpath", rpath)
repoPath := filepath.Join(filepath.Dir(rpath), strings.TrimSuffix(filepath.Base(rpath), filepath.Ext(rpath))+strings.ReplaceAll(filepath.Ext(rpath), ".", "_"))
fmt.Println("repopath", repoPath)
if filepath.Ext(info.Name()) != ".pak" || info.IsDir() || strings.Contains(info.Name(), "Textures") {
return nil
}
err = t.Filesystem.MkdirAll(repoPath, 0o660)
if err != nil {
return err
}
paths = append(paths, repoPath)
divine := exec.Command(bgr.divinePath, "-g", "bg3", "-s", path, "-d", filepath.Join(bgr.gitPath, repoPath), "-a", "extract-package")
var buf bytes.Buffer
divine.Stderr = &buf
divine.Stdout = &buf
err = divine.Run()
if err != nil {
return fmt.Errorf("an error occurred running divine.exe: %s", buf.String())
}
wg.Add(1)
go func() {
if err := convert("-w", "-r", filepath.Join(bgr.gitPath, repoPath)); err != nil {
mut.Lock()
errs = multierror.Append(errs, err)
mut.Unlock()
}
wg.Done()
}()
return nil
})
if err != nil {
return err
}
wg.Wait()
if errs.ErrorOrNil() != nil {
return nil
}
for _, v := range paths {
_, err = t.Add(v)
if err != nil {
return err
}
}
_, err = t.Commit(strings.Replace(versionChanges.String(), versionChanges.Title, versionChanges.Title+"\n", 1), nil)
if err != nil {
return err
}
return nil
}
func preRequisites() (BG3Path, divinePath string) {

View File

@ -1,15 +1,43 @@
package main
import "flag"
import (
"fmt"
"os"
)
func main() {
flag.Parse()
switch flag.Arg(0) {
// flag.Parse()
arg := ""
if len(os.Args) > 1 {
arg = os.Args[1]
}
switch arg {
case "convert":
convert(flag.Args()[1:])
err := convert(os.Args[1:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
case "init":
initRepository(flag.Args()[1:])
p := "."
if len(os.Args) > 2 {
p = os.Args[2]
}
repo, err := openRepository(p)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
err = repo.Init()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
default:
convert(flag.Args())
err := convert(os.Args[1:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
}

4
go.mod
View File

@ -4,10 +4,14 @@ go 1.15
replace github.com/pierrec/lz4/v4 v4.1.3 => ./third_party/lz4
replace github.com/jdferrell3/peinfo-go v0.0.0-20191128021257-cff0d8506fb1 => ./third_party/peinfo-go
require (
github.com/go-git/go-git/v5 v5.2.0
github.com/go-kit/kit v0.10.0
github.com/google/uuid v1.1.4
github.com/hashicorp/go-multierror v1.1.0
github.com/jdferrell3/peinfo-go v0.0.0-20191128021257-cff0d8506fb1
github.com/kr/pretty v0.2.1
github.com/pierrec/lz4/v4 v4.1.3
golang.org/x/net v0.0.0-20200301022130-244492dfa37a

8
go.sum
View File

@ -121,11 +121,14 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -300,6 +303,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -354,10 +359,13 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 h1:wHn06sgWHMO1VsQ8F+KzDJx/JzqfsNLnc+oEi07qD7s=
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

15
third_party/peinfo-go/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Mac files
.DS_Store

49
third_party/peinfo-go/LICENSE vendored Normal file
View File

@ -0,0 +1,49 @@
MIT License
Copyright (c) 2019 John Ferrell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
peinfo/revoked.go
Copyright (c) 2014 CloudFlare Inc.
Copyright (c) 2019 John Ferrell
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

62
third_party/peinfo-go/README.md vendored Normal file
View File

@ -0,0 +1,62 @@
# peinfo-go
This is a PE (Portable Executable) parser written in GoLang. I wanted to learn more about the PE format, specifically how the certificates were stored. What better way is there than to write some code?
_This is a work in progress and will continue to change._
This leverages the `debug/pe` package for parsing of the common headers/sections.
Current state:
- Displays some PE details
- Validates certificate, verifies certificate chain, checks against CRL
- Parses Version Info struct
- Displays imports
TODO:
- ~~Actually Parse Version Info struct (currently displayed as raw binary)~~
- Re-write function for finding Version Info (currently written so I could better understand the structure)
- ~~Custom certificate stores~~
## Example
```
[user:~/peinfo-go\ > go run cmd/main.go -certdir ~/RootCerts -versioninfo ~/Downloads/PsExec.exe
type: pe32
TimeDateStamp: 2016-06-28 18:43:09 +0000 UTC
Characteristics: [Executable 32bit]
Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
Cert:
subject: CN=Microsoft Corporation,OU=MOPR,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US
issuer: CN=Microsoft Code Signing PCA,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US
not before: 2015-06-04 17:42:45 +0000 UTC
not after: 2016-09-04 17:42:45 +0000 UTC
CRL: [http://crl.microsoft.com/pki/crl/products/MicCodSigPCA_08-31-2010.crl]
verified: true (chain expired: true)
Version Info:
BuildDate :
BuildVersion :
Comments :
CompanyName : Sysinternals - www.sysinternals.com
Copyright :
FileDescription : Execute processes remotely
FileVersion : 2.2
InternalName : PsExec
LegalCopyright : Copyright (C) 2001-2016 Mark Russinovich
LegalTrademarks :
OriginalFilename : psexec.c
PrivateBuild :
ProductName : Sysinternals PsExec
ProductVersion : 2.2
SpecialBuild :
langCharSet : 040904b0h$
```
## References
- https://golang.org/pkg/debug/pe/
- http://www.pelib.com/resources/luevel.txt
- https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/EXE.pm
- https://github.com/deptofdefense/SalSA/blob/master/pe.py
- https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#resource-directory-entries
- https://github.com/quarkslab/dreamboot/blob/31e155b06802dce94367c38ea93316f7cb86cb15/QuarksUBootkit/PeCoffLib.c
- https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#the-attribute-certificate-table-image-only

7
third_party/peinfo-go/go.mod vendored Normal file
View File

@ -0,0 +1,7 @@
module github.com/jdferrell3/peinfo-go
require (
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
)
go 1.13

10
third_party/peinfo-go/go.sum vendored Normal file
View File

@ -0,0 +1,10 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

17
third_party/peinfo-go/peinfo/consts.go vendored Normal file
View File

@ -0,0 +1,17 @@
package peinfo
var FileOS = map[uint32]string{
0x00001: "Win16",
0x00002: "PM-16",
0x00003: "PM-32",
0x00004: "Win32",
0x10000: "DOS",
0x20000: "OS/2 16-bit",
0x30000: "OS/2 32-bit",
0x40000: "Windows NT",
0x10001: "Windows 16-bit",
0x10004: "Windows 32-bit",
0x20002: "OS/2 16-bit PM-16",
0x30003: "OS/2 32-bit PM-32",
0x40004: "Windows NT 32-bit",
}

View File

@ -0,0 +1,31 @@
package peinfo
import (
"debug/pe"
"os"
"path/filepath"
)
// Initialize returns the config for execution
func Initialize(filePath string, verbose bool, rootCertDir string, extractCert bool) (ConfigT, error) {
fh, err := os.Open(filePath)
if nil != err {
return ConfigT{}, err
}
tempPE, err := pe.NewFile(fh)
if nil != err {
return ConfigT{}, err
}
file := ConfigT{
FileName: filepath.Base(filePath),
OSFile: fh,
PEFile: tempPE,
Verbose: verbose,
ExtractCert: extractCert,
RootCertDir: rootCertDir,
}
return file, nil
}

124
third_party/peinfo-go/peinfo/peinfo.go vendored Normal file
View File

@ -0,0 +1,124 @@
package peinfo
import (
"debug/pe"
"encoding/binary"
"fmt"
"os"
"time"
)
// http://www.pelib.com/resources/luevel.txt
// https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/EXE.pm
// https://github.com/deptofdefense/SalSA/blob/master/pe.py
// https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#resource-directory-entries
// https://github.com/quarkslab/dreamboot/blob/31e155b06802dce94367c38ea93316f7cb86cb15/QuarksUBootkit/PeCoffLib.c
// https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#the-attribute-certificate-table-image-only
// https://docs.microsoft.com/en-us/windows/desktop/menurc/vs-versioninfo
var (
sizeofOptionalHeader32 = uint16(binary.Size(pe.OptionalHeader32{}))
sizeofOptionalHeader64 = uint16(binary.Size(pe.OptionalHeader64{}))
)
type ImageDirectoryT struct {
Type int
VirtualAddress uint32
Size uint32
ImageBase uint64
}
func (cfg *ConfigT) HeaderMagic() uint16 {
pe64 := cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64
if pe64 {
return cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).Magic
}
return cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).Magic
}
func (cfg *ConfigT) GetPEType() string {
t := "pe32"
if cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64 {
t = "pe32+"
}
return t
}
func (cfg *ConfigT) GetImageSubSystem() string {
pe64 := cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64
subsystem := map[uint16]string{
0: "IMAGE_SUBSYSTEM_UNKNOWN",
1: "IMAGE_SUBSYSTEM_NATIVE",
2: "IMAGE_SUBSYSTEM_WINDOWS_GUI",
3: "IMAGE_SUBSYSTEM_WINDOWS_CUI",
4: "IMAGE_SUBSYSTEM_OS2_CUI",
5: "IMAGE_SUBSYSTEM_POSIX_CUI",
9: "IMAGE_SUBSYSTEM_WINDOWS_CE_GUI",
10: "IMAGE_SUBSYSTEM_EFI_APPLICATION",
11: "IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER",
12: "IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER",
13: "IMAGE_SUBSYSTEM_EFI_ROM",
14: "IMAGE_SUBSYSTEM_XBOX",
15: "IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION",
}
if pe64 {
return subsystem[cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).Subsystem]
}
return subsystem[cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).Subsystem]
}
// GetCharacteristics returns a list of PE characteristics
func (cfg *ConfigT) GetCharacteristics() []string {
characteristics := []string{}
if (cfg.PEFile.FileHeader.Characteristics & 0x0002) > 1 {
characteristics = append(characteristics, "Executable")
}
if (cfg.PEFile.FileHeader.Characteristics & 0x0100) > 1 {
characteristics = append(characteristics, "32bit")
}
if (cfg.PEFile.FileHeader.Characteristics & 0x2000) > 1 {
characteristics = append(characteristics, "DLL")
}
return characteristics
}
// GetTimeDateStamp returns the date-time stamp in the PE's header
func (cfg *ConfigT) GetTimeDateStamp() string {
tm := time.Unix(int64(cfg.PEFile.FileHeader.TimeDateStamp), 0)
return fmt.Sprintf("%s", tm.UTC())
}
// FindDataDirectory
func (cfg *ConfigT) FindDataDirectory(imageDirectoryEntryType int) (idd ImageDirectoryT) {
pe64 := cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64
var dd pe.DataDirectory
if pe64 {
dd = cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).DataDirectory[imageDirectoryEntryType]
idd.ImageBase = cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase
} else {
dd = cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).DataDirectory[imageDirectoryEntryType]
idd.ImageBase = uint64(cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).ImageBase)
}
idd.VirtualAddress = dd.VirtualAddress
idd.Size = dd.Size
idd.Type = imageDirectoryEntryType
return idd
}
// Tell is a wrapper for Seek()
func (cfg *ConfigT) Tell() int64 {
pos, _ := cfg.OSFile.Seek(0, os.SEEK_CUR)
return pos
}

90
third_party/peinfo-go/peinfo/structs.go vendored Normal file
View File

@ -0,0 +1,90 @@
package peinfo
import (
"debug/pe"
"os"
)
type ConfigT struct {
FileName string
OSFile *os.File
PEFile *pe.File
ExtractCert bool
Verbose bool
RootCertDir string
}
type ResourceDirectoryD struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
NumberOfNamedEntries uint16
NumberOfIdEntries uint16
}
type CertDetails struct {
Length uint32
Revision uint16
CertificateType uint16
DER []byte
}
// typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
// union {
// struct {
// DWORD NameOffset:31;
// DWORD NameIsString:1;
// };
// DWORD Name;
// WORD Id;
// };
// union {
// DWORD OffsetToData;
// struct {
// DWORD OffsetToDirectory:31;
// DWORD DataIsDirectory:1;
// };
// };
// } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
type ResourceDirectoryEntry struct {
Name uint32
OffsetToData uint32
}
type ResourceDirectoryEntryNamed struct {
Name uint32
OffsetToData uint32
}
/* Resource Directory Entry */
// type ResourceDirectoryEntryT struct {
// ResourceDirectoryEntry ResourceDirectoryEntry
// FileOffset uint32
// Size uint32
// DataIsDirectory bool
// }
type _IMAGE_RESOURCE_DATA_ENTRY struct {
OffsetToData uint32
Size uint32
CodePage uint32
Reserved uint32
}
type VS_FIXEDFILEINFO struct {
DwSignature uint32
DwStrucVersion uint32
DwFileVersionMS uint32
DwFileVersionLS uint32
DwProductVersionMS uint32
DwProductVersionLS uint32
DwFileFlagsMask uint32
DwFileFlags uint32
DwFileOS uint32
DwFileType uint32
DwFileSubtype uint32
DwFileDateMS uint32
DwFileDateLS uint32
}

267
third_party/peinfo-go/peinfo/verinfo.go vendored Normal file
View File

@ -0,0 +1,267 @@
package peinfo
import (
"bytes"
"debug/pe"
"encoding/binary"
"encoding/hex"
"fmt"
"os"
)
const (
RT_VERSION = 16
)
func (cfg *ConfigT) FindVerInfoOffset(fileOffset int64, sectionOffset uint32, sectionVirtualAddress uint32) (verInfoOffset int64, len uint32, err error) {
pos, _ := cfg.OSFile.Seek(fileOffset, os.SEEK_SET)
if pos != fileOffset {
return 0, 0, fmt.Errorf("did not seek to offset")
}
type VerInfoDetailsT struct {
Off uint32
Len uint32
D1 uint32
D2 uint32
}
var peoff VerInfoDetailsT
err = binary.Read(cfg.OSFile, binary.LittleEndian, &peoff)
if nil != err {
return verInfoOffset, len, err
}
// $filePos = $off + $$section{Base} - $$section{VirtualAddress};
verInfoOffset = int64(peoff.Off + sectionOffset - sectionVirtualAddress)
return verInfoOffset, peoff.Len, nil
}
func (cfg *ConfigT) GetVersionInfo() (vi map[string]string, keys []string, err error) {
vi = map[string]string{
"BuildDate": "",
"BuildVersion": "",
"Comments": "",
"CompanyName": "",
"Copyright": "",
"FileDescription": "",
"FileVersion": "",
"InternalName": "",
"LegalCopyright": "",
"LegalTrademarks": "",
"OriginalFilename": "",
"PrivateBuild": "",
"ProductName": "",
"ProductVersion": "",
"SpecialBuild": "",
"langCharSet": "",
// "varFileInfo": "",
}
keys = []string{
"BuildDate",
"BuildVersion",
"Comments",
"CompanyName",
"Copyright",
"FileDescription",
"FileVersion",
"InternalName",
"LegalCopyright",
"LegalTrademarks",
"OriginalFilename",
"PrivateBuild",
"ProductName",
"ProductVersion",
"SpecialBuild",
"langCharSet",
// "varFileInfo"
}
section := cfg.PEFile.Section(".rsrc")
if section == nil {
return vi, keys, fmt.Errorf("resource section not found")
}
// fmt.Printf("%+v\n", section)
// Resource
_, err = cfg.OSFile.Seek(int64(0), os.SEEK_SET)
if nil != err {
return vi, keys, err
}
idd := cfg.FindDataDirectory(pe.IMAGE_DIRECTORY_ENTRY_RESOURCE)
idd.VirtualAddress -= (section.VirtualAddress - section.Offset)
if cfg.Verbose {
fmt.Printf("IMAGE_DIRECTORY_ENTRY_RESOURCE virtual address: %d\n", idd.VirtualAddress)
fmt.Printf("IMAGE_DIRECTORY_ENTRY_RESOURCE size: %d\n", idd.Size)
fmt.Printf("IMAGE_DIRECTORY_ENTRY_RESOURCE image base: %d\n", idd.ImageBase)
}
pos, err := cfg.OSFile.Seek(int64(idd.VirtualAddress), os.SEEK_SET)
if nil != err {
return vi, keys, err
}
if pos != int64(idd.VirtualAddress) {
fmt.Errorf("did not seek to VirtualAddress")
}
var table ResourceDirectoryD
err = binary.Read(cfg.OSFile, binary.LittleEndian, &table)
if nil != err {
return vi, keys, err
}
// fmt.Printf("table %+v\n", table)
x := 0
for x < int(table.NumberOfNamedEntries+table.NumberOfIdEntries) {
var entry ResourceDirectoryEntry
err = binary.Read(cfg.OSFile, binary.LittleEndian, &entry)
if nil != err {
return vi, keys, err
}
if entry.Name == RT_VERSION {
// Directory
if (entry.OffsetToData&0x80000000)>>31 == 1 {
new := entry.OffsetToData&0x7fffffff + idd.VirtualAddress
cfg.OSFile.Seek(int64(new), os.SEEK_SET)
var innerDir ResourceDirectoryD
err = binary.Read(cfg.OSFile, binary.LittleEndian, &innerDir)
if nil != err {
return vi, keys, err
}
// pos := f.Tell()
// fmt.Printf("level 1 innerDir %+v (file offset=%d)\n", innerDir, pos)
y := 0
for y < int(innerDir.NumberOfNamedEntries+innerDir.NumberOfIdEntries) {
var entry ResourceDirectoryEntry
err = binary.Read(cfg.OSFile, binary.LittleEndian, &entry)
if nil != err {
return vi, keys, err
}
// pos := f.Tell()
// fmt.Printf("item %d - level 2 buff %s (file offset=%d)\n", y, entry, pos)
if (entry.OffsetToData&0x80000000)>>31 == 1 {
new := entry.OffsetToData&0x7fffffff + idd.VirtualAddress
// fmt.Printf("level 2 DirStart 0x%x (%d)\n", new, new)
cfg.OSFile.Seek(int64(new), os.SEEK_SET)
}
var innerDir ResourceDirectoryD
err = binary.Read(cfg.OSFile, binary.LittleEndian, &innerDir)
if nil != err {
return vi, keys, err
}
// pos = f.Tell()
// fmt.Printf("level 3 innerDir %+v (file offset=%d)\n", innerDir, pos)
z := 0
for z < int(innerDir.NumberOfNamedEntries+innerDir.NumberOfIdEntries) {
var entry ResourceDirectoryEntry
err = binary.Read(cfg.OSFile, binary.LittleEndian, &entry)
if nil != err {
return vi, keys, err
}
// pos := f.Tell()
// fmt.Printf("item %d - level 3 buff %s (file offset=%d)\n", y, entry, pos)
// fmt.Printf("ver: 0x%x\n", entry.OffsetToData+idd.VirtualAddress)
// find offset of VS_VERSION_INFO
off := int64(entry.OffsetToData + idd.VirtualAddress)
viPos, viLen, err := cfg.FindVerInfoOffset(off, section.SectionHeader.Offset, section.SectionHeader.VirtualAddress)
if nil != err {
return vi, keys, err
}
// fmt.Printf("VerInfo Struct filePos: 0x%x (%d)\n", viPos, viPos)
cfg.OSFile.Seek(viPos, os.SEEK_SET)
b := make([]byte, viLen)
err = binary.Read(cfg.OSFile, binary.LittleEndian, &b)
if nil != err {
return vi, keys, err
}
// fmt.Printf("%s\n", b)
if cfg.Verbose {
fmt.Printf(" %s\n", hex.Dump(b))
}
vi, err = parseVersionInfo(b, vi)
if nil != err {
return vi, keys, err
}
return vi, keys, nil
}
y++
}
}
}
x++
}
return vi, keys, fmt.Errorf("no version info found")
}
func parseVersionInfo(vi []byte, versionInfo map[string]string) (map[string]string, error) {
// Grab everything after "StringFileInfo"
stringFileInfoTemp := bytes.Split(vi, []byte{0x53, 0x0, 0x74, 0x0, 0x72, 0x0, 0x69, 0x0, 0x6e, 0x0, 0x67, 0x0, 0x46, 0x0, 0x69, 0x0, 0x6c, 0x0, 0x65, 0x0, 0x49, 0x0, 0x6e, 0x0, 0x66, 0x0, 0x6f})
if len(stringFileInfoTemp) < 2 {
err := fmt.Errorf("can't find StringFileInfo bytes")
return versionInfo, err
}
stringFileInfo := stringFileInfoTemp[1]
divide := bytes.Split(stringFileInfo, []byte{0x0, 0x1, 0x0})
langCharSet := trimSlice(divide[1])
versionInfo["langCharSet"] = string(langCharSet)
// check for slice out of bounds
if len(divide) < 3 {
err := fmt.Errorf("VersionInfo slice too small")
return versionInfo, err
}
end := len(divide) - 1
if end < 2 {
err := fmt.Errorf("slice end less than start")
return versionInfo, err
}
values := divide[2:end]
// TODO: handle varFileInfo, currently contains binary information which chrome does not display
// varFileInfo := divide[len(divide)-1]
// versionInfo["varFileInfo"] = string(trimSlice(varFileInfo))
for _, element := range values {
temp := bytes.Split(element, []byte{0x0, 0x0, 0x0})
valueInfo := temp[:len(temp)-1]
if len(valueInfo) > 1 {
name := string(trimSlice(valueInfo[0]))
value := string(trimSlice(valueInfo[1]))
versionInfo[name] = value
}
}
return versionInfo, nil
}
func trimSlice(nonTrimmed []byte) (trimmed []byte) {
for bytes.HasPrefix(nonTrimmed, []byte{0x0}) {
nonTrimmed = nonTrimmed[1:]
}
for i, val := range nonTrimmed {
if i%2 == 0 && val != 0x0 {
trimmed = append(trimmed, val)
}
}
return trimmed
}