Blob Blame History Raw
/********************************************************/
/*  ntapi: Native API core library                      */
/*  Copyright (C) 2013--2021  SysDeer Technologies, LLC */
/*  Released under GPLv2 and GPLv3; see COPYING.NTAPI.  */
/********************************************************/

#include <psxtypes/psxtypes.h>
#include <pemagine/pemagine.h>
#include <ntapi/nt_status.h>
#include <ntapi/nt_object.h>
#include <ntapi/nt_thread.h>
#include <ntapi/nt_process.h>
#include <ntapi/nt_string.h>
#include <ntapi/ntapi.h>
#include "ntapi_impl.h"

#define __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE    (0x50000)
#define __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_BUFFER_SIZE   (0x30000)
#define __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE   (0x10000)
#define __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE   (0x10000)

#define NT_PROCESS_SPAWN_FLAG_DEBUG_MASK                \
		(NT_PROCESS_SPAWN_FLAG_DEBUG_EXECUTION   \
		 | NT_PROCESS_SPAWN_FLAG_DEBUG_SUSPENDED)

typedef int32_t win32_create_process_utf16(
	__in_opt	wchar16_t *			appname,
	__in_out_opt	wchar16_t *			cmdline,
	__in_opt	nt_sa *				process_sa_attr,
	__in_opt	nt_sa *				thread_sa_attr,
	__in		int32_t				inherit_handles,
	__in		uint32_t			creation_flags,
	__in		wchar16_t *			environment,
	__in_opt	wchar16_t *			cwd,
	__in		nt_process_startup_info *	startup_info,
	__out		nt_process_info *		process_info);


static int32_t __stdcall __tt_spawn_return(
	nt_runtime_data_block *	rtblock,
	void *			himgfile,
	void *			hprocess,
	void *			hthread,
	int32_t			status)
{
	if (hprocess) {
		__ntapi->zw_terminate_process(
			hprocess,status);

		__ntapi->zw_close(hprocess);
		__ntapi->zw_close(hthread);
	}

	if (himgfile)
		__ntapi->zw_close(himgfile);

	__ntapi->zw_free_virtual_memory(
		NT_CURRENT_PROCESS_HANDLE,
		&rtblock->addr,
		&rtblock->size,
		NT_MEM_RELEASE);

	return status;
}

