How to have fun with ptrace syscall
Cyberdude
- Cyberdude
- How to have fun with ptrace syscall -
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
#include
int main()
{
printf("Userid = %d\n",getuid());
return 0;
}
----------------------------------------------------------------------------
if we compile and run it...
cyberdude@cyberdude-laptop:~$ gcc process.c -o process
cyberdude@cyberdude-laptop:~$ ./process
Userid = 1000
cyberdude@cyberdude-laptop:~$ 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
cyberdude@cyberdude-laptop:~$ gcc -shared lib.s -o lib
cyberdude@cyberdude-laptop:~$ LD_PRELOAD=./lib ./process
Userid = 0
cyberdude@cyberdude-laptop:~$ 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 :
cyberdude@cyberdude-laptop:~$ gcc -static process.c -o process
cyberdude@cyberdude-laptop:~$ LD_PRELOAD=./lib ./process
Userid = 1000
cyberdude@cyberdude-laptop:~$ 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 :
* PTRACE_TRACEME: This request let that we can trace the execution of
the process like if we are making a debug on the current process
* PTRACE_SYSCALL: If we make a fork and add this request to the parent,it
will notify everytime that the child enter or exit from one systemcall
* PTRACE_GETREGS: We can read the registry of the current process. The
values are readed in a structure named user_regs_struct. In this struct
there is an information named eax (that we know very well :D)
* PTRACE_SETREGS: With this request we can write in one registry all that
we want
So our strategy is the next :
[1] Create a new process
[2] make a fork() on this process to make a chil
[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
#include
#include
#include
#include
#include
#include
#include
#include
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"(®s)
);
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"(®s)
);
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