Getting the current working directory of some other running process (in XP!)

Started by snowsnowsnow, October 15, 2018, 12:30:08 PM

Previous topic - Next topic

snowsnowsnow

N.B.  Solution must work under Windows XP.

I'm looking for a way, in a Winbatch program, to determine the current working directory of another process.  To be clear, I am *NOT* talking about the directory which holds the running executable.  As noted, if you Google this question, you'll get hits assuming you mean the later.  Why this is so, confuses me.

Now, my first thought on this was that it should be possible to get it from Win32_Process.  I've used Win32_Process in WinBatch already, so I'm familiar with the idea.  However, there doesn't seem to be anything there for the current working directory.

Googling also reveals that one way to do it is via Code Injection - that is, injecting code into the running process to do GetWorkingDirectory() and somehow return the result to the calling process.  I have also used this technique previously in WinBatch, but as I recall, the limitation is that it only works for functions that don't return anything.  I think there isn't a way in WB to inject a function and then read back the result.

Finally, although a native (i.e., "in code") solution would be preferred, I am not ruling out using an external program.  As far as I can tell, there are two possibles here, neither of which are installed by default in Windows:

1) Handle - from SysInternals.com
2) tlist - from MS - part of the "WDK"

I have not been able to locate and download "tlist", so I cannot comment on that, but I did d/l the Handle program.  Unfortunately, this program does not work under XP.  It does seem to work and give the right answer under Windows 7.  I need a solution that works under XP.  I think that if I could locate an older version of the program, it would work under XP.  Does anyone know how to locate such a version of Handle.exe?



td

It does seem to be the case that most proposed solutions seem to just return the executable's launch path.  Code injection may be your best bet. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

But is there any way to do the code injection and get back a result?

I just checked one of my programs that uses this trick and the function injected is AllocConsole() - which doesn't return anything.

Maybe someone could write something in VB.NET or C# to do the code injection and return the result.  Then that program could be invoked from WinBatch.  But that's beyond my abilities; I've never written anything non-trivial in either of those languages.

td

There is a way but it requires using some form of shared memory.  For example, the Control Manager Extender uses memory mapped files to return a chunk of data from code injection but that is only part of what is needed. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

A Google search turns up a couple examples of using code injection using the creation of a thread in a remote process, allocating memory in the remote process and using the memory read and write process memory Win32 functions, and executing some machine instructions.  The net result being the return of the current working directory of the remote process.   The one example I noticed is written in C but it *might* be convertible to WIL DllCalls with a lot of work.  Something to consider.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

I hope you don't think me a lazy slug for asking, but could you give me a URL or two?

Save me a little duplicative Googling...

td

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

Out of curiosity, I thought I would see if the code in the above link actually works.  This is a very ugly as in no error handling and many unguarded assumptions, proof of concept sort of script.  For other forum members foolish enough to view this mess keep in mind that it can only be used on processes with the same bitness as the version of WinBatch running the script.

It did work on my system but your mileage will likely vary.

Code (winbatch) Select
;; Create the machine instruction buffer.
strCmdDir = '8B442404EB686400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E8000000005B83EB6989D983E9045351FFD0C3'
nSize =  StrLen(strCmdDir)
hCmdDir = BinaryAlloc(nSize +1)
BinaryPokeHex(hCmdDir, 0, strCmdDir)

;; Get the raw memory address.
pCmdDir = IntControl(42, hCmdDir,0,0,0)

;; Get a current running process handle.
AddExtender("WWPRC44I.DLL",0,"WWPRC64I.DLL")
lIds = tListProc()
;; devenv is a 32-bit process that happens to be running on my system
nLoc = ItemLocateWild('devenv*',lIds,@Tab, 1)
strPair = ItemExtract(nLoc, lIds, @tab)
ProcId = ItemExtract(2, strPair, '|')
hProcess=tOpenProc(ProcID,1)

;; Get the address of the  GetCurrentDirectoryA function
hSudoKernel32 = DllLoad('Kernel32.dll')
nRealKernel32 = DllCall(hSudoKernel32,long:'GetModuleHandleA', lpstr:'Kernel32.dll')
pFunc = DllCall(hSudoKernel32 , long:'GetProcAddress', long:nRealKernel32, lpstr:'GetCurrentDirectoryA')

;; Allocate some memory in the remote process for the machine code.
MEM_COMMIT = 4096
PAGE_EXECUTE_READWRITE = 64
pRemoteMem = DllCall(hSudoKernel32, long:'VirtualAllocEx',long:hProcess, lpnull, long:nSize, long:MEM_COMMIT, long:PAGE_EXECUTE_READWRITE)

; Write to remote memory.
DllCall(hSudoKernel32, long:'WriteProcessMemory',long:hProcess,long:pRemoteMem,long:pCmdDir,long:nSize,lpnull)

; Flush the execution cache to prevent unplesent events. (Probably not necessary but better safe...)
DllCall(hSudoKernel32, long:'FlushInstructionCache', long:hProcess, lpnull, long:0)

; Execute the injected code.
hThread = DllCall(hSudoKernel32, long:'CreateRemoteThread', long:hProcess, lpnull, long:0, long:pRemoteMem, long:pFunc ,long:0,lpnull)
if !hThread then dwError = DllLastError() ; So can view the error in WBS.
else dwError = 0

TimeDelay(1) ;; Used instead of a loop for feasibility testing only.

; Read output.
hSize = BinaryAlloc(4)
bResult = DllCall(hSudoKernel32, long:'ReadProcessMemory',long:hProcess,long:pRemoteMem,long:pCmdDir,long:nSize,lpbinary:hSize)

;;; Check if it more or less kinda worked.
BinaryEODSet(hCmdDir, nSize)
strCWD = BinaryPeekStr(hCmdDir, 10, 200)
Message('Remote Process CWD', strCWD)

MEM_RELEASE = 32768
DllCall(hSudoKernel32, long:'VirtualFreeEx',long:hProcess, long:pRemoteMem, long:nSize, long:MEM_RELEASE)

DllFree(hSudoKernel32)
tCloseProc(hProcess)
BinaryFree(hCmdDir)
exit
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

Should add that in its current state the script will only work with 32-bit processes with 32-bit WinBatch.  64-bit processes with 64-bit WinBatch would require different machine instructions and possibly even 64-bit wide address handling. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

Thanks for the script.  I have not gotten around to testing it yet, but I am looking forward to doing so.

As indicated, I'm fine with it being 32 bit only.  I'm interested in doing this on XP.

(Nitpickers will now post that it is possible to run XP 64 bit - but that's irrelevant)