int32_t __stdcall __ntapi_tt_spawn_foreign_process(nt_spawn_process_params * sparams)
{
	int32_t				status;
	nt_process_info			processinfo;
	nt_create_process_params	cparams;
	nt_runtime_data_block		rtblock;
	nt_unicode_string *		imgname;
	nt_peb * 			peb;
	char *				patharg;
	void *				hkernel32;
	void *				hat;
	void *				hfile;
	void *				himgfile;
	uint32_t			written;
	int32_t				envc;
	wchar16_t *			imgbuf;
	wchar16_t *			wenv;
	wchar16_t **			wenvp;
	char **				parg;
	char *				mark;
	char *				ch;
	char *				ch_arg;
	char *				ch_cap;
	int				fquote;
	uint32_t			finherit;
	uint32_t			fsuspended;
	uint32_t			fdebug;
	wchar16_t *			cmdline;
	nt_strconv_mbtonative		uparams;
	nt_unicode_string		nt_image;
	nt_unicode_string		nt_cmd_line;
	win32_create_process_utf16 *	create_process_fn;
	char				create_process_fn_name[]
					= "CreateProcessW";

	/* validation */
	if (!sparams->argv)
		return NT_STATUS_INVALID_PARAMETER;

	if (!sparams->himage && !sparams->patharg)
		return NT_STATUS_OBJECT_PATH_INVALID;

	if (!(peb = (nt_peb *)pe_get_peb_address()))
		return NT_STATUS_INTERNAL_ERROR;

	if (!peb->process_params)
		return NT_STATUS_INTERNAL_ERROR;

	if (sparams->rtctx || sparams->hsession || sparams->hready)
		return NT_STATUS_INVALID_PARAMETER;

	/* hkernel32 */
	if (!(hkernel32 = pe_get_kernel32_module_handle()))
		return NT_STATUS_DLL_NOT_FOUND;

	if (!(create_process_fn = (win32_create_process_utf16 *)
			(pe_get_procedure_address(
				hkernel32,create_process_fn_name))))
		return NT_STATUS_PROCEDURE_NOT_FOUND;

	/* hat */
	hat = (sparams->hroot && (sparams->argv[0][0] == '/'))
		? sparams->hroot
		: sparams->hcwd
			? sparams->hcwd
			: peb->process_params->cwd_handle;

	/* patharg */
	patharg = sparams->patharg
		? (sparams->patharg[0] == '/')
			? (sparams->patharg[1] == '?')
				? &sparams->patharg[0]
				: &sparams->patharg[1]
			: &sparams->patharg[0]
		: 0;

	/* rtblock, rdata */
	rtblock.addr		= 0;
	rtblock.size		= __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE;
	rtblock.remote_addr	= 0;
	rtblock.remote_size	= 0;
	rtblock.flags		= 0;

	if ((status = __ntapi->zw_allocate_virtual_memory(
			NT_CURRENT_PROCESS_HANDLE,
			&rtblock.addr,0,
			&rtblock.size,
			NT_MEM_COMMIT,
			NT_PAGE_READWRITE)))
		return status;

	__ntapi->tt_aligned_block_memset(
		rtblock.addr,0,rtblock.size);

	/* environment */
	for (envc=0,parg=sparams->envp; *parg; parg++)
		envc++;

	wenvp   = rtblock.addr;
	wenvp  += __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_BUFFER_SIZE / sizeof(*wenvp);
	wenv    = (wchar16_t *)&wenvp[++envc];

	if ((status = __ntapi->tt_array_convert_utf8_to_utf16(
			sparams->envp,
			wenvp,0,wenv,
			__SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE,
			&(size_t){0})))
		return __tt_spawn_return(
			&rtblock,0,
			0,0,status);

	/* imgbuf */
	imgbuf  = rtblock.addr;
	imgbuf += __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_BUFFER_SIZE / sizeof(wchar16_t);
	imgbuf += __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE / sizeof(wchar16_t);

	/* hfile */
	if (sparams->himage) {
		hfile    = sparams->himage;
		himgfile = 0;

	} else if ((status = __ntapi_tt_open_file_utf8(
			&hfile,hat,patharg,1,
			imgbuf,__SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE))) {
		return __tt_spawn_return(
			&rtblock,0,
			0,0,status);

		himgfile = hfile;
	}

	/* imgname */
	if ((status = __ntapi->zw_query_object(
			hfile,
			NT_OBJECT_NAME_INFORMATION,
			imgbuf,__SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE,
			&written)))
		return __tt_spawn_return(
			&rtblock,himgfile,
			0,0,status);

	imgname = (nt_unicode_string *)imgbuf;

	/* argv --> cmdline (utf8) */
	ch_arg = rtblock.addr;
	ch_cap = ch_arg + __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE;

	for (parg=sparams->argv; *parg; parg++) {
		for (ch=*parg, fquote=0; *ch && !fquote; ch++)
			fquote = ((*ch == ' ')
					|| (*ch == '\t')
					|| (*ch == '"'));

		if (fquote)
			*ch_arg++ = '"';

		for (ch=*parg; *ch; ) {
			if (ch[0] == '\\') {
				for (mark=&ch[1]; *mark=='\\'; )
					mark++;

				if ((ch_arg + 2*(mark-ch)) >= ch_cap)
					return __tt_spawn_return(
						&rtblock,himgfile,0,0,
						NT_STATUS_NAME_TOO_LONG);

				if (!mark[0] && fquote) {
					for (; *ch=='\\'; ch++) {
						*ch_arg++ = '\\';
						*ch_arg++ = '\\';
					}
				} else if (mark[0] == '"') {
					for (; *ch=='\\'; ch++) {
						*ch_arg++ = '\\';
						*ch_arg++ = '\\';
					}
				} else {
					*ch_arg++ = *ch++;
				}

			} else if (ch[0] == '"') {
				*ch_arg++ = '\\';
				*ch_arg++ = *ch++;

			} else {
				*ch_arg++ = *ch++;
			}
		}

		if (fquote)
			*ch_arg++ = '"';

		*ch_arg++ = ' ';

		if (ch_arg >= ch_cap)
			return __tt_spawn_return(
				&rtblock,himgfile,0,0,
				NT_STATUS_NAME_TOO_LONG);

	}

	ch_arg[-1] = 0;

	/* cmdline (utf8) --> cmdline (utf16) */
	cmdline  = rtblock.addr;
	cmdline += __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE / sizeof(wchar16_t);

	uparams.src                = rtblock.addr;
	uparams.src_size_in_bytes  = 0;
	uparams.dst                = cmdline;
	uparams.dst_size_in_bytes  = __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE - sizeof(wchar16_t);
	uparams.code_points        = 0;
	uparams.bytes_written      = 0;

	if ((status = __ntapi->uc_convert_unicode_stream_utf8_to_utf16(&uparams)))
		return __tt_spawn_return(
			&rtblock,himgfile,
			0,0,status);

	else if (uparams.leftover_count)
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			NT_STATUS_ILLEGAL_CHARACTER);

	/* nt_cmd_line */
	nt_cmd_line.strlen = uparams.bytes_written;
	nt_cmd_line.maxlen = uparams.bytes_written + sizeof(wchar16_t);
	nt_cmd_line.buffer = cmdline;

	/* nt_image */
	nt_image.buffer  = rtblock.addr;
	nt_image.buffer += __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE / sizeof(wchar16_t);
	nt_image.buffer += __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE / sizeof(wchar16_t);

	uparams.src                = (unsigned char *)sparams->argv[0];
	uparams.src_size_in_bytes  = 0;
	uparams.dst                = nt_image.buffer;
	uparams.dst_size_in_bytes  = __SPAWN_FOREIGN_PROCESS_RUNTIME_BLOCK_VECTOR_SIZE - sizeof(wchar16_t);
	uparams.code_points        = 0;
	uparams.bytes_written      = 0;

	if ((status = __ntapi->uc_convert_unicode_stream_utf8_to_utf16(&uparams)))
		return __tt_spawn_return(
			&rtblock,himgfile,
			0,0,status);

	else if (uparams.leftover_count)
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			NT_STATUS_ILLEGAL_CHARACTER);

	nt_image.strlen = uparams.bytes_written;
	nt_image.maxlen = uparams.bytes_written + sizeof(wchar16_t);

	nt_image.buffer[uparams.bytes_written / sizeof(wchar16_t)] = 0;

	/* cparams */
	__ntapi->tt_aligned_block_memset(
		&cparams,0,sizeof(cparams));

	cparams.image_name		= imgname->buffer;
	cparams.creation_flags_thread	= NT_PROCESS_CREATE_FLAGS_CREATE_THREAD_SUSPENDED;

	/* process_params */
	if ((status = __ntapi->rtl_create_process_parameters(
			&cparams.process_params,
			&nt_image,
			(nt_unicode_string *)0,
			(nt_unicode_string *)0,
			&nt_cmd_line,
			wenv,
			(nt_unicode_string *)0,
			(nt_unicode_string *)0,
			(nt_unicode_string *)0,
			(nt_unicode_string *)0)))
		return __tt_spawn_return(
			&rtblock,himgfile,
			0,0,status);

	__ntapi->rtl_normalize_process_params(cparams.process_params);

	if (sparams->startupinfo) {
		cparams.process_params->hstdin  = sparams->startupinfo->hstdin;
		cparams.process_params->hstdout = sparams->startupinfo->hstdout;
		cparams.process_params->hstderr = sparams->startupinfo->hstderr;
	}

	/* inherit handles? */
	if (cparams.process_params->hstdin
			|| cparams.process_params->hstdout
			|| cparams.process_params->hstderr)
		finherit = 1;

	else if (sparams->processflags & NT_PROCESS_CREATE_FLAGS_INHERIT_HANDLES)
		finherit = 1;

	else
		finherit = 0;

	/* process flags */
	fsuspended = 0;
	fdebug     = 0;

	if (sparams->processflags & NT_PROCESS_CREATE_FLAGS_CREATE_THREAD_SUSPENDED)
		fsuspended = NT_CREATE_SUSPENDED;

	else if (sparams->threadflags & NT_CREATE_SUSPENDED)
		fsuspended = NT_CREATE_SUSPENDED;

	else if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DEBUG_SUSPENDED)
		fsuspended = NT_CREATE_SUSPENDED;

	else if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DEBUG_EXECUTION)
		fdebug = NT_CREATE_SUSPENDED;

	/* hoppla: try either via kernel32 (sparams->startupinfo), or natively */
	if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DELEGATE_TO_SYSTEM_LIBRARY) {
		processinfo.hprocess   = 0;
		processinfo.hthread    = 0;
		processinfo.process_id = 0;
		processinfo.thread_id  = 0;

		if (!(create_process_fn(
				nt_image.buffer,
				nt_cmd_line.buffer,
				0,
				0,
				finherit,
				NT_PROCESS_INTEROP_FLAG_UNICODE_ENVIRONMENT
					| sparams->interopflags | fsuspended | fdebug,
				wenv,
				sparams->cwd,
				sparams->startupinfo,
				&processinfo)))
			return __tt_spawn_return(
				&rtblock,himgfile,
				0,0,status);

		if ((status = __ntapi->zw_query_information_process(
				processinfo.hprocess,
				NT_PROCESS_BASIC_INFORMATION,
				&cparams.pbi,sizeof(cparams.pbi),
				0)))
			return __tt_spawn_return(
				&rtblock,himgfile,
				processinfo.hprocess,
				processinfo.hthread,
				status);

		cparams.hprocess = processinfo.hprocess;
		cparams.hthread  = processinfo.hthread;

		cparams.cid.process_id = processinfo.process_id;
		cparams.cid.thread_id  = processinfo.thread_id;
	} else {
		cparams.creation_flags_thread = NT_PROCESS_CREATE_FLAGS_CREATE_THREAD_SUSPENDED;

		if (finherit)
			cparams.creation_flags_process |= NT_PROCESS_CREATE_FLAGS_INHERIT_HANDLES;

		if ((status = __ntapi->tt_create_native_process(&cparams)))
			return __tt_spawn_return(
				&rtblock,himgfile,
				0,0,status);
	}

	/* debug */
	if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DEBUG_MASK)
		if ((status = __ntapi->tt_debug_create_attach_object(
				&sparams->hdbgobj,
				cparams.hprocess,
				NT_DEBUG_KILL_ON_EXIT)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);

	/* output */
	sparams->hprocess = cparams.hprocess;
	sparams->hthread  = cparams.hthread;

	sparams->cid.process_id = cparams.pbi.unique_process_id;
	sparams->cid.thread_id  = cparams.cid.thread_id;

	__ntapi->tt_generic_memcpy(
		&sparams->pbi,
		&cparams.pbi,
		sizeof(nt_pbi));

	/* create suspended? */
	if (fsuspended)
		return __tt_spawn_return(
			&rtblock,himgfile,
			0,0,NT_STATUS_SUCCESS);

	/* tada */
	if ((status = __ntapi->zw_resume_thread(cparams.hthread,0)))
		return __tt_spawn_return(
			&rtblock,himgfile,
			cparams.hprocess,
			cparams.hthread,
			status);

	/* all done */
	return __tt_spawn_return(
		&rtblock,himgfile,
		0,0,NT_STATUS_SUCCESS);
}