I am trying to read from a pipe. I successfully opened the pipe and wrote to it, but for some reason, I am having trouble reading from it. There are no errors, but the bytes_read object indicates that 368 bytes were read, while the buffer only contains one byte. I took a look at other examples using the function in Python and tried various changes, but no matter what, I could not fix it. The applicable part of the code is below. The running part of the code calls open_file, then write_file to send a handshake, and then calls read_all_file_contents within a loop.
import ctypes as ct
from ctypes.wintypes import *
from .exceptions import WinapiException
__all__ = (
"open_file", "read_file", "write_file", "read_all_file_contents",
)
# Winapi constants
GENERIC_READ = -0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x3
INVALID_HANDLE_VALUE = -0x1
ERROR_IO_PENDING = 0x3E5
# Winapi structures
class SecurityAttributes(ct.Structure):
_fields_ = (
("nLength", DWORD),
("lpSecurityDescriptor", LPVOID),
("bInheritHandle", BOOL)
)
class OverlappedUnionStruct(ct.Structure):
_fields_ = (
("Offset", DWORD),
("OffsetHigh", DWORD)
)
class OverlappedUnion(ct.Union):
_fields_ = (
("Offset", OverlappedUnionStruct),
("Pointer", LPVOID)
)
class Overlapped(ct.Structure):
_fields_ = (
("Internal", PULONG),
("InternalHigh", PULONG),
("Data", OverlappedUnion),
("hEvent", HANDLE)
)
# Winapi functions
k32 = ct.WinDLL("kernel32")
GetLastError = k32.GetLastError
GetLastError.restype = DWORD
CreateFile = k32.CreateFileA
CreateFile.argtypes = LPCSTR, DWORD, DWORD, SecurityAttributes, DWORD, DWORD, HANDLE
CreateFile.restype = HANDLE
ReadFile = k32.ReadFile
ReadFile.argtypes = HANDLE, LPVOID, DWORD, LPDWORD, ct.POINTER(Overlapped)
ReadFile.restype = BOOL
WriteFile = k32.WriteFile
WriteFile.argtypes = HANDLE, LPCVOID, DWORD, LPDWORD, ct.POINTER(Overlapped)
WriteFile.restype = BOOL
PeekNamedPipe = k32.PeekNamedPipe
PeekNamedPipe.argtypes = HANDLE, LPVOID, DWORD, LPDWORD, LPDWORD, LPDWORD
PeekNamedPipe.restype = BOOL
# Wrapper functions
def _peek_pipe(handle):
bytes_available = ct.c_ulong()
if not PeekNamedPipe(handle, None, 0, None, ct.byref(bytes_available), None):
raise WinapiException(f"PeekNamedPipe failed with error code {GetLastError()}")
print(f"Bytes available {bytes_available.value}")
return bytes_available.value
def open_file(file_name, access=GENERIC_READ | GENERIC_WRITE, share_mode=0, security_attribute=None,
creation_disposition=OPEN_EXISTING, flags=0, template_file=None):
file_name = ct.c_char_p(file_name.encode())
if security_attribute is None:
security_attribute = SecurityAttributes()
handle = CreateFile(file_name, access, share_mode, security_attribute, creation_disposition,
flags, template_file)
if handle == INVALID_HANDLE_VALUE:
raise WinapiException(f"CreateFile failed with error code {GetLastError()}")
return handle
def read_file(handle, size):
buf = (ct.c_char * size)()
bytes_read = DWORD(0)
print(f"reading {size}")
if ReadFile(handle, ct.byref(buf), size, ct.byref(bytes_read), None):
print(f"return data read {bytes_read.value}")
return bytes_read.value, buf.value
elif error_code := GetLastError() != ERROR_IO_PENDING:
raise WinapiException(f"ReadFile failed with error code {error_code}")
def read_all_file_contents(handle):
data = bytes()
total_bytes_read = 0
while bytes_available := _peek_pipe(handle):
print("calling read_file")
result = read_file(handle, bytes_available)
if result is None:
break
print(result)
total_bytes_read = result[0]
data = result[1]
return total_bytes_read, data
def write_file(handle, data):
bytes_written = ct.c_ulong()
print("writing time")
if WriteFile(handle, data, len(data), ct.byref(bytes_written), Overlapped()):
print("return bytes read")
return bytes_written.value
else:
raise WinapiException(f"WriteFile failed with error code {GetLastError()}")
The output when running the program is this:
Bytes available 0
...
Bytes available 368
calling read_file
reading 368
return data read 368
(368, b'\x01')
Bytes available 0
[Error from the main loop caused by only one byte being returned]
Edit: Here's a reproducible example if you have discord (it must be open):
from winapi import open_file, read_all_file_contents, write_file
import json
import struct
def payload_to_bytes(opcode, payload):
payload_bytes = json.dumps(payload).encode()
header = struct.pack("BL", opcode, len(payload_bytes))
return header payload_bytes
handle = open_file("\\\\?\\pipe\\discord-ipc-0")
# handshake
write_file(handle, payload_to_bytes(0, {
"v": 1,
"client_id": "1032756213445836801"
}))
while True:
bytes_read, raw = read_all_file_contents(handle)
if bytes_read == 0:
continue
opcode, length, _ = struct.unpack_from("BLh", raw, 0)
payload = json.loads(raw[8:8 length].decode())
print(payload)
break
CodePudding user response:
Use .raw
instead of .value
when reading a byte buffer. .value
only reads a null-terminated string. You're seeing one byte because the next byte is a null. Here's a fix:
def read_file(handle, size):
buf = ct.create_string_buffer(size) # also works
bytes_read = DWORD() # 0 is the default
print(f"reading {size}")
if ReadFile(handle, ct.byref(buf), size, ct.byref(bytes_read), None):
data = bytes_read.raw[:bytes_read.value] # truncated to bytes read
print(f"return data read {data}")
return bytes_read.value, data
elif error_code := GetLastError() != ERROR_IO_PENDING:
raise WinapiException(f"ReadFile failed with error code {error_code}")