From 743fb001f3d381b14d48f3fdfc9ee648a7c0644c Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Fri, 19 Oct 2018 14:35:16 +0300 Subject: Refactor project, offer uio variant of region-rw --- Makefile | 13 +-- binsearch.c | 84 ------------------ bintrim.c | 44 --------- contrib/brute-map.bash | 2 +- proc-region-rw.c | 225 ----------------------------------------------- src/binsearch.c | 84 ++++++++++++++++++ src/bintrim.c | 44 +++++++++ src/cli/proc-region-rw.c | 194 ++++++++++++++++++++++++++++++++++++++++ src/cli/proc-region-rw.h | 9 ++ src/io/io-ptrace.c | 82 +++++++++++++++++ src/io/io-uio.c | 41 +++++++++ src/io/io.h | 27 ++++++ src/ptrace-region-rw.c | 12 +++ src/uio-region-rw.c | 13 +++ 14 files changed, 515 insertions(+), 359 deletions(-) delete mode 100644 binsearch.c delete mode 100644 bintrim.c delete mode 100644 proc-region-rw.c create mode 100644 src/binsearch.c create mode 100644 src/bintrim.c create mode 100644 src/cli/proc-region-rw.c create mode 100644 src/cli/proc-region-rw.h create mode 100644 src/io/io-ptrace.c create mode 100644 src/io/io-uio.c create mode 100644 src/io/io.h create mode 100644 src/ptrace-region-rw.c create mode 100644 src/uio-region-rw.c diff --git a/Makefile b/Makefile index c9dd6b5..71ce55b 100644 --- a/Makefile +++ b/Makefile @@ -7,17 +7,20 @@ WARNINGS := -Wall -Wextra -Wpedantic -Wformat=2 -Wstrict-aliasing=3 -Wstrict-ove -Wfloat-equal -Wcast-align -Wpointer-arith -Wchar-subscripts -Warray-bounds=2 override CFLAGS ?= -g -override CFLAGS += -std=c99 -D_DEFAULT_SOURCE $(WARNINGS) +override CFLAGS += -std=c99 $(WARNINGS) +override CPPFLAGS += -Isrc -bins = proc-region-rw binsearch bintrim +bins = ptrace-region-rw uio-region-rw binsearch bintrim all: $(bins) $(bins): %: $(LINK.c) $^ $(LDLIBS) -o $@ -proc-region-rw: proc-region-rw.c -binsearch: binsearch.c -bintrim: bintrim.c +ptrace-region-rw: src/ptrace-region-rw.c src/cli/proc-region-rw.c src/io/io-ptrace.c +uio-region-rw: private CPPFLAGS += -D_GNU_SOURCE +uio-region-rw: src/uio-region-rw.c src/cli/proc-region-rw.c src/io/io-uio.c +binsearch: src/binsearch.c +bintrim: src/bintrim.c install-bin: $(bins) install -Dm755 $^ -t "$(DESTDIR)$(PREFIX)$(bindir)" diff --git a/binsearch.c b/binsearch.c deleted file mode 100644 index a4195ba..0000000 --- a/binsearch.c +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include -#include -#include -#include - -static void -usage(const char *argv0) -{ - fprintf(stderr, "usage: %s needle [window-size] < haystack\n", argv0); - exit(EXIT_FAILURE); -} - -static const char* -search(const char *haystack, const char *needle, const size_t window_size) -{ - for (const char *s = haystack; s < haystack + window_size; ++s) { - if (memcmp(s, needle, window_size)) - continue; - return s; - } - return NULL; -} - -static void -search_and_exit_if_match(const char *haystack, const char *needle, const size_t window_size, const size_t offset) -{ - const char *match; - if ((match = search(haystack, needle, window_size))) { - printf("%zu\n", offset + match - haystack); - free((void*)needle); - free((void*)haystack); - exit(EXIT_SUCCESS); - } -} - -int -main(int argc, const char *argv[]) -{ - // default incase failure, or cant get size of file - size_t window_size = 4096 * 1024; - bool has_window_size = false; - - if (argc < 2) - usage(argv[0]); - else if (argc > 2) { - window_size = strtoull(argv[2], NULL, 10); - has_window_size = true; - } - - FILE *f; - if (!(f = fopen(argv[1], "rb"))) - err(EXIT_FAILURE, "fopen(%s)", argv[1]); - - if (!has_window_size) { - fseek(f, 0, SEEK_END); - const long tell = ftell(f); - window_size = (tell > 0 ? (size_t)tell : window_size); - fseek(f, 0, SEEK_SET); - } - - char *needle; - if (!(needle = malloc(window_size))) - err(EXIT_FAILURE, "malloc"); - - window_size = fread(needle, 1, window_size, f); - fclose(f); - - char *haystack; - if (!(haystack = calloc(2, window_size))) - err(EXIT_FAILURE, "calloc"); - - size_t rd = 0, offset = 0; - while ((rd = fread(haystack + window_size * !!offset, 1 + !offset, window_size, stdin))) { - search_and_exit_if_match(haystack, needle, (rd >= window_size ? window_size : 0), offset); - offset += window_size; - memmove(haystack, haystack + window_size, window_size); - } - - search_and_exit_if_match(haystack, needle, (rd >= window_size ? window_size : 0), offset); - free(needle); - free(haystack); - return EXIT_FAILURE; -} diff --git a/bintrim.c b/bintrim.c deleted file mode 100644 index 2a2018e..0000000 --- a/bintrim.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include - -int -main(int argc, const char *argv[]) -{ - unsigned char trim = 0; - - if (argc > 1) - trim = strtoul(argv[1], NULL, 10); - - bool leading = true; - size_t rd, out_sz = 0, out_allocated = 0; - char buf[4096], *out = NULL; - while ((rd = fread(buf, 1, sizeof(buf), stdin))) { - for (const char *s = buf; s < buf + rd; ++s) { - if (*s == trim && leading) - continue; - - if (out_sz >= out_allocated) { - if (!(out = realloc(out, out_allocated += sizeof(buf)))) - err(EXIT_FAILURE, "realloc"); - } - - out[out_sz++] = *s; - leading = false; - } - - const char *s; - for (s = out + (out_sz ? out_sz - 1 : 0); s > out && *s == trim; --s); - - const size_t to_write = (size_t)(s - out); - if (fwrite(out, 1, to_write, stdout) != to_write) - err(EXIT_FAILURE, "fwrite"); - - memmove(out, s, (out_sz = out_sz - to_write)); - } - - free(out); - return EXIT_SUCCESS; -} diff --git a/contrib/brute-map.bash b/contrib/brute-map.bash index f37581f..4a5a811 100755 --- a/contrib/brute-map.bash +++ b/contrib/brute-map.bash @@ -5,7 +5,7 @@ while read -r region; do offset=$(printf '%d' "0x$(awk '{print $3}' <<<"$region")") if ((offset == 0)); then - offset=$(binsearch <(proc-region-rw "$1" read <<<"$region" 2>/dev/null | bintrim) $3 < "$2") + offset=$(binsearch <(ptrace-region-rw "$1" read <<<"$region" 2>/dev/null | bintrim) $3 < "$2") if [[ -n "$offset" ]]; then hex=$(printf '%.8x' "$offset") awk '{printf "%s %s %s %s %s %s\n", $1, $2, "'"$hex"'", $4, $5, $6, $7}' <<<"$region" diff --git a/proc-region-rw.c b/proc-region-rw.c deleted file mode 100644 index f25fe2b..0000000 --- a/proc-region-rw.c +++ /dev/null @@ -1,225 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -// It's recommended to `setcap cap_sys_ptrace=eip proc-region-rw` to run this tool without sudo - -struct context { - void *buf; - - struct { - size_t offset, len; - bool has_offset, has_len; - - struct { - FILE *src; - size_t size; - } wm; // write/map - - enum { - MODE_MAP, - MODE_WRITE, - MODE_READ - } mode; - } op; - - struct { - FILE *mem; - pid_t pid; - } proc; -}; - -static void -usage(const char *argv0) -{ - fprintf(stderr, "usage: %s pid map file [offset] [len] < regions\n" - " %s pid write file [offset] [len] < regions\n" - " %s pid read [offset] [len] < regions\n" - " regions must be in /proc//maps format", argv0, argv0, argv0); - exit(EXIT_FAILURE); -} - -static void -context_init(struct context *ctx, size_t argc, const char *argv[]) -{ - if (argc < 3) - usage(argv[0]); - - size_t arg = 1; - *ctx = (struct context){0}; - ctx->proc.pid = strtoull(argv[arg++], NULL, 10); - - { - bool m = false, w = false, r = false; - const char *mode = argv[arg++]; - if (!(m = !strcmp(mode, "map")) && !(w = !strcmp(mode, "write")) && !(r = !strcmp(mode, "read"))) - errx(EXIT_FAILURE, "mode must be write or read"); - - ctx->op.mode = (m ? MODE_MAP : (w ? MODE_WRITE : MODE_READ)); - } - - const char *wmname = NULL; - if (ctx->op.mode == MODE_MAP || ctx->op.mode == MODE_WRITE) { - if (argc < arg + 1) - usage(argv[0]); - - wmname = argv[arg++]; - } - - if (argc >= arg + 1) { - ctx->op.offset = strtoull(argv[arg++], NULL, 10); - ctx->op.has_offset = true; - } - - if (argc >= arg + 1) { - ctx->op.len = strtoull(argv[arg++], NULL, 10); - ctx->op.has_len = true; - } - - if (wmname) { - if (!(ctx->op.wm.src = fopen(wmname, "rb"))) - err(EXIT_FAILURE, "fopen(%s)", wmname); - - if (fseek(ctx->op.wm.src, 0, SEEK_END) != 0) - err(EXIT_FAILURE, "fseek"); - - ctx->op.wm.size = ftell(ctx->op.wm.src); - } - - char path[128]; - snprintf(path, sizeof(path), "/proc/%u/mem", ctx->proc.pid); - if (!(ctx->proc.mem = fopen(path, "w+b"))) - err(EXIT_FAILURE, "fopen(%s)", path); -} - -static void -context_release(struct context *ctx) -{ - if (ctx->op.wm.src) - fclose(ctx->op.wm.src); - - fclose(ctx->proc.mem); - free(ctx->buf); - *ctx = (struct context){0}; -} - -static void -for_each_line_in_file(FILE *f, void (*cb)(const char *line, void *data), void *data) -{ - char *buffer = NULL; - size_t step = 1024, allocated = 0, written = 0, read = 0; - do { - if (written + read >= allocated && !(buffer = realloc(buffer, (allocated += step) + 1))) - err(EXIT_FAILURE, "realloc"); - buffer[(written += read)] = 0; - size_t ate = 0; - for (char *line = buffer, *nl; (nl = strchr(line, '\n')); line = nl + 1) { - *nl = 0; - cb(line, data); - ate += nl + 1 - line; - } - memmove(buffer, buffer + ate, (written = written - ate)); - } while ((read = fread(buffer + written, 1, allocated - written, f))); - free(buffer); -} - -static void -region_cb(const char *line, void *data) -{ - struct context *ctx = data; - unsigned long start, end, region_offset; - if (sscanf(line, "%lx-%lx %*s %lx", &start, &end, ®ion_offset) < 3) { - warnx("failed to parse mapping:\n%s", line); - return; - } - - warnx("%s", line); - start += ctx->op.offset; - - if (start > end) { - warnx("write offset %lx is out of bounds", start); - return; - } - - region_offset = (ctx->op.mode == MODE_MAP ? region_offset : 0); - - // requested write/read - const size_t rlen = (ctx->op.has_len ? ctx->op.len : (ctx->op.mode == MODE_READ ? end - start : ctx->op.wm.size)); - - // actual write/read - const size_t len = (rlen > end - start ? end - start : rlen); - - if (!len) - return; - - if (!(ctx->buf = realloc(ctx->buf, len))) - err(EXIT_FAILURE, "realloc"); - - clearerr(ctx->proc.mem); - if (ctx->op.mode == MODE_MAP || ctx->op.mode == MODE_WRITE) { - if (fseek(ctx->op.wm.src, region_offset, SEEK_SET) != 0) - err(EXIT_FAILURE, "fseek"); - - const size_t rd = fread(ctx->buf, 1, len, ctx->op.wm.src); - - if (fseek(ctx->proc.mem, start, SEEK_SET) != 0) - err(EXIT_FAILURE, "fseek"); - - const size_t wd = fwrite(ctx->buf, 1, rd, ctx->proc.mem); - - if (ferror(ctx->proc.mem)) { - warn("fread(/proc/%u/mem)", ctx->proc.pid); - return; - } - - if (ctx->op.mode == MODE_WRITE) { - if (rlen > wd) { - warnx("wrote %lu bytes (%lu bytes truncated) to offset 0x%lx", wd, rlen - wd, start); - } else { - warnx("wrote %lu bytes to offset 0x%lx", wd, start); - } - } else { - if (rlen > wd) { - warnx("mapped %lu bytes (%lu bytes truncated) from offset 0x%lx to offset 0x%lx", wd, rlen - wd, region_offset, start); - } else { - warnx("mapped %lu bytes from offset 0x%lx to offset 0x%lx", wd, region_offset, start); - } - } - } else { - if (fseek(ctx->proc.mem, start, SEEK_SET) != 0) - err(EXIT_FAILURE, "fseek"); - - const size_t rd = fread(ctx->buf, 1, len, ctx->proc.mem); - - if (ferror(ctx->proc.mem)) - warn("fread(/proc/%u/mem)", ctx->proc.pid); - - if (fwrite(ctx->buf, 1, rd, stdout) != rd) - err(EXIT_FAILURE, "fwrite"); - } -} - -int -main(int argc, const char *argv[]) -{ - struct context ctx; - context_init(&ctx, argc, argv); - - if (ptrace(PTRACE_ATTACH, ctx.proc.pid, NULL, NULL) == -1L) - err(EXIT_FAILURE, "ptrace(PTRACE_ATTACH, %u, NULL, NULL)", ctx.proc.pid); - - { - int status; - if (waitpid(ctx.proc.pid, &status, 0) == -1 || !WIFSTOPPED(status)) - err(EXIT_FAILURE, "waitpid"); - } - - for_each_line_in_file(stdin, region_cb, &ctx); - ptrace(PTRACE_DETACH, ctx.proc.pid, 1, 0); - context_release(&ctx); - return EXIT_SUCCESS; -} diff --git a/src/binsearch.c b/src/binsearch.c new file mode 100644 index 0000000..a4195ba --- /dev/null +++ b/src/binsearch.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +static void +usage(const char *argv0) +{ + fprintf(stderr, "usage: %s needle [window-size] < haystack\n", argv0); + exit(EXIT_FAILURE); +} + +static const char* +search(const char *haystack, const char *needle, const size_t window_size) +{ + for (const char *s = haystack; s < haystack + window_size; ++s) { + if (memcmp(s, needle, window_size)) + continue; + return s; + } + return NULL; +} + +static void +search_and_exit_if_match(const char *haystack, const char *needle, const size_t window_size, const size_t offset) +{ + const char *match; + if ((match = search(haystack, needle, window_size))) { + printf("%zu\n", offset + match - haystack); + free((void*)needle); + free((void*)haystack); + exit(EXIT_SUCCESS); + } +} + +int +main(int argc, const char *argv[]) +{ + // default incase failure, or cant get size of file + size_t window_size = 4096 * 1024; + bool has_window_size = false; + + if (argc < 2) + usage(argv[0]); + else if (argc > 2) { + window_size = strtoull(argv[2], NULL, 10); + has_window_size = true; + } + + FILE *f; + if (!(f = fopen(argv[1], "rb"))) + err(EXIT_FAILURE, "fopen(%s)", argv[1]); + + if (!has_window_size) { + fseek(f, 0, SEEK_END); + const long tell = ftell(f); + window_size = (tell > 0 ? (size_t)tell : window_size); + fseek(f, 0, SEEK_SET); + } + + char *needle; + if (!(needle = malloc(window_size))) + err(EXIT_FAILURE, "malloc"); + + window_size = fread(needle, 1, window_size, f); + fclose(f); + + char *haystack; + if (!(haystack = calloc(2, window_size))) + err(EXIT_FAILURE, "calloc"); + + size_t rd = 0, offset = 0; + while ((rd = fread(haystack + window_size * !!offset, 1 + !offset, window_size, stdin))) { + search_and_exit_if_match(haystack, needle, (rd >= window_size ? window_size : 0), offset); + offset += window_size; + memmove(haystack, haystack + window_size, window_size); + } + + search_and_exit_if_match(haystack, needle, (rd >= window_size ? window_size : 0), offset); + free(needle); + free(haystack); + return EXIT_FAILURE; +} diff --git a/src/bintrim.c b/src/bintrim.c new file mode 100644 index 0000000..2a2018e --- /dev/null +++ b/src/bintrim.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +int +main(int argc, const char *argv[]) +{ + unsigned char trim = 0; + + if (argc > 1) + trim = strtoul(argv[1], NULL, 10); + + bool leading = true; + size_t rd, out_sz = 0, out_allocated = 0; + char buf[4096], *out = NULL; + while ((rd = fread(buf, 1, sizeof(buf), stdin))) { + for (const char *s = buf; s < buf + rd; ++s) { + if (*s == trim && leading) + continue; + + if (out_sz >= out_allocated) { + if (!(out = realloc(out, out_allocated += sizeof(buf)))) + err(EXIT_FAILURE, "realloc"); + } + + out[out_sz++] = *s; + leading = false; + } + + const char *s; + for (s = out + (out_sz ? out_sz - 1 : 0); s > out && *s == trim; --s); + + const size_t to_write = (size_t)(s - out); + if (fwrite(out, 1, to_write, stdout) != to_write) + err(EXIT_FAILURE, "fwrite"); + + memmove(out, s, (out_sz = out_sz - to_write)); + } + + free(out); + return EXIT_SUCCESS; +} diff --git a/src/cli/proc-region-rw.c b/src/cli/proc-region-rw.c new file mode 100644 index 0000000..fa8f343 --- /dev/null +++ b/src/cli/proc-region-rw.c @@ -0,0 +1,194 @@ +#include +#include +#include +#include + +static void +usage(const char *argv0) +{ + fprintf(stderr, "usage: %s pid map file [offset] [len] < regions\n" + " %s pid write file [offset] [len] < regions\n" + " %s pid read [offset] [len] < regions\n" + " regions must be in /proc//maps format", argv0, argv0, argv0); + exit(EXIT_FAILURE); +} + +#include "io/io.h" + +struct context { + void *buf; + + struct { + size_t offset, len; + bool has_offset, has_len; + + struct { + FILE *src; + size_t size; + } wm; // write/map + + enum { + MODE_MAP, + MODE_WRITE, + MODE_READ + } mode; + } op; + + struct io io; +}; + +static inline void +context_init(struct context *ctx, size_t argc, const char *argv[]) +{ + size_t arg = 0; + *ctx = (struct context){0}; + + { + bool m = false, w = false, r = false; + const char *mode = argv[arg++]; + if (!(m = !strcmp(mode, "map")) && !(w = !strcmp(mode, "write")) && !(r = !strcmp(mode, "read"))) + errx(EXIT_FAILURE, "mode must be write or read"); + + ctx->op.mode = (m ? MODE_MAP : (w ? MODE_WRITE : MODE_READ)); + } + + const char *wmname = NULL; + if (ctx->op.mode == MODE_MAP || ctx->op.mode == MODE_WRITE) { + if (argc < arg + 1) + usage(argv[0]); + + wmname = argv[arg++]; + } + + if (argc >= arg + 1) { + ctx->op.offset = strtoull(argv[arg++], NULL, 10); + ctx->op.has_offset = true; + } + + if (argc >= arg + 1) { + ctx->op.len = strtoull(argv[arg++], NULL, 10); + ctx->op.has_len = true; + } + + if (wmname) { + if (!(ctx->op.wm.src = fopen(wmname, "rb"))) + err(EXIT_FAILURE, "fopen(%s)", wmname); + + if (fseek(ctx->op.wm.src, 0, SEEK_END) != 0) + err(EXIT_FAILURE, "fseek"); + + ctx->op.wm.size = ftell(ctx->op.wm.src); + } +} + +static void +context_release(struct context *ctx) +{ + if (ctx->op.wm.src) + fclose(ctx->op.wm.src); + + free(ctx->buf); + *ctx = (struct context){0}; +} + +static void +region_cb(const char *line, void *data) +{ + struct context *ctx = data; + unsigned long start, end, region_offset; + if (sscanf(line, "%lx-%lx %*s %lx", &start, &end, ®ion_offset) < 3) { + warnx("failed to parse mapping:\n%s", line); + return; + } + + warnx("%s", line); + start += ctx->op.offset; + + if (start > end) { + warnx("write offset %lx is out of bounds", start); + return; + } + + region_offset = (ctx->op.mode == MODE_MAP ? region_offset : 0); + + // requested write/read + const size_t rlen = (ctx->op.has_len ? ctx->op.len : (ctx->op.mode == MODE_READ ? end - start : ctx->op.wm.size)); + + // actual write/read + const size_t len = (rlen > end - start ? end - start : rlen); + + if (!len) + return; + + if (!(ctx->buf = realloc(ctx->buf, len))) + err(EXIT_FAILURE, "realloc"); + + if (ctx->op.mode == MODE_MAP || ctx->op.mode == MODE_WRITE) { + if (fseek(ctx->op.wm.src, region_offset, SEEK_SET) != 0) + err(EXIT_FAILURE, "fseek"); + + const size_t rd = fread(ctx->buf, 1, len, ctx->op.wm.src); + const size_t wd = ctx->io.write(&ctx->io, ctx->buf, start, rd); + + if (ctx->op.mode == MODE_WRITE) { + if (rlen > wd) { + warnx("wrote %lu bytes (%lu bytes truncated) to offset 0x%lx", wd, rlen - wd, start); + } else { + warnx("wrote %lu bytes to offset 0x%lx", wd, start); + } + } else { + if (rlen > wd) { + warnx("mapped %lu bytes (%lu bytes truncated) from offset 0x%lx to offset 0x%lx", wd, rlen - wd, region_offset, start); + } else { + warnx("mapped %lu bytes from offset 0x%lx to offset 0x%lx", wd, region_offset, start); + } + } + } else { + const size_t rd = ctx->io.read(&ctx->io, ctx->buf, start, len); + + if (fwrite(ctx->buf, 1, rd, stdout) != rd) + err(EXIT_FAILURE, "fwrite"); + } +} + +static inline void +for_each_line_in_file(FILE *f, void (*cb)(const char *line, void *data), void *data) +{ + char *buffer = NULL; + size_t step = 1024, allocated = 0, written = 0, read = 0; + do { + if (written + read >= allocated && !(buffer = realloc(buffer, (allocated += step) + 1))) + err(EXIT_FAILURE, "realloc"); + + buffer[(written += read)] = 0; + + size_t ate = 0; + for (char *line = buffer, *nl; (nl = strchr(line, '\n')); line = nl + 1) { + *nl = 0; + cb(line, data); + ate += nl + 1 - line; + } + + memmove(buffer, buffer + ate, (written = written - ate)); + } while ((read = fread(buffer + written, 1, allocated - written, f))); + free(buffer); +} + +int +proc_region_rw(int argc, const char *argv[], bool (*io_init)(struct io*, const pid_t)) +{ + if (argc < 3) + usage(argv[0]); + + const pid_t pid = strtoull(argv[1], NULL, 10); + struct context ctx; + context_init(&ctx, argc - 2, argv + 2); + + if (!io_init(&ctx.io, pid)) + return EXIT_FAILURE; + + for_each_line_in_file(stdin, region_cb, &ctx); + io_release(&ctx.io); + context_release(&ctx); + return EXIT_SUCCESS; +} diff --git a/src/cli/proc-region-rw.h b/src/cli/proc-region-rw.h new file mode 100644 index 0000000..7ebae67 --- /dev/null +++ b/src/cli/proc-region-rw.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include // pid_t + +struct io; + +int +proc_region_rw(int argc, const char *argv[], bool (*io_init)(struct io*, const pid_t)); diff --git a/src/io/io-ptrace.c b/src/io/io-ptrace.c new file mode 100644 index 0000000..50d0766 --- /dev/null +++ b/src/io/io-ptrace.c @@ -0,0 +1,82 @@ +#include "io.h" +#include +#include +#include +#include + +static size_t +io_ptrace_do(const struct io *io, void *ptr, const size_t offset, const size_t size, size_t (*iofun)(void*, size_t, size_t, FILE*)) +{ + if (fseek(io->backing, offset, SEEK_SET) != 0) { + warn("fseek(/proc/%u/mem, %zu)", io->pid, offset); + return 0; + } + + return iofun(ptr, 1, size, io->backing); +} + +static size_t +io_ptrace_write(const struct io *io, const void *ptr, const size_t offset, const size_t size) +{ + clearerr(io->backing); + const size_t ret = io_ptrace_do(io, (void*)ptr, offset, size, (size_t(*)())fwrite); + + if (ferror(io->backing)) + warn("fwrite(/proc/%u/mem)", io->pid); + + return ret; +} + +static size_t +io_ptrace_read(const struct io *io, void *ptr, const size_t offset, const size_t size) +{ + clearerr(io->backing); + const size_t ret = io_ptrace_do(io, ptr, offset, size, fread); + + if (ferror(io->backing)) + warn("fread(/proc/%u/mem)", io->pid); + + return ret; +} + +static void +io_ptrace_cleanup(struct io *io) +{ + if (io->backing) + fclose(io->backing); + + if (io->pid) + ptrace(PTRACE_DETACH, io->pid, 1, 0); +} + +bool +io_ptrace_init(struct io *io, const pid_t pid) +{ + *io = (struct io){ .pid = pid, .read = io_ptrace_read, .write = io_ptrace_write, .cleanup = io_ptrace_cleanup }; + + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1L) { + warn("ptrace(PTRACE_ATTACH, %u, NULL, NULL)", pid); + goto fail; + } + + { + int status; + if (waitpid(pid, &status, 0) == -1 || !WIFSTOPPED(status)) { + warn("waitpid(%u) == %d", pid, status); + goto fail; + } + } + + char path[128]; + snprintf(path, sizeof(path), "/proc/%u/mem", pid); + if (!(io->backing = fopen(path, "w+b"))) { + warn("fopen(%s)", path); + goto fail; + } + + return true; + +fail: + io->cleanup(io); + return false; +} diff --git a/src/io/io-uio.c b/src/io/io-uio.c new file mode 100644 index 0000000..dd9f873 --- /dev/null +++ b/src/io/io-uio.c @@ -0,0 +1,41 @@ +#include "io.h" +#include +#include +#include + +static size_t +io_uio_do(const struct io *io, const void *ptr, const size_t offset, const size_t size, ssize_t (*iofun)(pid_t, const struct iovec*, unsigned long, const struct iovec*, unsigned long, unsigned long)) +{ + const struct iovec lio = { .iov_base = (void*)ptr, .iov_len = size }; + const struct iovec rio = { .iov_base = (void*)(intptr_t)offset, .iov_len = size }; + return iofun(io->pid, &lio, 1, &rio, 1, 0); +} + +static size_t +io_uio_write(const struct io *io, const void *ptr, const size_t offset, const size_t size) +{ + const size_t ret = io_uio_do(io, ptr, offset, size, process_vm_writev); + + if (ret == (size_t)-1) + warn("process_vm_writev(%u)", io->pid); + + return (ret == (size_t)-1 ? 0 : ret); +} + +static size_t +io_uio_read(const struct io *io, void *ptr, const size_t offset, const size_t size) +{ + const size_t ret = io_uio_do(io, ptr, offset, size, process_vm_readv); + + if (ret == (size_t)-1) + warn("process_vm_readv(%u)", io->pid); + + return (ret == (size_t)-1 ? 0 : ret); +} + +bool +io_uio_init(struct io *io, const pid_t pid) +{ + *io = (struct io){ .pid = pid, .read = io_uio_read, .write = io_uio_write }; + return true; +} diff --git a/src/io/io.h b/src/io/io.h new file mode 100644 index 0000000..8a0639b --- /dev/null +++ b/src/io/io.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include // pid_t + +struct io { + void *backing; + size_t (*read)(const struct io *io, void *ptr, const size_t offset, const size_t size); + size_t (*write)(const struct io *io, const void *ptr, const size_t offset, const size_t size); + void (*cleanup)(struct io *io); + pid_t pid; +}; + +static inline void +io_release(struct io *io) +{ + if (io->cleanup) + io->cleanup(io); + *io = (struct io){0}; +} + +bool +io_uio_init(struct io *io, const pid_t pid); + +bool +io_ptrace_init(struct io *io, const pid_t pid); diff --git a/src/ptrace-region-rw.c b/src/ptrace-region-rw.c new file mode 100644 index 0000000..135c2a5 --- /dev/null +++ b/src/ptrace-region-rw.c @@ -0,0 +1,12 @@ +#include "cli/proc-region-rw.h" +#include "io/io.h" + +// This region-rw uses ptrace +// This works with older kernels, but it also ensures non racy read/writes, as it stops the process. +// It's recommended to `setcap cap_sys_ptrace=eip ptrace-region-rw` to run this tool without sudo + +int +main(int argc, const char *argv[]) +{ + return proc_region_rw(argc, argv, io_ptrace_init); +} diff --git a/src/uio-region-rw.c b/src/uio-region-rw.c new file mode 100644 index 0000000..96cac49 --- /dev/null +++ b/src/uio-region-rw.c @@ -0,0 +1,13 @@ +#include "cli/proc-region-rw.h" +#include "io/io.h" + +// This region-rw uses uio +// It needs recent kernel, but may be racy as it reads / writes while process is running. +// Ideal for realtime memory tools. +// It's recommended to `setcap cap_sys_ptrace=eip uio-region-rw` to run this tool without sudo + +int +main(int argc, const char *argv[]) +{ + return proc_region_rw(argc, argv, io_uio_init); +} -- cgit v1.2.3-70-g09d2