├── .gitignore ├── Makefile.linux ├── Makefile.mingw ├── Makefile.vc ├── README.md ├── UNLICENSE └── memdig.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.obj 3 | *.o 4 | memdig 5 | -------------------------------------------------------------------------------- /Makefile.linux: -------------------------------------------------------------------------------- 1 | CFLAGS = -std=c99 -Wall -Wextra -Os -g3 -pthread \ 2 | -Wno-missing-field-initializers -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE 3 | LDFLAGS = -pthread 4 | LDLIBS = -lm 5 | 6 | memdig : memdig.c 7 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) 8 | 9 | clean : 10 | $(RM) memdig 11 | -------------------------------------------------------------------------------- /Makefile.mingw: -------------------------------------------------------------------------------- 1 | CC = x86_64-w64-mingw32-gcc 2 | CFLAGS = -std=c99 -Wall -Wextra -Os \ 3 | -Wno-missing-field-initializers -D__USE_MINGW_ANSI_STDIO=1 4 | 5 | memdig.exe : memdig.c 6 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) 7 | 8 | clean : 9 | $(RM) *.exe 10 | -------------------------------------------------------------------------------- /Makefile.vc: -------------------------------------------------------------------------------- 1 | CFLAGS = -nologo -W4 -Ox -D_CRT_SECURE_NO_WARNINGS -wd4204 -wd4706 -wd4221 2 | 3 | memdig.exe : memdig.c 4 | 5 | clean : 6 | del *.exe *.obj 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MemDig: a memory cheat tool 2 | 3 | MemDig allows the user to manipulate the memory of another process, 4 | primary for the purposes of cheating. There have been many tools like 5 | this before, but this one is a scriptable command line program. 6 | 7 | There are a number of commands available from the program's command 8 | prompt. The "help" command provides a list with documentation. MemDig 9 | commands can also be supplied as command line arguments to the program 10 | itself, by prefixing them with one or two dashes. 11 | 12 | All commands can be shortened so long as they remain unambiguous, 13 | similar to gdb. For example, "attach" can be written as "a" or "att". 14 | 15 | The current set of commands is quite meager, though it can operate on 16 | integers and floats of any size. The command set will grow as more 17 | power is needed. 18 | 19 | ## Example Usage 20 | 21 | Here's how you might change the amount of gold in a game called 22 | Generic RPG. Suppose the process name is `grpg.exe` and you currently 23 | have 273 gold, stored as a 32-bit integer. 24 | 25 | memdig.exe --attach grpg.exe 26 | > find 273 27 | 317 values found 28 | 29 | (... perform an in-game action to change it to 312 gold ...) 30 | 31 | > narrow 312 32 | 1 value found 33 | > set 1000000 34 | 1 value set 35 | 36 | If all goes well, you would now have 1 million gold. 37 | 38 | The above could be scripted entirely as command arguments. 39 | 40 | memdig.exe -a grpg.exe -f 273 -w 10 -n 312 -s 1000000 -q 41 | 42 | The `-w 10` (i.e. `--wait 10`) will put a 10 second delay before the 43 | "narrow" command, giving you a chance to make changes to the game 44 | state. The `-q` (i.e. `--quit`) will exit the program before it beings 45 | the interactive prompt. 46 | 47 | ## Supported Types 48 | 49 | Suffixes can be used to set the type when searching memory. There are 50 | three integer width specifiers: byte (o), short (h), and quad (q), and 51 | each integer type is optionally unsigned (uo, uh, u, uq). For floating 52 | point, include a decimal or exponent in the normal format. An f suffix 53 | indicates single precision. 54 | 55 | * -45o (signed 8-bit) 56 | * 40000uh (unsigned 16-bit) 57 | * 0xffffq (unsigned 64-bit) 58 | * 10.0 (double) 59 | * 1e1f (float) 60 | 61 | ## Supported Platforms 62 | 63 | Currently Windows and Linux are supported. The platform API is fully 64 | abstracted, so support for additional platforms could be easily added. 65 | 66 | ## Future Plans 67 | 68 | * Remote, network interface 69 | * More values types (strings, SIMD) 70 | * Better handling of NaN and inf 71 | * Readline support (especially on Linux) 72 | * Automatic re-attach 73 | * Watchlist editing (add, remove) 74 | * Save/load address lists by name, to file 75 | * Address list transformations and filters 76 | * Progress indicator (find) 77 | * Symbol and region oriented commands (locate known addresses after ASLR) 78 | * (long shot) Export/create trainer EXE for a specific target 79 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /memdig.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* Platform API */ 6 | 7 | #define REGION_ITERATOR_DONE (1UL << 0) 8 | #define REGION_ITERATOR_READ (1UL << 1) 9 | #define REGION_ITERATOR_WRITE (1UL << 2) 10 | #define REGION_ITERATOR_EXECUTE (1UL << 3) 11 | 12 | #define PROCESS_ITERATOR_DONE (1UL << 0) 13 | 14 | #if 0 // (missing typedefs) 15 | struct process_iterator; 16 | int process_iterator_init(struct process_iterator *); 17 | int process_iterator_next(struct process_iterator *); 18 | int process_iterator_done(struct process_iterator *); 19 | void process_iterator_destroy(struct process_iterator *); 20 | 21 | struct region_iterator; 22 | int region_iterator_init(struct region_iterator *, os_handle); 23 | int region_iterator_next(struct region_iterator *); 24 | int region_iterator_done(struct region_iterator *); 25 | void *region_iterator_memory(struct region_iterator *); 26 | void region_iterator_destroy(struct region_iterator *); 27 | 28 | int os_write_memory(os_handle, uintptr_t, void *, size_t); 29 | void os_sleep(double); 30 | os_handle os_process_open(os_pid); 31 | void os_process_close(os_handle); 32 | const char *os_last_error(void); 33 | 34 | void os_thread_start(struct os_thread *, struct memdig *); 35 | void os_thread_join(struct os_thread *); 36 | void os_mutex_lock(struct os_thread *); 37 | void os_mutex_unlock(struct os_thread *); 38 | #endif 39 | 40 | /* MemDig API for platform */ 41 | 42 | struct memdig; 43 | static void memdig_locker(struct memdig *); 44 | 45 | /* Platform implementation */ 46 | 47 | #ifdef _WIN32 48 | #include 49 | #include 50 | #include 51 | 52 | typedef HANDLE os_handle; 53 | typedef DWORD os_pid; 54 | 55 | struct process_iterator { 56 | os_pid pid; 57 | char *name; 58 | unsigned long flags; 59 | 60 | // private 61 | HANDLE snapshot; 62 | char buf[MAX_PATH]; 63 | PROCESSENTRY32 entry; 64 | }; 65 | 66 | static int 67 | process_iterator_next(struct process_iterator *i) 68 | { 69 | if (Process32Next(i->snapshot, &i->entry)) { 70 | strcpy(i->name, i->entry.szExeFile); 71 | i->pid = i->entry.th32ProcessID; 72 | return !(i->flags = 0); 73 | } else { 74 | return !(i->flags = PROCESS_ITERATOR_DONE); 75 | } 76 | } 77 | 78 | static int 79 | process_iterator_init(struct process_iterator *i) 80 | { 81 | i->entry = (PROCESSENTRY32){sizeof(i->entry)}; 82 | i->flags = PROCESS_ITERATOR_DONE; 83 | i->name = i->buf; 84 | i->snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 85 | PROCESSENTRY32 entry[1] = {{sizeof(entry)}}; 86 | if (Process32First(i->snapshot, entry)) 87 | return process_iterator_next(i); 88 | else 89 | return 0; 90 | } 91 | 92 | static void 93 | process_iterator_destroy(struct process_iterator *i) 94 | { 95 | CloseHandle(i->snapshot); 96 | } 97 | 98 | struct region_iterator { 99 | uintptr_t base; 100 | size_t size; 101 | unsigned long flags; 102 | 103 | // private 104 | void *p; 105 | HANDLE process; 106 | void *buf; 107 | size_t bufsize; 108 | }; 109 | 110 | static int 111 | region_iterator_next(struct region_iterator *i) 112 | { 113 | MEMORY_BASIC_INFORMATION info[1]; 114 | for (;;) { 115 | if (VirtualQueryEx(i->process, i->p, info, sizeof(info))) { 116 | i->flags = 0; 117 | i->p = (char *)i->p + info->RegionSize; 118 | if (info->State == MEM_COMMIT) { 119 | i->size = info->RegionSize; 120 | i->base = (uintptr_t)info->AllocationBase; 121 | switch (info->AllocationProtect) { 122 | case PAGE_EXECUTE: 123 | i->flags |= REGION_ITERATOR_EXECUTE; 124 | break; 125 | case PAGE_EXECUTE_READ: 126 | i->flags |= REGION_ITERATOR_READ; 127 | i->flags |= REGION_ITERATOR_EXECUTE; 128 | break; 129 | case PAGE_EXECUTE_READWRITE: 130 | i->flags |= REGION_ITERATOR_READ; 131 | i->flags |= REGION_ITERATOR_WRITE; 132 | i->flags |= REGION_ITERATOR_EXECUTE; 133 | break; 134 | case PAGE_EXECUTE_WRITECOPY: 135 | i->flags |= REGION_ITERATOR_READ; 136 | i->flags |= REGION_ITERATOR_WRITE; 137 | i->flags |= REGION_ITERATOR_EXECUTE; 138 | break; 139 | case PAGE_READWRITE: 140 | i->flags |= REGION_ITERATOR_READ; 141 | i->flags |= REGION_ITERATOR_WRITE; 142 | break; 143 | case PAGE_READONLY: 144 | i->flags |= REGION_ITERATOR_READ; 145 | break; 146 | case PAGE_WRITECOPY: 147 | i->flags |= REGION_ITERATOR_READ; 148 | i->flags |= REGION_ITERATOR_WRITE; 149 | break; 150 | } 151 | break; 152 | } 153 | } else { 154 | i->flags = REGION_ITERATOR_DONE; 155 | break; 156 | } 157 | } 158 | return !(i->flags & REGION_ITERATOR_DONE); 159 | } 160 | 161 | static int 162 | region_iterator_init(struct region_iterator *i, os_handle process) 163 | { 164 | *i = (struct region_iterator){.process = process}; 165 | return region_iterator_next(i); 166 | } 167 | 168 | static const void * 169 | region_iterator_memory(struct region_iterator *i) 170 | { 171 | if (i->bufsize < i->size) { 172 | free(i->buf); 173 | i->bufsize = i->size; 174 | i->buf = malloc(i->bufsize); 175 | } 176 | SIZE_T actual; 177 | void *base = (void *)i->base; 178 | if (!ReadProcessMemory(i->process, base, i->buf, i->size, &actual)) 179 | return NULL; 180 | else if (actual < i->size) 181 | return NULL; 182 | return i->buf; 183 | } 184 | 185 | static void 186 | region_iterator_destroy(struct region_iterator *i) 187 | { 188 | free(i->buf); 189 | i->buf = NULL; 190 | } 191 | 192 | static int 193 | os_write_memory(os_handle target, uintptr_t base, void *buf, size_t bufsize) 194 | { 195 | SIZE_T actual; 196 | return WriteProcessMemory(target, (void *)base, buf, bufsize, &actual) 197 | && actual == bufsize; 198 | } 199 | 200 | static void 201 | os_sleep(double seconds) 202 | { 203 | DWORD ms = (DWORD)(seconds * 1000); 204 | Sleep(ms); 205 | } 206 | 207 | static os_handle 208 | os_process_open(os_pid id) 209 | { 210 | DWORD access = PROCESS_VM_READ | 211 | PROCESS_VM_WRITE | 212 | PROCESS_VM_OPERATION | 213 | PROCESS_QUERY_INFORMATION; 214 | HANDLE process = OpenProcess(access, 0, id); 215 | return process; 216 | } 217 | 218 | static void 219 | os_process_close(os_handle h) 220 | { 221 | CloseHandle(h); 222 | } 223 | 224 | static char error_buffer[4096]; 225 | 226 | static const char * 227 | os_last_error(void) 228 | { 229 | DWORD e = GetLastError(); 230 | SetLastError(0); 231 | DWORD lang = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 232 | DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; 233 | FormatMessage(flags, 0, e, lang, error_buffer, sizeof(error_buffer), 0); 234 | for (char *p = error_buffer; *p; p++) 235 | if (*p == '\n') 236 | *p = 0; 237 | return error_buffer; 238 | } 239 | 240 | struct os_thread { 241 | HANDLE thread; 242 | CRITICAL_SECTION mutex; 243 | }; 244 | 245 | static unsigned __stdcall 246 | os_stub(void *arg) 247 | { 248 | memdig_locker(arg); 249 | _endthreadex(0); 250 | } 251 | 252 | static void 253 | os_thread_start(struct os_thread *t, struct memdig *m) 254 | { 255 | InitializeCriticalSection(&t->mutex); 256 | t->thread = (HANDLE)_beginthreadex(0, 0, os_stub, m, 0, 0); 257 | } 258 | 259 | static void 260 | os_thread_join(struct os_thread *t) 261 | { 262 | WaitForSingleObject(t->thread, INFINITE); 263 | DeleteCriticalSection(&t->mutex); 264 | } 265 | 266 | static void 267 | os_mutex_lock(struct os_thread *t) 268 | { 269 | EnterCriticalSection(&t->mutex); 270 | } 271 | 272 | static void 273 | os_mutex_unlock(struct os_thread *t) 274 | { 275 | LeaveCriticalSection(&t->mutex); 276 | } 277 | 278 | #elif __linux__ 279 | #include 280 | #include 281 | #include 282 | #include 283 | 284 | #include 285 | #include 286 | #include 287 | 288 | typedef pid_t os_handle; 289 | typedef pid_t os_pid; 290 | 291 | struct process_iterator { 292 | os_pid pid; 293 | char *name; 294 | unsigned long flags; 295 | 296 | // private 297 | size_t i; 298 | size_t count; 299 | pid_t *pids; 300 | char buf[256]; 301 | }; 302 | 303 | static int 304 | process_iterator_next(struct process_iterator *i) 305 | { 306 | for (;;) { 307 | if (i->i == i->count) 308 | return !(i->flags = PROCESS_ITERATOR_DONE); 309 | i->pid = i->pids[i->i++]; 310 | sprintf(i->buf, "/proc/%ld/status", (long)i->pid); 311 | FILE *f = fopen(i->buf, "r"); 312 | if (f) { 313 | if (fgets(i->buf, sizeof(i->buf), f)) { 314 | char *p = i->buf; 315 | for (; *p && *p != '\t'; p++); 316 | i->name = p + (*p ? 1 : 0); 317 | for (; *p; p++) 318 | if (*p == '\n') 319 | *p = 0; 320 | fclose(f); 321 | return !(i->flags = 0); 322 | } else { 323 | fclose(f); 324 | } 325 | } 326 | } 327 | } 328 | 329 | static int 330 | pid_cmp(const void *a, const void *b) 331 | { 332 | return (int)*(pid_t *)a - (int)*(pid_t *)b; 333 | } 334 | 335 | static int 336 | process_iterator_init(struct process_iterator *i) 337 | { 338 | size_t size = 4096; 339 | *i = (struct process_iterator){ 340 | .pids = malloc(sizeof(i->pids[0]) * size), 341 | .flags = PROCESS_ITERATOR_DONE, 342 | }; 343 | DIR *dir = opendir("/proc"); 344 | if (!dir) 345 | return 0; 346 | struct dirent *e; 347 | while ((e = readdir(dir))) { 348 | int valid = 1; 349 | for (char *p = e->d_name; *p; p++) 350 | if (*p < '0' || *p > '9') 351 | valid = 0; 352 | if (valid) { 353 | if (i->count == size) { 354 | size *= 2; 355 | i->pids = realloc(i->pids, sizeof(i->pids[0]) * size); 356 | } 357 | i->pids[i->count++] = atoi(e->d_name); 358 | } 359 | } 360 | qsort(i->pids, i->count, sizeof(i->pids[0]), pid_cmp); 361 | closedir(dir); 362 | return process_iterator_next(i); 363 | } 364 | 365 | static void 366 | process_iterator_destroy(struct process_iterator *i) 367 | { 368 | free(i->pids); 369 | i->pids = NULL; 370 | } 371 | 372 | struct region_iterator { 373 | uintptr_t base; 374 | size_t size; 375 | unsigned long flags; 376 | 377 | //private 378 | pid_t pid; 379 | FILE *maps; 380 | char *buf; 381 | size_t bufsize; 382 | }; 383 | 384 | static int 385 | region_iterator_next(struct region_iterator *i) 386 | { 387 | char perms[8]; 388 | uintptr_t beg, end; 389 | int r = fscanf(i->maps, "%" SCNxPTR "-%" SCNxPTR " %7s", &beg, &end, perms); 390 | if (r != 3) { 391 | i->flags = REGION_ITERATOR_DONE; 392 | return 0; 393 | } 394 | int c; 395 | do 396 | c = fgetc(i->maps); 397 | while (c != '\n' && c != EOF); 398 | i->base = beg; 399 | i->size = end - beg; 400 | i->flags = 0; 401 | if (perms[0] == 'r') 402 | i->flags |= REGION_ITERATOR_READ; 403 | if (perms[1] == 'w') 404 | i->flags |= REGION_ITERATOR_WRITE; 405 | if (perms[2] == 'x') 406 | i->flags |= REGION_ITERATOR_EXECUTE; 407 | return 1; 408 | } 409 | 410 | static int 411 | region_iterator_init(struct region_iterator *i, os_handle pid) 412 | { 413 | i->flags = REGION_ITERATOR_DONE; 414 | char file[256]; 415 | sprintf(file, "/proc/%ld/maps", (long)pid); 416 | FILE *maps = fopen(file, "r"); 417 | if (!maps) 418 | return 0; 419 | *i = (struct region_iterator){ 420 | .pid = pid, 421 | .maps = maps, 422 | }; 423 | return region_iterator_next(i); 424 | } 425 | 426 | static const void * 427 | region_iterator_memory(struct region_iterator *i) 428 | { 429 | if (i->bufsize < i->size) { 430 | free(i->buf); 431 | i->bufsize = i->size; 432 | i->buf = malloc(i->bufsize); 433 | } 434 | struct iovec local = { 435 | .iov_base = i->buf, 436 | .iov_len = i->size, 437 | }; 438 | struct iovec remote = { 439 | .iov_base = (void *)i->base, 440 | .iov_len = i->size, 441 | }; 442 | ssize_t in = process_vm_readv(i->pid, &local, 1, &remote, 1, 0); 443 | return (in >= 0 && (size_t)in == i->size) ? i->buf : NULL; 444 | } 445 | 446 | static void 447 | region_iterator_destroy(struct region_iterator *i) 448 | { 449 | fclose(i->maps); 450 | free(i->buf); 451 | i->buf = NULL; 452 | } 453 | 454 | static int 455 | os_write_memory(os_handle pid, uintptr_t addr, void *buf, size_t bufsize) 456 | { 457 | struct iovec local = { 458 | .iov_base = buf, 459 | .iov_len = bufsize, 460 | }; 461 | struct iovec remote = { 462 | .iov_base = (void *)addr, 463 | .iov_len = bufsize, 464 | }; 465 | ssize_t out = process_vm_writev(pid, &local, 1, &remote, 1, 0); 466 | return out >= 0 && (size_t)out == bufsize; 467 | } 468 | 469 | static void 470 | os_sleep(double s) 471 | { 472 | struct timespec ts = { 473 | .tv_sec = s, 474 | .tv_nsec = (s - trunc(s)) * 1e9, 475 | }; 476 | nanosleep(&ts, 0); 477 | } 478 | 479 | static os_handle 480 | os_process_open(os_pid pid) 481 | { 482 | return pid; // TODO: verify capability 483 | } 484 | 485 | static void 486 | os_process_close(os_handle h) 487 | { 488 | (void)h; // nothing to do 489 | } 490 | 491 | const char * 492 | os_last_error(void) 493 | { 494 | return strerror(errno); 495 | } 496 | 497 | struct os_thread { 498 | pthread_t thread; 499 | pthread_mutex_t mutex; 500 | }; 501 | 502 | static void * 503 | os_stub(void *arg) 504 | { 505 | memdig_locker(arg); 506 | return NULL; 507 | } 508 | 509 | static void 510 | os_thread_start(struct os_thread *t, struct memdig *m) 511 | { 512 | pthread_mutex_init(&t->mutex, 0); 513 | pthread_create(&t->thread, 0, os_stub, m); 514 | } 515 | 516 | static void 517 | os_thread_join(struct os_thread *t) 518 | { 519 | pthread_join(t->thread, 0); 520 | pthread_mutex_destroy(&t->mutex); 521 | } 522 | 523 | static void 524 | os_mutex_lock(struct os_thread *t) 525 | { 526 | pthread_mutex_lock(&t->mutex); 527 | } 528 | 529 | static void 530 | os_mutex_unlock(struct os_thread *t) 531 | { 532 | pthread_mutex_unlock(&t->mutex); 533 | } 534 | 535 | #endif // __linux__ 536 | 537 | static int 538 | process_iterator_done(struct process_iterator *i) 539 | { 540 | return !!(i->flags & PROCESS_ITERATOR_DONE); 541 | } 542 | 543 | static int 544 | region_iterator_done(struct region_iterator *i) 545 | { 546 | return !!(i->flags & REGION_ITERATOR_DONE); 547 | } 548 | 549 | /* Logging */ 550 | 551 | static enum loglevel { 552 | LOGLEVEL_DEBUG = -2, 553 | LOGLEVEL_INFO = -1, 554 | LOGLEVEL_WARNING = 0, 555 | LOGLEVEL_ERROR = 1, 556 | } loglevel = 0; 557 | 558 | #define FATAL(...) \ 559 | do { \ 560 | fprintf(stderr, "memdig: " __VA_ARGS__); \ 561 | exit(-1); \ 562 | } while (0) 563 | 564 | #define LOG_ERROR(...) \ 565 | do { \ 566 | if (loglevel <= LOGLEVEL_ERROR) \ 567 | fprintf(stderr, "error: " __VA_ARGS__); \ 568 | goto fail; \ 569 | } while (0) 570 | 571 | #define LOG_WARNING(...) \ 572 | do { \ 573 | if (loglevel <= LOGLEVEL_WARNING) \ 574 | fprintf(stderr, "warning: " __VA_ARGS__); \ 575 | } while (0) 576 | 577 | #define LOG_INFO(...) \ 578 | do { \ 579 | if (loglevel <= LOGLEVEL_INFO) \ 580 | fprintf(stderr, "info: " __VA_ARGS__); \ 581 | } while (0) 582 | 583 | #define LOG_DEBUG(...) \ 584 | do { \ 585 | if (loglevel <= LOGLEVEL_DEBUG) \ 586 | fprintf(stderr, "debug: " __VA_ARGS__); \ 587 | } while (0) 588 | 589 | /* MemDig's operatable types */ 590 | 591 | enum value_type { 592 | VALUE_S8, 593 | VALUE_U8, 594 | VALUE_S16, 595 | VALUE_U16, 596 | VALUE_S32, 597 | VALUE_U32, 598 | VALUE_S64, 599 | VALUE_U64, 600 | VALUE_F32, 601 | VALUE_F64, 602 | }; 603 | 604 | struct value { 605 | enum value_type type; 606 | union { 607 | int8_t s8; 608 | uint8_t u8; 609 | int16_t s16; 610 | uint16_t u16; 611 | int32_t s32; 612 | uint32_t u32; 613 | int64_t s64; 614 | uint64_t u64; 615 | float f32; 616 | double f64; 617 | } value; 618 | }; 619 | 620 | #define VALUE_SIZE(v) ("bbcceeiiei"[(v).type] - 'a') 621 | 622 | enum value_parse_result { 623 | VALUE_PARSE_SUCCESS, 624 | VALUE_PARSE_OVERFLOW, 625 | VALUE_PARSE_INVALID, 626 | }; 627 | 628 | static enum value_parse_result 629 | value_parse(struct value *v, const char *arg) 630 | { 631 | int base = 10; 632 | int is_signed = 1; 633 | int is_integer = 1; 634 | size_t len = strlen(arg); 635 | const char *suffix = arg + len; 636 | char digits[] = "0123456789abcdef"; 637 | 638 | /* Check prefix to determine base and sign. */ 639 | if (arg[0] == '0' && arg[1] == 'x') { 640 | is_signed = 0; 641 | base = 16; 642 | arg += 2; 643 | len -= 2; 644 | } else if (arg[0] == '0') { 645 | is_signed = 0; 646 | base = 8; 647 | arg += 1; 648 | len -= 1; 649 | } 650 | digits[base] = 0; 651 | 652 | /* Find the suffix. */ 653 | while (suffix > arg && !strchr(digits, suffix[-1])) 654 | suffix--; 655 | 656 | /* Check for an integer. */ 657 | for (const char *p = arg; p < suffix; p++) { 658 | if (p == arg && *p == '-') 659 | p++; 660 | if (!strchr(digits, *p)) 661 | is_integer = 0; 662 | } 663 | if (is_integer && suffix[0] == 'u') { 664 | is_signed = 0; 665 | suffix++; 666 | } 667 | 668 | /* Parse the in-between. */ 669 | if (is_integer) { 670 | errno = 0; 671 | if (is_signed) { 672 | intmax_t s = strtoimax(arg, 0, base); 673 | if (errno) 674 | return VALUE_PARSE_OVERFLOW; 675 | switch (suffix[0]) { 676 | case 'o': 677 | v->type = VALUE_S8; 678 | if (s > INT8_MAX || s < INT8_MIN) 679 | return VALUE_PARSE_OVERFLOW; 680 | v->value.s8 = (int8_t)s; 681 | return VALUE_PARSE_SUCCESS; 682 | case 'h': 683 | v->type = VALUE_S16; 684 | if (s > INT16_MAX || s < INT16_MIN) 685 | return VALUE_PARSE_OVERFLOW; 686 | v->value.s16 = (int16_t)s; 687 | return VALUE_PARSE_SUCCESS; 688 | case 0: 689 | v->type = VALUE_S32; 690 | if (s > INT32_MAX || s < INT32_MIN) 691 | return VALUE_PARSE_OVERFLOW; 692 | v->value.s32 = (int32_t)s; 693 | return VALUE_PARSE_SUCCESS; 694 | case 'q': 695 | v->type = VALUE_S64; 696 | if (s > INT64_MAX || s < INT64_MIN) 697 | return VALUE_PARSE_OVERFLOW; 698 | v->value.s64 = (int64_t)s; 699 | return VALUE_PARSE_SUCCESS; 700 | } 701 | } else { 702 | uintmax_t u = strtoumax(arg, 0, base); 703 | if (errno) 704 | return VALUE_PARSE_OVERFLOW; 705 | switch (suffix[0]) { 706 | case 'o': 707 | v->type = VALUE_U8; 708 | if (u > UINT8_MAX) 709 | return VALUE_PARSE_OVERFLOW; 710 | v->value.u8 = (uint8_t)u; 711 | return VALUE_PARSE_SUCCESS; 712 | case 'h': 713 | v->type = VALUE_U16; 714 | if (u > UINT16_MAX) 715 | return VALUE_PARSE_OVERFLOW; 716 | v->value.u16 = (uint16_t)u; 717 | return VALUE_PARSE_SUCCESS; 718 | case 0: 719 | v->type = VALUE_U32; 720 | if (u > UINT32_MAX) 721 | return VALUE_PARSE_OVERFLOW; 722 | v->value.u32 = (uint32_t)u; 723 | return VALUE_PARSE_SUCCESS; 724 | case 'q': 725 | v->type = VALUE_U64; 726 | if (u > UINT64_MAX) 727 | return VALUE_PARSE_OVERFLOW; 728 | v->value.u64 = (uint64_t)u; 729 | return VALUE_PARSE_SUCCESS; 730 | } 731 | } 732 | } else { /* float */ 733 | errno = 0; 734 | char *end; 735 | switch (suffix[0]) { 736 | case 'f': 737 | v->type = VALUE_F32; 738 | v->value.f32 = strtof(arg, &end); 739 | if (end == suffix) 740 | return VALUE_PARSE_SUCCESS; 741 | break; 742 | case 0: 743 | v->type = VALUE_F64; 744 | v->value.f64 = strtod(arg, &end); 745 | if (end == suffix) 746 | return VALUE_PARSE_SUCCESS; 747 | break; 748 | } 749 | } 750 | return VALUE_PARSE_INVALID; 751 | } 752 | 753 | static void 754 | value_print(char *buf, size_t n, const struct value *v) 755 | { 756 | switch (v->type) { 757 | case VALUE_S8: { 758 | snprintf(buf, n, "%" PRId8 "o", v->value.s8); 759 | } return; 760 | case VALUE_U8: { 761 | snprintf(buf, n, "%" PRIu8 "uo", v->value.u8); 762 | } return; 763 | case VALUE_S16: { 764 | snprintf(buf, n, "%" PRId16 "h", v->value.s16); 765 | } return; 766 | case VALUE_U16: { 767 | snprintf(buf, n, "%" PRIu16 "uh", v->value.u16); 768 | } return; 769 | case VALUE_S32: { 770 | snprintf(buf, n, "%" PRId32 "", v->value.s32); 771 | } return; 772 | case VALUE_U32: { 773 | snprintf(buf, n, "%" PRIu32 "u", v->value.u32); 774 | } return; 775 | case VALUE_S64: { 776 | snprintf(buf, n, "%" PRId64 "q", v->value.s64); 777 | } return; 778 | case VALUE_U64: { 779 | snprintf(buf, n, "%" PRIu64 "uq", v->value.u64); 780 | } return; 781 | case VALUE_F32: { 782 | snprintf(buf, n, "%.9gf", v->value.f32); 783 | } return; 784 | case VALUE_F64: { 785 | snprintf(buf, n, "%.17g", v->value.f64); 786 | } return; 787 | } 788 | abort(); 789 | } 790 | 791 | static void 792 | value_read(struct value *v, enum value_type t, const void *p) 793 | { 794 | v->type = t; 795 | switch (v->type) { 796 | case VALUE_S8: { 797 | memcpy(&v->value.s8, p, sizeof(v->value.s8)); 798 | } return; 799 | case VALUE_U8: { 800 | memcpy(&v->value.u8, p, sizeof(v->value.u8)); 801 | } return; 802 | case VALUE_S16: { 803 | memcpy(&v->value.s16, p, sizeof(v->value.s16)); 804 | } return; 805 | case VALUE_U16: { 806 | memcpy(&v->value.u16, p, sizeof(v->value.u16)); 807 | } return; 808 | case VALUE_S32: { 809 | memcpy(&v->value.s32, p, sizeof(v->value.s32)); 810 | } return; 811 | case VALUE_U32: { 812 | memcpy(&v->value.u32, p, sizeof(v->value.u32)); 813 | } return; 814 | case VALUE_S64: { 815 | memcpy(&v->value.s64, p, sizeof(v->value.s64)); 816 | } return; 817 | case VALUE_U64: { 818 | memcpy(&v->value.u64, p, sizeof(v->value.u64)); 819 | } return; 820 | case VALUE_F32: { 821 | memcpy(&v->value.f32, p, sizeof(v->value.f32)); 822 | } return; 823 | case VALUE_F64: { 824 | memcpy(&v->value.f64, p, sizeof(v->value.f64)); 825 | } return; 826 | } 827 | abort(); 828 | } 829 | 830 | #define VALUE_COMPARE(a, b) ((a) < (b) ? -1 : (b) < (a) ? 1 : 0) 831 | 832 | static int 833 | value_compare(const struct value *a, const struct value *b) 834 | { 835 | if (a->type != b->type) 836 | return a->type - b->type; 837 | switch (a->type) { 838 | case VALUE_S8: 839 | return VALUE_COMPARE(a->value.s8, b->value.s8); 840 | case VALUE_U8: 841 | return VALUE_COMPARE(a->value.u8, b->value.u8); 842 | case VALUE_S16: 843 | return VALUE_COMPARE(a->value.s16, b->value.s16); 844 | case VALUE_U16: 845 | return VALUE_COMPARE(a->value.u16, b->value.u16); 846 | case VALUE_S32: 847 | return VALUE_COMPARE(a->value.s32, b->value.s32); 848 | case VALUE_U32: 849 | return VALUE_COMPARE(a->value.u32, b->value.u32); 850 | case VALUE_S64: 851 | return VALUE_COMPARE(a->value.s64, b->value.s64); 852 | case VALUE_U64: 853 | return VALUE_COMPARE(a->value.u64, b->value.u64); 854 | case VALUE_F32: 855 | return VALUE_COMPARE(a->value.f32, b->value.f32); 856 | case VALUE_F64: 857 | return VALUE_COMPARE(a->value.f64, b->value.f64); 858 | } 859 | abort(); 860 | } 861 | 862 | /* Watchlist */ 863 | 864 | struct watchlist { 865 | os_handle process; 866 | size_t count; 867 | size_t size; 868 | struct { 869 | uintptr_t addr; 870 | struct value prev; 871 | } *list; 872 | }; 873 | 874 | static void 875 | watchlist_init(struct watchlist *s, os_handle process) 876 | { 877 | s->process = process; 878 | s->size = 4096; 879 | s->count = 0; 880 | s->list = malloc(s->size * sizeof(s->list[0])); 881 | } 882 | 883 | static void 884 | watchlist_push(struct watchlist *s, uintptr_t a, const struct value *v) 885 | { 886 | if (s->count == s->size) { 887 | s->size *= 2; 888 | s->list = realloc(s->list, s->size * sizeof(s->list[0])); 889 | } 890 | s->list[s->count].addr = a; 891 | s->list[s->count].prev = *v; 892 | s->count++; 893 | } 894 | 895 | static void 896 | watchlist_free(struct watchlist *s) 897 | { 898 | free(s->list); 899 | s->list = NULL; 900 | } 901 | 902 | static void 903 | watchlist_clear(struct watchlist *s) 904 | { 905 | s->count = 0; 906 | } 907 | 908 | /* Memory scanning */ 909 | 910 | enum scan_op { 911 | SCAN_OP_EQ, 912 | SCAN_OP_LT, 913 | SCAN_OP_GT, 914 | SCAN_OP_LTEG, 915 | SCAN_OP_GTEQ, 916 | }; 917 | 918 | static int 919 | scan_op_parse(const char *s, enum scan_op *op) 920 | { 921 | static const struct { 922 | char name[3]; 923 | enum scan_op op; 924 | } table[] = { 925 | {"=", SCAN_OP_EQ}, 926 | {"<", SCAN_OP_LT}, 927 | {">", SCAN_OP_GT}, 928 | {"<=", SCAN_OP_LTEG}, 929 | {">=", SCAN_OP_GTEQ}, 930 | }; 931 | for (unsigned i = 0; i < sizeof(table) / sizeof(table[0]); i++) 932 | if (strcmp(table[i].name, s) == 0) { 933 | *op = table[i].op; 934 | return 1; 935 | } 936 | return 0; 937 | } 938 | 939 | static int 940 | scan(struct watchlist *wl, struct value *v, enum scan_op op) 941 | { 942 | unsigned value_size = VALUE_SIZE(*v); 943 | enum value_type type = v->type; 944 | watchlist_clear(wl); 945 | struct region_iterator it[1]; 946 | region_iterator_init(it, wl->process); 947 | for (; !region_iterator_done(it); region_iterator_next(it)) { 948 | const char *buf; 949 | if ((buf = region_iterator_memory(it))) { 950 | size_t count = it->size / value_size; 951 | for (size_t i = 0; i < count; i++) { 952 | struct value read; 953 | value_read(&read, type, buf + i * value_size); 954 | int cmp = value_compare(&read, v); 955 | int pass = 0; 956 | switch (op) { 957 | case SCAN_OP_EQ: 958 | pass = cmp == 0; 959 | break; 960 | case SCAN_OP_LT: 961 | pass = cmp < 0; 962 | break; 963 | case SCAN_OP_GT: 964 | pass = cmp > 0; 965 | break; 966 | case SCAN_OP_LTEG: 967 | pass = cmp <= 0; 968 | break; 969 | case SCAN_OP_GTEQ: 970 | pass = cmp >= 0; 971 | break; 972 | } 973 | if (pass) { 974 | uintptr_t addr = it->base + i * value_size; 975 | watchlist_push(wl, addr, &read); 976 | } 977 | } 978 | } else { 979 | LOG_DEBUG("memory read failed [0x%016" PRIxPTR "]: %s\n", 980 | it->base, os_last_error()); 981 | } 982 | } 983 | region_iterator_destroy(it); 984 | return 1; 985 | } 986 | 987 | typedef void (*watchlist_visitor)(uintptr_t, const struct value *, void *); 988 | 989 | static void 990 | watchlist_visit(struct watchlist *wl, watchlist_visitor f, void *arg) 991 | { 992 | struct region_iterator it[1]; 993 | region_iterator_init(it, wl->process); 994 | size_t n = 0; 995 | for (; !region_iterator_done(it) && n < wl->count; region_iterator_next(it)) { 996 | const char *buf = NULL; 997 | uintptr_t base = it->base; 998 | uintptr_t tail = base + it->size; 999 | while (n < wl->count && wl->list[n].addr < base) 1000 | n++; 1001 | while (n < wl->count && wl->list[n].addr >= base && wl->list[n].addr < tail) { 1002 | if (!buf) 1003 | buf = region_iterator_memory(it); 1004 | uintptr_t addr = wl->list[n].addr; 1005 | enum value_type type = wl->list[n].prev.type; 1006 | size_t offset = wl->list[n].addr - base; 1007 | if (buf) { 1008 | struct value value; 1009 | value_read(&value, type, buf + offset); 1010 | f(addr, &value, arg); 1011 | } else { 1012 | f(addr, NULL, arg); 1013 | } 1014 | n++; 1015 | } 1016 | } 1017 | region_iterator_destroy(it); 1018 | } 1019 | 1020 | struct narrow_visitor_state { 1021 | struct watchlist *wl; 1022 | struct value target; 1023 | enum scan_op op; 1024 | }; 1025 | 1026 | static void 1027 | narrow_visitor(uintptr_t addr, const struct value *v, void *arg) 1028 | { 1029 | char buf[64]; 1030 | value_print(buf, sizeof(buf), v); 1031 | struct narrow_visitor_state *s = arg; 1032 | int cmp = value_compare(v, &s->target); 1033 | int pass = 0; 1034 | switch (s->op) { 1035 | case SCAN_OP_EQ: 1036 | pass = cmp == 0; 1037 | break; 1038 | case SCAN_OP_LT: 1039 | pass = cmp < 0; 1040 | break; 1041 | case SCAN_OP_GT: 1042 | pass = cmp > 0; 1043 | break; 1044 | case SCAN_OP_LTEG: 1045 | pass = cmp <= 0; 1046 | break; 1047 | case SCAN_OP_GTEQ: 1048 | pass = cmp >= 0; 1049 | break; 1050 | } 1051 | if (pass) 1052 | watchlist_push(s->wl, addr, v); 1053 | } 1054 | 1055 | static int 1056 | narrow(struct watchlist *wl, enum scan_op op, struct value *v) 1057 | { 1058 | struct watchlist out[1]; 1059 | watchlist_init(out, wl->process); 1060 | struct narrow_visitor_state state = { 1061 | .wl = out, 1062 | .target = *v, 1063 | .op = op, 1064 | }; 1065 | watchlist_visit(wl, narrow_visitor, &state); 1066 | watchlist_free(wl); 1067 | *wl = *out; 1068 | return 1; 1069 | } 1070 | 1071 | static void 1072 | display_memory_regions(os_handle target) 1073 | { 1074 | struct region_iterator it[1]; 1075 | region_iterator_init(it, target); 1076 | for (; !region_iterator_done(it); region_iterator_next(it)) { 1077 | char protect[4] = { 1078 | it->flags & REGION_ITERATOR_READ ? 'R' : ' ', 1079 | it->flags & REGION_ITERATOR_WRITE ? 'W' : ' ', 1080 | it->flags & REGION_ITERATOR_EXECUTE ? 'X' : ' ', 1081 | }; 1082 | uintptr_t tail = it->base + it->size; 1083 | printf("%s 0x%016" PRIxPTR " 0x%016" PRIxPTR " %10zu bytes\n", 1084 | protect, it->base, tail, it->size); 1085 | } 1086 | region_iterator_destroy(it); 1087 | } 1088 | 1089 | /* Processes */ 1090 | 1091 | enum find_result { 1092 | FIND_SUCCESS, 1093 | FIND_FAILURE, 1094 | FIND_AMBIGUOUS, 1095 | }; 1096 | 1097 | static os_pid 1098 | process_find(const char *pattern, os_pid *pid) 1099 | { 1100 | struct process_iterator it[1]; 1101 | process_iterator_init(it); 1102 | *pid = 0; 1103 | os_pid target_pid = 0; 1104 | if (pattern[0] == ':') 1105 | target_pid = strtol(pattern + 1, NULL, 10); 1106 | for (; !process_iterator_done(it); process_iterator_next(it)) { 1107 | if (target_pid == it->pid || strstr(it->name, pattern)) { 1108 | if (*pid) 1109 | return FIND_AMBIGUOUS; 1110 | *pid = it->pid; 1111 | } 1112 | } 1113 | process_iterator_destroy(it); 1114 | if (!*pid) 1115 | return FIND_FAILURE; 1116 | return FIND_SUCCESS; 1117 | } 1118 | 1119 | /* Command processing */ 1120 | 1121 | enum command { 1122 | COMMAND_AMBIGUOUS = -2, 1123 | COMMAND_UNKNOWN = -1, 1124 | COMMAND_ATTACH = 0, 1125 | COMMAND_MEMORY, 1126 | COMMAND_FIND, 1127 | COMMAND_NARROW, 1128 | COMMAND_PUSH, 1129 | COMMAND_LIST, 1130 | COMMAND_SET, 1131 | COMMAND_LOCK, 1132 | COMMAND_WAIT, 1133 | COMMAND_HELP, 1134 | COMMAND_QUIT, 1135 | }; 1136 | 1137 | static struct { 1138 | const char *name; 1139 | const char *help; 1140 | const char *args; 1141 | } command_info[] = { 1142 | [COMMAND_ATTACH] = { 1143 | "attach", "select a new target process", 1144 | "[:pid|pattern]" 1145 | }, 1146 | [COMMAND_MEMORY] = { 1147 | "memory", "list committed memory regions", 1148 | 0 1149 | }, 1150 | [COMMAND_FIND] = { 1151 | "find", "find and remember integral memory values", 1152 | "[<|>|=|<=|>=] " 1153 | }, 1154 | [COMMAND_NARROW] = { 1155 | "narrow", "filter the current list of addresses", 1156 | "[<|>|=|<=|>=] " 1157 | }, 1158 | [COMMAND_PUSH] = { 1159 | "push", "manually add address to list", 1160 | "
" 1161 | }, 1162 | [COMMAND_LIST] = { 1163 | "list", "show the current address list", 1164 | "[proc|addr|lock]" 1165 | }, 1166 | [COMMAND_SET] = { 1167 | "set", "set memory at each listed address", 1168 | "" 1169 | }, 1170 | [COMMAND_LOCK] = { 1171 | "lock", "lock the memory at each listed address", 1172 | "[new value]" 1173 | }, 1174 | [COMMAND_WAIT] = { 1175 | "wait", "wait a fractional number of seconds", 1176 | "" 1177 | }, 1178 | [COMMAND_HELP] = { 1179 | "help", "print this help information", 1180 | 0}, 1181 | [COMMAND_QUIT] = { 1182 | "quit", "exit the program", 1183 | 0}, 1184 | }; 1185 | 1186 | static enum command 1187 | command_parse(const char *c) 1188 | { 1189 | unsigned n = sizeof(command_info) / sizeof(command_info[0]); 1190 | size_t len = strlen(c); 1191 | enum command command = COMMAND_UNKNOWN; 1192 | for (unsigned i = 0; i < n; i++) 1193 | if (strncmp(command_info[i].name, c, len) == 0) { 1194 | if (command != COMMAND_UNKNOWN) 1195 | return COMMAND_AMBIGUOUS; 1196 | else 1197 | command = i; 1198 | } 1199 | return command; 1200 | } 1201 | 1202 | /* High level MemDig API */ 1203 | 1204 | struct memdig { 1205 | os_pid id; 1206 | os_handle target; 1207 | struct os_thread thread; 1208 | enum value_type last_type; 1209 | struct watchlist active; 1210 | struct watchlist locked; 1211 | int running; 1212 | }; 1213 | 1214 | static void 1215 | memdig_locker(struct memdig *m) 1216 | { 1217 | for (;;) { 1218 | os_sleep(0.1); 1219 | os_mutex_lock(&m->thread); 1220 | if (!m->running) { 1221 | os_mutex_unlock(&m->thread); 1222 | break; 1223 | } 1224 | if (m->target) 1225 | for (size_t i = 0; i < m->locked.count; i++) { 1226 | uintptr_t addr = m->locked.list[i].addr; 1227 | struct value *value = &m->locked.list[i].prev; 1228 | unsigned size = VALUE_SIZE(*value); 1229 | os_write_memory(m->target, addr, &value->value, size); 1230 | } 1231 | os_mutex_unlock(&m->thread); 1232 | } 1233 | } 1234 | 1235 | static void 1236 | memdig_init(struct memdig *m) 1237 | { 1238 | *m = (struct memdig){ 1239 | .last_type = VALUE_S32, 1240 | .running = 1, 1241 | }; 1242 | os_thread_start(&m->thread, m); 1243 | } 1244 | 1245 | static void 1246 | list_visitor(uintptr_t addr, const struct value *v, void *file) 1247 | { 1248 | char buf[64] = "???"; 1249 | if (v) 1250 | value_print(buf, sizeof(buf), v); 1251 | fprintf(file, "0x%016" PRIxPTR " %s\n", addr, buf); 1252 | } 1253 | 1254 | enum memdig_result { 1255 | MEMDIG_RESULT_ERROR = -1, 1256 | MEMDIG_RESULT_OK = 0, 1257 | MEMDIG_RESULT_QUIT = 1, 1258 | }; 1259 | 1260 | static enum memdig_result 1261 | memdig_exec(struct memdig *m, int argc, char **argv) 1262 | { 1263 | if (argc == 0) 1264 | return MEMDIG_RESULT_OK; 1265 | char *verb = argv[0]; 1266 | enum command command = command_parse(verb); 1267 | switch (command) { 1268 | case COMMAND_AMBIGUOUS: { 1269 | LOG_ERROR("ambiguous command '%s'\n", verb); 1270 | } break; 1271 | case COMMAND_UNKNOWN: { 1272 | LOG_ERROR("unknown command '%s'\n", verb); 1273 | } break; 1274 | case COMMAND_ATTACH: { 1275 | if (argc == 1) { 1276 | if (m->target) 1277 | printf("attached to %ld\n", (long)m->id); 1278 | else 1279 | printf("not attached to a process\n"); 1280 | return MEMDIG_RESULT_OK; 1281 | } else if (argc != 2) 1282 | LOG_ERROR("wrong number of arguments\n"); 1283 | if (m->target) { 1284 | os_mutex_lock(&m->thread); 1285 | watchlist_free(&m->active); 1286 | watchlist_free(&m->locked); 1287 | os_process_close(m->target); 1288 | m->target = 0; 1289 | os_mutex_unlock(&m->thread); 1290 | } 1291 | char *pattern = argv[1]; 1292 | switch (process_find(pattern, &m->id)) { 1293 | case FIND_FAILURE: 1294 | LOG_ERROR("no process found for '%s'\n", pattern); 1295 | break; 1296 | case FIND_AMBIGUOUS: 1297 | LOG_ERROR("ambiguous target '%s'\n", pattern); 1298 | break; 1299 | case FIND_SUCCESS: 1300 | os_mutex_lock(&m->thread); 1301 | if (!(m->target = os_process_open(m->id))) { 1302 | if (!m->target) 1303 | LOG_ERROR("open process %ld failed: %s\n", 1304 | (long)m->id, os_last_error()); 1305 | m->id = 0; 1306 | } else { 1307 | watchlist_init(&m->active, m->target); 1308 | watchlist_init(&m->locked, m->target); 1309 | printf("attached to %ld\n", (long)m->id); 1310 | } 1311 | os_mutex_unlock(&m->thread); 1312 | break; 1313 | } 1314 | } break; 1315 | case COMMAND_MEMORY: { 1316 | if (!m->target) 1317 | LOG_ERROR("no process attached\n"); 1318 | display_memory_regions(m->target); 1319 | } break; 1320 | case COMMAND_FIND: { 1321 | if (!m->target) 1322 | LOG_ERROR("no process attached\n"); 1323 | if (argc < 2 || argc > 3) 1324 | LOG_ERROR("wrong number of arguments\n"); 1325 | enum scan_op op = SCAN_OP_EQ; 1326 | const char *arg = argv[1]; 1327 | if (argc == 3) { 1328 | if (!scan_op_parse(argv[1], &op)) 1329 | LOG_ERROR("invalid operator '%s'\n", argv[1]); 1330 | arg = argv[2]; 1331 | } 1332 | struct value value; 1333 | enum value_parse_result r = value_parse(&value, arg); 1334 | switch (r) { 1335 | case VALUE_PARSE_OVERFLOW: { 1336 | LOG_ERROR("overflow '%s'\n", arg); 1337 | } break; 1338 | case VALUE_PARSE_INVALID: { 1339 | LOG_ERROR("invalid value '%s'\n", arg); 1340 | } break; 1341 | case VALUE_PARSE_SUCCESS: { 1342 | char buf[64]; 1343 | value_print(buf, sizeof(buf), &value); 1344 | LOG_INFO("finding %s\n", buf); 1345 | } break; 1346 | } 1347 | m->last_type = value.type; 1348 | if (!scan(&m->active, &value, op)) 1349 | LOG_ERROR("scan failure'\n"); 1350 | else 1351 | printf("%zu values found\n", m->active.count); 1352 | } break; 1353 | case COMMAND_NARROW: { 1354 | if (!m->target) 1355 | LOG_ERROR("no process attached\n"); 1356 | if (argc < 2 || argc > 3) 1357 | LOG_ERROR("wrong number of arguments\n"); 1358 | enum scan_op op = SCAN_OP_EQ; 1359 | const char *arg = argv[1]; 1360 | if (argc == 3) { 1361 | if (!scan_op_parse(argv[1], &op)) 1362 | LOG_ERROR("invalid operator '%s'\n", argv[1]); 1363 | arg = argv[2]; 1364 | } 1365 | struct value value; 1366 | enum value_parse_result r = value_parse(&value, arg); 1367 | switch (r) { 1368 | case VALUE_PARSE_OVERFLOW: { 1369 | LOG_ERROR("overflow '%s'\n", arg); 1370 | } break; 1371 | case VALUE_PARSE_INVALID: { 1372 | LOG_ERROR("invalid value '%s'\n", arg); 1373 | } break; 1374 | case VALUE_PARSE_SUCCESS: { 1375 | char buf[64]; 1376 | value_print(buf, sizeof(buf), &value); 1377 | LOG_INFO("narrowing to %s\n", buf); 1378 | } break; 1379 | } 1380 | 1381 | if (!narrow(&m->active, op, &value)) 1382 | LOG_ERROR("scan failure'\n"); 1383 | else 1384 | printf("%zu values found\n", m->active.count); 1385 | } break; 1386 | case COMMAND_PUSH: { 1387 | if (!m->target) 1388 | LOG_ERROR("no process attached\n"); 1389 | if (argc != 2) 1390 | LOG_ERROR("wrong number of arguments"); 1391 | char *addrs = argv[1]; 1392 | if (strncmp(addrs, "0x", 2) != 0) 1393 | LOG_ERROR("unknown address format '%s'\n", addrs); 1394 | uintptr_t addr = (uintptr_t)strtoull(addrs + 2, NULL, 16); 1395 | struct value value = {.type = m->last_type}; 1396 | watchlist_push(&m->active, addr, &value); 1397 | } break; 1398 | case COMMAND_LIST: { 1399 | char arg = 'a'; 1400 | if (argc > 1) 1401 | arg = argv[1][0]; 1402 | switch (arg) { 1403 | case 'a': { 1404 | if (!m->target) 1405 | LOG_ERROR("no process attached\n"); 1406 | watchlist_visit(&m->active, list_visitor, stdout); 1407 | } break; 1408 | case 'p': { 1409 | struct process_iterator i[1]; 1410 | process_iterator_init(i); 1411 | for (; !process_iterator_done(i); process_iterator_next(i)) 1412 | printf("%8ld %s\n", (long)i->pid, i->name); 1413 | process_iterator_destroy(i); 1414 | } break; 1415 | case 'l': { 1416 | if (!m->target) 1417 | LOG_ERROR("no process attached\n"); 1418 | watchlist_visit(&m->locked, list_visitor, 0); 1419 | } break; 1420 | default: { 1421 | LOG_ERROR("unknown list type '%s'\n", argv[1]); 1422 | } break; 1423 | } 1424 | } break; 1425 | case COMMAND_SET: { 1426 | if (!m->target) 1427 | LOG_ERROR("no process attached\n"); 1428 | if (argc != 2) 1429 | LOG_ERROR("wrong number of arguments"); 1430 | const char *arg = argv[1]; 1431 | struct value value; 1432 | enum value_parse_result r = value_parse(&value, arg); 1433 | switch (r) { 1434 | case VALUE_PARSE_OVERFLOW: { 1435 | LOG_ERROR("overflow '%s'\n", arg); 1436 | } break; 1437 | case VALUE_PARSE_INVALID: { 1438 | LOG_ERROR("invalid value '%s'\n", arg); 1439 | } break; 1440 | case VALUE_PARSE_SUCCESS: { 1441 | char buf[64]; 1442 | value_print(buf, sizeof(buf), &value); 1443 | LOG_INFO("setting to %s\n", buf); 1444 | } break; 1445 | } 1446 | m->last_type = value.type; 1447 | size_t set_count = 0; 1448 | for (size_t i = 0; i < m->active.count; i++) { 1449 | uintptr_t addr = m->active.list[i].addr; 1450 | unsigned size = VALUE_SIZE(value); 1451 | if (!os_write_memory(m->target, addr, &value.value, size)) 1452 | LOG_WARNING("write memory failed: %s\n", 1453 | os_last_error()); 1454 | else 1455 | set_count++; 1456 | } 1457 | printf("%zu values set\n", set_count); 1458 | } break; 1459 | case COMMAND_LOCK: { 1460 | if (!m->target) 1461 | LOG_ERROR("no process attached\n"); 1462 | if (argc > 2) 1463 | LOG_ERROR("wrong number of arguments"); 1464 | struct value value; 1465 | int have_value = 0; 1466 | if (argc == 2) { 1467 | have_value = 1; 1468 | const char *arg = argv[1]; 1469 | enum value_parse_result r = value_parse(&value, arg); 1470 | switch (r) { 1471 | case VALUE_PARSE_OVERFLOW: { 1472 | LOG_ERROR("overflow '%s'\n", arg); 1473 | } break; 1474 | case VALUE_PARSE_INVALID: { 1475 | LOG_ERROR("invalid value '%s'\n", arg); 1476 | } break; 1477 | case VALUE_PARSE_SUCCESS: { 1478 | char buf[64]; 1479 | value_print(buf, sizeof(buf), &value); 1480 | LOG_INFO("setting to %s\n", buf); 1481 | } break; 1482 | } 1483 | m->last_type = value.type; 1484 | } 1485 | os_mutex_lock(&m->thread); 1486 | for (size_t i = 0; i < m->active.count; i++) { 1487 | uintptr_t addr = m->active.list[i].addr; 1488 | struct value *prev = &m->active.list[i].prev; 1489 | watchlist_push(&m->locked, addr, have_value ? &value : prev); 1490 | } 1491 | os_mutex_unlock(&m->thread); 1492 | } break; 1493 | case COMMAND_WAIT: { 1494 | if (argc != 2) 1495 | LOG_ERROR("wrong number of arguments"); 1496 | os_sleep(atof(argv[1])); 1497 | } break; 1498 | case COMMAND_HELP: { 1499 | unsigned n = sizeof(command_info) / sizeof(command_info[0]); 1500 | for (unsigned i = 0; i < n; i++) { 1501 | const char *name = command_info[i].name; 1502 | const char *help = command_info[i].help; 1503 | const char *args = command_info[i].args; 1504 | int argsize = (int)(30 - strlen(name)); 1505 | printf("%s %-*s %s\n", name, argsize, args ? args : "", help); 1506 | } 1507 | putchar('\n'); 1508 | puts("Commands can also be supplied as command line arguments, " 1509 | "where each\ncommand verb is prefixed with one or two " 1510 | "dashes.\n"); 1511 | puts("By default, memory is scanned for 32-bit signed integers. " 1512 | "The numeric\narguments to find, narrow, and set may have " 1513 | "C-like suffixes specifying\ntheir width and signedness " 1514 | "(o, h, q, uo, uh, uq). Floating point\nvalues are also " 1515 | "an option, with an 'f' suffix for single precision."); 1516 | } break; 1517 | case COMMAND_QUIT: { 1518 | return MEMDIG_RESULT_QUIT; 1519 | } break; 1520 | } 1521 | return MEMDIG_RESULT_OK; 1522 | fail: 1523 | return MEMDIG_RESULT_ERROR; 1524 | } 1525 | 1526 | static void 1527 | memdig_free(struct memdig *m) 1528 | { 1529 | os_mutex_lock(&m->thread); 1530 | if (m->target) { 1531 | watchlist_free(&m->active); 1532 | watchlist_free(&m->locked); 1533 | os_process_close(m->target); 1534 | m->target = 0; 1535 | } 1536 | m->running = 0; 1537 | os_mutex_unlock(&m->thread); 1538 | os_thread_join(&m->thread); 1539 | } 1540 | 1541 | #define PROMPT(f) \ 1542 | do { \ 1543 | fputs("> ", f); \ 1544 | fflush(f); \ 1545 | } while(0) 1546 | 1547 | int 1548 | main(int argc, char **argv) 1549 | { 1550 | int result = 0; 1551 | struct memdig memdig[1]; 1552 | memdig_init(memdig); 1553 | 1554 | char *xargv[16]; 1555 | int xargc = 0; 1556 | int i = 1; 1557 | while (i < argc && argv[i][0] == '-') { 1558 | xargc = 1; 1559 | xargv[0] = argv[i++]; 1560 | while (xargv[0][0] == '-' && xargv[0][0] != 0) 1561 | xargv[0]++; 1562 | while (i < argc && argv[i][0] != '-' && xargc < 15) 1563 | xargv[xargc++] = argv[i++]; 1564 | xargv[xargc] = NULL; 1565 | enum memdig_result r = memdig_exec(memdig, xargc, xargv); 1566 | if (r == MEMDIG_RESULT_ERROR) 1567 | result = -1; 1568 | if (r != MEMDIG_RESULT_OK) 1569 | goto quit; 1570 | if (strcmp(xargv[0], "help") == 0) 1571 | goto quit; 1572 | if (strcmp(xargv[0], "version") == 0) 1573 | goto quit; 1574 | } 1575 | 1576 | char line[4096]; 1577 | PROMPT(stdout); 1578 | const char *delim = " \n\t"; 1579 | while (fgets(line, sizeof(line), stdin)) { 1580 | xargc = 0; 1581 | xargv[0] = strtok(line, delim); 1582 | if (xargv[0]) { 1583 | do 1584 | xargc++; 1585 | while (xargc < 15 && (xargv[xargc] = strtok(NULL, delim))); 1586 | xargv[xargc] = NULL; 1587 | } 1588 | if (memdig_exec(memdig, xargc, xargv) == MEMDIG_RESULT_QUIT) 1589 | goto quit; 1590 | PROMPT(stdout); 1591 | } 1592 | 1593 | quit: 1594 | memdig_free(memdig); 1595 | return result; 1596 | } 1597 | --------------------------------------------------------------------------------