All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bloodshura.ignitium.ntv.process.impl.UnixProcess Maven / Gradle / Ivy

The newest version!
package com.github.bloodshura.ignitium.ntv.process.impl;

import com.github.bloodshura.ignitium.collection.list.XList;
import com.github.bloodshura.ignitium.collection.list.impl.XArrayList;
import com.github.bloodshura.ignitium.collection.view.XBasicView;
import com.github.bloodshura.ignitium.collection.view.XView;
import com.github.bloodshura.ignitium.io.File;
import com.github.bloodshura.ignitium.math.BaseConverter;
import com.github.bloodshura.ignitium.ntv.ErrorCodeNativeException;
import com.github.bloodshura.ignitium.ntv.NativeException;
import com.github.bloodshura.ignitium.ntv.lib.Unix;
import com.github.bloodshura.ignitium.ntv.lib.Unix.iovec;
import com.github.bloodshura.ignitium.ntv.process.Module;
import com.github.bloodshura.ignitium.ntv.process.NativeProcess;
import com.github.bloodshura.ignitium.ntv.util.Buffer;
import com.github.bloodshura.ignitium.ntv.util.RangeUtils;
import com.github.bloodshura.ignitium.sys.XSystem;
import com.github.bloodshura.ignitium.sys.terminal.TerminalProcess;
import com.github.bloodshura.ignitium.tokenizer.SimpleTokenizer;
import com.github.bloodshura.ignitium.worker.StringWorker;
import com.sun.jna.LastErrorException;
import com.sun.jna.Pointer;

import javax.annotation.Nonnull;
import java.io.IOException;

public class UnixProcess extends NativeProcess {
	private final iovec local;
	private final iovec remote;

	public UnixProcess(int processId) {
		super(processId);
		this.local = new iovec();
		this.remote = new iovec();

		// Disable JNA's auto-read and auto-write features.
		// Cause: https://github.com/java-native-access/jna/issues/778
		local.setAutoSynch(false);
		remote.setAutoSynch(false);
	}

	@Nonnull
	@Override
	public XView getModules() throws NativeException {
		File file = new File("/proc/" + getId() + "/maps");

		try {
			XList modules = new XArrayList<>();

			for (String line : file.readLines()) {
				SimpleTokenizer tokenizer = new SimpleTokenizer();
				XView tokens = tokenizer.tokenize(line);

				if (tokens.size() >= 6) {
					String region = tokens.get(0);
					int regionSeparator = region.indexOf('-');
					String path = StringWorker.join(' ', tokens.toArray(5));
					int pathSeparator = path.lastIndexOf('/');

					if (!path.isEmpty() && regionSeparator != -1) {
						String startStr = region.substring(0, regionSeparator);
						String endStr = region.substring(regionSeparator + 1);

						if (BaseConverter.isEncodable(startStr, BaseConverter.HEXADECIMAL) && BaseConverter.isEncodable(endStr, BaseConverter.HEXADECIMAL)) {
							long start = BaseConverter.encodeToLong(startStr, BaseConverter.HEXADECIMAL);
							long end = BaseConverter.encodeToInt(endStr, BaseConverter.HEXADECIMAL);
							String name = pathSeparator != -1 ? path.substring(pathSeparator + 1) : path;

							modules.add(new Module(this, name, start, (int) (end - start)));
						}
					}
				}
			}

			return new XBasicView<>(modules);
		} catch (IOException exception) {
			throw new NativeException("Could not read modules of process " + getId(), exception);
		}
	}

	@Override
	public void inject(@Nonnull File library) throws NativeException {
		if (!XSystem.getTerminal().exists("gdb")) {
			throw new NativeException("GDB (GNU Debugger) command not found");
		}

		if (getModules().any(module -> module.getName().equals(library.getFullName()))) {
			throw new NativeException("There's already a loaded library named \"" + library.getFullName() + '"');
		}

		XList gdbCommands = new XArrayList<>();

		gdbCommands.add("attach " + getId());
		gdbCommands.add("set $dlopen = (void*(*)(char*, int)) dlopen");
		gdbCommands.add("call $dlopen(\"" + library + "\", 1)");
		gdbCommands.add("detach");

		String gdbCommand = gdbCommands.map(command -> "--eval-command \"" + command + "\"").toString(' ');
		String shellCommand = "gdb --batch --quiet " + gdbCommand;

		try (TerminalProcess process = XSystem.getTerminal().runInShell(shellCommand)) {
			// TODO
		} catch (IOException exception) {
			throw new NativeException("Failed to run GDB injection command", exception);
		}
	}

	@Override
	public boolean isReadable(long address, int size) throws NativeException {
		if (!RangeUtils.validateRange(address, size)) {
			return false;
		}

		Pointer pointer = new Pointer(address);
		Buffer buffer = Buffer.common(size);

		populate(pointer, buffer, size);

		try {
			return Unix.process_vm_readv(getId(), local, 1, remote, 1, 0) == size;
		} catch (LastErrorException exception) {
			return false;
		}
	}

	@Override
	public boolean isWritable(long address, int size) throws NativeException {
		if (!RangeUtils.validateRange(address, size)) {
			return false;
		}

		throw new UnsupportedOperationException("UnixProcess::isWritable not implemented");
	}

	@Override
	public void read(long address, @Nonnull Buffer buffer, int size) throws NativeException {
		RangeUtils.checkRange(address, size, (int) buffer.size());

		Pointer pointer = new Pointer(address);

		populate(pointer, buffer, size);

		try {
			long read = Unix.process_vm_readv(getId(), local, 1, remote, 1, 0);

			if (read == 0x10000 /* -1 as ssize_t, according to Linux's man */) {
				throw new ErrorCodeNativeException("Could not read " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL));
			}

			if (read != size) {
				throw new NativeException("Expected to read " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL) + ", but " + read + " bytes were read instead");
			}
		} catch (LastErrorException exception) {
			throw new NativeException("Could not read " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL) + ": " + exception.getMessage());
		}
	}

	@Override
	public void write(long address, @Nonnull Buffer buffer) throws NativeException {
		RangeUtils.checkRange(address, (int) buffer.size());

		Pointer pointer = new Pointer(address);
		int size = (int) buffer.size();

		populate(pointer, buffer, size);

		try {
			long written = Unix.process_vm_writev(getId(), local, 1, remote, 1, 0);

			if (written == 0x10000 /* -1 as ssize_t, according to Linux's man */) {
				throw new ErrorCodeNativeException("Could not write " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL));
			}

			if (written != size) {
				throw new NativeException("Expected to write " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL) + ", but " + written + " bytes were written instead");
			}
		} catch (LastErrorException exception) {
			throw new NativeException(exception.getMessage());
		}
	}

	private void populate(@Nonnull Pointer pointer, @Nonnull Buffer buffer, int size) {
		// Calling 'writeField' instead of simply assigning the values to their respective fields,
		// because we disabled auto-synching of the iovec instances on constructor.
		// 'writeField' not only writes to the fields, but also to the underlying native memory.
		local.writeField("iov_base", buffer);
		local.writeField("iov_len", size);
		remote.writeField("iov_base", pointer);
		remote.writeField("iov_len", size);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy