sp1d3r

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:

  1. A HANDLE of the target process, provided that the HANDLE has sufficient access rights
  2. A SECURITY_ATTRIBUTES structure which determines the security descriptor
  3. The stack size for the thread (0 means default)
  4. 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
  5. The address of the parameter inside the target process memory to be passed to the thread function
  6. Some creation flags
  7. 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>
comments powered by Disqus