Skip to content

Commit d92e0d5

Browse files
authored
Initial import of the process_vm IO plugin ##io
1 parent dadb397 commit d92e0d5

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

dist/plugins-cfg/plugins.android.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ fs.reiserfs
7979
io.bfdbg
8080
io.debug
8181
io.default
82+
io.pvm
8283
io.dsc
8384
io.gzip
8485
io.http

dist/plugins-cfg/plugins.def.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ io.gzip
258258
io.http
259259
io.ihex
260260
io.isotp
261+
io.pvm
261262
io.mach
262263
io.malloc
263264
io.mmap

libr/include/r_io.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,7 @@ extern RIOPlugin r_io_plugin_xalz;
677677
extern RIOPlugin r_io_plugin_reg;
678678
extern RIOPlugin r_io_plugin_treebuf;
679679
extern RIOPlugin r_io_plugin_sysgdb;
680+
extern RIOPlugin r_io_plugin_pvm;
680681
extern RIOPlugin r_io_plugin_serial;
681682
extern RIOPlugin r_io_plugin_cyclic;
682683
extern RIOPlugin r_io_plugin_uf2;

libr/io/p/io_pvm.c

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/* radare2 - MIT - Copyright 2025 - apkunpacker */
2+
3+
#define R_LOG_ORIGIN "io.pvm"
4+
5+
#include <r_io.h>
6+
#include <r_lib.h>
7+
#include <r_util.h>
8+
9+
#if __linux__
10+
11+
#include <sys/uio.h>
12+
#include <sys/syscall.h>
13+
#include <sys/stat.h>
14+
#include <sys/types.h>
15+
#include <errno.h>
16+
#include <fcntl.h>
17+
18+
#define PTRACE_ENHANCED_URI "pvm://"
19+
#define DEFAULT_BUFFER_SIZE 4096
20+
#define MAX_LINE_LENGTH 2048
21+
#define MAX_PATH_LENGTH 1024
22+
23+
// Syscall declarations if not available
24+
#ifndef process_vm_readv
25+
static ssize_t process_vm_readv(pid_t pid, const struct iovec *local_iov, unsigned long liovcnt,
26+
const struct iovec *remote_iov, unsigned long riovcnt, unsigned long flags) {
27+
return syscall (__NR_process_vm_readv, pid, local_iov, liovcnt, remote_iov, riovcnt, flags);
28+
}
29+
#endif
30+
31+
#ifndef process_vm_writev
32+
static ssize_t process_vm_writev(pid_t pid, const struct iovec *local_iov, unsigned long liovcnt,
33+
const struct iovec *remote_iov, unsigned long riovcnt, unsigned long flags) {
34+
return syscall (__NR_process_vm_writev, pid, local_iov, liovcnt, remote_iov, riovcnt, flags);
35+
}
36+
#endif
37+
38+
typedef struct {
39+
pid_t pid;
40+
FILE* maps_file;
41+
size_t buffer_size;
42+
uint8_t* buffer;
43+
bool initialized;
44+
} ProcessVmData;
45+
46+
static void cleanup_ptrace_data(ProcessVmData* data) {
47+
if (!data) {
48+
return;
49+
}
50+
if (data->maps_file) {
51+
fclose (data->maps_file);
52+
data->maps_file = NULL;
53+
}
54+
if (data->buffer) {
55+
free (data->buffer);
56+
data->buffer = NULL;
57+
}
58+
data->initialized = false;
59+
}
60+
61+
static size_t pvm_read_memory(ProcessVmData* data, uint64_t addr, void* buffer, size_t size) {
62+
if (!data || !data->initialized || !buffer || size == 0) {
63+
return 0;
64+
}
65+
66+
struct iovec local_iov = {buffer, size};
67+
struct iovec remote_iov = {(void *)(uintptr_t)addr, size};
68+
ssize_t bytes = process_vm_readv (data->pid, &local_iov, 1, &remote_iov, 1, 0);
69+
return bytes < 0 ? 0 : (size_t)bytes;
70+
}
71+
72+
static size_t pvm_write_memory(ProcessVmData* data, uint64_t addr, const void* buffer, size_t size) {
73+
if (!data || !data->initialized || !buffer || size == 0) {
74+
return 0;
75+
}
76+
struct iovec local_iov = {(void*)buffer, size};
77+
struct iovec remote_iov = {(void *)(uintptr_t)addr, size};
78+
79+
ssize_t bytes = process_vm_writev (data->pid, &local_iov, 1, &remote_iov, 1, 0);
80+
return bytes < 0 ? 0 : (size_t)bytes;
81+
}
82+
83+
static bool __check(RIO *io, const char *pathname, bool many) {
84+
return r_str_startswith (pathname, PTRACE_ENHANCED_URI);
85+
}
86+
87+
static RIODesc *__open(RIO *io, const char *pathname, int rw, int mode) {
88+
if (!__check (io, pathname, 0)) {
89+
return NULL;
90+
}
91+
92+
// Extract PID from URI: ptrace://1234
93+
pathname += strlen (PTRACE_ENHANCED_URI);
94+
pid_t pid = (pid_t)strtol (pathname, NULL, 10);
95+
96+
if (pid <= 0 || pid > 4194304) {
97+
R_LOG_ERROR ("Invalid PID: %d", pid);
98+
return NULL;
99+
}
100+
101+
// Initialize enhanced ptrace data
102+
ProcessVmData *data = R_NEW0 (ProcessVmData);
103+
data->pid = pid;
104+
data->buffer_size = DEFAULT_BUFFER_SIZE;
105+
106+
// Open maps file
107+
char maps_path[MAX_PATH_LENGTH];
108+
snprintf (maps_path, sizeof (maps_path), "/proc/%d/maps", pid);
109+
data->maps_file = fopen (maps_path, "r");
110+
111+
if (!data->maps_file) {
112+
R_LOG_ERROR ("Cannot open %s: %s", maps_path, strerror (errno));
113+
free (data);
114+
return NULL;
115+
}
116+
117+
// Allocate buffer
118+
data->buffer = (uint8_t*)malloc (data->buffer_size);
119+
if (!data->buffer) {
120+
R_LOG_ERROR ("Buffer allocation failed");
121+
cleanup_ptrace_data (data);
122+
free (data);
123+
return NULL;
124+
}
125+
126+
// Test process_vm_readv access
127+
char test_buffer;
128+
struct iovec local_iov = {&test_buffer, 1};
129+
struct iovec remote_iov = {(void *)(uintptr_t)1, 1};
130+
errno = 0;
131+
process_vm_readv (pid, &local_iov, 1, &remote_iov, 1, 0);
132+
133+
if (errno == EPERM || errno == ESRCH) {
134+
R_LOG_ERROR ("Permission denied or process not found for PID %d", pid);
135+
cleanup_ptrace_data (data);
136+
free (data);
137+
return NULL;
138+
}
139+
140+
data->initialized = true;
141+
142+
R_LOG_INFO ("Process VM opened for PID %d", pid);
143+
return r_io_desc_new (io, &r_io_plugin_pvm, pathname,
144+
R_PERM_RW | (rw & R_PERM_X), mode, data);
145+
}
146+
147+
static int __read(RIO *io, RIODesc *desc, ut8 *buf, int count) {
148+
ProcessVmData *data = desc->data;
149+
if (!data || !data->initialized) {
150+
return -1;
151+
}
152+
153+
ut64 addr = r_io_desc_seek (desc, 0LL, R_IO_SEEK_CUR);
154+
return (int)pvm_read_memory (data, addr, buf, count);
155+
}
156+
157+
static int __write(RIO *io, RIODesc *desc, const ut8 *buf, int count) {
158+
ProcessVmData *data = desc->data;
159+
if (!data || !data->initialized) {
160+
return -1;
161+
}
162+
ut64 addr = r_io_desc_seek(desc, 0LL, R_IO_SEEK_CUR);
163+
return (int)pvm_write_memory (data, addr, buf, count);
164+
}
165+
166+
static ut64 __lseek(RIO *io, RIODesc *desc, ut64 offset, int whence) {
167+
switch (whence) {
168+
case SEEK_SET:
169+
case SEEK_CUR:
170+
return r_io_desc_seek (desc, offset, whence);
171+
case SEEK_END:
172+
return UT64_MAX; // The the whole address space
173+
}
174+
return offset;
175+
}
176+
177+
static bool __close(RIODesc *desc) {
178+
ProcessVmData *data = desc->data;
179+
if (data) {
180+
cleanup_ptrace_data (data);
181+
free (data);
182+
}
183+
return true;
184+
}
185+
186+
static int __getpid(RIODesc *desc) {
187+
ProcessVmData *data = desc->data;
188+
return data ? data->pid : -1;
189+
}
190+
191+
RIOPlugin r_io_plugin_pvm = {
192+
.meta = {
193+
.name = "pvm",
194+
.desc = "Access remote process memory using the Linux process_vm APIs",
195+
.license = "MIT",
196+
.author = "apkunpacker",
197+
},
198+
.uris = PTRACE_ENHANCED_URI,
199+
.open = __open,
200+
.close = __close,
201+
.read = __read,
202+
.write = __write,
203+
.seek = __lseek,
204+
.check = __check,
205+
.getpid = __getpid,
206+
};
207+
208+
#else
209+
RIOPlugin r_io_plugin_pvm = {
210+
.meta = {
211+
.name = NULL
212+
},
213+
};
214+
#endif

libr/io/p/pvm.mk

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
OBJ_PVM=io_pvm.o
2+
3+
STATIC_OBJ+=${OBJ_PVM}
4+
TARGET_PVM=io_pvm.${EXT_SO}
5+
ALL_TARGETS+=${TARGET_PVM}
6+
7+
${TARGET_PVM}: ${OBJ_PVM}
8+
${CC_LIB} ${CFLAGS} -o ${TARGET_PVM} ${LDFLAGS_LIB} \
9+
$(call libname,io_pvm) $(LDFLAGS) \
10+
${LDFLAGS_LINKPATH}../../util -L../../util -lr_util \
11+
${LDFLAGS_LINKPATH}.. -L.. -lr_io ${OBJ_PVM}

0 commit comments

Comments
 (0)