cmd.exe: cd
Introduction #
If you really looked into writing a shell on your own, or ran:
which cd # shell built in command
Now, quick googling gives me a stackoverflow answer:
So, if cd was a program it would work like this:
1. cd foo
2. the cd process starts
3. the cd process changes the directory for the cd process
4. the cd process exits
5. your shell still has the same state, including current working directory,
that it did before you started.
But, in windows, I have found a way to change the directory using a program in cmd.exe
.
While the solution doesn’t specifically target cmd.exe
, the method doesn’t work if the shell maintains a local variable of the current directory.
In that case, the shell need not operate in the same folder we are working in. I haven’t got any time to read powershell source code, so this is only an idea.
Github repo: NewChangeDirectory
Approach #
Let’s learn about a couple of functions which from the Windows API are used to do this.
If we wanted to execute custom functions as other process, we can use CreateRemoteThread from the API using the parent process handle:
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
I use this function to tweak things inside other processes (frequently).
Also, the following function is used to change the current directory of the process:
BOOL SetCurrentDirectory(
LPCTSTR lpPathName
);
My goal is to make a program which changes the current directory of its parent process.
Let’s try to get the parent process’ PID. We can use the Tool Help Library to find the parent process using the following code:
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // Get handle to the snapshot
if (hSnap == INVALID_HANDLE_VALUE) {
// quit
}
PROCESSENTRY32 pe;
memset(&pe, 0, sizeof(PROCESSENTRY32));
pe.dwSize = sizeof(PROCESSENTRY32);
DWORD parentPid = -1;
DWORD currentPid = GetCurrentProcessId();
while (Process32Next(hSnap, &pe)) {
if (pe.th32ProcessID == currentPid) { // Found it!
parentPid = pe.th32ParentProcessID;
break;
}
}
CloseHandle(hSnap); // Close the handle
Next, let’s try to get a handle to the process. We can use OpenProcess
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
Setting the dwDesiredAccess
property depends on the Process Access Rights we need. CreateRemoteThread
needs a lot of access rights (from the docs), so, I will use PROCESS_ALL_ACCESS
.
HANDLE hParent = OpenProcess(PROCESS_ALL_ACCESS, false, parentPid);
if (hParent == INVALID_HANDLE_VALUE) {
// quit
}
Let’s also get the target path of the folder (from argv) and validate it:
std::string targetPath;
{
fs::path _targetPath;
std::stringstream ss;
for (int i = 1; i < argc; ++i) {
ss << argv[i] << " ";
}
_targetPath = ss.str();
if (!fs::is_directory(_targetPath)) {
// quit
}
targetPath = fs::absolute(_targetPath).string();
}
We should now allocate some memory for the path inside the process using VirtualAllocEx.
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
void *remoteAddr = VirtualAllocEx(
hParent,
nullptr,
targetPath.size(),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
I prefer to commit the allocated memory immediately, but you can do either.
The next step, is to copy the path of this folder into this memory block. We can use WriteProcessMemory to do this.
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
WriteProcessMemory(
hParent,
remoteAddr,
targetPath.c_str(),
targetPath.size(),
&written
);
And now, we can finally call CreateRemoteThread
to SetCurrentDirectory
with the address of the allocated memory block as the parameter.
HANDLE hThread = CreateRemoteThread(
hParent,
NULL,
0,
(LPTHREAD_START_ROUTINE)SetCurrentDirectoryA,
remoteAddr,
0,
&threadId
);
Now, I would like to take some time to explain this function.
This function takes the following parameters:
- A
HANDLE
of the target process, provided that theHANDLE
has sufficient access rights - A
SECURITY_ATTRIBUTES
structure which determines thesecurity descriptor
- The stack size for the thread (0 means default)
- The start address of the thread
- This address is specific to the target process
- So, this means, the function should exist inside the target process
- The address of the parameter inside the target process memory to be passed to the thread function
- Some creation flags
- The destination location of the Thread ID of the new thread
Now, we can use the thread handle to wait until it finishes execution using WaitForSingleObject
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds // INFINITE
);
So, we wait for the thread to finish:
WaitForSingleObject(hThread, INFINITE);
We also can get the exit code of the thread using GetExitCodeThread.
DWORD exit = INT_MAX;
GetExitCodeThread(hThread, &exit);
Now, we can free the memory we allocated before:
VirtualFreeEx(hThread, remoteAddr, targetPath.size(), MEM_RELEASE);
Conclusion #
When I wrote coded the program and ran it in cmd.exe
,
the working directory of the shell gets changed.
Thanks for reading!
D:\Temp>Changedir.exe %tmp%
C:\Users\spider\AppData\Local\Temp>