PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/packetfu/packet.rb

http://packetfu.googlecode.com/
Ruby | 533 lines | 335 code | 39 blank | 159 comment | 40 complexity | 8d29378478e8d4fd7aad72ac5bb8f855 MD5 | raw file
  1. module PacketFu
  2. # Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
  3. # other packets. It acts as both a singleton class, so things like
  4. # Packet.parse can happen, and as an abstract class to provide
  5. # subclasses some structure.
  6. class Packet
  7. attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
  8. attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
  9. attr_accessor :iface # Default inferface to send packets to
  10. attr_accessor :inspect_style # Default is :dissect, can also be :hex or :default
  11. # Register subclasses in PacketFu.packet_class to do all kinds of neat things
  12. # that obviates those long if/else trees for parsing. It's pretty sweet.
  13. def self.inherited(subclass)
  14. PacketFu.add_packet_class(subclass)
  15. end
  16. # Force strings into binary.
  17. def self.force_binary(str)
  18. str.force_encoding "binary" if str.respond_to? :force_encoding
  19. end
  20. # Parse() creates the correct packet type based on the data, and returns the apporpiate
  21. # Packet subclass object.
  22. #
  23. # There is an assumption here that all incoming packets are either EthPacket
  24. # or InvalidPacket types. This will be addressed pretty soon.
  25. #
  26. # If application-layer parsing is /not/ desired, that should be indicated explicitly
  27. # with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
  28. #
  29. # It is no longer neccisary to manually add packet types here.
  30. def self.parse(packet=nil,args={})
  31. parse_app = true if(args[:parse_app].nil? or args[:parse_app])
  32. force_binary(packet)
  33. if parse_app
  34. classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
  35. else
  36. classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
  37. end
  38. p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
  39. parsed_packet = p.read(packet,args)
  40. end
  41. def handle_is_identity(ptype)
  42. idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
  43. if idx
  44. self.kind_of? PacketFu.packet_classes[idx]
  45. else
  46. raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
  47. end
  48. end
  49. # Get the binary string of the entire packet.
  50. def to_s
  51. @headers[0].to_s
  52. end
  53. # In the event of no proper decoding, at least send it to the inner-most header.
  54. def write(io)
  55. @headers[0].write(io)
  56. end
  57. # Get the outermost payload (body) of the packet; this is why all packet headers
  58. # should have a body type.
  59. def payload
  60. @headers.last.body
  61. end
  62. # Set the outermost payload (body) of the packet.
  63. def payload=(args)
  64. @headers.last.body=(args)
  65. end
  66. # Converts a packet to libpcap format. Bit of a hack?
  67. def to_pcap(args={})
  68. p = PcapPacket.new(:endian => args[:endian],
  69. :timestamp => Timestamp.new.to_s,
  70. :incl_len => self.to_s.size,
  71. :orig_len => self.to_s.size,
  72. :data => self)
  73. end
  74. # Put the entire packet into a libpcap file. XXX: this is a
  75. # hack for now just to confirm that packets are getting created
  76. # correctly. Now with append! XXX: Document this!
  77. def to_f(filename=nil,mode='w')
  78. filename ||= 'out.pcap'
  79. mode = mode.to_s[0,1] + "b"
  80. raise ArgumentError, "Unknown mode: #{mode.to_s}" unless mode =~ /^[wa]/
  81. if(mode == 'w' || !(File.exists?(filename)))
  82. data = [PcapHeader.new, self.to_pcap].map {|x| x.to_s}.join
  83. else
  84. data = self.to_pcap
  85. end
  86. File.open(filename, mode) {|f| f.write data}
  87. return [filename, 1, data.size]
  88. end
  89. # Put the entire packet on the wire by creating a temporary PacketFu::Inject object.
  90. # TODO: Do something with auto-checksumming?
  91. def to_w(iface=nil)
  92. iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s
  93. inj = PacketFu::Inject.new(:iface => iface)
  94. inj.array = [@headers[0].to_s]
  95. inj.inject
  96. end
  97. # Recalculates all the calcuated fields for all headers in the packet.
  98. # This is important since read() wipes out all the calculated fields
  99. # such as length and checksum and what all.
  100. def recalc(arg=:all)
  101. case arg
  102. when :ip
  103. ip_recalc(:all)
  104. when :icmp
  105. icmp_recalc(:all)
  106. when :udp
  107. udp_recalc(:all)
  108. when :tcp
  109. tcp_recalc(:all)
  110. when :all
  111. ip_recalc(:all) if @ip_header
  112. icmp_recalc(:all) if @icmp_header
  113. udp_recalc(:all) if @udp_header
  114. tcp_recalc(:all) if @tcp_header
  115. else
  116. raise ArgumentError, "Recalculating `#{arg}' unsupported. Try :all"
  117. end
  118. @headers[0]
  119. end
  120. # Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
  121. # Note that read is a destructive process, so any existing data will be lost.
  122. #
  123. # A note on the :strip => true argument: If :strip is set, defined lengths of data will
  124. # be believed, and any trailers (such as frame check sequences) will be chopped off. This
  125. # helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
  126. #
  127. # If :strip is false, header lengths are /not/ believed, and all data will be piped in.
  128. # When capturing from the wire, this is usually fine, but recalculating the length before
  129. # saving or re-transmitting will absolutely change the data payload; FCS data will become
  130. # part of the TCP data as far as tcp_len is concerned. Some effort has been made to preserve
  131. # the "real" payload for the purposes of checksums, but currently, it's impossible to seperate
  132. # new payload data from old trailers, so things like pkt.payload += "some data" will not work
  133. # correctly.
  134. #
  135. # So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
  136. # this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
  137. # should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
  138. #
  139. # Finally, packet subclasses should take two arguments: the string that is the data
  140. # to be transmuted into a packet, as well as args. This superclass method is merely
  141. # concerned with handling args common to many packet formats (namely, fixing packets
  142. # on the fly)
  143. def read(args={})
  144. if args[:fix] || args[:recalc]
  145. ip_recalc(:ip_sum) if self.is_ip?
  146. recalc(:tcp) if self.is_tcp?
  147. recalc(:udp) if self.is_udp?
  148. end
  149. end
  150. # Packets are bundles of lots of objects, so copying them
  151. # is a little complicated -- a dup of a packet is actually
  152. # full of pass-by-reference stuff in the @headers, so
  153. # if you change one, you're changing all this copies, too.
  154. #
  155. # Normally, this doesn't seem to be a big deal, and it's
  156. # a pretty decent performance tradeoff. But, if you're going
  157. # to be creating a template packet to base a bunch of slightly
  158. # different ones off of (like a fuzzer might), you'll want
  159. # to use clone()
  160. def clone
  161. Packet.parse(self.to_s)
  162. end
  163. # If two packets are represented as the same binary string, and
  164. # they're both actually PacketFu packets of the same sort, they're equal.
  165. #
  166. # The intuitive result is that a packet of a higher layer (like DNSPacket)
  167. # can be equal to a packet of a lower level (like UDPPacket) as long as
  168. # the bytes are equal (this can come up if a transport-layer packet has
  169. # a hand-crafted payload that is identical to what would have been created
  170. # by using an application layer packet)
  171. def ==(other)
  172. return false unless other.kind_of? self.class
  173. return false unless other.respond_to? :to_s
  174. self.to_s == other.to_s
  175. end
  176. # Peek provides summary data on packet contents.
  177. #
  178. # Each packet type should provide a peek_format.
  179. def peek(args={})
  180. idx = @headers.reverse.map {|h| h.respond_to? peek_format}.index(true)
  181. if idx
  182. @headers.reverse[idx].peek_format
  183. else
  184. peek_format
  185. end
  186. end
  187. # The peek_format is used to display a single line
  188. # of packet data useful for eyeballing. It should not exceed
  189. # 80 characters. The Packet superclass defines an example
  190. # peek_format, but it should hardly ever be triggered, since
  191. # peek traverses the @header list in reverse to find a suitable
  192. # format.
  193. #
  194. # === Format
  195. #
  196. # * A one or two character protocol initial. It should be unique
  197. # * The packet size
  198. # * Useful data in a human-usable form.
  199. #
  200. # Ideally, related peek_formats will all line up with each other
  201. # when printed to the screen.
  202. #
  203. # === Example
  204. #
  205. # tcp_packet.peek
  206. # #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0"
  207. # tcp_packet.peek.size
  208. # #=> 79
  209. #
  210. def peek_format
  211. peek_data = ["? "]
  212. peek_data << "%-5d" % self.to_s.size
  213. peek_data << "%68s" % self.to_s[0,34].unpack("H*")[0]
  214. peek_data.join
  215. end
  216. # Defines the layer this packet type lives at, based on the number of headers it
  217. # requires. Note that this has little to do with the OSI model, since TCP/IP
  218. # doesn't really have Session and Presentation layers.
  219. #
  220. # Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
  221. # TCP, UDP, and other transport protocols are layer 3, and application
  222. # protocols are at layer 4 or higher. InvalidPackets have an arbitrary
  223. # layer 0 to distinguish them.
  224. #
  225. # Because these don't change much, it's cheaper just to case through them,
  226. # and only resort to counting headers if we don't have a match -- this
  227. # makes adding protocols somewhat easier, but of course you can just
  228. # override this method over there, too. This is merely optimized
  229. # for the most likely protocols you see on the Internet.
  230. def self.layer
  231. case self.name # Lol ran into case's fancy treatment of classes
  232. when /InvalidPacket$/; 0
  233. when /EthPacket$/; 1
  234. when /IPPacket$/, /ARPPacket$/, /IPv6Packet$/; 2
  235. when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
  236. when /HSRPPacket$/; 4
  237. else; self.new.headers.size
  238. end
  239. end
  240. def layer
  241. self.class.layer
  242. end
  243. def self.layer_symbol
  244. case self.layer
  245. when 0; :invalid
  246. when 1; :link
  247. when 2; :internet
  248. when 3; :transport
  249. else; :application
  250. end
  251. end
  252. def layer_symbol
  253. self.class.layer_symbol
  254. end
  255. # Packet subclasses must override this, since the Packet superclass
  256. # can't actually parse anything.
  257. def self.can_parse?(str)
  258. false
  259. end
  260. # Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
  261. def hexify(str)
  262. str.force_encoding("ASCII-8BIT") if str.respond_to? :force_encoding
  263. hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
  264. regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n')
  265. chars = str.to_s.gsub(regex,'.')
  266. chars_lines = chars.scan(/.{1,16}/)
  267. ret = []
  268. hexascii_lines.size.times {|i| ret << "%-48s %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]}
  269. ret.join("\n")
  270. end
  271. # If @inspect_style is :default (or :ugly), the inspect output is the usual
  272. # inspect.
  273. #
  274. # If @inspect_style is :hex (or :pretty), the inspect output is
  275. # a much more compact hexdump-style, with a shortened set of packet header
  276. # names at the top.
  277. #
  278. # If @inspect_style is :dissect (or :verbose), the inspect output is the
  279. # longer, but more readable, dissection of the packet. This is the default.
  280. #
  281. # TODO: Have an option for colors. Everyone loves colorized irb output.
  282. def inspect_hex(arg=0)
  283. case arg
  284. when :layers
  285. ret = []
  286. @headers.size.times do |i|
  287. ret << hexify(@headers[i])
  288. end
  289. ret
  290. when (0..9)
  291. if @headers[arg]
  292. hexify(@headers[arg])
  293. else
  294. nil
  295. end
  296. when :all
  297. inspect_hex(0)
  298. end
  299. end
  300. def dissection_table
  301. table = []
  302. @headers.each_with_index do |header,table_idx|
  303. proto = header.class.name.sub(/^.*::/,"")
  304. table << [proto,[]]
  305. header.class.members.each do |elem|
  306. elem_sym = elem.to_sym # to_sym needed for 1.8
  307. next if elem_sym == :body
  308. elem_type_value = []
  309. elem_type_value[0] = elem
  310. readable_element = "#{elem}_readable"
  311. if header.respond_to? readable_element
  312. elem_type_value[1] = header.send(readable_element)
  313. else
  314. elem_type_value[1] = header.send(elem)
  315. end
  316. elem_type_value[2] = header[elem.to_sym].class.name
  317. table[table_idx][1] << elem_type_value
  318. end
  319. end
  320. table
  321. if @headers.last.members.map {|x| x.to_sym }.include? :body
  322. body_part = [:body, self.payload, @headers.last.body.class.name]
  323. end
  324. table << body_part
  325. end
  326. # Renders the dissection_table suitable for screen printing. Can take
  327. # one or two arguments. If just the one, only that layer will be displayed
  328. # take either a range or a number -- if a range, only protos within
  329. # that range will be rendered. If an integer, only that proto
  330. # will be rendered.
  331. def dissect
  332. dtable = self.dissection_table
  333. hex_body = nil
  334. if dtable.last.kind_of?(Array) and dtable.last.first == :body
  335. body = dtable.pop
  336. hex_body = hexify(body[1])
  337. end
  338. elem_widths = [0,0,0]
  339. dtable.each do |proto_table|
  340. proto_table[1].each do |elems|
  341. elems.each_with_index do |e,i|
  342. width = e.size
  343. elem_widths[i] = width if width > elem_widths[i]
  344. end
  345. end
  346. end
  347. total_width = elem_widths.inject(0) {|sum,x| sum+x}
  348. table = ""
  349. dtable.each do |proto|
  350. table << "--"
  351. table << proto[0]
  352. if total_width > proto[0].size
  353. table << ("-" * (total_width - proto[0].size + 2))
  354. else
  355. table << ("-" * (total_width + 2))
  356. end
  357. table << "\n"
  358. proto[1].each do |elems|
  359. table << " "
  360. elems_table = []
  361. (0..2).each do |i|
  362. elems_table << ("%-#{elem_widths[i]}s" % elems[i])
  363. end
  364. table << elems_table.join("\s")
  365. table << "\n"
  366. end
  367. end
  368. if hex_body && !hex_body.empty?
  369. table << "-" * 66
  370. table << "\n"
  371. table << "00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f---0123456789abcdef\n"
  372. table << "-" * 66
  373. table << "\n"
  374. table << hex_body
  375. end
  376. table
  377. end
  378. alias :orig_kind_of? :kind_of?
  379. def kind_of?(klass)
  380. return true if orig_kind_of? klass
  381. packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
  382. match = false
  383. packet_types.each do |p|
  384. if p.ancestors.include? klass
  385. match = true
  386. break
  387. end
  388. end
  389. return match
  390. end
  391. # For packets, inspect is overloaded as inspect_hex(0).
  392. # Not sure if this is a great idea yet, but it sure makes
  393. # the irb output more sane.
  394. #
  395. # If you hate this, you can run PacketFu.toggle_inspect to return
  396. # to the typical (and often unreadable) Object#inspect format.
  397. def inspect
  398. case @inspect_style
  399. when :dissect
  400. self.dissect
  401. when :hex
  402. self.proto.join("|") + "\n" + self.inspect_hex
  403. else
  404. super
  405. end
  406. end
  407. # Returns the size of the packet (as a binary string)
  408. def size
  409. self.to_s.size
  410. end
  411. # Returns an array of protocols contained in this packet. For example:
  412. #
  413. # t = PacketFu::TCPPacket.new
  414. # => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E.
  415. # 00 28 3c ab 00 00 ff 06 7f 25 00 00 00 00 00 00 .(<......%......
  416. # 00 00 93 5e 00 00 ad 4f e4 a4 00 00 00 00 50 00 ...^...O......P.
  417. # 40 00 4a 92 00 00 @.J...
  418. # t.proto
  419. # => ["Eth", "IP", "TCP"]
  420. #
  421. def proto
  422. type_array = []
  423. self.headers.each {|header| type_array << header.class.to_s.split('::').last.gsub(/Header$/,'')}
  424. type_array
  425. end
  426. alias_method :protocol, :proto
  427. alias_method :length, :size
  428. # the Packet class should not be instantiated directly, since it's an
  429. # abstract class that real packet types inherit from. Sadly, this
  430. # makes the Packet class more difficult to test directly.
  431. def initialize(args={})
  432. if self.class.name =~ /(::|^)PacketFu::Packet$/
  433. raise NoMethodError, "method `new' called for abstract class #{self.class.name}"
  434. end
  435. @inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect
  436. if args[:config]
  437. args[:config].each_pair do |k,v|
  438. case k
  439. when :eth_daddr; @eth_header.eth_daddr=v if @eth_header
  440. when :eth_saddr; @eth_header.eth_saddr=v if @eth_header
  441. when :ip_saddr; @ip_header.ip_saddr=v if @ip_header
  442. when :iface; @iface = v
  443. end
  444. end
  445. end
  446. end
  447. # Delegate to PacketFu's inspect_style, since the
  448. # class variable name is the same. Yay for namespace
  449. # pollution!
  450. def inspect_style=()
  451. PacketFu.inspect_style(arg)
  452. end
  453. #method_missing() delegates protocol-specific field actions to the apporpraite
  454. #class variable (which contains the associated packet type)
  455. #This register-of-protocols style switch will work for the
  456. #forseeable future (there aren't /that/ many packet types), and it's a handy
  457. #way to know at a glance what packet types are supported.
  458. def method_missing(sym, *args, &block)
  459. case sym.to_s
  460. when /^is_([a-zA-Z0-9]+)\?/
  461. ptype = $1
  462. if PacketFu.packet_prefixes.index(ptype)
  463. self.send(:handle_is_identity, $1)
  464. else
  465. super
  466. end
  467. when /^([a-zA-Z0-9]+)_.+/
  468. ptype = $1
  469. if PacketFu.packet_prefixes.index(ptype)
  470. self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
  471. else
  472. super
  473. end
  474. else
  475. super
  476. end
  477. end
  478. def respond_to?(sym, include_private = false)
  479. if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
  480. self.instance_variable_get("@#{$1}_header").respond_to? sym
  481. elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
  482. if PacketFu.packet_prefixes.index($1)
  483. true
  484. else
  485. super
  486. end
  487. else
  488. super
  489. end
  490. end
  491. end # class Packet
  492. end
  493. # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby