ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ---Implement a Dynamic Host Configuration Protocol (DHCP) client. -- -- DHCP, defined in rfc2132 and rfc2131, is a protocol for hosts to automatically -- configure themselves on a network (that is, obtain an ip address). This library, -- which have a trivial one-function interface, can send out DHCP packets of many -- types and parse the responses. -- -- @author Ron Bowes -- -- 2011-12-28 - Revised by Patrik Karlsson -- o Split dhcp_send into dhcp_send, dhcp_receive -- o Added basic support for adding options to requests -- o Added possibility to override transaction id -- o Added WPAD action local datetime = require "datetime" local ipOps = require "ipOps" local math = require "math" local nmap = require "nmap" local stdnse = require "stdnse" local string = require "string" local strbuf = require "strbuf" local table = require "table" local tableaux = require "tableaux" _ENV = stdnse.module("dhcp", stdnse.seeall) request_types = { DHCPDISCOVER = 1, DHCPOFFER = 2, DHCPREQUEST = 3, DHCPDECLINE = 4, DHCPACK = 5, DHCPNAK = 6, DHCPRELEASE = 7, DHCPINFORM = 8 } request_types_str = tableaux.invert(request_types) ---Read an IP address or a list of IP addresses. Print an error if the length isn't a multiple of 4. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_ip(data, pos, length) if(length ~= 4) then if((length % 4) ~= 0) then stdnse.debug1("dhcp-discover: Invalid length for an ip address (%d)", length) pos = pos + length return pos, nil else local results = {} for i=1, length, 4 do local value value, pos = string.unpack(">I4", data, pos) table.insert(results, ipOps.fromdword(value)) end return pos, results end else local value value, pos = string.unpack(">I4", data, pos) return pos, ipOps.fromdword(value) end end ---Read a string. The length of the string is given by the length field. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_string(data, pos, length) local value, pos = string.unpack(("c%d"):format(length), data, pos) return pos, value end ---Read a single byte. Print an error if the length isn't 1. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_1_byte(data, pos, length) if(length ~= 1) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be %d)", length, 1) pos = pos + length return pos, nil end local value, pos = string.unpack("B", data, pos) return pos, value end ---Read a message type. This is a single-byte value that's looked up in the request_types_str -- table. Print an error if the length isn't 1. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_message_type(data, pos, length) local value pos, value = read_1_byte(data, pos, length) if(value == nil) then stdnse.debug1("dhcp-discover: Couldn't read the 1-byte message type") return pos, nil end return pos, request_types_str[value] end ---Read a single byte, and return 'false' if it's 0, or 'true' if it's non-zero. Print an error if the -- length isn't 1. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_boolean(data, pos, length) local result pos, result = read_1_byte(data, pos, length) if(result == nil) then stdnse.debug1("dhcp-discover: Couldn't read the 1-byte boolean") return pos, nil elseif(result == 0) then return pos, "false" else return pos, "true" end end ---Read a 2-byte unsigned little endian value. Print an error if the length isn't 2. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_2_bytes(data, pos, length) if(length ~= 2) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be %d)", length, 2) pos = pos + length return pos, nil end local value, pos = string.unpack(">I2", data, pos) return pos, value end ---Read a list of 2-byte unsigned little endian values. Print an error if the length isn't a multiple -- of 2. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_2_bytes_list(data, pos, length) if((length % 2) ~= 0) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be multiple of %d)", length, 2) pos = pos + length return pos, nil else local results = {} for i=1, length, 2 do local value value, pos = string.unpack(">I2", data, pos) table.insert(results, value) end return pos, results end end ---Read a 4-byte unsigned little endian value. Print an error if the length isn't 4. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_4_bytes(data, pos, length) if(length ~= 4) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be %d)", length, 4) pos = pos + length return pos, nil end local value, pos = string.unpack(">I4", data, pos) return pos, value end ---Read a 4-byte unsigned little endian value, and interpret it as a time offset value. Print an -- error if the length isn't 4. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_time(data, pos, length) local result if(length ~= 4) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be %d)", length, 4) pos = pos + length return pos, nil end result, pos = string.unpack(">I4", data, pos) return pos, datetime.format_time(result) end ---Read a list of static routes. Each of them are a pair of IP addresses, a destination and a -- router. Print an error if the length isn't a multiple of 8. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_static_route(data, pos, length) if((length % 8) ~= 0) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be multiple of %d)", length, 8) pos = pos + length return pos, nil else local results = {} for i=1, length, 8 do local destination, router pos, destination = read_ip(data, pos, 4) pos, router = read_ip(data, pos, 4) table.insert(results, {destination=destination, router=router}) end return pos, results end end ---Read a list of policy filters. Each of them are a pair of IP addresses, an address and a -- mask. Print an error if the length isn't a multiple of 8. -- --@param data The packet. --@param pos The position in the packet. --@param length The length that the server claims the field is. --@return The new position (will always be pos + length, no matter what we think it should be) --@return The value of the field, or nil if the field length was wrong. local function read_policy_filter(data, pos, length) if((length % 8) ~= 0) then stdnse.debug1("dhcp-discover: Invalid length for data (%d; should be multiple of %d)", length, 8) pos = pos + length return pos, nil else local results = {} for i=1, length, 8 do local address, router, mask pos, address = read_ip(data, pos, 4) pos, mask = read_ip(data, pos, 4) table.insert(results, {address=address, mask=mask}) end return pos, results end end ---These are the different fields for DHCP. These have to come after the read_* function -- definitions. -- TODO: Add more from https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options local actions = { [1] = {name="Subnet Mask", func=read_ip, default=true}, [2] = {name="Time Offset", func=read_4_bytes, default=false}, [3] = {name="Router", func=read_ip, default=true}, [4] = {name="Time Server", func=read_ip, default=true}, [5] = {name="Name Server", func=read_ip, default=true}, [6] = {name="Domain Name Server", func=read_ip, default=true}, [7] = {name="Log Server", func=read_ip, default=true}, [8] = {name="Cookie Server", func=read_ip, default=true}, [9] = {name="LPR Server", func=read_ip, default=true}, [10] = {name="Impress Server", func=read_ip, default=true}, [11] = {name="Resource Location Server", func=read_ip, default=true}, [12] = {name="Hostname", func=read_string, default=true}, [13] = {name="Boot File Size", func=read_2_bytes, default=false}, [14] = {name="Merit Dump File", func=read_string, default=false}, [15] = {name="Domain Name", func=read_string, default=true}, [16] = {name="Swap Server", func=read_ip, default=true}, [17] = {name="Root Path", func=read_string, default=false}, [18] = {name="Extensions Path", func=read_string, default=false}, [19] = {name="IP Forwarding", func=read_boolean, default=false}, [20] = {name="Non-local Source Routing", func=read_boolean, default=true}, [21] = {name="Policy Filter", func=read_policy_filter, default=false}, [22] = {name="Maximum Datagram Reassembly Size",func=read_2_bytes, default=false}, [23] = {name="Default IP TTL", func=read_1_byte, default=false}, [24] = {name="Path MTU Aging Timeout", func=read_time, default=false}, [25] = {name="Path MTU Plateau", func=read_2_bytes_list, default=false}, [26] = {name="Interface MTU", func=read_2_bytes, default=false}, [27] = {name="All Subnets are Local", func=read_boolean, default=false}, [28] = {name="Broadcast Address", func=read_ip, default=true}, [29] = {name="Perform Mask Discovery", func=read_boolean, default=false}, [30] = {name="Mask Supplier", func=read_boolean, default=false}, [31] = {name="Perform Router Discovery", func=read_boolean, default=false}, [32] = {name="Router Solicitation Address", func=read_ip, default=true}, [33] = {name="Static Route", func=read_static_route, default=true}, [34] = {name="Trailer Encapsulation", func=read_boolean, default=false}, [35] = {name="ARP Cache Timeout", func=read_time, default=false}, [36] = {name="Ethernet Encapsulation", func=read_boolean, default=false}, [37] = {name="TCP Default TTL", func=read_1_byte, default=false}, [38] = {name="TCP Keepalive Interval", func=read_4_bytes, default=false}, [39] = {name="TCP Keepalive Garbage", func=read_boolean, default=false}, [40] = {name="NIS Domain", func=read_string, default=true}, [41] = {name="NIS Servers", func=read_ip, default=true}, [42] = {name="NTP Servers", func=read_ip, default=true}, [43] = {name="Vendor Specific Information", func=read_string, default=false}, [44] = {name="NetBIOS Name Server", func=read_ip, default=true}, [45] = {name="NetBIOS Datagram Server", func=read_ip, default=true}, [46] = {name="NetBIOS Node Type", func=read_1_byte, default=false}, [47] = {name="NetBIOS Scope", func=read_string, default=false}, [48] = {name="X Window Font Server", func=read_ip, default=true}, [49] = {name="X Window Display Manager", func=read_ip, default=true}, [50] = {name="Requested IP Address (client)", func=read_ip, default=false}, [51] = {name="IP Address Lease Time", func=read_time, default=false}, [52] = {name="Option Overload", func=read_1_byte, default=false}, [53] = {name="DHCP Message Type", func=read_message_type, default=false}, [54] = {name="Server Identifier", func=read_ip, default=true}, [55] = {name="Parameter Request List (client)", func=read_string, default=false}, [56] = {name="Error Message", func=read_string, default=true}, [57] = {name="Maximum DHCP Message Size", func=read_2_bytes, default=false}, [58] = {name="Renewal Time Value", func=read_time, default=false}, [59] = {name="Rebinding Time Value", func=read_time, default=false}, [60] = {name="Class Identifier", func=read_string, default=false}, [61] = {name="Client Identifier (client)", func=read_string, default=false}, [66] = {name="TFTP Server Name", func=read_string, default=false}, [67] = {name="Bootfile Name", func=read_string, default=false}, [252]= {name="WPAD", func=read_string, default=false}, } --- Does the send/receive, doesn't build/parse anything. local function dhcp_send(socket, host, packet) -- Send out the packet return socket:sendto(host, { number=67, protocol="udp" }, packet) end local function dhcp_receive(socket, transaction_id) local status, data = socket:receive() if ( not(status) ) then socket:close() return false, data end -- This pulls back 4 bytes in the packet that correspond to the transaction id. This should be randomly -- generated and different for every instance of a script (to prevent collisions) while status and data:sub(5, 8) ~= transaction_id do status, data = socket:receive() end return status, data end --- Builds a DHCP packet -- --@param request_type The type of request as an integer (use the request_types table at the -- top of this file). --@param ip_address Your ip address (as a dotted-decimal string). This tells the DHCP server where to -- send the response. Setting it to "255.255.255.255" or "0.0.0.0" is generally acceptable (if not, -- host.ip_src can work). --@param mac_address Your mac address (as a string up to 16 bytes) where the server will send the response. Like -- ip_address, setting to the broadcast address (FF:FF:FF:FF:FF:FF) is -- common (host.mac_addr_src works). --@param options [optional] A table of additional request options where each option is a table containing the -- following fields: -- * number - The option number -- * type - The option type ("string" or "ip") -- * value - The option value --@param request_options [optional] The options to request from the server, as an array of integers. For the -- acceptable options, see the actions table above or have a look at rfc2132. -- Some DHCP servers (such as my Linksys WRT54g) will ignore this list and send whichever -- information it wants. Default: all options marked as 'default' in the actions -- table above are requested (the typical interesting ones) if no verbosity is given. -- If any level of verbosity is on, get all types. --@param overrides [optional] A table of overrides. If a field in the table matches a field in the DHCP -- packet (see rfc2131 section 2 for a list of possible fields), the value in the table -- will be sent instead of the default value. --@param lease_time [optional] The lease time used when requesting an IP. Default: none. --@param transaction_id The identity of the transaction. -- --@return status (true or false) --@return The parsed response, as a table. function dhcp_build(request_type, ip_address, mac_address, options, request_options, overrides, lease_time, transaction_id) local packet = strbuf.new() -- Set up the default overrides if(overrides == nil) then overrides = {} end if(request_options == nil) then -- Request the defaults, or there's no verbosity; otherwise, request everything! request_options = strbuf.new() for i,v in pairs(actions) do if(v.default or nmap.verbosity() > 0) then request_options = request_options .. string.char(i) end end request_options = strbuf.dump(request_options) end -- Header packet = packet .. string.pack(">BBBB", overrides['op'] or 1, overrides['htype'] or 1, overrides['hlen'] or 6, overrides['hops'] or 0) -- BOOTREQUEST, 10mb ethernet, 6 bytes long, 0 hops packet = packet .. ( overrides['xid'] or transaction_id ) -- Transaction ID = packet = packet .. string.pack(">I2I2", overrides['secs'] or 0, overrides['flags'] or 0x0000) -- Secs, flags packet = packet .. ip_address -- Client address packet = packet .. string.pack("I4", overrides['cookie'] or 0x63825363) -- Magic cookie -- Options packet = packet .. string.pack(">BBB", 0x35, 1, request_type) -- Request type for _, option in ipairs(options or {}) do packet = packet .. string.pack(">B", option.number) if ( "string" == option.type or "ip" == option.type ) then packet = packet .. string.pack("s1", option.value) end end packet = packet .. string.pack(">Bs1", 0x37, request_options) -- Request options if lease_time then packet = packet .. string.pack(">BBI4", 0x33, 4, lease_time) -- Lease time end packet = packet .. "\xFF" -- Termination return true, strbuf.dump(packet) end ---Parse a DHCP packet (either a request or a response) and return the results -- as a table. -- -- The table at the top of this function (actions) defines the -- name of each field, as laid out in rfc2132, and the function that parses it. -- -- In theory, this should be able to parse any valid DHCP packet. -- --@param data The DHCP packet data. Any padding at the end of the packet will -- be ignored (by default, DHCP packets are padded with \x00 bytes). function dhcp_parse(data, transaction_id) local pos = 1 local result = {} -- Receive the first bit and make sure we got the correct operation back result.op, result.htype, result.hlen, result.hops, pos = string.unpack(">BBBB", data, pos) if(result['op'] ~= 2) then return false, string.format("DHCP server returned invalid reply ('op' wasn't BOOTREPLY (it was 0x%02x))", result['op']) end -- Confirm the transaction id result.xid, pos = string.unpack("c4", data, pos) if(result['xid'] ~= transaction_id) then return false, string.format("DHCP server returned invalid reply (transaction id didn't match (%s != %s))", result['xid'], transaction_id) end -- Unpack the secs, flags, addresses, sname, and file result.secs, result.flags, result.ciaddr, result.yiaddr, result.siaddr, result.giaddr, result.chaddr, result.sname, result.file, pos = string.unpack(">I2I2 I4I4I4I4 c16 c64 c128", data, pos) -- Convert the addresses to strings result['ciaddr_str'] = ipOps.fromdword(result['ciaddr']) result['yiaddr_str'] = ipOps.fromdword(result['yiaddr']) result['siaddr_str'] = ipOps.fromdword(result['siaddr']) result['giaddr_str'] = ipOps.fromdword(result['giaddr']) -- Confirm the cookie result.cookie, pos = string.unpack(">I4", data, pos) if(result['cookie'] ~= 0x63825363) then return false, "DHCP server returned invalid reply (the magic cookie was invalid)" end -- Parse the options result['options'] = {} while true do if #data - pos < 2 then stdnse.debug1("Unexpected end of options") break end local option, length option, length, pos = string.unpack(">BB", data, pos) -- Check for termination condition if(option == 0xFF) then break; end -- Get the action from the array, based on the code local action = actions[option] -- Verify we got a valid code (if we didn't, we're probably in big trouble) local value if(action == nil) then stdnse.debug1("dhcp-discover: Unknown option: %d", option) pos = pos + length else -- Call the function to parse the option, and insert the result into our results table stdnse.debug2("dhcp-discover: Attempting to parse %s", action['name']) pos, value = action['func'](data, pos, length) if(nmap.verbosity() == 0 and action.default == false) then stdnse.debug1("dhcp-discover: Server returned unrequested option (%s => %s)", action['name'], value) else if(value) then table.insert(result['options'], {name=action['name'], value=value}) else stdnse.debug1("dhcp-discover: Couldn't determine value for %s", action['name']); end end end -- Handle the 'Option Overload' option specially -- if it's set, it tells us to use the file and/or sname values after we -- run out of data. if(option == 52) then if(value == 1) then data = data .. result['file'] elseif(value == 2) then data = data .. result['sname'] elseif(value == 3) then data = data .. result['file'] .. result['sname'] else stdnse.debug1("dhcp-discover: Warning: 'Option Overload' gave an unsupported value: %d", value) end end end return true, result end ---Build and send any kind of DHCP packet, and parse the response. This is the only interface -- to the DHCP library, and should be the only one necessary. -- -- All DHCP packet have the same structure, but different fields. It is therefore easy to build -- any of the possible request types: -- * DHCPDISCOVER -- * DHCPOFFER -- * DHCPREQUEST -- * DHCPDECLINE -- * DHCPACK -- * DHCPNAK -- * DHCPRELEASE -- * DHCPINFORM -- -- Although these will all build a valid packet with any option, and the default options (that can be -- overridden with the overrides argument) won't necessarily work with every request -- type. If you're going to build some DHCP code on your own, I recommend reading rfc2131. -- --@param request_type The type of request as an integer (use the request_types table at the -- top of this file). --@param ip_address Your ip address (as a dotted-decimal string). This tells the DHCP server where to -- send the response. Setting it to "255.255.255.255" or "0.0.0.0" is generally acceptable (if not, -- host.ip_src can work). --@param mac_address Your mac address (as a string up to 16 bytes) where the server will send the response. Like -- ip_address, setting to the broadcast address (FF:FF:FF:FF:FF:FF) is -- common (host.mac_addr_src works). --@param options [optional] A table of additional request options where each option is a table containing the -- following fields: -- * number - The option number -- * type - The option type ("string" or "ip") -- * value - The option value --@param request_options [optional] The options to request from the server, as an array of integers. For the -- acceptable options, see the actions table above or have a look at rfc2132. -- Some DHCP servers (such as my Linksys WRT54g) will ignore this list and send whichever -- information it wants. Default: all options marked as 'default' in the actions -- table above are requested (the typical interesting ones) if no verbosity is given. -- If any level of verbosity is on, get all types. --@param overrides [optional] A table of overrides. If a field in the table matches a field in the DHCP -- packet (see rfc2131 section 2 for a list of possible fields), the value in the table -- will be sent instead of the default value. --@param lease_time [optional] The lease time used when requesting an IP. Default: none. --@return status (true or false) --@return The parsed response, as a table. function make_request(target, request_type, ip_address, mac_address, options, request_options, overrides, lease_time) -- A unique id that identifies this particular session (and lets us filter out what we don't want to see) local transaction_id = overrides and overrides['xid'] or string.pack("