int inject_code(const struct process_hook *ph) { char sbuf1[1024], sbuf2[1024]; struct my_user_regs regs, saved_regs, aregs; int status; size_t v = 0; assert(ph); printf("[+] 64bit mode\n"); if (ptrace(PTRACE_ATTACH, ph->pid, NULL, NULL) < 0) die("[-] ptrace"); waitpid(ph->pid, &status, 0); if (ptrace(PTRACE_GETREGS, ph->pid, NULL, ®s) < 0) die("[-] ptrace"); peek_text(ph->pid, regs.rsp + 1024, sbuf1, sizeof(sbuf1)); peek_text(ph->pid, regs.rsp, sbuf2, sizeof(sbuf2)); /* fake saved return address, triggering a SIGSEGV to catch */ v = 0; poke_text(ph->pid, regs.rsp, (char *)&v, sizeof(v)); poke_text(ph->pid, regs.rsp + 1024, ph->dso, strlen(ph->dso) + 1); memcpy(&saved_regs, ®s, sizeof(regs)); printf("[+] rdi=0x%zx rsp=0x%zx rip=0x%zx\n", regs.rdi, regs.rsp, regs.rip); /* arguments to function we call */ regs.rdi = regs.rsp + 1024; regs.rsi = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE; regs.rip = (size_t)ph->dlopen_address; if (ptrace(PTRACE_SETREGS, ph->pid, NULL, ®s) < 0) die("[-] ptrace"); if (ptrace(PTRACE_CONT, ph->pid, NULL, NULL) < 0) die("[-] ptrace"); /* Should receive a SIGSEGV for return to 0 */ waitpid(ph->pid, &status, 0); if (ptrace(PTRACE_GETREGS, ph->pid, NULL, &aregs) < 0) die("[-] ptrace"); printf("[+] rdi=0x%zx rsp=0x%zx rip=0x%zx\n", aregs.rdi, aregs.rsp, aregs.rip); if (ptrace(PTRACE_SETREGS, ph->pid, 0, &saved_regs) < 0) die("[-] ptrace"); poke_text(ph->pid, saved_regs.rsp + 1024, sbuf1, sizeof(sbuf1)); poke_text(ph->pid, saved_regs.rsp, sbuf2, sizeof(sbuf2)); if (ptrace(PTRACE_DETACH, ph->pid, NULL, NULL) < 0) die("[-] ptrace"); if (aregs.rip != 0) printf("[-] dlopen in target may have failed (no clean NULL fault)\n"); return 0; }
int inject_code(pid_t pid, size_t libc_addr, size_t dlopen_addr, char *dso) { char sbuf1[1024], sbuf2[1024]; struct user_regs_struct regs, saved_regs; int status; if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) die("ptrace 1"); waitpid(pid, &status, 0); if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) < 0) die("ptrace 2"); peek_text(pid, regs.rsp + 1024, sbuf1, sizeof(sbuf1)); peek_text(pid, regs.rsp, sbuf2, sizeof(sbuf2)); /* fake saved return address */ libc_addr = 0x0; poke_text(pid, regs.rsp, (char *)&libc_addr, sizeof(libc_addr)); poke_text(pid, regs.rsp + 1024, dso, strlen(dso) + 1); memcpy(&saved_regs, ®s, sizeof(regs)); /* pointer to &args */ printf("rdi=%zx rsp=%zx rip=%zx\n", regs.rdi, regs.rsp, regs.rip); regs.rdi = regs.rsp + 1024; regs.rsi = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE; regs.rip = dlopen_addr + 2;// kernel bug?! always need to add 2! if (ptrace(PTRACE_SETREGS, pid, NULL, ®s) < 0) die("ptrace 3"); if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) die("ptrace 4"); /* Should receive a SIGSEGV */ waitpid(pid, &status, 0); if (ptrace(PTRACE_SETREGS, pid, 0, &saved_regs) < 0) die("ptrace 5"); poke_text(pid, saved_regs.rsp + 1024, sbuf1, sizeof(sbuf1)); poke_text(pid, saved_regs.rsp, sbuf2, sizeof(sbuf2)); if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) die("ptrace 6"); return 0; }
int fprintf_process(pid_t pid) { // attach to the process if (ptrace(PTRACE_ATTACH, pid, NULL, NULL)) { perror("PTRACE_ATTACH"); check_yama(); return -1; } // wait for the process to actually stop if (waitpid(pid, 0, WSTOPPED) == -1) { perror("wait"); return -1; } // save the register state of the remote process struct user_regs_struct oldregs; if (ptrace(PTRACE_GETREGS, pid, NULL, &oldregs)) { perror("PTRACE_GETREGS"); ptrace(PTRACE_DETACH, pid, NULL, NULL); return -1; } void *rip = (void *)oldregs.rip; printf("their %%rip %p\n", rip); // First, we are going to allocate some memory for ourselves so we don't // need // to stop on the remote process' memory. We will do this by directly // invoking // the mmap(2) system call and asking for a single page. struct user_regs_struct newregs; memmove(&newregs, &oldregs, sizeof(newregs)); newregs.rax = 9; // mmap newregs.rdi = 0; // addr newregs.rsi = PAGE_SIZE; // length newregs.rdx = PROT_READ | PROT_EXEC; // prot newregs.r10 = MAP_PRIVATE | MAP_ANONYMOUS; // flags newregs.r8 = -1; // fd newregs.r9 = 0; // offset uint8_t old_word[8]; uint8_t new_word[8]; new_word[0] = 0x0f; // SYSCALL new_word[1] = 0x05; // SYSCALL new_word[2] = 0xff; // JMP %rax new_word[3] = 0xe0; // JMP %rax // insert the SYSCALL instruction into the process, and save the old word if (poke_text(pid, rip, new_word, old_word, sizeof(new_word))) { goto fail; } // set the new registers with our syscall arguments if (ptrace(PTRACE_SETREGS, pid, NULL, &newregs)) { perror("PTRACE_SETREGS"); goto fail; } // invoke mmap(2) if (singlestep(pid)) { goto fail; } // read the new register state, so we can see where the mmap went if (ptrace(PTRACE_GETREGS, pid, NULL, &newregs)) { perror("PTRACE_GETREGS"); return -1; } // this is the address of the memory we allocated void *mmap_memory = (void *)newregs.rax; if (mmap_memory == (void *)-1) { printf("failed to mmap\n"); goto fail; } printf("allocated memory at %p\n", mmap_memory); printf("executing jump to mmap region\n"); if (singlestep(pid)) { goto fail; } if (ptrace(PTRACE_GETREGS, pid, NULL, &newregs)) { perror("PTRACE_GETREGS"); goto fail; } if (newregs.rip == (long)mmap_memory) { printf("successfully jumped to mmap area\n"); } else { printf("unexpectedly jumped to %p\n", (void *)newregs.rip); goto fail; } // Calculate the position of the fprintf routine in the other process' // address // space. This is a little bit tricky because of ASLR on Linux. What we do // is // we find the offset in memory that libc has been loaded in their process, // and then we find the offset in memory that libc has been loaded in our // process. Then we take the delta betwen our fprintf and our libc start, // and // assume that the same delta will apply to the other process. // // For this mechanism to work, this program must be compiled with -fPIC to // ensure that our fprintf has an address relative to the one in libc. // // Additionally, this could fail if libc has been updated since the remote // process has been restarted. This is a pretty unlikely situation, but if // the // remote process has been running for a long time and you update libc, the // offset of the symbols could have changed slightly. void *their_libc = find_library(pid, libc_string); void *our_libc = find_library(getpid(), libc_string); void *their_fprintf = their_libc + ((void *)fprintf - our_libc); FILE *their_stderr = their_libc + ((void *)stderr - our_libc); printf("their libc %p\n", their_libc); printf("their fprintf %p\n", their_libc); printf("their stderr %p\n", their_stderr); // We want to make a call like: // // fprintf(stderr, "instruction pointer = %p\n", rip); // // To do this we're going to do the following: // // * put a CALL instruction into the mmap area that calls fprintf // * put a TRAP instruction right after the CALL // * put the format string right after the TRAP // * use the TRAP to restore the original text/program state // memory we are going to copy into our mmap area uint8_t new_text[32]; memset(new_text, 0, sizeof(new_text)); // insert a CALL instruction size_t offset = 0; new_text[offset++] = 0xe8; // CALL rel32 int32_t fprintf_delta = compute_jmp(mmap_memory, their_fprintf); memmove(new_text + offset, &fprintf_delta, sizeof(fprintf_delta)); offset += sizeof(fprintf_delta); // insert a TRAP instruction new_text[offset++] = 0xcc; // copy our fprintf format string right after the TRAP instruction memmove(new_text + offset, format, strlen(format)); // update the mmap area printf("inserting code/data into the mmap area at %p\n", mmap_memory); if (poke_text(pid, mmap_memory, new_text, NULL, sizeof(new_text))) { goto fail; } if (poke_text(pid, rip, new_word, NULL, sizeof(new_word))) { goto fail; } // set up our registers with the args to fprintf // memmove(&newregs, &oldregs, sizeof(newregs)); newregs.rax = 0; // no vector registers are used newregs.rdi = (long)their_stderr; // pointer to stderr in the caller newregs.rsi = (long)mmap_memory + offset; // pointer to the format string newregs.rdx = oldregs.rip; // the integer we want to print printf("setting the registers of the remote process\n"); if (ptrace(PTRACE_SETREGS, pid, NULL, &newregs)) { perror("PTRACE_SETREGS"); goto fail; } // continue the program, and wait for the trap printf("continuing execution\n"); ptrace(PTRACE_CONT, pid, NULL, NULL); if (do_wait("PTRACE_CONT")) { goto fail; } if (ptrace(PTRACE_GETREGS, pid, NULL, &newregs)) { perror("PTRACE_GETREGS"); goto fail; } newregs.rax = (long)rip; if (ptrace(PTRACE_SETREGS, pid, NULL, &newregs)) { perror("PTRACE_SETREGS"); goto fail; } new_word[0] = 0xff; // JMP %rax new_word[1] = 0xe0; // JMP %rax poke_text(pid, (void *)newregs.rip, new_word, NULL, sizeof(new_word)); printf("jumping back to original rip\n"); if (singlestep(pid)) { goto fail; } if (ptrace(PTRACE_GETREGS, pid, NULL, &newregs)) { perror("PTRACE_GETREGS"); goto fail; } if (newregs.rip == (long)rip) { printf("successfully jumped back to original %%rip at %p\n", rip); } else { printf("unexpectedly jumped to %p (expected to be at %p)\n", (void *)newregs.rip, rip); goto fail; } // unmap the memory we allocated newregs.rax = 11; // munmap newregs.rdi = (long)mmap_memory; // addr newregs.rsi = PAGE_SIZE; // size if (ptrace(PTRACE_SETREGS, pid, NULL, &newregs)) { perror("PTRACE_SETREGS"); goto fail; } // make the system call printf("making call to mmap\n"); if (singlestep(pid)) { goto fail; } if (ptrace(PTRACE_GETREGS, pid, NULL, &newregs)) { perror("PTRACE_GETREGS"); goto fail; } printf("munmap returned with status %llu\n", newregs.rax); printf("restoring old text at %p\n", rip); poke_text(pid, rip, old_word, NULL, sizeof(old_word)); printf("restoring old registers\n"); if (ptrace(PTRACE_SETREGS, pid, NULL, &oldregs)) { perror("PTRACE_SETREGS"); goto fail; } // detach the process printf("detaching\n"); if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) { perror("PTRACE_DETACH"); goto fail; } return 0; fail: poke_text(pid, rip, old_word, NULL, sizeof(old_word)); if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) { perror("PTRACE_DETACH"); } return 1; }
int inject_code(pid_t pid, size_t libc_addr, size_t dlopen_addr, char *dso_file) { char sbuf1[1024], sbuf2[1024]; struct my_user_regs regs, saved_regs, aregs; int status; char *dso = NULL; size_t v = 0; printf("32bit mode\n"); dso = realpath(dso_file, NULL); if (!dso || access(dso, X_OK) < 0) die(dso ? dso : dso_file); printf("Using normalized path '%s' for injection.\n", dso); if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) die("ptrace 1"); waitpid(pid, &status, 0); if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) < 0) die("ptrace 2"); peek_text(pid, regs.esp + 1024, sbuf1, sizeof(sbuf1)); peek_text(pid, regs.esp, sbuf2, sizeof(sbuf2)); /* fake saved return address, triggering a SIGSEGV to catch */ libc_addr = 0x0; poke_text(pid, regs.esp, (char *)&libc_addr, sizeof(libc_addr)); poke_text(pid, regs.esp + 1024, dso, strlen(dso) + 1); free(dso); memcpy(&saved_regs, ®s, sizeof(regs)); printf("esp=0x%zx eip=0x%zx\n", regs.esp, regs.eip); /* arguments passed on stack this time (x86) */ v = regs.esp + 1024; poke_text(pid, regs.esp + sizeof(size_t), &v, sizeof(v)); v = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE; poke_text(pid, regs.esp + 2*sizeof(size_t), &v, sizeof(v)); /* kernel bug. always add 2; in -m32 mode on 64bit systems its * not needed!!! */ regs.eip = dlopen_addr + 2; if (ptrace(PTRACE_SETREGS, pid, NULL, ®s) < 0) die("ptrace 3"); if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) die("ptrace 4"); /* Should receive a SIGSEGV for return to 0 */ waitpid(pid, &status, 0); if (ptrace(PTRACE_GETREGS, pid, NULL, &aregs) < 0) die("ptrace 5"); printf("esp=0x%zx eip=0x%zx\n", aregs.esp, aregs.eip); if (ptrace(PTRACE_SETREGS, pid, 0, &saved_regs) < 0) die("ptrace 6"); poke_text(pid, saved_regs.esp + 1024, sbuf1, sizeof(sbuf1)); poke_text(pid, saved_regs.esp, sbuf2, sizeof(sbuf2)); if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) die("ptrace 7"); if (aregs.eip != 0) printf("dlopen in target may failed (no clean NULL fault)!\n"); return 0; }
int inject_code(pid_t pid, size_t libc_addr, size_t dlopen_addr, char *dso_file) { char sbuf1[1024], sbuf2[1024]; struct my_user_regs regs, saved_regs, aregs; int status; char *dso = NULL; printf("64bit mode\n"); dso = realpath(dso_file, NULL); if (!dso || access(dso, X_OK) < 0) die(dso ? dso : dso_file); printf("Using normalized path '%s' for injection.\n", dso); if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) die("ptrace 1"); waitpid(pid, &status, 0); if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) < 0) die("ptrace 2"); peek_text(pid, regs.rsp + 1024, sbuf1, sizeof(sbuf1)); peek_text(pid, regs.rsp, sbuf2, sizeof(sbuf2)); /* fake saved return address, triggering a SIGSEGV to catch */ libc_addr = 0x0; poke_text(pid, regs.rsp, (char *)&libc_addr, sizeof(libc_addr)); poke_text(pid, regs.rsp + 1024, dso, strlen(dso) + 1); free(dso); memcpy(&saved_regs, ®s, sizeof(regs)); printf("rdi=0x%zx rsp=0x%zx rip=0x%zx\n", regs.rdi, regs.rsp, regs.rip); /* arguments to function we call */ regs.rdi = regs.rsp + 1024; regs.rsi = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE; regs.rip = dlopen_addr + 2;// kernel bug?! always need to add 2! if (ptrace(PTRACE_SETREGS, pid, NULL, ®s) < 0) die("ptrace 3"); if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) die("ptrace 4"); /* Should receive a SIGSEGV for return to 0 */ waitpid(pid, &status, 0); if (ptrace(PTRACE_GETREGS, pid, NULL, &aregs) < 0) die("ptrace 5"); printf("rdi=0x%zx rsp=0x%zx rip=0x%zx\n", aregs.rdi, aregs.rsp, aregs.rip); if (ptrace(PTRACE_SETREGS, pid, 0, &saved_regs) < 0) die("ptrace 6"); poke_text(pid, saved_regs.rsp + 1024, sbuf1, sizeof(sbuf1)); poke_text(pid, saved_regs.rsp, sbuf2, sizeof(sbuf2)); if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) die("ptrace 7"); if (aregs.rip != 0) printf("dlopen in target may failed (no clean NULL fault)!\n"); return 0; }