Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

How to have fun with ptrace syscall

Cyberdude
Electrical Ordered Freedom #2 (EOF-DR-RRLF)
May 2008

[Back to index] [Comments]

Hi boys in this text i want show to you how is possible to "hack" one process using some assembly strings and the Dynamic linker. If you search in internet you can find it : a dynamic linker is the part of an operating system that loads and links the shared libraries for an executable when it is run. Such linkers typically also have a shared library that is linked with the executable when it is compiled and may determine the actions of the linker. One shared library,in addition to being loaded statically or dynamically, are also often classified according to how they are shared among programs. In Linux the dynamic linker shared libraries, tipically are based on a common set of environment variables, including LD_LIBRARY_PATH and LD_PRELOAD. In this text we will use the LD_PRELOAD variable. When LD_PRELOAD is set, the dynamic linker will use the specified library before any other when it searches for shared libraries. Now imagine that our process is the next code:

process.c

#include <stdio.h>
#include <unistd.h>
int main()
{
        printf("Userid = %d\n",getuid());
        return 0;
}
 

if we compile and run it...

[email protected]:~$ gcc process.c -o process

[email protected]:~$ ./process
Userid = 1000

[email protected]:~$ strace ./process
execve("./process", ["./process"], [/* 32 vars */]) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f3c000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=67304, ...}) = 0
mmap2(NULL, 67304, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f2b000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0`\1\000"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=1307104, ...}) = 0
mmap2(NULL, 1312164, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7dea000
mmap2(0xb7f25000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13b) = 0xb7f25000
mmap2(0xb7f28000, 9636, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7f28000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7de9000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7de96c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7f25000, 4096, PROT_READ)   = 0
munmap(0xb7f2b000, 67304)               = 0
getuid32()                              = 1000
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f3b000
write(1, "Userid = 1000\n", 14Userid = 1000
)         = 14
exit_group(0)                           = ?
Process 6885 detached

The line that we want change is the next:
getuid32()                              = 1000

Imagine that we want obtain that when the process call the syscall getuid32() the returnament is not 1000 but is an id that we decide. I can just use a new library and call it tnk to LD_PRELOAD. In my lib i will create a new method named getuid() so when the operativ sistem will catch the string: printf("Userid = %d\n",getuid()) will use my persona getuid() and not the original. My lib is very simply and is showed next

lib.s

.globl getuid
        .type   getuid, @function      
getuid:
        movl    $0, %eax
        ret
 

This code is very simply, i just declare the name of my funcion as getuid and after move the value 0 in the eax registry. When i call the ret syscall, the funcion will return the value that is in the eax registry, so it will return the value 0. In this way when you will run the function getuid () from this library, it will return to you the value 0 and not the true id. At this point you have just to complie the lib like a shared library and run the prev code using the LD_PRELOAD and the shared lib as showed next

[email protected]:~$ gcc -shared lib.s -o lib

[email protected]:~$ LD_PRELOAD=./lib ./process
Userid = 0

[email protected]:~$ LD_PRELOAD=./lib strace ./process
execve("./process", ["./process"], [/* 33 vars */]) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fd4000
open("./lib", O_RDONLY)                 = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\3\0\000"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=5215, ...}) = 0
getcwd("/home/cyberdude", 128)          = 16
mmap2(NULL, 5436, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7fd2000
mmap2(0xb7fd3000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xb7fd3000
mprotect(0xbfbbc000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN) = 0
close(3)                                = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=67304, ...}) = 0
mmap2(NULL, 67304, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fc1000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0`\1\000"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=1307104, ...}) = 0
mmap2(NULL, 1312164, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7e80000
mmap2(0xb7fbb000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13b) = 0xb7fbb000
mmap2(0xb7fbe000, 9636, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fbe000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7e7f000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7e7fac0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7fbb000, 4096, PROT_READ)   = 0
munmap(0xb7fc1000, 67304)               = 0
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fd1000
write(1, "Userid = 0\n", 11Userid = 0
)            = 11
exit_group(0)                           = ?
Process 6932 detached

As you can see, the difference between the two exection of the same prcess is in this block of istruction. The first block is the block that we runned without using the LD_PRELOAD variable. The second block is the block that we runned with the LD_PRELOAD. The difference is that in the first block there is a called to getuid32() with returnament 1000, in the second block this system call is not execute. The write is different too becouse the Write call in the first block return the original id, the Write call in the second block return the userid = 0 as decided in our lib

BLOCK 1

mprotect(0xb7f25000, 4096, PROT_READ)   = 0
munmap(0xb7f2b000, 67304)               = 0
getuid32()                              = 1000
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
                                                                   0xb7f3b000
write(1, "Userid = 1000\n", 14Userid = 1000

BLOCK 2

mprotect(0xb7fbb000, 4096, PROT_READ)   = 0
munmap(0xb7fc1000, 67304)               = 0
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
                                                                   0xb7fd1000
write(1, "Userid = 0\n", 11Userid = 0

But unluckly, this tecnique is not good if the process is compiled like a static process, as showed next:

[email protected]:~$ gcc -static process.c -o process

[email protected]:~$ LD_PRELOAD=./lib ./process
Userid = 1000

[email protected]:~$ LD_PRELOAD=./lib strace ./process
execve("./process", ["./process"], [/* 33 vars */]) = 0
uname({sys="Linux", node="cyberdude-laptop", ...}) = 0
brk(0)                                  = 0x80bc000
brk(0x80bccb0)                          = 0x80bccb0
set_thread_area({entry_number:-1 -> 6, base_addr:0x80bc830, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
brk(0x80ddcb0)                          = 0x80ddcb0
brk(0x80de000)                          = 0x80de000
getuid32()                              = 1000
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f19000
write(1, "Userid = 1000\n", 14Userid = 1000
)         = 14
exit_group(0)                           = ?
Process 6985 detached

In this case we have to change our strategy using differents system call There is one system call named ptrace(). We can do some requests to ptrace, but we are interested just to the next 4 requests:

So our strategy is the next:

  1. Create a new process
  2. make a fork() on this process to make a child
  3. The child will make a request: PTRACE_TRACEME so the parent can trace he
  4. The child will run the stati process that we want hack
  5. The parent will make a request: PTRACE_SYSCALL on his child, so the parent always know what is the syscall that the child is running in the current moment
  6. The parent will make a request: PTRACE_GETREGS to know the registry of everyone syscall called by the child
  7. The parent will control the orig_eax value of the current syscall called by the child. If the value is the same that i search (in our case is SYS_getuid32), the parent will change the value of eax (the result of the syscall) with the value that we want (in out case the value 0)

To explain it better we let that the process call the true getuid procedure but we will interect the returnament of this procedure and we will change it before that the process can return it to the operativ system. In this way we can set the returnament as we want

hack.s

patch:  .string "/home/cyberdude/process"       /* The path of the process
                                                   that we want hack     */
.globl main                                     /* The main function     */
main:
        movl    $0, -4(%ebp)                    /* In this address I will
                                                   store  the variable to
                                                   wait the child process*/
        movl    $0, -8(%ebp)                    /* In this address I  will
                                                   store the output value
                                                   that  i will read from
                                                   eax  registry  of  sys
                                                   call runned by child  */
        movl    $1, -12(%ebp)                   /* Thi is a value  that i
                                                   will use to control if
                                                   the      parent     is
                                                   intercepting a entering
                                                   syscall or exiting it  */

        call    fork                            /* I call the fork syscall
                                                   to obtain the child    */
        movl    %eax, -16(%ebp)                 /* And i store the child' s
                                                   pid in -16(%ebp)       */

        cmpl    $0, -16(%ebp)                   /* Now  compare  this  pid
                                                   with 0 to know  if  i'
m
                                                   in  parent  process  or
                                                   child it               */
        jne     parent                          /* If the pid is not 0 i'm
                                                   in the parent execution
                                                   so  i   jump  to  label
                                                   named parent           */

child:                                          /* Else i  continue  with
                                                   child processing      */

        movl    $26,%eax                        /* The syscall ptrace    */            
        movl    $0, %ebx                        /* The  first   Parameter
                                                   setted to 0 is TRACEME*/
        movl    $0, %ecx                        /* second parameter      */
        movl    $0, %edx                        /* therd parameter       */
        movl    $0, %esi                        /* fourtf parameter      */
        int     $0x80                           /* i run it in this way
                                                   the child accept that
                                                   the parent trace  he  
                                                   during  his execution */

        pushl   $0                              /* Now i run the process */
        pushl   $patch                          /*  created  previously  */
        pushl   $patch                          /* with the syscall      */
        call    execlp                          /* execlp                */

        jmp     quit                            /* Here is finished the
                                                   child'
s life          */

parent:                                         /* Here start the life
                                                   of parent             */
        leal    -4(%ebp), %eax                  /* I leal the space of
                                                   variable previously
                                                   created               */
        movl    %eax, (%esp)                    /* push this space       */
        call    wait                            /* and call the wait to
                                                   wait the execution of
                                                   child                 */

whileCicle:

        movl    $26,%eax                        /* The ptrace function   */
        movl    $24,%ebx                        /* The first parameter is
                                                   24 = PTRACE_SYSCALL   */
        movl    -16(%ebp),%ecx                  /* In -16(%ebp there is
                                                   the pid, it  is  the
                                                   second  parameter of
                                                   the ptrace function   */
        movl    $0,%edx
        movl    $0,%esi
        int     0x80
       

        leal    -4(%ebp), %eax
        movl    %eax, (%esp)
        call    wait

        movl    -4(%ebp), %eax                  /* Now i control if the  */
        andl    $127, %eax                      /* returnament of the    */
        testl   %eax, %eax                      /* the wait funcion is  
                                                   equals to 127 (if is  
                                                   equals the child is
                                                   finished              */
        je      quit                            /* If is finished jump to
                                                   quit                  */

        leal    -92(%ebp), %eax                 /* Else i leal the address
                                                   -92(%ebp) in %eax     */
        pushl   %eax
        pushl   $0
        movl    -16(%ebp), %eax
        pushl   %eax
        pushl   $12                             /* This parameter rapresent
                                                   GETREGS, i use it to
                                                   obtain the registrs used
                                                   for the intercepted sys
                                                   call                    */
        call    ptrace 

        movl    -48(%ebp), %eax                 /* In -48(%ebp) there is
                                                   the value orig_eax that
                                                   say to me the number of
                                                   the systemcall catched  */
        movl    %eax, -8(%ebp)
        cmpl    $199, -8(%ebp)                  /* I control if this number
                                                   is 199 (getuid() number */
        jne     whileCicle                      /* If is not 199 i come back
                                                   to the while Cicle to
                                                   intercept the next call */

        cmpl    $0, -12(%ebp)                   /* Else i control the value
                                                   in -12(%ebp)! i set this
                                                   variable previously to 1
                                                   so if his value is still
                                                   1 it does  mean  that  i
                                                   never change this  value
                                                   so  i'm  in  the  moment
                                                   that i'
m entering in the
                                                   syscall                 */
        je      changeEaxValue                  /* If is equals to 0 it  is
                                                   the moment that  i'm  in
                                                   in the out of syscall so
                                                   i can  take  the  output
                                                   value and change it     */

        movl    $0, -12(%ebp)                   /* Else   if  i'
m  in  the
                                                   entering moment, i have
                                                   to change the value  of
                                                   my  variable  to  0  to
                                                   remember that the  next
                                                   intercept time, will be
                                                   the out time           */
        jmp     whileCicle                      /* After that i change the
                                                   variable  value  i  can
                                                   come back to the  cicle
                                                   to incercept  the  next
                                                   syscall                */

changeEaxValue:
        leal    -92(%ebp), %eax                 /* If i'm in the  exiting
                                                   of syscall i leal some
                                                   space  from  ebp  and
                                                   push it               */
        pushl   %eax
        pushl   $0
        movl    -16(%ebp), %eax
        pushl   %eax
        pushl   $12                             /* I obtain the GETREGS */
        call    ptrace

        movl    $0, -68(%ebp)                   /* I change  the  output
                                                   value that is  stored
                                                   in -68(%ebp) to 0! in
                                                   this way i'
m changing
                                                   the   returnament  of
                                                   the  intercepted  sys
                                                   call   (that  in  our
                                                   case is the getuid()) */

        leal    -92(%ebp), %eax                 /* I  repush   all  the  */
        pushl   %eax                            /* structure of ptrace's */
        pushl   $0                              /* function              */
        movl    -16(%ebp), %eax
        pushl   %eax   
        pushl   $13                             /* and call the SETREGS  */
        call    ptrace                          /* function on ptrace()  */

        movl    $1, -12(%ebp)                   /* After i change another
                                                   time the variable value
                                                   to remember that in the
                                                   next   time,  the  call
                                                   interception will be
                                                   to entering syscall   */
        jmp     whileCicle
quit:
        movl    $0, %eax
        call    exit

The next code is the same of the showed previously, just in asm inline version, in this way, maybe you can understand better the process execution

AsmInLineVersion.c

#include <sys/ptrace.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>
#include <sys/reg.h>

int main()
{
        char* path = "/home/cyberdude/process";
        int status = 0;
        int callIdNumber = 0;
        int isEntering = 1;
        struct user_regs_struct regs;
        int pid = fork();

        if(!pid)
        {
                asm(
                        "movl   $26,%%eax;"
                        "movl   $0, %%ebx;"    
                        "movl   $0, %%ecx;"    
                        "movl   $0, %%edx;"    
                        "movl   $0, %%esi;"    
                        "int    $0x80;"

                        "movl   $11,%%eax;"
                        "movl   %%edi,%%ebx;"
                        "movl   $0,%%ecx;"
                        "movl   $0,%%edx;"
                        "int    $0x80;"

                        :: "D"(path)
                );
        }
        else
        {
                wait(&status);
                while(1)
                {
               
                        asm(
                                "movl   $26,%%eax;"            
                                "movl   $24,%%ebx;"            
                                "movl   $0,%%edx;"
                                "movl   $0,%%esi;"
                                "int    $0x80;"

                                :: "c"(pid)            
                        );
               
                        wait(&status);
                        if ( WIFEXITED( status ) ) break;      
                        asm(
                                "movl   $26,%%eax;"
                                "movl   $12,%%ebx;"
                                "movl   $0,%%edx;"
                                "int    $0x80;"

                                :: "c"(pid), "S"(&regs)
                        );

                        callIdNumber= regs.orig_eax;
                        if ( callIdNumber == 199 )
                        {
                                if ( isEntering )
                                        isEntering = 0;
                                else
                                {
                                        regs.eax = 0;
                                        asm(
                                                "movl   $26,%%eax;"
                                                "movl   $13,%%ebx;"
                                                "movl   $0,%%edx;"
                                                "int    $0x80;"
               
                                                :: "c"(pid), "S"(&regs)
                                        );
                                        isEntering = 1;
                                }
                        }
                }
        }
        return 0;
}
 

And with this code is finished this text ... see you to the next article bye bye Cyberdude! Happy code to everybody :D

[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxer.org aka vx.netlux.org
deenesitfrplruua