17.1 Hide execution of CMD commands

This chapter covers the use of Socket API and CMD command line tools to implement local CMD command execution and pipeless forward CMD and pipeless reverse CMD three functions. Execute the local CMD implementation and use the CreateProcess function to create a new CMD process and redirect the standard input, output and error output to the current process Standard input, output and error output. Pipeless forward CMD and pipeless reverse CMD use the WSASocket function to create a TCP socket and add The standard input, output and error output of the CMD process are redirected to the socket handle to implement remote command execution through a network connection.

First, implement a CMD command line running function by using CreatePipe to create an anonymous pipe, and use the CreateProcess function to create a new CMD process, then redirects standard input, output, and error output to the current process’s standard input, output, and error output. In this way, you can execute the CMD command through the input and output of the current process and obtain the command output results.

CreatePipe function, used to create an anonymous pipe. An anonymous pipe is a mechanism for inter-process communication that allows one process to transfer output data to another process. The prototype of the CreatePipe function is as follows:

BOOL CreatePipe(
  PHANDLE hReadPipe,
  PHANDLE hWritePipe,
  LPSECURITY_ATTRIBUTES lpPipeAttributes,
  DWORD nSize
);

The parameters hReadPipe and hWritePipe are pointers to the HANDLE type, used to receive the handles of the read and write ends of the created pipe. The parameter lpPipeAttributes is a pointer to the SECURITY_ATTRIBUTES structure, used to set the security of the pipe. The parameter nSize is a value of type DWORD, used to specify the buffer size of the pipe. It can usually be set to 0 to use the system default value.

After creating an anonymous pipe, you can use the ReadFile function to read data from the read end of the pipe and the WriteFile function to write data to the write end of the pipe. After using the pipe, you should use the CloseHandle function to close the handle of the pipe to release resources.

The CreateProcess function can create a new process, allocate memory space for the process, initialize environment variables, create the main thread, etc. Among them, the parameter lpApplicationName is used to specify the name of the executable file to be executed, and the parameter lpCommandLine is used to specify the command line parameters. If the lpApplicationName parameter is NULL, the system will automatically use the command line specified by the lpCommandLine parameter to create a process.

The function prototype is as follows:

BOOL CreateProcess(
  LPCSTR lpApplicationName,
  LPSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCSTR lpCurrentDirectory,
  LPSTARTUPINFO lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

This function creates a process that includes a console window. If you need to use the CreateProcess() function to create a process without a console window, you need to specify the CREATE_NO_WINDOW flag in the dwCreationFlags parameter.

When creating a process, you can set some properties of the process through the STARTUPINFO structure, such as the redirection of standard input, standard output and standard error output, the display mode of the startup window, etc. At the same time, the CreateProcess() function will return a PROCESS_INFORMATION structure, which contains information such as the handle and ID of the new process.

As shown in the RunCommand function below, this function passes in a string type command parameter and returns a string execution result. Inside the function, use the CreatePipe() function to create Create an anonymous pipe and use the CreateProcess() function to start a new CMD process and redirect its standard output and error output to the writing end of the pipe. Then use the ReadFile() function to read the output data from the reading end of the pipe and store the read data in a buffer. Finally, it concatenates the contents of the buffer into a complete output result and returns it to the caller.

// Execute CMD command in hidden mode
BOOL RunCommand(char* cmdStr, char* message)
{<!-- -->
  DWORD readByte = 0;
  
  //Execute command line
  char command[1024] = {<!-- --> 0 };

  // buffer
  char buf[8192] = {<!-- --> 0 };

  HANDLE hRead, hWrite;
  //Start configuration information
  STARTUPINFOsi;
  //Process information
  PROCESS_INFORMATION pi;
  // Pipe security properties
  SECURITY_ATTRIBUTES sa;

  // Splice CMD commands
  sprintf(command, "cmd.exe /c %s", cmdStr);
  // printf("-- CMD command: [%s]n", command);

  //Configure pipeline security properties
  sa.nLength = sizeof(sa);
  // Pipe handles can be inherited
  sa.bInheritHandle = TRUE;
  sa.lpSecurityDescriptor = NULL;

  //Create an anonymous pipe, the pipe handle can be inherited
  if (!CreatePipe( & amp;hRead, & amp;hWrite, & amp;sa, 1024))
  {<!-- -->
    // printf("Pipe creation failed %xn", (unsigned int)GetLastError());
    return FALSE;
  }

  //Configure cmd startup information
  ZeroMemory( & amp;si, sizeof(si));
  si.cb = sizeof(si); // Get compatible size
  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; // Standard output, etc. use additional
  si.wShowWindow = SW_HIDE; //Hide window startup
  si.hStdOutput = si.hStdError = hWrite; // The output stream and error stream point to the write end of the pipe

  //Create a child process and run the command. The child process is inheritable
  if (!CreateProcess(
    NULL, // Do not pass the program path, use the command line
    command, // command line command
    NULL, // Do not inherit process handle (default)
    NULL, // Do not inherit thread handle (default)
    TRUE, // inherit handle
    0, // No creation flag (default)
    NULL, // use default environment variables
    NULL, // use the directory of the parent process
     & amp;si, // STARTUPINFO structure stores startup information
     & amp;pi)) // PROCESS_INFORMATION saves process-related information after startup
  {<!-- -->
    // printf("Failed to create process %x \
", (unsigned int)GetLastError());
    CloseHandle(hRead);
    CloseHandle(hWrite);
    return FALSE;
  }
  CloseHandle(hWrite);

  /*
  The write end handle of the pipe has been inherited by cmd's output stream and error stream, that is, when cmd outputs, data will be written to the pipe.
  We can get the output of cmd by reading the read end of the pipe
  */
  while (ReadFile(hRead, buf, 8192, &readByte, NULL))
  {<!-- -->
    strcat(message, buf);
    ZeroMemory(buf, 8192);
  }

  //printf("-- [CMD] Message: [%s] Length:%d n", message, strlen(message) + 1);
  CloseHandle(hRead);
  return TRUE;
}

The above function is very easy to call. Let’s take the execution of the ipconfig function as an example. The calling case is RunCommand((char*)"ipconfig", szBuffer), and the function executes the command ipconfig parameters, and store the return value in the szBuffer variable. The output rendering is as follows;