64 vs 32 Continued. (It’s a Trap!)

It’s time to continue exploring the differences in 64-bit and 32-bit architecture (at least from a buffer overflow standpoint) by taking a look at their respective stacks in memory.  We are going to use the same  program we used last time and see what we figure out by using gdb.  I’m going to walk through the memory very carefully and see exactly what happens using different input strings to fill up the buffers.  We have done this for 32-bit execution before but a refresher is great and we can always learn something new.

(In the first part of the post there should be something that feels a bit off to you in the discussion.  If you know how this works you’ll know what’s wrong.  It’s there for a reason and I address it later.)

Filling the 32-Bit Buffer

First run I’m going to enter eight A’s into buffer_two.  That is 8 bytes of information, which is what we have allocated for each buffer.  That will show us exactly where buffer two is in our memory and where it ends.  I set two break points.  The first right after we initialize buffer_one and buffer_two.  The second right after the command line argument is copied into buffer_two.

(gdb) x/32xw $esp
0xbffff260:     0xbffff4c9      0x0000002f      0x08049930      0x006f7774
0xbffff270:     0x00000002(*)   0x00656e6f      0xbffff340      0x00000005(**)
0xbffff280:     0xbffff2a0      0xb7fbd000      0x00000000      0xb7e2da63
0xbffff290:     0x08048570      0x00000000      0x00000000      0xb7e2da63
0xbffff2a0:     0x00000002      0xbffff334      0xbffff340      0xb7fed79a
0xbffff2b0:     0x00000002      0xbffff334      0xbffff2d4      0x0804994c
0xbffff2c0:     0x0804821c      0xb7fbd000      0x00000000      0x00000000

Here is the memory starting at the stack pointer right after we initialize buffer_one and buffer_two. We can see buffer_two’s entry (*) and we can see variable’s entry (**). Buffer_one is right there as well between them but at first glance it isn’t as obvious as one might think to determine where buffer two actually starts here. Lets take a look at what happens when we enter four A’s.

0xbffff260:     0xbffff4c9      0x0000002f      0x08049930      0x41414141
0xbffff270:     0x00000000      0x00656e6f      0xbffff340      0x00000005
0xbffff280:     0xbffff2a0      0xb7fbd000      0x00000000      0xb7e2da63
0xbffff290:     0x08048570      0x00000000      0x00000000      0xb7e2da63
0xbffff2a0:     0x00000002      0xbffff334      0xbffff340      0xb7fed79a
0xbffff2b0:     0x00000002      0xbffff334      0xbffff2d4      0x0804994c
0xbffff2c0:     0x0804821c      0xb7fbd000      0x00000000      0x00000000
0xbffff2d0:     0x00000000      0xc308a7ac      0xf959a3bc      0x00000000

We see where the entry point into buffer_two is here. Which is actually the set of 4 bytes below where 2 is in memory. But that makes sense if we consider the sizes of the buffers we created and where value resides in memory. Now lets go ahead and fill up the rest of buffer two by using eight A’s as our command argument.

(gdb) x/32xw $esp
0xbffff260:     0xbffff4c5      0x0000002f      0x08049930      0x41414141
0xbffff270:     0x41414141      0x00656e00      0xbffff340      0x00000005
0xbffff280:     0xbffff2a0      0xb7fbd000      0x00000000      0xb7e2da63
0xbffff290:     0x08048570      0x00000000      0x00000000      0xb7e2da63
0xbffff2a0:     0x00000002      0xbffff334      0xbffff340      0xb7fed79a
0xbffff2b0:     0x00000002      0xbffff334      0xbffff2d4      0x0804994c
0xbffff2c0:     0x0804821c      0xb7fbd000      0x00000000      0x00000000
0xbffff2d0:     0x00000000      0x4b16dcfe      0x7147d8ee      0x00000000

Now we see where the full range of buffer two is. Notice that we have also zeroed out the bit right after buffer_two (in bold). Now lets take a look at what happens when we use 12 bytes in the overflow.

(gdb) x/32xw $esp
0xbffff260:     0xbffff4c1      0x0000002f      0x08049930      0x41414141
0xbffff270:     0x41414141      0x41414141      0xbffff300      0x00000005
0xbffff280:     0xbffff2a0      0xb7fbd000      0x00000000      0xb7e2da63
0xbffff290:     0x08048570      0x00000000      0x00000000      0xb7e2da63
0xbffff2a0:     0x00000002      0xbffff334      0xbffff340      0xb7fed79a
0xbffff2b0:     0x00000002      0xbffff334      0xbffff2d4      0x0804994c
0xbffff2c0:     0x0804821c      0xb7fbd000      0x00000000      0x00000000
0xbffff2d0:     0x00000000      0x47cf9326      0x7d9e9736      0x00000000

So now we see where buffer_one starts, which is as we should have expected. You may be wondering about that null byte that follows us around at this point. It comes from the property that in a C program a character array is terminated with a null byte. If you don’t leave room in the array for the null byte it will be placed right after the array overwriting what is right after the array. As we expect if we enter 16 bytes we will overflow the null byte into value and zero out the 5 as demonstrated in gdb.

0xbffff250:     0xbffff4bd      0x0000002f      0x08049930      0x41414141
0xbffff260:     0x41414141      0x41414141      0x41414141      0x00000000
0xbffff270:     0xbffff290      0xb7fbd000      0x00000000      0xb7e2da63
0xbffff280:     0x08048570      0x00000000      0x00000000      0xb7e2da63
0xbffff290:     0x00000002      0xbffff324      0xbffff330      0xb7fed79a
0xbffff2a0:     0x00000002      0xbffff324      0xbffff2c4      0x0804994c
0xbffff2b0:     0x0804821c      0xb7fbd000      0x00000000      0x00000000
0xbffff2c0:     0x00000000      0x7fb99bc0      0x45e8ffd0      0x00000000

We have seen that before, but it’s a nice refresher. Lets check out the 64-bit execution.

Filling the 64-Bit Buffer

Lets see what the stack looks like as we stop right after initializing buffers one and two.

(gdb) x/32xw $rsp
0x7fffffffe060:	0xffffe178	0x00007fff	0x00000000	0x00000002
0x7fffffffe070:	0x006f7774	0x00000000	0x004004c0	0x00000000
0x7fffffffe080:	0x00656e6f	0x00007fff	0x00000000	0x00000005
0x7fffffffe090:	0x004006d0	0x00000000	0xf7a2e830	0x00007fff
0x7fffffffe0a0:	0x00000000	0x00000000	0xffffe178	0x00007fff
0x7fffffffe0b0:	0x00000000	0x00000002	0x004005b6	0x00000000
0x7fffffffe0c0:	0x00000000	0x00000000	0x6bd55c87	0xb652137c
0x7fffffffe0d0:	0x004004c0	0x00000000	0xffffe170	0x00007fff

That’s certainly different than we saw before. Its obvious why we need to pass larger strings to overflow this buffer by looking at the distances between the 2 in buffer two and 5 in value. In the 32-bit execution we had 12 bytes between these two memory entries. Now we have 28 bytes between them. Lets see what happens as we fill up the buffers starting with four A’s again and increasing as we go.

(gdb) x/32xw $rsp
0x7fffffffe060:	0xffffe178	0x00007fff	0x00000000	0x00000002
0x7fffffffe070:	0x41414141	0x00000000	0x004004c0	0x00000000
0x7fffffffe080:	0x00656e6f	0x00007fff	0x00000000	0x00000005
0x7fffffffe090:	0x004006d0	0x00000000	0xf7a2e830	0x00007fff
0x7fffffffe0a0:	0x00000000	0x00000000	0xffffe178	0x00007fff
0x7fffffffe0b0:	0x00000000	0x00000002	0x004005b6	0x00000000
0x7fffffffe0c0:	0x00000000	0x00000000	0x6bd55c87	0xb652137c
0x7fffffffe0d0:	0x004004c0	0x00000000	0xffffe170	0x00007fff

Here we see another difference. Unlike the 32-bit architecture we enter the buffer in the four bytes above the 2 entry instead of the four bytes below. Lets keep going with eight A’s.

(gdb) x/32xw $rsp
0x7fffffffe060:	0xffffe178	0x00007fff	0x00000000	0x00000002
0x7fffffffe070:	0x41414141	0x41414141	0x00400400	0x00000000
0x7fffffffe080:	0x00656e6f	0x00007fff	0x00000000	0x00000005
0x7fffffffe090:	0x004006d0	0x00000000	0xf7a2e830	0x00007fff
0x7fffffffe0a0:	0x00000000	0x00000000	0xffffe178	0x00007fff
0x7fffffffe0b0:	0x00000000	0x00000002	0x004005b6	0x00000000
0x7fffffffe0c0:	0x00000000	0x00000000	0x4ab0ea04	0x36a97e3e
0x7fffffffe0d0:	0x004004c0	0x00000000	0xffffe170	0x00007fff

We can see we are continuing to fill the buffer towards the value variable. Now with 12 A’s.

(gdb) x/32xw $rsp
0x7fffffffe050:	0xffffe168	0x00007fff	0x00000000	0x00000002
0x7fffffffe060:	0x41414141	0x41414141	0x41414141	0x00000000
0x7fffffffe070:	0x00656e6f	0x00007fff	0x00000000	0x00000005
0x7fffffffe080:	0x004006d0	0x00000000	0xf7a2e830	0x00007fff
0x7fffffffe090:	0x00000000	0x00000000	0xffffe168	0x00007fff
0x7fffffffe0a0:	0xf7ffcca0	0x00000002	0x004005b6	0x00000000
0x7fffffffe0b0:	0x00000000	0x00000000	0xed3420e7	0x4ece233f
0x7fffffffe0c0:	0x004004c0	0x00000000	0xffffe160	0x00007fff

We are continuing to move towards value in memory. We already know that we need 28 A’s to get to value, lets try 16 A’s and see if we continue filling up the memory as we expect to.

(gdb) x/32xw $rsp
0x7fffffffe050:	0xffffe168	0x00007fff	0x00000000	0x00000002
0x7fffffffe060:	0x41414141	0x41414141	0x41414141	0x41414141
0x7fffffffe070:	0x00656e00	0x00007fff	0x00000000	0x00000005
0x7fffffffe080:	0x004006d0	0x00000000	0xf7a2e830	0x00007fff
0x7fffffffe090:	0x00000000	0x00000000	0xffffe168	0x00007fff
0x7fffffffe0a0:	0xf7ffcca0	0x00000002	0x004005b6	0x00000000
0x7fffffffe0b0:	0x00000000	0x00000000	0xd24d1ac9	0x92cf1211
0x7fffffffe0c0:	0x004004c0	0x00000000	0xffffe160	0x00007fff

The memory is filling pretty much as we expected it to. So we have eyes on what is causing our distance issue in the stack overflow. But you should be asking yourself something right now.

Why Didn’t We Overwrite The Two?????

The answer to that question is because the 2 isn’t what it looks like at first glance.  We entered strings into buffer_one and buffer_two.  So we aren’t actually looking for 2 as the signal of the first entry of buffer_two.  We are looking for this value 6f7774. When you see letters entered into a buffer you should think ASCII written in memory. In ASCII 6f = 0, 77 = w, 74 = t. It’s backwards because of little endian architecture. We didn’t enter the buffer below the first entry in the 32=bit architecture and above in the 64-bit we were looking at the wrong memory segment. Armed with that train of thought we can see exactly where buffer one begins by searching for the 656e6f value in memory.

With that knowledge we now know that there are 16 bytes between the base of buffer_two and the base of buffer_one in the 64-bit architecture.  There are 12 bytes between the base of buffer_one and the value entry.  Both of which add up to our 28 bytes required to reach value.

Conclusion

If you’re new to this then yes I admit I set you up for initial failure.  I did it for good reason though.  When you’re casually thinking about the number 2 and the number two it probably doesn’t stand out that those are in fact different objects.  One is a number representation one is a word representing a number.  They represent the same thing but they do so in different ways that matter when it comes to computer memory.  I invite you to reread the discussion about the comparison and look for clues that we had the wrong piece of memory from the start, they are there if you look carefully (or not so carefully now that you know).

If you read the first part and thought to yourself this person is an idiot.  Then I invite you to present suggestions as to how such a lesson is communicated in a better fashion.  I’m always open to them.

We have made some progress, even if it is just looking at how the two buffers fill up in different architectures.  But now we have to try to figure out the structure differences in the stacks.  There are some naive thoughts as to the reason but they are easily dismissed.  The big thing that should be standing out to you right now is that there are 8 extra bytes between buffer_two and buffer_one, while only 4 extra bytes between buffer_one and value.

Next time we will play around with the program some more and see what happens in memory by varying parameters.

Spoilers:

Here are some hints that we are on the wrong track from the start.

  • We see a 2 in memory but there is no corresponding 1.  If the two as the data initialized for buffer_two then the corresponding 1 value should have been between it and the value memory.
  • It should have seemed pretty strange that after entering our string we started writing to the buffer below the initial value in buffer_two, then above it in the 64-bit architecture.
  • If the 2 in the 32-bit example was the entry point for buffer_two that would mean there could only be 4 bytes for buffer_two, or 4 bytes for buffer_one, or 6 bytes each.
  • We didn’t overwrite the 2 in memory on the 64-bit stack.
  • If we enter two A’s into the buffer the 2 value is not overwritten but in our output only the two A’s appear in buffer_two.
  • When we started entering A’s into memory it is represented by 41.

Depending on your level of knowledge about how C programs work and computer architecture there are more indications but starting out those are good in addition to the discussion in the main post.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s