/* * m_rev * Linux 2.6 x86/x86-64 ptrace()-based argv[] cloaking utility by * ernie@ernie * * Any feedback to ernie.at.ernie@gmail.com * * Brief description how this thing works in steps: * 1. Process forks a child. * 2. Child begins to ptrace the parent process. * 3. Parent performs a "syscall slide" and then executes given target binary. * 4. Child parses parents stack (ENVV/AUXV), just after return from execve. * 5. Child saves the entry point code block of newly created process. * 6. Places stage one code to the entry point location. Stage one code * consists of: * - INT3 trap * - stack space allocation * - mprotect this space to be executable * - INT3 * - prctl PR_SET_NAME syscall * - INT3 * 7. Goes through all INT3 traps and reaches end of the stage one code. * 8. Places target arguments/environment on the stack. * 9. Places stage two code on the stack. Stage two code consists of: * - waitpid/wait4 syscall * - jump back to the entry point * 10. Recovers the entry point code block. * 11. Child exits. * 12. Parent continues with target arguments. * * FAQ: * Q: Why does child ptrace parent? * A: To preserve session. (Is there a better way to do that? Tell me about it.) * * Compilation (you need Linux kernel headers): * make m_rev * or for 64-bit * make CFLAGS=-DBIT64 m_rev * * Example: * $ m_rev * usage: ./m_rev_new [[args] --] runthis -t this -a args -- fakethis -f faked - * a args * [args] * sleep - sleep 15 seconds before exit * clearenv - clear environment * $ m_rev sleep 64 -- zZzZ -z -Z * $ ps xf * 2607 ? S 0:00 sshd: nh@pts/1 * 2608 pts/1 Ss 0:00 \_ -bash * 2650 pts/1 S+ 0:00 \_ zZzZ -z -Z * * TODO: * - mv (obsfucation for real location) * - Major code clean-up (i.e. merge logic of main 32/64-bit code parts). * - More overflow checks. * - Test with grsec. * - Check out portability possibilities (*BSD anyone?). * - setsid option * * Changelog: * ver 0.2 (2008-01-25) * - ENV pointer array fix * - prctl() (embedded in the first stage code) * - ET_DYN/auxv processing * * ver 0.1 (2008-01-16) * - initial release * * * Keywords: hide hidden arguments args cloak command cmd line linux x86 x86-64 * argv prctl cover mask ptrace * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef BIT64 #define BIT32 #define BITSIZE 4 #else #define BITSIZE 8 #endif #define VECTORSPACE 0x64 * BITSIZE #define DEBUG #define HELP #define SEARCHPATH #define ALIGN(x,a) (((x)+(a)-1)&~((a)-1)) int copy_from_debugee(pid_t pid, void *addr, void *to, size_t len) { #ifdef BIT32 __u32 data; #else __u64 data; #endif if (len < BITSIZE || len % BITSIZE) return -1; while (len) { data = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); if (errno != 0) return -1; len -= BITSIZE; addr += BITSIZE; memcpy(to, &data, BITSIZE); to += BITSIZE; } return 0; } int copy_to_debugee(pid_t pid, void *addr, void *from, size_t len) { int ret; #ifdef BIT32 __u32 data; #else __u64 data; #endif if (len < BITSIZE || len % BITSIZE) return -1; while (len) { memcpy(&data, from, BITSIZE); ret = ptrace(PTRACE_POKEDATA, pid, addr, data); if (ret) return -1; len -= BITSIZE; addr += BITSIZE; from += BITSIZE; } return 0; } int copy_qw_to_debugee(pid_t pid, void *addr, void *from) { return copy_to_debugee(pid, addr, from, BITSIZE); } void * get_entry_point(char *path) { FILE *bin; int ret; #ifdef BIT32 Elf32_Ehdr elfh; #else Elf64_Ehdr elfh; #endif #ifdef SEARCHPATH char *pathenv = getenv("PATH"); char pathm[1024], *pp; struct stat st; if (pathenv && strlen(pathenv) < 16384 && strchr(path, '/') == NULL) { pp = pathenv = strdup(pathenv); if (pp == NULL) err("assert"); do { if (*pp == ':') { *pp++ = '\0'; } } while (*pp++); while (pathenv < pp) { if (snprintf(pathm, sizeof(pathm) - 1, "%s/%s", pathenv, path) < 0) { err("die"); } if (!stat(pathm, &st)) { chdir(pathenv); break; } pathenv += strlen(pathenv) + 1; } } /* * We are not free()-ing pathenv, but we know about it, right? */ #endif bin = fopen(path, "rb"); if (bin == NULL) return NULL; ret = fread(&elfh, sizeof(elfh), 1, bin); if (ret != 1) { fclose(bin); return NULL; } /* * More ELF checks here. */ fclose(bin); /* * Dirty sexy money. */ if (elfh.e_type & ET_DYN && !(elfh.e_type & ET_EXEC)) return (elfh.e_entry + ((0x555555554AAA - /* vaddr */ 0) & ~4095)); return elfh.e_entry; } int test_print(unsigned char *addr, size_t len) { int i; for (i = 0; i < len; i++) printf("%02x ", addr[i]); printf("\n"); return 0; } void err(char *reason) { #ifdef DEBUG fprintf(stderr, "err: %s\n", reason); #endif exit(1); } void usage(char *p) { #ifdef HELP printf("usage: %s [[args] --] runthis -t this -a args" " -- fakethis -f faked -a args\n", p); printf(" [args]\n"); printf(" sleep - sleep 15 seconds before exit\n"); printf(" clearenv - clear environment\n"); #endif exit(1); } void dump_stack(pid_t pid) { #ifdef BIT32 __u32 storage; #else __u64 storage; #endif void *sp; struct user_regs_struct regs; if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) err("dump_stack"); #ifdef BIT32 sp = regs.esp; #else sp = regs.rsp; #endif while (1) { storage = ptrace(PTRACE_PEEKDATA, pid, sp, 0); if (errno) return; printf("%p: %p\n", sp, storage); sp += BITSIZE; } } #define VECTOR_AUX 0 #define VECTOR_ENV 1 void * get_ivs(pid_t pid, int vector) { #ifdef BIT32 Elf32_auxv_t auxie; __u32 storage; #else Elf64_auxv_t auxie; __u64 storage; #endif struct user_regs_struct regs; void *sp, *sp_env; if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) err("get_auxv_entry"); #ifdef BIT32 sp = regs.esp; #else sp = regs.rsp; #endif storage = ptrace(PTRACE_PEEKDATA, pid, sp, 0); if (errno) err("get_auxv_entry"); sp += storage * BITSIZE + BITSIZE; storage = ptrace(PTRACE_PEEKDATA, pid, sp, 0); if (errno) err("get_auxv_entry - PTRACE_PEEKDATA"); if (storage) { dump_stack(pid); err("get_auxv_entry - stack is bORken"); } sp += BITSIZE; sp_env = sp; while (storage = ptrace(PTRACE_PEEKDATA, pid, sp, 0)) { if (errno) err("get_auxv_entry - PTRACE_PEEKDATA"); sp += BITSIZE; } sp += BITSIZE; if (vector == VECTOR_ENV) { char *p = malloc(sp - sp_env + BITSIZE); if (p == NULL) err("get_auxv_entry - malloc"); if (copy_from_debugee(pid, sp_env, p, sp - sp_env)) err("get_auxv_entry - copy_from_debugee"); return p; } /* * Here we are, the AUXV. */ while (1) { if (copy_from_debugee(pid, sp, &auxie, sizeof(auxie))) err("get_auxv_entry - copy_from_debugee"); if (auxie.a_type == AT_ENTRY) return (void *) auxie.a_un.a_val; else if (auxie.a_type == AT_NULL) return (void *) 0; sp += sizeof(auxie); } /* * Reach this. */ } int main(int nc, char **na) { int wait_val; pid_t pid, parent_pid; int i, targetargc, envc, sep[3], ret; FILE *targetbin; char **targetargs, **fakeargs, **envv, **etmp; void *e_entry, *at_entry, *execareastart, *esp_argv, *sp, *ep; __u32 dw; __u64 qw; struct user_regs_struct regs; unsigned char save[1024]; unsigned char *argvblock, *avp; unsigned int len; int sleepydoo = 0, clearenvdoo = 0; extern char **environ; #ifdef BIT32 /* * 32-bit first stage code * 8048080: cc int3 * 8048081: 81 ec ff 13 00 00 sub $0x13ff,%esp * 8048087: b8 7d 00 00 00 mov $0x7d,%eax * 804808c: 89 e3 mov %esp,%ebx * 804808e: 81 c3 ff 0f 00 00 add $0xfff,%ebx * 8048094: 81 e3 00 f0 ff ff and $0xfffff000,%ebx * 804809a: b9 00 10 00 00 mov $0x1000,%ecx * 804809f: ba 07 00 00 00 mov $0x7,%edx * 80480a4: cd 80 int $0x80 * 80480a6: cc int3 * 80480a7: b8 ac 00 00 00 mov $0xac,%eax * 80480ac: bb 0f 00 00 00 mov $0xf,%ebx * 80480b1: 89 e1 mov %esp,%ecx * 80480b3: 31 d2 xor %edx,%edx * 80480b5: cd 80 int $0x80 * 80480b7: cc int3 * * +- padding instructions */ unsigned char nsh[] = "\xcc\x81\xec\xff\x13\x00\x00\xb8\x7d\x00\x00\x00" "\x89\xe3\x81\xc3\xff\x0f\x00\x00\x81\xe3\x00\xf0" "\xff\xff\xb9\x00\x10\x00\x00\xba\x07\x00\x00\x00" "\xcd\x80\xcc\xb8\xac\x00\x00\x00\xbb\x0f\x00\x00" "\x00\x89\xe1\x31\xd2\xcd\x80\xcc"; /* * 32-bit second stage code * 8048080: b8 07 00 00 00 mov $0x7,%eax * 8048085: bb ff ff ff ff mov $0xffffffff,%ebx * 804808a: 31 c9 xor %ecx,%ecx * 804808c: 31 d2 xor %edx,%edx * 804808e: cd 80 int $0x80 * 8048090: 8d 84 24 cc 07 00 00 lea 0x7cc(%esp),%eax * 8048097: ff 20 jmp *(%eax) */ unsigned char secondstage[] = "\x90\x90\x90\x90\xb8\x07\x00\x00\x00\xbb\xff\xff" "\xff\xff\x31\xc9\x31\xd2\xcd\x80\x8d\x84\x24\xcc" "\x07\x00\x00\xff\x20\xcc\x90\x90"; #else /* * 64-bit first stage code * 400078: cc int3 * 400079: 48 81 ec ff 13 00 00 sub $0x13ff,%rsp * 400080: 48 c7 c0 0a 00 00 00 mov $0xa,%rax * 400087: 48 89 e7 mov %rsp,%rdi * 40008a: 48 81 c7 ff 0f 00 00 add $0xfff,%rdi * 400091: 48 81 e7 00 f0 ff ff and $0xfffffffffffff000,%rdi * 400098: 48 c7 c6 00 10 00 00 mov $0x1000,%rsi * 40009f: 48 c7 c2 07 00 00 00 mov $0x7,%rdx * 4000a6: 0f 05 syscall * 4000a8: cc int3 * 4000a9: 48 c7 c0 9d 00 00 00 mov $0x9d,%rax * 4000b0: 48 c7 c7 0f 00 00 00 mov $0xf,%rdi * 4000b7: 48 89 e6 mov %rsp,%rsi * 4000ba: 48 31 d2 xor %rdx,%rdx * 4000bd: 0f 05 syscall * 4000bf: cc int3 * */ unsigned char nsh[] = "\xcc\x48\x81\xec\xff\x13\x00\x00\x48\xc7\xc0\x0a" "\x00\x00\x00\x48\x89\xe7\x48\x81\xc7\xff\x0f\x00" "\x00\x48\x81\xe7\x00\xf0\xff\xff\x48\xc7\xc6\x00" "\x10\x00\x00\x48\xc7\xc2\x07\x00\x00\x00\x0f\x05" "\xcc\x48\xc7\xc0\x9d\x00\x00\x00\x48\xc7\xc7\x0f" "\x00\x00\x00\x48\x89\xe6\x48\x31\xd2\x0f\x05\xcc" "\x90\x90\x90\x90\x90\x90\x90\x90"; /* * 64-bit second stage code * 400078: 48 c7 c0 3d 00 00 00 mov $0x3d,%rax * 40007f: 48 31 f6 xor %rsi,%rsi * 400082: 48 c7 c7 ff ff ff ff mov $0xffffffffffffffff,%rdi * 400089: 48 31 d2 xor %rdx,%rdx * 40008c: 4d 31 d2 xor %r10,%r10 * 40008f: 0f 05 syscall * 400091: 48 8d 84 24 c8 07 00 lea 0x7c8(%rsp),%rax * 400098: 00 * 400099: ff 20 jmpq *(%rax) */ unsigned char secondstage[] = "\x48\xc7\xc0\x3d\x00\x00\x00\x48\x31\xf6\x48\xc7" "\xc7\xff\xff\xff\xff\x48\x31\xd2\x4d\x31\xd2\x0f" "\x05\x48\x8d\x84\x24\xc8\x07\x00\x00\xff\x20\x90" "\x90\x90\x90\x90"; #endif #if 0 void (*f) () = &secondstage; f(); #endif if (nc < 2) usage(na[0]); for (i = 1, sep[0] = 0; i < nc; i++) { if (!strcmp("--", na[i])) { if (sep[0]++ > 2 || i == 1) usage(na[0]); else sep[sep[0]] = i; } } if (!sep[0] || (sep[0] == 1 && nc < 4) || (sep[0] == 2 && nc < 6)) usage(na[0]); if (sep[0] == 1) { if (sep[1] == nc - 1) { err("-- as the last argument?"); } else { targetargs = &na[1]; targetargc = sep[1] - 1; fakeargs = &na[sep[1] + 1]; } } else { if (sep[1] == sep[2] - 1) err("Two separators in row."); if (sep[2] == nc - 1) { err("-- as the last argument?"); } else { targetargs = &na[sep[1] + 1]; targetargc = sep[2] - sep[1] - 1; fakeargs = &na[sep[2] + 1]; } i = 1; do { printf("my arg %d: %s\n", i, na[i]); if (!strcmp(na[i], "sleep")) sleepydoo = 1; else if (!strcmp(na[i], "clearenv")) clearenvdoo = 1; } while (++i != sep[1]); } if ((sizeof(nsh) - 1) % BITSIZE) err("invalid alignment of the stage one code"); envc = 0; envv = environ; while (*envv) { envv++; envc++; } printf("envc %d\n", envc); if ((targetargc + envc) * BITSIZE > (VECTORSPACE - BITSIZE * 8)) { err("potential overflow detected, increase the limit"); } for (i = 0, len = 0; i < targetargc; i++) len += strlen(targetargs[i]) + 1; if (len > 1024) err("this will overflow either, fix it, please"); if ((e_entry = get_entry_point(targetargs[0])) == NULL) { err("Cannot obtain the entry point."); } switch (pid = fork()) { case -1: perror("fork"); break; case 0: pid = getppid(); ret = ptrace(PTRACE_ATTACH, pid, 0, 0); printf("%d ret ptrace parent\n", ret); wait(&wait_val); while (1) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) err("SYSCALL"); wait(&wait_val); ptrace(PTRACE_GETREGS, pid, NULL, ®s); #ifdef BIT32 if (regs.orig_eax == 11) break; #else if (regs.orig_rax == 59) break; #endif } ptrace(PTRACE_SYSCALL, pid, 0, 0); wait(&wait_val); /* * Mkey, this is after return from execve. */ at_entry = get_ivs(pid, VECTOR_AUX); /* * Free me my dear fag. */ envv = get_ivs(pid, VECTOR_ENV); printf("AT_ENTRY == %p\n", at_entry); envc = 0; etmp = envv; do { /* * printf("%p env\n", *etmp); */ envc++; } while (*etmp++); if (at_entry && e_entry != at_entry) e_entry = at_entry; printf("real entry %p\n", e_entry); printf("PAGESIZE %d\n", sysconf(_SC_PAGESIZE)); /* * Save the entry point block. */ if (copy_from_debugee (pid, e_entry, (void *) &save, sizeof(nsh) - 1) == -1) { perror("copy_from_debugee EPB"); printf("SAVE cfd failed || DETACHING\n"); ptrace(PTRACE_DETACH, pid, 0, 0); err("EP block copy is critical for our nation.\n"); } /* * test_print(save, sizeof(nsh) - 1 + 4); */ /* * Copy there stage one code. */ if (copy_to_debugee(pid, e_entry, nsh, (sizeof(nsh) - 1)) == -1) printf("ctd failed\n"); /* * Continue. */ for (i = 0; i < 3; i++) { if (ptrace(PTRACE_CONT, pid, 0, 0) != 0) perror("PTRACE_CONT"); wait(&wait_val); ptrace(PTRACE_GETREGS, pid, NULL, ®s); #ifdef BIT32 printf("TRAP: %p\n", regs.eip); sp = regs.esp; #else printf("TRAP: %p\n", regs.rip); sp = regs.rsp; #endif } /* * Mkey, stack space allocated, prctl call follows. * Let's pray. */ if (copy_to_debugee (pid, sp, fakeargs[0], ALIGN(strlen(fakeargs[0]), BITSIZE)) == -1) printf("prctl ctd failed\n"); if (ptrace(PTRACE_CONT, pid, 0, 0) != 0) perror("ass"); wait(&wait_val); /* * char buf[1024]; sprintf(buf, "cat /proc/%d/maps", pid); * system(buf); */ /* * FINAL TRAP HIT */ #ifdef BIT32 esp_argv = regs.esp + VECTORSPACE; dw = targetargc; printf("BITSIZE %d\n", BITSIZE); if (ptrace(PTRACE_POKEDATA, pid, regs.esp, dw) == -1) printf("fatal error\n"); for (i = 0; i < targetargc; i++) { if (ptrace(PTRACE_POKEDATA, pid, regs.esp + 4 + (4 * i), esp_argv) == -1) printf("fatal error\n"); printf("DBG: adding targetargs[%d] <%s>\n", i, targetargs[i]); esp_argv += strlen(targetargs[i]) + 1; } dw = 0; ep = regs.esp + (targetargc * BITSIZE) + BITSIZE; if (ptrace(PTRACE_POKEDATA, pid, ep, dw) == -1) printf("fatal error\n"); ep += BITSIZE; for (i = 0; i < envc; i++) { dw = envv[i]; if (ptrace(PTRACE_POKEDATA, pid, ep, dw) == -1) printf("fatal error\n"); printf("DBG: adding env %p\n", envv[i]); ep += BITSIZE; } dw = 0; if (ptrace(PTRACE_POKEDATA, pid, ep, dw) == -1) printf("fatal error\n"); argvblock = malloc((len = esp_argv - (regs.esp + VECTORSPACE) + 8)); len = ALIGN(len, BITSIZE); for (i = 0, avp = argvblock; i < targetargc; i++) { memcpy(avp, targetargs[i], strlen(targetargs[i]) + 1); avp += strlen(targetargs[i]) + 1; } if (copy_to_debugee(pid, regs.esp + VECTORSPACE, argvblock, len)) { printf("fatal error\n"); } execareastart = (regs.esp + 4095) & ~(4095) + 2000; if (ptrace(PTRACE_POKEDATA, pid, regs.esp + 1996, e_entry)) perror("PTRACE_POKEDATA"); printf("trampoline %p\n", regs.esp + 1996); if (copy_to_debugee (pid, execareastart, secondstage, sizeof(secondstage) - 1)) printf("copy_to_debugee failed\n"); printf("%p exec area\n", execareastart); regs.eip = execareastart; #else esp_argv = regs.rsp + VECTORSPACE; qw = targetargc; if (copy_qw_to_debugee(pid, regs.rsp, &qw)) printf("fatal error\n"); for (i = 0; i < targetargc; i++) { qw = (__u64) esp_argv; if (copy_qw_to_debugee(pid, regs.rsp + 8 + (8 * i), &qw) == -1) printf("fatal error\n"); printf("DBG: adding targetargs[%d] <%s>\n", i, targetargs[i]); esp_argv += strlen(targetargs[i]) + 1; } qw = 0; copy_qw_to_debugee(pid, regs.rsp + 8 + targetargc * 8, &qw); for (i = 0, ep = regs.rsp + 16 + (targetargc * 8); i < envc; i++) { qw = (__u64) envv[i]; if (copy_qw_to_debugee(pid, ep + (8 * i), &qw) == -1) printf("fatal error\n"); printf("DBG: adding env %p\n", envv[i]); } qw = 0; copy_qw_to_debugee(pid, ep + (envc * 8), &qw); printf("occupied space %d\n", envc * 8 + targetargc * 8 + 24); len = esp_argv - (regs.rsp + VECTORSPACE) + 16; argvblock = malloc(len); if (argvblock == NULL) err("fatal"); len = ALIGN(len, 8); for (i = 0, avp = argvblock; i < targetargc; i++) { memcpy(avp, targetargs[i], strlen(targetargs[i]) + 1); avp += strlen(targetargs[i]) + 1; } if (copy_to_debugee(pid, regs.rsp + VECTORSPACE, argvblock, len)) { printf("fatal error\n"); } execareastart = (regs.rsp + 4095) & ~(4095) + 2000; qw = (__u64) e_entry; copy_qw_to_debugee(pid, regs.rsp + 1992, &qw); printf("trampoline %p\n", (__u64) (regs.rsp + 1992)); if (copy_to_debugee (pid, execareastart, secondstage, sizeof(secondstage) - 1)) printf("ctd failed\n"); printf("%p exec area\n", execareastart); regs.rip = execareastart; #endif /* * dump_stack(pid); */ ptrace(PTRACE_SETREGS, pid, NULL, ®s); if (copy_to_debugee (pid, (void *) e_entry, save, sizeof(nsh) - 1) == -1) printf("ctd failed\n"); if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) perror("detach"); printf("childpid %d, parentpid %d\n", getpid(), getppid()); if (sleepydoo) sleep(0xf); printf("Emil se louci.\n"); /* * He he.. */ free(envv); break; default: printf("Parent: %s\n", targetargs[0]); if (clearenvdoo) clearenv(); /* * Let's perform a syscall slide before execve. */ i = 0xf; while (i--) uname(save); /* * Whoa, nice slide, man. */ execv(targetargs[0], fakeargs); } return 0; } /* * vim: set ts=2 */