ÿØÿà 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ÿÙ--- A minimalistic PPPoE (Point-to-point protocol over Ethernet) -- library, implementing basic support for PPPoE -- Discovery and Configuration requests. The PPPoE protocol is ethernet based -- and hence does not use any IPs or port numbers. -- -- The library contains a number of classes to support packet creation, -- parsing and sending/receiving responses. The classes are: -- o LCP - Contains classes to build and parse PPPoE LCP requests and -- responses. -- -- o PPPoE - Contains classes to build and parse PPPoE requests and -- responses. -- -- o Comm - Contains some basic functions for sending and receiving -- LCP and PPPoE requests and responses. -- -- o Helper- The Helper class serves as the main interface between scripts -- and the library. -- -- -- @author Patrik Karlsson -- local rand = require "rand" local nmap = require "nmap" local packet = require "packet" local stdnse = require "stdnse" local string = require "string" local table = require "table" _ENV = stdnse.module("pppoe", stdnse.seeall) EtherType = { PPPOE_DISCOVERY = 0x8863, PPPOE_SESSION = 0x8864, } -- A Class to handle the Link Control Protocol LCP LCP = { ConfigOption = { RESERVED = 0, MRU = 1, AUTH_PROTO = 3, QUAL_PROTO = 4, MAGIC_NUMBER = 5, PROTO_COMPR = 7, ACFC = 8, -- Value has already been encoded, treat it as a byte stream RAW = -1, -- Creates a new config option -- @param option number containing the configuration option -- @param value containing the configuration option value -- @param raw string containing the configuration options raw value -- @return o new instance of ConfigOption new = function(self, option, value, raw) local o = { option = option, value = value, raw = raw, } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the ConfigOption -- class -- @param data string containing raw bytes to parse -- @return o instance of ConfigOption parse = function(data) local opt, pos, len = {}, 1, 0 opt.option, len, pos = string.unpack("BB", data, pos) opt.raw, pos = string.unpack("c" .. ( len - 2 ), data, pos) -- MRU if ( 1 == opt.option ) then opt.value = string.unpack(">I2", opt.raw) end return LCP.ConfigOption:new(opt.option, opt.value, opt.raw) end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) -- MRU if ( self.raw ) then return string.pack(">BB", self.option, #self.raw + 2) .. self.raw elseif( 1 == self.option ) then return string.pack(">BBI2", 1, 4, self.value) else error( ("Unsupported configuration option %d"):format(self.option) ) end end, }, -- A class to hold multiple ordered config options ConfigOptions = { new = function(self, options) local o = { options = options or {}, } setmetatable(o, self) self.__index = self return o end, -- Adds a new config option to the table -- @param option instance of ConfigOption add = function(self, option) table.insert(self.options, option) end, -- Gets a config option by ID -- @param opt number containing the configuration option to retrieve -- @return v instance of ConfigOption getById = function(self, opt) for _, v in ipairs(self.options) do if ( v.option == opt ) then return v end end end, -- Returns all config options in an ordered table -- @return tab table containing all configuration options getTable = function(self) local tab = {} for _, v in ipairs(self.options) do table.insert(tab, v) end return tab end, -- Parses a byte stream and builds a new instance of the ConfigOptions -- class -- @param data string containing raw bytes to parse -- @return o instance of ConfigOption parse = function(data) local options = LCP.ConfigOptions:new() local pos, opt, opt_val, len repeat opt, len, pos = string.unpack(">BB", data, pos) if ( 0 == opt ) then break end opt_val, pos = string.unpack("c"..len, data, (pos - 2)) options:add(LCP.ConfigOption.parse(opt_val)) until( pos == #data ) return options end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) local str = "" for _, v in ipairs(self.options) do str = str .. tostring(v) end return str end, }, ConfigOptionName = { [0] = "Reserved", [1] = "Maximum receive unit", [3] = "Authentication protocol", [4] = "Quality protocol", [5] = "Magic number", [7] = "Protocol field compression", [8] = "Address and control field compression", }, Code = { CONFIG_REQUEST = 1, CONFIG_ACK = 2, CONFIG_NAK = 3, TERMINATE_REQUEST = 5, TERMINATE_ACK = 6, }, -- The LCP Header Header = { -- Creates a new instance of the LCP header -- @param code number containing the LCP code of the request -- @param identifier number containing the LCP identifier new = function(self, code, identifier) local o = { code = code, identifier = identifier or 1, length = 0, } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the Header class -- @param data string containing raw bytes to parse -- @return o instance of ConfigOption parse = function(data) local header = LCP.Header:new() header.code, header.identifier, header.length = string.unpack(">BBI2", data) return header end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) return string.pack(">BBI2", self.code, self.identifier, self.length) end, }, ConfigRequest = { -- Creates a new instance of the ConfigRequest class -- @param identifier number containing the LCP identifier -- @param options table of LCP.ConfigOption options -- @return o instance of ConfigRequest new = function(self, identifier, options) local o = { header = LCP.Header:new(LCP.Code.CONFIG_REQUEST, identifier), options = LCP.ConfigOptions:new(options) } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the ConfigRequest -- class -- @param data string containing raw bytes to parse -- @return o instance of ConfigRequest parse = function(data) local req = LCP.ConfigRequest:new() req.header = LCP.Header.parse(data) req.options = LCP.ConfigOptions.parse(data:sub(#tostring(req.header) + 1)) return req end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) self.header.length = 4 + #tostring(self.options) return tostring(self.header) .. tostring(self.options) end, }, ConfigNak = { -- Creates a new instance of the ConfigNak class -- @param identifier number containing the LCP identifier -- @param options table of LCP.ConfigOption options -- @return o instance of ConfigNak new = function(self, identifier, options) local o = { header = LCP.Header:new(LCP.Code.CONFIG_NAK, identifier), options = LCP.ConfigOptions:new(options), } setmetatable(o, self) self.__index = self return o end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) self.header.length = 4 + #tostring(self.options) return tostring(self.header) .. tostring(self.options) end, }, ConfigAck = { -- Creates a new instance of the ConfigAck class -- @param identifier number containing the LCP identifier -- @param options table of LCP.ConfigOption options -- @return o instance of ConfigNak new = function(self, identifier, options) local o = { header = LCP.Header:new(LCP.Code.CONFIG_ACK, identifier), options = LCP.ConfigOptions:new(options), } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the ConfigAck class -- @param data string containing raw bytes to parse -- @return o instance of ConfigRequest parse = function(data) local ack = LCP.ConfigAck:new() ack.header = LCP.Header.parse(data) ack.options = LCP.ConfigOptions.parse(data:sub(#tostring(ack.header) + 1)) return ack end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) self.header.length = 4 + #tostring(self.options) return tostring(self.header) .. tostring(self.options) end, }, TerminateRequest = { -- Creates a new instance of the TerminateRequest class -- @param identifier number containing the LCP identifier -- @return o instance of ConfigNak new = function(self, identifier, data) local o = { header = LCP.Header:new(LCP.Code.TERMINATE_REQUEST, identifier), data = data or "", } setmetatable(o, self) self.__index = self return o end, -- Converts the class instance to string -- @return string containing the raw config option __tostring = function(self) self.header.length = 4 + #self.data return tostring(self.header) .. self.data end, } } -- The PPPoE class PPPoE = { -- Supported PPPoE codes (requests/responses) Code = { SESSION_DATA = 0x00, PADO = 0x07, PADI = 0x09, PADR = 0x19, PADS = 0x65, PADT = 0xa7, }, -- Support PPPoE Tag types TagType = { SERVICE_NAME = 0x0101, AC_NAME = 0x0102, HOST_UNIQUE = 0x0103, AC_COOKIE = 0x0104, }, -- Table used to convert table IDs to Names TagName = { [0x0101] = "Service-Name", [0x0102] = "AC-Name", [0x0103] = "Host-Uniq", [0x0104] = "AC-Cookie", }, Header = { -- Creates a new instance of the PPPoE header class -- @param code number containing the PPPoE code -- @param session number containing the PPPoE session -- @return o instance of Header new = function(self, code, session) local o = { version = 1, type = 1, code = code, session = session or 0, length = 0, } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the class -- @param data string containing raw bytes to parse -- @return o instance of Header parse = function(data) local header = PPPoE.Header:new() local vertyp vertyp, header.code, header.session, header.length = string.unpack(">BBI2I2", data) header.version = (vertyp >> 4) header.type = (vertyp & 0x0F) return header end, -- Converts the instance to string -- @return string containing the raw config option __tostring = function(self) local vertype = (self.version << 4) + self.type return string.pack(">BBI2I2", vertype, self.code, self.session, self.length) end, }, -- The TAG NVP Class Tag = { -- Creates a new instance of the Tag class -- @param tag number containing the tag type -- @param value string/number containing the tag value -- @return o instance of Tag new = function(self, tag, value) local o = { tag = tag, value = value or "" } setmetatable(o, self) self.__index = self return o end, -- Converts the instance to string -- @return string containing the raw config option __tostring = function(self) return string.pack(">I2s2", self.tag, self.value) end, }, PADI = { -- Creates a new instance of the PADI class -- @param tags table of PPPoE.Tag instances -- @param value string/number containing the tag value -- @return o instance of ConfigNak new = function(self, tags) local c = rand.random_string(8) local o = { header = PPPoE.Header:new(PPPoE.Code.PADI), tags = tags or { PPPoE.Tag:new(PPPoE.TagType.SERVICE_NAME), PPPoE.Tag:new(PPPoE.TagType.HOST_UNIQUE, c) } } setmetatable(o, self) self.__index = self return o end, -- Converts the instance to string -- @return string containing the raw config option __tostring = function(self) local tags = "" for _, tag in ipairs(self.tags) do tags = tags .. tostring(tag) end self.header.length = #tags return tostring(self.header) .. tags end, }, PADO = { -- Creates a new instance of the PADO class -- @return o instance of PADO new = function(self) local o = { tags = {} } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the class -- @param data string containing raw bytes to parse -- @return o instance of PADO parse = function(data) local pado = PPPoE.PADO:new() pado.header = PPPoE.Header.parse(data) local pos = #tostring(pado.header) + 1 pado.data = data:sub(pos) repeat local tag, decoded, raw tag, raw, pos = string.unpack(">I2s2", pos) if ( PPPoE.TagDecoder[tag] ) then decoded = PPPoE.TagDecoder[tag](raw) else stdnse.debug1("PPPoE: Unsupported tag (%d)", tag) end local t = PPPoE.Tag:new(tag, raw) t.decoded = decoded table.insert(pado.tags, t) until( pos >= #data ) return pado end, }, PADR = { -- Creates a new instance of the PADR class -- @param tags table of PPPoE.Tag instances -- @return o instance of PADR new = function(self, tags) local o = { tags = tags or {}, header = PPPoE.Header:new(PPPoE.Code.PADR) } setmetatable(o, self) self.__index = self return o end, -- Converts the instance to string -- @return string containing the raw config option __tostring = function(self) local tags = "" for _, tag in ipairs(self.tags) do tags = tags .. tostring(tag) end self.header.length = #tags return tostring(self.header) .. tags end, }, PADS = { -- Creates a new instance of the PADS class -- @return o instance of PADS new = function(self) local o = { tags = {} } setmetatable(o, self) self.__index = self return o end, -- Parses a byte stream and builds a new instance of the class -- @param data string containing raw bytes to parse -- @return o instance of PADS parse = function(data) local pads = PPPoE.PADS:new() pads.header = PPPoE.Header.parse(data) local pos = #tostring(pads.header) + 1 pads.data = data:sub(pos) return pads end, }, PADT = { -- Creates a new instance of the PADT class -- @param session number containing the PPPoE session -- @return o instance of PADT new = function(self, session) local o = { header = PPPoE.Header:new(PPPoE.Code.PADT) } setmetatable(o, self) o.header.session = session self.__index = self return o end, -- Parses a byte stream and builds a new instance of the class -- @param data string containing raw bytes to parse -- @return o instance of PADI parse = function(data) local padt = PPPoE.PADT:new() padt.header = PPPoE.Header.parse(data) return padt end, -- Converts the instance to string -- @return string containing the raw config option __tostring = function(self) return tostring(self.header) end, }, SessionData = { -- Creates a new instance of the SessionData class -- @param session number containing the PPPoE session -- @param data string containing the LCP data to send -- @return o instance of ConfigNak new = function(self, session, data) local o = { data = data or "", header = PPPoE.Header:new(PPPoE.Code.SESSION_DATA) } setmetatable(o, self) o.header.session = session self.__index = self return o end, -- Parses a byte stream and builds a new instance of the class -- @param data string containing raw bytes to parse -- @return o instance of SessionData parse = function(data) local sess = PPPoE.SessionData:new() sess.header = PPPoE.Header.parse(data) local pos = #tostring(sess.header) + 1 + 2 sess.data = data:sub(pos) return sess end, -- Converts the instance to string -- @return string containing the raw config option __tostring = function(self) -- 2 for the encapsulation self.header.length = 2 + 4 + #self.data return tostring(self.header) .. "\xC0\x21" .. self.data end, } } -- A bunch of tag decoders PPPoE.TagDecoder = {} PPPoE.TagDecoder.decodeHex = stdnse.tohex PPPoE.TagDecoder.decodeStr = function(data) return data end PPPoE.TagDecoder[PPPoE.TagType.SERVICE_NAME] = PPPoE.TagDecoder.decodeStr PPPoE.TagDecoder[PPPoE.TagType.AC_NAME] = PPPoE.TagDecoder.decodeStr PPPoE.TagDecoder[PPPoE.TagType.AC_COOKIE] = PPPoE.TagDecoder.decodeHex PPPoE.TagDecoder[PPPoE.TagType.HOST_UNIQUE] = PPPoE.TagDecoder.decodeHex -- The Comm class responsible for communication with the PPPoE server Comm = { -- Creates a new instance of the Comm class -- @param iface string containing the interface name -- @param src_mac string containing the source MAC address -- @param dst_mac string containing the destination MAC address -- @return o new instance of Comm new = function(self, iface, src_mac, dst_mac) local o = { iface = iface, src_mac = src_mac, dst_mac = dst_mac, } setmetatable(o, self) self.__index = self return o end, -- Sets up the pcap receiving socket -- @return status true on success connect = function(self) self.socket = nmap.new_socket() self.socket:set_timeout(10000) local mac = stdnse.format_mac(self.src_mac) -- let's set a filter on PPPoE we can then check what packet is ours, -- based on the HOST_UNIQUE tag, if we need to self.socket:pcap_open(self.iface, 1500, false, "ether[0x0c:2] == 0x8863 or ether[0x0c:2] == 0x8864 and ether dst " .. mac) return true end, -- Sends a packet -- @param data class containing the request to send -- @return status true on success, false on failure send = function(self, data) local eth_type = ( data.header.code == PPPoE.Code.SESSION_DATA ) and 0x8864 or 0x8863 local ether = self.dst_mac .. self.src_mac .. string.pack(">I2", eth_type) local p = packet.Frame:new(ether .. tostring(data)) local sock = nmap.new_dnet() if ( not(sock) ) then return false, "Failed to create raw socket" end local status = sock:ethernet_open(self.iface) -- we don't actually need to do this as the script simply crashes -- if we don't have the right permissions at this point if ( not(status) ) then return false, "Failed to open raw socket" end status = sock:ethernet_send(p.frame_buf) if ( not(status) ) then return false, "Failed to send data" end sock:ethernet_close() return true end, -- Receive a response from the server -- @return status true on success, false on failure -- @return response class containing the response or -- err string on error recv = function(self) local status, _, l2, l3 = self.socket:pcap_receive() -- if we got no response, just return false as there's -- probably not really an error if ( not(status) ) then return false, "Did not receive any packets" end local header = PPPoE.Header.parse(l3) local p = packet.Frame:new(l2..l3) -- there's probably a more elegant way of doing this if ( EtherType.PPPOE_DISCOVERY == p.ether_type ) then if ( header.code == PPPoE.Code.PADO ) then local pado = PPPoE.PADO.parse(l3) pado.mac_srv = p.mac_src return true, pado elseif ( header.code == PPPoE.Code.PADS ) then local pads = PPPoE.PADS.parse(l3) return true, pads elseif ( header.code == PPPoE.Code.PADT ) then local pads = PPPoE.PADT.parse(l3) return true, pads end elseif ( EtherType.PPPOE_SESSION == p.ether_type ) then return true, PPPoE.SessionData.parse(l3) end return false, ("Received unsupported response, can't decode code (%d)"):format(header.code) end, -- Does an "exchange", ie, sends a request and waits for a response -- @param data class containing the request to send -- @return status true on success, false on failure -- @return response class containing the response or -- err string on error exch = function(self, data) local status, err = self:send(data) if ( not(status) ) then return false, err end local retries, resp = 3, nil repeat status, resp = self:recv() if ( data.header and 0 == data.header.session ) then return true, resp elseif ( data.header and data.header.session == resp.header.session ) then return true, resp end retries = retries - 1 until(retries == 0) return false, "Failed to retrieve proper PPPoE response" end, } -- The Helper class is the main script interface Helper = { -- Creates a new instance of Helper -- @param iface string containing the name of the interface to use -- @return o new instance on success, nil on failure new = function(self, iface) local o = { iface = iface, -- set the LCP identifier to 0 identifier = 0, } setmetatable(o, self) self.__index = self if ( not(nmap.is_privileged()) ) then return nil, "The PPPoE library requires Nmap to be run in privileged mode" end -- get src_mac local info = nmap.get_interface_info(iface) if ( not(info) or not(info.mac) ) then return nil, "Failed to get source MAC address" end o.comm = Comm:new(iface, info.mac) return o end, -- Sets up the pcap socket for listening and does some other preparations -- @return status true on success, false on failure connect = function(self) return self.comm:connect() end, -- Performs a PPPoE discovery initiation by sending a PADI request to the -- ethernet broadcast address -- @return status true on success, false on failure -- @return pado instance of PADO on success, err string on failure discoverInit = function(self) local padi = PPPoE.PADI:new() self.comm.dst_mac = ("\xFF"):rep(6) local status, err = self.comm:send(padi) if ( not(status) ) then return false, err end -- wait for a pado local pado, retries = nil, 3 repeat status, pado = self.comm:recv() if ( not(status) ) then return status, pado end retries = retries - 1 until( pado.tags or retries == 0 ) if ( not(pado.tags) ) then return false, "PADO response contained no tags" end local pado_host_unique for _, tag in ipairs(pado.tags) do if ( PPPoE.TagType.HOST_UNIQUE == tag.tag ) then pado_host_unique = tag.raw end end -- store the tags for later use self.tags = pado.tags self.comm.dst_mac = pado.mac_srv if ( pado_host_unique and pado_host_unique ~= padi.tags[PPPoE.TagType.HOST_UNIQUE] ) then -- currently, we don't handle this, we probably should -- in order to do so, we need to split the function exch -- to recv and send return false, "Got incorrect answer" end return true, pado end, -- Performs a Discovery Request by sending PADR to the PPPoE ethernet -- address -- @return status true on success, false on failure -- @return pads instance of PADS on success discoverRequest = function(self) -- remove the AC-Name tag if there is one local function getTag(tag) for _, t in ipairs(self.tags) do if ( tag == t.tag ) then return t end end end local taglist = { PPPoE.TagType.SERVICE_NAME, PPPoE.TagType.HOST_UNIQUE, PPPoE.TagType.AC_COOKIE } local tags = {} for _, t in ipairs(taglist) do if ( getTag(t) ) then table.insert(tags, getTag(t)) end end local padr = PPPoE.PADR:new(tags) local status, pads = self.comm:exch(padr) if ( status ) then self.session = pads.header.session end return status, pads end, -- Attempts to specify a method for authentication -- If the server responds with another method it's NAK:ed and we try to set -- our requested method instead. If this fails, we return a failure -- @param method string containing one of the following methods: -- MSCHAPv1, MSCHAPv2 or PAP -- @return status true on success, false on failure -- err string containing error message on failure setAuthMethod = function(self, method) local AuthMethod = { methods = { { name = "EAP", value = "\xC2\x27" }, { name = "MSCHAPv1", value = "\xC2\x23\x80" }, { name = "MSCHAPv2", value = "\xC2\x23\x81" }, { name = "PAP", value = "\xC0\x23" }, } } AuthMethod.byName = function(name) for _, m in ipairs(AuthMethod.methods) do if ( m.name == name ) then return m end end end AuthMethod.byValue = function(value) for _, m in ipairs(AuthMethod.methods) do if ( m.value == value ) then return m end end end local auth_data = ( AuthMethod.byName(method) and AuthMethod.byName(method).value ) if ( not(auth_data) ) then return false, ("Unsupported authentication mode (%s)"):format(method) end self.identifier = self.identifier + 1 -- First do a Configuration Request local options = { LCP.ConfigOption:new(LCP.ConfigOption.MRU, 1492) } local lcp_req = LCP.ConfigRequest:new(self.identifier, options) local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req)) local status, resp = self.comm:exch(sess_req) if ( not(status) or PPPoE.Code.SESSION_DATA ~= resp.header.code ) then return false, "Unexpected packet type was received" end -- Make sure we got a Configuration Request in return local lcp_header = LCP.Header.parse(resp.data) if ( LCP.Code.CONFIG_REQUEST ~= lcp_header.code ) then return false, ("Unexpected packet type was received (%d)"):format(lcp_header.code) end local config_req = LCP.ConfigRequest.parse(resp.data) if ( not(config_req.options) ) then return false, "Failed to retrieve any options from response" end local auth_proposed = config_req.options:getById(LCP.ConfigOption.AUTH_PROTO) if ( auth_proposed.raw ~= auth_data ) then local options = { LCP.ConfigOption:new(LCP.ConfigOption.AUTH_PROTO, nil, auth_data) } local lcp_req = LCP.ConfigNak:new(self.identifier, options) local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req)) local status, resp = self.comm:exch(sess_req) if ( not(status) or PPPoE.Code.SESSION_DATA ~= resp.header.code ) then return false, "Unexpected packet type was received" end -- Make sure we got a Configuration Request in return local lcp_header = LCP.Header.parse(resp.data) if ( LCP.Code.CONFIG_REQUEST ~= lcp_header.code ) then return false, ("Unexpected packet type was received (%d)"):format(lcp_header.code) end config_req = LCP.ConfigRequest.parse(resp.data) -- if the authentication methods match, send an ACK if ( config_req.options:getById(LCP.ConfigOption.AUTH_PROTO).raw == auth_data ) then -- The ACK is essential the Config Request, only with a different code -- Do a dirty attempt to just replace the code and send the request back as an ack self.identifier = self.identifier + 1 local lcp_req = LCP.ConfigAck:new(config_req.header.identifier, config_req.options:getTable()) local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req)) local status, resp = self.comm:send(sess_req) return true end return false, "Authentication method was not accepted" end return false, "Failed to negotiate authentication mechanism" end, -- Sends a LCP Terminate Request and waits for an ACK -- Attempts to do so 10 times before aborting -- @return status true on success false on failure close = function(self) local tries = 10 repeat if ( 0 == self.session ) then break end local lcp_req = LCP.TerminateRequest:new(self.identifier) local sess_req = PPPoE.SessionData:new(self.session, tostring(lcp_req)) local status, resp = self.comm:exch(sess_req) if ( status and resp.header and resp.header.code ) then if ( PPPoE.Code.SESSION_DATA == resp.header.code ) then local lcp_header = LCP.Header.parse(resp.data) if ( LCP.Code.TERMINATE_ACK == lcp_header.code ) then break end end end tries = tries - 1 until( tries == 0 ) self.comm:exch(PPPoE.PADT:new(self.session)) return true end, } return _ENV;