j0h

ReadFile_PLC.py

j0h
Jun 8th, 2026
161
0
Never
5
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.40 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. import argparse
  3. import time
  4. import serial
  5.  
  6. PORT = "/dev/ttyACM0"
  7. BAUD = 9600
  8.  
  9. DEFAULT_FILE = "/FCDATA01/DATALOG/1-secLog/LOG_260608.CSV"
  10. DEFAULT_OUT = "LOG_260608.CSV"
  11. BLOCK_HEX = "5C0"  # 0x5C0 = 1472 bytes
  12.  
  13.  
  14. def bcc_xor(data: bytes) -> bytes:
  15.     b = 0
  16.     for x in data:
  17.         b ^= x
  18.     return f"{b:02X}".encode("ascii")
  19.  
  20.  
  21. def frame_bcc(body: bytes) -> bytes:
  22.     msg = b"\x05" + body
  23.     return msg + bcc_xor(msg) + b"\r"
  24.  
  25.  
  26. def frame_no_bcc(body: bytes) -> bytes:
  27.     return b"\x05" + body + b"\r"
  28.  
  29.  
  30. def recv_until_cr(ser, timeout=3.0, limit=4096) -> bytes:
  31.     end = time.time() + timeout
  32.     buf = bytearray()
  33.  
  34.     while time.time() < end and len(buf) < limit:
  35.         b = ser.read(1)
  36.         if not b:
  37.             continue
  38.         buf += b
  39.         if b == b"\r":
  40.             break
  41.  
  42.     return bytes(buf)
  43.  
  44.  
  45. def printable(data: bytes) -> str:
  46.     out = []
  47.     for b in data:
  48.         if 32 <= b <= 126:
  49.             out.append(chr(b))
  50.         elif b == 0x00:
  51.             out.append("<NUL>")
  52.         elif b == 0x05:
  53.             out.append("<ENQ>")
  54.         elif b == 0x06:
  55.             out.append("<ACK>")
  56.         elif b == 0x15:
  57.             out.append("<NAK>")
  58.         elif b == 0x0D:
  59.             out.append("<CR>")
  60.         else:
  61.             out.append(f"<{b:02X}>")
  62.     return "".join(out)
  63.  
  64.  
  65. def open_file_body(path: str, block_hex: str = BLOCK_HEX) -> bytes:
  66.     path = "/" + path.strip("/")
  67.     p = path.encode("ascii") + b"\x00"
  68.     length = f"{len(p):03X}".encode("ascii")
  69.     return b"FF1Fu" + length + p + block_hex.encode("ascii")
  70.  
  71.  
  72. '''
  73. Something here isnt quite right.
  74. Extraneous 0's in line 67 time stamp,
  75. but everything ive tried is worse.
  76. its really close.
  77. '''
  78.  
  79. def strip_download_payload(rx: bytes) -> bytes:
  80.     """
  81.    Expected reply:
  82.      ACK FF 1 <block_len_hex_3> <data> BCC CR
  83.  
  84.    Header:
  85.      06 FF 1 5 C 0 ...
  86.      bytes 0..5 are ACK/device/continuation/block length.
  87.    Trailer:
  88.      last 3 bytes are BCC_hi BCC_lo CR.
  89.    """
  90.     if not rx or rx[0] != 0x06:
  91.         return b""
  92.  
  93.     if len(rx) < 9:
  94.         return b""
  95.  
  96.     return rx[6:-3]
  97.  
  98.  
  99. def get_reply_block_len(rx: bytes):
  100.     if not rx or rx[0] != 0x06 or len(rx) < 6:
  101.         return None
  102.  
  103.     try:
  104.         return int(rx[3:6].decode("ascii"), 16)
  105.     except Exception:
  106.         return None
  107.  
  108.  
  109. def download_file(ser, plc_path: str, out_path: str, expected_size=None, debug=False):
  110.     ser.reset_input_buffer()
  111.  
  112.     open_pkt = frame_bcc(open_file_body(plc_path))
  113.  
  114.     if debug:
  115.         print("TX open hex:", open_pkt.hex(" ").upper())
  116.         print("TX open txt:", printable(open_pkt))
  117.  
  118.     ser.write(open_pkt)
  119.     ser.flush()
  120.  
  121.     rx = recv_until_cr(ser, timeout=3.0, limit=8192)
  122.  
  123.     if debug:
  124.         print("RX open hex:", rx.hex(" ").upper() if rx else "")
  125.         print("RX open txt:", printable(rx) if rx else "")
  126.  
  127.     if not rx or rx[0] != 0x06:
  128.         raise RuntimeError("Open file did not ACK")
  129.  
  130.     data = bytearray()
  131.     first_payload = strip_download_payload(rx)
  132.  
  133.     if first_payload:
  134.         data += first_payload
  135.         print(f"received first block: {len(first_payload)} bytes")
  136.  
  137.     block_count = 1 if first_payload else 0
  138.  
  139.     while True:
  140.         if expected_size is not None and len(data) >= expected_size:
  141.             data = data[:expected_size]
  142.             break
  143.  
  144.         pkt = frame_no_bcc(b"FF1FU27")
  145.  
  146.         if debug:
  147.             print()
  148.             print("TX next hex:", pkt.hex(" ").upper())
  149.             print("TX next txt:", printable(pkt))
  150.  
  151.         ser.write(pkt)
  152.         ser.flush()
  153.  
  154.         rx = recv_until_cr(ser, timeout=3.0, limit=8192)
  155.  
  156.         if debug:
  157.             print("RX next hex:", rx.hex(" ").upper() if rx else "")
  158.             print("RX next txt:", printable(rx[:80]) if rx else "")
  159.  
  160.         if not rx:
  161.             print("no more reply")
  162.             break
  163.  
  164.         if rx[0] == 0x15:
  165.             print("NAK received")
  166.             break
  167.  
  168.         if rx[0] != 0x06:
  169.             print("unexpected reply:", printable(rx))
  170.             break
  171.  
  172.         payload = strip_download_payload(rx)
  173.         blen = get_reply_block_len(rx)
  174.  
  175.         if not payload:
  176.             print("empty payload")
  177.             break
  178.  
  179.         data += payload
  180.         block_count += 1
  181.  
  182.         print(f"block {block_count}: reply_len={blen} payload={len(payload)} total={len(data)}")
  183.  
  184.         # likely final short block
  185.         if expected_size is None and blen is not None and blen < int(BLOCK_HEX, 16):
  186.             break
  187.  
  188.     # Try close/finalize. Not proven, but likely harmless no-BCC continuation style.
  189.     try:
  190.         ser.write(frame_no_bcc(b"FF0FU28"))
  191.         ser.flush()
  192.         recv_until_cr(ser, timeout=0.5)
  193.     except Exception:
  194.         pass
  195.  
  196.     with open(out_path, "wb") as f:
  197.         f.write(data)
  198.  
  199.     print()
  200.     print(f"saved {len(data)} bytes to {out_path}")
  201.  
  202.  
  203. def main():
  204.     ap = argparse.ArgumentParser()
  205.     ap.add_argument("port", nargs="?", default=PORT)
  206.     ap.add_argument("--baud", type=int, default=BAUD)
  207.     ap.add_argument("--file", default=DEFAULT_FILE)
  208.     ap.add_argument("-o", "--out", default=DEFAULT_OUT)
  209.     ap.add_argument("--size", type=int, default=218617)
  210.     ap.add_argument("--debug", action="store_true")
  211.     args = ap.parse_args()
  212.  
  213.     with serial.Serial(
  214.         args.port,
  215.         args.baud,
  216.         bytesize=serial.SEVENBITS,
  217.         parity=serial.PARITY_EVEN,
  218.         stopbits=serial.STOPBITS_ONE,
  219.         timeout=0.05,
  220.         write_timeout=1.0,
  221.     ) as ser:
  222.         time.sleep(0.5)
  223.         download_file(
  224.             ser,
  225.             plc_path=args.file,
  226.             out_path=args.out,
  227.             expected_size=args.size,
  228.             debug=args.debug,
  229.         )
  230.  
  231.  
  232. if __name__ == "__main__":
  233.     main()
  234. '''  
  235. Example:
  236. $./rd_file.py /dev/ttyACM0 --file /FCDATA01/DATALOG/1-secLog/20260406/20260406_00.CSV -o bleh.CSV
  237.  
  238. ## Might not init properly, or maybe its timing...
  239. $ ./rd_file.py
  240. Traceback (most recent call last):
  241.  File "/home/l/Desktop/plcSD/./rd_file.py", line 233, in <module>
  242.    main()
  243.  File "/home/l/Desktop/plcSD/./rd_file.py", line 223, in main
  244.    download_file(
  245.  File "/home/l/Desktop/plcSD/./rd_file.py", line 128, in download_file
  246.    raise RuntimeError("Open file did not ACK")
  247. RuntimeError: Open file did not ACK
  248.  
  249. Try again basicly.
  250. reads files (upload to PC, aka download by any other definition)
  251.  
  252. '''
  253.  
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • Darvimoh
    35 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • Wynsalut
    12 days
    # CSS 0.45 KB | 0 0
    1. ⭐ Changelly Profit Exploit Documentation ⭐
    2.  
    3. https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=ejmjjpfaod
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment