├── Makefile ├── README.md ├── UNLICENSE ├── main.c ├── psyscall.c └── sysheaders.h /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -g 2 | CFLAGS += -Wall -std=c89 -pedantic 3 | 4 | all: psyscall 5 | 6 | psyscall: main.o psyscall.o 7 | $(CC) $(CFLAGS) $^ -o $@ $(LDLIBS) 8 | 9 | %.o: %.c 10 | $(CC) $(CFLAGS) -c -o $@ $< 11 | 12 | syscalls.inc: 13 | # Parse syscall numbers from sys/syscall.h header. 14 | echo "#include " \ 15 | | $(CC) -dM -E - \ 16 | | sed -n 's/^#define __NR_\([^ ]*\) .*$$/{"\1", __NR_\1},/p' \ 17 | | env LC_ALL=C sort >"$@" 18 | 19 | constants.inc: sysheaders.h 20 | # Filter out bad headers (for example, non-existent header files) if 21 | # preprocessing of all sysheaders.h includes does not succeed. Then, 22 | # generate constant values from the remaining good system headers. 23 | if $(CC) -E "$<" >/dev/null; then $(CC) -dM -E "$<"; else \ 24 | while IFS= read -r inc; \ 25 | do echo "$$inc" | $(CC) -E - >/dev/null && echo "$$inc"; \ 26 | done <"$<" | $(CC) -dM -E -; \ 27 | fi 2>/dev/null \ 28 | | sed -nr 's/^#define ([^_][A-Z0-9_]+) (0x[0-9A-Fa-f]+|[0-9]+)$$/{"\1", (long)\2},/p' \ 29 | | env LC_ALL=C sort >"$@" 30 | 31 | main.o: main.c syscalls.inc constants.inc 32 | $(CC) $(CFLAGS) -c -o $@ $< 33 | 34 | clean: 35 | $(RM) psyscall *.inc *.o 36 | 37 | .PHONY: all clean 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # psyscall 2 | 3 | Linux syscall() injection to external processes *in a portable fashion* using `ptrace(2)`. 4 | 5 | Tested on x86 (Arch Linux & Ubuntu), ARMv7 (Android 6 & 7), MIPS (Debian), PPC64 (Debian). 6 | 7 | Requires `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` (or root privileges). 8 | 9 | Yes, this is just a proof of concept and silly toy. 10 | 11 | ## Examples 12 | 13 | **Kill emacs as vim** 14 | ``` 15 | % ./psyscall `pidof vim` kill `pidof emacs` SIGTERM 16 | [88] syscall(kill, 142, SIGTERM) = 0 17 | ``` 18 | j/k i don't have `emacs` installed. 19 | 20 | **Drop root privileges** 21 | ``` 22 | % pidof wireshark 23 | 609 24 | % sudo ./psyscall 609 getuid 25 | [sudo] password for resilar: 26 | [609] syscall(getuid) = 0 27 | % sudo ./psyscall 609 setuid 1000 28 | [609] syscall(setuid, 1000) = 0 29 | % sudo ./psyscall 609 getuid 30 | [609] syscall(getuid) = 1000 31 | ``` 32 | 33 | **Make `sleep` speak and exit with the code 42** 34 | ``` 35 | % sleep 60 & 36 | [1] 123 37 | % ./psyscall 123 write 1 \"foobar\" 6 38 | foobar[123] syscall(write, 1, "foobar", 6) = 6 39 | % ./psyscall 123 exit 42 40 | [123] syscall(exit, 42) = 42 41 | [1] + exit 42 sleep 60 42 | % wait 123; echo $? 43 | 42 44 | ``` 45 | 46 | **Redirect stderr to /tmp/stderr.log and write getcwd() to it** 47 | ``` 48 | % touch /tmp/stderr.log 49 | % ./psyscall 666 open '"/tmp/stderr.log"' O_RDWR 50 | [666] syscall(open, "/tmp/stderr.log", O_RDWR) = 3 51 | % ./psyscall 666 dup2 3 2 52 | [666] syscall(dup2, 3, 2) = 2 53 | % ./psyscall 666 mmap 0 0x1000 'PROT_READ|PROT_WRITE' 'MAP_PRIVATE|MAP_ANONYMOUS' -1 0 54 | [666] syscall(mmap, 0, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd2f830b000 55 | % ./psyscall 666 getcwd 0x7fd2f830b000 0x1000 56 | [666] syscall(getcwd, 0x7fd2f830b000, 0x1000) = 24 57 | % ./psyscall 666 write 2 0x7fd2f830b000 24 58 | [666] syscall(write, 2, 0x7fd2f830b000, 24) = 24 59 | % cat /tmp/stderr.log 60 | /home/resilar/psyscall-target 61 | ``` 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | extern long psyscall(pid_t pid, long number, ...); 17 | 18 | struct entry { const char *name; long number; }; 19 | static struct entry syscalls[] = { 20 | #include "syscalls.inc" 21 | }; 22 | static struct entry constants[] = { 23 | #include "constants.inc" 24 | }; 25 | 26 | static int entry_cmp(const void *a, const void *b) { 27 | const struct entry *A = a, *B = b; 28 | return strcmp(A->name, B->name); 29 | } 30 | 31 | static long find_syscall(char *name) 32 | { 33 | char *end; 34 | long nr = strtol(name, &end, 0); 35 | if (*end != '\0') { 36 | struct entry key, *hit; 37 | key.name = name; 38 | key.number = 0; 39 | hit = bsearch(&key, syscalls, sizeof(syscalls)/sizeof(struct entry), 40 | sizeof(struct entry), entry_cmp); 41 | return hit ? hit->number : -1; 42 | } 43 | return nr; 44 | } 45 | 46 | /* 47 | * Parses C-style constant (e.g., '42', 'PROT_READ|PROT_WRITE', ...) 48 | */ 49 | long parse_constant(char *arg, int *err) 50 | { 51 | char *end; 52 | long value; 53 | 54 | if (*arg == '\0' || *arg == '|' || *arg == '+') { 55 | *err |= !!*arg; 56 | return 0; 57 | } 58 | 59 | if ((end = arg + strcspn(arg, "+|")) && *end) { 60 | char op = *end; 61 | *end = '\0'; 62 | value = parse_constant(arg, err); 63 | *end++ = op; 64 | switch (op) { 65 | case '+': value += parse_constant(end, err); break; 66 | case '|': value |= parse_constant(end, err); break; 67 | default: *err |= 1; break; 68 | } 69 | return value; 70 | } 71 | 72 | value = strtol(arg, &end, 0); 73 | if (*end) { 74 | if (end == arg) { 75 | struct entry key, *hit; 76 | key.name = arg; 77 | key.number = 0; 78 | hit = bsearch(&key, constants, 79 | sizeof(constants)/sizeof(struct entry), 80 | sizeof(struct entry), entry_cmp); 81 | if (hit) { 82 | value = hit->number; 83 | } else { 84 | *err |= 1; 85 | } 86 | } else { 87 | *err |= 1; 88 | } 89 | } 90 | 91 | return value; 92 | } 93 | 94 | /* 95 | * There are more efficient and easier methods to write remote process memory. 96 | * However, PTRACE_POKEDATA is the most portable, so we use it. 97 | */ 98 | static size_t ptrace_write(pid_t pid, void *addr, void *buf, size_t len) 99 | { 100 | size_t n = len; 101 | int errnold = errno; 102 | errno = 0; 103 | while (!errno && len > 0) { 104 | size_t i, j; 105 | if ((i = ((size_t)addr % sizeof(long))) || len < sizeof(long)) { 106 | union { 107 | long value; 108 | char buf[sizeof(long)]; 109 | } data; 110 | data.value = ptrace(PTRACE_PEEKDATA, pid, (char *)addr-i, 0); 111 | if (!errno) { 112 | for (j = i; j < sizeof(long) && j-i < len; j++) 113 | data.buf[j] = ((char *)buf)[j-i]; 114 | if (!ptrace(PTRACE_POKEDATA, pid, (char *)addr-i, data.value)) { 115 | addr = (char *)addr + (j-i); 116 | buf = (char *)buf + (j-i); 117 | len -= j-i; 118 | } 119 | } 120 | } else { 121 | for (i = 0, j = len/sizeof(long); i < j; i++) { 122 | if (ptrace(PTRACE_POKEDATA, pid, addr, *(long *)buf) == -1) 123 | break; 124 | addr = (char *)addr + sizeof(long); 125 | buf = (char *)buf + sizeof(long); 126 | len -= sizeof(long); 127 | } 128 | } 129 | } 130 | if (!errno) 131 | errno = errnold; 132 | return n - len; 133 | } 134 | 135 | int main(int argc, char *argv[]) 136 | { 137 | int i; 138 | pid_t pid; 139 | unsigned long addr, len; 140 | long ret, number, arg[6] = {0, 0, 0, 0, 0, 0}; 141 | 142 | errno = 0; 143 | if (argc < 3 || argc > 9) { 144 | fprintf(stderr, "syscall() inject0r\n"); 145 | fprintf(stderr, "usage: %s pid syscall [arg0] ... [arg5]\n", argv[0]); 146 | return argc > 1; 147 | } else if (!(pid = atoi(argv[1])) || pid == getpid() || kill(pid, 0)) { 148 | if (errno) 149 | fprintf(stderr, "bad pid: %s (%s)\n", argv[1], strerror(errno)); 150 | else fprintf(stderr, "bad pid: %s\n", argv[1]); 151 | return 2; 152 | } else if ((number = find_syscall(argv[2])) < 0) { 153 | fprintf(stderr, "bad syscall: %s\n", argv[2]); 154 | return 3; 155 | } 156 | 157 | /* 158 | * Parse arguments and count the total length of input strings. 159 | */ 160 | len = 0; 161 | for (i = 3; i < argc; i++) { 162 | const char *p; 163 | int err = 0; 164 | if (argv[i][0] == '"' && (p = strchr(argv[i]+1, '"')) && !p[1]) { 165 | len += p-argv[i]; 166 | continue; 167 | } 168 | arg[i-3] = parse_constant(argv[i], &err); 169 | if (err) { 170 | fprintf(stderr, "bad arg%d: %s\n", i-3, argv[i]); 171 | return 10+i; 172 | } 173 | } 174 | 175 | /* 176 | * Allocate a block of memory for string arguments. 177 | */ 178 | addr = 0; 179 | if (len) { 180 | int status; 181 | long pagesz, sc; 182 | unsigned long j; 183 | if ((sc = find_syscall("mmap2")) == -1) { 184 | if ((sc = find_syscall("mmap")) == -1) { 185 | fprintf(stderr, "__NR_mmap missing\n"); 186 | return 4; 187 | } 188 | } 189 | if ((pagesz = sysconf(_SC_PAGE_SIZE)) == -1) { 190 | fprintf(stderr, "error determining page size errno=%d (%s)\n", 191 | errno, strerror(errno)); 192 | return 5; 193 | } 194 | ret = psyscall(pid, sc, NULL, (len = pagesz * (1 + (len-1)/pagesz)), 195 | PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 196 | if (ret == (long)MAP_FAILED) { 197 | fprintf(stderr, "broken psyscall (or mmap failed) errno=%d (%s)\n", 198 | errno, strerror(errno)); 199 | return 6; 200 | } 201 | addr = (unsigned long)ret; 202 | 203 | if (ptrace(PTRACE_ATTACH, pid, NULL, 0) == -1) { 204 | fprintf(stderr, "ptrace() attach failed: %s\n", strerror(errno)); 205 | psyscall(pid, SYS_munmap, addr, len); 206 | return 7; 207 | } 208 | if (waitpid(pid, &status, 0) == -1 || !WIFSTOPPED(status)) { 209 | fprintf(stderr, "stopping target process failed\n"); 210 | ptrace(PTRACE_DETACH, pid, NULL, 0); 211 | psyscall(pid, SYS_munmap, addr, len); 212 | return 8; 213 | } 214 | 215 | for (i = 3, j = 0; i < argc; i++) { 216 | char *p; 217 | if (argv[i][0] == '"' && (p = strchr(argv[i]+1, '"')) && !p[1]) { 218 | *p = '\0'; 219 | ret = ptrace_write(pid, (char *)addr+j, argv[i]+1, p-argv[i]); 220 | *p = '"'; 221 | if (ret == p-argv[i]) { 222 | arg[i-3] = addr+j; 223 | j += ret; 224 | } else { 225 | fprintf(stderr, "ptrace_write() failed\n"); 226 | ptrace(PTRACE_DETACH, pid, NULL, 0); 227 | psyscall(pid, SYS_munmap, addr, len); 228 | return 9; 229 | } 230 | } 231 | } 232 | ptrace(PTRACE_DETACH, pid, NULL, 0); 233 | } 234 | 235 | /* 236 | * Finally, inject the syscall. 237 | */ 238 | errno = 0; 239 | ret = psyscall(pid, number, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]); 240 | if (len) psyscall(pid, SYS_munmap, addr, len); 241 | if (ret == -1) { 242 | fprintf(stderr, "[%d] psyscall() errno=%d (%s)\n", 243 | pid, errno, strerror(errno)); 244 | return 10; 245 | } 246 | 247 | if (isatty(STDOUT_FILENO)) { 248 | fprintf(stdout, "[%d] syscall(%s", pid, argv[2]); 249 | for (i = 3; i < argc; i++) { 250 | fprintf(stdout, ", %s", arg[i-3] ? argv[i] : "0"); 251 | } 252 | fprintf(stdout, ") = "); 253 | } 254 | fprintf(stdout, ((ret+!ret) & 0xFFF) ? "%ld\n" : "0x%08lx\n", ret); 255 | return 0; 256 | } 257 | -------------------------------------------------------------------------------- /psyscall.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static void *pdlsym(pid_t pid, void *base, const char *symbol); 20 | 21 | #define PT_REGS (sizeof(((struct user *)0)->regs)/sizeof(unsigned long)) 22 | static struct { 23 | enum reg_type { 24 | ARCH_GP = 0x00, 25 | ARCH_PC = 0x01, 26 | ARCH_SP = 0x02, 27 | ARCH_RET = 0x04, 28 | ARCH_ARG = 0x08 29 | } regs[PT_REGS]; 30 | int pc, sp, ret; 31 | } arch; 32 | 33 | static void *stub1(long number, ...) { 34 | syscall(SYS_kill, getpid(), SIGSTOP); 35 | syscall(SYS_getpid, number); 36 | return (void *)syscall(SYS_getppid, 0, 1, 2, 3, 4, 5); 37 | } 38 | 39 | static int stub0(void *x) 40 | { 41 | volatile long pid = (long)getpid(); 42 | ptrace(PTRACE_TRACEME); 43 | while (syscall(SYS_kill, pid, SIGSTOP, &pid) != 1337) { 44 | if (!x) { 45 | x = ((void *(*)(long, ...))((~(unsigned long)stub1 & ~0x3) | 2)) 46 | (~(long)stub0, ~(long)stub0, ~(long)stub0, 47 | ~(long)stub0, ~(long)stub0, ~(long)stub0); 48 | } 49 | } 50 | return pid && !x; 51 | } 52 | 53 | static int init_arch() 54 | { 55 | pid_t child, parent; 56 | int status, i; 57 | unsigned long stack, pagesz; 58 | long regs0[PT_REGS], regs1[PT_REGS], regs[PT_REGS]; 59 | 60 | arch.sp = arch.pc = arch.ret = -1; 61 | memset(arch.regs, 0, sizeof(arch.regs)); 62 | 63 | /* 64 | * Allocate stack for child. 65 | */ 66 | pagesz = sysconf(_SC_PAGE_SIZE); 67 | stack = (unsigned long)mmap(NULL, pagesz, PROT_READ|PROT_WRITE, 68 | MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) + pagesz; 69 | child = clone(stub0, (void *)stack, SIGCHLD, NULL); 70 | if (child == -1) { 71 | fprintf(stderr, "clone(): %s\n", strerror(errno)); 72 | munmap((void *)(stack-pagesz), pagesz); 73 | return 0; 74 | } 75 | parent = getpid(); 76 | waitpid(child, &status, 0); 77 | if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { 78 | fprintf(stderr, "failed to stop a clone\n"); 79 | goto die; 80 | } 81 | if (ptrace(PTRACE_GETREGS, child, NULL, ®s0) == -1) { 82 | fprintf(stderr, "ptrace(PTRACE_GETREGS): %s\n", strerror(errno)); 83 | goto die; 84 | } 85 | 86 | /* 87 | * PC register. 88 | */ 89 | ptrace(PTRACE_CONT, child, NULL, NULL); 90 | waitpid(child, &status, 0); 91 | ptrace(PTRACE_GETREGS, child, NULL, ®s); 92 | for (i = 0; i < PT_REGS; i++) { 93 | if ((regs[i] & ~0x3) == (~(unsigned long)stub1 & ~0x3)) { 94 | unsigned long regsi = regs[i]; 95 | regs[i] = (unsigned long)stub1; 96 | ptrace(PTRACE_SETREGS, child, NULL, regs); 97 | ptrace(PTRACE_CONT, child, NULL, NULL); 98 | waitpid(child, &status, 0); 99 | regs[i] = regsi; 100 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) { 101 | arch.regs[arch.pc = i] |= ARCH_PC; 102 | break; 103 | } 104 | } 105 | } 106 | 107 | /* 108 | * ARG registers. 109 | */ 110 | for (i = 0; i < PT_REGS; i++) { 111 | if (regs[i] == ~(unsigned long)stub0) 112 | arch.regs[i] |= ARCH_ARG; 113 | } 114 | 115 | /* 116 | * Get registers after getpid() and getppid() syscalls. 117 | */ 118 | if (ptrace(PTRACE_SYSCALL, child, NULL, NULL) == -1) { 119 | fprintf(stderr, "ptrace(PTRACE_SYSCALL): %s\n", strerror(errno)); 120 | goto die; 121 | } 122 | waitpid(child, &status, 0); 123 | if (WIFSTOPPED(status) && (WSTOPSIG(status) & ~0x80) == SIGTRAP) { 124 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 125 | waitpid(child, &status, 0); 126 | } 127 | ptrace(PTRACE_GETREGS, child, NULL, ®s1); 128 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 129 | waitpid(child, &status, 0); 130 | if (WIFSTOPPED(status) && (WSTOPSIG(status) & ~0x80) == SIGTRAP) { 131 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 132 | waitpid(child, &status, 0); 133 | } 134 | ptrace(PTRACE_GETREGS, child, NULL, ®s); 135 | 136 | /* 137 | * Mark RET registers. 138 | */ 139 | for (i = 0; i < PT_REGS; i++) { 140 | if (regs1[i] == child && regs[i] == parent) 141 | arch.regs[i] |= ARCH_RET; 142 | } 143 | 144 | /* 145 | * SP register. 146 | */ 147 | ptrace(PTRACE_CONT, child, NULL, NULL); 148 | waitpid(child, &status, 0); 149 | ptrace(PTRACE_GETREGS, child, NULL, ®s); 150 | for (i = 0; i < PT_REGS; i++) { 151 | if (regs0[i] <= stack && stack <= regs0[i] + 0x100) { 152 | if (regs1[i] <= stack && stack <= regs1[i] + pagesz) { 153 | if (regs0[i] == regs[i] && regs1[i] <= regs0[i]) { 154 | if (arch.sp < 0 || regs1[i] < regs1[arch.sp]) 155 | arch.sp = i; 156 | arch.regs[i] |= ARCH_SP; 157 | } 158 | } 159 | } 160 | } 161 | 162 | /* 163 | * RET registers. 164 | */ 165 | for (i = 0; i < PT_REGS; i++) { 166 | if (arch.regs[i] & ARCH_RET) { 167 | unsigned long regsi = regs[i]; 168 | regs[i] = 1337; 169 | ptrace(PTRACE_SETREGS, child, NULL, regs); 170 | ptrace(PTRACE_CONT, child, NULL, NULL); 171 | waitpid(child, &status, 0); 172 | regs[i] = regsi; 173 | 174 | if (WIFEXITED(status)) { 175 | child = -1; 176 | arch.ret = i; 177 | break; 178 | } 179 | } 180 | } 181 | 182 | #if 0 183 | for (i = 0; i < PT_REGS; i++) { 184 | printf("regs[%02d] = 0x%016lX", i, regs[i]); 185 | if (arch.pc == i) printf(" *PC*"); 186 | else if (arch.regs[i] & ARCH_PC) printf(" PC"); 187 | if (arch.sp == i) printf(" *SP*"); 188 | else if (arch.regs[i] & ARCH_SP) printf(" SP"); 189 | if (arch.ret == i) printf(" *RET*"); 190 | else if (arch.regs[i] & ARCH_RET) printf(" RET"); 191 | if (arch.regs[i] & ARCH_ARG) printf(" ARG"); 192 | printf("\n"); 193 | } 194 | #endif 195 | 196 | if (arch.pc < 0) fprintf(stderr, "PC register missing\n"); 197 | if (arch.sp < 0) fprintf(stderr, "SP register missing\n"); 198 | if (arch.ret < 0) fprintf(stderr, "RET register missing\n"); 199 | 200 | die: 201 | if (child >= 0) 202 | kill(child, SIGKILL); 203 | munmap((void *)(stack-pagesz), pagesz); 204 | return arch.sp >= 0 && arch.pc >= 0 && arch.ret >= 0; 205 | } 206 | 207 | /* 208 | * /proc/pid/maps format: 209 | * address perms offset dev inode pathname 210 | * 00400000-00580000 r-xp 00000000 fe:01 4858009 /usr/lib/nethack/nethack 211 | */ 212 | struct proc_map { 213 | void *start, *end; 214 | char perms[4]; 215 | char path[PATH_MAX]; 216 | }; 217 | 218 | static FILE *proc_maps_open(pid_t pid) 219 | { 220 | if (pid) { 221 | char filename[32]; 222 | sprintf(filename, "/proc/%ld/maps", (long)pid); 223 | return fopen(filename, "r"); 224 | } 225 | return fopen("/proc/self/maps", "r"); 226 | } 227 | 228 | static FILE *proc_maps_iter(FILE *it, struct proc_map *map) 229 | { 230 | if (it) { 231 | map->path[0] = '\0'; 232 | if (fscanf(it, "%p-%p %c%c%c%c %*[^ ] %*[^ ] %*[^ ]%*[ ]%[^\n]", 233 | &map->start, &map->end, &map->perms[0], &map->perms[1], 234 | &map->perms[2], &map->perms[3], map->path) >= 6) { 235 | return it; 236 | } 237 | fclose(it); 238 | } 239 | memset(map, 0, sizeof(struct proc_map)); 240 | return 0; 241 | } 242 | 243 | static int proc_maps_find(pid_t pid, void *addr, char *path, 244 | struct proc_map *out) 245 | { 246 | FILE *it = proc_maps_open(pid); 247 | while ((it = proc_maps_iter(it, out))) { 248 | if (path && strcmp(out->path, path) != 0) 249 | continue; 250 | if (addr && !(out->start <= addr && addr < out->end)) 251 | continue; 252 | fclose(it); 253 | return 1; 254 | } 255 | return 0; 256 | } 257 | 258 | long psyscall(pid_t pid, long number, ...) 259 | { 260 | va_list ap; 261 | FILE *it; 262 | pid_t child; 263 | int i, status; 264 | void *stack_va; 265 | struct proc_map libc, map; 266 | unsigned long syscall_va; 267 | long argv[6], regs[PT_REGS], saved[PT_REGS], ret; 268 | static int initialized = 0; 269 | if (!initialized && !(initialized = init_arch())) { 270 | errno = EFAULT; 271 | return -1; 272 | } 273 | 274 | /* 275 | * Stop the target process. 276 | */ 277 | if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) { 278 | fprintf(stderr, "ptrace(PTRACE_ATTACH): %s\n", strerror(errno)); 279 | return -1; 280 | } 281 | waitpid(pid, &status, 0); 282 | if (!WIFSTOPPED(status)) { 283 | fprintf(stderr, "failed to stop target pid=%d\n", (int)pid); 284 | errno = ECHILD; 285 | return -1; 286 | } 287 | 288 | /* 289 | * Find the virtual address of syscall() in the target process. 290 | */ 291 | it = proc_maps_open(pid); 292 | while ((it = proc_maps_iter(it, &libc))) { 293 | char *file = strrchr(libc.path, '/'); 294 | if ((file = strstr(file ? file + 1 : libc.path, "libc")) 295 | && !strcmp(&file[strspn(file+4, "0123456789-.")+4], "so")) { 296 | syscall_va = (unsigned long)pdlsym(pid, libc.start, "syscall"); 297 | if (syscall_va) { 298 | fclose(it); 299 | break; 300 | } 301 | } 302 | } 303 | if (it == NULL || !proc_maps_find(pid, 0, "[stack]", &map)) { 304 | const char *fmt = it ? "stack of pid=%d missing\n" 305 | : "libc of pid=%d not found\n" 306 | "perhaps the target is statically linked?\n"; 307 | fprintf(stderr, fmt, (int)pid); 308 | ptrace(PTRACE_DETACH, pid, NULL, NULL); 309 | errno = EINVAL; 310 | return -1; 311 | } 312 | stack_va = (char *)map.start + 0x80; 313 | 314 | /* 315 | * Capture (local) syscall context from a fork. 316 | */ 317 | va_start(ap, number); 318 | for (i = 0; i < 6; argv[i++] = va_arg(ap, long)); 319 | va_end(ap); 320 | if (!(child = fork())) { 321 | if (ptrace(PTRACE_TRACEME) != -1 && !kill(getpid(), SIGSTOP)) { 322 | ((long (*)(long, ...))((~(unsigned long)psyscall & ~0x3) | 0x2)) 323 | (number, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); 324 | } 325 | exit(0); 326 | } else if (child == -1) { 327 | fprintf(stderr, "fork(): %s\n", strerror(errno)); 328 | ptrace(PTRACE_DETACH, pid, NULL, NULL); 329 | return -1; 330 | } 331 | waitpid(child, &status, 0); 332 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) { 333 | ptrace(PTRACE_CONT, child, NULL, NULL); 334 | waitpid(child, &status, 0); 335 | } 336 | if (!WIFSTOPPED(status)) { 337 | fprintf(stderr, "failed to stop fork\n"); 338 | ptrace(PTRACE_DETACH, pid, NULL, NULL); 339 | kill(child, SIGKILL); 340 | errno = ECHILD; 341 | return -1; 342 | } 343 | ptrace(PTRACE_GETREGS, child, NULL, ®s); 344 | 345 | /* 346 | * Prepare registers and stack. 347 | * TODO: This fails spectacularly if we have less than 0x10 longs of free 348 | * space (per SP register) available in the bottom of the stack. 349 | */ 350 | ptrace(PTRACE_GETREGS, pid, NULL, &saved); 351 | regs[arch.pc] = syscall_va; 352 | for (i = 0; i < PT_REGS; i++) { 353 | if (!arch.regs[i]) { 354 | regs[i] = saved[i]; 355 | } else if (arch.regs[i] & ARCH_SP) { 356 | int j; 357 | if (!proc_maps_find(child, (void *)regs[i], NULL, &map)) 358 | continue; 359 | if (map.perms[0] != 'r' || map.perms[1] != 'w') 360 | continue; 361 | 362 | for (j = 0; j < 0x10; j++) { 363 | ptrace(PTRACE_POKEDATA, pid, (long *)stack_va+j, 364 | ptrace(PTRACE_PEEKDATA, child, (long *)regs[i]+j, NULL)); 365 | } 366 | regs[i] = (long)stack_va; 367 | stack_va = (long *)stack_va+j; 368 | } 369 | } 370 | kill(child, SIGKILL); 371 | 372 | /* 373 | * Execute syscall() in the target process. 374 | */ 375 | ptrace(PTRACE_SETREGS, pid, NULL, ®s); 376 | ptrace(PTRACE_SYSCALL, pid, NULL, NULL); 377 | waitpid(pid, &status, 0); 378 | if (WIFSTOPPED(status) && (WSTOPSIG(status) & ~0x80) == SIGTRAP) { 379 | ptrace(PTRACE_SYSCALL, pid, NULL, NULL); 380 | waitpid(pid, &status, 0); 381 | } 382 | if (WIFEXITED(status)) { 383 | fprintf(stderr, "target pid=%d exited unexpectedly", (int)pid); 384 | errno = ESRCH; 385 | return WEXITSTATUS(status); 386 | } 387 | 388 | /* 389 | * Get result and detach from the target. 390 | */ 391 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 392 | ptrace(PTRACE_SETREGS, pid, NULL, &saved); 393 | ptrace(PTRACE_DETACH, pid, NULL, NULL); 394 | if (WIFSTOPPED(status)) { 395 | ret = regs[arch.ret]; 396 | } else { 397 | fprintf(stderr, "failed to execute injected syscall\n"); 398 | errno = ECHILD; 399 | ret = -1; 400 | } 401 | 402 | return ret; 403 | } 404 | 405 | /* 406 | * The rest of this file contains pdlsym() implementation for ELF systems. 407 | */ 408 | 409 | struct elf { 410 | pid_t pid; 411 | uintptr_t base; 412 | uint8_t class, data; 413 | uint16_t type; 414 | int W; 415 | 416 | int (*getN)(pid_t pid, const void *addr, void *buf, size_t len); 417 | 418 | struct { 419 | uintptr_t offset; 420 | uint16_t size, num; 421 | } phdr; 422 | 423 | uintptr_t symtab, syment; 424 | uintptr_t strtab, strsz; 425 | }; 426 | 427 | static int readN(pid_t pid, const void *addr, void *buf, size_t len) 428 | { 429 | int errnold = errno; 430 | if (!pid) { 431 | memmove(buf, addr, len); 432 | return 1; 433 | } 434 | 435 | errno = 0; 436 | while (!errno && len > 0) { 437 | size_t i, j; 438 | if ((i = ((size_t)addr % sizeof(long))) || len < sizeof(long)) { 439 | union { 440 | long value; 441 | char buf[sizeof(long)]; 442 | } data; 443 | data.value = ptrace(PTRACE_PEEKDATA, pid, (char *)addr - i, NULL); 444 | if (!errno) { 445 | for (j = i; j < sizeof(long) && j-i < len; j++) 446 | ((char *)buf)[j-i] = data.buf[j]; 447 | addr = (char *)addr + (j-i); 448 | buf = (char *)buf + (j-i); 449 | len -= j-i; 450 | } 451 | } else { 452 | for (i = 0, j = len/sizeof(long); i < j; i++) { 453 | *(long *)buf = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); 454 | if (errno) break; 455 | addr = (char *)addr + sizeof(long); 456 | buf = (char *)buf + sizeof(long); 457 | len -= sizeof(long); 458 | } 459 | } 460 | } 461 | if (!errno) 462 | errno = errnold; 463 | return !len; 464 | } 465 | 466 | static int Ndaer(pid_t pid, const void *addr, void *buf, size_t len) 467 | { 468 | int ok = readN(pid, addr, buf, len); 469 | if (ok) { 470 | char *p, *q; 471 | for (p = buf, q = p + len-1; p < q; *p ^= *q, *q ^= *p, *p++ ^= *q--); 472 | } 473 | return ok; 474 | } 475 | 476 | static uint8_t get8(pid_t pid, const void *addr) 477 | { 478 | uint8_t ret; 479 | return readN(pid, addr, &ret, sizeof(uint8_t)) ? ret : 0; 480 | } 481 | static uint16_t get16(struct elf *elf, const void *addr) 482 | { 483 | uint16_t ret; 484 | return elf->getN(elf->pid, addr, &ret, sizeof(uint16_t)) ? ret : 0; 485 | } 486 | static uint32_t get32(struct elf *elf, const void *addr) 487 | { 488 | uint32_t ret; 489 | return elf->getN(elf->pid, addr, &ret, sizeof(uint32_t)) ? ret : 0; 490 | } 491 | static uint64_t get64(struct elf *elf, const void *addr) 492 | { 493 | uint64_t ret; 494 | return elf->getN(elf->pid, addr, &ret, sizeof(uint64_t)) ? ret : 0; 495 | } 496 | 497 | static uintptr_t getW(struct elf *elf, const void *addr) 498 | { 499 | return (elf->class == 0x01) ? (uintptr_t)get32(elf, addr) 500 | : (uintptr_t)get64(elf, addr); 501 | } 502 | 503 | static int loadelf(pid_t pid, const void *addr, struct elf *elf) 504 | { 505 | uint32_t magic; 506 | int i, j, loads; 507 | const char *base = addr; 508 | 509 | /* 510 | * ELF header. 511 | */ 512 | elf->pid = pid; 513 | elf->base = (uintptr_t)base; 514 | if (readN(pid, base, &magic, 4) && !memcmp(&magic, "\x7F" "ELF", 4) 515 | && ((elf->class = get8(pid, base+4)) == 1 || elf->class == 2) 516 | && ((elf->data = get8(pid, base+5)) == 1 || elf->data == 2) 517 | && get8(pid, base+6) == 1) { 518 | union { uint16_t value; char buf[2]; } data; 519 | data.value = (uint16_t)0x1122; 520 | elf->getN = (data.buf[0] & elf->data) ? Ndaer : readN; 521 | elf->type = get16(elf, base + 0x10); 522 | elf->W = (2 << elf->class); 523 | } else { 524 | /* Bad ELF */ 525 | return 0; 526 | } 527 | 528 | /* 529 | * Program headers. 530 | */ 531 | loads = 0; 532 | elf->strtab = elf->strsz = elf->symtab = elf->syment = 0; 533 | elf->phdr.offset = getW(elf, base + 0x18 + elf->W); 534 | elf->phdr.size = get16(elf, base + 0x18 + elf->W*3 + 0x6); 535 | elf->phdr.num = get16(elf, base + 0x18 + elf->W*3 + 0x8); 536 | for (i = 0; i < elf->phdr.num; i++) { 537 | uint32_t phtype; 538 | uintptr_t offset, vaddr, filesz, memsz; 539 | const char *ph = base + elf->phdr.offset + i*elf->phdr.size; 540 | 541 | phtype = get32(elf, ph); 542 | if (phtype == 0 /* PT_NULL */) 543 | break; 544 | if (phtype != 1 /* PT_LOAD */ && phtype != 2 /* PT_DYNAMIC */) 545 | continue; 546 | 547 | offset = getW(elf, ph + elf->W); 548 | vaddr = getW(elf, ph + elf->W*2); 549 | filesz = getW(elf, ph + elf->W*4); 550 | memsz = getW(elf, ph + elf->W*5); 551 | if (vaddr < offset || memsz < filesz) 552 | return 0; 553 | 554 | if (phtype == 1) { /* PT_LOAD */ 555 | if (elf->type == 2) { /* ET_EXEC */ 556 | if (vaddr - offset < elf->base) { 557 | /* This is not the lowest base of the ELF */ 558 | errno = EFAULT; 559 | return 0; 560 | } 561 | } 562 | loads++; 563 | } else if (phtype == 2) { /* PT_DYNAMIC */ 564 | const char *tag; 565 | uintptr_t type, value; 566 | tag = (char *)((elf->type == 2) ? 0 : base) + vaddr; 567 | for (j = 0; 2*j*elf->W < memsz; j++) { 568 | if ((type = getW(elf, tag + 2*elf->W*j))) { 569 | value = getW(elf, tag + 2*elf->W*j + elf->W); 570 | switch (type) { 571 | case 5: elf->strtab = value; break; /* DT_STRTAB */ 572 | case 6: elf->symtab = value; break; /* DT_SYMTAB */ 573 | case 10: elf->strsz = value; break; /* DT_STRSZ */ 574 | case 11: elf->syment = value; break; /* DT_SYMENT */ 575 | default: break; 576 | } 577 | } else { 578 | /* DT_NULL */ 579 | break; 580 | } 581 | } 582 | } 583 | } 584 | 585 | /* Check that we have all program headers required for dynamic linking */ 586 | if (!loads || !elf->strtab || !elf->strsz || !elf->symtab || !elf->syment) 587 | return 0; 588 | 589 | /* String table (immediately) follows the symbol table */ 590 | if (!(elf->symtab < elf->strtab)) 591 | return 0; 592 | 593 | /* Symbol entry size is a non-zero integer that divides symtab size */ 594 | if ((elf->strtab - elf->symtab) % elf->syment) 595 | return 0; 596 | 597 | /* All OK! */ 598 | return 1; 599 | } 600 | 601 | static int sym_iter(struct elf *elf, int i, uint32_t *stridx, uintptr_t *value) 602 | { 603 | if (i*elf->syment < elf->strtab - elf->symtab) { 604 | const char *sym = (char *)elf->symtab + i*elf->syment; 605 | if (elf->symtab < elf->base) 606 | sym += elf->base; 607 | if ((*stridx = get32(elf, sym)) < elf->strsz) { 608 | if ((*value = getW(elf, sym + elf->W)) && elf->type != 2) 609 | *value += elf->base; 610 | return 1; 611 | } 612 | } 613 | return 0; 614 | } 615 | 616 | static void *pdlsym(pid_t pid, void *base, const char *symbol) 617 | { 618 | struct elf elf; 619 | uintptr_t value = 0; 620 | if (loadelf((pid == getpid()) ? 0 : pid, base, &elf)) { 621 | int i; 622 | uint32_t stridx; 623 | const char *pstrtab; 624 | size_t size = strlen(symbol) + 1; 625 | if (size == 0) 626 | return NULL; 627 | pstrtab = (char *)elf.strtab + (elf.strtab < elf.base ? elf.base : 0); 628 | for (i = 0; sym_iter(&elf, i, &stridx, &value); value = 0, i++) { 629 | if (value && stridx+size <= elf.strsz) { 630 | size_t j = 0; 631 | while (j < size) { 632 | char buf[sizeof(long)]; 633 | int n = ((uintptr_t)pstrtab + stridx+j) % sizeof(buf); 634 | n = (size-j < sizeof(buf)) ? size-j : sizeof(buf) - n; 635 | if (!readN(elf.pid, pstrtab + stridx+j, &buf, n)) 636 | break; 637 | if (memcmp(&symbol[j], &buf, n)) 638 | break; 639 | j += n; 640 | } 641 | if (j == size) 642 | break; 643 | } 644 | } 645 | } 646 | return (void *)value; 647 | } 648 | -------------------------------------------------------------------------------- /sysheaders.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | --------------------------------------------------------------------------------