Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bb19365
Basic refactoring to be more Pythonic.
abaire Sep 7, 2021
2dbbdcf
Factors out connection code.
abaire Sep 7, 2021
11b79c9
Removes unnecessary readLoop method.
abaire Sep 7, 2021
25d0b72
Handles fifo parameter the same way as qemu (expecting .in and .out)
abaire Sep 8, 2021
70ccdc5
Fixes pylint warnings.
abaire Sep 8, 2021
686e25e
Adds parameter to create fifos.
abaire Sep 8, 2021
7c13de3
Removes unnecessary _writeDev method.
abaire Sep 8, 2021
9a7770c
Gives a name to the packet trailer byte.
abaire Sep 8, 2021
a81c5ce
Adds timing info.
abaire Sep 8, 2021
0439edd
Adds basic KD sniffer.
abaire Sep 9, 2021
b68a13a
Adds graceful handling of desync in sniffer.
abaire Sep 9, 2021
d6d9676
Adds more symbolic constants.
abaire Sep 9, 2021
eea1e77
Adds more verbose version logging.
abaire Sep 9, 2021
455c725
Renames unpack to make it distinct from struct.unpack.
abaire Sep 9, 2021
9711eae
Adds TCP support.
abaire Sep 9, 2021
ece6cc6
Adds server mode.
abaire Sep 9, 2021
6cedc4c
Factors packet reading into DebugConnection.
abaire Sep 9, 2021
2fdcfc2
Removes duplication between sniffer and debugger.
abaire Sep 9, 2021
13904a7
Adds detailed debug logging.
abaire Sep 9, 2021
0249732
Additional logging for loadsymbols.
abaire Sep 9, 2021
360b6bc
Unifies logging.
abaire Sep 9, 2021
b4055a1
Implements sync behavior.
abaire Sep 9, 2021
3cdc8ce
Implements correct packet numbering.
abaire Sep 9, 2021
21939c4
Improves PACKET_TYPE_KD_DEBUG_IO handling.
abaire Sep 9, 2021
25bd5e6
Pylint fixes.
abaire Sep 9, 2021
e4c0ff1
Fixes post-reset packet id handling.
abaire Sep 9, 2021
e86ade7
Pylint fixes.
abaire Sep 9, 2021
2d736b7
Fixes hexasc behavior.
abaire Sep 10, 2021
f35a779
Adds handling of duplicate packets.
abaire Sep 11, 2021
7bd9ac0
Reduces warning output.
abaire Sep 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
.vscode/

*.pyc
17 changes: 17 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
black = "*"
pylint = "*"

[dev-packages]
black = "*"

[requires]
python_version = "3.9"

[pipenv]
allow_prereleases = true
295 changes: 295 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

200 changes: 200 additions & 0 deletions constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
"""Constants used by the Kernel Debug protocol."""

# Copyright (C) 2007 SecureWorks, Inc.
# Copyright (C) 2013 espes
# Copyright (C) 2017 Jannik Vogel
#
# This program is free software subject to the terms of the GNU General
# Public License. You can use, copy, redistribute and/or modify the
# program under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version. You should have received a copy of
# the GNU General Public License along with this program. If not,
# please see http://www.gnu.org/licenses/ for a copy of the GNU General
# Public License.
#
# The program is subject to a disclaimer of warranty and a limitation of
# liability, as disclosed below.
#
# Disclaimer of Warranty.
#
# THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
# APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
# HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
# WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU
# ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, CORRECTION OR
# RECOVERY FROM DATA LOSS OR DATA ERRORS.
#
# Limitation of Liability.
#
# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
# WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
# CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
# ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
# NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES
# SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
# WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

# Constants are allowed to be camel case to match definitions in ReactOS source.
# pylint: disable = invalid-name

INITIAL_PACKET_ID = 0x80800000
SYNC_PACKET_ID = 0x00000800

PACKET_LEADER = 0x30303030
CONTROL_PACKET_LEADER = 0x69696969
PACKET_TRAILER = 0xAA

PACKET_TYPE_UNUSED = 0
PACKET_TYPE_KD_STATE_CHANGE32 = 1
PACKET_TYPE_KD_STATE_MANIPULATE = 2
PACKET_TYPE_KD_DEBUG_IO = 3
PACKET_TYPE_KD_ACKNOWLEDGE = 4
PACKET_TYPE_KD_RESEND = 5
PACKET_TYPE_KD_RESET = 6
PACKET_TYPE_KD_STATE_CHANGE64 = 7
PACKET_TYPE_MAX = 8

