Repository: fortra/impacket
Issue category: Bug Report

Issue:
dhcp.py: decode error  "object has no attribute 'encode'"
### Configuration
  
impacket version:  HEAD
Python version:  3.11
Target OS:  Linux

### Debug Output With Command String  

````
		dhcp = DhcpPacket(buffer)
		print(dhcp)
``````


```
Traceback (most recent call last):
  File "/home/mdt/Source/emdete/honeypot/snooper/main.py", line 14, in learn_dhcp
    dhcp = DHCP(buffer)
           ^^^^^^^^^^^^
  File "/home/mdt/Source/oss/python/impacket/impacket/dhcp.py", line 148, in __init__
    structure.Structure.__init__(self, data, alignment)
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 87, in __init__
    self.fromString(data)
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 152, in fromString
    self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 307, in unpack
    return eval(dataClassOrCode, {}, fields)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
  File "/home/mdt/Source/oss/python/impacket/impacket/dhcp.py", line 179, in unpackOptions
    value = self.unpack(format, options[i+2:i+2+size])
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 382, in unpack
    return dataClassOrCode(data)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/six.py", line 644, in b
    return s.encode("latin-1")
           ^^^^^^^^
AttributeError: ("'bytearray' object has no attribute 'encode'", "When unpacking field 'options | _ | b''[:0]'")
```


### PCAP  

```
 b'\x01\x01\x06\x00\xa3Y\xdf\x06\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf84A\xdb2.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x032\x04\xc0\xa8\x00\x8c6\x04\xc0\xa8\x00\x019\x02\x02@7\x07\x01\x03\x06\x0c\x0f\x1c*<\x0cudhcp 1.36.1=\x07\x01\xf84A\xdb2.\xff\x00\x00\x00\x00\x00\x00\x00\x00'
```

### Additional context  





Hints:
it seems the line 381 in `impacket/structure.py` should read:

```
if (isinstance(data, bytes) or isinstance(data, bytearray)) and dataClassOrCode is b:
```

because the buffer is not bytes but bytearray. fixing this leads to the next error:

```
Traceback (most recent call last):
  File "/home/mdt/Source/emdete/honeypot/snooper/main.py", line 14, in learn_dhcp
    dhcp = DhcpPacket(buffer)
           ^^^^^^^^^^^^^^^^^^
  File "/home/mdt/Source/oss/python/impacket/impacket/dhcp.py", line 148, in __init__
    structure.Structure.__init__(self, data, alignment)
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 87, in __init__
    self.fromString(data)
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 152, in fromString
    self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 307, in unpack
    return eval(dataClassOrCode, {}, fields)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
  File "/home/mdt/Source/oss/python/impacket/impacket/dhcp.py", line 179, in unpackOptions
    value = self.unpack(format, options[i+2:i+2+size])
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mdt/Source/oss/python/impacket/impacket/structure.py", line 386, in unpack
    return unpack(format, data)[0]
           ^^^^^^^^^^^^^^^^^^^^
struct.error: ('unpack requires a buffer of 4 bytes', "When unpacking field 'options | _ | b''[:0]'")

```

I also got this error, here is my code:
```
import struct
from impacket import ImpactDecoder
from impacket import dhcp
import socket

dhcp_packet = (
    b'\x01'              # Message type: Boot Request (1 byte)
    b'\x01'              # Hardware type: Ethernet (1 byte)
    b'\x06'              # Hardware address length: 6 (1 byte)
    b'\x00'              # Hops: 0 (1 byte)
    b'\x39\x03\xF3\x26'  # Transaction ID: Random (4 bytes)
    b'\x00\x00'          # Seconds elapsed: 0 (2 bytes)
    b'\x80\x00'          # Flags: 0x8000 (Broadcast) (2 bytes)
    b'\x00\x00\x00\x00'  # Client IP address: 0.0.0.0 (4 bytes)
    b'\x00\x00\x00\x00'  # Your (client) IP address: 0.0.0.0 (4 bytes)
    b'\x00\x00\x00\x00'  # Next server IP address: 0.0.0.0 (4 bytes)
    b'\x00\x00\x00\x00'  # Relay agent IP address: 0.0.0.0 (4 bytes)
    b'\x00\x26\x9e\x04\x0a\x5b'  # Client MAC address (6 bytes)
    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'  # Client hardware address padding (10 bytes)
    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'  # Server name padding (64 bytes)
    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'   # Boot filename padding (128 bytes)
    b'\x63\x82\x53\x63'  # Magic cookie: DHCP (4 bytes)
    # DHCP Options:
    b'\x35\x01\x01'
    b'\x32\x04\xc0\xa8\x01\x64'
    b'\x33\x04\x01\xa8\x01\x64'      # Option: (53) DHCP Message Type (Discover) (3 bytes)
    # b'\x3d\x07\x01\x00\x26\x9e\x04\x0a\x5b'  # Option: (61) Client identifier (7 bytes)
    b'\x37\x01\x03\x01\x06'  # Option: (55) Parameter Request List (5 bytes)
    # b'\xff'              # End Option (1 byte)
)

dhcp_decoder = ImpactDecoder.BootpDecoder()
dhcp_packet_decoded = dhcp_decoder.decode(dhcp_packet)
print(dhcp_packet_decoded)
```
Could anyone take a look at it?
As @emdete mentioned, this error can be solved by modifying the following condition to also include `bytearray` objects https://github.com/fortra/impacket/blob/3ce41be452dfe578f7edea16bc816e4f7fabe04d/impacket/structure.py#L384

The fix _may_ cause some unwanted splash damage so further testing is required.

Another option would be to fix the method responsible for calling structure.unpack with bytearray as an argument (instead of bytes). It was recently introduced by another PR: https://github.com/fortra/impacket/commit/3f645107bb4db65fd8a328399031a257723c6bfb.

We could convert `options[...]` to `bytes` in the following line: https://github.com/fortra/impacket/blob/3ce41be452dfe578f7edea16bc816e4f7fabe04d/impacket/dhcp.py#L181

Both alternatives fix the problem

Patch:
diff --git a/impacket/dhcp.py b/impacket/dhcp.py
index aeabb4013..dde0c3909 100644
--- a/impacket/dhcp.py
+++ b/impacket/dhcp.py
@@ -178,7 +178,7 @@ def unpackOptions(self, options):
             # size = self.calcUnpackSize(format, options[i+1:])
             size = options[i+1]
             # print i, name, format, size
-            value = self.unpack(format, options[i+2:i+2+size])
+            value = self.unpack(format, bytes(options[i+2:i+2+size]))
             answer.append((name, value))
             i += 2+size
 


Test patch: