Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

debug/pe: Failed to parse an exe file that is modified by UpdateResourceA Windows API #65145

Closed
mkch opened this issue Jan 18, 2024 · 3 comments

Comments

@mkch
Copy link

mkch commented Jan 18, 2024

Go version

go version go1.21.6 windows/amd64

Output of go env in your module/workspace:

set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\gopher\AppData\Local\go-build
set GOENV=C:\Users\gopher\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\gopher\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\gopher\go
set GOPRIVATE=
set GOPROXY=https://goproxy.cn,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.21.6
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\gopher\AppData\Local\Temp\go-build2882690104=/tmp/go-build -gno-record-gcc-switches

What did you do?

Two files in a directory:

// main.go
package main

import "syscall"

func main() {
	syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxA").Call(0, 0, 0, 0)
}
// addres.go
package main

import (
	"debug/pe"
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

func main() {
	dll := syscall.NewLazyDLL("kernel32.dll")
	var filename = [9]byte{'m', 'a', 'i', 'n', '.', 'e', 'x', 'e', 0} // NULL terminated string "main.exe"
	h, _, err := dll.NewProc("BeginUpdateResourceA").Call(uintptr(unsafe.Pointer(&filename)), 0)
	if h == 0 {
		panic(err)
	}
	const RT_RCDATA = 10 // Application-defined resource (raw data).
	var data = [1]byte{1}
	r, _, err := dll.NewProc("UpdateResourceA").Call(h, RT_RCDATA, 100, 0, uintptr(unsafe.Pointer(&data)), uintptr(len(data)))
	if r == 0 {
		panic(err)
	}
	r, _, err = dll.NewProc("EndUpdateResourceA").Call(h, 0)
	if r == 0 {
		panic(err)
	}

	f, err := os.Open("main.exe")
	if err != nil {
		panic(err)
	}
	pe, err := pe.NewFile(f)
	fmt.Printf("%p, %v", pe, err)
}

Execute the the following commands in that directory:

go build main.go
go run addres.go

What did you see happen?

0x0, fail to read string table: unexpected EOF

What did you expect to see?

<some_pointer>, nil

@qmuntal qmuntal added OS-Windows NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Jan 18, 2024
@qmuntal
Copy link
Contributor

qmuntal commented Jan 18, 2024

I can reproduce this issue, but it seems that the UpdateResourceA call, as used here, is partially corrupting the binary. It can still be executed, but the string table length does not contain a valid value, and pe.NewFile correctly errors out when it fails to read it.

Other PE parsers also fail to read that image:

>llvm-readobj --headers main.exe
llvm-readobj.exe: error: 'main.exe': string table empty

>dumpbin main.exe                        
Microsoft (R) COFF/PE Dumper Version 14.29.30153.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file main.exe

File Type: EXECUTABLE IMAGE
main.exe : fatal error LNK1106: invalid file or disk full: cannot seek to 0x183482

@golang/windows

@aarzilli
Copy link
Contributor

I can reproduce this issue, but it seems that the UpdateResourceA call, as used here, is partially corrupting the binary

FWIW I reached a similar conclusion:

modified-main.exe does look malformed, the PointerToSymbolTable and NumberOfSymbol entries of the file header are unchanged, even though something has been added to the file, so debug/pe can't find the string table

However I don't know enough about PE and (especially) UpdateResource to be sure.

@qmuntal
Copy link
Contributor

qmuntal commented Jan 18, 2024

However I don't know enough about PE and (especially) UpdateResource to be sure.

Doing a quick online search I've found that UpdateResource can indeed corrupt PE files, there are multiple reports of this happening. E.g.: electron/rcedit#69 (comment)

Since rcedit relies on the BeginUpdateResource/UpdateResource/EndUpdateResource winapi functions to do all the actual work of modifying an exe, it appears those functions trim off anything past the "official" end of the .exe file (as defined by the file size in the header), which is a popular place to put extra program data, which will be ignored by the OS.

People has come up with workarounds to fix the corrupted binaries, but there is nothing debug/pe can do here other than complain when a PE file is not compliant.

@qmuntal qmuntal closed this as completed Jan 18, 2024
@qmuntal qmuntal closed this as not planned Won't fix, can't repro, duplicate, stale Jan 18, 2024
@qmuntal qmuntal removed the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants