Giter Site home page Giter Site logo

packetgen's Introduction

Gem Version Action status

PacketGen

PacketGen provides simple ways to generate, send and capture network packets.

Installation

PacketGen depends on PcapRub, which needs pcap development files to install. On Debian, you have to do:

$ sudo apt install libpcap-dev

Installation using RubyGems is then easy:

$ gem install packetgen

Or add it to a Gemfile:

gem 'packetgen'

Usage

Easily create packets

PacketGen.gen('IP')             # generate a IP packet object
PacketGen.gen('TCP')            # generate a TCP over IP packet object
PacketGen.gen('IP').add('TCP')  # the same
PacketGen.gen('Eth')            # generate a Ethernet packet object
PacketGen.gen('IP').add('IP')   # generate a IP-in-IP tunnel packet object

# Generate a IP packet object, specifying addresses
PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.1.2')

# get binary packet
PacketGen.gen('IP').to_s

Send packets on wire

# send Ethernet packet
PacketGen.gen('Eth', src: '00:00:00:00:00:01', dst: '00:00:00:00:00:02').to_w
# send IP packet
PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.1.2').to_w
# send forged IP packet over Ethernet
PacketGen.gen('Eth', src: '00:00:00:00:00:01', dst: '00:00:00:00:00:02').add('IP').to_w('eth1')
# send a IEEE 802.11 frame
PacketGen.gen('RadioTap').
          add('Dot11::Management', mac1: client, mac2: bssid, mac3: bssid).
          add('Dot11::DeAuth', reason: 7).
          to_w('wlan0')

Parse packets from binary data

packet = PacketGen.parse(binary_data)

Capture packets from wire

# Capture packets from first network interface, action from a block
PacketGen.capture do |packet|
  do_stuffs_with_packet
end

# Capture some packets, and act on them afterward
packets = PacketGen.capture(iface: 'eth0', max: 10)   # return when 10 packets were captured

# Use filters
packets = PacketGen.capture(iface: 'eth0', filter: 'ip src 1.1.1.2', max: 1)

Easily manipulate packets

# access header fields
pkt = PacketGen.gen('IP').add('TCP')
pkt.ip.src = '192.168.1.1'
pkt.ip(src: '192.168.1.1', ttl: 4)
pkt.tcp.dport = 80

# access header fields when multiple header of one kind exist
pkt = PacketGen.gen('IP').add('IP')
pkt.ip.src = '192.168.1.1'  # set outer src field
pkt.ip(2).src = '10.0.0.1'  # set inner src field

# test packet types
pkt = PacketGen.gen('IP').add('TCP')
pkt.is? 'TCP'   # => true
pkt.is? 'IP'    # => true
pkt.is? 'UDP'   # => false

# encapulsate/decapsulate packets
pkt2 = PacketGen.gen('IP')
pkt2.encapsulate pkt                   # pkt2 is now a IP/IP/TCP packet
pkt2.decapsulate(pkt2.ip)              # pkt2 is now inner IP/TCP packet

Read/write PcapNG files

# read a PcapNG file, containing multiple packets
packets = PacketGen.read('file.pcapng')
packets.first.udp.sport = 65535
# write only one packet to a PcapNG file
pkt.write('one_packet.pcapng')
# write multiple packets to a PcapNG file
PacketGen.write('more_packets.pcapng', packets)

Add custom header/protocol

Since v1.1.0, PacketGen permits adding your own header classes. First, define the new header class. For example:

module MyModule
 class MyHeader < PacketGen::Header::Base
   define_field :field1, PacketGen::Types::Int32
   define_field :field2, PacketGen::Types::Int32
 end
end

Then, class must be declared to PacketGen:

PacketGen::Header.add_class MyModule::MyHeader

Finally, bindings must be declared:

# bind MyHeader as IP protocol number 254 (needed by Packet#parse and Packet#add)
PacketGen::Header::IP.bind_header MyModule::MyHeader, protocol: 254

And use it:

pkt = Packet.gen('IP').add('MyHeader', field1: 0x12345678)
pkt.myheader.field2.read 0x01

Interactive console

PacketGen provides an interactive console: pgconsole.

In this console, context includes PacketGen module to give direct access to PacketGen classes. A special config object gives local network configuration:

$ pgconsole
pg(main)> config
=> #<PacketGen::Config:0x00559f27d2afe8
 @hwaddr="75:74:73:72:71:70",
 @iface="eth0",
 @ipaddr="192.168.0.2">
pg(main)> packets = capture(max: 5)
pg(main)> exit

If pry gem is installed, it is used as backend for pgconsole, else IRB is used.

Plugins

PacketGen provides a plugin system (see wiki).

Available plugins (available as gem) are:

See also

Wiki: https://github.com/sdaubert/packetgen/wiki

API documentation: http://www.rubydoc.info/gems/packetgen

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/sdaubert/packetgen.

License

MIT License (see LICENSE)

Other sources

All original code maintains its copyright from its original authors and licensing.

This is mainly for PcapNG (originally copied from PacketFu, but i am the original author.

packetgen's People

Contributors

optix2000 avatar picatz avatar sdaubert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

packetgen's Issues

http parser can't handle invalid byte sequences

For whatever reason, I ended up writing my own HTTP parser. So, of course, there would be bugs. 😂

I need to submit a fix to handle these kinds of cases. It could look something like this:

tcp_pkt_body =  "GET /wiggle HTTP/1.1\r\nHost: ocsp.comodoca.com\r\nConnection: close\r\nUser-Agent: wiggle\r\n\r\n\r\xD1"

# if I do this
/(CONNECT|DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT)/ =~ tcp_pkt_body
# raises error: ArgumentError: invalid byte sequence in UTF-8

# so I need to cleanse it
tcp_pkt_body = tcp_pkt_body.chars.select(&:valid_encoding?).join
# => "GET /wiggle HTTP/1.1\r\nHost: ocsp.comodoca.com\r\nConnection: close\r\nUser-Agent: wiggle\r\n\r\n\r"

# and now I can regex it to get a match
/(CONNECT|DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT)/ =~ tcp_pkt_body
# => 0

Unless @sdaubert has a better way of handling this invalid encoding problem.

Read .pcap formatted files

Sometimes I want to use PacketGen to work with packet captures that may not be in .pcapng format. For example, if I dumped stuff with tcpdump and then wanted to work with that capture with packetgen later, it's not exactly straight forward.

Under the hood, I think we could pretty easily have the .read() method work with both .pcap and .pcapng files by using PCAPRUB's .open_offline() method.

No Worky

pcap = PacketGen.read('example.pcap')

How open_offline works

packets = PCAPRUB::Pcap.open_offline('example.pcap')

How I've Been Using It

Here's basically how I've been getting around not being able to just use .read(), because I need to:

parsed_packets = []
PCAPRUB::Pcap.open_offline('example.pcap').each_packet do |packet|
  parsed_packets << PacketGen.parse(packet.to_s)
end
parsed_packets

Proposal

# Read packets from +filename+.
#
# @see {PcapNG::File} for more on reading pcapng files.
# @param [String] filename PcapNG or Pcap file
# @return [Array<Packet>]
def self.read(filename)
  extension = File.extname(filename).downcase
  if extension == ".pcap"
    # read pcap via PCAPRUB::Pcap.open_offline in some fashion
    # honestly I haven't figured out the best way yet
    # but, ya' know
  elsif extension == ".pcapng"
    PcapNG::File.new.read_packets filename
  else
    # error, extension be a lie OR:
    # check for them, automagically read both even if the filename was kittens
  end
end

Thoughts on this?

add send broadcast support

when I set the dst to "255.255.255.255",
have the error:

irb(main):012:0> PacketGen.gen('IP', src: '192.168.0.6', dst: '255.255.255.255', ttl: 1).to_w

Errno::EACCES: Permission denied - send(2)
	from /var/lib/gems/2.3.0/gems/packetgen-1.4.3/lib/packetgen/header/ip.rb:203:in `send'
	from /var/lib/gems/2.3.0/gems/packetgen-1.4.3/lib/packetgen/header/ip.rb:203:in `to_w'
	from /var/lib/gems/2.3.0/gems/packetgen-1.4.3/lib/packetgen/packet.rb:210:in `to_w'
	from (irb):12
	from /usr/bin/irb:11:in `<main>'

Header classes: use RFC names for fields

Rename all fields from header classes which do not use RFC name.

  • Eth#proto -> Eth#ethertype
  • ARP#hw_type -> ARP#hrd (alias: #htype)
  • ARP#proto -> ARP#pro (alias: #ptype)
  • ARP#hw_len -> ARP#hln (alias: #hlen)
  • ARP#proto_len -> ARP#pln (alias: #plen)
  • ARP#opcode -> ARP#op (alias: #opcode)
  • ARP#src_mac -> ARP#sha (alias: #src_mac)
  • ARP#src_ip -> ARP#spa (alias: #src_ip)
  • ARP#dst_mac -> ARP#tha (alias: #dst_mac)
  • ARP#dst_ip -> ARP#tpa (alias: #dst_ip)
  • IP#proto -> IP#protocol

Add an alias to Header.all method

Hello there!
It would be more convenient to add an alias to method Header.all to be Header.list_headers, or Header.list

Also, the comment/document would be more descriptive/obvious if it was

   # List all available headers
    # Get known header classes
    # @return [Array<Class>]
    def self.all
      return @header_classes if @header_classes
      @header_classes = @added_header_classes.values
    end

You're Awesome

You're Awesome

awesome

I know this is a weird place I guess, but thank you for working on this project.

Capture Timeout

The Issue

For the .start method when using the Capture class, a @pcap instance variable is created using PCAPRUB's open_live method. The very last argument for the .open_live method when using the .start method is 1. However, I'd assume this should be the @timeout instance variable that is set in the set_options method. Within the Capture class's initialize method a default for the @timeout variable could be set to 1 when users don't provide anything, while also allowing them to actually change it for their capture needs.

Hope that makes sense. I'm going to make a PR to fix this. 👍

PacketGen.parse crashes

PacketGen.parse crashes with message

ArgumentError: wrong number of arguments (given 2, expected 1)

Build an SNMP

Hello there, if you have seen this issue. I tried to use packetgen to do so but I could do it using the current documentation

Scapy version

pkt = IP(src="192.168.30.100", dst="192.168.30.199")/UDP(sport=161)/SNMP(community="private",PDU=SNMPset(varbindlist=SNMPvarbind(oid=ASN1_OID("1.3.6.1.4.1.9.2.1.55.192.168.30.10"),value="pwnd-router.config")]))

