Intel AMT discovery
Published:
Updated:
There has been some articles lately about Intel Active Management Technology (AMT) and its impact on security, trust, privacy and free-software. AMT supposed to be widely deployed in newest Intel hardware. So I wanted to see if I could find some AMT devices in the wild.
Update: 2017-05-15 Add references related to CVE-2017-5689 (AMT vulerability).
What is AMT anyway?
AMT is an Intel technology for out of band management (without cooperation of the OS) of a computer over the network even if the computer is turned off. It can be used to do things such as:
- booting, shutting down, rebooting, waking the computer;
- changing the booting method (such as enabling PXE);
- serial-over-LAN, KVM, IDE and USB redirections, etc.
It implements the Desktop and mobile Architecture for System Hardware (DASH) standard and is similar to the Intelligent Platform Management Interface (IMPI) in terms of features. It uses SOAP over HTTP with some WS-* greatness. It comes with bells and whistles such as integration with Active Directory 😱.
When AMT is enabled, IP packets incoming on the builtin network adapter for some TCP and UDP ports are sent directly to the ME instead of reaching the OS. The ME has its own processor and its own OS and can give access to the hardware over the network. Usually, the ME and the main system share the same network interface, MAC address and IPv6 address.
A physical system’s out-of-band Management Access Point and the In-Band host shall share the MAC address and IPv4 address of the network interface. Manageability traffic shall be routed to the MAP [Management Access Point] through the well known system ports defined by IANA.
TCP/UDP messages addressed to certain registered ports are routed to Intel AMT when those ports are enabled. Messages received on a wired LAN interface go directly to Intel AMT. Messages received on a wireless interface go to the host wireless driver. The driver detects the destination port and sends the message to Intel AMT.
My machine
My work laptop has a Intel Management Engine Interface (MEI) device and the system loads the MEI Linux module:
$ lspci
[...]
00:16.0 Communication controller: Intel Corporation 7 Series/C210 Series Chipset Family MEI Controller #1 (rev 04)
[...]
$ lsmod | grep mei
mei_me 32768 0
mei 94208 1 mei_me
$ ls -l /dev/mei*
crw------- 1 root root 246, 0 juil. 7 08:53 /dev/mei0
$ grep mei /lib/modules/4.6.0-1-amd64/modules.alias
alias pci:v00008086d00005A9Asv*sd*bc*sc*i* mei_me
alias pci:v00008086d00001A9Asv*sd*bc*sc*i* mei_me
[...]
alias mei:pn544:0bb17a78-2a8e-4c50-94d4-50266723775c:*:* pn544_me
$ cat /sys/bus/pci/drivers/mei_me/0000:00:16.0/uevent
MAJOR=248
MINOR=0
DEVNAME=mei0
MEI is a PCI-based interface to the Management Engine (ME) from within the computer.
However, there is no option to disable AMT in the BIOS on my laptop. Apparently, AMT is not enabled on this device even if this not absolutely clear. The hardware seems to be there though.
AMT Discovery
We can use the discovery mechanism of AMT in order to detect AMT devices on a network. The AMT (and DASH) discovery uses two phases:
-
the first phase uses ASF RMCP (Alert Standard Format - Remote Management and Control Protocol);
-
the second phase uses the WS-Management Identify method.
The second phase is not so useful so I will focus on the first one.
Implementation
The first phase is quite simple:
-
the client sends a (possibly) broadcast RMCP Presence Ping message over UDP port 623 (asf-rmcp);
-
the nodes supporting ASF, such as DASH/AMT and IPMI (Intelligent Platform Management Interface) nodes, send a RMCP Presence Pong.
Size | Field |
---|---|
1B | Version (0x6 for RMCP 1.0) |
1B | Reserved |
1B | Sequence number (0--254, 255 when no no acknowledge is needed) |
1B | Class of Message |
Bit 7, 1 for acknowledge | |
Bits 6:4, reserved | |
Bits 3:0, 6 for ASF, 7 for IPMI, etc. |
All messages which are not acknowledges have a RMCP data field after the header:
Size | Field |
---|---|
4B | IANA Entreprise Number, servces as a namespace for the message type (4542 for ASF-RMCP) |
1B | Message Type (for ASF-RMCP, we have 0x80 for Presence Ping, 0x40 for Presence Pong) |
1B | Message Tag |
1B | Reserved |
1B | Data Length |
Var | Data (payload) |
We can handle RMCP messages with:
ASF_RMCP_VERSION1 = 0x6
IANA_ASF = 4542
ASF_RMCP_FORMAT = "!BBBBIBBBB"
# RCMP ASF message (not ack)
class Message:
def __init__(self):
self.version = ASF_RMCP_VERSION1
self.reserved = 0x00
self.seqno = 0x00
self.message_class = 0x00
self.entreprise_number = IANA_ASF
self.message_type = 0x00
self.message_tag = 0x00
self.reserved = 0x00
self.data = bytearray()
def load(self, message):
if (len(message) < struct.calcsize(ASF_RMCP_FORMAT)):
raise "Message too small"
(self.version, self.reserved, self.seqno, self.message_class,
self.entreprise_number, self.message_type, self.message_tag,
self.reserved, data_length) = \
struct.unpack_from(ASF_RMCP_FORMAT, message)
if len(message) != data_length + struct.calcsize(ASF_RMCP_FORMAT):
raise "Bad length"
rmcp_size = struct.calcsize(ASF_RMCP_FORMAT)
self.data = bytearray(memoryview(message)[rmcp_size:])
def to_bytes(self):
size = struct.calcsize(ASF_RMCP_FORMAT) + len(self.data)
res = bytearray(size)
struct.pack_into(ASF_RMCP_FORMAT, res, 0,
self.version, self.reserved, self.seqno,
self.message_class, self.entreprise_number,
self.message_type, self.message_tag, self.reserved,
len(self.data))
memoryview(res)[struct.calcsize(ASF_RMCP_FORMAT):] = self.data
return res
For Presence Ping, there is no payload. For Presence Pong, the payload is:
Size | Field |
---|---|
4B | IANA Entreprise Number (4542 if not OEM specific-things are used) |
4B | OEM Defined |
1B | Supported Entities |
Bit 7, set if IPMI is supported | |
Bits 6:4, reserved | |
Bits 3:0, 1 for ASF version 1.0 | |
1B | Supported interactions |
Bit 5: set if DASH (AMT) is supported | |
5B | Reserved |
We can handle Pong Presence data with:
ASF_RMCP_PONG_FORMAT = "!IIBBBBBBBB"
class PongData:
def __init__(self, payload):
if struct.calcsize(ASF_RMCP_PONG_FORMAT) != len(payload):
print("Bad length for pong payload expected %i but was %i" %
(struct.calcsize(ASF_RMCP_PONG_FORMAT), len(payload)))
(self.entreprise_number, self.oem_defined, self.supported_entities,
self.supported_interactions, self.reserved1,
self.reserved2, self.reserved3, self.reserved4, self.reserved5,
self.reserved6) = struct.unpack_from(ASF_RMCP_PONG_FORMAT, payload)
def ipmi(self):
return (self.supported_entities & 127) != 0
def asf(self):
return (self.supported_entities & 15) == 1
def dash(self):
return (self.supported_interactions & 32) != 0
def features(self):
res = []
if self.ipmi():
res.append("ipmi")
if self.asf():
res.append("asf")
if self.dash():
res.append("dash")
return res
We send a Presence Ping message to some (possibly broadcast) address:
ASF_RMCP_PORT = 623
ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING = 0x80
m = Message()
m.message_class = ASF_RMCP_VERSION1
m.message_type = ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(m.to_bytes(), (address, ASF_RMCP_PORT))
And then we process the messages:
ASF_RMCP_MESSAGE_TYPE_PRESENCE_PONG = 0x40
ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING_ACK = 0x86
sock.settimeout(1)
try:
while True:
data, addr = sock.recvfrom(1024)
logging.debug("From " + str(addr[0]) + ": " + str(data))
if len(data) == 4 and data[0] == ASF_RMCP_VERSION1 and data[2] == 0 \
and data[3] == ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING_ACK:
logging.debug("Ack from " + str(addr[0]))
continue
try:
m.load(data)
except:
continue
if m.message_type == ASF_RMCP_MESSAGE_TYPE_PRESENCE_PONG:
# Pong:
print(str(addr[0]))
pongData = PongData(m.data)
features = pongData.features()
print("\tEntreprise: %s" %
entreprise_name(pongData.entreprise_number))
if len(features) != 0:
print("\tFeatures: %s" % ",".join(features))
except socket.timeout:
pass
Full code
Here is the full code:
#!/usr/bin/env python3
# Use ASF RMCP to discover RMCP-aware nodes (such as AMT/AMT or IPMI)
# Keywords: DMTF, ASF RMCP, DASH, AMT, IPMI.
import socket
import ctypes
import struct
import sys
import logging
import ipaddress
ASF_RMCP_PORT = 623
ASF_RMCP_FORMAT = "!BBBBIBBBB"
ASF_RMCP_PONG_FORMAT = "!IIBBBBBBBB"
ASF_RMCP_VERSION1 = 0x6
ASF_RMCP_MESSAGE_TYPE_PRESENCE_PONG = 0x40
ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING = 0x80
ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING_ACK = 0x86
IANA_ASF = 4542
address = sys.argv[1]
if ipaddress.ip_address(address).version == 4:
address = "::ffff:" + address
entreprise_names = {
343: "Intel",
3704: "AMD",
4542: "Alerting Specifications Forum",
}
def entreprise_name(n):
if n in entreprise_names:
return entreprise_names[n]
else:
return str(n)
# RCMP ASF message (not ack)
class Message:
def __init__(self):
self.version = ASF_RMCP_VERSION1
self.reserved = 0x00
self.seqno = 0x00
self.message_class = 0x00
self.entreprise_number = IANA_ASF
self.message_type = 0x00
self.message_tag = 0x00
self.reserved = 0x00
self.data = bytearray()
def load(self, message):
if (len(message) < struct.calcsize(ASF_RMCP_FORMAT)):
raise "Message too small"
(self.version, self.reserved, self.seqno, self.message_class,
self.entreprise_number, self.message_type, self.message_tag,
self.reserved, data_length) = \
struct.unpack_from(ASF_RMCP_FORMAT, message)
if len(message) != data_length + struct.calcsize(ASF_RMCP_FORMAT):
raise "Bad length"
rmcp_size = struct.calcsize(ASF_RMCP_FORMAT)
self.data = bytearray(memoryview(message)[rmcp_size:])
def to_bytes(self):
size = struct.calcsize(ASF_RMCP_FORMAT) + len(self.data)
res = bytearray(size)
struct.pack_into(ASF_RMCP_FORMAT, res, 0,
self.version, self.reserved, self.seqno,
self.message_class, self.entreprise_number,
self.message_type, self.message_tag, self.reserved,
len(self.data))
memoryview(res)[struct.calcsize(ASF_RMCP_FORMAT):] = self.data
return res
class PongData:
def __init__(self, payload):
if struct.calcsize(ASF_RMCP_PONG_FORMAT) != len(payload):
print("Bad length for pong payload expected %i but was %i" %
(struct.calcsize(ASF_RMCP_PONG_FORMAT), len(payload)))
(self.entreprise_number, self.oem_defined, self.supported_entities,
self.supported_interactions, self.reserved1,
self.reserved2, self.reserved3, self.reserved4, self.reserved5,
self.reserved6) = struct.unpack_from(ASF_RMCP_PONG_FORMAT, payload)
def ipmi(self):
return (self.supported_entities & 127) != 0
def asf(self):
return (self.supported_entities & 15) == 1
def dash(self):
return (self.supported_interactions & 32) != 0
def features(self):
res = []
if self.ipmi():
res.append("ipmi")
if self.asf():
res.append("asf")
if self.dash():
res.append("dash")
return res
m = Message()
m.message_class = ASF_RMCP_VERSION1
m.message_type = ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(m.to_bytes(), (address, ASF_RMCP_PORT))
sock.settimeout(1)
logging.info("Listening")
try:
while True:
data, addr = sock.recvfrom(1024)
logging.debug("From " + str(addr[0]) + ": " + str(data))
if len(data) == 4 and data[0] == ASF_RMCP_VERSION1 and data[2] == 0 \
and data[3] == ASF_RMCP_MESSAGE_TYPE_PRESENCE_PING_ACK:
logging.debug("Ack from " + str(addr[0]))
continue
try:
m.load(data)
except:
continue
if m.message_type == ASF_RMCP_MESSAGE_TYPE_PRESENCE_PONG:
# Pong:
print(str(addr[0]))
pongData = PongData(m.data)
features = pongData.features()
print("\tEntreprise: %s" %
entreprise_name(pongData.entreprise_number))
if len(features) != 0:
print("\tFeatures: %s" % ",".join(features))
except socket.timeout:
pass
Results
We can discover devices on the local network by using its broadcast address:
$ ./rmcp-discover 192.0.2.255
::ffff:192.0.2.56
Entreprise: Intel
Features: dash
::ffff:152.81.7.32
Entreprise: Intel
Features: dash
::ffff:192.0.2.228
Entreprise: Intel
Features: dash
::ffff:192.0.2.230
Entreprise: Intel
Features: dash
::ffff:152.81.3.90
Entreprise: Intel
Features: dash
::ffff:192.0.2.170
Entreprise: Intel
Features: dash
::ffff:152.81.8.105
Entreprise: Intel
Features: dash
::ffff:152.81.5.123
Entreprise: Intel
Features: dash
::ffff:192.0.2.235
Entreprise: Intel
Features: dash
::ffff:192.0.2.29
Entreprise: Intel
Features: dash
::ffff:192.0.2.233
Entreprise: Intel
Features: dash
::ffff:192.0.2.171
Entreprise: Intel
Features: dash
They advertise Intel and DASH: those are probably AMT devices.
We can use the same script to discover IPMI nodes as well:
$ ./rmcp-discover 198.51.100.42
::ffff:198.51.100.42
Entreprise: Alerting Specifications Forum
Features: ipmi,asf
We cannot (reliably) use this to detect AMT on the local machine. The reason is that the messages are sent to the ME when they arrive on the hardware Ethernet adapter. Messages emitted by the localhost to its own IP address are handled internally by the OS: they are received by the Ethernet adapter and thus do not reach the ME. In order to communicate to its own ME, the OS needs to communicate using the MEI instead of using IP. The Intel LMS (Local Management Service) can be installed to reach the local ME over IP: as far as I know, it listens on the suitable localhost TCP and UDP ports and forwards the request to the ME using the MEI.
References
Technical documentation
- DASH
- ASF
- IPMI
- Intel Side-band technology
- AMT
- Intel vPro Technology Reference Guide
- Netbook and Desktop PCs with Intel vPro Technology in Small-and Medium-Size Business Environments
Documentation
Articles
- Joanna Rutkowska: Towards (reasonably) trustworthy x86 laptops
- Intel x86s hide another CPU that can take over your machine (you can't audit it)
- FSF: "Active Management Technology": The obscure remote control in some Intel hardware
- Invisible Things Lab to present two new technical presentations disclosing system-level vulnerabilities affecting modern PC hardware at its core
CVE-2017-5689
Interesting references following the INTEL-SA-00075/CVE-2017-5689 vulerability:
- CVE-2017-5689 detector
- nmap script for CVE-2017-5689
mei-amt-check
: « Check whether AMT is enabled and provisioned under Linux »- Intel's remote AMT vulnerability
- Silent Bob is Silent
- Intel's Management Engine is a security hazard, and users need a way to disable it