A little ptrace()-based utility for process argument/name hiding. Works on most Linux 2.6 kernels/configurations (x86/x86-64 architecture).
c8189416cea76ef2b7593e1099350b755174245c2e87c027f52dae3aff4fe941
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/user.h>
#include <unistd.h>
#include <errno.h>
#include <elf.h>
#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
*/