librex 0.0.42 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. data/README.markdown +1 -1
  2. data/lib/rex/compat.rb +10 -0
  3. data/lib/rex/post/meterpreter/channels/pools/file.rb +1 -1
  4. data/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb +20 -18
  5. data/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb +11 -22
  6. data/lib/rex/post/meterpreter/extensions/stdapi/fs/file_stat.rb +2 -1
  7. data/lib/rex/post/meterpreter/extensions/stdapi/railgun.rb.ts.rb +4 -0
  8. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb +27 -0
  9. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb.ut.rb +7 -0
  10. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_advapi32.rb +498 -242
  11. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_iphlpapi.rb +18 -18
  12. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb +695 -694
  13. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb +6 -5
  14. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_ntdll.rb +24 -24
  15. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_shell32.rb +5 -4
  16. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_user32.rb +551 -551
  17. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_ws2_32.rb +93 -93
  18. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +56 -42
  19. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb.ut.rb +4 -4
  20. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll_helper.rb.ut.rb +5 -5
  21. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll_wrapper.rb +26 -0
  22. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll_wrapper.rb.ut.rb +63 -0
  23. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb +4 -4
  24. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +151 -96
  25. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb.ut.rb +80 -5
  26. data/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb +3 -3
  27. data/lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb +11 -11
  28. data/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb +3 -3
  29. data/lib/rex/post/meterpreter/packet.rb +12 -11
  30. data/lib/rex/proto/dhcp/server.rb +36 -42
  31. data/lib/rex/socket/range_walker.rb +1 -1
  32. data/lib/rex/text.rb +18 -1
  33. data/lib/rex/ui/text/table.rb +1 -1
  34. metadata +5 -3
@@ -15,8 +15,48 @@ module Stdapi
15
15
  module Railgun
16
16
  class Railgun::UnitTest < Test::Unit::TestCase
17
17
 
18
+ # DLLs we know should be available at the time of this writing,
19
+ # and DLLs that because of changes since then should be available
20
+ STOCK_DLLS =
21
+ ['kernel32', 'ntdll', 'user32', 'ws2_32',
22
+ 'iphlpapi', 'netapi32', 'advapi32', 'shell32'] | Railgun::BUILTIN_DLLS
23
+
18
24
  include MockMagic
19
25
 
26
+ def test_known_dll_names
27
+ railgun = Railgun.new(make_mock_client())
28
+
29
+ dll_names = railgun.known_dll_names
30
+
31
+ assert_equal(dll_names.length, dll_names.uniq.length,
32
+ "known_dll_names should not have duplicates")
33
+
34
+ STOCK_DLLS.each do |name|
35
+ assert(dll_names.include?(name),
36
+ "known_dll_names should include #{name}")
37
+ end
38
+ end
39
+ #
40
+ # TODO:
41
+ # def test_multi
42
+ # mock_function_descriptions.each do |func|
43
+ # railgun = Railgun.new(make_mock_client(func[:platform]))
44
+ #
45
+ # functions = [
46
+ # [func[:dll_name], func[:name], func[:ruby_args]]
47
+ # ]
48
+ #
49
+ # results = railgun.multi(functions)
50
+ # end
51
+ # end
52
+ #
53
+ def test_const
54
+ railgun = Railgun.new(make_mock_client())
55
+
56
+ assert_equal(0, railgun.const('SUCCESS'),
57
+ "const should look up constants like SUCCESS")
58
+ end
59
+
20
60
  def test_add_dll
21
61
  railgun = Railgun.new(make_mock_client())
22
62
 
@@ -33,11 +73,46 @@ class Railgun::UnitTest < Test::Unit::TestCase
33
73
  assert_equal(actual_dll.dll_path, target_windows_name,
34
74
  "add_dll should set a dll path when specified")
35
75
 
36
- #
37
- # wrapper = railgun.send(target_dll_name.to_sym)
38
- #
39
- # assert_same(wrapper._dll, actual_dll,
40
- # "railgun instance responds with dll wrapper as expected")
76
+ wrapper = railgun.send(target_dll_name.to_sym)
77
+
78
+ assert_same(wrapper._dll, actual_dll,
79
+ "railgun instance responds with dll wrapper of requested dll")
80
+ end
81
+
82
+ def test_method_missing
83
+ railgun = Railgun.new(make_mock_client())
84
+
85
+ STOCK_DLLS.each do |dll_name|
86
+ assert_nothing_raised do
87
+ railgun.send(dll_name.to_sym)
88
+ end
89
+ end
90
+ end
91
+
92
+ def test_get_dll
93
+ railgun = Railgun.new(make_mock_client())
94
+
95
+ STOCK_DLLS.each do |dll_name|
96
+ dll = railgun.get_dll(dll_name)
97
+
98
+ # We want to ensure autoloading is working
99
+ assert(dll.instance_of?(DLL),
100
+ "get_dll should be able to return a value for dll #{dll_name}")
101
+
102
+ assert(dll.frozen?,
103
+ "Stock DLLs loaded lazily in get_dll should be frozen")
104
+
105
+ # adding a function should create a local unfrozen instance
106
+ railgun.add_function(dll_name, '__lolz', 'VOID', [])
107
+
108
+ unfrozen_dll = railgun.get_dll(dll_name)
109
+
110
+ assert_not_same(dll, unfrozen_dll,
111
+ "add_function should create a local unfrozen instance that get_dll can then access")
112
+
113
+ assert(!unfrozen_dll.frozen?,
114
+ "add_function should create a local unfrozen instance that get_dll can then access")
115
+ end
41
116
  end
42
117
 
43
118
  def test_add_function
@@ -30,7 +30,7 @@ class Config
30
30
  def getuid
31
31
  request = Packet.create_request('stdapi_sys_config_getuid')
32
32
  response = client.send_request(request)
33
- return response.get_tlv_value(TLV_TYPE_USER_NAME)
33
+ return Rex::Text.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) )
34
34
  end
35
35
 
36
36
  #
@@ -62,7 +62,7 @@ class Config
62
62
  req = Packet.create_request('stdapi_sys_config_steal_token')
63
63
  req.add_tlv(TLV_TYPE_PID, pid.to_i)
64
64
  res = client.send_request(req)
65
- return res.get_tlv_value(TLV_TYPE_USER_NAME)
65
+ return Rex::Text.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) )
66
66
  end
67
67
 
68
68
  #
@@ -71,7 +71,7 @@ class Config
71
71
  def drop_token
72
72
  req = Packet.create_request('stdapi_sys_config_drop_token')
73
73
  res = client.send_request(req)
74
- return res.get_tlv_value(TLV_TYPE_USER_NAME)
74
+ return Rex::Text.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) )
75
75
  end
76
76
 
77
77
  #
@@ -151,7 +151,7 @@ class Process < Rex::Post::Process
151
151
  end
152
152
  end
153
153
 
154
- request.add_tlv(TLV_TYPE_PROCESS_PATH, path);
154
+ request.add_tlv(TLV_TYPE_PROCESS_PATH, Rex::Text.unicode_filter_decode( path ));
155
155
 
156
156
  # If process arguments were supplied
157
157
  if (arguments != nil)
@@ -177,7 +177,7 @@ class Process < Rex::Post::Process
177
177
  # Return a process instance
178
178
  return self.new(pid, handle, channel)
179
179
  end
180
-
180
+
181
181
  #
182
182
  # Kills one or more processes.
183
183
  #
@@ -223,7 +223,7 @@ class Process < Rex::Post::Process
223
223
 
224
224
  response.each(TLV_TYPE_PROCESS_GROUP) { |p|
225
225
  arch = ""
226
-
226
+
227
227
  pa = p.get_tlv_value( TLV_TYPE_PROCESS_ARCH )
228
228
  if( pa != nil )
229
229
  if pa == 1 # PROCESS_ARCH_X86
@@ -232,15 +232,15 @@ class Process < Rex::Post::Process
232
232
  arch = ARCH_X86_64
233
233
  end
234
234
  end
235
-
235
+
236
236
  processes <<
237
237
  {
238
238
  'pid' => p.get_tlv_value(TLV_TYPE_PID),
239
239
  'parentid' => p.get_tlv_value(TLV_TYPE_PARENT_PID),
240
- 'name' => p.get_tlv_value(TLV_TYPE_PROCESS_NAME),
241
- 'path' => p.get_tlv_value(TLV_TYPE_PROCESS_PATH),
240
+ 'name' => Rex::Text.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),
241
+ 'path' => Rex::Text.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),
242
242
  'session' => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),
243
- 'user' => p.get_tlv_value(TLV_TYPE_USER_NAME),
243
+ 'user' => Rex::Text.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),
244
244
  'arch' => arch
245
245
  }
246
246
  }
@@ -291,7 +291,7 @@ class Process < Rex::Post::Process
291
291
  def self.finalize(client,handle)
292
292
  proc { self.close(client,handle) }
293
293
  end
294
-
294
+
295
295
  #
296
296
  # Returns the executable name of the process.
297
297
  #
@@ -316,7 +316,7 @@ class Process < Rex::Post::Process
316
316
  handle = nil;
317
317
  return true
318
318
  end
319
-
319
+
320
320
  #
321
321
  # Instance method
322
322
  #
@@ -358,8 +358,8 @@ protected
358
358
  response = client.send_request(request)
359
359
 
360
360
  # Populate the hash
361
- info['name'] = response.get_tlv_value(TLV_TYPE_PROCESS_NAME)
362
- info['path'] = response.get_tlv_value(TLV_TYPE_PROCESS_PATH)
361
+ info['name'] = Rex::Text.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_NAME) )
362
+ info['path'] = Rex::Text.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_PATH) )
363
363
 
364
364
  return info
365
365
  end
@@ -44,8 +44,8 @@ class Registry
44
44
  request = Packet.create_request('stdapi_registry_load_key')
45
45
  request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
46
46
  request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
47
- request.add_tlv(TLV_TYPE_FILE_PATH,hive_file)
48
-
47
+ request.add_tlv(TLV_TYPE_FILE_PATH, Rex::Text.unicode_filter_decode( hive_file ))
48
+
49
49
  response = client.send_request(request)
50
50
  return response.get_tlv(TLV_TYPE_RESULT).value
51
51
  end
@@ -95,7 +95,7 @@ class Registry
95
95
  return Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RemoteRegistryKey.new(
96
96
  client, target_host, root_key, response.get_tlv(TLV_TYPE_HKEY).value)
97
97
  end
98
-
98
+
99
99
  #
100
100
  # Creates the supplied registry key or opens it if it already exists.
101
101
  #
@@ -113,7 +113,7 @@ class Tlv
113
113
  def initialize(type, value = nil, compress=false)
114
114
  @type = type
115
115
  @compress = compress
116
-
116
+
117
117
  if (value != nil)
118
118
  if (type & TLV_META_TYPE_STRING == TLV_META_TYPE_STRING)
119
119
  if (value.kind_of?(Fixnum))
@@ -219,7 +219,7 @@ class Tlv
219
219
  # Serializers
220
220
  #
221
221
  ##
222
-
222
+
223
223
  #
224
224
  # Converts the TLV to raw.
225
225
  #
@@ -240,14 +240,14 @@ class Tlv
240
240
  raw = [0].pack("c")
241
241
  end
242
242
  end
243
-
243
+
244
244
  # check if the tlv is to be compressed...
245
245
  if( @compress )
246
246
  raw_uncompressed = raw
247
247
  # compress the raw data
248
248
  raw_compressed = Rex::Text.zlib_deflate( raw_uncompressed )
249
249
  # check we have actually made the raw data smaller...
250
- # (small blobs often compress slightly larger then the origional)
250
+ # (small blobs often compress slightly larger then the origional)
251
251
  # if the compressed data is not smaller, we dont use the compressed data
252
252
  if( raw_compressed.length < raw_uncompressed.length )
253
253
  # if so, set the TLV's type to indicate compression is used
@@ -257,7 +257,7 @@ class Tlv
257
257
  raw = [ raw_uncompressed.length ].pack("N") + raw_compressed
258
258
  end
259
259
  end
260
-
260
+
261
261
  return [raw.length + 8, self.type].pack("NN") + raw
262
262
  end
263
263
 
@@ -273,7 +273,7 @@ class Tlv
273
273
  if( self.type & TLV_META_TYPE_COMPRESSED == TLV_META_TYPE_COMPRESSED )
274
274
  # set this TLV as using compression
275
275
  @compress = true
276
- # remove the TLV_META_TYPE_COMPRESSED flag from the tlv type to restore the
276
+ # remove the TLV_META_TYPE_COMPRESSED flag from the tlv type to restore the
277
277
  # tlv type to its origional, allowing for transparent data compression.
278
278
  self.type = self.type ^ TLV_META_TYPE_COMPRESSED
279
279
  # decompress the compressed data (skipping the length and type DWORD's)
@@ -283,7 +283,7 @@ class Tlv
283
283
  # update the raw buffer with the new length, decompressed data and updated type.
284
284
  raw = [length, self.type].pack("NN") + raw_decompressed
285
285
  end
286
-
286
+
287
287
  if (self.type & TLV_META_TYPE_STRING == TLV_META_TYPE_STRING)
288
288
  if (raw.length > 0)
289
289
  self.value = raw[8..length-2]
@@ -293,7 +293,7 @@ class Tlv
293
293
  elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT)
294
294
  self.value = raw.unpack("NNN")[2]
295
295
  elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD)
296
- self.value = raw.unpack("NNQ")[2]
296
+ self.value = raw.unpack("NNQ")[2]
297
297
  self.value = self.ntohq( self.value )
298
298
  elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL)
299
299
  self.value = raw.unpack("NNc")[2]
@@ -309,9 +309,9 @@ class Tlv
309
309
 
310
310
  return length;
311
311
  end
312
-
312
+
313
313
  protected
314
-
314
+
315
315
  def htonq( value )
316
316
  if( [1].pack( 's' ) == [1].pack( 'n' ) )
317
317
  return value
@@ -322,7 +322,7 @@ class Tlv
322
322
  def ntohq( value )
323
323
  return htonq( value )
324
324
  end
325
-
325
+
326
326
  end
327
327
 
328
328
  ###
@@ -685,5 +685,6 @@ class Packet < GroupTlv
685
685
  end
686
686
  end
687
687
 
688
+
688
689
  end; end; end
689
690
 
@@ -1,4 +1,4 @@
1
- # $Id: server.rb 12471 2011-04-29 22:58:55Z scriptjunkie $
1
+ # $Id: server.rb 13165 2011-07-14 02:34:25Z scriptjunkie $
2
2
 
3
3
  require 'rex/socket'
4
4
  require 'rex/proto/dhcp'
@@ -27,8 +27,6 @@ class Server
27
27
  self.context = context
28
28
  self.sock = nil
29
29
 
30
- @shutting_down = false
31
-
32
30
  self.myfilename = hash['FILENAME'] || ""
33
31
  self.myfilename << ("\x00" * (128 - self.myfilename.length))
34
32
 
@@ -74,17 +72,10 @@ class Server
74
72
  end
75
73
 
76
74
  self.served = {}
77
- if (hash['SERVEONCE'])
78
- self.serveOnce = true
79
- else
80
- self.serveOnce = false
81
- end
82
-
83
- if (hash['PXE'])
84
- self.servePXE = true
85
- else
86
- self.servePXE = false
87
- end
75
+ self.serveOnce = hash.include?('SERVEONCE')
76
+
77
+ self.servePXE = (hash.include?('PXE') or hash.include?('FILENAME') or hash.include?('PXEONLY'))
78
+ self.serveOnlyPXE = hash.include?('PXEONLY')
88
79
 
89
80
  # Always assume we don't give out hostnames ...
90
81
  self.give_hostname = false
@@ -120,8 +111,8 @@ class Server
120
111
 
121
112
  # Stop the DHCP server
122
113
  def stop
123
- @shutting_down = true
124
114
  self.thread.kill
115
+ self.served = {}
125
116
  self.sock.close rescue nil
126
117
  end
127
118
 
@@ -131,7 +122,7 @@ class Server
131
122
  allowed_options = [
132
123
  :serveOnce, :servePXE, :relayip, :leasetime, :dnsserv,
133
124
  :pxeconfigfile, :pxepathprefix, :pxereboottime, :router,
134
- :give_hostname, :served_hostname, :served_over
125
+ :give_hostname, :served_hostname, :served_over, :serveOnlyPXE
135
126
  ]
136
127
 
137
128
  opts.each_pair { |k,v|
@@ -158,7 +149,7 @@ class Server
158
149
  attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv
159
150
  attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce
160
151
  attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
161
- attr_accessor :servePXE, :pxeconfigfile, :pxepathprefix, :pxereboottime
152
+ attr_accessor :servePXE, :pxeconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
162
153
  attr_accessor :give_hostname, :served_hostname, :served_over
163
154
 
164
155
  protected
@@ -242,24 +233,26 @@ protected
242
233
  end
243
234
  end
244
235
 
245
- if pxeclient == false && self.servePXE == true
246
- #dlog ("No tftp server request; ignoring (probably not PXE client)")
247
- return
248
- end
236
+ # don't serve if only serving PXE and not PXE request
237
+ return if pxeclient == false and self.serveOnlyPXE == true
249
238
 
250
239
  # prepare response
251
240
  pkt = [Response].pack('C')
252
241
  pkt << buf[1..7] #hwtype, hwlen, hops, txid
253
242
  pkt << "\x00\x00\x00\x00" #elapsed, flags
254
243
  pkt << clientip
255
- if messageType == DHCPDiscover
256
- # give next ip address (not super reliable high volume but it should work for a basic server)
244
+
245
+ # if this is somebody we've seen before, use the saved IP
246
+ if self.served.include?( buf[28..43] )
247
+ pkt << Rex::Socket.addr_iton(self.served[buf[28..43]][0])
248
+ else # otherwise go to next ip address
257
249
  self.current_ip += 1
258
250
  if self.current_ip > self.end_ip
259
251
  self.current_ip = self.start_ip
260
252
  end
253
+ self.served.merge!( buf[28..43] => [ self.current_ip, messageType == DHCPRequest ] )
254
+ pkt << Rex::Socket.addr_iton(self.current_ip)
261
255
  end
262
- pkt << Rex::Socket.addr_iton(self.current_ip)
263
256
  pkt << self.ipstring #next server ip
264
257
  pkt << self.relayip
265
258
  pkt << buf[28..43] #client hw address
@@ -270,15 +263,17 @@ protected
270
263
 
271
264
  if messageType == DHCPDiscover #DHCP Discover - send DHCP Offer
272
265
  pkt << [DHCPOffer].pack('C')
273
- # check if already served based on hw addr (MAC address)
274
- if self.serveOnce == true && self.served.has_key?(buf[28..43])
275
- #dlog ("Already served; allowing normal boot")
266
+ # check if already served an Ack based on hw addr (MAC address)
267
+ # if serveOnce & PXE, don't reply to another PXE request
268
+ # if serveOnce & ! PXE, don't reply to anything
269
+ if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
270
+ self.served[buf[28..43]][1] and (pxeclient == true or self.servePXE == false)
276
271
  return
277
272
  end
278
273
  elsif messageType == DHCPRequest #DHCP Request - send DHCP ACK
274
+ self.served[buf[28..43]][1] = true # mark as requested
279
275
  pkt << [DHCPAck].pack('C')
280
276
  # now we ignore their discovers (but we'll respond to requests in case a packet was lost)
281
- self.served.merge!( buf[28..43] => true )
282
277
  if ( self.served_over != 0 )
283
278
  # NOTE: this is sufficient for low-traffic net
284
279
  # for high-traffic, this will probably lead to
@@ -286,8 +281,7 @@ protected
286
281
  self.served_over += 1
287
282
  end
288
283
  else
289
- #dlog("ignoring unknown DHCP request - type #{messageType}")
290
- return
284
+ return # ignore unknown DHCP request
291
285
  end
292
286
 
293
287
  # Options!
@@ -296,20 +290,20 @@ protected
296
290
  pkt << dhcpoption(OpSubnetMask, self.netmaskn)
297
291
  pkt << dhcpoption(OpRouter, self.router)
298
292
  pkt << dhcpoption(OpDns, self.dnsserv)
299
- pkt << dhcpoption(OpPXEMagic, PXEMagic)
300
- pkt << dhcpoption(OpPXEConfigFile, self.pxeconfigfile)
301
- pkt << dhcpoption(OpPXEPathPrefix, self.pxepathprefix)
302
- pkt << dhcpoption(OpPXERebootTime, [self.pxereboottime].pack('N'))
303
- if ( self.give_hostname == true )
304
- send_hostname = self.served_hostname
305
- if ( self.served_over != 0 )
306
- # NOTE : see above comments for the 'uniqueness'
307
- # of this value
308
- send_hostname += self.served_over.to_s
293
+ if self.servePXE # PXE options
294
+ pkt << dhcpoption(OpPXEMagic, PXEMagic)
295
+ pkt << dhcpoption(OpPXEConfigFile, self.pxeconfigfile)
296
+ pkt << dhcpoption(OpPXEPathPrefix, self.pxepathprefix)
297
+ pkt << dhcpoption(OpPXERebootTime, [self.pxereboottime].pack('N'))
298
+ if ( self.give_hostname == true )
299
+ send_hostname = self.served_hostname
300
+ if ( self.served_over != 0 )
301
+ # NOTE : see above comments for the 'uniqueness' of this value
302
+ send_hostname += self.served_over.to_s
303
+ end
304
+ pkt << dhcpoption(OpHostname, send_hostname)
309
305
  end
310
- pkt << dhcpoption(OpHostname, send_hostname)
311
306
  end
312
-
313
307
  pkt << dhcpoption(OpEnd)
314
308
 
315
309
  pkt << ("\x00" * 32) #padding