made some API public
[nyatools.git] / procutil.d
blob28c04d166d612202309a08ce4216a3a203883737
1 /* Written by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module procutil is aliced;
19 import iv.vfs;
20 import iv.vfs.io;
22 version = pt_use_mem;
23 static assert((void*).sizeof == 4);
26 // ////////////////////////////////////////////////////////////////////////// //
27 // pid or -1
28 int findProcessByName (const(char)[] procname) {
29 import core.sys.posix.dirent;
30 static assert(dirent.d_name.offsetof == 19);
31 import core.sys.posix.unistd : readlink;
32 import core.stdc.stdio : snprintf;
33 import std.conv : to;
34 import std.string : fromStringz;
36 if (procname.length == 0) return -1;
38 char[128] tbuf = void;
39 char[1024] ebuf = void;
41 auto dir = opendir("/proc/");
42 if (dir is null) return -1;
43 scope(exit) closedir(dir);
45 for (;;) {
46 auto de = readdir(dir);
47 if (de is null) break;
48 if (de.d_type != DT_DIR) continue;
50 int pid = 0;
52 try {
53 pid = de.d_name.ptr.fromStringz.to!int;
54 } catch (Exception) { pid = 0; }
56 if (pid > 1 && pid <= int.max) {
57 snprintf(tbuf.ptr, tbuf.length, "/proc/%s/exe", de.d_name.ptr);
58 tbuf[$-1] = 0;
59 auto len = readlink(tbuf.ptr, ebuf.ptr, ebuf.length);
60 if (len > 0 && len < ebuf.length) {
61 //{ import core.stdc.stdio : printf; ebuf[len] = 0; printf("pid: %u; exe: [%s]\n", pid, ebuf.ptr); }
62 if (ebuf[0..len] == procname) return pid;
63 int pos = cast(int)len;
64 while (pos > 0 && ebuf[pos-1] != '/') --pos;
65 if (ebuf[pos..len] == procname) return pid;
70 return -1;
74 // ////////////////////////////////////////////////////////////////////////// //
75 public align(1) struct MemoryRegionInfo {
76 align(1):
77 uint start, end;
78 string name;
82 // ////////////////////////////////////////////////////////////////////////// //
83 public MemoryRegionInfo[] readMemoryRegions (uint pid, bool includeStack=false) {
84 import std.format : format;
86 static byte hexDigit (char ch) pure nothrow @safe @nogc {
87 pragma(inline, true);
88 return
89 ch >= '0' && ch <= '9' ? cast(byte)(ch-'0') :
90 ch >= 'A' && ch <= 'F' ? cast(byte)(ch-'A'+10) :
91 ch >= 'a' && ch <= 'f' ? cast(byte)(ch-'a'+10) :
92 -1;
95 static uint parseHex(T : const(char)[]) (ref T s) {
96 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
97 if (s.length == 0 || hexDigit(s.ptr[0]) < 0) throw new Exception("hex number expected");
98 uint res = 0;
99 while (s.length) {
100 auto d = hexDigit(s.ptr[0]);
101 if (d < 0) break;
102 uint nr = res*16+d;
103 if (nr < res) throw new Exception("hex overflow");
104 res = nr;
105 s = s[1..$];
107 return res;
110 static void consume(T : const(char)[]) (ref T s, char ch) {
111 if (s.length == 0 && s.ptr[0] != ch) throw new Exception("'"~ch~"' expected");
112 s = s[1..$];
115 static void skipSpaces(T : const(char)[]) (ref T s) {
116 if (s.length == 0 && s.ptr[0] > ' ') throw new Exception("space expected");
117 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
120 MemoryRegionInfo[] res;
121 foreach (char[] ln; VFile("/proc/%s/maps".format(pid)).byLine) {
122 uint start, end;
123 string name;
124 try {
125 //stderr.writeln("*** [", ln, "]");
126 start = parseHex(ln);
127 consume(ln, '-');
128 end = parseHex(ln);
129 skipSpaces(ln);
130 if (start >= end) continue;
131 // skip regions with less than 16 bytes of data
132 if (end-start < 16) continue;
133 if (ln.length < 5) throw new Exception("invalid region description");
134 // skip non-writeable, {executable} and shared regions
135 if (ln.ptr[0] != 'r' || ln.ptr[1] != 'w' /*|| ln.ptr[2] == 'x'*/ || ln.ptr[3] != 'p') continue;
136 ln = ln[4..$];
137 skipSpaces(ln);
138 // if offset is not 0, it is mmaped region, skip it
139 if (parseHex(ln) != 0) continue;
140 // if devicehi or devilelo is not 0, it is mmaped region, skip it
141 skipSpaces(ln);
142 if (parseHex(ln) != 0) continue;
143 consume(ln, ':');
144 if (parseHex(ln) != 0) continue;
145 // if inode is not 0, it is mmaped region, skip it
146 skipSpaces(ln);
147 if (parseHex(ln) != 0) continue;
148 // get region name
149 if (ln.length > 0) {
150 skipSpaces(ln);
151 name = ln.idup;
152 while (name.length && name[$-1] <= ' ') name = name[0..$-1];
154 } catch (Exception) {
155 // can't parse this region, skip it
156 continue;
158 if (name.length > 0 && name.ptr[0] == '[') {
159 // specials; allow only "heap"
160 //writefln("**** %08X:%08X <%s>", start, end, name);
161 if (name.length < 6 || name[0..5] != "[heap") {
162 if (!includeStack) continue;
163 if (name.length < 6 || name[0..6] != "[stack") continue;
166 //stderr.writefln("%08X:%08X <%s>", start, end, name);
167 res ~= MemoryRegionInfo(start, end, name);
169 return res;
173 // ////////////////////////////////////////////////////////////////////////// //
175 * search the target process' /proc/pid/maps entry and find an executable
176 * region of memory that we can use to run code in.
177 * returns zero if nothing was found.
179 public uint findXSpaceInPrey (int pid) {
180 import core.stdc.stdio : FILE, fopen, fclose, snprintf, fgets, sscanf;
181 import core.stdc.string : strstr;
182 bool found = false;
183 FILE* fp;
184 char[30] filename;
185 char[850] line;
186 c_ulong addr, eaddr;
187 char[16] perms;
188 snprintf(filename.ptr, filename.length, "/proc/%u/maps", cast(uint)pid);
189 fp = fopen(filename.ptr, "r");
190 if (fp is null) return 0;
191 while (fgets(line.ptr, 850, fp) !is null) {
192 sscanf(line.ptr, "%lx-%lx %s %*s %*s %*d", &addr, &eaddr, perms.ptr);
193 if (strstr(perms.ptr, "x") !is null && eaddr > addr && eaddr-addr >= 128) { found = true; break; }
195 fclose(fp);
196 return (found ? addr : 0);
201 * gets the base address of libc.so inside a process by reading /proc/pid/maps.
202 * returns 0 if nothing was found.
204 private uint findLibcInPrey (int pid) {
205 import core.stdc.stdio : FILE, fopen, fclose, snprintf, fgets, sscanf;
206 import core.stdc.string : strstr;
207 bool found = false;
208 FILE *fp;
209 char[30] filename;
210 char[850] line;
211 c_ulong addr;
212 char[16] perms;
213 char* modulePath;
214 snprintf(filename.ptr, filename.length, "/proc/%d/maps", pid);
215 fp = fopen(filename.ptr, "r");
216 if (fp is null) return 0;
217 while (fgets(line.ptr, 850, fp) !is null) {
218 import std.string : fromStringz;
219 auto ln = line.ptr.fromStringz;
220 while (ln.length && ln[$-1] <= ' ') ln = ln[0..$-1];
221 if (ln.length == 0 || ln.ptr[0] <= ' ') continue;
222 // remove 5 fields
223 foreach (immutable _; 0..5) {
224 while (ln.length > 0 && ln.ptr[0] > ' ') ln = ln[1..$];
225 while (ln.length > 0 && ln.ptr[0] <= ' ') ln = ln[1..$];
227 if (ln.length == 0) continue;
228 int pos = cast(int)ln.length;
229 while (pos > 0 && ln.ptr[pos-1] != '/') --pos;
230 ln = ln[pos..$];
231 if (ln.length < 8 || ln[0..5] != "libc-") continue;
232 ln = ln[5..$];
233 pos = 0;
234 // digits and dots
235 while (pos < ln.length) {
236 if (ln.ptr[pos] >= '0' && ln.ptr[pos] <= '9') { ++pos; continue; }
237 if (ln.ptr[pos] != '.') break;
238 if (ln.length-pos > 1 && ln.ptr[pos+1] >= '0' && ln.ptr[pos+1] <= '9') { pos += 2; continue; }
239 break;
241 //{ import std.stdio; stderr.writeln("[", ln, "] - [", ln[pos..$], "]"); }
242 if (pos >= ln.length || ln.ptr[pos] != '.' || ln.length-pos < 3 || ln[pos..pos+3] != ".so") continue;
243 ln = ln[pos+3..$];
244 //{ import std.stdio; stderr.writeln("*[", ln, "]"); }
245 if (ln.length != 0) {
246 // it should be dots and digits
247 if (ln.ptr[0] != '.') continue;
248 pos = 0;
249 while (pos < ln.length) {
250 if (ln.ptr[pos] >= '0' && ln.ptr[pos] <= '9') { ++pos; continue; }
251 if (ln.ptr[pos] != '.') break;
252 if (ln.length-pos > 1 && ln.ptr[pos+1] >= '0' && ln.ptr[pos+1] <= '9') { pos += 2; continue; }
253 break;
255 if (pos < ln.length) continue;
257 // wow, i found her!
258 sscanf(line.ptr, "%lx-%*lx %s %*s %*s %*d", &addr, perms.ptr);
259 if (strstr(perms.ptr, "x") !is null) { found = true; break; }
261 fclose(fp);
262 //{ import core.stdc.stdio : printf; printf("%u: 0x%08x: %d\n", pid, addr, (found ? 1 : 0)); }
263 return (found ? addr : 0);
267 // ////////////////////////////////////////////////////////////////////////// //
268 struct AttachedProc {
269 private:
270 static struct Nfo {
271 uint rc;
272 uint pid;
273 int memfd;
274 bool fdwr;
275 @disable this (this);
278 private:
279 uint nfop;
281 void decRef () nothrow @nogc {
282 if (nfop) {
283 auto n = cast(Nfo*)nfop;
284 if (--n.rc == 0) {
285 import core.stdc.stdlib : free;
286 if (n.memfd >= 0) {
287 import core.sys.posix.unistd : close;
288 close(n.memfd);
290 // addr is ignored under Linux, but should be 1 under FreeBSD in order to let the child process continue at what it had been interrupted
291 ptrace(PtraceRequest.DETACH, n.pid, 1, 0); // == 0
292 free(n);
294 nfop = 0;
298 public:
299 this (uint apid) { attachTo(apid); }
300 this (this) nothrow @trusted @nogc { pragma(inline, true); if (nfop) (cast(Nfo*)nfop).rc += 1; }
301 ~this () nothrow @nogc { pragma(inline, true); if (nfop) decRef(); }
303 void close () nothrow @nogc { pragma(inline, true); if (nfop) decRef(); }
305 void attachTo (uint apid) {
306 import core.stdc.stdlib : malloc, free;
307 import core.stdc.string : memset;
308 import core.sys.posix.sys.wait : waitpid, WIFSTOPPED;
309 int status;
310 close();
311 if (apid == 0 || apid == 1) throw new Exception("attach failed");
312 // attach, to the target application, which should cause a SIGSTOP
313 if (ptrace(PtraceRequest.ATTACH, apid, null, null) == -1) throw new Exception("attach failed");
314 // detach on failure, just in case
315 scope(failure) ptrace(PtraceRequest.DETACH, apid, 1, 0); // == 0
316 // wait for the SIGSTOP to take place
317 if (waitpid(apid, &status, 0) == -1 || !WIFSTOPPED(status)) throw new Exception("error waiting target to stop");
318 auto n = cast(Nfo*)malloc(Nfo.sizeof);
319 if (n is null) throw new Exception("out of memory"); // hm...
320 memset(n, 0, Nfo.sizeof);
321 n.rc = 1;
322 n.pid = apid;
323 n.memfd = -666;
324 nfop = cast(uint)n;
327 void opAssign() (in auto ref AttachedProc o) nothrow @nogc {
328 // increase other's rc, and then decrease ours; doing in in another order is unsafe
329 if (o.nfop) (cast(Nfo*)o.nfop).rc += 1;
330 if (nfop) decRef();
331 nfop = o.nfop;
334 @property bool valid () const nothrow @safe @nogc { pragma(inline, true); return (nfop != 0); }
336 private bool openFD () nothrow @nogc {
337 if (!nfop) return false;
338 auto n = cast(Nfo*)nfop;
339 uint pid = n.pid;
340 if (n.memfd == -666) {
341 import core.stdc.stdio : snprintf;
342 import core.sys.posix.fcntl : open, O_RDONLY, O_RDWR;
343 char[64] name = 0;
344 snprintf(name.ptr, name.length-1, "/proc/%d/mem", pid);
345 n.memfd = open(name.ptr, O_RDWR);
346 if (n.memfd < 0) {
347 n.fdwr = false;
348 n.memfd = open(name.ptr, O_RDONLY);
349 if (n.memfd < 0) n.memfd = -1;
350 } else {
351 n.fdwr = true;
354 return (n.memfd >= 0);
357 T[] readBytes(T) (T[] buf, uint addr) {
358 import core.sys.posix.unistd : pread;
359 if (!nfop) throw new Exception("can't read from unintialized attach");
360 if (buf.length > int.max/2) throw new Exception("too many elements in read buffer");
361 if (cast(long)buf.length*T.sizeof > int.max/2) throw new Exception("too many elements in read buffer");
362 if (buf.length == 0) return buf;
363 auto rd = (0x1_0000_0000L-addr);
364 if (rd < T.sizeof) throw new Exception("read error");
365 if (rd > buf.length*T.sizeof) rd = buf.length*T.sizeof;
366 auto n = cast(Nfo*)nfop;
367 uint pid = n.pid;
368 version(pt_use_mem) {
369 if (!openFD) throw new Exception("can't open process memory");
370 rd /= T.sizeof;
371 auto len = pread(n.memfd, buf.ptr, cast(uint)(rd*T.sizeof), addr);
372 if (len < T.sizeof) throw new Exception("read error");
373 return buf[0..len/T.sizeof];
374 } else {
375 import core.stdc.errno;
376 import core.stdc.string : memcpy;
377 auto dp = cast(uint*)buf.ptr;
378 uint len = 0;
379 errno = 0;
380 foreach (immutable _; 0..rd/4) {
381 uint res = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
382 if (res == -1 && errno != 0) throw new Exception("read error");
383 *dp++ = res;
384 addr += 4;
385 len += 4;
386 //if (ptrace(PtraceRequest.POKEDATA, pid, addr, res) != 0) throw new Exception("writing error");
388 if (rd%4) {
389 uint res = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
390 if (res == -1 && errno != 0) throw new Exception("read error");
391 memcpy(dp, &res, cast(uint)(rd%4));
392 len += rd%4;
394 if (len < T.sizeof) throw new Exception("read error");
395 return buf[0..len/T.sizeof];
399 void writeBytes (const(void)[] buf, uint addr) {
400 import core.sys.posix.unistd : pwrite;
401 if (!nfop) throw new Exception("can't write to unintialized attach");
402 if (buf.length == 0) return;
403 auto n = cast(Nfo*)nfop;
404 uint pid = n.pid;
405 if (!openFD) throw new Exception("can't open process memory");
406 if (!n.fdwr) throw new Exception("can't open process memory for writing");
407 long wr = 0x1_0000_0000L-addr;
408 if (wr < buf.length) throw new Exception("write error");
409 auto len = pwrite(n.memfd, buf.ptr, cast(uint)buf.length, addr);
410 if (len != buf.length) throw new Exception("write error");
415 // ////////////////////////////////////////////////////////////////////////// //
416 public T[] procReadMem(T) (uint pid, T[] buf, uint addr) {
417 if (pid <= 2) return null; //throw new Exception("read error");
418 if (buf.length == 0) return buf[];
420 iovec local, remote;
421 local.iov_base = cast(void*)buf.ptr;
422 local.iov_len = buf.length;
423 remote.iov_base = cast(void*)addr;
424 remote.iov_len = buf.length;
425 auto len = process_vm_readv(pid, &local, 1, &remote, 1, 0);
426 if (len < T.sizeof) return null; //throw new Exception("read error");
427 return buf[0..len/T.sizeof];
429 import core.stdc.errno;
430 auto bp = cast(ubyte*)buf.ptr;
431 auto left = buf.length*T.sizeof;
432 uint total;
433 errno = 0;
434 while (left > 0) {
435 import core.stdc.string : memcpy;
436 auto ov = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
437 if (ov == -1 && errno != 0) {
438 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "reading failed at 0x%08x (%u bytes left)\n", addr, cast(uint)left); }
439 return null;
441 if (left >= 4) memcpy(bp, &ov, 4); else memcpy(bp, &ov, left);
442 //{ import core.stdc.stdio : printf; printf("writing 0x%08x at 0x%08x (%u bytes left)\n", v, addr, cast(uint)left); }
443 if (left <= 4) { total += left; left = 0; break; }
444 left -= 4;
445 addr += 4;
446 bp += 4;
447 total += 4;
449 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "procReadMem complete (%u bytes)\n", total); }
450 return buf[0..total/T.sizeof];
454 public bool procWriteMem (uint pid, const(void)[] buf, uint addr) {
455 if (pid <= 2) return false; //throw new Exception("write error");
456 if (buf.length == 0) return true;
458 iovec local, remote;
459 local.iov_base = cast(void*)buf.ptr;
460 local.iov_len = buf.length;
461 remote.iov_base = cast(void*)addr;
462 remote.iov_len = buf.length;
463 auto len = process_vm_writev(pid, &local, 1, &remote, 1, 0);
464 if (len != buf.length) return false; //throw new Exception("write error");
465 return true;
467 auto bp = cast(const(ubyte)*)buf.ptr;
468 auto left = buf.length;
469 while (left > 0) {
470 import core.stdc.string : memcpy;
471 uint v = 0;
472 if (left < 4) {
473 import core.stdc.errno;
474 errno = 0;
475 auto ov = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
476 if (ov == -1 && errno != 0) return false;
477 v = ov;
479 memcpy(&v, bp, 4);
480 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "writing 0x%08x at 0x%08x (%u bytes left)\n", v, addr, cast(uint)left); }
481 if (ptrace(PtraceRequest.POKEDATA, pid, addr, v) != 0) return false;
482 if (left <= 4) break;
483 left -= 4;
484 addr += 4;
485 bp += 4;
487 //{ import core.stdc.stdio : stderr, fprintf; fprintf("procWriteMem complete\n"); }
488 return true;
492 // ////////////////////////////////////////////////////////////////////////// //
493 // code injector
494 // some code was taken from "linux-inject" project: https://github.com/gaffe23/linux-inject
495 private:
496 import iv.olly.disasm2;
499 extern(C) nothrow @nogc {
500 enum RTLD_LAZY = 0x00001; // POSIX
501 void* __libc_dlopen_mode (const(char)* name, int mode);
502 void* __libc_dlsym (void* soh, const(char)* name);
503 int __libc_dlclose (void* soh);
508 * injectSharedLibrary()
510 * This is the code that will actually be injected into the target process.
511 * This code is responsible for loading the shared library into the target
512 * process' address space. First, it calls malloc() to allocate a buffer to
513 * hold the filename of the library to be loaded. Then, it calls
514 * __libc_dlopen_mode(), libc's implementation of dlopen(), to load the desired
515 * shared library. Finally, it calls free() to free the buffer containing the
516 * library name. Each time it needs to give control back to the injector
517 * process, it breaks back in by executing an "int $3" instruction. See the
518 * comments below for more details on how this works.
521 private void injectSharedLibrary (/*uint mallocaddr, uint freeaddr, uint dlopenaddr*/) nothrow @trusted @nogc {
522 // here are the assumptions I'm making about what data will be located
523 // where at the time the target executes this code:
524 asm nothrow @trusted @nogc {
525 naked;
526 // choose the amount of memory to allocate with malloc() based on the size
527 // of the path to the shared library passed via ecx
528 push ECX;
529 // call malloc
530 call EDI;
531 // break back in so that the injector can get the return value
532 int 3;
534 // call __libc_dlopen_mode() to load the shared library
535 // 2nd argument to __libc_dlopen_mode(): flag = RTLD_LAZY
536 push 1;
537 // 1st argument to __libc_dlopen_mode(): filename = the buffer we allocated earlier
538 push ECX;
539 // call __libc_dlopen_mode()
540 call EDI;
541 // break back in so that the injector can check the return value
542 int 3;
544 // call free() on the previously malloc'd buffer
545 // 1st argument to free(): ptr = the buffer we allocated earlier
546 push ECX;
547 // call free()
548 call EDI;
549 int 3; // final out
551 ret; // end marker; do not remove, do not add another "ret"
556 private uint islLength () {
557 DisasmData da;
558 auto code = cast(const(ubyte)*)&injectSharedLibrary;
559 uint addr = cast(uint)&injectSharedLibrary;
560 auto staddr = addr;
561 for (;;) {
562 auto len = disasm(code[0..64], addr, &da, /*DA_DUMP|DA_TEXT|DA_HILITE*/0, null);
563 if (len == 0) return 0;
564 if (da.size == 1 && code[0] == 0xc3) return da.ip+1-staddr;
565 addr += len;
566 code += len;
572 * checkloaded()
574 * Given a process ID and the name of a shared library, check whether that
575 * process has loaded the shared library by reading entries in its
576 * /proc/[pid]/maps file.
578 * args:
579 * - pid_t pid: the pid of the process to check
580 * - char* libname: the library to search /proc/[pid]/maps for
582 * returns:
583 * - an int indicating whether or not the library has been loaded into the
584 * process (1 = yes, 0 = no)
587 private bool checkloaded (int pid, const(char)[] libname) {
588 import core.stdc.stdio : FILE, fopen, fclose, snprintf, fgets, sscanf;
589 import core.stdc.string : strstr;
590 FILE *fp;
591 char[30] filename;
592 char[850] line, ln2 = 0;
593 char[5] perms;
594 char* modulePath;
595 if (libname.length == 0 || libname.length > ln2.length-1) return false;
596 ln2[0..libname.length] = libname[];
597 snprintf(filename.ptr, filename.length, "/proc/%d/maps", pid);
598 fp = fopen(filename.ptr, "r");
599 if (fp is null) return 0;
600 while (fgets(line.ptr, 850, fp) !is null) {
601 if (strstr(line.ptr, ln2.ptr) !is null) {
602 fclose(fp);
603 return true;
606 fclose(fp);
607 return false;
612 * getFunctionAddress()
614 * Find the address of a function within our own loaded copy of libc.so.
616 * args:
617 * - char* funcName: name of the function whose address we want to find
619 * returns:
620 * - a long containing the address of that function
623 private uint getFunctionAddress (const(char)[] funcname) {
624 //import core.sys.linux.dlfcn;
625 if (funcname.length == 0 || funcname.length > 127) return false;
626 char[129] buf = 0;
627 buf[0..funcname.length] = funcname[];
628 void* self = __libc_dlopen_mode("libc.so.6", RTLD_LAZY);
629 void* funcAddr = __libc_dlsym(self, buf.ptr);
630 return cast(uint)funcAddr;
634 // ////////////////////////////////////////////////////////////////////////// //
635 public bool injectSO (int pid, const(char)[] libname) {
636 import core.sys.posix.unistd : getpid;
637 import core.stdc.string : memset;
639 if (pid <= 1 || libname.length == 0 || libname.length > 1023) return false;
641 auto mypid = getpid();
642 auto mylibcaddr = findLibcInPrey(mypid);
643 if (mylibcaddr == 0) {
644 { import core.stdc.stdio : printf; printf("no libc found in our process\n"); }
645 return false;
648 // find the addresses of the syscalls that we'd like to use inside the
649 // target, as loaded inside THIS process (i.e. NOT the target process)
650 auto mallocAddr = getFunctionAddress("malloc");
651 if (mallocAddr == 0) {
652 { import core.stdc.stdio : printf; printf("no malloc found in our process\n"); }
653 return false;
655 auto freeAddr = getFunctionAddress("free");
656 if (freeAddr == 0) {
657 { import core.stdc.stdio : printf; printf("no free found in our process\n"); }
658 return false;
660 auto dlopenAddr = getFunctionAddress("__libc_dlopen_mode");
661 if (dlopenAddr == 0) {
662 { import core.stdc.stdio : printf; printf("no dlopen found in our process\n"); }
663 return false;
666 // use the base address of libc to calculate offsets for the syscalls we want to use
667 uint mallocOffset = mallocAddr-mylibcaddr;
668 uint freeOffset = freeAddr-mylibcaddr;
669 uint dlopenOffset = dlopenAddr-mylibcaddr;
671 // get the target process' libc address and use it to find the
672 // addresses of the syscalls we want to use inside the target process
673 uint targetLibcAddr = findLibcInPrey(pid);
674 if (targetLibcAddr == 0) return false;
675 uint targetMallocAddr = targetLibcAddr+mallocOffset;
676 uint targetFreeAddr = targetLibcAddr+freeOffset;
677 uint targetDlopenAddr = targetLibcAddr+dlopenOffset;
679 // find a good address to copy code to
680 uint ijAddr = findXSpaceInPrey(pid);
681 if (ijAddr == 0) {
682 { import core.stdc.stdio : printf; printf("can't find free space to paste our code\n"); }
683 return false;
685 ijAddr += uint.sizeof;
686 //{ import core.stdc.stdio : printf; printf("[0x%08x]\n", addr); }
688 ubyte[128] bkdata = 0;
689 uint bkdataUsed = 0;
691 uint ijcSize = islLength();
692 if (ijcSize == 0 || ijcSize >= bkdata.length-1) {
693 { import core.stdc.stdio : printf; printf("disasm fucked\n"); }
694 return false;
697 user_regs_struct oldregs, regs;
698 memset(&oldregs, 0, user_regs_struct.sizeof);
699 memset(&regs, 0, user_regs_struct.sizeof);
701 static bool attachTo (uint apid) {
702 import core.stdc.stdlib : malloc, free;
703 import core.stdc.string : memset;
704 import core.sys.posix.sys.wait : waitpid, WIFSTOPPED;
705 int status;
706 // attach, to the target application, which should cause a SIGSTOP
707 if (ptrace(PtraceRequest.ATTACH, apid, null, null) == -1) return false;
708 // detach on failure, just in case
709 // wait for the SIGSTOP to take place
710 if (waitpid(apid, &status, 0) == -1 || !WIFSTOPPED(status)) {
711 ptrace(PtraceRequest.DETACH, apid, 1, 0);
712 return false;
714 return true;
717 static bool singleStep (uint apid) {
718 import core.stdc.stdlib : malloc, free;
719 import core.stdc.string : memset;
720 import core.sys.posix.sys.wait : waitpid, WIFEXITED;
721 int status;
722 if (ptrace(PtraceRequest.SINGLESTEP, apid, null, null) == -1) return false;
723 if (waitpid(apid, &status, 0) == -1 || WIFEXITED(status)) return false;
724 return true;
727 static bool singleStepUntilInt3 (uint apid, uint saddr, uint len, user_regs_struct* regsp) {
728 import core.stdc.stdlib : malloc, free;
729 import core.stdc.string : memset;
730 import core.sys.posix.sys.wait : waitpid, WIFEXITED;
731 user_regs_struct regs;
732 if (regsp is null) regsp = &regs;
733 for (;;) {
734 int status;
735 if (ptrace(PtraceRequest.SINGLESTEP, apid, null, null) == -1) return false;
736 if (waitpid(apid, &status, 0) == -1 || WIFEXITED(status)) return false;
737 if (ptrace(PtraceRequest.GETREGS, apid, null, regsp) == -1) return false;
738 ubyte[MAXCMDSIZE] ib;
739 procReadMem(apid, ib[], regsp.eip);
741 import core.stdc.stdio : stderr, fprintf;
742 //fprintf(stderr, "EIP=0x%08x (%02X)\n", regsp.eip, ib);
743 DisasmData da;
744 DAConfig cfg;
745 cfg.tabarguments = true;
746 auto dclen = disasm(ib[], regsp.eip, &da, DA_DUMP|DA_TEXT|DA_HILITE, &cfg);
747 if (dclen == 0) {
748 fprintf(stderr, "EIP=0x%08x FUCK!\n", regsp.eip);
749 } else {
750 import std.stdio : stderr;
751 stderr.writefln("0x%08x: %-16s %s", da.ip, da.dumpstr, da.resstr);
754 if (ib[0] == 0xcc) break;
756 return true;
759 static bool int3Step (uint apid, uint saddr, uint len, user_regs_struct* regsp) {
760 version(all) {
761 import core.stdc.stdlib : malloc, free;
762 import core.stdc.string : memset;
763 import core.sys.posix.signal : siginfo_t, SIGTRAP;
764 import core.sys.posix.sys.wait : waitpid, WIFEXITED;
765 user_regs_struct regs;
766 if (regsp is null) regsp = &regs;
767 for (;;) {
768 int status;
769 if (ptrace(PtraceRequest.CONT, apid, null, null) == -1) return false;
770 if (waitpid(apid, &status, 0) == -1 || WIFEXITED(status)) return false;
771 // check what signal we received
772 siginfo_t tsig;
773 if (ptrace(PtraceRequest.GETSIGINFO, apid, null, &tsig) == -1) return false;
774 if (tsig.si_signo == SIGTRAP) {
775 memset(regsp, 0, user_regs_struct.sizeof);
776 if (ptrace(PtraceRequest.GETREGS, apid, null, regsp) == -1) return false;
777 if (regsp.eip >= saddr && regsp.eip <= saddr+len) return true;
778 { import core.stdc.stdio : printf; printf("trap signal at 0x%08x\n", regsp.eip); }
779 // not in our function, continue
780 } else {
781 // some other signal, skip it
782 { import core.stdc.stdio : printf; printf("non-trap signal at 0x%08x (%u)\n", regsp.eip, cast(uint)tsig.si_signo); }
783 return false;
786 return true;
787 } else {
788 { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "========================\n"); }
789 user_regs_struct regs;
790 if (regsp is null) regsp = &regs;
791 if (ptrace(PtraceRequest.GETREGS, apid, null, regsp) == -1) return false;
792 { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "ecx=0x%08x; edi=0x%08x\n", regsp.ecx, regsp.edi); }
793 auto res = singleStepUntilInt3(apid, saddr, len, regsp);
794 if (res) {
795 // skip int3
796 //++regsp.eip;
797 //if (ptrace(PtraceRequest.SETREGS, apid, null, regsp) == -1) return false;
799 { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "res:%u; eax=0x%08x\n", (res ? 1 : 0), regsp.eax); }
800 return res;
804 if (!attachTo(pid)) {
805 { import core.stdc.stdio : printf; printf("can't attach to target process\n"); }
806 return false;
809 // if prey is somewhere below libc, trace it until it returns
810 // this should prevent deadlocks on multithreaded apps (i hope)
811 while (regs.eip >= targetLibcAddr) {
812 //{ import core.stdc.stdio : printf; printf("EIP=0x%08x\n", regs.eip); }
813 if (!singleStep(pid)) {
814 { import core.stdc.stdio : printf; printf("singlestepping failed\n"); }
815 return false;
817 if (ptrace(PtraceRequest.GETREGS, pid, null, &regs) == -1) {
818 { import core.stdc.stdio : printf; printf("can't get registers of target process\n"); }
819 return false;
822 //{ import core.stdc.stdio : printf; printf("EIP=0x%08x\n", regs.eip); }
824 if (ptrace(PtraceRequest.GETREGS, pid, null, &oldregs) == -1) {
825 ptrace(PtraceRequest.DETACH, pid, 1, 0);
826 { import core.stdc.stdio : printf; printf("can't get registers of target process\n"); }
827 return false;
829 regs = oldregs;
831 scope(exit) {
832 // restore data and registers, and detach
833 if (bkdataUsed) procWriteMem(pid, bkdata[0..bkdataUsed], ijAddr);
834 ptrace(PtraceRequest.SETREGS, pid, null, &oldregs);
835 ptrace(PtraceRequest.DETACH, pid, 1, 0);
836 { import core.stdc.stdio : printf; printf("prey process restored\n"); }
839 // back up whatever data used to be at the address we want to modify.
840 if (procReadMem(pid, bkdata[0..ijcSize], ijAddr).length != ijcSize) {
841 { import core.stdc.stdio : printf; printf("can't read process memory\n"); }
842 return false;
845 auto code = cast(immutable(ubyte)*)&injectSharedLibrary;
846 if (!procWriteMem(pid, code[0..ijcSize], ijAddr)) {
847 { import core.stdc.stdio : printf; printf("can't write process memory\n"); }
848 return false;
850 // fix "ret"
852 ubyte int3 = 0xc3;
853 if (!procWriteMem(pid, (&int3)[0..1], ijAddr+ijcSize-1)) {
854 { import core.stdc.stdio : printf; printf("can't write process memory\n"); }
855 return false;
858 bkdataUsed = ijcSize;
860 user_regs_struct malloc_regs;
862 // now that we have an address to copy code to, set the target's eip to it
863 regs.eip = ijAddr;
865 // pass arguments to my function injectSharedLibrary() by loading them into the right registers
866 // see comments in injectSharedLibrary() for more details
867 regs.ecx = libname.length+1; // including zero
868 regs.edi = targetMallocAddr;
869 if (ptrace(PtraceRequest.SETREGS, pid, null, &regs) == -1) {
870 { import core.stdc.stdio : printf; printf("can't set registers of target process\n"); }
871 return false;
874 //singleStepUntilInt3(pid, ijAddr, ijcSize, &malloc_regs);
875 //return false;
877 // now that the new code is in place, let the target run our injected code
878 if (!int3Step(pid, ijAddr, ijcSize, &malloc_regs)) {
879 { import core.stdc.stdio : printf; printf("int3 stepping failed\n"); }
880 return false;
883 // at this point, the target should have run malloc()
884 // check its return value to see if it succeeded, and bail out cleanly if it didn't
885 uint targetBuf = malloc_regs.eax;
886 if (targetBuf == 0) {
887 { import core.stdc.stdio : printf; printf("target process failed to allocate memory\n"); }
888 return false;
891 // if we get here, then malloc likely succeeded, so now we need to copy
892 // the path to the shared library we want to inject into the buffer
893 // that the target process just malloc'd; this is needed so that it can
894 // be passed as an argument to __libc_dlopen_mode later on
895 if (!procWriteMem(pid, libname[], targetBuf)) {
896 { import core.stdc.stdio : printf; printf("writing memory failed\n"); }
897 return false;
899 // and zero
900 ubyte zeroByte = 0;
901 if (!procWriteMem(pid, (&zeroByte)[0..1], targetBuf+cast(uint)libname.length)) {
902 { import core.stdc.stdio : printf; printf("writing memory failed\n"); }
903 return false;
906 // now call __libc_dlopen_mode() to attempt to load the shared library
908 // pass arguments to my function injectSharedLibrary() by loading them into the right registers
909 // see comments in injectSharedLibrary() for more details
910 malloc_regs.ecx = targetBuf;
911 malloc_regs.edi = targetDlopenAddr;
912 if (ptrace(PtraceRequest.SETREGS, pid, null, &malloc_regs) == -1) {
913 { import core.stdc.stdio : printf; printf("can't set registers of target process\n"); }
914 return false;
916 if (!int3Step(pid, ijAddr, ijcSize, &malloc_regs)) {
917 { import core.stdc.stdio : printf; printf("int3 stepping failed\n"); }
918 return false;
921 // check out what the registers look like after calling __libc_dlopen_mode
922 uint libAddr = malloc_regs.eax;
924 // if eax is 0 here, then dlopen failed, and we should bail out cleanly
925 if (libAddr == 0) {
926 { import core.stdc.stdio : printf; printf("prey dlopen failed\n"); }
927 return false;
930 // now check /proc/pid/maps to see whether injection was successful.
931 bool res = false;
932 if (checkloaded(pid, libname)) {
933 { import core.stdc.stdio : printf; printf("injected ok\n"); }
934 res = true;
935 } else {
936 { import core.stdc.stdio : printf; printf("injecting failed\n"); }
939 // as a courtesy, free the buffer that we allocated inside the target
940 // process. we don't really care whether this succeeds, so don't
941 // bother checking the return value.
943 // pass arguments to my function injectSharedLibrary() by loading them into the right registers
944 // see comments in injectSharedLibrary() for more details
945 malloc_regs.ecx = targetBuf;
946 malloc_regs.edi = targetFreeAddr;
947 if (ptrace(PtraceRequest.SETREGS, pid, null, &malloc_regs) == -1) {
948 { import core.stdc.stdio : printf; printf("can't set registers of target process\n"); }
949 return false;
951 int3Step(pid, ijAddr, ijcSize, &malloc_regs);
952 { import core.stdc.stdio : printf; printf("injection complete\n"); }
954 return res;
958 // ////////////////////////////////////////////////////////////////////////// //
959 private:
960 import core.sys.posix.sys.uio : iovec;
961 import core.sys.posix.sys.types : pid_t, ssize_t;
962 import core.stdc.config : c_ulong;
963 extern(C) nothrow @nogc:
965 ssize_t process_vm_readv (pid_t pid, const(iovec)* local_iov, c_ulong liovcnt, const(iovec)* remote_iov, c_ulong riovcnt, c_ulong flags);
966 ssize_t process_vm_writev (pid_t pid, const(iovec)* local_iov, c_ulong liovcnt, const(iovec)* remote_iov, c_ulong riovcnt, c_ulong flags);
969 // Type of the REQUEST argument to `ptrace()`
970 enum PtraceRequest : int {
971 TRACEME = 0, // indicate that the process making this request should be traced; all signals received by this process can be intercepted by its parent, and its parent can use the other `ptrace' requests
972 PEEKTEXT = 1, // return the word in the process's text space at address ADDR
973 PEEKDATA = 2, // return the word in the process's data space at address ADDR
974 PEEKUSER = 3, // return the word in the process's user area at offset ADDR
975 POKETEXT = 4, // write the word DATA into the process's text space at address ADDR
976 POKEDATA = 5, // write the word DATA into the process's data space at address ADDR
977 POKEUSER = 6, // write the word DATA into the process's user area at offset ADDR
978 CONT = 7, // continue the process
979 KILL = 8, // kill the process
980 SINGLESTEP = 9, // single step the process. This is not supported on all machines
981 GETREGS = 12, // get all general purpose registers used by a processes. This is not supported on all machines
982 SETREGS = 13, // set all general purpose registers used by a processes. This is not supported on all machines
983 GETFPREGS = 14, // get all floating point registers used by a processes. This is not supported on all machines
984 SETFPREGS = 15, // set all floating point registers used by a processes. This is not supported on all machines
985 ATTACH = 16, // attach to a process that is already running
986 DETACH = 17, // detach from a process attached to with ATTACH
987 GETFPXREGS = 18, // get all extended floating point registers used by a processes. This is not supported on all machines
988 SETFPXREGS = 19, // set all extended floating point registers used by a processes. This is not supported on all machines
989 SYSCALL = 24, // continue and stop at the next (return from) syscall
990 SETOPTIONS = 0x4200, // set ptrace filter options
991 GETEVENTMSG = 0x4201, // get last ptrace message
992 GETSIGINFO = 0x4202, // get siginfo for process
993 SETSIGINFO = 0x4203, // set new siginfo for process
994 GETREGSET = 0x4204, // get register content
995 SETREGSET = 0x4205, // set register content
996 SEIZE = 0x4206, // like ATTACH, but do not force tracee to trap and do not affect signal or group stop state
997 INTERRUPT = 0x4207, // trap seized tracee
998 LISTEN = 0x4208, // wait for next group event
999 PEEKSIGINFO = 0x4209,
1000 GETSIGMASK = 0x420a,
1001 SETSIGMASK = 0x420b,
1002 SECCOMP_GET_FILTER = 0x420c,
1006 // flag for LISTEN
1007 enum uint PTRACE_SEIZE_DEVEL = 0x80000000u;
1009 // options set using SETOPTIONS
1010 alias PtraceSetOptions = uint;
1011 enum /*PtraceSetOptions*/ : uint {
1012 PTRACE_O_TRACESYSGOOD = 0x00000001u,
1013 PTRACE_O_TRACEFORK = 0x00000002u,
1014 PTRACE_O_TRACEVFORK = 0x00000004u,
1015 PTRACE_O_TRACECLONE = 0x00000008u,
1016 PTRACE_O_TRACEEXEC = 0x00000010u,
1017 PTRACE_O_TRACEVFORKDONE = 0x00000020u,
1018 PTRACE_O_TRACEEXIT = 0x00000040u,
1019 PTRACE_O_TRACESECCOMP = 0x00000080u,
1020 PTRACE_O_EXITKILL = 0x00100000u,
1021 PTRACE_O_SUSPEND_SECCOMP = 0x00200000u,
1022 PTRACE_O_MASK = 0x003000ffu,
1025 // wait extended result codes for the above trace options
1026 enum PtraceEventCodes {
1027 FORK = 1,
1028 VFORK = 2,
1029 CLONE = 3,
1030 EXEC = 4,
1031 VFORK_DONE = 5,
1032 EXIT = 6,
1033 SECCOMP = 7,
1036 // arguments for PEEKSIGINFO
1037 struct PtracePeekSigInfoArgs {
1038 ulong off; // From which siginfo to start
1039 uint flags; // Flags for peeksiginfo
1040 int nr; // How many siginfos to take
1043 enum uint PTRACE_PEEKSIGINFO_SHARED = 1u<<0; // read signals from a shared (process wide) queue
1045 /* Perform process tracing functions. REQUEST is one of the values above, and determines the action to be taken.
1046 * For all requests except TRACEME, PID specifies the process to be traced.
1048 * PID and the other arguments described above for the various requests should
1049 * appear (those that are used for the particular request) as:
1050 * pid_t PID, void *ADDR, int DATA, void *ADDR2
1051 * after REQUEST. */
1052 int ptrace (PtraceRequest request, ...);
1055 /* These are the 32-bit x86 structures. */
1056 struct user_fpregs_struct {
1057 uint cwd;
1058 uint swd;
1059 uint twd;
1060 uint fip;
1061 uint fcs;
1062 uint foo;
1063 uint fos;
1064 uint[20] st_space;
1067 struct user_fpxregs_struct {
1068 ushort cwd;
1069 ushort swd;
1070 ushort twd;
1071 ushort fop;
1072 uint fip;
1073 uint fcs;
1074 uint foo;
1075 uint fos;
1076 uint mxcsr;
1077 uint reserved;
1078 uint[32] st_space; // 8*16 bytes for each FP-reg = 128 bytes
1079 uint[32] xmm_space; // 8*16 bytes for each XMM-reg = 128 bytes
1080 uint[56] padding;
1083 struct user_regs_struct {
1084 uint ebx;
1085 uint ecx;
1086 uint edx;
1087 uint esi;
1088 uint edi;
1089 uint ebp;
1090 uint eax;
1091 uint xds;
1092 uint xes;
1093 uint xfs;
1094 uint xgs;
1095 uint orig_eax;
1096 uint eip;
1097 uint xcs;
1098 uint eflags;
1099 uint esp;
1100 uint xss;
1103 struct user {
1104 user_regs_struct regs;
1105 int u_fpvalid;
1106 user_fpregs_struct i387;
1107 uint u_tsize;
1108 uint u_dsize;
1109 uint u_ssize;
1110 uint start_code;
1111 uint start_stack;
1112 uint signal;
1113 int reserved;
1114 user_regs_struct* u_ar0;
1115 user_fpregs_struct* u_fpstate;
1116 uint magic;
1117 char[32] u_comm;
1118 int[8] u_debugreg;