Obtain MAC Address from Devices using Python - python

I'm looking for a way (with python) to obtain the layer II address from a device on my local network. Layer III addresses are known.
The goal is to build a script that will poll a databases of IP addresses on regular intervals ensuring that the mac addresses have not changed and if they have, email alerts to myself.

To answer the question with Python depends on your platform. I don't have Windows handy, so the following solution works on the Linux box I wrote it on. A small change to the regular expression will make it work in OS X.
First, you must ping the target. That will place the target -- as long as it's within your netmask, which it sounds like in this situation it will be -- in your system's ARP cache. Observe:
13:40 jsmith#undertow% ping 97.107.138.15
PING 97.107.138.15 (97.107.138.15) 56(84) bytes of data.
64 bytes from 97.107.138.15: icmp_seq=1 ttl=64 time=1.25 ms
^C
13:40 jsmith#undertow% arp -n 97.107.138.15
Address HWtype HWaddress Flags Mask Iface
97.107.138.15 ether fe:fd:61:6b:8a:0f C eth0
Knowing that, you do a little subprocess magic -- otherwise you're writing ARP cache checking code yourself, and you don't want to do that:
>>> from subprocess import Popen, PIPE
>>> import re
>>> IP = "1.2.3.4"
>>> # do_ping(IP)
>>> # The time between ping and arp check must be small, as ARP may not cache long
>>> pid = Popen(["arp", "-n", IP], stdout=PIPE)
>>> s = pid.communicate()[0]
>>> mac = re.search(r"(([a-f\d]{1,2}\:){5}[a-f\d]{1,2})", s).groups()[0]
>>> mac
"fe:fd:61:6b:8a:0f"

There was a similar question answered not too long ago on this site. As mentioned in the answer chosen by the asker of that question, Python doesn't have a built-in way to do it. You must either call a system command such as arp to get ARP information, or generate your own packets using Scapy.
Edit: An example using Scapy from their website:
Here is another tool that will
constantly monitor all interfaces on a
machine and print all ARP request it
sees, even on 802.11 frames from a
Wi-Fi card in monitor mode. Note the
store=0 parameter to sniff() to avoid
storing all packets in memory for
nothing.
#! /usr/bin/env python
from scapy import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")
sniff(prn=arp_monitor_callback, filter="arp", store=0)
You could also do something similar to the verified answer. See https://scapy.readthedocs.io/en/latest/routing.html
>>> mac = getmacbyip("10.0.0.1")
>>> mac
'f3:ae:5e:76:31:9b'
This is fully cross platform.
Not exactly what you're looking for, but definitely on the right track. Enjoy!

In Linux sometimems you miss the command line util "arp". A base yocto linux embedded environment image for instance.
An alternative way without the "arp" tool would be to read and parse the file /proc/net/arp:
root#raspberrypi:~# cat /proc/net/arp
IP address HW type Flags HW address Mask Device
192.168.1.1 0x1 0x2 xx:xx:xx:xx:xx:xx * wlan0
192.168.1.33 0x1 0x2 yy:yy:yy:yy:yy:yy * wlan0

A simple solution using scapy, to scan the 192.168.0.0/24 subnet is as follows:
from scapy.all import *
ans,unans = arping("192.168.0.0/24", verbose=0)
for s,r in ans:
print("{} {}".format(r[Ether].src,s[ARP].pdst))

Sounds like you want to monitor ARP spoofers? In this case, all you need is arpwatch, available in every well-supplied Linux distribution near you. Download sources here: http://ee.lbl.gov/

for Unix based systems:
#!/usr/bin/env python2.7
import re
import subprocess
arp_out =subprocess.check_output(['arp','-lan'])
re.findall(r"((\w{2,2}\:{0,1}){6})",arp_out)
will return list of tuples with macs.
scapy is an amazing tool , but seems to be overkill for this case

an easier way, if on linux:
print os.system('arp -n ' + str(remoteIP))
you will get:
Address HWtype HWaddress Flags Mask Iface
192.168..... ether 9B:39:15:f2:45:51 C wlan0

General update for Python 3.7. Remark: the option -n for arp does not provide the arp list on windows systems as provided with certain answers for linux based systems. Use the option -a as stated in the answer here.
from subprocess import Popen, PIPE
pid = Popen(['arp', '-a', ip], stdout=PIPE, stderr=PIPE)
IP, MAC, var = ((pid.communicate()[0].decode('utf-8').split('Type\r\n'))[1]).split(' ')
IP = IP.strip(' ')
MAC = MAC.strip(' ')
if ip == IP:
print ('Remote Host : %s\n MAC : %s' % (IP, MAC))

Related

Why am I obtaining this strange output using the scapy.sniff() function trying to sniff traffic of the opened website?

I am very new in Pyhon (I mainly came from Java) and I am following a Pytohon course applied to security on Udemy in which it is presented an example of packet sniffer implementation using scapy module. I am using Python 3 and this is the structure of my Python project with the scapy version highlighted:
It seems to work but I have some doubts related to the output of this application.
This is my source code of my script:
#!usr/bin/env python
# INSTALL THE FOLLOWING PYTHON MODULES:
# - pip3 install scapy
# - pip3 install scapy_http
import scapy.all as scapy
from scapy.layers import http
#
def sniff(interface):
scapy.sniff(iface=interface, store=False, prn=process_sniffed_packet)
def process_sniffed_packet(packet):
#print(packet)
# Check if our packet has HTTP layer. If our packet has the HTTP layer and it is HTTPRequest.
# In this way I am excluding some garbage information in which I am not interested into.
if packet.haslayer(http.HTTPRequest):
print(packet)
sniff("eth0")
So this script sniff the traffic on the eth0 port and by the content of this if statement:
if packet.haslayer(http.HTTPRequest):
print(packet)
it print only the packet related to the HTTP layer avoiding to print other garbage information in which I am not interested.
So I execute the script launching this command into my Linux shell:
python3 packet_sniffer.py
and the script wait until I open a website into the browser and I obtain an output like this:
root#kali:~/Documents/PycharmWS/packet_sniffer# python3 packet_sniffer.py
b"\x00PV\xfd\xa9B\x00PV)\x97\xc7\x08\x00E\x00\x01\x9d\xdb\x84#\x00#\x06\x18*\xc0\xa8\xdf\x85\xd8:\xcdC\x90$\x00Pe\xa7\xb3\x8eM\xf9Y\xd6P\x18\xf9\x8aG<\x00\x00POST /gts1o1 HTTP/1.1\r\nHost: ocsp.pki.goog\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\r\nAccept: */*\r\nAccept-Language: en-US,en;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: application/ocsp-request\r\nContent-Length: 83\r\nConnection: keep-alive\r\n\r\n0Q0O0M0K0I0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14BF0\xc2'\x19\xdb\xdep\xf0\x8f\xfcs\xe5\xa6_f8\x17\xbc\x04\x14\x98\xd1\xf8n\x10\xeb\xcf\x9b\xec`\x9f\x18\x90\x1b\xa0\xeb}\t\xfd+\x02\x10Qn\xe3\x01\xd1(\xfa$\x08\x00\x00\x00\x002\n\x81"
Here I have some doubt:
1) Why am I obtaining this strange string as output? On the Udemy tutorial there is a more understandable output showing information of the package as such as refereres, User-Agent, Host in a more clear way. What is all these \x.. value in my output? It seems to me that it is coded in some way but I am absolutly not sure about this.
2) Opening different web sites sometimes it provide me the output after that a specific website was opened but some other time it give me no output. How is it possible?
3) Is it working only over HTTP or it is sniffing also over HTTPS?
What is wrong? What am I missing? How can it ry to fix it?
Question 1
What are Python byte arrays?
That's the byte array of the packet. "\x55" means 01010101. In python, a bytes object is like a string, but prepending with b like b"bytes" or b'bytes'.
As an example, if we take the first 4 bytes of the bytearray that was printed,
and write it to a file, we can see the bytes representations according to xxd.
$ python -c 'f=open("temp", "wb");f.write(b"\x00PV\xfd");f.close()'
$ xxd temp
00000000: 0050 56fd .PV.
Here,
b"\x00P" => 0050
b"V\xfd" => 56fd
The 2nd char, P in the byte array, is the hex representation of the ASCII char P.
So you are getting this output because you are printing the bytes of the packet. If you want to print a different representation.
How to print packets in scapy
Use packet.show() instead of print(packet) to have scapy analyze it for you.
Output will look like this:
$ python script.py
###[ Ethernet ]###
dst = cc:65:ad:da:39:70
src = 6c:96:cf:d8:7f:e7
type = IPv6
###[ IPv6 ]###
version = 6
tc = 2
fl = 131466
...
###[ HTTP 1 ]###
###[ HTTP Request ]###
Method = 'GET'
Path = '/online'
Http_Version= 'HTTP/1.1'
...
You could use print(packet.summary()) instead to get something like this per packet:
Ether / IPv6 / TCP / HTTP / 'GET' '/online' 'HTTP/1.1'
Scapy packet object documentation is here, and to see the methods/attributes of an object in Python, use dir(Object).
Question 2
HTTP is a different protocol than HTTPS. Scapy listens for HTTP with that filter and drops HTTPS.
Question 3
Correct. See 2.
Ultimately, if you want to capture all of the packets, don't use a filter. There are ways to decrypt HTTPS packets, but that's for a different question ;)

Python-Nmap scan ports by list

i have try to scan some port in list. I know nmap has support for port scan from file. I'm tried on nmap and that working:
nmap 141.101.220.172 -p $(tr '\n' , </home/congminhcpt/donnq/ports.list)
but when i'm try to use it on python, some thing has problems with error code in nmap scan result:
Error #485: Your port specifications are illegal.
That my python code:
import json
import nmap
nm = nmap.PortScanner()
arg = '-sV -p $(tr \'\\n\' , </home/congminhcpt/donnq/ports.list)'
data = nm.scan(hosts='141.101.220.172', arguments=arg)
this is ports.list file:
25
80
110
143
443
Hope some one can help me!
Thanks!
The nmap python library won't accept the redirection from the file, you'd better list the ports right in the command line, since there are just a few:
arg = '-sV -p T:21-25,80,139,8080' # or whatever ports you want
T: means you want to scan TCP ports. If you want UDP, put U there.
thanks, simple way is convert list to string and scan with argument='-p ' + port_string
thanks for help!

How to get my network adapter IP with python?

If I type "ipconfig" in cmd, its gives some IPs... I need the one who belongs to my network adapter\internal IP.
So. for example:
If I have 3 Ips:
10.100.102.2
192.168.231.1
192.168.233.1
And I need the first one. How do I make python know that this is the one I need\The one belongs to my internal IP?
The solution for extracting the first IPv4 address of ethernet adapter (Python on Windows):
- used modules: os, re
import os, re
addresses = os.popen('IPCONFIG | FINDSTR /R "Ethernet adapter Local Area Connection .* Address.*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"')
first_eth_address = re.search(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', addresses.read()).group()
print(first_eth_address)

Python: get MAC address of default gateway

Is there any quick way in Python to get the MAC address of the default gateway?
I can't make any ARP requests from the Linux machine I'm running my code on, so it has to come directly from the ARP table.
The following DevFS files will provide this information:
/proc/net/arp
/proc/net/route
Find the route entry with 0/0 as the host/mask:
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 B91A210A 0003 0 0 100 00000000 0 0 0
and convert the Gateway field (it's in little-endian hex... grr):
import struct
from IPy import IP
address = IP(struct.unpack('<I', struct.pack('>I', int(i, 16)))[0])
#Converts 'B91A210A' to IP('10.33.26.185')
From there, you can find your default gateway in the arp table:
IP address HW type Flags HW address Mask Device
10.33.26.185 0x1 0x2 e6:92:ec:f5:af:f7 * eth0
If it doesn't show up, issue a single ping, then check again, then fail.
Are you using Linux? You could parse the /proc/net/arp file. It contains the HW address of your gateway.
You can read from /proc/net/arp and parse the content, that will give you couples of known IP-MAC addresses.
The gateway is probably known at all times, if not you should ping it, and an ARP request will be automatically generated.
You can find the default gw in /proc/net/route

Sniffing UDP packets using scapy in Mac

I am trying to sniff UDP packets using scapy sniff function, I send the packets in the Looback interface, the sending code is simple as follows:
from socket import *
IPv4 = "127.0.0.1"
Port = 45943
ClientSock = socket(AF_INET, SOCK_DGRAM)
while True:
MESSAGE = raw_input()
ClientSock.sendto(MESSAGE, (IPv4, Port))
However when i run (in another terminal tab after importing scapy):
a = sniff(iface="lo0", count = 5)
I get the following result:
>>> a.nsummary()
0000 Raw
0001 Raw
0002 Raw
0003 Raw
0004 Raw
whereas i am supposed to get UDP packets!, can any one point out to anything that i am missing here.
thanks
Unable to guess datalink type (interface=lo0 linktype=0)
That message translates as "Scapy doesn't understand the DLT_NULL link-layer header type, as used on the loopback device in *BSD and OS X, so it doesn't support the loopback device on *BSD and OS X".
So you're out of luck if you want to use Scapy on OS X to capture on the loopback device, unless and until Scapy is enhanced to handle DLT_NULL. (DLT_NULL is not that hard to handle, so presumably the only reason it's not handled is that most of the people using it on a loopback device are doing so on Linux, where the link-layer header type value on the loopback device is DLT_EN10MB, i.e. Ethernet, so nobody's bothered to fix it. I'll see if I can get it working and, if so, send them a patch.)
Some suggestions.
Instead of a.nsummary(), you can print out more information on individual packets using something like
a[1].show()
a[1].show2()
hexdump(a[1])
to examine the first packet.
2) You can force the protocol decoding to a particular type of packet format. For instance, a RAW_IP packet capture (link layer header type = 101) can be forced to be IPv6 using
conf.l2types.register(101, IPv6)
If you want to add a new layer on top of UDP, you can add a new dissector based on the port used.

Categories