fixes for new ncrpc
[nyatools.git] / procutil.d
blob3a5cd6214a105f15c275189bf3da1e93dbcd2170
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.olly.assembler;
20 import iv.vfs;
21 import iv.vfs.io;
23 version = pt_use_mem;
24 static assert((void*).sizeof == 4);
27 // ////////////////////////////////////////////////////////////////////////// //
28 // pid or -1
29 int findProcessByName (const(char)[] procname) {
30 import core.sys.posix.dirent;
31 static assert(dirent.d_name.offsetof == 19);
32 import core.sys.posix.unistd : readlink;
33 import core.stdc.stdio : snprintf;
34 import std.conv : to;
35 import std.string : fromStringz;
37 if (procname.length == 0) return -1;
39 char[128] tbuf = void;
40 char[1024] ebuf = void;
42 auto dir = opendir("/proc/");
43 if (dir is null) return -1;
44 scope(exit) closedir(dir);
46 for (;;) {
47 auto de = readdir(dir);
48 if (de is null) break;
49 if (de.d_type != DT_DIR) continue;
51 int pid = 0;
53 try {
54 pid = de.d_name.ptr.fromStringz.to!int;
55 } catch (Exception) { pid = 0; }
57 if (pid > 1 && pid <= int.max) {
58 snprintf(tbuf.ptr, tbuf.length, "/proc/%s/exe", de.d_name.ptr);
59 tbuf[$-1] = 0;
60 auto len = readlink(tbuf.ptr, ebuf.ptr, ebuf.length);
61 if (len > 0 && len < ebuf.length) {
62 //{ import core.stdc.stdio : printf; ebuf[len] = 0; printf("pid: %u; exe: [%s]\n", pid, ebuf.ptr); }
63 if (ebuf[0..len] == procname) return pid;
64 int pos = cast(int)len;
65 while (pos > 0 && ebuf[pos-1] != '/') --pos;
66 if (ebuf[pos..len] == procname) return pid;
67 if (ebuf[pos..len] == "wine-preloader") {
68 // oops, wine app; get name from command line
69 import core.stdc.stdio : FILE, fopen, fclose, fread;
70 snprintf(tbuf.ptr, tbuf.length, "/proc/%s/cmdline", de.d_name.ptr);
71 tbuf[$-1] = 0;
72 auto fl = fopen(tbuf.ptr, "r");
73 if (fl !is null) {
74 scope(exit) fclose(fl);
75 char[850] line;
76 len = fread(line.ptr, 1, line.length-1, fl);
77 if (len > 0) {
78 line.ptr[len] = 0;
79 //{ import core.stdc.stdio; printf("%u: lx=[%s]\n", pid, line.ptr); }
80 pos = 0;
81 while (pos < len && line.ptr[pos]) ++pos;
82 auto lx = line[0..pos];
83 //{ import std.stdio; writeln("<", lx, ">"); }
84 if (lx == procname) return pid;
85 pos = cast(int)lx.length;
86 while (pos > 0 && lx[pos-1] != '/' && lx[pos-1] != '\\') --pos;
87 if (lx[pos..$] == procname) return pid;
95 return -1;
99 // ////////////////////////////////////////////////////////////////////////// //
100 public align(1) struct MemoryRegionInfo {
101 align(1):
102 uint start, end;
103 string name;
107 // ////////////////////////////////////////////////////////////////////////// //
108 public MemoryRegionInfo[] readMemoryRegions (uint pid, bool includeStack=false) {
109 import std.format : format;
111 static byte hexDigit (char ch) pure nothrow @safe @nogc {
112 pragma(inline, true);
113 return
114 ch >= '0' && ch <= '9' ? cast(byte)(ch-'0') :
115 ch >= 'A' && ch <= 'F' ? cast(byte)(ch-'A'+10) :
116 ch >= 'a' && ch <= 'f' ? cast(byte)(ch-'a'+10) :
120 static uint parseHex(T : const(char)[]) (ref T s) {
121 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
122 if (s.length == 0 || hexDigit(s.ptr[0]) < 0) throw new Exception("hex number expected");
123 uint res = 0;
124 while (s.length) {
125 auto d = hexDigit(s.ptr[0]);
126 if (d < 0) break;
127 uint nr = res*16+d;
128 if (nr < res) throw new Exception("hex overflow");
129 res = nr;
130 s = s[1..$];
132 return res;
135 static void consume(T : const(char)[]) (ref T s, char ch) {
136 if (s.length == 0 && s.ptr[0] != ch) throw new Exception("'"~ch~"' expected");
137 s = s[1..$];
140 static void skipSpaces(T : const(char)[]) (ref T s) {
141 if (s.length == 0 && s.ptr[0] > ' ') throw new Exception("space expected");
142 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
145 MemoryRegionInfo[] res;
146 foreach (char[] ln; VFile("/proc/%s/maps".format(pid)).byLine) {
147 uint start, end;
148 string name;
149 try {
150 //stderr.writeln("*** [", ln, "]");
151 start = parseHex(ln);
152 consume(ln, '-');
153 end = parseHex(ln);
154 skipSpaces(ln);
155 if (start >= end) continue;
156 // skip regions with less than 16 bytes of data
157 if (end-start < 16) continue;
158 if (ln.length < 5) throw new Exception("invalid region description");
159 // skip non-writeable, {executable} and shared regions
160 if (ln.ptr[0] != 'r' || ln.ptr[1] != 'w' /*|| ln.ptr[2] == 'x'*/ || ln.ptr[3] != 'p') continue;
161 ln = ln[4..$];
162 skipSpaces(ln);
163 // if offset is not 0, it is mmaped region, skip it
164 if (parseHex(ln) != 0) continue;
165 // if devicehi or devilelo is not 0, it is mmaped region, skip it
166 skipSpaces(ln);
167 if (parseHex(ln) != 0) continue;
168 consume(ln, ':');
169 if (parseHex(ln) != 0) continue;
170 // if inode is not 0, it is mmaped region, skip it
171 skipSpaces(ln);
172 if (parseHex(ln) != 0) continue;
173 // get region name
174 if (ln.length > 0) {
175 skipSpaces(ln);
176 name = ln.idup;
177 while (name.length && name[$-1] <= ' ') name = name[0..$-1];
179 } catch (Exception) {
180 // can't parse this region, skip it
181 continue;
183 if (name.length > 0 && name.ptr[0] == '[') {
184 // specials; allow only "heap"
185 //writefln("**** %08X:%08X <%s>", start, end, name);
186 if (name.length < 6 || name[0..5] != "[heap") {
187 if (!includeStack) continue;
188 if (name.length < 6 || name[0..6] != "[stack") continue;
191 //stderr.writefln("%08X:%08X <%s>", start, end, name);
192 res ~= MemoryRegionInfo(start, end, name);
194 return res;
198 // ////////////////////////////////////////////////////////////////////////// //
200 * search the target process' /proc/pid/maps entry and find an executable
201 * region of memory that we can use to run code in.
202 * returns zero if nothing was found.
204 public uint findXSpaceInPrey (int pid) {
205 // try to get "ld-" first
206 if (auto lda = findLibInPrey(pid, "ld-")) return lda;
207 import core.stdc.stdio : FILE, fopen, fclose, snprintf, fgets, sscanf;
208 import core.stdc.string : strstr;
209 bool found = false;
210 FILE* fp;
211 char[30] filename;
212 char[850] line;
213 c_ulong addr, eaddr;
214 char[16] perms;
215 snprintf(filename.ptr, filename.length, "/proc/%u/maps", cast(uint)pid);
216 fp = fopen(filename.ptr, "r");
217 if (fp is null) return 0;
218 while (fgets(line.ptr, 850, fp) !is null) {
219 sscanf(line.ptr, "%lx-%lx %s %*s %*s %*d", &addr, &eaddr, perms.ptr);
220 if (strstr(perms.ptr, "x") !is null && eaddr > addr && eaddr-addr >= 512) { found = true; break; }
222 fclose(fp);
223 return (found ? addr : 0);
228 * gets the base address of libc.so inside a process by reading /proc/pid/maps.
229 * returns 0 if nothing was found.
230 * basename is name without version and .so, i.e. "libc-" for libc, for example
232 private uint findLibInPrey (int pid, const(char)[] basename) {
233 import core.stdc.stdio : FILE, fopen, fclose, snprintf, fgets, sscanf;
234 import core.stdc.string : strstr;
235 bool found = false;
236 FILE *fp;
237 char[30] filename;
238 char[850] line;
239 c_ulong addr;
240 char[16] perms;
241 snprintf(filename.ptr, filename.length, "/proc/%d/maps", pid);
242 fp = fopen(filename.ptr, "r");
243 if (fp is null) return 0;
244 while (fgets(line.ptr, 850, fp) !is null) {
245 import std.string : fromStringz;
246 auto ln = line.ptr.fromStringz;
247 while (ln.length && ln[$-1] <= ' ') ln = ln[0..$-1];
248 if (ln.length == 0 || ln.ptr[0] <= ' ') continue;
249 // remove 5 fields
250 foreach (immutable _; 0..5) {
251 while (ln.length > 0 && ln.ptr[0] > ' ') ln = ln[1..$];
252 while (ln.length > 0 && ln.ptr[0] <= ' ') ln = ln[1..$];
254 if (ln.length == 0) continue;
255 int pos = cast(int)ln.length;
256 while (pos > 0 && ln.ptr[pos-1] != '/') --pos;
257 ln = ln[pos..$];
258 if (ln.length < basename.length+3 || ln[0..basename.length] != basename) continue;
259 ln = ln[basename.length..$];
260 pos = 0;
261 // digits and dots
262 while (pos < ln.length) {
263 if (ln.ptr[pos] >= '0' && ln.ptr[pos] <= '9') { ++pos; continue; }
264 if (ln.ptr[pos] != '.') break;
265 if (ln.length-pos > 1 && ln.ptr[pos+1] >= '0' && ln.ptr[pos+1] <= '9') { pos += 2; continue; }
266 break;
268 //{ import std.stdio; stderr.writeln("[", ln, "] - [", ln[pos..$], "]"); }
269 if (pos >= ln.length || ln.ptr[pos] != '.' || ln.length-pos < 3 || ln[pos..pos+3] != ".so") continue;
270 ln = ln[pos+3..$];
271 //{ import std.stdio; stderr.writeln("*[", ln, "]"); }
272 if (ln.length != 0) {
273 // it should be dots and digits
274 if (ln.ptr[0] != '.') continue;
275 pos = 0;
276 while (pos < ln.length) {
277 if (ln.ptr[pos] >= '0' && ln.ptr[pos] <= '9') { ++pos; continue; }
278 if (ln.ptr[pos] != '.') break;
279 if (ln.length-pos > 1 && ln.ptr[pos+1] >= '0' && ln.ptr[pos+1] <= '9') { pos += 2; continue; }
280 break;
282 if (pos < ln.length) continue;
284 // wow, i found her!
285 sscanf(line.ptr, "%lx-%*lx %s %*s %*s %*d", &addr, perms.ptr);
286 if (strstr(perms.ptr, "x") !is null) { found = true; break; }
288 fclose(fp);
289 //{ import core.stdc.stdio : printf; printf("%u: 0x%08x: %d\n", pid, addr, (found ? 1 : 0)); }
290 return (found ? addr : 0);
294 // ////////////////////////////////////////////////////////////////////////// //
295 struct AttachedProc {
296 private:
297 static struct Nfo {
298 uint rc;
299 uint pid;
300 int memfd;
301 bool fdwr;
302 @disable this (this);
305 private:
306 uint nfop;
308 void decRef () nothrow @nogc {
309 if (nfop) {
310 auto n = cast(Nfo*)nfop;
311 if (--n.rc == 0) {
312 import core.stdc.stdlib : free;
313 if (n.memfd >= 0) {
314 import core.sys.posix.unistd : close;
315 close(n.memfd);
317 // 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
318 ptrace(PtraceRequest.DETACH, n.pid, 1, 0); // == 0
319 free(n);
321 nfop = 0;
325 public:
326 this (uint apid) { attachTo(apid); }
327 this (this) nothrow @trusted @nogc { pragma(inline, true); if (nfop) (cast(Nfo*)nfop).rc += 1; }
328 ~this () nothrow @nogc { pragma(inline, true); if (nfop) decRef(); }
330 void close () nothrow @nogc { pragma(inline, true); if (nfop) decRef(); }
332 void attachTo (uint apid) {
333 import core.stdc.stdlib : malloc, free;
334 import core.stdc.string : memset;
335 import core.sys.posix.sys.wait : waitpid, WIFSTOPPED;
336 int status;
337 close();
338 if (apid == 0 || apid == 1) throw new Exception("attach failed");
339 // attach, to the target application, which should cause a SIGSTOP
340 if (ptrace(PtraceRequest.ATTACH, apid, null, null) == -1) throw new Exception("attach failed");
341 // detach on failure, just in case
342 scope(failure) ptrace(PtraceRequest.DETACH, apid, 1, 0); // == 0
343 // wait for the SIGSTOP to take place
344 if (waitpid(apid, &status, 0) == -1 || !WIFSTOPPED(status)) throw new Exception("error waiting target to stop");
345 auto n = cast(Nfo*)malloc(Nfo.sizeof);
346 if (n is null) throw new Exception("out of memory"); // hm...
347 memset(n, 0, Nfo.sizeof);
348 n.rc = 1;
349 n.pid = apid;
350 n.memfd = -666;
351 nfop = cast(uint)n;
354 void opAssign() (in auto ref AttachedProc o) nothrow @nogc {
355 // increase other's rc, and then decrease ours; doing in in another order is unsafe
356 if (o.nfop) (cast(Nfo*)o.nfop).rc += 1;
357 if (nfop) decRef();
358 nfop = o.nfop;
361 @property bool valid () const nothrow @safe @nogc { pragma(inline, true); return (nfop != 0); }
363 private bool openFD () nothrow @nogc {
364 if (!nfop) return false;
365 auto n = cast(Nfo*)nfop;
366 uint pid = n.pid;
367 if (n.memfd == -666) {
368 import core.stdc.stdio : snprintf;
369 import core.sys.posix.fcntl : open, O_RDONLY, O_RDWR;
370 char[64] name = 0;
371 snprintf(name.ptr, name.length-1, "/proc/%d/mem", pid);
372 n.memfd = open(name.ptr, O_RDWR);
373 if (n.memfd < 0) {
374 n.fdwr = false;
375 n.memfd = open(name.ptr, O_RDONLY);
376 if (n.memfd < 0) n.memfd = -1;
377 } else {
378 n.fdwr = true;
381 return (n.memfd >= 0);
384 T[] readBytes(T) (T[] buf, uint addr) {
385 import core.sys.posix.unistd : pread;
386 if (!nfop) throw new Exception("can't read from unintialized attach");
387 if (buf.length > int.max/2) throw new Exception("too many elements in read buffer");
388 if (cast(long)buf.length*T.sizeof > int.max/2) throw new Exception("too many elements in read buffer");
389 if (buf.length == 0) return buf;
390 auto rd = (0x1_0000_0000L-addr);
391 if (rd < T.sizeof) throw new Exception("read error");
392 if (rd > buf.length*T.sizeof) rd = buf.length*T.sizeof;
393 auto n = cast(Nfo*)nfop;
394 uint pid = n.pid;
395 version(pt_use_mem) {
396 if (!openFD) throw new Exception("can't open process memory");
397 rd /= T.sizeof;
398 auto len = pread(n.memfd, buf.ptr, cast(uint)(rd*T.sizeof), addr);
399 if (len < T.sizeof) throw new Exception("read error");
400 return buf[0..len/T.sizeof];
401 } else {
402 import core.stdc.errno;
403 import core.stdc.string : memcpy;
404 auto dp = cast(uint*)buf.ptr;
405 uint len = 0;
406 errno = 0;
407 foreach (immutable _; 0..rd/4) {
408 uint res = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
409 if (res == -1 && errno != 0) throw new Exception("read error");
410 *dp++ = res;
411 addr += 4;
412 len += 4;
413 //if (ptrace(PtraceRequest.POKEDATA, pid, addr, res) != 0) throw new Exception("writing error");
415 if (rd%4) {
416 uint res = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
417 if (res == -1 && errno != 0) throw new Exception("read error");
418 memcpy(dp, &res, cast(uint)(rd%4));
419 len += rd%4;
421 if (len < T.sizeof) throw new Exception("read error");
422 return buf[0..len/T.sizeof];
426 void writeBytes (const(void)[] buf, uint addr) {
427 import core.sys.posix.unistd : pwrite;
428 if (!nfop) throw new Exception("can't write to unintialized attach");
429 if (buf.length == 0) return;
430 auto n = cast(Nfo*)nfop;
431 uint pid = n.pid;
432 if (!openFD) throw new Exception("can't open process memory");
433 if (!n.fdwr) throw new Exception("can't open process memory for writing");
434 long wr = 0x1_0000_0000L-addr;
435 if (wr < buf.length) throw new Exception("write error");
436 auto len = pwrite(n.memfd, buf.ptr, cast(uint)buf.length, addr);
437 if (len != buf.length) throw new Exception("write error");
442 // ////////////////////////////////////////////////////////////////////////// //
443 public T[] procReadMem(T) (uint pid, T[] buf, uint addr) {
444 if (pid <= 2) return null; //throw new Exception("read error");
445 if (buf.length == 0) return buf[];
447 iovec local, remote;
448 local.iov_base = cast(void*)buf.ptr;
449 local.iov_len = buf.length;
450 remote.iov_base = cast(void*)addr;
451 remote.iov_len = buf.length;
452 auto len = process_vm_readv(pid, &local, 1, &remote, 1, 0);
453 if (len < T.sizeof) return null; //throw new Exception("read error");
454 return buf[0..len/T.sizeof];
456 import core.stdc.errno;
457 auto bp = cast(ubyte*)buf.ptr;
458 auto left = buf.length*T.sizeof;
459 uint total;
460 errno = 0;
461 while (left > 0) {
462 import core.stdc.string : memcpy;
463 auto ov = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
464 if (ov == -1 && errno != 0) {
465 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "reading failed at 0x%08x (%u bytes left)\n", addr, cast(uint)left); }
466 return null;
468 if (left >= 4) memcpy(bp, &ov, 4); else memcpy(bp, &ov, left);
469 //{ import core.stdc.stdio : printf; printf("writing 0x%08x at 0x%08x (%u bytes left)\n", v, addr, cast(uint)left); }
470 if (left <= 4) { total += left; left = 0; break; }
471 left -= 4;
472 addr += 4;
473 bp += 4;
474 total += 4;
476 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "procReadMem complete (%u bytes)\n", total); }
477 return buf[0..total/T.sizeof];
481 public bool procWriteMem (uint pid, const(void)[] buf, uint addr) {
482 if (pid <= 2) return false; //throw new Exception("write error");
483 if (buf.length == 0) return true;
485 iovec local, remote;
486 local.iov_base = cast(void*)buf.ptr;
487 local.iov_len = buf.length;
488 remote.iov_base = cast(void*)addr;
489 remote.iov_len = buf.length;
490 auto len = process_vm_writev(pid, &local, 1, &remote, 1, 0);
491 if (len != buf.length) return false; //throw new Exception("write error");
492 return true;
494 auto bp = cast(const(ubyte)*)buf.ptr;
495 auto left = buf.length;
496 while (left > 0) {
497 import core.stdc.string : memcpy;
498 uint v = 0;
499 if (left < 4) {
500 import core.stdc.errno;
501 errno = 0;
502 auto ov = ptrace(PtraceRequest.PEEKDATA, pid, addr, null);
503 if (ov == -1 && errno != 0) return false;
504 v = ov;
506 memcpy(&v, bp, 4);
507 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "writing 0x%08x at 0x%08x (%u bytes left)\n", v, addr, cast(uint)left); }
508 if (ptrace(PtraceRequest.POKEDATA, pid, addr, v) != 0) return false;
509 if (left <= 4) break;
510 left -= 4;
511 addr += 4;
512 bp += 4;
514 //{ import core.stdc.stdio : stderr, fprintf; fprintf("procWriteMem complete\n"); }
515 return true;
519 // ////////////////////////////////////////////////////////////////////////// //
520 // code injector
521 // some code was taken from "linux-inject" project: https://github.com/gaffe23/linux-inject
522 private:
523 import iv.olly.asm1;
524 import iv.olly.disasm2;
527 extern(C) nothrow @nogc {
528 enum RTLD_LAZY = 0x00001; // POSIX
529 void* __libc_dlopen_mode (const(char)* name, int mode);
530 void* __libc_dlsym (void* soh, const(char)* name);
531 int __libc_dlclose (void* soh);
535 private uint getFunctionAddress (const(char)[] funcname) {
536 //import core.sys.linux.dlfcn;
537 if (funcname.length == 0 || funcname.length > 127) return false;
538 char[129] buf = 0;
539 buf[0..funcname.length] = funcname[];
540 void* self = __libc_dlopen_mode("libc.so.6", RTLD_LAZY);
541 void* funcAddr = __libc_dlsym(self, buf.ptr);
542 return cast(uint)funcAddr;
546 // ////////////////////////////////////////////////////////////////////////// //
547 public bool injectSO (int pid, const(char)[] libname, const(char)[] initarg) {
548 static immutable string injectCode = q{
549 ; call __libc_dlopen_mode() to load the shared library
550 push 1 ; 2nd argument to __libc_dlopen_mode(): flag = RTLD_LAZY
551 push libnameofs ; 1st argument to __libc_dlopen_mode(): filename = the buffer we allocated earlier
552 call dlopen ; call __libc_dlopen_mode()
553 add esp,4*2 ; as it is cdecl, we need to pop arguments
554 ; exit if library is not loaded
555 or eax,eax
556 jz quit
558 ; call __libc_dlsym() to find the symbol
559 push initnameofs ; 2nd argument to __libc_dlsym() -- function name
560 push eax ; 1st argument to __libc_dlsym() -- library handle
561 call dlsym ; call __libc_dlsym()
562 add esp,4*2 ; as it is cdecl, we need to pop arguments
564 ; call the init function if it is there
565 or eax,eax
566 jz skipcall
568 push iniargofs ; argument
569 call eax ; call init function
570 pop edx ; as it is cdecl, we need to pop arguments (this is smaller than fixing esp)
571 jmp quit
573 skipcall:
574 mov eax,1 ; success flag
576 quit:
577 int3 ; final out
580 static bool attachTo (uint apid) {
581 import core.stdc.stdlib : malloc, free;
582 import core.stdc.string : memset;
583 import core.sys.posix.sys.wait : waitpid, WIFSTOPPED;
584 int status;
585 // attach, to the target application, which should cause a SIGSTOP
586 if (ptrace(PtraceRequest.ATTACH, apid, null, null) == -1) return false;
587 // detach on failure, just in case
588 // wait for the SIGSTOP to take place
589 if (waitpid(apid, &status, 0) == -1 || !WIFSTOPPED(status)) {
590 ptrace(PtraceRequest.DETACH, apid, 1, 0);
591 return false;
593 return true;
596 static bool singleStep (uint apid) {
597 import core.stdc.stdlib : malloc, free;
598 import core.stdc.string : memset;
599 import core.sys.posix.sys.wait : waitpid, WIFEXITED;
600 int status;
601 if (ptrace(PtraceRequest.SINGLESTEP, apid, null, null) == -1) return false;
602 if (waitpid(apid, &status, 0) == -1 || WIFEXITED(status)) return false;
603 return true;
606 static bool singleStepUntilInt3 (uint apid, uint saddr, uint len, user_regs_struct* regsp) {
607 import core.stdc.stdlib : malloc, free;
608 import core.stdc.string : memset;
609 import core.sys.posix.sys.wait : waitpid, WIFEXITED;
610 user_regs_struct regs;
611 if (regsp is null) regsp = &regs;
612 uint oeip = 0;
613 for (;;) {
614 int status;
615 if (ptrace(PtraceRequest.SINGLESTEP, apid, null, null) == -1) return false;
616 if (waitpid(apid, &status, 0) == -1 || WIFEXITED(status)) return false;
617 if (ptrace(PtraceRequest.GETREGS, apid, null, regsp) == -1) return false;
618 ubyte[MAXCMDSIZE] ib;
619 procReadMem(apid, ib[], regsp.eip);
621 import core.stdc.stdio : stderr, fprintf;
622 //fprintf(stderr, "EIP=0x%08x (%02X)\n", regsp.eip, ib);
623 DisasmData da;
624 DAConfig cfg;
625 cfg.tabarguments = true;
626 auto dclen = disasm(ib[], regsp.eip, &da, DA_DUMP|DA_TEXT|DA_HILITE, &cfg);
627 if (dclen == 0) {
628 fprintf(stderr, "EIP=0x%08x FUCK!\n", regsp.eip);
629 } else {
630 import std.stdio : stderr;
631 //stderr.writefln("0x%08x: %-16s %s", da.ip, da.dumpstr, da.resstr);
633 if (oeip == regsp.eip) {
634 if (ib[0] == 0xf4) {
635 import std.stdio : stderr;
636 stderr.writefln("0x%08x: %-16s %s", da.ip, da.dumpstr, da.resstr);
637 return false;
640 oeip = regsp.eip;
642 if (ib[0] == 0xcc) break;
644 return true;
647 static bool int3Step (string msg, uint apid, uint saddr, uint len, user_regs_struct* regsp) {
648 version(all) {
649 import core.stdc.stdlib : malloc, free;
650 import core.stdc.string : memset;
651 import core.sys.posix.signal : siginfo_t, SIGTRAP;
652 import core.sys.posix.sys.wait : waitpid, WIFEXITED;
653 user_regs_struct regsxx;
654 if (regsp is null) regsp = &regsxx;
655 //{ import std.stdio : stderr; stderr.writeln("STEPPING: ", msg); }
656 for (;;) {
657 int status;
658 if (ptrace(PtraceRequest.CONT, apid, null, null) == -1) return false;
659 if (waitpid(apid, &status, 0) == -1 || WIFEXITED(status)) {
660 { import core.stdc.stdio : printf; printf("XFUCK0\n"); }
661 return false;
663 // check what signal we received
664 siginfo_t tsig;
665 if (ptrace(PtraceRequest.GETSIGINFO, apid, null, &tsig) == -1) {
666 { import core.stdc.stdio : printf; printf("XFUCK1\n"); }
667 return false;
669 if (tsig.si_signo == SIGTRAP) {
670 memset(regsp, 0, user_regs_struct.sizeof);
671 if (ptrace(PtraceRequest.GETREGS, apid, null, regsp) == -1) {
672 { import core.stdc.stdio : printf; printf("XFUCK2\n"); }
673 return false;
675 if (regsp.eip >= saddr && regsp.eip <= saddr+len) {
676 ubyte bt;
677 if (procReadMem(apid, (&bt)[0..1], regsp.eip).length == 1 && bt == 0x90) {
678 import core.stdc.stdio : printf;
679 printf("eip=0x%08x eax=0x%08x ebx=0x%08x ecx=0x%08x edx=0x%08x esi=0x%08x edi=0x%08x\n", regsp.eip, regsp.eax, regsp.ebx, regsp.ecx, regsp.edx, regsp.esi, regsp.edi);
680 continue;
682 //{ import core.stdc.stdio : printf; printf("*** %02X 0x%08x\n", bt, regsp.eip); }
683 //{ import core.stdc.stdio : printf; printf("EIP=0x%08x\n", regsp.eip); }
684 return true;
686 { import core.stdc.stdio : printf; printf("trap signal at 0x%08x\n", regsp.eip); }
687 // not in our function, continue
688 } else {
689 // some other signal, skip it
690 { import core.stdc.stdio : printf; printf("non-trap signal at 0x%08x (%u)\n", regsp.eip, cast(uint)tsig.si_signo); }
691 return false;
694 version(all) {
695 import core.thread;
696 import core.time;
697 Thread.sleep(500.msecs);
699 return true;
700 } else {
701 { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "========================\n"); }
702 user_regs_struct regs;
703 if (regsp is null) regsp = &regs;
704 if (ptrace(PtraceRequest.GETREGS, apid, null, regsp) == -1) return false;
705 { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "ecx=0x%08x; edi=0x%08x\n", regsp.ecx, regsp.edi); }
706 auto res = singleStepUntilInt3(apid, saddr, len, regsp);
707 if (res) {
708 // skip int3
709 //++regsp.eip;
710 //if (ptrace(PtraceRequest.SETREGS, apid, null, regsp) == -1) return false;
712 { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "res:%u; eax=0x%08x\n", (res ? 1 : 0), regsp.eax); }
713 return res;
717 import core.sys.posix.unistd : getpid;
719 if (pid <= 1 || libname.length == 0 || libname.length > 1023) return false;
720 if (initarg.length > 1023) return false; // alas
722 auto mypid = getpid();
723 auto mylibcaddr = findLibInPrey(mypid, "libc-");
724 if (mylibcaddr == 0) {
725 { import core.stdc.stdio : printf; printf("no libc found in our process (WTF?!)\n"); }
726 return false;
729 // find a good address to copy code to
730 uint ijAddr = findXSpaceInPrey(pid);
731 if (ijAddr == 0) {
732 { import core.stdc.stdio : printf; printf("can't find free space to paste our code\n"); }
733 return false;
735 //{ import core.stdc.stdio : printf; printf("[0x%08x]\n", addr); }
737 // find the addresses of the syscalls that we'd like to use inside the
738 // target, as loaded inside THIS process (i.e. NOT the target process)
739 auto mallocAddr = getFunctionAddress("malloc");
740 if (mallocAddr == 0) {
741 { import core.stdc.stdio : printf; printf("no malloc found in our process\n"); }
742 return false;
744 auto freeAddr = getFunctionAddress("free");
745 if (freeAddr == 0) {
746 { import core.stdc.stdio : printf; printf("no free found in our process\n"); }
747 return false;
749 auto dlopenAddr = getFunctionAddress("__libc_dlopen_mode");
750 if (dlopenAddr == 0) {
751 { import core.stdc.stdio : printf; printf("no dlopen found in our process\n"); }
752 return false;
754 auto dlsymAddr = getFunctionAddress("__libc_dlsym");
755 if (dlopenAddr == 0) {
756 { import core.stdc.stdio : printf; printf("no dlsym found in our process\n"); }
757 return false;
760 // use the base address of libc to calculate offsets for the syscalls we want to use
761 uint mallocOffset = mallocAddr-mylibcaddr;
762 uint freeOffset = freeAddr-mylibcaddr;
763 uint dlopenOffset = dlopenAddr-mylibcaddr;
764 uint dlsymOffset = dlsymAddr-mylibcaddr;
766 // get the target process' libc address and use it to find the
767 // addresses of the syscalls we want to use inside the target process
768 uint targetLibcAddr = findLibInPrey(pid, "libc-");
769 if (targetLibcAddr == 0) return false;
772 ubyte[] ijcode;
773 uint ijcodeUsed = 0;
774 // build injection code
776 auto ass = new Assembler(ijAddr);
777 ass.addLabel("malloc", targetLibcAddr+mallocOffset);
778 ass.addLabel("free", targetLibcAddr+freeOffset);
779 ass.addLabel("dlopen", targetLibcAddr+dlopenOffset);
780 ass.addLabel("dlsym", targetLibcAddr+dlsymOffset);
781 ass.addLines(injectCode);
783 void putAsmStr (const(void)[] s) {
784 import std.format : format;
785 foreach (ubyte b; cast(const(ubyte[]))s) {
786 string ln = " db 0x%02x".format(b);
787 //{ import iv.vfs.io; writeln("[", ln, "]"); }
788 ass.addLines(ln);
790 ass.addLines(" db 0x00");
793 // put strings
794 ass.addLabelHere("libnameofs");
795 putAsmStr(libname);
796 ass.addLabelHere("initnameofs");
797 putAsmStr("initme");
798 ass.addLabelHere("iniargofs");
799 putAsmStr(initarg);
801 ijcode = ass.getCode();
802 //{ import std.stdio; writeln("assembled to ", ijcode.length, " bytes"); }
803 //ass.disasmCode(ijcode, ass.orgpc);
804 ijcodeUsed = cast(uint)ijcode.length;
806 scope(exit) ijcode.destroy;
808 user_regs_struct oldregs, regs;
810 if (!attachTo(pid)) {
811 { import core.stdc.stdio : printf; printf("can't attach to target process\n"); }
812 return false;
815 if (ptrace(PtraceRequest.GETREGS, pid, null, &oldregs) == -1) {
816 ptrace(PtraceRequest.DETACH, pid, 1, 0);
817 { import core.stdc.stdio : printf; printf("can't get registers of target process\n"); }
818 return false;
821 ubyte[] bkdata = new ubyte[](ijcode.length);
822 bool bkdataUsed = false;
824 scope(exit) {
825 // restore data and registers, and detach
826 if (bkdataUsed) procWriteMem(pid, bkdata[], ijAddr);
827 bkdata.destroy;
828 ptrace(PtraceRequest.SETREGS, pid, null, &oldregs);
829 ptrace(PtraceRequest.DETACH, pid, 1, 0);
830 //{ import core.stdc.stdio : printf; printf("prey process restored\n"); }
833 // if prey is somewhere below libc, trace it until it returns
834 // this should prevent deadlocks on multithreaded apps (i hope)
835 while (oldregs.eip >= targetLibcAddr) {
836 //{ import core.stdc.stdio : printf; printf("EIP=0x%08x\n", oldregs.eip); }
837 if (!singleStep(pid)) {
838 { import core.stdc.stdio : printf; printf("singlestepping failed\n"); }
839 return false;
841 if (ptrace(PtraceRequest.GETREGS, pid, null, &oldregs) == -1) {
842 { import core.stdc.stdio : printf; printf("can't get registers of target process\n"); }
843 return false;
846 regs = oldregs;
847 //{ import core.stdc.stdio : printf; printf("EIP=0x%08x\n", regs.eip); }
849 // back up whatever data used to be at the address we want to modify.
850 if (procReadMem(pid, bkdata[], ijAddr).length != bkdata.length) {
851 { import core.stdc.stdio : printf; printf("can't read process memory\n"); }
852 return false;
854 bkdataUsed = true;
856 if (!procWriteMem(pid, ijcode[], ijAddr)) {
857 { import core.stdc.stdio : printf; printf("can't write process memory\n"); }
858 return false;
861 // execute our injected code
862 regs.eip = ijAddr;
863 if (ptrace(PtraceRequest.SETREGS, pid, null, &regs) == -1) {
864 { import core.stdc.stdio : printf; printf("can't set registers of target process\n"); }
865 return false;
867 if (!int3Step("boo", pid, ijAddr, ijcodeUsed, &regs)) {
868 { import core.stdc.stdio : printf; printf("int3 stepping failed\n"); }
869 return false;
871 //{ import core.stdc.stdio : printf; printf("EIP=0x%08x\n", regs.eip); }
873 // at this point, the target should has success in eax (!=0)
874 uint targetBuf = regs.eax;
875 if (regs.eax == 0) {
876 { import core.stdc.stdio : printf; printf("target process failed to initialize\n"); }
877 return false;
880 return true;
884 // ////////////////////////////////////////////////////////////////////////// //
885 private:
886 import core.sys.posix.sys.uio : iovec;
887 import core.sys.posix.sys.types : pid_t, ssize_t;
888 import core.stdc.config : c_ulong;
889 extern(C) nothrow @nogc:
891 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);
892 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);
895 // Type of the REQUEST argument to `ptrace()`
896 enum PtraceRequest : int {
897 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
898 PEEKTEXT = 1, // return the word in the process's text space at address ADDR
899 PEEKDATA = 2, // return the word in the process's data space at address ADDR
900 PEEKUSER = 3, // return the word in the process's user area at offset ADDR
901 POKETEXT = 4, // write the word DATA into the process's text space at address ADDR
902 POKEDATA = 5, // write the word DATA into the process's data space at address ADDR
903 POKEUSER = 6, // write the word DATA into the process's user area at offset ADDR
904 CONT = 7, // continue the process
905 KILL = 8, // kill the process
906 SINGLESTEP = 9, // single step the process. This is not supported on all machines
907 GETREGS = 12, // get all general purpose registers used by a processes. This is not supported on all machines
908 SETREGS = 13, // set all general purpose registers used by a processes. This is not supported on all machines
909 GETFPREGS = 14, // get all floating point registers used by a processes. This is not supported on all machines
910 SETFPREGS = 15, // set all floating point registers used by a processes. This is not supported on all machines
911 ATTACH = 16, // attach to a process that is already running
912 DETACH = 17, // detach from a process attached to with ATTACH
913 GETFPXREGS = 18, // get all extended floating point registers used by a processes. This is not supported on all machines
914 SETFPXREGS = 19, // set all extended floating point registers used by a processes. This is not supported on all machines
915 SYSCALL = 24, // continue and stop at the next (return from) syscall
916 SETOPTIONS = 0x4200, // set ptrace filter options
917 GETEVENTMSG = 0x4201, // get last ptrace message
918 GETSIGINFO = 0x4202, // get siginfo for process
919 SETSIGINFO = 0x4203, // set new siginfo for process
920 GETREGSET = 0x4204, // get register content
921 SETREGSET = 0x4205, // set register content
922 SEIZE = 0x4206, // like ATTACH, but do not force tracee to trap and do not affect signal or group stop state
923 INTERRUPT = 0x4207, // trap seized tracee
924 LISTEN = 0x4208, // wait for next group event
925 PEEKSIGINFO = 0x4209,
926 GETSIGMASK = 0x420a,
927 SETSIGMASK = 0x420b,
928 SECCOMP_GET_FILTER = 0x420c,
932 // flag for LISTEN
933 enum uint PTRACE_SEIZE_DEVEL = 0x80000000u;
935 // options set using SETOPTIONS
936 alias PtraceSetOptions = uint;
937 enum /*PtraceSetOptions*/ : uint {
938 PTRACE_O_TRACESYSGOOD = 0x00000001u,
939 PTRACE_O_TRACEFORK = 0x00000002u,
940 PTRACE_O_TRACEVFORK = 0x00000004u,
941 PTRACE_O_TRACECLONE = 0x00000008u,
942 PTRACE_O_TRACEEXEC = 0x00000010u,
943 PTRACE_O_TRACEVFORKDONE = 0x00000020u,
944 PTRACE_O_TRACEEXIT = 0x00000040u,
945 PTRACE_O_TRACESECCOMP = 0x00000080u,
946 PTRACE_O_EXITKILL = 0x00100000u,
947 PTRACE_O_SUSPEND_SECCOMP = 0x00200000u,
948 PTRACE_O_MASK = 0x003000ffu,
951 // wait extended result codes for the above trace options
952 enum PtraceEventCodes {
953 FORK = 1,
954 VFORK = 2,
955 CLONE = 3,
956 EXEC = 4,
957 VFORK_DONE = 5,
958 EXIT = 6,
959 SECCOMP = 7,
962 // arguments for PEEKSIGINFO
963 struct PtracePeekSigInfoArgs {
964 ulong off; // From which siginfo to start
965 uint flags; // Flags for peeksiginfo
966 int nr; // How many siginfos to take
969 enum uint PTRACE_PEEKSIGINFO_SHARED = 1u<<0; // read signals from a shared (process wide) queue
971 /* Perform process tracing functions. REQUEST is one of the values above, and determines the action to be taken.
972 * For all requests except TRACEME, PID specifies the process to be traced.
974 * PID and the other arguments described above for the various requests should
975 * appear (those that are used for the particular request) as:
976 * pid_t PID, void *ADDR, int DATA, void *ADDR2
977 * after REQUEST. */
978 int ptrace (PtraceRequest request, ...);
981 /* These are the 32-bit x86 structures. */
982 struct user_fpregs_struct {
983 uint cwd;
984 uint swd;
985 uint twd;
986 uint fip;
987 uint fcs;
988 uint foo;
989 uint fos;
990 uint[20] st_space;
993 struct user_fpxregs_struct {
994 ushort cwd;
995 ushort swd;
996 ushort twd;
997 ushort fop;
998 uint fip;
999 uint fcs;
1000 uint foo;
1001 uint fos;
1002 uint mxcsr;
1003 uint reserved;
1004 uint[32] st_space; // 8*16 bytes for each FP-reg = 128 bytes
1005 uint[32] xmm_space; // 8*16 bytes for each XMM-reg = 128 bytes
1006 uint[56] padding;
1009 struct user_regs_struct {
1010 uint ebx;
1011 uint ecx;
1012 uint edx;
1013 uint esi;
1014 uint edi;
1015 uint ebp;
1016 uint eax;
1017 uint xds;
1018 uint xes;
1019 uint xfs;
1020 uint xgs;
1021 uint orig_eax;
1022 uint eip;
1023 uint xcs;
1024 uint eflags;
1025 uint esp;
1026 uint xss;
1029 struct user {
1030 user_regs_struct regs;
1031 int u_fpvalid;
1032 user_fpregs_struct i387;
1033 uint u_tsize;
1034 uint u_dsize;
1035 uint u_ssize;
1036 uint start_code;
1037 uint start_stack;
1038 uint signal;
1039 int reserved;
1040 user_regs_struct* u_ar0;
1041 user_fpregs_struct* u_fpstate;
1042 uint magic;
1043 char[32] u_comm;
1044 int[8] u_debugreg;