send(pkt)

tried

pkt1 = PacketGen.gen('IP', src: '192.168.102.205', dst: '127.0.0.1').
                 add('UDP', dport: 161, sport: 161).
                 add('SNMP', community: 'private', pdu: "DID KNOW HOW TO ADD PDU DETAILS THIS WAY" )

and

snmp = PacketGen::Header::SNMP.new
snmp.community = 'private'
snmp.data.chosen = 3 # SetRequest
snmp.pdu

I believe my issue that I couldn't understand how to assign the PDU details

  • I guess SNMPset == SetRequest from scapy
  • how to set varbindlist details
  • SNMPvargind details
    as below from scapy
PDU=SNMPset(varbindlist=SNMPvarbind(oid=ASN1_OID("1.3.6.1.4.1.9.2.1.55.192.168.30.10"),value="pwnd-router.config")])

Killing capture with nmap.

While running this script:

require 'packetgen'

ipv4 = 0
ipv6 = 0
other = 0

PacketGen.capture('en0', promisc: true) do |packet|
  if packet.is? 'IP'
    ipv4 += 1
  elsif packet.is? 'IPv6'
    ipv6 += 1
  else
    other += 1
  end    
  print "IPv4: #{ipv4} , IPv6: #{ipv6} , OTHER: #{other} \r"
end

If I run nmap in another shell:

$ nmap 192.168.0.0/24

I will get ( after a couple of seconds ):

/usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.0/lib/packetgen/packet.rb:244:in `parse': cannot identify first header in string (PacketGen::ParseError)
	from /usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.0/lib/packetgen/packet.rb:68:in `parse'
	from /usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.0/lib/packetgen/capture.rb:54:in `block (2 levels) in start'
	from /usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.0/lib/packetgen/capture.rb:51:in `each'
	from /usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.0/lib/packetgen/capture.rb:51:in `block in start'

So, this seems to be tied to the fact that there are invalid packets being sent. Though, I think for the capture, should there be a graceful failure for these things, or an option to fail a little more gracefully and allow capturing to continue even if there's a failure? Perhaps classify the packet as an invalid packet type?

cannot send a Dot11 packet

From #55:

pkt = PacketGen.gen('Dot11::Management', mac1: client, mac2: bssid, mac3: bssid)
pkt.add('Dot11::DeAuth', reason: 7)
pkt.calc

pkt.to_w('wlan0')

Nothing is sent

SMBv1/2/3

This great gem messes SMB protocol

Add a mechanism to declare bit fields in header classes

Idea:

  • struct has a attribute of type Int8/16/32
  • use a class method to generate bit fields on the attribute

By example: Header::IP has 2 fields of 4 bits each (version and ihl)
Header::IP will de declared as:

class IP < Struct.new(:u8, :tos, :length, :id, :frag, :ttl, :proto,:sum, :src, :dst, :body) 
  define_bit_fields_on :u8, :version, 4, :ihl, 4
  ...
end

This will create getter and setter methods to access version and ihl in u8 attribute.

This mechanism will be used to declare:

  • 4-bit fields and flags in IP header,
  • first 32-bit word in IPv6 header (version (4 bits), traffic class (8 bits, not on byte boundary), flow label (20 bits)),
  • TCP flags.

pgconsole on macos/osx error

$ pgconsole 
/usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.1/lib/packetgen/config.rb:34:in `initialize': uninitialized constant Socket::AF_PACKET (NameError)
Did you mean?  Socket::AF_CNT
	from /usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.1/bin/pgconsole:26:in `new'
	from /usr/local/lib/ruby/gems/2.4.0/gems/packetgen-1.4.1/bin/pgconsole:26:in `<top (required)>'
	from /usr/local/bin/pgconsole:22:in `load'
	from /usr/local/bin/pgconsole:22:in `<main>'
[ ╯ '□']╯︵┻━┻)

The config for pgconsole doesn't seem to be compatible with osx/macos.

Side Note

You're the best. There's no serious need for me to have this right now, but, I figured I'd let ya' know.

Maybe I can figure it out and make a PR.

Capture Syntax in README

Hey Sylvain, hope you're doing well!

I felt the need to mess around with packetgen some more this weekend. I really like this project and I use it quite often!

As a side note: I'm sort of curious to try and translate the programming logic from this gem to the Crystal programming language. If you haven't heard of that project, I think you should check it out!

README

Anyway, I decided to go through the README examples for capturing packets and noticed this:

Old Capture Syntax

This example is found in the README:

packets = PacketGen.capture('eth0', max: 10)
# => ArgumentError: wrong number of arguments (given 2, expected 0..1)
# from /Users/kentgruber/.rvm/gems/ruby-2.4.1/gems/packetgen-1.4.3/lib/packetgen.rb:45:in `capture'

New Capture Syntax

To get it to work, need to use the new capture syntax:

packets = PacketGen.capture(iface: 'eth0', max: 10)

Let me know if you can re-produce this problem / let me know that I'm using the API wrong. It should be a pretty easy fix to update the README to include the newer syntax if that is the main problem. I do suspect some sort of error checking could be improved, just not entirely sure where yet.

default capture device doesn't exist on macOS

👋 Hello @sdaubert, I really hope you're doing well!

This morning I decided to run rspec on this repository and came across an error on macOS:

../packetgen/lib/packetgen/capture.rb:65:in `open_live': lo: No such device exists (BIOCSETIF failed: Device not configured) (RuntimeError)

I believe this to be attributed to the default capture interface lo. On macOS, perhaps at the very least on my laptop, the loopback interface is named lo0.

Interfacez

I wrote a gem a little while back called interfacez. This gem uses the Ruby standard library under the hood and provides a clean semantics for this behavior I think we're looking for. Moreover, I really think it could be a replacement for NetworkInterface ( written by Rapid7, with lots of slow C bindings ) and event Pcap.lookupdev.

Example Usage

require 'interfacez'

# check if loopback exists
Interfacez.loopback?
# => true

# get loopback
Interfacez.loopback
# => "lo0"

# get first network interface to capture on ( lookupdev )
Interfacez.default
# => "en0"

Gem install runtime error

user$ gem install packetgen
Building native extensions.  This could take a while...
ERROR:  Error installing packetgen:
        ERROR: Failed to build gem native extension.

    current directory: /Users/user/.rvm/gems/ruby-2.4.2/gems/pcaprub-0.12.4/ext/pcaprub_c
/Users/yser/.rvm/rubies/ruby-2.4.2/bin/ruby -r ./siteconf20171014-32877-1wibpka.rb extconf.rb

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: int main(int argc, char **argv)
4: {
5:   return 0;
6: }
/* end */

My gem & ruby is up to date and I can't find any solution to this error.

make #recalc within #to_w

Hello there, the title says it all
Why do we need to call recalc manually?
we can make it called by default or add it as an option to to_w in case someone want to send it without calculation(no idea when someone needs that) to be something like

pkt.to_w(iface, recalc: false)

Also, I hope we can add number of packets to be sent

pkt.to_w(iface, interval: 1000)

Make specifying an interface optional.

Thoughts

When working with PacketFu, I'm able to start capturing packets without needing to specify the interface to use because that is optionally figured out for me if I don't pass in that argument.

For PacketGen, I'm forced to specify the interface to listen on as the first argument. I think this should be moved to the second, optional hash that can be passed in optionally. I think we should put that interface specification as an option and make capturing that much simpler.

Results

Turn This

require 'packetgen'

PacketGen.capture('en0') do |packet|
  #do_stuffs_with_packet
end

Into This

require 'packetgen'

PacketGen.capture(iface: 'en0') do |packet|
  #do_stuffs_with_packet
end

And this:

require 'packetgen'

PacketGen.capture do |packet|
  #do_stuffs_with_packet
end

And this:

require 'packetgen'

PacketGen.capture(iface: 'en0', max: 2) do |packet|
  #do_stuffs_with_packet
end

How?

We ( basically ) just need to use Pcaprub's interface lookup to achieve this by changing the set_options method for PacketGen::Capture:

def set_options(options)
      @max = options[:max] if options[:max]
      @filter = options[:filter] if options[:filter]
      @timeout = options[:timeout] if options[:timeout]
      if options[:promisc]
        @promisc = options[:promisc]
      else
        @promisc ||= false
      end
      if options[:snaplen]
        @snaplen = options[:snaplen]
      else
        @snaplen ||= DEFAULT_SNAPLEN
      end
      if options[:parse].nil?
        @parse = true if @parse.nil?
      else
        @parse = options[:parse]
      end
      if options[:iface]
        @iface = options[:iface] 
      else
        @iface = Pcap.lookupdev
      end
    end

My Implementation

I've implemented this to work; but I'm having problems with some of the specs where I'm getting errors like these ( which probably has nothing to do with interface specification, I'd think, I dunno ):
screen shot

I can make the pull request, otherwise if you want to implement it yourself, it's not too bad -- but requires some changes to the tests obviously to reflect the interface specification as a part of the main hash.

shrug

Sending Deauth packet

Hello guys
I see you have done a tremendous work in packetgen however I believe that more examples are needed for each header to use.

I was looking for sending Dot11 DeAuth packet and I couldn't make it using the available docs.

My suggestion is to make one or more practical example for each header as you've done with manipulate packets part.

Thanks again for the great effort

Header::Base.bind_header is messy

Current beahviour is merely broken: :op keyword change how to handle multiple fields. But adding others bindings through others calls to .bind_header breaks that as these calls use first defined :op, and not operator define by these calls.

Create an interactive console

It would be great to add a binary file that works as an interactive console, something like Scapy console with the elegance of msfconsole way.

What do you think?

Make Gitbook based Wiki

Hello @sdaubert

As PacketGet is getting more awesome, the wiki is not synced with the lib.

You can create a repository like "packetgen_docs" and connect Gitbook to it so people either can PR to the wiki on github (it will be synced with gitbook once merged) or, you can add people you trust as writers to the gitbook space or both

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.