PACKET_TYPE_TABLE = {
PACKET_TYPE_UNUSED: "PACKET_TYPE_UNUSED",
PACKET_TYPE_KD_STATE_CHANGE32: "PACKET_TYPE_KD_STATE_CHANGE32",
PACKET_TYPE_KD_STATE_MANIPULATE: "PACKET_TYPE_KD_STATE_MANIPULATE",
PACKET_TYPE_KD_DEBUG_IO: "PACKET_TYPE_KD_DEBUG_IO",
PACKET_TYPE_KD_ACKNOWLEDGE: "PACKET_TYPE_KD_ACKNOWLEDGE",
PACKET_TYPE_KD_RESEND: "PACKET_TYPE_KD_RESEND",
PACKET_TYPE_KD_RESET: "PACKET_TYPE_KD_RESET",
PACKET_TYPE_KD_STATE_CHANGE64: "PACKET_TYPE_KD_STATE_CHANGE64",
PACKET_TYPE_MAX: "PACKET_TYPE_MAX",
}

# PACKET_TYPE_KD_DEBUG_IO apis
# DBGKD_DEBUG_IO
DbgKdPrintStringApi = 0x00003230
DbgKdGetStringApi = 0x00003231

# PACKET_TYPE_KD_STATE_CHANGE states
# X86_NT5_DBGKD_WAIT_STATE_CHANGE64
DbgKdExceptionStateChange = 0x00003030
DbgKdLoadSymbolsStateChange = 0x00003031

STATE_CHANGE_TABLE = {
DbgKdExceptionStateChange: "DbgKdExceptionStateChange",
DbgKdLoadSymbolsStateChange: "DbgKdLoadSymbolsStateChange",
}

# PACKET_TYPE_KD_STATE_MANIPULATE api numbers
# DBGKD_MANIPULATE_STATE64
DbgKdReadVirtualMemoryApi = 0x00003130
DbgKdWriteVirtualMemoryApi = 0x00003131
DbgKdGetContextApi = 0x00003132
DbgKdSetContextApi = 0x00003133
DbgKdWriteBreakPointApi = 0x00003134
DbgKdRestoreBreakPointApi = 0x00003135
DbgKdContinueApi = 0x00003136
DbgKdReadControlSpaceApi = 0x00003137
DbgKdWriteControlSpaceApi = 0x00003138
DbgKdReadIoSpaceApi = 0x00003139
DbgKdWriteIoSpaceApi = 0x0000313A
DbgKdRebootApi = 0x0000313B
DbgKdContinueApi2 = 0x0000313C
DbgKdReadPhysicalMemoryApi = 0x0000313D
DbgKdWritePhysicalMemoryApi = 0x0000313E
DbgKdSetSpecialCallApi = 0x00003140
DbgKdClearSpecialCallsApi = 0x00003141
DbgKdSetInternalBreakPointApi = 0x00003142
DbgKdGetInternalBreakPointApi = 0x00003143
DbgKdReadIoSpaceExtendedApi = 0x00003144
DbgKdWriteIoSpaceExtendedApi = 0x00003145
DbgKdGetVersionApi = 0x00003146
DbgKdWriteBreakPointExApi = 0x00003147
DbgKdRestoreBreakPointExApi = 0x00003148
DbgKdCauseBugCheckApi = 0x00003149
DbgKdSwitchProcessor = 0x00003150
DbgKdPageInApi = 0x00003151
DbgKdReadMachineSpecificRegister = 0x00003152
DbgKdWriteMachineSpecificRegister = 0x00003153
DbgKdSearchMemoryApi = 0x00003156
DbgKdGetBusDataApi = 0x00003157
DbgKdSetBusDataApi = 0x00003158
DbgKdCheckLowMemoryApi = 0x00003159

STATE_MANIPULATE_TABLE = {
DbgKdReadVirtualMemoryApi: "DbgKdReadVirtualMemoryApi",
DbgKdWriteVirtualMemoryApi: "DbgKdWriteVirtualMemoryApi",
DbgKdGetContextApi: "DbgKdGetContextApi",
DbgKdSetContextApi: "DbgKdSetContextApi",
DbgKdWriteBreakPointApi: "DbgKdWriteBreakPointApi",
DbgKdRestoreBreakPointApi: "DbgKdRestoreBreakPointApi",
DbgKdContinueApi: "DbgKdContinueApi",
DbgKdReadControlSpaceApi: "DbgKdReadControlSpaceApi",
DbgKdWriteControlSpaceApi: "DbgKdWriteControlSpaceApi",
DbgKdReadIoSpaceApi: "DbgKdReadIoSpaceApi",
DbgKdWriteIoSpaceApi: "DbgKdWriteIoSpaceApi",
DbgKdRebootApi: "DbgKdRebootApi",
DbgKdContinueApi2: "DbgKdContinueApi2",
DbgKdReadPhysicalMemoryApi: "DbgKdReadPhysicalMemoryApi",
DbgKdWritePhysicalMemoryApi: "DbgKdWritePhysicalMemoryApi",
DbgKdSetSpecialCallApi: "DbgKdSetSpecialCallApi",
DbgKdClearSpecialCallsApi: "DbgKdClearSpecialCallsApi",
DbgKdSetInternalBreakPointApi: "DbgKdSetInternalBreakPointApi",
DbgKdGetInternalBreakPointApi: "DbgKdGetInternalBreakPointApi",
DbgKdReadIoSpaceExtendedApi: "DbgKdReadIoSpaceExtendedApi",
DbgKdWriteIoSpaceExtendedApi: "DbgKdWriteIoSpaceExtendedApi",
DbgKdGetVersionApi: "DbgKdGetVersionApi",
DbgKdWriteBreakPointExApi: "DbgKdWriteBreakPointExApi",
DbgKdRestoreBreakPointExApi: "DbgKdRestoreBreakPointExApi",
DbgKdCauseBugCheckApi: "DbgKdCauseBugCheckApi",
DbgKdSwitchProcessor: "DbgKdSwitchProcessor",
DbgKdPageInApi: "DbgKdPageInApi",
DbgKdReadMachineSpecificRegister: "DbgKdReadMachineSpecificRegister",
DbgKdWriteMachineSpecificRegister: "DbgKdWriteMachineSpecificRegister",
DbgKdSearchMemoryApi: "DbgKdSearchMemoryApi",
DbgKdGetBusDataApi: "DbgKdGetBusDataApi",
DbgKdSetBusDataApi: "DbgKdSetBusDataApi",
DbgKdCheckLowMemoryApi: "DbgKdCheckLowMemoryApi",
}


HRESULT_STATUS_SUCCESS = 0x00000000
HRESULT_STATUS_PENDING = 0x00000103
HRESULT_STATUS_UNSUCCESSFUL = 0xC0000001
HRESULT_DBG_EXCEPTION_HANDLED = 0x00010001
HRESULT_DBG_CONTINUE = 0x00010002
HRESULT_DBG_REPLY_LATER = 0x40010001
HRESULT_DBG_UNABLE_TO_PROVIDE_HANDLE = 0x40010002
HRESULT_DBG_TERMINATE_THREAD = 0x40010003
HRESULT_DBG_TERMINATE_PROCESS = 0x40010004
HRESULT_DBG_CONTROL_C = 0x40010005
HRESULT_DBG_PRINTEXCEPTION_C = 0x40010006
HRESULT_DBG_RIPEXCEPTION = 0x40010007
HRESULT_DBG_CONTROL_BREAK = 0x40010008
HRESULT_DBG_COMMAND_EXCEPTION = 0x40010009
HRESULT_DBG_EXCEPTION_NOT_HANDLED = 0x80010001
HRESULT_DBG_NO_STATE_CHANGE = 0xC0010001
HRESULT_DBG_APP_NOT_IDLE = 0xC0010002


STATE_CHANGE_EXCEPTIONS = {
0xC0000005: "EXCEPTION_ACCESS_VIOLATION",
0xC000008C: "EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
0x80000003: "EXCEPTION_BREAKPOINT",
0x80000002: "EXCEPTION_DATATYPE_MISALIGNMENT",
0xC000008D: "EXCEPTION_FLT_DENORMAL_OPERAND",
0xC000008E: "EXCEPTION_FLT_DIVIDE_BY_ZERO",
0xC000008F: "EXCEPTION_FLT_INEXACT_RESULT",
0xC0000030: "EXCEPTION_FLT_INVALID_OPERATION",
0xC0000091: "EXCEPTION_FLT_OVERFLOW",
0xC0000032: "EXCEPTION_FLT_STACK_CHECK",
0xC0000033: "EXCEPTION_FLT_UNDERFLOW",
0x80000001: "EXCEPTION_GUARD_PAGE",
0xC000001D: "EXCEPTION_ILLEGAL_INSTRUCTION",
0xC0000006: "EXCEPTION_IN_PAGE_ERROR",
0xC0000094: "EXCEPTION_INT_DIVIDE_BY_ZERO",
0xC0000035: "EXCEPTION_INT_OVERFLOW",
0xC00000FD: "EXCEPTION_STACK_OVERFLOW",
}
168 changes: 168 additions & 0 deletions debug_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""Abstract connection interface for kernel debugging."""

import logging
import os
import pathlib
import socket
import struct

from constants import PACKET_LEADER, CONTROL_PACKET_LEADER, PACKET_TRAILER
import kd_packet


class DebugConnection:
"""Models a connection to the target device (e.g. FIFO, socket)."""

def __init__(self, endpoint):
self.endpoint = endpoint
self._connection_write = None
self._connection_read = None

self._recv = None
self._send = None

def connect(self):
"""Connects to the debug endpoint."""
if isinstance(self.endpoint, tuple):
self._connect_socket(self.endpoint)
else:
self._connect_fifo(self.endpoint)

def handle_socket(self, connection):
"""Utilizes the given socket as the transport layer."""
self._connection_read = connection
self._connection_write = self._connection_read
self._recv = self._recv_socket
self._send = self._send_socket

def _connect_socket(self, host_port):
logging.info("Connecting to %s:%d", host_port[0], host_port[1])
self.handle_socket(socket.socket())
self._connection_read.connect(host_port)

def _connect_fifo(self, path):
"""Connects via a pair of .in and .out named pipes."""

logging.info("Connecting to FIFO at '%s'", path)

def require_fifo_exists(fifo_path):
path_object = pathlib.Path(fifo_path)
if not path_object.exists():
raise Exception(
"Qemu requires two pipes with a common base name and '.in' and "
"'.out' suffixes. E.g., '/tmp/foo.in' and '/tmp/foo.out'."
)

if not path_object.is_fifo():
raise Exception(f"'{path_object.name}' is not a fifo.")

# The naming is from the perspective of qemu, so "in" is written to and "out" is read from.
write_fifo = f"{path}.in"
read_fifo = f"{path}.out"

require_fifo_exists(write_fifo)
require_fifo_exists(read_fifo)

flags = 0
if os.name == "nt":
flags |= os.O_BINARY # pylint: disable = no-member

self._connection_write = os.open(write_fifo, flags | os.O_WRONLY)
self._connection_read = os.open(read_fifo, flags | os.O_RDONLY)

self._recv = self._recv_fifo
self._send = self._send_fifo

def read_packet(self) -> (kd_packet.KDPacket, bytearray):
"""Reads a single KD packet from the connection."""
packet_leader, discarded_bytes = self._read_packet_leader()

buf = self.read(12)
(packet_type, data_size, packet_id, expected_checksum) = struct.unpack(
"HHII", buf
)

if data_size:
payload = self.read(data_size)
else:
payload = bytearray([])

# send ack if it's a non-control packet
if packet_leader == PACKET_LEADER:
# packet trailer
# self._log("Reading trailer...")
trail = self.read(1)
# self._log("Trailer: %x", trail[0])
if trail[0] != PACKET_TRAILER:
raise Exception("Invalid packet trailer 0x%x" % trail[0])

return (
kd_packet.KDPacket(
packet_leader, packet_type, packet_id, expected_checksum, payload
),
discarded_bytes,
)

def read(self, wanted):
"""Reads exactly `wanted` bytes from the connection, blocking as necessary."""
total = 0
outbuf = bytearray([])
while total < wanted:
buf = self._recv(wanted - total)
count = len(buf)
if count:
total += count
outbuf += buf
else:
raise ConnectionResetError("Failed to read from connection.")

return outbuf

def write(self, buffer):
"""Writes `buffer` to the connection, blocking as necessary."""

while len(buffer):
written = self._send(buffer)
buffer = buffer[written:]

def _read_packet_leader(self) -> (int, bytearray):
"""Reads from the connection until a valid packet leader is found.

Returns (packet_leader, discarded_bytes):
- packet_leader: The 4 byte leader that was read
- discarded_bytes: An array of bytes that were read and discarded
before the leader was found.
"""
buf = self.read(4)
packet_leader = struct.unpack("I", buf)[0]

discarded_bytes = bytearray([])
while packet_leader not in (PACKET_LEADER, CONTROL_PACKET_LEADER):
discarded_bytes.append(buf[0])
buf = buf[1:] + self.read(1)
packet_leader = struct.unpack("I", buf)[0]

return packet_leader, discarded_bytes

def _recv_fifo(self, max_bytes):
"""Receives up to `max_bytes` bytes from the connection."""
bytes_read = os.read(self._connection_read, max_bytes)
if len(bytes_read) == 0:
print("Failed to read from connection")

return bytes_read

def _send_fifo(self, buf):
"""Sends some or all of the given buf, returns the number of bytes actually sent."""
return os.write(self._connection_write, buf)

def _recv_socket(self, max_bytes):
"""Receives up to `max_bytes` bytes from the connection."""
bytes_read = self._connection_read.recv(max_bytes)
if len(bytes_read) == 0:
print("Failed to read from connection")
return bytes_read

def _send_socket(self, buf):
"""Sends some or all of the given buf, returns the number of bytes actually sent."""
return self._connection_write.send(buf)
Loading