├── settings.gradle ├── .travis.yml ├── src └── main │ └── kotlin │ └── org │ └── jire │ └── kotmem │ ├── win32 │ ├── User32.kt │ ├── LPMODULEINFO.kt │ ├── Win32Module.kt │ ├── Psapi.kt │ ├── Kernel32.kt │ ├── Win32Process.kt │ └── Win32.kt │ ├── linux │ ├── LinuxModule.kt │ ├── iovec.kt │ ├── uio.kt │ └── LinuxProcess.kt │ ├── Keys.kt │ ├── Caching.kt │ ├── Module.kt │ ├── mac │ ├── mac.kt │ └── MacProcess.kt │ ├── NativeBuffer.kt │ ├── DataType.kt │ ├── Processes.kt │ └── Process.kt ├── README.md └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'kotmem' -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | notifications: 5 | email: false -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/User32.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.win32 2 | 3 | import com.sun.jna.Native 4 | 5 | object User32 { 6 | 7 | @JvmStatic 8 | external fun GetKeyState(vKey: Int): Short 9 | 10 | init { 11 | Native.register("user32") 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/linux/LinuxModule.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.linux 2 | 3 | import com.sun.jna.Pointer 4 | import org.jire.kotmem.Module 5 | 6 | class LinuxModule(process: LinuxProcess, pointer: Pointer, override val name: String, 7 | override val size: Int) : Module(process, pointer) -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/linux/iovec.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.linux 2 | 3 | import com.sun.jna.Pointer 4 | import com.sun.jna.Structure 5 | 6 | class iovec(@JvmField var iov_base: Pointer? = null, @JvmField var iov_len: Int? = null) : Structure() { 7 | 8 | override fun getFieldOrder() = arrayListOf("iov_base", "iov_len") 9 | 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/LPMODULEINFO.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.win32 2 | 3 | import com.sun.jna.Structure 4 | import com.sun.jna.platform.win32.WinNT.HANDLE 5 | 6 | class LPMODULEINFO(@JvmField var lpBaseOfDll: HANDLE? = null, @JvmField var SizeOfImage: Int? = null, 7 | @JvmField var EntryPoint: HANDLE? = null) : Structure() { 8 | 9 | override fun getFieldOrder() = listOf("lpBaseOfDll", "SizeOfImage", "EntryPoint") 10 | 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/Keys.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem 2 | 3 | import com.sun.jna.Platform 4 | import org.jire.kotmem.win32.User32 5 | 6 | object Keys { 7 | 8 | @JvmStatic @JvmName("state") 9 | operator fun invoke(keyCode: Int) = when { 10 | Platform.isWindows() -> User32.GetKeyState(keyCode).toInt() 11 | else -> throw UnsupportedOperationException("Unsupported platform") 12 | } 13 | 14 | @JvmStatic @JvmName("isPressed") 15 | operator fun get(vKey: Int) = Keys(vKey) < 0 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/linux/uio.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.linux 2 | 3 | import com.sun.jna.Native 4 | import com.sun.jna.NativeLibrary 5 | 6 | object uio { 7 | 8 | @JvmStatic 9 | external fun process_vm_readv(pid: Int, local: iovec, liovcnt: Long, remote: iovec, 10 | riovcnt: Long, flags: Long): Long 11 | 12 | @JvmStatic 13 | external fun process_vm_writev(pid: Int, local: iovec, liovcnt: Long, 14 | remote: iovec, riovcnt: Long, flags: Long): Long 15 | 16 | init { 17 | Native.register(NativeLibrary.getInstance("c")) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/Win32Module.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.win32 2 | 3 | import com.sun.jna.Native 4 | import com.sun.jna.platform.win32.WinDef 5 | import org.jire.kotmem.Module 6 | 7 | class Win32Module(process: Win32Process, val hModule: WinDef.HMODULE, 8 | val lpModuleInfo: LPMODULEINFO) : Module(process, hModule.pointer) { 9 | 10 | override val name by lazy { 11 | val lpBaseName = ByteArray(256) 12 | Psapi.GetModuleBaseNameA(process.handle.pointer, hModule, lpBaseName, lpBaseName.size) 13 | Native.toString(lpBaseName) 14 | } 15 | 16 | override val size = lpModuleInfo.SizeOfImage!! 17 | 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/Caching.kt: -------------------------------------------------------------------------------- 1 | @file:JvmMultifileClass 2 | @file:JvmName("Caching") 3 | 4 | package org.jire.kotmem 5 | 6 | import com.sun.jna.Pointer 7 | import java.util.* 8 | 9 | private val pointer = ThreadLocal.withInitial { Pointer(0) } 10 | 11 | fun cachedPointer(address: Long): Pointer { 12 | val pointer = pointer.get() 13 | Pointer.nativeValue(pointer, address) 14 | return pointer 15 | } 16 | 17 | private val bufferByClass = ThreadLocal.withInitial { HashMap, NativeBuffer>() } 18 | 19 | fun cachedBuffer(type: Class<*>, bytes: Int): NativeBuffer { 20 | var buf = bufferByClass.get()[type] 21 | if (buf == null) { 22 | buf = NativeBuffer(bytes.toLong()) 23 | bufferByClass.get().put(type, buf) 24 | } 25 | return buf 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/Module.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem 2 | 3 | import com.sun.jna.Pointer 4 | 5 | abstract class Module(val process: Process, val pointer: Pointer, val address: Long = Pointer.nativeValue(pointer)) { 6 | 7 | val buffer by lazy { process[cachedPointer(address), size] } 8 | 9 | abstract val name: String 10 | 11 | abstract val size: Int 12 | 13 | operator inline fun get(offset: Long): T = process[address + offset] 14 | 15 | operator inline fun get(offset: Int): T = get(offset.toLong()) 16 | 17 | operator inline fun set(offset: Long, data: T) = process.set(address + offset, data) 18 | 19 | operator inline fun set(offset: Int, data: T): Unit = set(offset.toLong(), data) 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/mac/mac.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.mac 2 | 3 | import com.sun.jna.* 4 | import com.sun.jna.ptr.IntByReference 5 | import org.jire.kotmem.NativeBuffer 6 | 7 | object mac { 8 | 9 | @JvmStatic 10 | external fun task_for_pid(taskID: Int, pid: Int, out: IntByReference?): Int 11 | 12 | @JvmStatic 13 | external fun getpid(): Int 14 | 15 | @JvmStatic 16 | external fun mach_task_self(): Int 17 | 18 | @JvmStatic 19 | external fun vm_write(taskID: Int, address: Pointer, buffer: NativeBuffer, size: Int): Int 20 | 21 | @JvmStatic 22 | external fun vm_read(taskID: Int, address: Pointer, size: Int, buffer: NativeBuffer, ref: IntByReference?): Int 23 | 24 | @JvmStatic 25 | external fun mach_error_string(result: Int): String 26 | 27 | init { 28 | Native.register(NativeLibrary.getInstance("c")) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/mac/MacProcess.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.mac 2 | 3 | import com.sun.jna.Pointer 4 | import org.jire.kotmem.* 5 | import java.util.* 6 | 7 | class MacProcess(id: Int, val task: Int) : Process(id) { 8 | 9 | override val modules by lazy { 10 | val map = HashMap() 11 | 12 | // TODO resolve modules 13 | 14 | Collections.unmodifiableMap(map) 15 | } 16 | 17 | override fun read(address: Pointer, buffer: NativeBuffer, bytes: Int) { 18 | if (mac.vm_read(task, address, bytes, buffer, null) != bytes) 19 | throw IllegalStateException("Read memory failed at address ${Pointer.nativeValue(address)} bytes $bytes") 20 | Pointer.nativeValue(buffer, Pointer.nativeValue(buffer.getPointer(0))) 21 | } 22 | 23 | override fun write(address: Pointer, buffer: NativeBuffer, bytes: Int) { 24 | if (mac.vm_write(task, address, buffer, bytes) != 0) 25 | throw IllegalStateException("Write memory failed at address ${Pointer.nativeValue(address)} bytes $bytes") 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/NativeBuffer.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem 2 | 3 | import com.sun.jna.Native 4 | import com.sun.jna.Pointer 5 | 6 | class NativeBuffer(val size: Long, val peer: Long = Native.malloc(size)) : Pointer(peer) { 7 | 8 | fun byte() = getByte(0) 9 | 10 | fun short() = getShort(0) 11 | 12 | fun int() = getInt(0) 13 | 14 | fun long() = getLong(0) 15 | 16 | fun float() = getFloat(0) 17 | 18 | fun double() = getDouble(0) 19 | 20 | fun boolean() = byte() > 0 21 | 22 | infix fun bytes(dest: ByteArray) = read(0, dest, 0, dest.size) 23 | 24 | infix fun byte(value: Byte) = apply { setByte(0, value) } 25 | 26 | infix fun short(value: Short) = apply { setShort(0, value) } 27 | 28 | infix fun int(value: Int) = apply { setInt(0, value) } 29 | 30 | infix fun long(value: Long) = apply { setLong(0, value) } 31 | 32 | infix fun float(value: Float) = apply { setFloat(0, value) } 33 | 34 | infix fun double(value: Double) = apply { setDouble(0, value) } 35 | 36 | infix fun boolean(value: Boolean) = byte(if (value) 1 else 0) 37 | 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/Psapi.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.win32 2 | 3 | import com.sun.jna.* 4 | import com.sun.jna.platform.win32.WinDef 5 | import com.sun.jna.ptr.IntByReference 6 | import com.sun.jna.win32.StdCallLibrary 7 | 8 | object Psapi { 9 | 10 | @JvmStatic 11 | fun EnumProcessModulesEx(hProcess: Pointer, lphModule: Array, 12 | cb: Int, lpcbNeeded: IntByReference, filterFlag: Int) = 13 | STD.EnumProcessModulesEx(hProcess, lphModule, cb, lpcbNeeded, filterFlag) 14 | 15 | @JvmStatic 16 | external fun GetModuleInformation(hProcess: Pointer, hModule: WinDef.HMODULE, lpmodinfo: LPMODULEINFO, cb: Int): Boolean 17 | 18 | @JvmStatic 19 | external fun GetModuleBaseNameA(hProcess: Pointer, hModule: WinDef.HMODULE, lpBaseName: ByteArray, nSize: Int): Int 20 | 21 | init { 22 | Native.register(NativeLibrary.getInstance("Psapi")) 23 | } 24 | 25 | } 26 | 27 | private val STD = Native.loadLibrary("Psapi", PsapiStd::class.java) as PsapiStd 28 | 29 | private interface PsapiStd : StdCallLibrary { 30 | 31 | fun EnumProcessModulesEx(hProcess: Pointer, lphModule: Array, cb: Int, 32 | lpcbNeeded: IntByReference, filterFlag: Int): Boolean 33 | 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/Kernel32.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.win32 2 | 3 | import com.sun.jna.* 4 | import com.sun.jna.platform.win32.Tlhelp32 5 | import com.sun.jna.platform.win32.WinDef 6 | import com.sun.jna.platform.win32.WinNT.HANDLE 7 | import com.sun.jna.win32.W32APIOptions 8 | 9 | object Kernel32 { 10 | 11 | @JvmStatic 12 | external fun CreateToolhelp32Snapshot(dwFlags: WinDef.DWORD, th32ProcessID: Int): HANDLE 13 | 14 | @JvmStatic 15 | external fun Process32Next(hSnapshot: HANDLE, lppe: Tlhelp32.PROCESSENTRY32): Boolean 16 | 17 | @JvmStatic 18 | external fun OpenProcess(dwDesiredAccess: Int, bInheritHandle: Boolean, dwProcessId: Int): HANDLE 19 | 20 | @JvmStatic 21 | external fun CloseHandle(hObject: HANDLE): Boolean 22 | 23 | @JvmStatic 24 | external fun WriteProcessMemory(hProcess: Pointer, lpBaseAddress: Pointer, lpBuffer: Pointer, 25 | nSize: Int, lpNumberOfBytesWritten: Int): Long 26 | 27 | @JvmStatic 28 | external fun ReadProcessMemory(hProcess: Pointer, lpBaseAddress: Pointer, lpBuffer: Pointer, 29 | nSize: Int, lpNumberOfBytesWritten: Int): Long 30 | 31 | init { 32 | Native.register(NativeLibrary.getInstance("Kernel32", W32APIOptions.UNICODE_OPTIONS)) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/Win32Process.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.win32 2 | 3 | import com.sun.jna.Native 4 | import com.sun.jna.Pointer 5 | import com.sun.jna.platform.win32.* 6 | import com.sun.jna.ptr.IntByReference 7 | import org.jire.kotmem.NativeBuffer 8 | import org.jire.kotmem.Process 9 | import java.util.* 10 | 11 | class Win32Process(id: Int, val handle: WinNT.HANDLE) : Process(id) { 12 | 13 | override val modules by lazy { 14 | val map = HashMap() 15 | 16 | val hProcess = handle.pointer 17 | val modules = arrayOfNulls(1024) 18 | val needed = IntByReference() 19 | if (Psapi.EnumProcessModulesEx(hProcess, modules, modules.size, needed, 1)) { 20 | for (i in 0..needed.value / 4) { 21 | val module = modules[i] ?: continue 22 | val info = LPMODULEINFO() 23 | if (Psapi.GetModuleInformation(hProcess, module, info, info.size())) { 24 | val win32Module = Win32Module(this, module, info) 25 | map.put(win32Module.name, win32Module) 26 | } 27 | } 28 | } 29 | 30 | Collections.unmodifiableMap(map) 31 | } 32 | 33 | override fun read(address: Pointer, buffer: NativeBuffer, bytes: Int) { 34 | if (!readProcessMemory(this, address, buffer, bytes)) 35 | throw Win32Exception(Native.getLastError()) 36 | } 37 | 38 | override fun write(address: Pointer, buffer: NativeBuffer, bytes: Int) { 39 | if (!writeProcessMemory(this, address, buffer, bytes)) 40 | throw Win32Exception(Native.getLastError()) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/DataType.kt: -------------------------------------------------------------------------------- 1 | @file:JvmMultifileClass 2 | @file:JvmName("Kotmem") 3 | 4 | package org.jire.kotmem 5 | 6 | import org.jire.kotmem.DataType.* 7 | import java.util.* 8 | 9 | sealed class DataType(val bytes: Int, val read: NativeBuffer.() -> T, val write: NativeBuffer.(T) -> Any) { 10 | 11 | object ByteDataType : DataType(1, { byte() }, { byte(it) }) 12 | 13 | object ShortDataType : DataType(2, { short() }, { short(it) }) 14 | 15 | object IntDataType : DataType(4, { int() }, { int(it) }) 16 | 17 | object LongDataType : DataType(8, { long() }, { long(it) }) 18 | 19 | object FloatDataType : DataType(4, { float() }, { float(it) }) 20 | 21 | object DoubleDataType : DataType(8, { double() }, { double(it) }) 22 | 23 | object BooleanDataType : DataType(1, { byte() > 0 }, { byte((if (it) 1 else 0).toByte()) }) 24 | 25 | } 26 | 27 | private val classToType by lazy { 28 | val map = HashMap, DataType<*>>() 29 | 30 | map[java.lang.Byte::class.java] = ByteDataType 31 | map[java.lang.Short::class.java] = ShortDataType 32 | map[java.lang.Integer::class.java] = IntDataType 33 | map[java.lang.Long::class.java] = LongDataType 34 | map[java.lang.Float::class.java] = FloatDataType 35 | map[java.lang.Double::class.java] = DoubleDataType 36 | map[java.lang.Boolean::class.java] = BooleanDataType 37 | 38 | Collections.unmodifiableMap(map) 39 | } 40 | 41 | fun dataTypeOf(`class`: Class) = classToType[`class`] as DataType -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/win32/Win32.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Win32") 2 | 3 | package org.jire.kotmem.win32 4 | 5 | import com.sun.jna.Native 6 | import com.sun.jna.Pointer 7 | import com.sun.jna.platform.win32.Tlhelp32 8 | import org.jire.kotmem.NativeBuffer 9 | 10 | const val PROCESS_QUERY_INFORMATION = 0x400 11 | const val PROCESS_VM_READ = 0x10 12 | const val PROCESS_VM_WRITE = 0x20 13 | const val PROCESS_VM_OPERATION = 0x8 14 | 15 | const val PROCESS_FULL_ACCESS = PROCESS_QUERY_INFORMATION or PROCESS_VM_READ or PROCESS_VM_WRITE or PROCESS_VM_OPERATION 16 | 17 | fun processIDByName(processName: String): Int { 18 | val snapshot = Kernel32.CreateToolhelp32Snapshot(Tlhelp32.TH32CS_SNAPALL, 0) 19 | val entry = Tlhelp32.PROCESSENTRY32.ByReference() 20 | try { 21 | while (Kernel32.Process32Next(snapshot, entry)) { 22 | val entryName = Native.toString(entry.szExeFile) 23 | if (processName.equals(entryName)) 24 | return entry.th32ProcessID.toInt() 25 | } 26 | throw IllegalStateException("Could not find process ID of \"$processName\"") 27 | } finally { 28 | Kernel32.CloseHandle(snapshot) 29 | } 30 | } 31 | 32 | fun openProcess(processID: Int, accessFlags: Int = PROCESS_FULL_ACCESS) 33 | = Win32Process(processID, Kernel32.OpenProcess(accessFlags, true, processID)) 34 | 35 | fun readProcessMemory(process: Win32Process, address: Pointer, buffer: NativeBuffer, bytes: Int) = 36 | Kernel32.ReadProcessMemory(process.handle.pointer, address, buffer, bytes, 0) > 0 37 | 38 | fun writeProcessMemory(process: Win32Process, address: Pointer, buffer: NativeBuffer, bytes: Int) = 39 | Kernel32.WriteProcessMemory(process.handle.pointer, address, buffer, bytes, 0) > 0 -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/Processes.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem 2 | 3 | import com.sun.jna.Platform 4 | import com.sun.jna.ptr.IntByReference 5 | import org.jire.kotmem.linux.LinuxProcess 6 | import org.jire.kotmem.mac.MacProcess 7 | import org.jire.kotmem.mac.mac 8 | import org.jire.kotmem.win32.openProcess 9 | import org.jire.kotmem.win32.processIDByName 10 | import java.util.* 11 | 12 | object Processes { 13 | 14 | @JvmStatic 15 | operator fun get(processID: Int): Process = when { 16 | Platform.isWindows() -> openProcess(processID) 17 | Platform.isLinux() /*&& sudo*/ -> LinuxProcess(processID) 18 | Platform.isMac() /*&& sudo*/ -> { 19 | val out = IntByReference() 20 | if (mac.task_for_pid(mac.mach_task_self(), processID, out) != 0) 21 | throw IllegalStateException("Failed to find mach task port for process") 22 | MacProcess(processID, out.value) 23 | } 24 | else -> throw UnsupportedOperationException("Unsupported platform or not enough privilege") 25 | } 26 | 27 | @JvmStatic 28 | operator inline fun get(processID: Int, action: (Process) -> Unit): Process { 29 | val process = get(processID) 30 | action(process) 31 | return process 32 | } 33 | 34 | @JvmStatic 35 | operator fun get(processName: String) = when { 36 | Platform.isWindows() -> Processes[processIDByName(processName)] 37 | Platform.isLinux() || Platform.isMac() -> { 38 | val search = Runtime.getRuntime().exec(arrayOf("bash", "-c", 39 | "ps -A | grep -m1 \"$processName\" | awk '{print $1}'")) 40 | Processes[Scanner(search.inputStream).nextInt()] 41 | } 42 | else -> throw UnsupportedOperationException("Unsupported platform") 43 | } 44 | 45 | @JvmStatic 46 | operator inline fun get(processName: String, action: (Process) -> Unit): Process { 47 | val process = Processes[processName] 48 | action(process) 49 | return process 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/linux/LinuxProcess.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem.linux 2 | 3 | import com.sun.jna.Pointer 4 | import org.jire.kotmem.* 5 | import java.lang.Long.parseLong 6 | import java.nio.file.Files 7 | import java.nio.file.Paths 8 | import java.util.* 9 | 10 | class LinuxProcess(id: Int) : Process(id) { 11 | 12 | private val local = iovec() 13 | private val remote = iovec() 14 | 15 | override val modules by lazy { 16 | val map = HashMap() 17 | 18 | for (line in Files.readAllLines(Paths.get("/proc/$id/maps"))) { 19 | val split = line.split(" ") 20 | val regionSplit = split[0].split("-") 21 | 22 | val start = parseLong(regionSplit[0], 16) 23 | val end = parseLong(regionSplit[1], 16) 24 | 25 | val offset = parseLong(split[2], 16) 26 | if (offset <= 0) continue 27 | 28 | var path = ""; 29 | var i = 5 30 | while (i < split.size) { 31 | val s = split[i].trim { it <= ' ' } 32 | if (s.isEmpty() && ++i > split.size) break 33 | else if (s.isEmpty() && !split[i].trim { it <= ' ' }.isEmpty()) path += split[i] 34 | else if (!s.isEmpty()) path += split[i] 35 | i++ 36 | } 37 | 38 | val moduleName = path.substring(path.lastIndexOf("/") + 1, path.length) 39 | map.put(moduleName, LinuxModule(this, Pointer.createConstant(start), moduleName, (end - start).toInt())) 40 | } 41 | 42 | Collections.unmodifiableMap(map) 43 | } 44 | 45 | override fun read(address: Pointer, buffer: NativeBuffer, bytes: Int) { 46 | local.iov_base = buffer 47 | local.iov_len = bytes 48 | 49 | remote.iov_base = address 50 | remote.iov_len = bytes 51 | 52 | if (uio.process_vm_readv(id, local, 1, remote, 1, 0) != bytes.toLong()) 53 | throw IllegalStateException("Read memory failed at address ${Pointer.nativeValue(address)} bytes $bytes") 54 | } 55 | 56 | override fun write(address: Pointer, buffer: NativeBuffer, bytes: Int) { 57 | local.iov_base = buffer 58 | local.iov_len = bytes 59 | 60 | remote.iov_base = address 61 | remote.iov_len = bytes 62 | 63 | if (uio.process_vm_writev(id, local, 1, remote, 1, 0) != bytes.toLong()) 64 | throw IllegalStateException("Write memory failed at address ${Pointer.nativeValue(address)} bytes $bytes") 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Kotmem 2 | _Easy process, module and memory interfacing through Kotlin on the JVM_ 3 | 4 | [![Build Status](https://travis-ci.org/Jire/Kotmem.svg?branch=master)](https://travis-ci.org/Jire/Kotmem) 5 | [![Dependency Status](https://www.versioneye.com/user/projects/578838bcc3d40f003caa2efa/badge.svg?style=flat)](https://www.versioneye.com/user/projects/578838bcc3d40f003caa2efa) 6 | [![license](https://img.shields.io/github/license/Jire/Kotmem.svg)](https://github.com/Jire/Kotmem/blob/master/LICENSE) 7 | 8 | ### Gradle 9 | ```groovy 10 | compile 'org.jire.kotmem:Kotmem:0.86' 11 | ``` 12 | 13 | ### Maven 14 | ```xml 15 | 16 | org.jire.kotmem 17 | Kotmem 18 | 0.86 19 | 20 | ``` 21 | 22 | --- 23 | 24 | ###One Minute Intro 25 | 26 | You can open a process by name: 27 | 28 | ```kotlin 29 | val process = Processes["just_for_fun.exe"] 30 | ``` 31 | 32 | Or by ID: 33 | 34 | ```kotlin 35 | val process = Processes[1337] 36 | ``` 37 | 38 | Now let's use the process to read at some address. Note that the type can't be inferred by the compiler here, it must 39 | be explicit in the value declaration. 40 | 41 | ```kotlin 42 | val cafeBabe: Int = process[0xCAFEBABE] 43 | ``` 44 | 45 | Here the compiler can infer that the type is `Boolean`, thus we can omit. 46 | 47 | ```kotlin 48 | if (process[0xBADCAFE]) println("We're in a bad cafe!") 49 | ``` 50 | 51 | We're also able to write at some address. The data argument provides the type thus the type can always be inferred by 52 | the compiler. 53 | 54 | ```kotlin 55 | process[0xBADCAFE] = false 56 | ``` 57 | 58 | We can resolve a process' module as well. These are cached by name on first call. 59 | 60 | ```kotlin 61 | val awesomeDLL = process["awesome.dll"] 62 | ``` 63 | 64 | With the module we are able to query its address `awesomeDLL.address` and name `awesomeDLL.name`. These are lazily 65 | initiated and are cached once accessed. 66 | 67 | We can also use a module to read and write. Doing so will use the module's address as a base and an offset of such is 68 | supplied by the user. 69 | 70 | ```kotlin 71 | val faceFeed: Short = awesomeDLL[0xFACEFEED] 72 | awesomeDLL[0xFACEFEED] = faceFeed + 1 73 | ``` -------------------------------------------------------------------------------- /src/main/kotlin/org/jire/kotmem/Process.kt: -------------------------------------------------------------------------------- 1 | package org.jire.kotmem 2 | 3 | import com.sun.jna.Pointer 4 | 5 | abstract class Process(val id: Int) { 6 | 7 | abstract val modules: Map 8 | 9 | /** 10 | * Reads the specified amount of bytes at the address into a non-cached `NativeBuffer`. 11 | * 12 | * **Warning:** This method should not be used often, it creates a buffer of the specified bytes size. 13 | * 14 | * @param address A pointer to the address to read from. 15 | * @param bytes The amount of bytes to read into the buffer. 16 | * @return The non-cached `NativeBuffer`. 17 | */ 18 | operator fun get(address: Pointer, bytes: Int): NativeBuffer { 19 | val buffer = NativeBuffer(bytes.toLong()) 20 | read(address, buffer, bytes) 21 | return buffer 22 | } 23 | 24 | operator fun get(address: Long, bytes: Int) = get(cachedPointer(address), bytes) 25 | 26 | operator fun get(address: Int, bytes: Int) = get(address.toLong(), bytes) 27 | 28 | operator inline fun get(address: Pointer, dataType: DataType): T { 29 | val type = T::class.java 30 | val bytes = dataType.bytes 31 | val buffer = cachedBuffer(type, bytes) 32 | read(address, buffer, bytes) 33 | return dataType.read(buffer) 34 | } 35 | 36 | operator inline fun get(address: Long, dataType: DataType): T 37 | = get(cachedPointer(address), dataType) 38 | 39 | operator inline fun get(address: Long): T = get(address, dataTypeOf(T::class.java)) 40 | 41 | operator inline fun get(address: Int): T = get(address.toLong()) 42 | 43 | operator inline fun set(address: Pointer, data: T) { 44 | val type = T::class.java 45 | val dataType = dataTypeOf(type) 46 | val bytes = dataType.bytes 47 | val buf = cachedBuffer(type, bytes) 48 | dataType.write(buf, data) 49 | write(address, buf, bytes) 50 | } 51 | 52 | operator inline fun set(address: Long, data: T): Unit = set(cachedPointer(address), data) 53 | 54 | operator inline fun set(address: Int, data: T): Unit = set(address.toLong(), data) 55 | 56 | operator fun get(moduleName: String) = modules[moduleName]!! 57 | 58 | abstract fun read(address: Pointer, buffer: NativeBuffer, bytes: Int) 59 | 60 | abstract fun write(address: Pointer, buffer: NativeBuffer, bytes: Int) 61 | 62 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. --------------------------------------------------------------------------------