Windows API Call Variable Issue - Inconsistent Results

I am working on an application which relies heavily on calling the Windows API but whilst testing some code I found what seems to be inconsistencies in the result of API calls depending on the structure of the go code. I have been reading the official documentation but cannot pinpoint what exactly is happening here.

I have put together an example in a gist which can be found here: https://gist.github.com/niallnsec/1ffc71faa8d1f6a9ceaf27492ceadb8c
Note: For the code to run there must be a copy of notepad running on the system and the code will likely need to be run as an administrator. Also this code will only work on 64 bit systems.

The code walks the process tree looking for Notepad.exe and when it is located it tries to extract the command line specified for the process. What has got me confused is that when calling ReadProcessMemory in the main function it will successfully read the string out of memory. However, when calling the function in exactly the same way inside another method (StringRemote) no data will be returned.

On my system (Windows 10 x64, golang 1.10.1) running the code gives the following output (I am using Notepad2.exe on my system but the process being queried is largely irrelevant):

PID: 16744
Base Addr: a42000
ProcID: 4168
Params: 2d92a40
CMD Line: 2d930a6
L HProc: fc
L PBuffer: 2d930a6
L BLength: 52
R HProc: fc
R PBuffer: 2d930a6
R BLength: 52
CMD Line: "C:\Program Files\Notepad2\Notepad2.exe"
CMD Line:

However, if I uncomment the fmt.Println call in StringRemote or change the call to ReadProcessMemory in that function to use nil instead of &br then the function works. I can also make the code in StringRemote work if I make br a global variable instead of a local one.

PID: 16744
Base Addr: a42000
ProcID: 4168
Params: 2d92a40
CMD Line: 2d930a6
L HProc: fc
L PBuffer: 2d930a6
L BLength: 52
R HProc: fc
R PBuffer: 2d930a6
R BLength: 52
0
CMD Line: "C:\Program Files\Notepad2\Notepad2.exe"
CMD Line: "C:\Program Files\Notepad2\Notepad2.exe"

I tried attaching an API monitor to the program while it was running and it showed that when calling from StringRemote the value of br passed to ReadProcessMemory is random junk. But if I include the fmt.Printfln call then the value is correctly initialised to 0 and passed to ReadProcessMemory

It seems like either I am missing something about Windows interop or there is an issue with the way variables are being initialised/marshalled in Go. Interestingly, it does not matter whether the fmt.Println call is placed before or after the call to ReadProcessMemory in StringRemote to make it work.

I would very much appreciate any insight anyone has into this issue as I am at a loss to explain what is going on here.

Well, I don’t understand the syscalls in question. But in your first variant, inline in main, you pass the pointer &br to several calls and the value is presumably used somehow between them. In the second variant br is zero and never used anywhere when you do the final syscall. This is a difference.

You say that having the printf in the second variant fixes it. If that’s the case I would look into the fact that an uintptr towards &br isn’t seen by the garbage collector and the value may have been deallocated, or possibly optimized away entirely since it’s not visibly used in the code. You might want runtime.KeepAlive on it after the syscall, if it is in fact used for something.

Thank you very much for the reply! Previously I was using br inside of StringRemote to perform a check after the syscall like so:

if br != us.Length {
    return errPartialRead
}

Interestingly even with this check the issue I described above still persisted until adding the fmt.Println line.

I had not thought about the possibility of Go optimising the variable out entirely but that would certainly make sense. I added a call to runtime.KeepAlive (I was not familiar with this function previously) after the syscall and this has fixed the issue I was having.

Thanks again for your help! :slight_smile:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.