diff --git a/nselib/packet.lua b/nselib/packet.lua index 3e9260089..11ae0f96c 100644 --- a/nselib/packet.lua +++ b/nselib/packet.lua @@ -70,7 +70,23 @@ function set_u32(b,i, num) string.char(bit.band(num, 0xff)) return b:sub(0+1, i+1-1) .. s .. b:sub(i+1+4) end - +--- Get a 1-byte string from a number. +-- @param num A number. +function numtostr8(num) + return string.char(num) +end +--- Get a 2-byte string from a number. +-- (big-endian) +-- @param num A number. +function numtostr16(num) + return set_u16("..", 0, num) +end +--- Get a 4-byte string from a number. +-- (big-endian) +-- @param num A number. +function numtostr32(num) + return set_u32("....", 0, num) +end --- Calculate a standard Internet checksum. -- @param b Data to checksum. @@ -98,6 +114,7 @@ end -- ip protocol field IPPROTO_IP = 0 -- Dummy protocol for TCP +IPPROTO_HOPOPTS = 0 -- IPv6 hop-by-hop options IPPROTO_ICMP = 1 -- Internet Control Message Protocol IPPROTO_IGMP = 2 -- Internet Group Management Protocol IPPROTO_IPIP = 4 -- IPIP tunnels (older KA9Q tunnels use 94) @@ -111,9 +128,13 @@ IPPROTO_RSVP = 46 -- RSVP protocol IPPROTO_GRE = 47 -- Cisco GRE tunnels (rfc 1701,1702) IPPROTO_IPV6 = 41 -- IPv6-in-IPv4 tunnelling -IPPROTO_ESP = 50 -- Encapsulation Security Payload protocol -IPPROTO_AH = 51 -- Authentication Header protocol -IPPROTO_BEETPH = 94 -- IP option pseudo header for BEET +IPPROTO_ROUTING = 43 -- IPv6 routing header +IPPROTO_FRAGMENT= 44 -- IPv6 fragmentation header +IPPROTO_ESP = 50 -- Encapsulation Security Payload protocol +IPPROTO_AH = 51 -- Authentication Header protocol +IPPROTO_ICMPV6 = 58 -- ICMP for IPv6 +IPPROTO_DSTOPTS = 60 -- IPv6 destination options +IPPROTO_BEETPH = 94 -- IP option pseudo header for BEET IPPROTO_PIM = 103 -- Protocol Independent Multicast IPPROTO_COMP = 108 -- Compression Header protocol @@ -121,6 +142,76 @@ IPPROTO_SCTP = 132 -- Stream Control Transport Protocol IPPROTO_UDPLITE = 136 -- UDP-Lite (RFC 3828) +ICMP6_ECHO_REQUEST = 128 +ICMP6_ECHO_REPLY = 129 +MLD_LISTENER_QUERY = 130 +MLD_LISTENER_REPORT = 131 +MLD_LISTENER_REDUCTION = 132 +ND_ROUTER_SOLICIT = 133 +ND_ROUTER_ADVERT = 134 +ND_NEIGHBOR_SOLICIT = 135 +ND_NEIGHBOR_ADVERT = 136 +ND_REDIRECT = 137 + +ND_OPT_SOURCE_LINKADDR = 1 +ND_OPT_TARGET_LINKADDR = 2 +ND_OPT_PREFIX_INFORMATION = 3 +ND_OPT_REDIRECTED_HEADER = 4 +ND_OPT_MTU = 5 +ND_OPT_RTR_ADV_INTERVAL = 7 +ND_OPT_HOME_AGENT_INFO = 8 + +ETHER_TYPE_IPV4 = string.char(0x08, 0x00) +ETHER_TYPE_IPV6 = string.char(0x86, 0xdd) + +---------------------------------------------------------------------------------------------------------------- +-- Frame is a class +Frame = {} + +function Frame:new(frame, force_continue) + local packet = nil + local packet_len = 0 + if frame and #frame > 14 then + packet = string.sub(frame, 15, -1) + packet_len = #frame - 14 + end + local o = Packet:new(packet, packet_len, force_continue) + + o.build_ether_frame = self.build_ether_frame + o.ether_parse = self.ether_parse + o.frame_buf = frame + o:ether_parse() + return o +end +--- Build an Ethernet frame. +-- @param mac_dst six-byte string of the destination MAC address. +-- @param mac_src six-byte string of the source MAC address. +-- @param ether_type two-byte string of the type. +-- @param packet string of the payload. +-- @return frame string of the Ether frame. +function Frame:build_ether_frame(mac_dst, mac_src, ether_type, packet) + self.mac_dst = mac_dst or self.mac_dst + self.mac_src = mac_src or self.mac_src + self.ether_type = ether_type or self.ether_type + self.buf = packet or self.buf + if not self.ether_type then + return nil, "Unknown packet type." + end + self.frame_buf = self.mac_dst..self.mac_src..self.ether_type..self.buf +end +--- Parse an Ethernet frame. +-- @param frame string of the Ether frame. +-- @return mac_dst six-byte string of the destination MAC address. +-- @return mac_src six-byte string of the source MAC address. +-- @return packet string of the payload. +function Frame:ether_parse() + if not self.frame_buf or #self.frame_buf < 14 then -- too short + return false + end + self.mac_dst = string.sub(self.frame_buf, 1, 6) + self.mac_src = string.sub(self.frame_buf, 7, 12) +end + ---------------------------------------------------------------------------------------------------------------- -- Packet is a class Packet = {} @@ -136,11 +227,28 @@ Packet = {} -- @return A new Packet. function Packet:new(packet, packet_len, force_continue) local o = setmetatable({}, {__index = Packet}) + if not packet then + return o + end o.buf = packet o.packet_len = packet_len - if not o:ip_parse(force_continue) then + o.ip_v = bit.rshift(string.byte(o.buf), 4) + if o.ip_v == 4 and not o:ip_parse(force_continue) then + return nil + elseif o.ip_v == 6 and not o:ip6_parse(force_continue) then return nil end + + if o.ip_v == 6 then + while o:ipv6_is_extension_header() do + if not o:ipv6_ext_header_parse(force_continue) or o.ip6_data_offset >= o.packet_len then + stdnse.print_debug("Error while parsing IPv6 extension headers.") + return o + end + end + o.ip_p = o.ip6_nhdr + end + if o.ip_p == IPPROTO_TCP then if not o:tcp_parse(force_continue) then stdnse.print_debug("Error while parsing TCP packet\n") @@ -153,9 +261,129 @@ function Packet:new(packet, packet_len, force_continue) if not o:icmp_parse(force_continue) then stdnse.print_debug("Error while parsing ICMP packet\n") end + elseif o.ip_p == IPPROTO_ICMPV6 then + if not o:icmpv6_parse(force_continue) then + stdnse.print_debug("Error while parsing ICMPv6 packet\n") + end end return o end +--- Convert Version, Traffic Class and Flow Label to a 4-byte string. +-- @param ip6_tc Number stands for Traffic Class. +-- @param ip6_fl Number stands for Flow Label. +-- @return The first four-byte string of an IPv6 header. +function ipv6_hdr_pack_tc_fl(ip6_tc, ip6_fl) + local ver_tc_fl = bit.lshift(6, 28) + + bit.lshift(bit.band(ip6_tc, 0xFF), 20) + + bit.band(ip6_fl, 0xFFFFF) + return numtostr32(ver_tc_fl) +end +--- Build an IPv6 packet. +-- @param src 16-byte string of the source IPv6 address. +-- @param dsr 16-byte string of the destination IPv6 address. +-- @param nx_hdr integer that represents next header. +-- @param h_limit integer that represents hop limit. +-- @param t_class integer that represents traffic class. +-- @param f_label integer that represents flow label. +function Packet:build_ipv6_packet(src, dst, nx_hdr, payload, h_limit, t_class, f_label) + self.ether_type = ETHER_TYPE_IPV6 + self.ip_v = 6 + self.ip6_src = src or self.ip6_src + self.ip6_dst = dst or self.ip6_dst + self.ip6_nhdr = nx_hdr or self.ip6_nhdr + self.l4_packet = payload or self.l4_packet + self.ip6_tc = t_class or self.ip6_tc or 1 + self.ip6_fl = f_label or self.ip6_fl or 1 + self.ip6_hlimit = h_limit or self.ip6_hlimit or 255 + self.ip6_plen = #(self.exheader or "")+#(self.l4_packet or "") + self.buf = + ipv6_hdr_pack_tc_fl(self.ip6_tc, self.ip6_fl) .. + numtostr16(self.ip6_plen) .. --payload length + string.char(self.ip6_nxt_hdr) .. --next header + string.char(self.ip6_hlimit) .. --hop limit + self.ip6_src .. --Source + self.ip6_dst ..--dest + (self.exheader or "").. + (self.l4_packet or "") +end +--- Return true if and only if the next header is an known extension header. +-- @param nhdr Next header. +function Packet:ipv6_is_extension_header(nhdr) + self.ip6_nhdr = nhdr or self.ip6_nhdr + if self.ip6_nhdr == IPPROTO_HOPOPTS or + self.ip6_nhdr == IPPROTO_DSTOPTS or + self.ip6_nhdr == IPPROTO_ROUTING or + self.ip6_nhdr == IPPROTO_FRAGMENT then + return true + end + return nil +end +--- Count IPv6 checksum. +-- @return the checksum. +function Packet:count_ipv6_pseudoheader_cksum() + local pseudoheader = self.ip6_src .. self.ip6_dst .. numtostr16(#self.l4_packet) .. string.char(0x0,0x0,0x0) .. string.char(self.ip6_nxt_hdr) + local ck_content = pseudoheader .. self.l4_packet + return in_cksum(ck_content) +end +--- Set ICMPv6 checksum. +function Packet:set_icmp6_cksum(check_sum) + self.l4_packet = set_u16(self.l4_packet, 2, check_sum) +end +--- Build an ICMPv6 header. +-- @param icmpv6_type integer that represent ICMPv6 type. +-- @param icmpv6_code integer that represent ICMPv6 code. +-- @param icmpv6_payload string of the payload +-- @param ip6_src 16-byte string of the source IPv6 address. +-- @param ip6_dst 16-byte string of the destination IPv6 address. +function Packet:build_icmpv6_header(icmpv6_type, icmpv6_code, icmpv6_payload, ip6_src, ip6_dst) + self.ip6_nxt_hdr = IPPROTO_ICMPV6 + self.icmpv6_type = icmpv6_type or self.icmpv6_type + self.icmpv6_code = icmpv6_code or self.icmpv6_code + self.icmpv6_payload = icmpv6_payload or self.icmpv6_payload + self.ip6_src = ip6_src or self.ip6_src + self.ip6_dst = ip6_dst or self.ip6_dst + + self.l4_packet = + string.char(self.icmpv6_type,self.icmpv6_code) .. + string.char(0x00,0x00) .. --checksum + (self.icmpv6_payload or "") + local check_sum = self:count_ipv6_pseudoheader_cksum() + self:set_icmp6_cksum(check_sum) +end +--- Build an ICMPv6 Echo Request frame. +-- @param mac_src six-byte string of source MAC address. +-- @param mac_dst sis-byte string of destination MAC address. +-- @param ip6_src 16-byte string of source IPv6 address. +-- @param ip6_dst 16-byte string of destinatiion IPv6 address. +-- @param id integer that represents Echo ID. +-- @param sequence integer that represents Echo sequence. +-- @param data string of Echo data. +-- @param tc integer that represents traffic class of IPv6 packet. +-- @param fl integer that represents flow label of IPv6 packet. +-- @param hop-limit integer that represents hop limit of IPv6 packet. +function Packet:build_icmpv6_echo_request(id, sequence, data, mac_src, mac_dst, ip6_src, ip6_dst, tc, fl, hop_limit) + self.mac_src = mac_src or self.mac_src + self.mac_dst = mac_dst or self.mac_dst + + self.ip6_src = ip6_src or self.ip6_src + self.ip6_dst = ip6_dst or self.ip6_dst + self.traffic_class = tc or 1 + self.flow_label = fl or 1 + self.ip6_hlimit = hop_limit or 255 + + self.icmpv6_type = ICMP6_ECHO_REQUEST + self.icmpv6_code = 0 + + self.echo_id = id or self.echo_id or 0xdead + self.echo_seq = sequence or self.echo_seq or 0xbeef + self.echo_data = data or self.echo_data or "" + + self.icmpv6_payload = numtostr16(self.echo_id) .. numtostr16(self.echo_seq) .. self.echo_data +end +--- Set an ICMPv6 option message. +function Packet:set_icmpv6_option(opt_type,msg) + return string.char(opt_type, (#msg+2)/8) .. msg +end -- Helpers @@ -171,6 +399,86 @@ function iptobin(str) end return ret end +--- Convert an IPv6 address string (like "fe80:21::1") to a raw +-- string 16 bytes long (big-endian). +-- @param str IPv6 address string. +-- @return 16-byte string. +function ip6tobin(str) + if not str then + return nil + end + -- Handle IPv4-compatible IPv6 address. + local ipv6_size = 8 -- An IPv6 address is 8*16bits long. But for IPv4-compatible address, the IPv6-style part is 6*16bits long. + local ip4_bin = "" + local dot_count = stdnse.strsplit("%.", str) + if #dot_count == 4 then -- It might be IPv4-compatible IPv6 address. + local ip64 = stdnse.strsplit(":", str) + local ip4_str = ip64[#ip64] -- Get the embeded IPv4 address string. + ip4_bin = iptobin(ip4_str) + if not ip4_bin then + return nil + end + ipv6_size = 6 + str = string.sub(str, 1, -#ip4_str-1) + elseif #dot_count ~= 1 then + return nil + end + -- Handle the left IPv6-style part. + local sides = stdnse.strsplit("::", str) + if #sides > 2 then + return nil + end + local head = stdnse.strsplit(":", sides[1]) + if #sides > 1 then + local tail = stdnse.strsplit(":", sides[2]) + if tail[#tail] == "" then + table.remove(tail, #tail) + end + local missing = ipv6_size - #head - #tail + while missing > 0 do + table.insert(head, "0") + missing = missing - 1 + end + for _, e in ipairs(tail) do + table.insert(head, e) + end + end + if #head ~= ipv6_size then + return nil + end + -- Transfer the 16-bit units to raw string. + local unit16 + local addr_hex = "" + for _, unit16 in ipairs(head) do + local h8 = string.sub(unit16,-4,-3) + local l8 = string.sub(unit16,-2,-1) + local unit8 + for _,unit8 in pairs({h8,l8}) do + if (unit8 == "") then + addr_hex = addr_hex .. string.char(0x00) + else + addr_hex = addr_hex .. string.char("0x"..unit8) + end + end + end + + return addr_hex .. ip4_bin +end +--- Convert a MAC address string (like "00:23:ae:5d:3b:10") to +-- a raw six-byte long. +-- @param str MAC address string. +-- @return Six-byte string. +function mactobin(str) + if not str then + return nil, "MAC was not specified." + end + local unit8 + local addr_hex = "" + for unit8 in string.gmatch(str,"%x+") do + addr_hex = addr_hex .. string.char("0x"..unit8) + end + return addr_hex +end --- Convert a four-byte raw string to a dotted-quad IP address string. -- @param raw_ip_addr Four-byte string. -- @return IP address string. @@ -180,6 +488,27 @@ function toip(raw_ip_addr) end return string.format("%i.%i.%i.%i", string.byte(raw_ip_addr,1,4)) end +--- Convert a 16-byte raw string to an IPv6 address string. +-- @param raw_ipv6_addr 16-byte string. +-- @return IPv6 address string. +function toipv6(raw_ipv6_addr) + if not raw_ipv6_addr then + return nil, "IPv6 address was not specified." + end + return string.format("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + string.byte(raw_ipv6_addr,1,16)) +end +--- Generate the link-local IPv6 address from the MAC address. +-- @param mac MAC address string. +-- @return Link-local IPv6 address string. +function mac_to_lladdr(mac) + if not mac then + return nil, "MAC was not specified." + end + local interfier = string.char(bit.bor(string.byte(mac,1),0x02))..string.sub(mac,2,3)..string.char(0xff,0xfe)..string.sub(mac,4,6) + local ll_prefix = ip6tobin("fe80::") + return string.sub(ll_prefix,1,8)..interfier +end --- Get an 8-bit integer at a 0-based byte offset in the packet. -- @param index Offset. -- @return An 8-bit integer. @@ -269,6 +598,38 @@ function Packet:ip_parse(force_continue) self.ip_data_offset = self.ip_offset + self.ip_hl*4 return true end +--- Parse an IPv6 packet header. +-- @param force_continue Ignored. +-- @return Whether the parsing succeeded. +function Packet:ip6_parse(force_continue) + self.ip6_offset = 0 + if #self.buf < 40 then -- too short + return false + end + self.ip_v = bit.rshift(bit.band(self:u8(self.ip6_offset + 0), 0xF0), 4) + if self.ip_v ~= 6 then -- not ipv6 + return false + end + self.ip6 = true + self.ip6_tc = bit.rshift(bit.band(self:u16(self.ip6_offset + 0), 0x0FF0), 4) + self.ip6_fl = bit.band(self:u8(self.ip6_offset + 1), 0x0F)*65536 + self:u16(self.ip6_offset + 2) + self.ip6_plen = self:u16(self.ip6_offset + 4) + self.ip6_nhdr = self:u8(self.ip6_offset + 6) + self.ip6_hlimt = self:u8(self.ip6_offset + 7) + self.ip6_src = self:raw(self.ip6_offset + 8, 16) + self.ip6_dst = self:raw(self.ip6_offset + 24, 16) + self.ip6_data_offset = 40 + return true +end +--- Pare an IPv6 extension header. Just jump over it at the moment. +-- @param force_continue Ignored. +-- @return Whether the parsing succeeded. +function Packet:ipv6_ext_header_parse(force_continue) + local ext_hdr_len = self.u8(self.ip6_data_offset + 1) + ext_hdr_len = ext_hdr_len*8 + 8 + self.ip6_data_offset = self.ip6_data_offset + ext_hdr_len + self.ip6_nhdr = self.u8(self.ip6_data_offset) +end --- Set the header length field. function Packet:ip_set_hl(len) self:set_u8(self.ip_offset + 0, bit.bor(bit.lshift(self.ip_v, 4), bit.band(len, 0x0F))) @@ -420,6 +781,25 @@ function Packet:icmp_tostring() return self:ip_tostring() .. " ICMP(" .. self.icmp_payload:tostring() .. ")" end +---------------------------------------------------------------------------------------------------------------- +--- Parse an ICMPv6 packet header. +-- @param force_continue Ignored. +-- @return Whether the parsing succeeded. +function Packet:icmpv6_parse(force_continue) + self.icmpv6_offset = self.ip6_data_offset + if #self.buf < self.icmpv6_offset + 8 then -- let's say 8 bytes minimum + return false + end + self.icmpv6 = true + self.icmpv6_type = self:u8(self.icmpv6_offset + 0) + self.icmpv6_code = self:u8(self.icmpv6_offset + 1) + + if self.icmpv6_type == ND_NEIGHBOR_SOLICIT then + self.ns_target = self:raw(self.icmpv6_offset + 8, 16) + end + return true +end + ---------------------------------------------------------------------------------------------------------------- -- Parse a TCP packet header. -- @param force_continue Whether a short packet causes parsing to fail.