From bb19365573322286f0e8c7c0e931988b43df3c7a Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 09:45:47 -0700 Subject: [PATCH 01/30] Basic refactoring to be more Pythonic. --- .gitignore | 4 + Pipfile | 16 + Pipfile.lock | 222 ++++++ windpl.py | 1732 ++++++++++++++++++++++++----------------------- windpl_extra.py | 1121 +++++++++++++++--------------- windpl_loop.py | 462 ++++++------- 6 files changed, 1909 insertions(+), 1648 deletions(-) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9399d86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +.vscode/ + +*.pyc diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..21b3e30 --- /dev/null +++ b/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +black = "*" + +[dev-packages] +black = "*" + +[requires] +python_version = "3.9" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..cd9949b --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,222 @@ +{ + "_meta": { + "hash": { + "sha256": "93b7444c5e175150f10109271ecf7fc99eed16aeab9597d95d386c9966cc2196" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "black": { + "hashes": [ + "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7", + "sha256:570608d28aa3af1792b98c4a337dbac6367877b47b12b88ab42095cfc1a627c2" + ], + "index": "pypi", + "version": "==21.8b0" + }, + "click": { + "hashes": [ + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "platformdirs": { + "hashes": [ + "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f", + "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.0" + }, + "regex": { + "hashes": [ + "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468", + "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354", + "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308", + "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d", + "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc", + "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8", + "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797", + "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2", + "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13", + "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d", + "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a", + "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0", + "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73", + "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1", + "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed", + "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a", + "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b", + "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f", + "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256", + "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb", + "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2", + "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983", + "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb", + "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645", + "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8", + "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a", + "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906", + "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f", + "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c", + "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892", + "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0", + "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e", + "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e", + "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed", + "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c", + "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374", + "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd", + "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791", + "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a", + "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1", + "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759" + ], + "version": "==2021.8.28" + }, + "tomli": { + "hashes": [ + "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", + "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + ], + "version": "==3.10.0.2" + } + }, + "develop": { + "black": { + "hashes": [ + "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7", + "sha256:570608d28aa3af1792b98c4a337dbac6367877b47b12b88ab42095cfc1a627c2" + ], + "index": "pypi", + "version": "==21.8b0" + }, + "click": { + "hashes": [ + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "platformdirs": { + "hashes": [ + "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f", + "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.0" + }, + "regex": { + "hashes": [ + "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468", + "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354", + "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308", + "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d", + "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc", + "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8", + "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797", + "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2", + "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13", + "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d", + "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a", + "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0", + "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73", + "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1", + "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed", + "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a", + "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b", + "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f", + "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256", + "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb", + "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2", + "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983", + "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb", + "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645", + "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8", + "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a", + "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906", + "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f", + "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c", + "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892", + "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0", + "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e", + "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e", + "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed", + "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c", + "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374", + "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd", + "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791", + "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a", + "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1", + "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759" + ], + "version": "==2021.8.28" + }, + "tomli": { + "hashes": [ + "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", + "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + ], + "version": "==3.10.0.2" + } + } +} diff --git a/windpl.py b/windpl.py index b483713..96d196e 100755 --- a/windpl.py +++ b/windpl.py @@ -5,899 +5,921 @@ # 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 +# 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 +# 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 +# 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 +# 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. -#use IO::Select -#use IO::Socket -#use File::stat -#use Fcntl ':mode' -#use strict +# use IO::Select +# use IO::Socket +# use File::stat +# use Fcntl ':mode' +# use strict +import argparse import socket import sys import struct +from struct import pack -dev = sys.argv[1] -timeout = 10 # max time to wait on packet -running = 1 - -kernelcontext = {} -kernelcontext['peb'] = 0 -kernelcontext['pid'] = 0 -kernelcontext['eprocess'] = 0 -kernelcontext['dtb'] = 0 - -processcontext = {} -processcontext['peb'] = 0 -processcontext['pid'] = 0 -processcontext['eprocess'] = 0 -processcontext['dtb'] = 0 - -pcontext = kernelcontext - -#version -#kernelbase - -nextpid = 1 - -breakpoints = {} - -curbp = 1 -controlspace = 0 -controlspacesent = False - -#isserial -#serial -#client - -if False: - ds = stat(dev) or die("$!") - if (S_ISCHR(ds.mode)): - #require Device::SerialPort - #FIXME: serial = tie( *FH, 'Device::SerialPort', "dev" ) or die("Can't tie: $!" - serial.baudrate(115200) - serial.parity("none") - serial.databits(8) - serial.stopbits(1) - serial.handshake("none") - serial.write_settings or die("failed writing settings") - FH.blocking(0) - elif (S_ISSOCK(ds.mode)): - client = None #FIXME: IO::Socket::UNIX->new(Type = SOCK_STREAM, Peer = dev) or die("Can't create socket!") - else: - die("dev not a character device or a socket") - -#FIXME: Add support for TCP sockets too -client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) -print("Connecting to '" + dev + "'") -client.connect(dev) - -def substr(buf, start, length = None): - if length == None: - return buf[start:] - return buf[start:start + length] - -def patch_substr(buf, start, length, fmt, data = None): - if data == None: - data = fmt - return buf[0:start] + fmt + buf[start+length:] - else: - return buf[0:start] + struct.pack(fmt, data) + buf[start+length:] - -def unpack(fmt, data): - return struct.unpack(fmt, data)[0] +from windpl_extra import logical2physical -def pack(fmt, data): - return struct.pack(fmt, data) -def writeDev(data): - if False: - if (serial): - serial.write(data) - else: - client.syswrite(data) - client.send(data) - -def readLoop(wanted): - total = 0 - outbuf = bytearray([]) - while ( total < wanted ): - if False: - if (serial): - (count, buf ) = serial.read(1) - else: - #FH.blocking(1) - count = client.sysread(buf, 1) - if (count == 0): - die("eof") - # print("readLoop count %x\n", ord(buf) - #FH.blocking(0) - buf = client.recv(1) - count = len(buf) - if (count): - total += count - outbuf += buf - return outbuf +def substr(buf, start, length=None): + if length == None: + return buf[start:] + return buf[start : start + length] -def hexformat(buf): - length = len(buf) - if length == 0: - return - b = "0000 " - c = 0 - for x in buf: - c += 1 - b += "%02x " % x - if ( c % 16 ) == 0: - if ( c < length ): - b += "\n%04x " % c - if substr( b, -1, 1 ) != "\n": - b += "\n" - return b - -#FIXME: Verify behaviour -def cksum(buf): - v = 0 - for b in buf: - v += b - return v - -PACKET_LEADER = 0x30303030 -CONTROL_PACKET_LEADER = 0x69696969 - -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 - -def handlePacket(): - - ptype, buf = getPacket() - - if (len(buf) == 0): - return - - if (ptype == PACKET_TYPE_KD_STATE_MANIPULATE): - handleStateManipulate(buf) - elif (ptype == PACKET_TYPE_KD_DEBUG_IO): - handleDebugIO(buf) - elif (ptype == PACKET_TYPE_KD_STATE_CHANGE64): - handleStateChange(buf) - - return (ptype, buf) - -def getPacket(): - global nextpid - ptype = None - payload = bytearray([]) - buf = readLoop(4) - plh = unpack( "I", buf ) - if ((plh == PACKET_LEADER) or (plh == CONTROL_PACKET_LEADER)): - print("Got packet leader: %08x" % plh) - - buf = readLoop(2) - ptype = unpack( "H", buf ) - print("Packet type: " + str(ptype) + "") - - buf = readLoop(2) - bc = unpack( "H", buf ) - print("Byte count: " + str(bc) + "") - - buf = readLoop(4) - pid = unpack( "I", buf ) - nextpid = pid - print("Packet ID: %08x" % pid) - - buf = readLoop(4) - ck = unpack( "I", buf ) - print("Checksum: %08x" % ck) - - if bc: - payload = readLoop(bc) - - # send ack if it's a non-control packet - if ( plh == PACKET_LEADER ): - # packet trailer - trail = readLoop(1) - print(hexformat(trail)) - if ( trail[0] == 0xAA ): - # print("sending Ack\n";#) - sendAck() - return (ptype, payload) - -# PACKET_TYPE_KD_DEBUG_IO apis -# DBGKD_DEBUG_IO -DbgKdPrintStringApi = 0x00003230 -DbgKdGetStringApi = 0x00003231 -def handleDebugIO(buf): - - apiNumber = unpack("I", substr(buf, 0, 4)) - if (apiNumber == DbgKdPrintStringApi): - print("DBG PRINT STRING: " + substr(buf, 0x10).decode('utf-8')) - -# PACKET_TYPE_KD_STATE_CHANGE states -# X86_NT5_DBGKD_WAIT_STATE_CHANGE64 -DbgKdExceptionStateChange = 0x00003030 -DbgKdLoadSymbolsStateChange = 0x00003031 - -def handleStateChange(buf): - - newState = unpack("I", substr(buf, 0, 4)) - print("State Change: %08x" % newState) - - if (newState == DbgKdExceptionStateChange): - exceptions[0xc0000005] = "EXCEPTION_ACCESS_VIOLATION", - exceptions[0xc000008c] = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED", - exceptions[0x80000003] = "EXCEPTION_BREAKPOINT", - exceptions[0x80000002] = "EXCEPTION_DATATYPE_MISALIGNMENT", - exceptions[0xc000008d] = "EXCEPTION_FLT_DENORMAL_OPERAND", - exceptions[0xc000008e] = "EXCEPTION_FLT_DIVIDE_BY_ZERO", - exceptions[0xc000008f] = "EXCEPTION_FLT_INEXACT_RESULT", - exceptions[0xc0000030] = "EXCEPTION_FLT_INVALID_OPERATION", - exceptions[0xc0000091] = "EXCEPTION_FLT_OVERFLOW", - exceptions[0xc0000032] = "EXCEPTION_FLT_STACK_CHECK", - exceptions[0xc0000033] = "EXCEPTION_FLT_UNDERFLOW", - exceptions[0x80000001] = "EXCEPTION_GUARD_PAGE", - exceptions[0xc000001d] = "EXCEPTION_ILLEGAL_INSTRUCTION", - exceptions[0xc0000006] = "EXCEPTION_IN_PAGE_ERROR", - exceptions[0xc0000094] = "EXCEPTION_INT_DIVIDE_BY_ZERO", - exceptions[0xc0000035] = "EXCEPTION_INT_OVERFLOW", - exceptions[0xc00000fd] = "EXCEPTION_STACK_OVERFLOW" - - # DBGKM_EXCEPTION64 - ex = substr(buf, 32) - - code = unpack("I", substr(ex,0,4)) - flags = unpack("I", substr(ex,4,4)) - record = unpack("I", substr(ex,8,4)) - address = unpack("I", substr(ex,16,4)) - parameters = unpack("I", substr(ex,24,4)) - - if (code in exceptions): - print("*** %s ", exceptions[code]) + +def patch_substr(buf, start, length, fmt, data=None): + if data == None: + data = fmt + return buf[0:start] + fmt + buf[start + length :] else: - print("*** Exception %08x " % code) - print("at %08x\n", address) - - print("Exception flags = %08x" % flags) - print("Exception record = %08x" % record) - print("Exception address = %08x" % address) - print("Number parameters = %08x" % parameters) - - running = 0 - - # my @v = getVersionInfo() - # version = v[0] - # $kernelbase = v[2] - elif (newState == DbgKdLoadSymbolsStateChange): - #DBGKD_LOAD_SYMBOLS64 - - filename = substr(buf, 0x3b8) - filename = filename.decode('utf-8').strip() - print("Load Symbols for " + filename + "") - - #nothing to do... - - sendDbgKdContinue2() - - -# 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 - -def handleStateManipulate(buf): - - apiNumber = unpack("I", substr(buf, 0, 4)) - print("State Manipulate: %08x" % apiNumber) - - if ( apiNumber == DbgKdWriteBreakPointApi ): - bp = "%08x" % unpack( "I", substr( buf, 16, 4 ) ) - handle = unpack( "I", substr( buf, 20, 4 ) ) - print("Breakpoint " + handle + " set at " + bp + "") - breakpoints[bp] = handle - elif ( apiNumber == DbgKdRestoreBreakPointApi ): - handle = unpack( "I", substr( buf, 16, 4 ) ) - print("Breakpoint " + handle + " cleared") - elif ( apiNumber == DbgKdGetVersionApi ): - version = substr( buf, 16 ) - print("VERS: " + hexformat(version)) - elif ( apiNumber == DbgKdReadVirtualMemoryApi ): - vmem = substr( buf, 56 ) - print("VMEM:\n" + hexasc(vmem)) - elif ( apiNumber == DbgKdReadPhysicalMemoryApi ): - pmem = substr( buf, 56 ) - print("PMEM:\n" + hexasc(pmem)) - elif ( apiNumber == DbgKdReadControlSpaceApi ): - controlspace = substr( buf, 56 ) - print("CNTL: " + hexformat(controlspace)) - else: - print("UNKN: " + hexasc(buf)) - + return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] +def unpack(fmt, data): + return struct.unpack(fmt, data)[0] + +def hexformat(buf): + length = len(buf) + if length == 0: + return + b = "0000 " + c = 0 + for x in buf: + c += 1 + b += "%02x " % x + if (c % 16) == 0: + if c < length: + b += "\n%04x " % c + if substr(b, -1, 1) != "\n": + b += "\n" + return b +def hexasc(buf): + length = len(buf) + if length == 0: + return + count = 0 + ascii = "" + out = "0000 " + for x in buf: + c = ord(x) + out += "%02x " % c + if (c > 0x1F) and (c < 0x7F): + ascii += x + else: + ascii += "." + count += 1 + if (count % 16) == 0: + if count < length: + out += " " + ascii + "\n%04x " % count + else: + out += " " + ascii + "\n" + ascii = "" + + padding = 0 + if ascii: + padding = 48 - ((count % 16) * 3) + + out += " " * padding + out += " " + ascii + "\n" + return out +# FIXME: Verify behaviour +def cksum(buf): + v = 0 + for b in buf: + v += b + return v def packetHeader(d): - header = "\x30\x30\x30\x30".encode('utf-8') # packet leader - header += "\x02\x00".encode('utf-8') # packet type PACKET_TYPE_KD_STATE_MANIPULATE - header += pack( "H", len(d) ) # sizeof data - header += pack( "I", nextpid ) # packet id - header += pack( "I", cksum(d) ) # checksum of data - return header - -def sendAck(): - ack = "\x69\x69\x69\x69\x04\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode('utf-8') - print(nextpid) - ack = patch_substr( ack, 8, 4 , "I", nextpid ) - - print(hexformat(ack)) - writeDev(ack) - -def sendReset(): - rst = "\x69\x69\x69\x69\x06\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode('utf-8') - - # print("Sending reset packet\n" - # print(hexformat(rst) - writeDev(rst) - - -def getContext(): - context = {} - sendDbgKdGetContext() - buf = waitStateManipulate(DbgKdGetContextApi) - if ( len(buf) > 204 ): - ctx = substr( buf, 56 ) - - #print("CTXT: ", hexformat($context) - context['ContextFlags'] = unpack( "I", substr( ctx, 0, 4 ) ) - context['DR0'] = unpack( "I", substr( ctx, 4, 4 ) ) - context['DR1'] = unpack( "I", substr( ctx, 8, 4 ) ) - context['DR2'] = unpack( "I", substr( ctx, 12, 4 ) ) - context['DR3'] = unpack( "I", substr( ctx, 16, 4 ) ) - context['DR6'] = unpack( "I", substr( ctx, 20, 4 ) ) - context['DR7'] = unpack( "I", substr( ctx, 24, 4 ) ) - context['fp.ControlWord'] = unpack( "I", substr( ctx, 28, 4 ) ) - context['fp.StatusWord'] = unpack( "I", substr( ctx, 32, 4 ) ) - context['fp.TagWord'] = unpack( "I", substr( ctx, 36, 4 ) ) - context['fp.ErrorOffset'] = unpack( "I", substr( ctx, 40, 4 ) ) - context['fp.ErrorSelector'] = unpack( "I", substr( ctx, 44, 4 ) ) - context['fp.DataOffset'] = unpack( "I", substr( ctx, 48, 4 ) ) - context['fp.DataSelector'] = unpack( "I", substr( ctx, 52, 4 ) ) - context['fp.RegisterArea'] = substr( ctx, 56, 80 ) - context['fp.Cr0NpxState'] = unpack( "I", substr( ctx, 136, 4 ) ) - context['GS'] = unpack( "I", substr( ctx, 140, 4 ) ) - context['FS'] = unpack( "I", substr( ctx, 144, 4 ) ) - context['ES'] = unpack( "I", substr( ctx, 148, 4 ) ) - context['DS'] = unpack( "I", substr( ctx, 152, 4 ) ) - context['EDI'] = unpack( "I", substr( ctx, 156, 4 ) ) - context['ESI'] = unpack( "I", substr( ctx, 160, 4 ) ) - context['EBX'] = unpack( "I", substr( ctx, 164, 4 ) ) - context['EDX'] = unpack( "I", substr( ctx, 168, 4 ) ) - context['ECX'] = unpack( "I", substr( ctx, 172, 4 ) ) - context['EAX'] = unpack( "I", substr( ctx, 176, 4 ) ) - context['EBP'] = unpack( "I", substr( ctx, 180, 4 ) ) - context['EIP'] = unpack( "I", substr( ctx, 184, 4 ) ) - context['CS'] = unpack( "I", substr( ctx, 188, 4 ) ) - context['Eflags'] = unpack( "I", substr( ctx, 192, 4 ) ) - context['ESP'] = unpack( "I", substr( ctx, 196, 4 ) ) - context['SS'] = unpack( "I", substr( ctx, 200, 4 ) ) - context['leftovers'] = substr( ctx, 204 ) - return context - -def setContext(context): - ctx = pack( "I", context['ContextFlags'] ) - ctx += pack( "I", context['DR0'] ) - ctx += pack( "I", context['DR1'] ) - ctx += pack( "I", context['DR2'] ) - ctx += pack( "I", context['DR3'] ) - ctx += pack( "I", context['DR6'] ) - ctx += pack( "I", context['DR7'] ) - ctx += pack( "I", context['fp.ControlWord'] ) - ctx += pack( "I", context['fp.StatusWord'] ) - ctx += pack( "I", context['fp.TagWord'] ) - ctx += pack( "I", context['fp.ErrorOffset'] ) - ctx += pack( "I", context['fp.ErrorSelector'] ) - ctx += pack( "I", context['fp.DataOffset'] ) - ctx += pack( "I", context['fp.DataSelector'] ) - ctx += context['fp.RegisterArea'] - ctx += pack( "I", context['fp.Cr0NpxState'] ) - ctx += pack( "I", context['GS'] ) - ctx += pack( "I", context['FS'] ) - ctx += pack( "I", context['ES'] ) - ctx += pack( "I", context['DS'] ) - ctx += pack( "I", context['EDI'] ) - ctx += pack( "I", context['ESI'] ) - ctx += pack( "I", context['EBX'] ) - ctx += pack( "I", context['EDX'] ) - ctx += pack( "I", context['ECX'] ) - ctx += pack( "I", context['EAX'] ) - ctx += pack( "I", context['EBP'] ) - ctx += pack( "I", context['EIP'] ) - ctx += pack( "I", context['CS'] ) - ctx += pack( "I", context['Eflags'] ) - ctx += pack( "I", context['ESP'] ) - ctx += pack( "I", context['SS'] ) - ctx += context['leftovers'] - sendDbgKdSetContext(ctx) - waitStateManipulate(DbgKdSetContextApi) - -def getVersionInfo(): - print("getVersionInfo") - # os version, protocol version, kernel base, module list, debugger data - sendDbgKdGetVersion() - buf = waitStateManipulate(DbgKdGetVersionApi) - if ( len(buf) > 32 ): - v = substr( buf, 16 ) - osv = "%d.%d" % (unpack( "H", substr( v, 4, 2 ) ), unpack( "H", substr( v, 6, 2 ) )) - pv = unpack( "H", substr( v, 8, 2 ) ) - machinetype = unpack( "H", substr( v, 12, 2 ) ) - kernbase = unpack( "I", substr( v, 16, 4 ) ) - modlist = unpack( "I", substr( v, 24, 4 ) ) - ddata = unpack( "I", substr( v, 32, 4 ) ) - if ( pv < 5 ): - print("Debug protocol version %d not supported" % pv) - sys.exit() - if ( machinetype and ( machinetype != 0x2d ) ): - print("Processor architecture %04x not supported" % machinetype) - sys.exit() - - print("Windows version = %s" % osv) - print("Protocol version = %d" % pv) - print("Kernel base = %08x" % kernbase) - print("Module list = %08x" % modlist) - print("Debugger data = %08x" % ddata) - - return ( osv, pv, kernbase, modlist, ddata ) - return ( "0.0", 0, 0, 0, 0 ) - -def printVersionData(): - v = getVersionInfo() - print("Windows version = %s" % v[0]) - print("Protocol version = %d" % v[1]) - print("Kernel base = %08x" % v[2]) - print("Module list = %08x" % v[3]) - print("Debugger data = %08x" % v[4]) - -if False: - def getKernelModules(): - save = pcontext - pcontext = kernelcontext # this procedure is kernel context only - modules = [] - v = getVersionInfo() - flink = readDword( v[3] ) - modlist = walkList(flink) - for mod in modlist: - - #print("module at %08x\n", $mod - buf = readVirtualMemory( mod, 0x34 ) - if ( len(buf) == 0x34 ): - base = unpack( "I", substr( buf, 0x18, 4 ) ) - if base == 0: - continue - entry = unpack( "I", substr( buf, 0x1c, 4 ) ) - size = unpack( "I", substr( buf, 0x20, 4 ) ) - path = substr( buf, 0x24, 8 ) - name = substr( buf, 0x2c, 8 ) - modules[base]['name'] = unicodeStructToAscii(name) - modules[base]['path'] = unicodeStructToAscii(path) - modules[base]['size'] = size - modules[base]['entry'] = entry - pcontext = save - return modules - - def unicodeStructToAscii(struct): - if len(struct) != 8: - return - length = unpack( "H", substr( struct, 0, 2 ) ) - vaddr = unpack( "I", substr( struct, 4, 4 ) ) - buf = readVirtualMemory( vaddr, length ) - if ( len(buf) == length ): - buf = None #FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii - return buf - -def hexasc(buf): - length = len(buf) - if length == 0: - return - count = 0 - ascii = "" - out = "0000 " - for x in buf: - c = ord(x) - out += "%02x " % c - if ( ( c > 0x1f ) and ( c < 0x7f ) ): - ascii += x - else: - ascii += "." - count += 1 - if ( count % 16 ) == 0: - if ( count < length ): - out += " " + ascii + "\n%04x " % count - else: - out += " " + ascii + "\n" - ascii = "" - if (ascii): - padding = 48 - ( ( count % 16 ) * 3 ) - out += " " * padding - out += " " + ascii + "\n" - return out - -def sendManipulateStatePacket(d): - h = packetHeader(d) - - print("SEND: " + hexformat(h) + hexformat(d)) - writeDev(h) - writeDev(d) - writeDev(bytes([0xAA])) - -def sendDbgKdContinue2(): - - #print("Sending DbgKdContinue2Api packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdContinueApi2 ) - d = patch_substr( d, 8, 4 , "I", 0x00010001 ) - d = patch_substr( d, 16, 4 , "I", 0x00010001 ) - d = patch_substr( d, 24, 4 , "I", 0x400 ) # TraceFlag - d = patch_substr( d, 28, 4 , "I", 0x01 ) # Dr7 - sendManipulateStatePacket(d) - -def sendDbgKdGetVersion(): - - #print("Sending DbgKdGetVersionApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdGetVersionApi ) - sendManipulateStatePacket(d) - -def sendDbgKdWriteBreakPoint(bp): - bp = hex(bp) - - #print("Sending DbgKdWriteBreakPointApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdWriteBreakPointApi ) - d = patch_substr( d, 16, 4 , "I", bp ) - d = patch_substr( d, 20, 4 , "I", curbp ) - curbp += 1 - sendManipulateStatePacket(d) - -def sendDbgKdRestoreBreakPoint(bp): - if ( defined( breakpoints[bp] ) ): - - #print("Sending DbgKdRestoreBreakPointApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdRestoreBreakPointApi ) - d = patch_substr( d, 16, 4 , "I", breakpoints[bp] ) - sendManipulateStatePacket(d) - delete( breakpoints[bp] ) - else: - print("Breakpoint not set at bp") - -def sendDbgKdReadControlSpace(): - - #print("Sending DbgKdReadControlSpaceApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", 0x3137 ) - d = patch_substr( d, 16, 4 , "I", 0x02cc ) - d = patch_substr( d, 24, 4 , "I", 84 ) - sendManipulateStatePacket(d) - -def sendDbgKdWriteControlSpace(): - - #print("Sending DbgKdWriteControlSpaceApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", 0x3138 ) - d = patch_substr( d, 16, 4 , "I", 0x02cc ) - d = patch_substr( d, 24, 4 , "I", 84 ) - d += controlspace - sendManipulateStatePacket(d) - controlspacesent = 1 - -def sendDbgKdGetContext(): - - #print("Sending DbgKdGetContextApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdGetContextApi ) - sendManipulateStatePacket(d) - -def sendDbgKdSetContext(ctx): - - #print("Sending DbgKdSetContextApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdSetContextApi ) - d = patch_substr( d, 16, 4, substr( ctx, 0, 4 )) - d += ctx - sendManipulateStatePacket(d) - -def sendDbgKdReadPhysicalMemory(addr, readlen): - - #print("Sending DbgKdReadPhysicalMemoryApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdReadPhysicalMemoryApi ) - d = patch_substr( d, 16, 4 , "I", addr ) - d = patch_substr( d, 24, 4 , "I", readlen ) - sendManipulateStatePacket(d) - -def sendDbgKdReadVirtualMemory(vaddr, readlen): - - #print("Sending DbgKdReadVirtualMemoryApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdReadVirtualMemoryApi ) - d = patch_substr( d, 16, 4 , "I", vaddr ) - d = patch_substr( d, 24, 4 , "I", readlen ) - sendManipulateStatePacket(d) - -def sendDbgKdWriteVirtualMemory(vaddr, data): - writelen = len(data) - - #print("Sending DbgKdWriteVirtualMemoryApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdWriteVirtualMemoryApi ) - d = patch_substr( d, 16, 4 , "I", vaddr ) - d = patch_substr( d, 24, 4 , "I", writelen ) - d += data - sendManipulateStatePacket(d) - -def readDword(addr): - - #print("Reading dword at %08x\n", addr - buf = readVirtualMemory( addr, 4 ) - if ( len(buf) == 4 ): - return unpack( "I", buf ) - return "failed" - -def readPhysicalMemory(addr, length): - chunksize = 0x800 # max to request in one packet - out = bytearray([]) - while ( length > 0 ): - - if ( length < chunksize ): - sendDbgKdReadPhysicalMemory( addr, length ) - buf = waitStateManipulate(DbgKdReadPhysicalMemoryApi) - if ( len(buf) > 56 ): - out += substr( buf, 56 ) - length = 0 - else: - sendDbgKdReadPhysicalMemory( addr, chunksize ) - buf = waitStateManipulate(DbgKdReadPhysicalMemoryApi) - if ( len(buf) > 56 ): - out += substr( buf, 56 ) - length -= chunksize - addr += chunksize - return out - -def writePhysicalMemory(addr, buf): - length = len(buf) - chunksize = 0x800 # max to send in one packet - offset = 0 - #FIXME: Logic sucks here.. - while ( length > 0 ): - if ( length < chunksize ): - sendDbgKdWritePhysicalMemory( addr, buf ) - waitStateManipulate(0x313e) - length = 0 - else: - sendDbgKdWritePhysicalMemory( addr, substr( buf, offset, chunksize ) ) - waitStateManipulate(0x313e) - length -= chunksize - offset += chunksize - addr += chunksize - return - -def writeVirtualMemory(addr, buf): - length = len(buf) - #FIXME: return unless addr && length - chunksize = 0x800 # max to send in one packet - offset = 0 - if ( pcontext['pid'] == 0 ): - #FIXME: Logic sucks here.. - while ( length > 0 ): - if ( length < chunksize ): - sendDbgKdWriteVirtualMemory( addr, buf ) - waitStateManipulate(DbgKdWriteVirtualMemoryApi) - length = 0 - else: - sendDbgKdWriteVirtualMemory( addr, substr( buf, offset, chunksize ) ) - waitStateManipulate(DbgKdWriteVirtualMemoryApi) - length -= chunksize - offset += chunksize - addr += chunksize - else: - distance_to_page_boundary = 0x1000 - ( addr & 0xfff ) - #FIXME: Logic sucks here.. - if ( distance_to_page_boundary > length ): - physaddr = logical2physical(addr) - writePhysicalMemory( physaddr, buf ) - return - else: - physaddr = logical2physical(addr) - buf = writePhysicalMemory( physaddr, substr( buf, 0, distance_to_page_boundary ) ) - addr += distance_to_page_boundary - offset += distance_to_page_boundary - remainder = length - distance_to_page_boundary - - while ( remainder > 0 ): - if ( remainder < 0x1000 ): - physaddr = logical2physical(addr) - writePhysicalMemory( physaddr, substr( buf, offset, remainder ) ) - remainder = 0 - else: - physaddr = logical2physical(addr) - writePhysicalMemory( physaddr, substr( buf, offset, 0x1000 ) ) - addr += 0x1000 - offset += 0x1000 - remainder -= 0x1000 - return - -#FIXME: Indentation is horrible -def readVirtualMemory(addr, length): - #FIXME: return unless addr && length - chunksize = 0x800 # max to request in one packet - out = bytearray() - buf = bytearray() - if ( pcontext['pid'] == 0 ): - while ( length > 0 ): - if ( length < chunksize ): - sendDbgKdReadVirtualMemory( addr, length ) - buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) - if ( len(buf) > 56 ): - out += substr( buf, 56 ) - length = 0 - else: - sendDbgKdReadVirtualMemory( addr, chunksize ) - buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) - if ( len(buf) > 56 ): - out += substr( buf, 56 ) - length -= chunksize - addr += chunksize - return out - else: - distance_to_page_boundary = 0x1000 - ( addr & 0xfff ) - if ( distance_to_page_boundary > length ): - physaddr = logical2physical(addr) - return readPhysicalMemory( physaddr, length ) - else: - physaddr = logical2physical(addr) - buf = readPhysicalMemory( physaddr, distance_to_page_boundary ) - addr += distance_to_page_boundary - remainder = length - distance_to_page_boundary - while ( remainder > 0 ): - if ( remainder < 0x1000 ): - physaddr = logical2physical(addr) - buf += readPhysicalMemory( physaddr, remainder ) - remainder = 0 + header = "\x30\x30\x30\x30".encode("utf-8") # packet leader + header += "\x02\x00".encode("utf-8") # packet type PACKET_TYPE_KD_STATE_MANIPULATE + header += pack("H", len(d)) # sizeof data + header += pack("I", nextpid) # packet id + header += pack("I", cksum(d)) # checksum of data + return header + + +class DebugContext: + PACKET_LEADER = 0x30303030 + CONTROL_PACKET_LEADER = 0x69696969 + + 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_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 + + # 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 + + def __init__(self) -> None: + self.timeout = 10 # max time to wait on packet + self.running = 1 + + self.kernelcontext = {} + self.kernelcontext["peb"] = 0 + self.kernelcontext["pid"] = 0 + self.kernelcontext["eprocess"] = 0 + self.kernelcontext["dtb"] = 0 + + self.processcontext = {} + self.processcontext["peb"] = 0 + self.processcontext["pid"] = 0 + self.processcontext["eprocess"] = 0 + self.processcontext["dtb"] = 0 + self.pcontext = kernelcontext + self.nextpid = 1 + self.breakpoints = {} + + self.curbp = 1 + self.controlspace = 0 + self.controlspacesent = False + + self.client = None + + def connect(self, endpoint): + if False: + ds = stat(dev) or die("$!") + if S_ISCHR(ds.mode): + # require Device::SerialPort + # FIXME: serial = tie( *FH, 'Device::SerialPort', "dev" ) or die("Can't tie: $!" + serial.baudrate(115200) + serial.parity("none") + serial.databits(8) + serial.stopbits(1) + serial.handshake("none") + serial.write_settings or die("failed writing settings") + FH.blocking(0) + elif S_ISSOCK(ds.mode): + client = None # FIXME: IO::Socket::UNIX->new(Type = SOCK_STREAM, Peer = dev) or die("Can't create socket!") + else: + die("dev not a character device or a socket") + + # FIXME: Add support for TCP sockets too + self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + print(f"Connecting to '{endpoint}'") + self.client.connect(endpoint) + + def run(self): + self.sendReset() + while True: + self.handlePacket() + print("") + + # FIXME: windpl_loop.py + + def writeDev(self, data): + if False: + if serial: + serial.write(data) + else: + self.client.syswrite(data) + self.client.send(data) + + def readLoop(self, wanted): + total = 0 + outbuf = bytearray([]) + while total < wanted: + if False: + if serial: + (count, buf) = serial.read(1) + else: + # FH.blocking(1) + count = client.sysread(buf, 1) + if count == 0: + die("eof") + # print("readLoop count %x\n", ord(buf) + # FH.blocking(0) + buf = self.client.recv(wanted - total) + count = len(buf) + if count: + total += count + outbuf += buf + return outbuf + + def handlePacket(self): + ptype, buf = self.getPacket() + + if len(buf) == 0: + return + + if ptype == self.PACKET_TYPE_KD_STATE_MANIPULATE: + self.handleStateManipulate(buf) + elif ptype == self.PACKET_TYPE_KD_DEBUG_IO: + self.handleDebugIO(buf) + elif ptype == self.PACKET_TYPE_KD_STATE_CHANGE64: + self.handleStateChange(buf) + + return (ptype, buf) + + def getPacket(self): + global nextpid + ptype = None + payload = bytearray([]) + buf = self.readLoop(4) + plh = unpack("I", buf) + if (plh == self.PACKET_LEADER) or (plh == self.CONTROL_PACKET_LEADER): + print("Got packet leader: %08x" % plh) + + buf = self.readLoop(2) + ptype = unpack("H", buf) + print("Packet type: " + str(ptype) + "") + + buf = self.readLoop(2) + bc = unpack("H", buf) + print("Byte count: " + str(bc) + "") + + buf = self.readLoop(4) + pid = unpack("I", buf) + nextpid = pid + print("Packet ID: %08x" % pid) + + buf = self.readLoop(4) + ck = unpack("I", buf) + print("Checksum: %08x" % ck) + + if bc: + payload = self.readLoop(bc) + + # send ack if it's a non-control packet + if plh == self.PACKET_LEADER: + # packet trailer + trail = self.readLoop(1) + print(hexformat(trail)) + if trail[0] == 0xAA: + # print("sending Ack\n";#) + self.sendAck() + return (ptype, payload) + + def handleDebugIO(self, buf): + apiNumber = unpack("I", substr(buf, 0, 4)) + if apiNumber == self.DbgKdPrintStringApi: + print("DBG PRINT STRING: " + substr(buf, 0x10).decode("utf-8")) + + def handleStateChange(self, buf): + newState = unpack("I", substr(buf, 0, 4)) + print("State Change: %08x" % newState) + + if newState == self.DbgKdExceptionStateChange: + 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", + } + + # DBGKM_EXCEPTION64 + ex = substr(buf, 32) + + code = unpack("I", substr(ex, 0, 4)) + flags = unpack("I", substr(ex, 4, 4)) + record = unpack("I", substr(ex, 8, 4)) + address = unpack("I", substr(ex, 16, 4)) + parameters = unpack("I", substr(ex, 24, 4)) + + if code in exceptions: + print("*** %s ", exceptions[code]) + else: + print("*** Exception %08x " % code) + print("at %08x\n", address) + + print("Exception flags = %08x" % flags) + print("Exception record = %08x" % record) + print("Exception address = %08x" % address) + print("Number parameters = %08x" % parameters) + + running = 0 + + # my @v = getVersionInfo() + # version = v[0] + # $kernelbase = v[2] + elif newState == self.DbgKdLoadSymbolsStateChange: + # DBGKD_LOAD_SYMBOLS64 + + filename = substr(buf, 0x3B8) + filename = filename.decode("utf-8").strip() + print("Load Symbols for " + filename + "") + + # nothing to do... + + self.sendDbgKdContinue2() + + def handleStateManipulate(self, buf): + + apiNumber = unpack("I", substr(buf, 0, 4)) + print("State Manipulate: %08x" % apiNumber) + + if apiNumber == self.DbgKdWriteBreakPointApi: + bp = "%08x" % unpack("I", substr(buf, 16, 4)) + handle = unpack("I", substr(buf, 20, 4)) + print("Breakpoint " + handle + " set at " + bp + "") + self.breakpoints[bp] = handle + elif apiNumber == self.DbgKdRestoreBreakPointApi: + handle = unpack("I", substr(buf, 16, 4)) + print("Breakpoint " + handle + " cleared") + elif apiNumber == self.DbgKdGetVersionApi: + version = substr(buf, 16) + print("VERS: " + hexformat(version)) + elif apiNumber == self.DbgKdReadVirtualMemoryApi: + vmem = substr(buf, 56) + print("VMEM:\n" + hexasc(vmem)) + elif apiNumber == self.DbgKdReadPhysicalMemoryApi: + pmem = substr(buf, 56) + print("PMEM:\n" + hexasc(pmem)) + elif apiNumber == self.DbgKdReadControlSpaceApi: + controlspace = substr(buf, 56) + print("CNTL: " + hexformat(controlspace)) else: - physaddr = logical2physical(addr) - buf += readPhysicalMemory( physaddr, 0x1000 ) - addr += 0x1000 - remainder -= 0x1000 - return buf - - -def sendDbgKdReboot(): - print("Sending DbgKdRebootApi packet") - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", 0x313b ) - sendManipulateStatePacket(d) - - -def waitStateManipulate(wanted): - if running: - return - - ptype = bytearray([]) - buf = bytearray([]) - try: - while (1): - (ptype, buf) = handlePacket(1) - if (ptype == PACKET_TYPE_KD_STATE_MANIPULATE): - api = unpack( "I", substr( buf, 0, 4 ) ) - if (api == wanted): - break - except timeout: - print("Timeout waiting for %04x packet reply" % wanted) - return buf - -def getPspCidTable(): - pspcidtable = 0 - save = pcontext - pcontext = kernelcontext # this procedure is kernel context only - sendDbgKdGetVersion() - buf = waitStateManipulate(DbgKdGetVersionApi) - pddata = unpack( "I", substr( buf, 48, 4 ) ) - if (pddata): - #print("Pointer to debugger data struct is at %08x\n", pddata - ddata = readDword(pddata) - if ( ddata != "failed" ): - #print("debugger data struct is at %08x\n", ddata - pspcidtable = readDword( ddata + 88 ) - if ( pspcidtable != "failed" ): - #print("PspCidTable is %08x\n", $pspcidtable + print("UNKN: " + hexasc(buf)) + + def sendAck(self): + ack = "\x69\x69\x69\x69\x04\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode( + "utf-8" + ) + print(nextpid) + ack = patch_substr(ack, 8, 4, "I", nextpid) + + print(hexformat(ack)) + self.writeDev(ack) + + def sendReset(self): + rst = "\x69\x69\x69\x69\x06\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode( + "utf-8" + ) + + # print("Sending reset packet\n" + # print(hexformat(rst) + self.writeDev(rst) + + def getContext(self): + context = {} + self.sendDbgKdGetContext() + buf = self.waitStateManipulate(DbgKdGetContextApi) + if len(buf) > 204: + ctx = substr(buf, 56) + + # print("CTXT: ", hexformat($context) + context["ContextFlags"] = unpack("I", substr(ctx, 0, 4)) + context["DR0"] = unpack("I", substr(ctx, 4, 4)) + context["DR1"] = unpack("I", substr(ctx, 8, 4)) + context["DR2"] = unpack("I", substr(ctx, 12, 4)) + context["DR3"] = unpack("I", substr(ctx, 16, 4)) + context["DR6"] = unpack("I", substr(ctx, 20, 4)) + context["DR7"] = unpack("I", substr(ctx, 24, 4)) + context["fp.ControlWord"] = unpack("I", substr(ctx, 28, 4)) + context["fp.StatusWord"] = unpack("I", substr(ctx, 32, 4)) + context["fp.TagWord"] = unpack("I", substr(ctx, 36, 4)) + context["fp.ErrorOffset"] = unpack("I", substr(ctx, 40, 4)) + context["fp.ErrorSelector"] = unpack("I", substr(ctx, 44, 4)) + context["fp.DataOffset"] = unpack("I", substr(ctx, 48, 4)) + context["fp.DataSelector"] = unpack("I", substr(ctx, 52, 4)) + context["fp.RegisterArea"] = substr(ctx, 56, 80) + context["fp.Cr0NpxState"] = unpack("I", substr(ctx, 136, 4)) + context["GS"] = unpack("I", substr(ctx, 140, 4)) + context["FS"] = unpack("I", substr(ctx, 144, 4)) + context["ES"] = unpack("I", substr(ctx, 148, 4)) + context["DS"] = unpack("I", substr(ctx, 152, 4)) + context["EDI"] = unpack("I", substr(ctx, 156, 4)) + context["ESI"] = unpack("I", substr(ctx, 160, 4)) + context["EBX"] = unpack("I", substr(ctx, 164, 4)) + context["EDX"] = unpack("I", substr(ctx, 168, 4)) + context["ECX"] = unpack("I", substr(ctx, 172, 4)) + context["EAX"] = unpack("I", substr(ctx, 176, 4)) + context["EBP"] = unpack("I", substr(ctx, 180, 4)) + context["EIP"] = unpack("I", substr(ctx, 184, 4)) + context["CS"] = unpack("I", substr(ctx, 188, 4)) + context["Eflags"] = unpack("I", substr(ctx, 192, 4)) + context["ESP"] = unpack("I", substr(ctx, 196, 4)) + context["SS"] = unpack("I", substr(ctx, 200, 4)) + context["leftovers"] = substr(ctx, 204) + return context + + def setContext(self, context): + ctx = pack("I", context["ContextFlags"]) + ctx += pack("I", context["DR0"]) + ctx += pack("I", context["DR1"]) + ctx += pack("I", context["DR2"]) + ctx += pack("I", context["DR3"]) + ctx += pack("I", context["DR6"]) + ctx += pack("I", context["DR7"]) + ctx += pack("I", context["fp.ControlWord"]) + ctx += pack("I", context["fp.StatusWord"]) + ctx += pack("I", context["fp.TagWord"]) + ctx += pack("I", context["fp.ErrorOffset"]) + ctx += pack("I", context["fp.ErrorSelector"]) + ctx += pack("I", context["fp.DataOffset"]) + ctx += pack("I", context["fp.DataSelector"]) + ctx += context["fp.RegisterArea"] + ctx += pack("I", context["fp.Cr0NpxState"]) + ctx += pack("I", context["GS"]) + ctx += pack("I", context["FS"]) + ctx += pack("I", context["ES"]) + ctx += pack("I", context["DS"]) + ctx += pack("I", context["EDI"]) + ctx += pack("I", context["ESI"]) + ctx += pack("I", context["EBX"]) + ctx += pack("I", context["EDX"]) + ctx += pack("I", context["ECX"]) + ctx += pack("I", context["EAX"]) + ctx += pack("I", context["EBP"]) + ctx += pack("I", context["EIP"]) + ctx += pack("I", context["CS"]) + ctx += pack("I", context["Eflags"]) + ctx += pack("I", context["ESP"]) + ctx += pack("I", context["SS"]) + ctx += context["leftovers"] + self.sendDbgKdSetContext(ctx) + self.waitStateManipulate(DbgKdSetContextApi) + + def getVersionInfo(self): + print("getVersionInfo") + # os version, protocol version, kernel base, module list, debugger data + self.sendDbgKdGetVersion() + buf = self.waitStateManipulate(DbgKdGetVersionApi) + if len(buf) > 32: + v = substr(buf, 16) + osv = "%d.%d" % (unpack("H", substr(v, 4, 2)), unpack("H", substr(v, 6, 2))) + pv = unpack("H", substr(v, 8, 2)) + machinetype = unpack("H", substr(v, 12, 2)) + kernbase = unpack("I", substr(v, 16, 4)) + modlist = unpack("I", substr(v, 24, 4)) + ddata = unpack("I", substr(v, 32, 4)) + if pv < 5: + print("Debug protocol version %d not supported" % pv) + sys.exit() + if machinetype and (machinetype != 0x2D): + print("Processor architecture %04x not supported" % machinetype) + sys.exit() + + print("Windows version = %s" % osv) + print("Protocol version = %d" % pv) + print("Kernel base = %08x" % kernbase) + print("Module list = %08x" % modlist) + print("Debugger data = %08x" % ddata) + + return (osv, pv, kernbase, modlist, ddata) + return ("0.0", 0, 0, 0, 0) + + def printVersionData(self): + v = self.getVersionInfo() + print("Windows version = %s" % v[0]) + print("Protocol version = %d" % v[1]) + print("Kernel base = %08x" % v[2]) + print("Module list = %08x" % v[3]) + print("Debugger data = %08x" % v[4]) + + if False: + + def getKernelModules(): + save = pcontext + pcontext = kernelcontext # this procedure is kernel context only + modules = [] + v = getVersionInfo() + flink = readDword(v[3]) + modlist = walkList(flink) + for mod in modlist: + + # print("module at %08x\n", $mod + buf = readVirtualMemory(mod, 0x34) + if len(buf) == 0x34: + base = unpack("I", substr(buf, 0x18, 4)) + if base == 0: + continue + entry = unpack("I", substr(buf, 0x1C, 4)) + size = unpack("I", substr(buf, 0x20, 4)) + path = substr(buf, 0x24, 8) + name = substr(buf, 0x2C, 8) + modules[base]["name"] = unicodeStructToAscii(name) + modules[base]["path"] = unicodeStructToAscii(path) + modules[base]["size"] = size + modules[base]["entry"] = entry + pcontext = save + return modules + + def unicodeStructToAscii(struct): + if len(struct) != 8: + return + length = unpack("H", substr(struct, 0, 2)) + vaddr = unpack("I", substr(struct, 4, 4)) + buf = readVirtualMemory(vaddr, length) + if len(buf) == length: + buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii + return buf + + def sendManipulateStatePacket(self, d): + h = packetHeader(d) + + print("SEND: " + hexformat(h) + hexformat(d)) + self.writeDev(h) + self.writeDev(d) + self.writeDev(bytes([0xAA])) + + def sendDbgKdContinue2(self): + + # print("Sending DbgKdContinue2Api packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdContinueApi2) + d = patch_substr(d, 8, 4, "I", 0x00010001) + d = patch_substr(d, 16, 4, "I", 0x00010001) + d = patch_substr(d, 24, 4, "I", 0x400) # TraceFlag + d = patch_substr(d, 28, 4, "I", 0x01) # Dr7 + self.sendManipulateStatePacket(d) + + def sendDbgKdGetVersion(self): + + # print("Sending DbgKdGetVersionApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdGetVersionApi) + self.sendManipulateStatePacket(d) + + def sendDbgKdWriteBreakPoint(self, bp): + bp = hex(bp) + + # print("Sending DbgKdWriteBreakPointApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdWriteBreakPointApi) + d = patch_substr(d, 16, 4, "I", bp) + d = patch_substr(d, 20, 4, "I", self.curbp) + self.curbp += 1 + self.sendManipulateStatePacket(d) + + def sendDbgKdRestoreBreakPoint(self, bp): + if bp in self.breakpoints: + + # print("Sending DbgKdRestoreBreakPointApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdRestoreBreakPointApi) + d = patch_substr(d, 16, 4, "I", self.breakpoints[bp]) + self.sendManipulateStatePacket(d) + del self.breakpoints[bp] + + else: + print("Breakpoint not set at bp") + + def sendDbgKdReadControlSpace(self): + + # print("Sending DbgKdReadControlSpaceApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", 0x3137) + d = patch_substr(d, 16, 4, "I", 0x02CC) + d = patch_substr(d, 24, 4, "I", 84) + self.sendManipulateStatePacket(d) + + def sendDbgKdWriteControlSpace(self): + + # print("Sending DbgKdWriteControlSpaceApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", 0x3138) + d = patch_substr(d, 16, 4, "I", 0x02CC) + d = patch_substr(d, 24, 4, "I", 84) + d += self.controlspace + self.sendManipulateStatePacket(d) + controlspacesent = 1 + + def sendDbgKdGetContext(self): + + # print("Sending DbgKdGetContextApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdGetContextApi) + self.sendManipulateStatePacket(d) + + def sendDbgKdSetContext(self, ctx): + + # print("Sending DbgKdSetContextApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdSetContextApi) + d = patch_substr(d, 16, 4, substr(ctx, 0, 4)) + d += ctx + self.sendManipulateStatePacket(d) + + def sendDbgKdReadPhysicalMemory(self, addr, readlen): + + # print("Sending DbgKdReadPhysicalMemoryApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdReadPhysicalMemoryApi) + d = patch_substr(d, 16, 4, "I", addr) + d = patch_substr(d, 24, 4, "I", readlen) + self.sendManipulateStatePacket(d) + + def sendDbgKdReadVirtualMemory(self, vaddr, readlen): + + # print("Sending DbgKdReadVirtualMemoryApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdReadVirtualMemoryApi) + d = patch_substr(d, 16, 4, "I", vaddr) + d = patch_substr(d, 24, 4, "I", readlen) + self.sendManipulateStatePacket(d) + + def sendDbgKdWriteVirtualMemory(self, vaddr, data): + writelen = len(data) + + # print("Sending DbgKdWriteVirtualMemoryApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", self.DbgKdWriteVirtualMemoryApi) + d = patch_substr(d, 16, 4, "I", vaddr) + d = patch_substr(d, 24, 4, "I", writelen) + d += data + self.sendManipulateStatePacket(d) + + def readDword(self, addr): + + # print("Reading dword at %08x\n", addr + buf = self.readVirtualMemory(addr, 4) + if len(buf) == 4: + return unpack("I", buf) + return "failed" + + def readPhysicalMemory(self, addr, length): + chunksize = 0x800 # max to request in one packet + out = bytearray([]) + while length > 0: + + if length < chunksize: + self.sendDbgKdReadPhysicalMemory(addr, length) + buf = self.waitStateManipulate(DbgKdReadPhysicalMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length = 0 + else: + self.sendDbgKdReadPhysicalMemory(addr, chunksize) + buf = self.waitStateManipulate(DbgKdReadPhysicalMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length -= chunksize + addr += chunksize + return out + + def writePhysicalMemory(self, addr, buf): + length = len(buf) + chunksize = 0x800 # max to send in one packet + offset = 0 + # FIXME: Logic sucks here.. + while length > 0: + if length < chunksize: + self.sendDbgKdWritePhysicalMemory(addr, buf) + self.waitStateManipulate(0x313E) + length = 0 + else: + self.sendDbgKdWritePhysicalMemory(addr, substr(buf, offset, chunksize)) + self.waitStateManipulate(0x313E) + length -= chunksize + offset += chunksize + addr += chunksize + + def writeVirtualMemory(self, addr, buf): + length = len(buf) + # FIXME: return unless addr && length + chunksize = 0x800 # max to send in one packet + offset = 0 + if self.pcontext["pid"] == 0: + # FIXME: Logic sucks here.. + while length > 0: + if length < chunksize: + self.sendDbgKdWriteVirtualMemory(addr, buf) + self.waitStateManipulate(self.DbgKdWriteVirtualMemoryApi) + length = 0 + else: + self.sendDbgKdWriteVirtualMemory( + addr, substr(buf, offset, chunksize) + ) + self.waitStateManipulate(self.DbgKdWriteVirtualMemoryApi) + length -= chunksize + offset += chunksize + addr += chunksize + else: + distance_to_page_boundary = 0x1000 - (addr & 0xFFF) + # FIXME: Logic sucks here.. + if distance_to_page_boundary > length: + physaddr = logical2physical(addr) + writePhysicalMemory(physaddr, buf) + return + else: + physaddr = logical2physical(addr) + buf = writePhysicalMemory( + physaddr, substr(buf, 0, distance_to_page_boundary) + ) + addr += distance_to_page_boundary + offset += distance_to_page_boundary + remainder = length - distance_to_page_boundary + + while remainder > 0: + if remainder < 0x1000: + physaddr = logical2physical(addr) + writePhysicalMemory(physaddr, substr(buf, offset, remainder)) + remainder = 0 + else: + physaddr = logical2physical(addr) + writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) + addr += 0x1000 + offset += 0x1000 + remainder -= 0x1000 + return + + # FIXME: Indentation is horrible + def readVirtualMemory(addr, length): + # FIXME: return unless addr && length + chunksize = 0x800 # max to request in one packet + out = bytearray() + buf = bytearray() + if pcontext["pid"] == 0: + while length > 0: + if length < chunksize: + sendDbgKdReadVirtualMemory(addr, length) + buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length = 0 + else: + sendDbgKdReadVirtualMemory(addr, chunksize) + buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length -= chunksize + addr += chunksize + return out + else: + distance_to_page_boundary = 0x1000 - (addr & 0xFFF) + if distance_to_page_boundary > length: + physaddr = logical2physical(addr) + return readPhysicalMemory(physaddr, length) + else: + physaddr = logical2physical(addr) + buf = readPhysicalMemory(physaddr, distance_to_page_boundary) + addr += distance_to_page_boundary + remainder = length - distance_to_page_boundary + while remainder > 0: + if remainder < 0x1000: + physaddr = logical2physical(addr) + buf += readPhysicalMemory(physaddr, remainder) + remainder = 0 + else: + physaddr = logical2physical(addr) + buf += readPhysicalMemory(physaddr, 0x1000) + addr += 0x1000 + remainder -= 0x1000 + return buf + + def sendDbgKdReboot(): + print("Sending DbgKdRebootApi packet") + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", 0x313B) + sendManipulateStatePacket(d) + + def waitStateManipulate(wanted): + if running: + return + + ptype = bytearray([]) + buf = bytearray([]) + try: + while 1: + (ptype, buf) = handlePacket(1) + if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: + api = unpack("I", substr(buf, 0, 4)) + if api == wanted: + break + except timeout: + print("Timeout waiting for %04x packet reply" % wanted) + return buf + + def getPspCidTable(): + pspcidtable = 0 + save = pcontext + pcontext = kernelcontext # this procedure is kernel context only + sendDbgKdGetVersion() + buf = waitStateManipulate(DbgKdGetVersionApi) + pddata = unpack("I", substr(buf, 48, 4)) + if pddata: + # print("Pointer to debugger data struct is at %08x\n", pddata + ddata = readDword(pddata) + if ddata != "failed": + # print("debugger data struct is at %08x\n", ddata + pspcidtable = readDword(ddata + 88) + if pspcidtable != "failed": + # print("PspCidTable is %08x\n", $pspcidtable + pcontext = save + return pspcidtable pcontext = save - return pspcidtable - pcontext = save - return 0 + return 0 + + +def main(args): + context = DebugContext() + context.connect(args.named_pipe) + + context.run() + return 1 -sendReset() -while(True): - handlePacket() - print("") +if __name__ == "__main__": -# FIXME: windpl_loop.py + def _parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "named_pipe", + help="The path to the named pipe used by xemu", + ) + return parser.parse_args() + sys.exit(main(_parse_args())) diff --git a/windpl_extra.py b/windpl_extra.py index ef33361..9447007 100644 --- a/windpl_extra.py +++ b/windpl_extra.py @@ -2,585 +2,582 @@ # 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 +# 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 +# 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 +# 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 +# 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. - -def injectSUSShellcode: - title = shift - message = shift - userbase = 0x7ffe0800 - ring0base = 0xffdf0800 - save = $pcontext - $pcontext = \%kernelcontext; # this procedure is kernel context only - messageboxa = getProcAddress( "user32.dll", "MessageBoxA" ) - - sc = - "\x6a\x00\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x6A\x00\xE8\x00\x00\x00\x00\xc3$title\x00$message\x00" - substr( $sc, 3, 4 ) = pack( "I", $userbase + 20 ) - substr( $sc, 8, 4 ) = pack( "I", $userbase + 21 + len($title) ) - substr( $sc, 15, 4 ) = pack( "I", $messageboxa - ( $userbase + 19 ) ) - - writeVirtualMemory( $ring0base, $sc ) - printf "Shellcode injected at %08x (%08x)\n", $ring0base, $userbase - $pcontext = $save - -def insertApc: - print "Searching for thread in explorer.exe\n" - my %procs = getProcessList() - thread - for ( sort keys %procs ): - n = lc( $procs{$_}{'name'] ) - print "Found $n\n" - if ( $n == "explorer.exe" ): - - #printf "Found explorer.exe\n" - $thread = shift( @{ $procs{$_}{'threads'] } ) - last - unless ($thread): - print "Failed to find thread\n" - return - printf "Using thread object at %08x\n", $thread - kernelret = findRet() - shellcode = 0x7ffe0800 - apc = "\x00" x 48 - putme = 0xffdf0900 - save = $pcontext - $pcontext = \%kernelcontext; # this procedure is kernel context only - substr( $apc, 0, 2 ) = pack( "S", 0x12 ); # type = Apc object - substr( $apc, 2, 2 ) = pack( "S", 0x30 ); # size of object = 48 bytes - substr( $apc, 8, 4 ) = pack( "I", $thread ); # ethread ptr - substr( $apc, 20, 4 ) = pack( "I", $kernelret ); # ret command in kernel - substr( $apc, 28, 4 ) = pack( "I", $shellcode ); # shellcode vaddr - substr( $apc, 36, 4 ) = pack( "I", $putme + 0x50 ); # system arg 1 - substr( $apc, 40, 4 ) = pack( "I", $putme + 0x54 ); # system arg 2 - substr( $apc, 46, 1 ) = "\x01"; # Apc mode = user mode - substr( $apc, 47, 1 ) = "\x01"; # Inserted = true (well, it will be) - - printf "Built APC object for thread at %08x\n", $thread - - #print hexformat($apc) - printf "Inserting into APC list at %08x\n", $thread + 0x3c - oldflink = readDword( $thread + 0x3c ) - printf "Replacing old Apc flink: %08x\n", $oldflink - if ( $oldflink != "failed" ): - substr( $apc, 12, 4 ) = pack( "I", $oldflink ); # flink - substr( $apc, 16, 4 ) = pack( "I", $thread + 0x3c ); # blink - - # write APC object to SharedUserSpace - writeVirtualMemory( $putme, $apc ) - - # insert our APC into the list - writeVirtualMemory( $thread + 0x3c, pack( "I", $putme + 12 ) ) - - # set UserApcPending = TRUE - writeVirtualMemory( $thread + 0x4a, "\x01" ) - printf "Inserted APC into thread at %08x\n", $thread - else: - print "Failed to insert APC\n" - $pcontext = $save - -def parsePE: - - # Some PE parsing code borrowed from Metasploit PE module - my %pe_hdr - crap - base = shift - data = readVirtualMemory( $base, 0x800 ) - $data += readVirtualMemory( $base + 0x800, 0x800 ) - - #printf "Read %d bytes of PE header at %08x\n", len($data),$base - return unless len($data) == 0x1000 - - return unless substr( $data, 0, 2 ) == "MZ" - peo = unpack( "I", substr( $data, 0x3c, 4 ) ) - return unless substr( $data, $peo, 2 ) == "PE" - - pe_hdr["MachineID"] = unpack( "S", substr( $data, $peo + 4 ) ) - pe_hdr["NumberOfSections"] = unpack( "S", substr( $data, $peo + 6 ) ) - pe_hdr["TimeDateStamp"] = unpack( "L", substr( $data, $peo + 8 ) ) - pe_hdr["PointerToSymbolTable"] = unpack( "L", substr( $data, $peo + 12 ) ) - pe_hdr["NumberOfSymbols"] = unpack( "L", substr( $data, $peo + 16 ) ) - pe_hdr["SizeOfOptionalHeader"] = unpack( "S", substr( $data, $peo + 20 ) ) - pe_hdr["Characteristics"] = unpack( "S", substr( $data, $peo + 22 ) ) - - if ( pe_hdr["SizeOfOptionalHeader"] < 224 ): - return 0 - opthdr = substr( $data, $peo + 24, pe_hdr["SizeOfOptionalHeader"] ) - pe_hdr["Magic "] = unpack( "S", substr( $opthdr, 0 ) ) - pe_hdr["MajorLinker"] = unpack( "C", substr( $opthdr, 2 ) ) - pe_hdr["MinorLinker"] = unpack( "C", substr( $opthdr, 3 ) ) - pe_hdr["SizeOfCode"] = unpack( "L", substr( $opthdr, 4 ) ) - pe_hdr["SizeOfInitialized"] = unpack( "L", substr( $opthdr, 8 ) ) - pe_hdr["SizeOfUninitialized"] = unpack( "L", substr( $opthdr, 12 ) ) - - pe_hdr["EntryPoint"] = unpack( "L", substr( $opthdr, 16 ) ) - pe_hdr["BaseOfCode"] = unpack( "L", substr( $opthdr, 20 ) ) - pe_hdr["BaseOfData"] = unpack( "L", substr( $opthdr, 24 ) ) - - pe_hdr["ImageBase"] = unpack( "L", substr( $opthdr, 28 ) ) - pe_hdr["SectionAlign"] = unpack( "L", substr( $opthdr, 32 ) ) - pe_hdr["FileAlign"] = unpack( "L", substr( $opthdr, 36 ) ) - - pe_hdr["MajorOS"] = unpack( "S", substr( $opthdr, 38 ) ) - pe_hdr["MinorOS"] = unpack( "S", substr( $opthdr, 40 ) ) - pe_hdr["MajorImage"] = unpack( "S", substr( $opthdr, 42 ) ) - pe_hdr["MinorImage"] = unpack( "S", substr( $opthdr, 44 ) ) - pe_hdr["MajorSub"] = unpack( "S", substr( $opthdr, 46 ) ) - pe_hdr["MinorSub"] = unpack( "S", substr( $opthdr, 48 ) ) - - pe_hdr["Reserved"] = unpack( "L", substr( $opthdr, 52 ) ) - pe_hdr["SizeOfImage"] = unpack( "L", substr( $opthdr, 56 ) ) - pe_hdr["SizeOfHeaders"] = unpack( "L", substr( $opthdr, 60 ) ) - pe_hdr["Checksum"] = unpack( "L", substr( $opthdr, 64 ) ) - pe_hdr["Subsystem"] = unpack( "S", substr( $opthdr, 68 ) ) - pe_hdr["DllCharacteristics"] = unpack( "S", substr( $opthdr, 70 ) ) - pe_hdr["SizeOfStackReserve"] = unpack( "L", substr( $opthdr, 72 ) ) - pe_hdr["SizeOfStackCommit"] = unpack( "L", substr( $opthdr, 76 ) ) - pe_hdr["SizeOfHeapReserve"] = unpack( "L", substr( $opthdr, 80 ) ) - pe_hdr["SizeOfHeapCommit"] = unpack( "L", substr( $opthdr, 84 ) ) - pe_hdr["LoaderFlags"] = unpack( "L", substr( $opthdr, 88 ) ) - pe_hdr["NumberOfRvaAndSizes"] = unpack( "L", substr( $opthdr, 92 ) ) - - my @RVAMAP = qw(export import resource exception certificate basereloc - debug archspec globalptr tls load_config boundimport importaddress - delayimport comruntime none) - - # parse the rva data - rva_data = substr( $opthdr, 96, pe_hdr["NumberOfRvaAndSizes"] * 8 ) - my %RVA - for x in range(0, pe_hdr{"NumberOfRvaAndSizes"): - if ( !$RVAMAP[$x] ) { $RVAMAP[$x] = "unknown_$x" $RVA{ $RVAMAP[$x] } = [ - unpack( "L", substr( $rva_data, ( $x * 8 ) ) ), - unpack( "L", substr( $rva_data, ( $x * 8 ) + 4 ) ), - ] - - # parse the section headers - sec_begn = $peo + 24 + pe_hdr["SizeOfOptionalHeader"] - sec_data = substr( $data, $sec_begn ) - - for x in range(0, pe_hdr{"NumberOfSections"): - sec_head = $sec_begn + ( $x * 40 ) - sec_name = substr( $data, $sec_head, 8 ) - $sec_name =~ s/\x00//g - if ( $sec_name == "" ) { $sec_name = ".sec$x" - #sec_name = ".sec$x" - vsize = unpack( "L", substr( $data, $sec_head + 8 ) ) - voffset = unpack( "L", substr( $data, $sec_head + 12 ) ) - rsize = unpack( "L", substr( $data, $sec_head + 16 ) ) - roffset = unpack( "L", substr( $data, $sec_head + 20 ) ) - if ( $voffset == pe_hdr["BaseOfCode"] ): - $type = "CODE" - elsif ( $voffset == pe_hdr["BaseOfData"] ): - $type = "DATA" - else: - $type = "UNKNOWN" - pe_hdr['import'] = $RVA{'import']->[0] - pe_hdr['export'] = $RVA{'export']->[0] - pe_hdr['importsize'] = $RVA{'import']->[1] - pe_hdr['exportsize'] = $RVA{'export']->[1] - return %pe_hdr - -def getImports: - base = shift; # base address of module - ioffset = shift; # offset to import table - size = shift; # size of import table - crap - - imports = readVirtualMemory( $base + $ioffset, $size ) - - for ( i = 0 ; $i < $size ; $i += 20 ): - last if substr( $imports, $i, 20 ) == "\x00" x 20 - rvaILT = unpack( "L", substr( $imports, $i, 4 ) ) - timestamp = unpack( "L", substr( $imports, $i + 4, 4 ) ) - forwarderchain = unpack( "L", substr( $imports, $i + 8, 4 ) ) - rvaModuleName = unpack( "L", substr( $imports, $i + 12, 4 ) ) - rvaIAT = unpack( "L", substr( $imports, $i + 16, 4 ) ) - modname = readVirtualMemory( $base + $rvaModuleName ) - $modname =~ s/\x00.*// - - if ($rvaILT): - count = 0 - ibuf = readVirtualMemory( $base + $rvaILT, 4 ) - IGRAB: while ( $ibuf != "\x00\x00\x00\x00" ): - importthunkRVA = unpack( "L", $ibuf ) - last IGRAB if $importthunkRVA == 0 - - if ( $importthunkRVA & 0x8000000 ): - printf "ORD: 0x%x\n", $importthunkRVA & ~0x80000000 - else: - importname = - readVirtualMemory( $importthunkRVA & ~0x80000000, 255 ) - $importname = substr( $importname, 2 ) - ( $importname, $crap ) = split( /\x00/, $importname ) - thunk = $base + $rvaIAT + ( $count * 4 ) - my ( $mod, $suff ) = split( /\./, lc($modname) ) - printf "%s (0x%x)\n", $importname, $thunk - $count++ - $ibuf = - readVirtualMemory( $base + ( $rvaILT + $count * 4 ), 4 ) - } # end while - } # end if rvaILT - else: - count = 0 - ibuf = readVirtualMemory( $base + $rvaIAT, 4 ) - IGRAB: while ( $ibuf != "\x00\x00\x00\x00" ): - importthunkRVA = unpack( "L", $ibuf ) - last IGRAB if $importthunkRVA == 0 - importname = - readVirtualMemory( $base + $importthunkRVA, 255 ) - $importname = substr( $importname, 2 ) - ( $importname, $crap ) = split( /\x00/, $importname ) - thunk = $base + $rvaIAT + ( $count * 4 ) - my ( $mod, $suff ) = split( /\./, lc($modname) ) - printf "%s (0x%x)\n", $importname, $thunk - - $count++ - $ibuf = readVirtualMemory( $base + $rvaIAT, 4 ) - } # end while - } # end if rvaILT - } # end if import module - -def locateExportNameInTable: - procname = shift - base = shift - eoffset = shift - size = shift - my %exp = getExports( $base, $eoffset, $size ) - for ( keys %exp ): - if ( $exp{$_} == $procname ): - return $_ - -def getExports: - base = shift - eoffset = shift - size = shift - my %exportlist - - return unless $base && $eoffset && $size - exports = readVirtualMemory( $base + $eoffset, $size ) - - ebase = unpack( "I", substr( $exports, 16, 4 ) ) - enumfuncs = unpack( "I", substr( $exports, 20, 4 ) ) - enumnames = unpack( "I", substr( $exports, 24, 4 ) ) - EATrva = unpack( "I", substr( $exports, 28, 4 ) ) - ENTrva = unpack( "I", substr( $exports, 32, 4 ) ) - EOTrva = unpack( "I", substr( $exports, 36, 4 ) ) - my ( @exportnames, @exportordinals, @exportfunctions ) - - # get ascii name table boundaries - nbegin = readDword( $base + $ENTrva ) - nend = readDword( $base + $ENTrva + ( ( $enumnames - 1 ) * 4 ) ) - lastname = readVirtualMemory( $nend + $base, 255 ) - term = index( $lastname, "\x00" ) - $nend += $term - namebuf = readVirtualMemory( $nbegin + $base, $nend - $nbegin ) - - #print hexasc($namebuf) - - nametable = readVirtualMemory( $ENTrva + $base, $enumnames * 4 ) - functable = readVirtualMemory( $EATrva + $base, $enumfuncs * 4 ) - ordtable = readVirtualMemory( $EOTrva + $base, $enumfuncs * 2 ) - - for ( 0 .. $enumnames - 1 ): - n = unpack( "L", substr( $nametable, $_ * 4, 4 ) ) - if ( $n >= $nbegin ): - ename = substr( $namebuf, $n - $nbegin, 255 ) - $ename =~ s/\x00.*//g - push( @exportnames, $ename ) - - #printf "Adding name index %d (begins at %08x: raw %08x) %s\n", - #$_, $n, $n-$nbegin, $ename - for ( 0 .. $enumfuncs - 1 ): - eord = unpack( "S", substr( $ordtable, $_ * 2, 2 ) ) - push( @exportordinals, $eord ) - for ( 0 .. $enumfuncs - 1 ): - eaddr = unpack( "L", substr( $functable, $_ * 4, 4 ) ) - push( @exportfunctions, $eaddr ) - - for o ( 0 .. $#exportnames ): - name = $exportnames[$o] - ord = $exportordinals[$o] - addr = $exportfunctions[$ord] - $name ||= $ord - $exportlist{ addr + $base } = $name - return %exportlist - -def findRet: - hp - pos - save = $pcontext - $pcontext = \%kernelcontext; # this procedure is kernel context only - for ( 0 .. 100 ): - $hp = $_ - buf = - readVirtualMemory( $kernelbase + 0x1000 + ( $hp * 0x800 ), 0x800 ) - $pos = index( $buf, "\xc3" ) - last unless $pos == -1 - ret = $kernelbase + 0x1000 + ( $hp * 0x800 ) + $pos - printf "Found RETN instruction at %08x", $ret - $pcontext = $save - return $ret - -def logical2physical: - logical = shift; # a virtual address in a process - pdb = pcontext['dtb'] - return unless $pdb - offset = $logical & 0xfff; # save byte offset - pde = ( $logical >> 22 ) & 0x3ff - pte = ( $logical >> 12 ) & 0x3ff - buf = readPhysicalMemory( $pdb + ( $pde * 4 ), 4 ) - valid = unpack( "I", $buf ) & 0x1 - if ($valid): - ptb = unpack( "I", $buf ) & 0xfffff000 - -#printf "Seeking to PTB %08x + PTE %03x * 4 = %08x\n", $ptb, $pte, $ptb + ($pte * 4) - $buf = readPhysicalMemory( $ptb + ( $pte * 4 ), 4 ) - $valid = unpack( "I", $buf ) & 0x1 - if ($valid): - phys = unpack( "I", $buf ) & 0xfffff000 - return ( $phys | offset ); #restore byte offset - printf "Invalid PTE found for va %08x: %08x\n", $logical, - unpack( "I", $buf ) - return 0 - -def listExports: - base = shift - my %pe = parsePE($base) - if ( $pe{'export'] && $pe{'exportsize'] ): - printf "Exports found in PE file at %08x:\n", $base - my %exp = getExports( $base, $pe{'export'], $pe{'exportsize'] ) - for ( sort keys %exp ): - printf "%08x:%s\n", $_, $exp{$_} - else: - print "No export table found\n" - - - -def getEprocess: - pid = shift - j = ( $pid >> 18 ) & 0xff - k = ( $pid >> 10 ) & 0xff - l = ( $pid >> 2 ) & 0xff - save = $pcontext - $pcontext = \%kernelcontext; # this procedure is kernel context only - #print "Finding eprocess[$j][$k][$l]\n" - pspcidtable = getPspCidTable() - - if ($pspcidtable): - subtable - if ( $version >= 6.0 ): - $subtable = readDword($pspcidtable) - else: - table - ptable = readDword($pspcidtable) - if ( $ptable != "failed" ): - - #print("ptable: %08x\n", $ptable - $table = readDword( $ptable + 8 ) - if ( $table != "failed" ): - - #print("table: %08x\n", $table - $subtable = readDword( $table + ( $j * 4 ) ) - if ( ($subtable) && ( $subtable != "failed" ) ): - - #print("subtable: %08x\n", $subtable - subsubtable = readDword( $subtable + ( $k * 4 ) ) - if ( $subsubtable != "failed" ): - - #print("subsubtable: %08x\n", $subsubtable - entry = readDword( $subsubtable + ( $l * 8 ) ) - if ( $entry != "failed" ): - if ( $version < 6 ): - $entry |= 0x80000000; # lock bit - else: - $entry &= 0xfffffffe; # lock bit - - #print("eprocess of pid 0x%x starts at %08x\n", $pid, $entry - $pcontext = $save - return $entry - $pcontext = $save +# def injectSUSShellcode(title, message): +# userbase = 0x7ffe0800 +# ring0base = 0xffdf0800 +# save = $pcontext +# $pcontext = \%kernelcontext; # this procedure is kernel context only +# messageboxa = getProcAddress( "user32.dll", "MessageBoxA" ) +# +# sc = +# "\x6a\x00\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x6A\x00\xE8\x00\x00\x00\x00\xc3$title\x00$message\x00" +# substr( $sc, 3, 4 ) = pack( "I", $userbase + 20 ) +# substr( $sc, 8, 4 ) = pack( "I", $userbase + 21 + len($title) ) +# substr( $sc, 15, 4 ) = pack( "I", $messageboxa - ( $userbase + 19 ) ) +# +# writeVirtualMemory( $ring0base, $sc ) +# printf "Shellcode injected at %08x (%08x)\n", $ring0base, $userbase +# $pcontext = $save + +# def insertApc: +# print "Searching for thread in explorer.exe\n" +# my %procs = getProcessList() +# thread +# for ( sort keys %procs ): +# n = lc( $procs{$_}{'name'] ) +# print "Found $n\n" +# if ( $n == "explorer.exe" ): +# +# #printf "Found explorer.exe\n" +# $thread = shift( @{ $procs{$_}{'threads'] } ) +# last +# unless ($thread): +# print "Failed to find thread\n" +# return +# printf "Using thread object at %08x\n", $thread +# kernelret = findRet() +# shellcode = 0x7ffe0800 +# apc = "\x00" x 48 +# putme = 0xffdf0900 +# save = $pcontext +# $pcontext = \%kernelcontext; # this procedure is kernel context only +# substr( $apc, 0, 2 ) = pack( "S", 0x12 ); # type = Apc object +# substr( $apc, 2, 2 ) = pack( "S", 0x30 ); # size of object = 48 bytes +# substr( $apc, 8, 4 ) = pack( "I", $thread ); # ethread ptr +# substr( $apc, 20, 4 ) = pack( "I", $kernelret ); # ret command in kernel +# substr( $apc, 28, 4 ) = pack( "I", $shellcode ); # shellcode vaddr +# substr( $apc, 36, 4 ) = pack( "I", $putme + 0x50 ); # system arg 1 +# substr( $apc, 40, 4 ) = pack( "I", $putme + 0x54 ); # system arg 2 +# substr( $apc, 46, 1 ) = "\x01"; # Apc mode = user mode +# substr( $apc, 47, 1 ) = "\x01"; # Inserted = true (well, it will be) +# +# printf "Built APC object for thread at %08x\n", $thread +# +# #print hexformat($apc) +# printf "Inserting into APC list at %08x\n", $thread + 0x3c +# oldflink = readDword( $thread + 0x3c ) +# printf "Replacing old Apc flink: %08x\n", $oldflink +# if ( $oldflink != "failed" ): +# substr( $apc, 12, 4 ) = pack( "I", $oldflink ); # flink +# substr( $apc, 16, 4 ) = pack( "I", $thread + 0x3c ); # blink +# +# # write APC object to SharedUserSpace +# writeVirtualMemory( $putme, $apc ) +# +# # insert our APC into the list +# writeVirtualMemory( $thread + 0x3c, pack( "I", $putme + 12 ) ) +# +# # set UserApcPending = TRUE +# writeVirtualMemory( $thread + 0x4a, "\x01" ) +# printf "Inserted APC into thread at %08x\n", $thread +# else: +# print "Failed to insert APC\n" +# $pcontext = $save + +# def parsePE: +# +# # Some PE parsing code borrowed from Metasploit PE module +# my %pe_hdr +# crap +# base = shift +# data = readVirtualMemory( $base, 0x800 ) +# $data += readVirtualMemory( $base + 0x800, 0x800 ) +# +# #printf "Read %d bytes of PE header at %08x\n", len($data),$base +# return unless len($data) == 0x1000 +# +# return unless substr( $data, 0, 2 ) == "MZ" +# peo = unpack( "I", substr( $data, 0x3c, 4 ) ) +# return unless substr( $data, $peo, 2 ) == "PE" +# +# pe_hdr["MachineID"] = unpack( "S", substr( $data, $peo + 4 ) ) +# pe_hdr["NumberOfSections"] = unpack( "S", substr( $data, $peo + 6 ) ) +# pe_hdr["TimeDateStamp"] = unpack( "L", substr( $data, $peo + 8 ) ) +# pe_hdr["PointerToSymbolTable"] = unpack( "L", substr( $data, $peo + 12 ) ) +# pe_hdr["NumberOfSymbols"] = unpack( "L", substr( $data, $peo + 16 ) ) +# pe_hdr["SizeOfOptionalHeader"] = unpack( "S", substr( $data, $peo + 20 ) ) +# pe_hdr["Characteristics"] = unpack( "S", substr( $data, $peo + 22 ) ) +# +# if ( pe_hdr["SizeOfOptionalHeader"] < 224 ): +# return 0 +# opthdr = substr( $data, $peo + 24, pe_hdr["SizeOfOptionalHeader"] ) +# pe_hdr["Magic "] = unpack( "S", substr( $opthdr, 0 ) ) +# pe_hdr["MajorLinker"] = unpack( "C", substr( $opthdr, 2 ) ) +# pe_hdr["MinorLinker"] = unpack( "C", substr( $opthdr, 3 ) ) +# pe_hdr["SizeOfCode"] = unpack( "L", substr( $opthdr, 4 ) ) +# pe_hdr["SizeOfInitialized"] = unpack( "L", substr( $opthdr, 8 ) ) +# pe_hdr["SizeOfUninitialized"] = unpack( "L", substr( $opthdr, 12 ) ) +# +# pe_hdr["EntryPoint"] = unpack( "L", substr( $opthdr, 16 ) ) +# pe_hdr["BaseOfCode"] = unpack( "L", substr( $opthdr, 20 ) ) +# pe_hdr["BaseOfData"] = unpack( "L", substr( $opthdr, 24 ) ) +# +# pe_hdr["ImageBase"] = unpack( "L", substr( $opthdr, 28 ) ) +# pe_hdr["SectionAlign"] = unpack( "L", substr( $opthdr, 32 ) ) +# pe_hdr["FileAlign"] = unpack( "L", substr( $opthdr, 36 ) ) +# +# pe_hdr["MajorOS"] = unpack( "S", substr( $opthdr, 38 ) ) +# pe_hdr["MinorOS"] = unpack( "S", substr( $opthdr, 40 ) ) +# pe_hdr["MajorImage"] = unpack( "S", substr( $opthdr, 42 ) ) +# pe_hdr["MinorImage"] = unpack( "S", substr( $opthdr, 44 ) ) +# pe_hdr["MajorSub"] = unpack( "S", substr( $opthdr, 46 ) ) +# pe_hdr["MinorSub"] = unpack( "S", substr( $opthdr, 48 ) ) +# +# pe_hdr["Reserved"] = unpack( "L", substr( $opthdr, 52 ) ) +# pe_hdr["SizeOfImage"] = unpack( "L", substr( $opthdr, 56 ) ) +# pe_hdr["SizeOfHeaders"] = unpack( "L", substr( $opthdr, 60 ) ) +# pe_hdr["Checksum"] = unpack( "L", substr( $opthdr, 64 ) ) +# pe_hdr["Subsystem"] = unpack( "S", substr( $opthdr, 68 ) ) +# pe_hdr["DllCharacteristics"] = unpack( "S", substr( $opthdr, 70 ) ) +# pe_hdr["SizeOfStackReserve"] = unpack( "L", substr( $opthdr, 72 ) ) +# pe_hdr["SizeOfStackCommit"] = unpack( "L", substr( $opthdr, 76 ) ) +# pe_hdr["SizeOfHeapReserve"] = unpack( "L", substr( $opthdr, 80 ) ) +# pe_hdr["SizeOfHeapCommit"] = unpack( "L", substr( $opthdr, 84 ) ) +# pe_hdr["LoaderFlags"] = unpack( "L", substr( $opthdr, 88 ) ) +# pe_hdr["NumberOfRvaAndSizes"] = unpack( "L", substr( $opthdr, 92 ) ) +# +# my @RVAMAP = qw(export import resource exception certificate basereloc +# debug archspec globalptr tls load_config boundimport importaddress +# delayimport comruntime none) +# +# # parse the rva data +# rva_data = substr( $opthdr, 96, pe_hdr["NumberOfRvaAndSizes"] * 8 ) +# my %RVA +# for x in range(0, pe_hdr{"NumberOfRvaAndSizes"): +# if ( !$RVAMAP[$x] ) { $RVAMAP[$x] = "unknown_$x" $RVA{ $RVAMAP[$x] } = [ +# unpack( "L", substr( $rva_data, ( $x * 8 ) ) ), +# unpack( "L", substr( $rva_data, ( $x * 8 ) + 4 ) ), +# ] +# +# # parse the section headers +# sec_begn = $peo + 24 + pe_hdr["SizeOfOptionalHeader"] +# sec_data = substr( $data, $sec_begn ) +# +# for x in range(0, pe_hdr{"NumberOfSections"): +# sec_head = $sec_begn + ( $x * 40 ) +# sec_name = substr( $data, $sec_head, 8 ) +# $sec_name =~ s/\x00//g +# if ( $sec_name == "" ) { $sec_name = ".sec$x" +# #sec_name = ".sec$x" +# vsize = unpack( "L", substr( $data, $sec_head + 8 ) ) +# voffset = unpack( "L", substr( $data, $sec_head + 12 ) ) +# rsize = unpack( "L", substr( $data, $sec_head + 16 ) ) +# roffset = unpack( "L", substr( $data, $sec_head + 20 ) ) +# if ( $voffset == pe_hdr["BaseOfCode"] ): +# $type = "CODE" +# elsif ( $voffset == pe_hdr["BaseOfData"] ): +# $type = "DATA" +# else: +# $type = "UNKNOWN" +# pe_hdr['import'] = $RVA{'import']->[0] +# pe_hdr['export'] = $RVA{'export']->[0] +# pe_hdr['importsize'] = $RVA{'import']->[1] +# pe_hdr['exportsize'] = $RVA{'export']->[1] +# return %pe_hdr + +# def getImports: +# base = shift; # base address of module +# ioffset = shift; # offset to import table +# size = shift; # size of import table +# crap +# +# imports = readVirtualMemory( $base + $ioffset, $size ) +# +# for ( i = 0 ; $i < $size ; $i += 20 ): +# last if substr( $imports, $i, 20 ) == "\x00" x 20 +# rvaILT = unpack( "L", substr( $imports, $i, 4 ) ) +# timestamp = unpack( "L", substr( $imports, $i + 4, 4 ) ) +# forwarderchain = unpack( "L", substr( $imports, $i + 8, 4 ) ) +# rvaModuleName = unpack( "L", substr( $imports, $i + 12, 4 ) ) +# rvaIAT = unpack( "L", substr( $imports, $i + 16, 4 ) ) +# modname = readVirtualMemory( $base + $rvaModuleName ) +# $modname =~ s/\x00.*// +# +# if ($rvaILT): +# count = 0 +# ibuf = readVirtualMemory( $base + $rvaILT, 4 ) +# IGRAB: while ( $ibuf != "\x00\x00\x00\x00" ): +# importthunkRVA = unpack( "L", $ibuf ) +# last IGRAB if $importthunkRVA == 0 +# +# if ( $importthunkRVA & 0x8000000 ): +# printf "ORD: 0x%x\n", $importthunkRVA & ~0x80000000 +# else: +# importname = +# readVirtualMemory( $importthunkRVA & ~0x80000000, 255 ) +# $importname = substr( $importname, 2 ) +# ( $importname, $crap ) = split( /\x00/, $importname ) +# thunk = $base + $rvaIAT + ( $count * 4 ) +# my ( $mod, $suff ) = split( /\./, lc($modname) ) +# printf "%s (0x%x)\n", $importname, $thunk +# $count++ +# $ibuf = +# readVirtualMemory( $base + ( $rvaILT + $count * 4 ), 4 ) +# } # end while +# } # end if rvaILT +# else: +# count = 0 +# ibuf = readVirtualMemory( $base + $rvaIAT, 4 ) +# IGRAB: while ( $ibuf != "\x00\x00\x00\x00" ): +# importthunkRVA = unpack( "L", $ibuf ) +# last IGRAB if $importthunkRVA == 0 +# importname = +# readVirtualMemory( $base + $importthunkRVA, 255 ) +# $importname = substr( $importname, 2 ) +# ( $importname, $crap ) = split( /\x00/, $importname ) +# thunk = $base + $rvaIAT + ( $count * 4 ) +# my ( $mod, $suff ) = split( /\./, lc($modname) ) +# printf "%s (0x%x)\n", $importname, $thunk +# +# $count++ +# $ibuf = readVirtualMemory( $base + $rvaIAT, 4 ) +# } # end while +# } # end if rvaILT +# } # end if import module + +# def locateExportNameInTable: +# procname = shift +# base = shift +# eoffset = shift +# size = shift +# my %exp = getExports( $base, $eoffset, $size ) +# for ( keys %exp ): +# if ( $exp{$_} == $procname ): +# return $_ + +# def getExports: +# base = shift +# eoffset = shift +# size = shift +# my %exportlist +# +# return unless $base && $eoffset && $size +# exports = readVirtualMemory( $base + $eoffset, $size ) +# +# ebase = unpack( "I", substr( $exports, 16, 4 ) ) +# enumfuncs = unpack( "I", substr( $exports, 20, 4 ) ) +# enumnames = unpack( "I", substr( $exports, 24, 4 ) ) +# EATrva = unpack( "I", substr( $exports, 28, 4 ) ) +# ENTrva = unpack( "I", substr( $exports, 32, 4 ) ) +# EOTrva = unpack( "I", substr( $exports, 36, 4 ) ) +# my ( @exportnames, @exportordinals, @exportfunctions ) +# +# # get ascii name table boundaries +# nbegin = readDword( $base + $ENTrva ) +# nend = readDword( $base + $ENTrva + ( ( $enumnames - 1 ) * 4 ) ) +# lastname = readVirtualMemory( $nend + $base, 255 ) +# term = index( $lastname, "\x00" ) +# $nend += $term +# namebuf = readVirtualMemory( $nbegin + $base, $nend - $nbegin ) +# +# #print hexasc($namebuf) +# +# nametable = readVirtualMemory( $ENTrva + $base, $enumnames * 4 ) +# functable = readVirtualMemory( $EATrva + $base, $enumfuncs * 4 ) +# ordtable = readVirtualMemory( $EOTrva + $base, $enumfuncs * 2 ) +# +# for ( 0 .. $enumnames - 1 ): +# n = unpack( "L", substr( $nametable, $_ * 4, 4 ) ) +# if ( $n >= $nbegin ): +# ename = substr( $namebuf, $n - $nbegin, 255 ) +# $ename =~ s/\x00.*//g +# push( @exportnames, $ename ) +# +# #printf "Adding name index %d (begins at %08x: raw %08x) %s\n", +# #$_, $n, $n-$nbegin, $ename +# for ( 0 .. $enumfuncs - 1 ): +# eord = unpack( "S", substr( $ordtable, $_ * 2, 2 ) ) +# push( @exportordinals, $eord ) +# for ( 0 .. $enumfuncs - 1 ): +# eaddr = unpack( "L", substr( $functable, $_ * 4, 4 ) ) +# push( @exportfunctions, $eaddr ) +# +# for o ( 0 .. $#exportnames ): +# name = $exportnames[$o] +# ord = $exportordinals[$o] +# addr = $exportfunctions[$ord] +# $name ||= $ord +# $exportlist{ addr + $base } = $name +# return %exportlist + +# def findRet: +# hp +# pos +# save = $pcontext +# $pcontext = \%kernelcontext; # this procedure is kernel context only +# for ( 0 .. 100 ): +# $hp = $_ +# buf = +# readVirtualMemory( $kernelbase + 0x1000 + ( $hp * 0x800 ), 0x800 ) +# $pos = index( $buf, "\xc3" ) +# last unless $pos == -1 +# ret = $kernelbase + 0x1000 + ( $hp * 0x800 ) + $pos +# printf "Found RETN instruction at %08x", $ret +# $pcontext = $save +# return $ret + + +def logical2physical(logical): + # pdb = pcontext['dtb'] + # return unless $pdb + # offset = $logical & 0xfff; # save byte offset + # pde = ( $logical >> 22 ) & 0x3ff + # pte = ( $logical >> 12 ) & 0x3ff + # buf = readPhysicalMemory( $pdb + ( $pde * 4 ), 4 ) + # valid = unpack( "I", $buf ) & 0x1 + # if ($valid): + # ptb = unpack( "I", $buf ) & 0xfffff000 + # + # #printf "Seeking to PTB %08x + PTE %03x * 4 = %08x\n", $ptb, $pte, $ptb + ($pte * 4) + # $buf = readPhysicalMemory( $ptb + ( $pte * 4 ), 4 ) + # $valid = unpack( "I", $buf ) & 0x1 + # if ($valid): + # phys = unpack( "I", $buf ) & 0xfffff000 + # return ( $phys | offset ); #restore byte offset + # printf "Invalid PTE found for va %08x: %08x\n", $logical, + # unpack( "I", $buf ) + print("TODO: IMPLEMENT LOGICAL2PHYSICAL") return 0 -def getProcessList: - ep - my %prochash - my ( $listoffset, $pidoffset, $nameoffset, $timeoffset ) - my ( $threadoffset, $peboffset, $dtboffset ) - save = $pcontext - $pcontext = \%kernelcontext; # this procedure is kernel context only - if ( $version >= 6.0 ): - - # xp, vista - $ep = getEprocess(4) - $listoffset = 0x88 - $pidoffset = 0x84 - $nameoffset = 0x174 - $timeoffset = 0x70 - $threadoffset = 0x1b0 - $peboffset = 0x1b0 - $dtboffset = 0x18 - else: - - # win2k - $ep = getEprocess(8) - $listoffset = 0xa0 - $pidoffset = 0x9c - $nameoffset = 0x1fc - $timeoffset = 0x88 - $threadoffset = 0x1a4 - $peboffset = 0x1b0 - $dtboffset = 0x18 - - #print("System ep: %08x\n", $ep - unless ($ep) { $pcontext = $save; return my @procs = walkList( $ep + $listoffset, $listoffset ) - for eproc (@procs): - e = readVirtualMemory( $eproc, 0x21c ) - if ( len($e) == 0x21c ): - - #print hexformat($e) - name = substr( $e, $nameoffset, 16 ) - $name =~ s/\x00//g - pid = unpack( "I", substr( $e, $pidoffset, 4 ) ) - next unless ($pid) && ( $pid < 0xffff ) - dtb = unpack( "I", substr( $e, $dtboffset, 4 ) ) - peb = unpack( "I", substr( $e, $peboffset, 4 ) ) - created = ft2Time( substr( $e, $timeoffset, 8 ) ) - my @threads = - walkList( unpack( "I", substr( $e, 0x50, 4 ) ), $threadoffset ) - if (@threads): - $prochash{$eproc}{'pid'] = $pid - $prochash{$eproc}{'name'] = $name - $prochash{$eproc}{'created'] = $created - $prochash{$eproc}{'dtb'] = $dtb - $prochash{$eproc}{'peb'] = $peb - @{ $prochash{$eproc}{'threads'] } = @threads - $pcontext = $save - return %prochash - -def ft2Time: - ft = shift - return 0 unless len($ft) == 8 - ch = 0x019db1de - cl = 0xd53e8000 - lo = unpack( "I", substr( $ft, 0, 4 ) ) - hi = unpack( "I", substr( $ft, 4, 4 ) ) - return 0 if ( $hi < $ch ) || ( ( $hi == $ch ) && ( $lo < $cl ) ) - return ( ( ( ( $hi * 0x10000 ) * 0x10000 ) + $lo ) - - ( ( ( $ch * 0x10000 ) * 0x10000 ) + $cl ) ) / 10000000 - -def walkList: - my @ret - flink = shift; # address of LIST_ENTRY in struct - offset = shift; # offset to LIST_ENTRY from beginning of struct - top = $flink - while ( $flink != 0 ): - push( @ret, $flink - offset ) - $flink = readDword($flink) - last if ( $flink == $top ) || ( $flink == "failed" ) - return @ret - - -def getProcAddress: - module = shift - procname = shift - save = $pcontext - addr - - # get eprocess list, start with bottom process - my %procs = getProcessList() - for ( sort keys %procs ): - dtb = $procs{$_}{'dtb'] - peb = $procs{$_}{'peb'] - pid = $procs{$_}{'pid'] - eprocess = $_ - next unless $peb - print("Searching for %s in modules of pid %x (eprocess is %08x)\n", - $procname, $pid, $eprocess - - # set process context - processcontext['dtb'] = $dtb - processcontext['pid'] = $pid - processcontext['peb'] = $peb - processcontext['eprocess'] = $eprocess - - $pcontext = \%processcontext - - my %modules = getUserModules() - for ( sort keys %modules ): - if ( ( $modules{$_}{'name'] =~ /^$module$/i ) - || ( $modules{$_}{'name'] =~ /^$module\.dll/i ) ) - : - print("Found instance of %s at %08x\n", $module, $_ - my %pe = parsePE($_) - addr = - locateExportNameInTable( $procname, $_, $pe{'export'], - $pe{'exportsize'] ) - goto DONEGOTPROC - DONEGOTPROC: - - # back to original process context - $pcontext = $save - return addr - -def getUserModules: - my %modules - - # read PEB into buf - peb = pcontext['peb'] - pebdata = readVirtualMemory( $peb, 0x300 ) - next unless len($pebdata) == 0x300 +# def listExports(base): +# my %pe = parsePE($base) +# if ( $pe{'export'] && $pe{'exportsize'] ): +# printf "Exports found in PE file at %08x:\n", $base +# my %exp = getExports( $base, $pe{'export'], $pe{'exportsize'] ) +# for ( sort keys %exp ): +# printf "%08x:%s\n", $_, $exp{$_} +# else: +# print "No export table found\n" + + +# def getEprocess: +# pid = shift +# j = ( $pid >> 18 ) & 0xff +# k = ( $pid >> 10 ) & 0xff +# l = ( $pid >> 2 ) & 0xff +# save = $pcontext +# $pcontext = \%kernelcontext; # this procedure is kernel context only +# #print "Finding eprocess[$j][$k][$l]\n" +# pspcidtable = getPspCidTable() +# +# if ($pspcidtable): +# subtable +# if ( $version >= 6.0 ): +# $subtable = readDword($pspcidtable) +# else: +# table +# ptable = readDword($pspcidtable) +# if ( $ptable != "failed" ): +# +# #print("ptable: %08x\n", $ptable +# $table = readDword( $ptable + 8 ) +# if ( $table != "failed" ): +# +# #print("table: %08x\n", $table +# $subtable = readDword( $table + ( $j * 4 ) ) +# if ( ($subtable) && ( $subtable != "failed" ) ): +# +# #print("subtable: %08x\n", $subtable +# subsubtable = readDword( $subtable + ( $k * 4 ) ) +# if ( $subsubtable != "failed" ): +# +# #print("subsubtable: %08x\n", $subsubtable +# entry = readDword( $subsubtable + ( $l * 8 ) ) +# if ( $entry != "failed" ): +# if ( $version < 6 ): +# $entry |= 0x80000000; # lock bit +# else: +# $entry &= 0xfffffffe; # lock bit +# +# #print("eprocess of pid 0x%x starts at %08x\n", $pid, $entry +# $pcontext = $save +# return $entry +# $pcontext = $save +# return 0 + +# def getProcessList: +# ep +# my %prochash +# my ( $listoffset, $pidoffset, $nameoffset, $timeoffset ) +# my ( $threadoffset, $peboffset, $dtboffset ) +# save = $pcontext +# $pcontext = \%kernelcontext; # this procedure is kernel context only +# if ( $version >= 6.0 ): +# +# # xp, vista +# $ep = getEprocess(4) +# $listoffset = 0x88 +# $pidoffset = 0x84 +# $nameoffset = 0x174 +# $timeoffset = 0x70 +# $threadoffset = 0x1b0 +# $peboffset = 0x1b0 +# $dtboffset = 0x18 +# else: +# +# # win2k +# $ep = getEprocess(8) +# $listoffset = 0xa0 +# $pidoffset = 0x9c +# $nameoffset = 0x1fc +# $timeoffset = 0x88 +# $threadoffset = 0x1a4 +# $peboffset = 0x1b0 +# $dtboffset = 0x18 +# +# #print("System ep: %08x\n", $ep +# unless ($ep) { $pcontext = $save; return my @procs = walkList( $ep + $listoffset, $listoffset ) +# for eproc (@procs): +# e = readVirtualMemory( $eproc, 0x21c ) +# if ( len($e) == 0x21c ): +# +# #print hexformat($e) +# name = substr( $e, $nameoffset, 16 ) +# $name =~ s/\x00//g +# pid = unpack( "I", substr( $e, $pidoffset, 4 ) ) +# next unless ($pid) && ( $pid < 0xffff ) +# dtb = unpack( "I", substr( $e, $dtboffset, 4 ) ) +# peb = unpack( "I", substr( $e, $peboffset, 4 ) ) +# created = ft2Time( substr( $e, $timeoffset, 8 ) ) +# +# my @threads = +# walkList( unpack( "I", substr( $e, 0x50, 4 ) ), $threadoffset ) +# if (@threads): +# $prochash{$eproc}{'pid'] = $pid +# $prochash{$eproc}{'name'] = $name +# $prochash{$eproc}{'created'] = $created +# $prochash{$eproc}{'dtb'] = $dtb +# $prochash{$eproc}{'peb'] = $peb +# @{ $prochash{$eproc}{'threads'] } = @threads +# $pcontext = $save +# return %prochash + +# def ft2Time: +# ft = shift +# return 0 unless len($ft) == 8 +# ch = 0x019db1de +# cl = 0xd53e8000 +# lo = unpack( "I", substr( $ft, 0, 4 ) ) +# hi = unpack( "I", substr( $ft, 4, 4 ) ) +# return 0 if ( $hi < $ch ) || ( ( $hi == $ch ) && ( $lo < $cl ) ) +# return ( ( ( ( $hi * 0x10000 ) * 0x10000 ) + $lo ) - +# ( ( ( $ch * 0x10000 ) * 0x10000 ) + $cl ) ) / 10000000 + +# def walkList: +# my @ret +# flink = shift; # address of LIST_ENTRY in struct +# offset = shift; # offset to LIST_ENTRY from beginning of struct +# top = $flink +# while ( $flink != 0 ): +# push( @ret, $flink - offset ) +# $flink = readDword($flink) +# last if ( $flink == $top ) || ( $flink == "failed" ) +# return @ret + + +# def getProcAddress: +# module = shift +# procname = shift +# save = $pcontext +# addr +# +# # get eprocess list, start with bottom process +# my %procs = getProcessList() +# for ( sort keys %procs ): +# dtb = $procs{$_}{'dtb'] +# peb = $procs{$_}{'peb'] +# pid = $procs{$_}{'pid'] +# eprocess = $_ +# next unless $peb +# print("Searching for %s in modules of pid %x (eprocess is %08x)\n", +# $procname, $pid, $eprocess +# +# # set process context +# processcontext['dtb'] = $dtb +# processcontext['pid'] = $pid +# processcontext['peb'] = $peb +# processcontext['eprocess'] = $eprocess +# +# $pcontext = \%processcontext +# +# my %modules = getUserModules() +# for ( sort keys %modules ): +# if ( ( $modules{$_}{'name'] =~ /^$module$/i ) +# || ( $modules{$_}{'name'] =~ /^$module\.dll/i ) ) +# : +# print("Found instance of %s at %08x\n", $module, $_ +# my %pe = parsePE($_) +# addr = +# locateExportNameInTable( $procname, $_, $pe{'export'], +# $pe{'exportsize'] ) +# goto DONEGOTPROC +# DONEGOTPROC: +# +# # back to original process context +# $pcontext = $save +# return addr - # get module list - mptr = unpack( "I", substr( $pebdata, 0x0c, 4 ) ) - modulelist = readDword( $mptr + 0x14, 4 ) - my @modlist = walkList($modulelist) - for mod (@modlist): - buf = readVirtualMemory( $mod, 0x34 ) - if ( len($buf) == 0x34 ): - base = unpack( "I", substr( $buf, 0x10, 4 ) ) - next if $base == 0 - entry = unpack( "I", substr( $buf, 0x14, 4 ) ) - size = unpack( "I", substr( $buf, 0x18, 4 ) ) - path = substr( $buf, 0x1c, 8 ) - name = substr( $buf, 0x24, 8 ) - $modules{$base}{'name'] = unicodeStructToAscii($name) - $modules{$base}{'path'] = unicodeStructToAscii($path) - $modules{$base}{'size'] = $size - $modules{$base}{'entry'] = $entry - return %modules +# def getUserModules: +# my %modules +# +# # read PEB into buf +# peb = pcontext['peb'] +# pebdata = readVirtualMemory( $peb, 0x300 ) +# next unless len($pebdata) == 0x300 +# +# # get module list +# mptr = unpack( "I", substr( $pebdata, 0x0c, 4 ) ) +# modulelist = readDword( $mptr + 0x14, 4 ) +# my @modlist = walkList($modulelist) +# for mod (@modlist): +# buf = readVirtualMemory( $mod, 0x34 ) +# if ( len($buf) == 0x34 ): +# base = unpack( "I", substr( $buf, 0x10, 4 ) ) +# next if $base == 0 +# entry = unpack( "I", substr( $buf, 0x14, 4 ) ) +# size = unpack( "I", substr( $buf, 0x18, 4 ) ) +# path = substr( $buf, 0x1c, 8 ) +# name = substr( $buf, 0x24, 8 ) +# $modules{$base}{'name'] = unicodeStructToAscii($name) +# $modules{$base}{'path'] = unicodeStructToAscii($path) +# $modules{$base}{'size'] = $size +# $modules{$base}{'entry'] = $entry +# return %modules diff --git a/windpl_loop.py b/windpl_loop.py index 1a39d0e..f1bee88 100644 --- a/windpl_loop.py +++ b/windpl_loop.py @@ -2,259 +2,259 @@ # 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 +# 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 +# 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 +# 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 +# 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. +import re - - - - -#s = IO::Select->new() -#$s->add( \*STDIN ) -#if (serial): +# s = IO::Select->new() +# $s->add( \*STDIN ) +# if (serial): # $s->add( \*FH ) -#elif (client): +# elif (client): # $s->add(client) -sendReset() - def match(text, pattern): - assert(pattern[0] == '/') - assert(pattern[-1] == '/') - return re.search(pattern[1:-1], text) -while (ready = $s->can_read ): - for fh (@ready): - if ( $fh == \*STDIN ): - line = <$fh> - if (match(line, '/break/'): - print("Sending break...\n" - writeDev("b") - if ( $running == 1 ): - print("Kernel is busy (send break command)\n" - elif (match(line, '/processcontext ([0-9A-Fa-f]+)/'): - pid = hex($1) - if ( pid == 0 ): - pcontext = kernelcontext - print("Process context is kernel\n" - else: - eproc = getEprocess(pid) - dtb = readDword( $eproc + 0x18 ) - peb = readDword( $eproc + 0x1b0 ) + assert pattern[0] == "/" + assert pattern[-1] == "/" + return re.search(pattern[1:-1], text) - if ($peb): - processcontext['eprocess'] = $eproc - processcontext['dtb'] = dtb - processcontext['peb'] = $peb - processcontext['pid'] = pid - pcontext = \%processcontext - print("Implicit process is now %x\n", pid - else: - print("Invalid PID (PEB not found in eprocess)\n" - - elif (match(line, '/getprocaddress (\S+) (\S+)/'): - dll = $1 - export = $2 - addr = getProcAddress( dll, export ) - if (addr): - print("%s!%s:%08x\n", dll, export, addr - else: - print("%s!%s not found\n", dll, export - elif (match(line, '/listexports ([0-9A-Fa-f]+)/'): - listExports( hex($1) ) - elif (match(line, '/^logical2physical ([0-9A-Fa-f]+)/'): - print("%08x -> %08x\n", hex($1), logical2physical( hex($1) ) - elif (match(line, '/^parsepe ([0-9A-Fa-f]+)/'): - my %PE = parsePE( hex($1) ) - compiled = localtime( $PE{"TimeDateStamp"] ) - print("Compiled on $compiled\n" - elif (match(line, '/^writevirtualmemory ([0-9A-Fa-f]+) [0-9A-Fa-f][0-9A-Fa-f]/'): - chomp($line) - my ( $c, addr, @bytes ) = split( /\s+/, $line ) - sendDbgKdWriteVirtualMemory( hex(addr), join( "", map { chr(hex) } @bytes ) ) - elif (match(line, '/^(?:messagebox|mb)\s+(.*)\|(.*)/'): - title = $1 - message = $2 - injectSUSShellcode( $title, $message ) - insertApc() - elif (match(line, '/^processlist|^listprocess/'): - print("Walking process list...\n" - my %procs = getProcessList() - for ( reverse sort keys %procs ): - c = localtime( $procs{$_}{'created'] ) - print("%04x %s\n", $procs{$_}{'pid'], $procs{$_}{'name'] - printf - "Eprocess: %08x DTB: %08x PEB: %08x Created: %s\n", $_, - $procs{$_}{'dtb'], $procs{$_}{'peb'], $c - print("Threads: " - print(join( " ", - map { sprint("%08x", $_ } @{ $procs{$_}{'threads'] } ) - print("\n\n" - elif (match(line, '/^module|^listmodules/'): - my %modules - if ( pcontext['pid'] == 0 ): - %modules = getKernelModules() - else: - %modules = getUserModules() - for ( sort keys %modules ): - print("%s\tPath:%s\n", modules[$_}{'name'], modules[$_}{'path'] - print("base:%08x " . "size:%08x entry:%08x\n\n", $_, modules[$_}{'size'], modules[$_}{'entry'] - elif (match(line, '/^findprocessbyname (\S+)/'): - name = $1 - procs = getProcessList() - for proc in procs: - c = localtime(proc['created'] ) - n = proc['name'] - if ( name.lower() == n.lower() ): - #FIXME: !!! - #print("%04x %s\n", $procs{$_}{'pid'], - # $procs{$_}{'name'] - #printf - # "Eprocess: %08x DTB: %08x PEB: %08x Created: %s\n", - # $_, $procs{$_}{'dtb'], $procs{$_}{'peb'], $c - #print("Threads: " - #print(join( " ", - #map { sprint("%08x", $_ @{ $procs{$_}{'threads'] } ) - #print("\n") - break - elif (match(line, '/^eprocess ([0-9A-Fa-f]+)/'): - ep = getEprocess( hex($1) ) - sendDbgKdReadVirtualMemory( $ep, 648 ) - buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) - if ( len(buf) > 56 ): - eproc = substr( buf, 56 ) - if ( len($eproc) > 0x20c ): - name - if ( version > 5 ): - $name = substr( $eproc, 0x174, 16 ) - else: - $name = substr( $eproc, 0x1fc, 16 ) - $name =~ s/\x00//g - print("Process name is $name\n" - next = unpack( "I", substr( $eproc, 0xa0, 4 ) ) - - elif (match(line, '/^bp ([0-9A-Fa-f]+)/'): - sendDbgKdWriteBreakPoint($1) - elif (match(line, '/^bc ([0-9A-Fa-f]+)/'): - sendDbgKdRestoreBreakPoint($1) - elif (match(line, '/^bl/'): - print("Breakpoints:\n", join( "\n", sort keys %breakpoints ), "\n" - elif (match(line, '/^continue/'): - sendDbgKdContinue2() - $running = 1 - elif (match(line, '/^getpspcidtable/'): - getPspCidTable() - elif (match(line, '/^(autocontinue|g)$/'): - # get/set context to update EIP before continuing - my %context = getContext() - context['EIP']++ - setContext(%context) - sendDbgKdContinue2() - $running = 1 - elif (match(line, '/^version/'): - printVersionData() - elif (match(line, '/^readcontrolspace/'): - sendDbgKdReadControlSpace() - elif (match(line, '/^writecontrolspace/'): - if ($controlspace): - sendDbgKdWriteControlSpace() - else: - print("Haven't gotten control space yet!\n" - elif (match(line, '/^r (.*)=(.*)/'): - reg = $1 - val = $2 - my %context = getContext() - if ( len(reg) < 4 ): - reg = uc(reg) - if ( exists context[reg} ): - if ( reg == "fp.RegisterArea" ): - print("Not supported yet.\n" - elif ( reg == "leftovers" ): - print("Not supported.\n" - else: - context[reg} = hex(val) - setContext(%context) - %context = getContext() - print("New value of %s is %08x\n", reg, context[reg} - else: - print("Register reg unknown\n" - elif (match(line, '/^r (.*)/'): - reg = $1 - my %context = getContext() - if ( len(reg) < 4 ): - reg = uc(reg) - if ( exists context[reg} ): - if ( reg == "fp.RegisterArea" ): - print("%s = \n%s\n", reg, hexprint(reg) - else: - print("%s = %08x\n", reg, context[reg] - else: - print("Register reg unknown\n" - elif (match(line, '/^r$|^getcontext/'): - my %context = getContext() - for ( sort keys %context ): - if ( ( $_ != "fp.RegisterArea" ) && ( $_ != "leftovers" ) ): - print("%s=%08x\n", $_, context[$_} - elif (match(line, '/^dw ([0-9A-Fa-f]+)/'): - print("%08x: %08x\n", hex($1), readDword( hex($1) ) - elif (match(line, '/^(?:readvirtualmem|d) ([0-9A-Fa-f]+)/'): - vaddr = $1 - readlen - if (match(line, '/(?:readvirtualmem|d) ([0-9A-Fa-f]+) ([0-9A-Fa-f]+)/'): - readlen = hex($2) - readlen ||= 4 - buf = readVirtualMemory( hex(vaddr), readlen ) - print(hexasc(buf) - elif (match(line, '/^(?:readphysicalmem|dp) ([0-9A-Fa-f]+)/'): - addr = $1 - readlen - if (match(line, '/(?:readphysicalmem|dp) ([0-9A-Fa-f]+) ([0-9A-Fa-f]+)/'): - readlen = hex($2) - readlen ||= 4 - sendDbgKdReadPhysicalMemory( hex(addr), readlen ) - elif (match(line, '/^reboot/'): - sendDbgKdReboot() - elif (match(line, '/^quit|^exit/'): - sys.exit() - elif (match(line, '/^reset/'): - sendReset() - elif ( $fh == \*FH || $fh == client ): - handlePacket(0) - print("\n\n" +# +# sendReset() +# +# while (ready = $s->can_read ): +# for fh (@ready): +# if ( $fh == \*STDIN ): +# line = <$fh> +# if (match(line, '/break/'): +# print("Sending break...\n" +# writeDev("b") +# if ( $running == 1 ): +# print("Kernel is busy (send break command)\n" +# elif (match(line, '/processcontext ([0-9A-Fa-f]+)/'): +# pid = hex($1) +# if ( pid == 0 ): +# pcontext = kernelcontext +# print("Process context is kernel\n" +# else: +# eproc = getEprocess(pid) +# dtb = readDword( $eproc + 0x18 ) +# peb = readDword( $eproc + 0x1b0 ) +# +# if ($peb): +# processcontext['eprocess'] = $eproc +# processcontext['dtb'] = dtb +# processcontext['peb'] = $peb +# processcontext['pid'] = pid +# pcontext = \%processcontext +# print("Implicit process is now %x\n", pid +# else: +# print("Invalid PID (PEB not found in eprocess)\n" +# +# elif (match(line, '/getprocaddress (\S+) (\S+)/'): +# dll = $1 +# export = $2 +# addr = getProcAddress( dll, export ) +# if (addr): +# print("%s!%s:%08x\n", dll, export, addr +# else: +# print("%s!%s not found\n", dll, export +# elif (match(line, '/listexports ([0-9A-Fa-f]+)/'): +# listExports( hex($1) ) +# elif (match(line, '/^logical2physical ([0-9A-Fa-f]+)/'): +# print("%08x -> %08x\n", hex($1), logical2physical( hex($1) ) +# elif (match(line, '/^parsepe ([0-9A-Fa-f]+)/'): +# my %PE = parsePE( hex($1) ) +# compiled = localtime( $PE{"TimeDateStamp"] ) +# print("Compiled on $compiled\n" +# elif (match(line, '/^writevirtualmemory ([0-9A-Fa-f]+) [0-9A-Fa-f][0-9A-Fa-f]/'): +# chomp($line) +# my ( $c, addr, @bytes ) = split( /\s+/, $line ) +# sendDbgKdWriteVirtualMemory( hex(addr), join( "", map { chr(hex) } @bytes ) ) +# elif (match(line, '/^(?:messagebox|mb)\s+(.*)\|(.*)/'): +# title = $1 +# message = $2 +# injectSUSShellcode( $title, $message ) +# insertApc() +# elif (match(line, '/^processlist|^listprocess/'): +# print("Walking process list...\n" +# my %procs = getProcessList() +# for ( reverse sort keys %procs ): +# c = localtime( $procs{$_}{'created'] ) +# print("%04x %s\n", $procs{$_}{'pid'], $procs{$_}{'name'] +# printf +# "Eprocess: %08x DTB: %08x PEB: %08x Created: %s\n", $_, +# $procs{$_}{'dtb'], $procs{$_}{'peb'], $c +# print("Threads: " +# print(join( " ", +# map { sprint("%08x", $_ } @{ $procs{$_}{'threads'] } ) +# print("\n\n" +# elif (match(line, '/^module|^listmodules/'): +# my %modules +# if ( pcontext['pid'] == 0 ): +# %modules = getKernelModules() +# else: +# %modules = getUserModules() +# for ( sort keys %modules ): +# print("%s\tPath:%s\n", modules[$_}{'name'], modules[$_}{'path'] +# print("base:%08x " . "size:%08x entry:%08x\n\n", $_, modules[$_}{'size'], modules[$_}{'entry'] +# elif (match(line, '/^findprocessbyname (\S+)/'): +# name = $1 +# procs = getProcessList() +# for proc in procs: +# c = localtime(proc['created'] ) +# n = proc['name'] +# if ( name.lower() == n.lower() ): +# #FIXME: !!! +# #print("%04x %s\n", $procs{$_}{'pid'], +# # $procs{$_}{'name'] +# #printf +# # "Eprocess: %08x DTB: %08x PEB: %08x Created: %s\n", +# # $_, $procs{$_}{'dtb'], $procs{$_}{'peb'], $c +# #print("Threads: " +# #print(join( " ", +# #map { sprint("%08x", $_ @{ $procs{$_}{'threads'] } ) +# #print("\n") +# break +# elif (match(line, '/^eprocess ([0-9A-Fa-f]+)/'): +# ep = getEprocess( hex($1) ) +# sendDbgKdReadVirtualMemory( $ep, 648 ) +# buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) +# if ( len(buf) > 56 ): +# eproc = substr( buf, 56 ) +# if ( len($eproc) > 0x20c ): +# name +# if ( version > 5 ): +# $name = substr( $eproc, 0x174, 16 ) +# else: +# $name = substr( $eproc, 0x1fc, 16 ) +# $name =~ s/\x00//g +# print("Process name is $name\n" +# next = unpack( "I", substr( $eproc, 0xa0, 4 ) ) +# +# elif (match(line, '/^bp ([0-9A-Fa-f]+)/'): +# sendDbgKdWriteBreakPoint($1) +# elif (match(line, '/^bc ([0-9A-Fa-f]+)/'): +# sendDbgKdRestoreBreakPoint($1) +# elif (match(line, '/^bl/'): +# print("Breakpoints:\n", join( "\n", sort keys %breakpoints ), "\n" +# elif (match(line, '/^continue/'): +# sendDbgKdContinue2() +# $running = 1 +# elif (match(line, '/^getpspcidtable/'): +# getPspCidTable() +# elif (match(line, '/^(autocontinue|g)$/'): +# +# # get/set context to update EIP before continuing +# my %context = getContext() +# context['EIP']++ +# setContext(%context) +# sendDbgKdContinue2() +# $running = 1 +# elif (match(line, '/^version/'): +# printVersionData() +# elif (match(line, '/^readcontrolspace/'): +# sendDbgKdReadControlSpace() +# elif (match(line, '/^writecontrolspace/'): +# if ($controlspace): +# sendDbgKdWriteControlSpace() +# else: +# print("Haven't gotten control space yet!\n" +# elif (match(line, '/^r (.*)=(.*)/'): +# reg = $1 +# val = $2 +# my %context = getContext() +# if ( len(reg) < 4 ): +# reg = uc(reg) +# if ( exists context[reg} ): +# if ( reg == "fp.RegisterArea" ): +# print("Not supported yet.\n" +# elif ( reg == "leftovers" ): +# print("Not supported.\n" +# else: +# context[reg} = hex(val) +# setContext(%context) +# %context = getContext() +# print("New value of %s is %08x\n", reg, context[reg} +# else: +# print("Register reg unknown\n" +# elif (match(line, '/^r (.*)/'): +# reg = $1 +# my %context = getContext() +# if ( len(reg) < 4 ): +# reg = uc(reg) +# if ( exists context[reg} ): +# if ( reg == "fp.RegisterArea" ): +# print("%s = \n%s\n", reg, hexprint(reg) +# else: +# print("%s = %08x\n", reg, context[reg] +# else: +# print("Register reg unknown\n" +# elif (match(line, '/^r$|^getcontext/'): +# my %context = getContext() +# for ( sort keys %context ): +# if ( ( $_ != "fp.RegisterArea" ) && ( $_ != "leftovers" ) ): +# print("%s=%08x\n", $_, context[$_} +# elif (match(line, '/^dw ([0-9A-Fa-f]+)/'): +# print("%08x: %08x\n", hex($1), readDword( hex($1) ) +# elif (match(line, '/^(?:readvirtualmem|d) ([0-9A-Fa-f]+)/'): +# vaddr = $1 +# readlen +# if (match(line, '/(?:readvirtualmem|d) ([0-9A-Fa-f]+) ([0-9A-Fa-f]+)/'): +# readlen = hex($2) +# readlen ||= 4 +# buf = readVirtualMemory( hex(vaddr), readlen ) +# print(hexasc(buf) +# elif (match(line, '/^(?:readphysicalmem|dp) ([0-9A-Fa-f]+)/'): +# addr = $1 +# readlen +# if (match(line, '/(?:readphysicalmem|dp) ([0-9A-Fa-f]+) ([0-9A-Fa-f]+)/'): +# readlen = hex($2) +# readlen ||= 4 +# sendDbgKdReadPhysicalMemory( hex(addr), readlen ) +# elif (match(line, '/^reboot/'): +# sendDbgKdReboot() +# elif (match(line, '/^quit|^exit/'): +# sys.exit() +# elif (match(line, '/^reset/'): +# sendReset() +# elif ( $fh == \*FH || $fh == client ): +# handlePacket(0) +# print("\n\n" From 2dbbdcf76679b1afd2c0071c54fec7eba0788a6c Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 10:40:18 -0700 Subject: [PATCH 02/30] Factors out connection code. --- Pipfile | 1 + Pipfile.lock | 75 +++++- windpl.py | 647 +++++++++++++++++++++++++++------------------------ 3 files changed, 421 insertions(+), 302 deletions(-) diff --git a/Pipfile b/Pipfile index 21b3e30..73b5a24 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ name = "pypi" [packages] black = "*" +pylint = "*" [dev-packages] black = "*" diff --git a/Pipfile.lock b/Pipfile.lock index cd9949b..a826029 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "93b7444c5e175150f10109271ecf7fc99eed16aeab9597d95d386c9966cc2196" + "sha256": "1fb939a3f88dbcf408171f9451c3d9acbaccb47d5e123cc7842af9844ecc6fe6" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,14 @@ ] }, "default": { + "astroid": { + "hashes": [ + "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c", + "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e" + ], + "markers": "python_version ~= '3.6'", + "version": "==2.7.3" + }, "black": { "hashes": [ "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7", @@ -32,6 +40,49 @@ "markers": "python_version >= '3.6'", "version": "==8.0.1" }, + "isort": { + "hashes": [ + "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899", + "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2" + ], + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "version": "==5.9.3" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653", + "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61", + "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2", + "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837", + "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3", + "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43", + "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726", + "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3", + "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587", + "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8", + "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a", + "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd", + "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f", + "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad", + "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4", + "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b", + "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf", + "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981", + "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741", + "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e", + "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", + "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.6.0" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, "mypy-extensions": { "hashes": [ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", @@ -54,6 +105,14 @@ "markers": "python_version >= '3.6'", "version": "==2.3.0" }, + "pylint": { + "hashes": [ + "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1", + "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852" + ], + "index": "pypi", + "version": "==2.10.2" + }, "regex": { "hashes": [ "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468", @@ -100,6 +159,14 @@ ], "version": "==2021.8.28" }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, "tomli": { "hashes": [ "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", @@ -115,6 +182,12 @@ "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" ], "version": "==3.10.0.2" + }, + "wrapt": { + "hashes": [ + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" + ], + "version": "==1.12.1" } }, "develop": { diff --git a/windpl.py b/windpl.py index 96d196e..0926621 100755 --- a/windpl.py +++ b/windpl.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# win/xbox KD client -# +"""Win/xbox KernelDebug client.""" + # Copyright (C) 2007 SecureWorks, Inc. # Copyright (C) 2013 espes # Copyright (C) 2017 Jannik Vogel @@ -41,6 +41,8 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +# pylint: disable = invalid-name, missing-function-docstring, fixme + # use IO::Select # use IO::Socket # use File::stat @@ -48,26 +50,84 @@ # use strict import argparse -import socket +import os import sys import struct from struct import pack from windpl_extra import logical2physical +PACKET_LEADER = 0x30303030 +CONTROL_PACKET_LEADER = 0x69696969 + +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_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 + +# 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 + def substr(buf, start, length=None): - if length == None: + if length is None: return buf[start:] return buf[start : start + length] def patch_substr(buf, start, length, fmt, data=None): - if data == None: - data = fmt + if data is None: return buf[0:start] + fmt + buf[start + length :] - else: - return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] + + return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] def unpack(fmt, data): @@ -77,48 +137,51 @@ def unpack(fmt, data): def hexformat(buf): length = len(buf) if length == 0: - return + return None + b = "0000 " c = 0 for x in buf: c += 1 b += "%02x " % x - if (c % 16) == 0: - if c < length: - b += "\n%04x " % c - if substr(b, -1, 1) != "\n": + if (c % 16) == 0 and c < length: + b += "\n%04x " % c + + if b[-1] != "\n": b += "\n" + return b def hexasc(buf): length = len(buf) if length == 0: - return + return None + count = 0 - ascii = "" + ascii_string = "" out = "0000 " for x in buf: c = ord(x) out += "%02x " % c - if (c > 0x1F) and (c < 0x7F): - ascii += x + if 0x1F < c < 0x7F: + ascii_string += x else: - ascii += "." + ascii_string += "." count += 1 if (count % 16) == 0: if count < length: - out += " " + ascii + "\n%04x " % count + out += " " + ascii_string + "\n%04x " % count else: - out += " " + ascii + "\n" - ascii = "" + out += " " + ascii_string + "\n" + ascii_string = "" padding = 0 - if ascii: + if ascii_string: padding = 48 - ((count % 16) * 3) out += " " * padding - out += " " + ascii + "\n" + out += " " + ascii_string + "\n" return out @@ -130,78 +193,71 @@ def cksum(buf): return v -def packetHeader(d): - header = "\x30\x30\x30\x30".encode("utf-8") # packet leader - header += "\x02\x00".encode("utf-8") # packet type PACKET_TYPE_KD_STATE_MANIPULATE - header += pack("H", len(d)) # sizeof data - header += pack("I", nextpid) # packet id - header += pack("I", cksum(d)) # checksum of data - return header +class DebugConnection: + """Models a connection to the target device (e.g. FIFO, socket).""" + + def __init__(self, endpoint): + self.endpoint = endpoint + self._connection = None + + def connect(self): + # FIXME: Add support for TCP sockets too + # self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + # self.client.connect(endpoint) + flags = os.O_RDWR + if os.name == 'nt': + flags |= os.O_BINARY + + self._connection = os.open(self.endpoint, flags) + + def disconnect(self): + pass + + def recv(self, max_bytes): + """Receives up to `max_bytes` bytes from the connection.""" + return os.read(self._connection, max_bytes) + + def send(self, buf): + return os.write(self._connection, buf) + + def read(self, wanted): + """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" + total = 0 + outbuf = bytearray([]) + while total < wanted: + # if False: + # if serial: + # (count, buf) = serial.read(1) + # else: + # # FH.blocking(1) + # count = client.sysread(buf, 1) + # if count == 0: + # die("eof") + # # print("readLoop count %x\n", ord(buf) + # # FH.blocking(0) + buf = self.recv(wanted - total) + + count = len(buf) + if count: + total += count + outbuf += buf + + return outbuf + + def write(self, buffer): + """Writes `buffer` to the connection, blocking as necessary.""" + + while len(buffer): + written = self.send(buffer) + buffer = buffer[written:] class DebugContext: - PACKET_LEADER = 0x30303030 - CONTROL_PACKET_LEADER = 0x69696969 - - 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_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 - - # 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 + """Models a KernelDebug session.""" def __init__(self) -> None: self.timeout = 10 # max time to wait on packet - self.running = 1 + self.running = True self.kernelcontext = {} self.kernelcontext["peb"] = 0 @@ -214,7 +270,7 @@ def __init__(self) -> None: self.processcontext["pid"] = 0 self.processcontext["eprocess"] = 0 self.processcontext["dtb"] = 0 - self.pcontext = kernelcontext + self.pcontext = self.kernelcontext self.nextpid = 1 self.breakpoints = {} @@ -225,27 +281,27 @@ def __init__(self) -> None: self.client = None def connect(self, endpoint): - if False: - ds = stat(dev) or die("$!") - if S_ISCHR(ds.mode): - # require Device::SerialPort - # FIXME: serial = tie( *FH, 'Device::SerialPort', "dev" ) or die("Can't tie: $!" - serial.baudrate(115200) - serial.parity("none") - serial.databits(8) - serial.stopbits(1) - serial.handshake("none") - serial.write_settings or die("failed writing settings") - FH.blocking(0) - elif S_ISSOCK(ds.mode): - client = None # FIXME: IO::Socket::UNIX->new(Type = SOCK_STREAM, Peer = dev) or die("Can't create socket!") - else: - die("dev not a character device or a socket") + # if False: + # ds = stat(dev) or die("$!") + # if S_ISCHR(ds.mode): + # # require Device::SerialPort + # # FIXME: serial = tie( *FH, 'Device::SerialPort', "dev" ) or die("Can't tie: $!" + # serial.baudrate(115200) + # serial.parity("none") + # serial.databits(8) + # serial.stopbits(1) + # serial.handshake("none") + # serial.write_settings or die("failed writing settings") + # FH.blocking(0) + # elif S_ISSOCK(ds.mode): + # client = None # FIXME: IO::Socket::UNIX->new(Type = SOCK_STREAM, Peer = dev) or die("Can't create socket!") + # else: + # die("dev not a character device or a socket") - # FIXME: Add support for TCP sockets too - self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) print(f"Connecting to '{endpoint}'") - self.client.connect(endpoint) + + self.client = DebugConnection(endpoint) + self.client.connect() def run(self): self.sendReset() @@ -256,56 +312,37 @@ def run(self): # FIXME: windpl_loop.py def writeDev(self, data): - if False: - if serial: - serial.write(data) - else: - self.client.syswrite(data) - self.client.send(data) + # if False: + # if serial: + # serial.write(data) + # else: + # self.client.syswrite(data) + self.client.write(data) def readLoop(self, wanted): - total = 0 - outbuf = bytearray([]) - while total < wanted: - if False: - if serial: - (count, buf) = serial.read(1) - else: - # FH.blocking(1) - count = client.sysread(buf, 1) - if count == 0: - die("eof") - # print("readLoop count %x\n", ord(buf) - # FH.blocking(0) - buf = self.client.recv(wanted - total) - count = len(buf) - if count: - total += count - outbuf += buf - return outbuf + return self.client.read(wanted) def handlePacket(self): ptype, buf = self.getPacket() if len(buf) == 0: - return + return None - if ptype == self.PACKET_TYPE_KD_STATE_MANIPULATE: + if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: self.handleStateManipulate(buf) - elif ptype == self.PACKET_TYPE_KD_DEBUG_IO: + elif ptype == PACKET_TYPE_KD_DEBUG_IO: self.handleDebugIO(buf) - elif ptype == self.PACKET_TYPE_KD_STATE_CHANGE64: + elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: self.handleStateChange(buf) - return (ptype, buf) + return ptype, buf def getPacket(self): - global nextpid ptype = None payload = bytearray([]) buf = self.readLoop(4) plh = unpack("I", buf) - if (plh == self.PACKET_LEADER) or (plh == self.CONTROL_PACKET_LEADER): + if plh in (PACKET_LEADER, CONTROL_PACKET_LEADER): print("Got packet leader: %08x" % plh) buf = self.readLoop(2) @@ -318,7 +355,7 @@ def getPacket(self): buf = self.readLoop(4) pid = unpack("I", buf) - nextpid = pid + self.nextpid = pid print("Packet ID: %08x" % pid) buf = self.readLoop(4) @@ -329,25 +366,25 @@ def getPacket(self): payload = self.readLoop(bc) # send ack if it's a non-control packet - if plh == self.PACKET_LEADER: + if plh == PACKET_LEADER: # packet trailer trail = self.readLoop(1) print(hexformat(trail)) if trail[0] == 0xAA: # print("sending Ack\n";#) self.sendAck() - return (ptype, payload) + return ptype, payload - def handleDebugIO(self, buf): + def handleDebugIO(self, buf): # pylint: disable = no-self-use apiNumber = unpack("I", substr(buf, 0, 4)) - if apiNumber == self.DbgKdPrintStringApi: + if apiNumber == DbgKdPrintStringApi: print("DBG PRINT STRING: " + substr(buf, 0x10).decode("utf-8")) def handleStateChange(self, buf): newState = unpack("I", substr(buf, 0, 4)) print("State Change: %08x" % newState) - if newState == self.DbgKdExceptionStateChange: + if newState == DbgKdExceptionStateChange: exceptions = { 0xC0000005: "EXCEPTION_ACCESS_VIOLATION", 0xC000008C: "EXCEPTION_ARRAY_BOUNDS_EXCEEDED", @@ -388,12 +425,12 @@ def handleStateChange(self, buf): print("Exception address = %08x" % address) print("Number parameters = %08x" % parameters) - running = 0 + self.running = False # my @v = getVersionInfo() # version = v[0] # $kernelbase = v[2] - elif newState == self.DbgKdLoadSymbolsStateChange: + elif newState == DbgKdLoadSymbolsStateChange: # DBGKD_LOAD_SYMBOLS64 filename = substr(buf, 0x3B8) @@ -409,24 +446,24 @@ def handleStateManipulate(self, buf): apiNumber = unpack("I", substr(buf, 0, 4)) print("State Manipulate: %08x" % apiNumber) - if apiNumber == self.DbgKdWriteBreakPointApi: + if apiNumber == DbgKdWriteBreakPointApi: bp = "%08x" % unpack("I", substr(buf, 16, 4)) handle = unpack("I", substr(buf, 20, 4)) print("Breakpoint " + handle + " set at " + bp + "") self.breakpoints[bp] = handle - elif apiNumber == self.DbgKdRestoreBreakPointApi: + elif apiNumber == DbgKdRestoreBreakPointApi: handle = unpack("I", substr(buf, 16, 4)) print("Breakpoint " + handle + " cleared") - elif apiNumber == self.DbgKdGetVersionApi: + elif apiNumber == DbgKdGetVersionApi: version = substr(buf, 16) print("VERS: " + hexformat(version)) - elif apiNumber == self.DbgKdReadVirtualMemoryApi: + elif apiNumber == DbgKdReadVirtualMemoryApi: vmem = substr(buf, 56) print("VMEM:\n" + hexasc(vmem)) - elif apiNumber == self.DbgKdReadPhysicalMemoryApi: + elif apiNumber == DbgKdReadPhysicalMemoryApi: pmem = substr(buf, 56) print("PMEM:\n" + hexasc(pmem)) - elif apiNumber == self.DbgKdReadControlSpaceApi: + elif apiNumber == DbgKdReadControlSpaceApi: controlspace = substr(buf, 56) print("CNTL: " + hexformat(controlspace)) else: @@ -436,8 +473,8 @@ def sendAck(self): ack = "\x69\x69\x69\x69\x04\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode( "utf-8" ) - print(nextpid) - ack = patch_substr(ack, 8, 4, "I", nextpid) + print(self.nextpid) + ack = patch_substr(ack, 8, 4, "I", self.nextpid) print(hexformat(ack)) self.writeDev(ack) @@ -455,44 +492,47 @@ def getContext(self): context = {} self.sendDbgKdGetContext() buf = self.waitStateManipulate(DbgKdGetContextApi) - if len(buf) > 204: - ctx = substr(buf, 56) - - # print("CTXT: ", hexformat($context) - context["ContextFlags"] = unpack("I", substr(ctx, 0, 4)) - context["DR0"] = unpack("I", substr(ctx, 4, 4)) - context["DR1"] = unpack("I", substr(ctx, 8, 4)) - context["DR2"] = unpack("I", substr(ctx, 12, 4)) - context["DR3"] = unpack("I", substr(ctx, 16, 4)) - context["DR6"] = unpack("I", substr(ctx, 20, 4)) - context["DR7"] = unpack("I", substr(ctx, 24, 4)) - context["fp.ControlWord"] = unpack("I", substr(ctx, 28, 4)) - context["fp.StatusWord"] = unpack("I", substr(ctx, 32, 4)) - context["fp.TagWord"] = unpack("I", substr(ctx, 36, 4)) - context["fp.ErrorOffset"] = unpack("I", substr(ctx, 40, 4)) - context["fp.ErrorSelector"] = unpack("I", substr(ctx, 44, 4)) - context["fp.DataOffset"] = unpack("I", substr(ctx, 48, 4)) - context["fp.DataSelector"] = unpack("I", substr(ctx, 52, 4)) - context["fp.RegisterArea"] = substr(ctx, 56, 80) - context["fp.Cr0NpxState"] = unpack("I", substr(ctx, 136, 4)) - context["GS"] = unpack("I", substr(ctx, 140, 4)) - context["FS"] = unpack("I", substr(ctx, 144, 4)) - context["ES"] = unpack("I", substr(ctx, 148, 4)) - context["DS"] = unpack("I", substr(ctx, 152, 4)) - context["EDI"] = unpack("I", substr(ctx, 156, 4)) - context["ESI"] = unpack("I", substr(ctx, 160, 4)) - context["EBX"] = unpack("I", substr(ctx, 164, 4)) - context["EDX"] = unpack("I", substr(ctx, 168, 4)) - context["ECX"] = unpack("I", substr(ctx, 172, 4)) - context["EAX"] = unpack("I", substr(ctx, 176, 4)) - context["EBP"] = unpack("I", substr(ctx, 180, 4)) - context["EIP"] = unpack("I", substr(ctx, 184, 4)) - context["CS"] = unpack("I", substr(ctx, 188, 4)) - context["Eflags"] = unpack("I", substr(ctx, 192, 4)) - context["ESP"] = unpack("I", substr(ctx, 196, 4)) - context["SS"] = unpack("I", substr(ctx, 200, 4)) - context["leftovers"] = substr(ctx, 204) - return context + + if len(buf) <= 204: + return None + + ctx = buf[56:] + + # print("CTXT: ", hexformat($context) + context["ContextFlags"] = unpack("I", ctx[0:4]) + context["DR0"] = unpack("I", ctx[4:8]) + context["DR1"] = unpack("I", ctx[8 : 8 + 4]) + context["DR2"] = unpack("I", ctx[12 : 12 + 4]) + context["DR3"] = unpack("I", ctx[16 : 16 + 4]) + context["DR6"] = unpack("I", ctx[20 : 20 + 4]) + context["DR7"] = unpack("I", ctx[24 : 24 + 4]) + context["fp.ControlWord"] = unpack("I", ctx[28 : 28 + 4]) + context["fp.StatusWord"] = unpack("I", ctx[32 : 32 + 4]) + context["fp.TagWord"] = unpack("I", ctx[36 : 36 + 4]) + context["fp.ErrorOffset"] = unpack("I", ctx[40 : 40 + 4]) + context["fp.ErrorSelector"] = unpack("I", ctx[44 : 44 + 4]) + context["fp.DataOffset"] = unpack("I", ctx[48 : 48 + 4]) + context["fp.DataSelector"] = unpack("I", ctx[52 : 52 + 4]) + context["fp.RegisterArea"] = ctx[56 : 56 + 80] + context["fp.Cr0NpxState"] = unpack("I", ctx[136 : 136 + 4]) + context["GS"] = unpack("I", ctx[140 : 140 + 4]) + context["FS"] = unpack("I", ctx[144 : 144 + 4]) + context["ES"] = unpack("I", ctx[148 : 148 + 4]) + context["DS"] = unpack("I", ctx[152 : 152 + 4]) + context["EDI"] = unpack("I", ctx[156 : 156 + 4]) + context["ESI"] = unpack("I", ctx[160 : 160 + 4]) + context["EBX"] = unpack("I", ctx[164 : 164 + 4]) + context["EDX"] = unpack("I", ctx[168 : 168 + 4]) + context["ECX"] = unpack("I", ctx[172 : 172 + 4]) + context["EAX"] = unpack("I", ctx[176 : 176 + 4]) + context["EBP"] = unpack("I", ctx[180 : 180 + 4]) + context["EIP"] = unpack("I", ctx[184:188]) + context["CS"] = unpack("I", ctx[188:192]) + context["Eflags"] = unpack("I", ctx[192:196]) + context["ESP"] = unpack("I", ctx[196:200]) + context["SS"] = unpack("I", ctx[200:204]) + context["leftovers"] = ctx[204:] + return context def setContext(self, context): ctx = pack("I", context["ContextFlags"]) @@ -568,46 +608,55 @@ def printVersionData(self): print("Module list = %08x" % v[3]) print("Debugger data = %08x" % v[4]) - if False: - - def getKernelModules(): - save = pcontext - pcontext = kernelcontext # this procedure is kernel context only - modules = [] - v = getVersionInfo() - flink = readDword(v[3]) - modlist = walkList(flink) - for mod in modlist: - - # print("module at %08x\n", $mod - buf = readVirtualMemory(mod, 0x34) - if len(buf) == 0x34: - base = unpack("I", substr(buf, 0x18, 4)) - if base == 0: - continue - entry = unpack("I", substr(buf, 0x1C, 4)) - size = unpack("I", substr(buf, 0x20, 4)) - path = substr(buf, 0x24, 8) - name = substr(buf, 0x2C, 8) - modules[base]["name"] = unicodeStructToAscii(name) - modules[base]["path"] = unicodeStructToAscii(path) - modules[base]["size"] = size - modules[base]["entry"] = entry - pcontext = save - return modules - - def unicodeStructToAscii(struct): - if len(struct) != 8: - return - length = unpack("H", substr(struct, 0, 2)) - vaddr = unpack("I", substr(struct, 4, 4)) - buf = readVirtualMemory(vaddr, length) - if len(buf) == length: - buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii - return buf + # if False: + # def getKernelModules(): + # save = pcontext + # pcontext = kernelcontext # this procedure is kernel context only + # modules = [] + # v = getVersionInfo() + # flink = readDword(v[3]) + # modlist = walkList(flink) + # for mod in modlist: + # + # # print("module at %08x\n", $mod + # buf = readVirtualMemory(mod, 0x34) + # if len(buf) == 0x34: + # base = unpack("I", substr(buf, 0x18, 4)) + # if base == 0: + # continue + # entry = unpack("I", substr(buf, 0x1C, 4)) + # size = unpack("I", substr(buf, 0x20, 4)) + # path = substr(buf, 0x24, 8) + # name = substr(buf, 0x2C, 8) + # modules[base]["name"] = unicodeStructToAscii(name) + # modules[base]["path"] = unicodeStructToAscii(path) + # modules[base]["size"] = size + # modules[base]["entry"] = entry + # pcontext = save + # return modules + # + # def unicodeStructToAscii(struct): + # if len(struct) != 8: + # return + # length = unpack("H", substr(struct, 0, 2)) + # vaddr = unpack("I", substr(struct, 4, 4)) + # buf = readVirtualMemory(vaddr, length) + # if len(buf) == length: + # buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii + # return buf + + def packetHeader(self, d): + header = "\x30\x30\x30\x30".encode("utf-8") # packet leader + header += "\x02\x00".encode( + "utf-8" + ) # packet type PACKET_TYPE_KD_STATE_MANIPULATE + header += pack("H", len(d)) # sizeof data + header += pack("I", self.nextpid) # packet id + header += pack("I", cksum(d)) # checksum of data + return header def sendManipulateStatePacket(self, d): - h = packetHeader(d) + h = self.packetHeader(d) print("SEND: " + hexformat(h) + hexformat(d)) self.writeDev(h) @@ -618,7 +667,7 @@ def sendDbgKdContinue2(self): # print("Sending DbgKdContinue2Api packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdContinueApi2) + d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) d = patch_substr(d, 8, 4, "I", 0x00010001) d = patch_substr(d, 16, 4, "I", 0x00010001) d = patch_substr(d, 24, 4, "I", 0x400) # TraceFlag @@ -629,7 +678,7 @@ def sendDbgKdGetVersion(self): # print("Sending DbgKdGetVersionApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdGetVersionApi) + d = patch_substr(d, 0, 4, "I", DbgKdGetVersionApi) self.sendManipulateStatePacket(d) def sendDbgKdWriteBreakPoint(self, bp): @@ -637,7 +686,7 @@ def sendDbgKdWriteBreakPoint(self, bp): # print("Sending DbgKdWriteBreakPointApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdWriteBreakPointApi) + d = patch_substr(d, 0, 4, "I", DbgKdWriteBreakPointApi) d = patch_substr(d, 16, 4, "I", bp) d = patch_substr(d, 20, 4, "I", self.curbp) self.curbp += 1 @@ -648,7 +697,7 @@ def sendDbgKdRestoreBreakPoint(self, bp): # print("Sending DbgKdRestoreBreakPointApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdRestoreBreakPointApi) + d = patch_substr(d, 0, 4, "I", DbgKdRestoreBreakPointApi) d = patch_substr(d, 16, 4, "I", self.breakpoints[bp]) self.sendManipulateStatePacket(d) del self.breakpoints[bp] @@ -674,21 +723,21 @@ def sendDbgKdWriteControlSpace(self): d = patch_substr(d, 24, 4, "I", 84) d += self.controlspace self.sendManipulateStatePacket(d) - controlspacesent = 1 + self.controlspacesent = True def sendDbgKdGetContext(self): # print("Sending DbgKdGetContextApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdGetContextApi) + d = patch_substr(d, 0, 4, "I", DbgKdGetContextApi) self.sendManipulateStatePacket(d) def sendDbgKdSetContext(self, ctx): # print("Sending DbgKdSetContextApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdSetContextApi) - d = patch_substr(d, 16, 4, substr(ctx, 0, 4)) + d = patch_substr(d, 0, 4, "I", DbgKdSetContextApi) + d = patch_substr(d, 16, 4, ctx[0:4]) d += ctx self.sendManipulateStatePacket(d) @@ -696,7 +745,7 @@ def sendDbgKdReadPhysicalMemory(self, addr, readlen): # print("Sending DbgKdReadPhysicalMemoryApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdReadPhysicalMemoryApi) + d = patch_substr(d, 0, 4, "I", DbgKdReadPhysicalMemoryApi) d = patch_substr(d, 16, 4, "I", addr) d = patch_substr(d, 24, 4, "I", readlen) self.sendManipulateStatePacket(d) @@ -705,7 +754,7 @@ def sendDbgKdReadVirtualMemory(self, vaddr, readlen): # print("Sending DbgKdReadVirtualMemoryApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdReadVirtualMemoryApi) + d = patch_substr(d, 0, 4, "I", DbgKdReadVirtualMemoryApi) d = patch_substr(d, 16, 4, "I", vaddr) d = patch_substr(d, 24, 4, "I", readlen) self.sendManipulateStatePacket(d) @@ -715,7 +764,7 @@ def sendDbgKdWriteVirtualMemory(self, vaddr, data): # print("Sending DbgKdWriteVirtualMemoryApi packet\n" d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", self.DbgKdWriteVirtualMemoryApi) + d = patch_substr(d, 0, 4, "I", DbgKdWriteVirtualMemoryApi) d = patch_substr(d, 16, 4, "I", vaddr) d = patch_substr(d, 24, 4, "I", writelen) d += data @@ -776,13 +825,13 @@ def writeVirtualMemory(self, addr, buf): while length > 0: if length < chunksize: self.sendDbgKdWriteVirtualMemory(addr, buf) - self.waitStateManipulate(self.DbgKdWriteVirtualMemoryApi) + self.waitStateManipulate(DbgKdWriteVirtualMemoryApi) length = 0 else: self.sendDbgKdWriteVirtualMemory( addr, substr(buf, offset, chunksize) ) - self.waitStateManipulate(self.DbgKdWriteVirtualMemoryApi) + self.waitStateManipulate(DbgKdWriteVirtualMemoryApi) length -= chunksize offset += chunksize addr += chunksize @@ -791,89 +840,85 @@ def writeVirtualMemory(self, addr, buf): # FIXME: Logic sucks here.. if distance_to_page_boundary > length: physaddr = logical2physical(addr) - writePhysicalMemory(physaddr, buf) + self.writePhysicalMemory(physaddr, buf) return - else: - physaddr = logical2physical(addr) - buf = writePhysicalMemory( - physaddr, substr(buf, 0, distance_to_page_boundary) - ) - addr += distance_to_page_boundary - offset += distance_to_page_boundary - remainder = length - distance_to_page_boundary + + physaddr = logical2physical(addr) + buf = buf[:distance_to_page_boundary] + self.writePhysicalMemory(physaddr, buf) + + addr += distance_to_page_boundary + offset += distance_to_page_boundary + remainder = length - distance_to_page_boundary while remainder > 0: if remainder < 0x1000: physaddr = logical2physical(addr) - writePhysicalMemory(physaddr, substr(buf, offset, remainder)) + self.writePhysicalMemory(physaddr, substr(buf, offset, remainder)) remainder = 0 else: physaddr = logical2physical(addr) - writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) + self.writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) addr += 0x1000 offset += 0x1000 remainder -= 0x1000 - return # FIXME: Indentation is horrible - def readVirtualMemory(addr, length): + def readVirtualMemory(self, addr, length): # FIXME: return unless addr && length chunksize = 0x800 # max to request in one packet out = bytearray() - buf = bytearray() - if pcontext["pid"] == 0: + if self.pcontext["pid"] == 0: while length > 0: if length < chunksize: - sendDbgKdReadVirtualMemory(addr, length) - buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) + self.sendDbgKdReadVirtualMemory(addr, length) + buf = self.waitStateManipulate(DbgKdReadVirtualMemoryApi) if len(buf) > 56: out += substr(buf, 56) length = 0 else: - sendDbgKdReadVirtualMemory(addr, chunksize) - buf = waitStateManipulate(DbgKdReadVirtualMemoryApi) + self.sendDbgKdReadVirtualMemory(addr, chunksize) + buf = self.waitStateManipulate(DbgKdReadVirtualMemoryApi) if len(buf) > 56: out += substr(buf, 56) length -= chunksize addr += chunksize return out - else: - distance_to_page_boundary = 0x1000 - (addr & 0xFFF) - if distance_to_page_boundary > length: + + distance_to_page_boundary = 0x1000 - (addr & 0xFFF) + if distance_to_page_boundary > length: + physaddr = logical2physical(addr) + return self.readPhysicalMemory(physaddr, length) + + physaddr = logical2physical(addr) + buf = self.readPhysicalMemory(physaddr, distance_to_page_boundary) + addr += distance_to_page_boundary + remainder = length - distance_to_page_boundary + while remainder > 0: + if remainder < 0x1000: physaddr = logical2physical(addr) - return readPhysicalMemory(physaddr, length) + buf += self.readPhysicalMemory(physaddr, remainder) + remainder = 0 else: physaddr = logical2physical(addr) - buf = readPhysicalMemory(physaddr, distance_to_page_boundary) - addr += distance_to_page_boundary - remainder = length - distance_to_page_boundary - while remainder > 0: - if remainder < 0x1000: - physaddr = logical2physical(addr) - buf += readPhysicalMemory(physaddr, remainder) - remainder = 0 - else: - physaddr = logical2physical(addr) - buf += readPhysicalMemory(physaddr, 0x1000) - addr += 0x1000 - remainder -= 0x1000 - return buf - - def sendDbgKdReboot(): + buf += self.readPhysicalMemory(physaddr, 0x1000) + addr += 0x1000 + remainder -= 0x1000 + return buf + + def sendDbgKdReboot(self): print("Sending DbgKdRebootApi packet") d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", 0x313B) - sendManipulateStatePacket(d) + self.sendManipulateStatePacket(d) - def waitStateManipulate(wanted): - if running: - return + def waitStateManipulate(self, wanted): + if self.running: + return None - ptype = bytearray([]) - buf = bytearray([]) try: while 1: - (ptype, buf) = handlePacket(1) + (ptype, buf) = self.handlePacket(1) if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: api = unpack("I", substr(buf, 0, 4)) if api == wanted: @@ -882,24 +927,24 @@ def waitStateManipulate(wanted): print("Timeout waiting for %04x packet reply" % wanted) return buf - def getPspCidTable(): + def getPspCidTable(self): pspcidtable = 0 - save = pcontext - pcontext = kernelcontext # this procedure is kernel context only - sendDbgKdGetVersion() - buf = waitStateManipulate(DbgKdGetVersionApi) + save = self.pcontext + self.pcontext = self.kernelcontext # this procedure is kernel context only + self.sendDbgKdGetVersion() + buf = self.waitStateManipulate(DbgKdGetVersionApi) pddata = unpack("I", substr(buf, 48, 4)) if pddata: # print("Pointer to debugger data struct is at %08x\n", pddata - ddata = readDword(pddata) + ddata = self.readDword(pddata) if ddata != "failed": # print("debugger data struct is at %08x\n", ddata - pspcidtable = readDword(ddata + 88) + pspcidtable = self.readDword(ddata + 88) if pspcidtable != "failed": # print("PspCidTable is %08x\n", $pspcidtable - pcontext = save + self.pcontext = save return pspcidtable - pcontext = save + self.pcontext = save return 0 From 11b79c93b3668314a24e1113fa23f356cae3eb9a Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 14:57:21 -0700 Subject: [PATCH 03/30] Removes unnecessary readLoop method. --- windpl.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/windpl.py b/windpl.py index 0926621..4cc5051 100755 --- a/windpl.py +++ b/windpl.py @@ -233,7 +233,7 @@ def read(self, wanted): # count = client.sysread(buf, 1) # if count == 0: # die("eof") - # # print("readLoop count %x\n", ord(buf) + # # print("client.read count %x\n", ord(buf) # # FH.blocking(0) buf = self.recv(wanted - total) @@ -319,9 +319,6 @@ def writeDev(self, data): # self.client.syswrite(data) self.client.write(data) - def readLoop(self, wanted): - return self.client.read(wanted) - def handlePacket(self): ptype, buf = self.getPacket() @@ -340,35 +337,35 @@ def handlePacket(self): def getPacket(self): ptype = None payload = bytearray([]) - buf = self.readLoop(4) + buf = self.client.read(4) plh = unpack("I", buf) if plh in (PACKET_LEADER, CONTROL_PACKET_LEADER): print("Got packet leader: %08x" % plh) - buf = self.readLoop(2) + buf = self.client.read(2) ptype = unpack("H", buf) print("Packet type: " + str(ptype) + "") - buf = self.readLoop(2) + buf = self.client.read(2) bc = unpack("H", buf) print("Byte count: " + str(bc) + "") - buf = self.readLoop(4) + buf = self.client.read(4) pid = unpack("I", buf) self.nextpid = pid print("Packet ID: %08x" % pid) - buf = self.readLoop(4) + buf = self.client.read(4) ck = unpack("I", buf) print("Checksum: %08x" % ck) if bc: - payload = self.readLoop(bc) + payload = self.client.read(bc) # send ack if it's a non-control packet if plh == PACKET_LEADER: # packet trailer - trail = self.readLoop(1) + trail = self.client.read(1) print(hexformat(trail)) if trail[0] == 0xAA: # print("sending Ack\n";#) From 25d0b72f9908364d0216d1620494835ec5e198c9 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 20:57:22 -0700 Subject: [PATCH 04/30] Handles fifo parameter the same way as qemu (expecting .in and .out) --- debug_connection.py | 92 ++++++++++++++++++++++++++++++ windpl.py | 134 ++++++++++++++++---------------------------- 2 files changed, 141 insertions(+), 85 deletions(-) create mode 100644 debug_connection.py diff --git a/debug_connection.py b/debug_connection.py new file mode 100644 index 0000000..6c4e1b3 --- /dev/null +++ b/debug_connection.py @@ -0,0 +1,92 @@ +"""Abstract connection interface for kernel debugging.""" +import os +import pathlib + +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 + + def connect(self): + # FIXME: Add support for TCP sockets too + # self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + # self.client.connect(endpoint) + + path = pathlib.Path(self.endpoint) + if not path.exists(): + def require_fifo_exists(fifo_path): + path = pathlib.Path(fifo_path) + if not path.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.is_fifo(): + raise Exception(f"'{path.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"{self.endpoint}.in" + read_fifo = f"{self.endpoint}.out" + + require_fifo_exists(write_fifo) + require_fifo_exists(read_fifo) + + flags = os.O_RDWR + if os.name == 'nt': + flags |= os.O_BINARY + + self._connection_write = os.open(write_fifo, flags) + self._connection_read = os.open(read_fifo, flags) + + return + + if path.is_fifo(): + 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'.") + else: + raise Exception(f"Unsupported connection type {self.endpoint}") + + def disconnect(self): + pass + + def recv(self, max_bytes): + """Receives up to `max_bytes` bytes from the connection.""" + return os.read(self._connection_read, max_bytes) + + def send(self, buf): + return os.write(self._connection_write, buf) + + def read(self, wanted): + """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" + total = 0 + outbuf = bytearray([]) + while total < wanted: + # if False: + # if serial: + # (count, buf) = serial.read(1) + # else: + # # FH.blocking(1) + # count = client.sysread(buf, 1) + # if count == 0: + # die("eof") + # # print("client.read count %x\n", ord(buf) + # # FH.blocking(0) + buf = self.recv(wanted - total) + + count = len(buf) + if count: + total += count + outbuf += buf + + return outbuf + + def write(self, buffer): + """Writes `buffer` to the connection, blocking as necessary.""" + + while len(buffer): + written = self.send(buffer) + buffer = buffer[written:] diff --git a/windpl.py b/windpl.py index 4cc5051..79e87a9 100755 --- a/windpl.py +++ b/windpl.py @@ -55,6 +55,7 @@ import struct from struct import pack +from debug_connection import DebugConnection from windpl_extra import logical2physical PACKET_LEADER = 0x30303030 @@ -70,6 +71,18 @@ 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 @@ -185,73 +198,13 @@ def hexasc(buf): return out -# FIXME: Verify behaviour -def cksum(buf): +def generate_checksum(buf): v = 0 for b in buf: v += b return v -class DebugConnection: - """Models a connection to the target device (e.g. FIFO, socket).""" - - def __init__(self, endpoint): - self.endpoint = endpoint - self._connection = None - - def connect(self): - # FIXME: Add support for TCP sockets too - # self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - # self.client.connect(endpoint) - flags = os.O_RDWR - if os.name == 'nt': - flags |= os.O_BINARY - - self._connection = os.open(self.endpoint, flags) - - def disconnect(self): - pass - - def recv(self, max_bytes): - """Receives up to `max_bytes` bytes from the connection.""" - return os.read(self._connection, max_bytes) - - def send(self, buf): - return os.write(self._connection, buf) - - def read(self, wanted): - """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" - total = 0 - outbuf = bytearray([]) - while total < wanted: - # if False: - # if serial: - # (count, buf) = serial.read(1) - # else: - # # FH.blocking(1) - # count = client.sysread(buf, 1) - # if count == 0: - # die("eof") - # # print("client.read count %x\n", ord(buf) - # # FH.blocking(0) - buf = self.recv(wanted - total) - - count = len(buf) - if count: - total += count - outbuf += buf - - return outbuf - - def write(self, buffer): - """Writes `buffer` to the connection, blocking as necessary.""" - - while len(buffer): - written = self.send(buffer) - buffer = buffer[written:] - - class DebugContext: """Models a KernelDebug session.""" @@ -304,10 +257,13 @@ def connect(self, endpoint): self.client.connect() def run(self): + print("Waiting for target device...") self.sendReset() + packets = 0 while True: self.handlePacket() - print("") + print(f"{packets}") + packets += 1 # FIXME: windpl_loop.py @@ -335,42 +291,52 @@ def handlePacket(self): return ptype, buf def getPacket(self): - ptype = None + packet_type = None payload = bytearray([]) buf = self.client.read(4) - plh = unpack("I", buf) - if plh in (PACKET_LEADER, CONTROL_PACKET_LEADER): - print("Got packet leader: %08x" % plh) + packet_signature = unpack("I", buf) + if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): + print("Got packet leader: %08x" % packet_signature) buf = self.client.read(2) - ptype = unpack("H", buf) - print("Packet type: " + str(ptype) + "") + packet_type = unpack("H", buf) + packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") + print(f"Packet type: {packet_type} ({packet_type_name})") + if packet_type_name == "": + print("!! Unexpected packet type %04x" % packet_type) buf = self.client.read(2) - bc = unpack("H", buf) - print("Byte count: " + str(bc) + "") + data_size = unpack("H", buf) + print(f"Byte count: {data_size}") buf = self.client.read(4) - pid = unpack("I", buf) - self.nextpid = pid - print("Packet ID: %08x" % pid) + packet_id = unpack("I", buf) + self.nextpid = packet_id + print("Packet ID: %08x" % packet_id) buf = self.client.read(4) - ck = unpack("I", buf) - print("Checksum: %08x" % ck) + expected_checksum = unpack("I", buf) + print("Checksum: %08x" % expected_checksum) - if bc: - payload = self.client.read(bc) + if data_size: + payload = self.client.read(data_size) + + payload_checksum = generate_checksum(payload) + if payload_checksum != expected_checksum: + print(f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}") + sys.exit(1) # send ack if it's a non-control packet - if plh == PACKET_LEADER: + if packet_signature == PACKET_LEADER: # packet trailer trail = self.client.read(1) print(hexformat(trail)) if trail[0] == 0xAA: - # print("sending Ack\n";#) + print("sending Ack") self.sendAck() - return ptype, payload + else: + print("Discarding non-KD packet bytes: %08x" % packet_signature) + return packet_type, payload def handleDebugIO(self, buf): # pylint: disable = no-self-use apiNumber = unpack("I", substr(buf, 0, 4)) @@ -477,13 +443,11 @@ def sendAck(self): self.writeDev(ack) def sendReset(self): - rst = "\x69\x69\x69\x69\x06\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode( - "utf-8" - ) + reset_packet = pack("IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0) # print("Sending reset packet\n" # print(hexformat(rst) - self.writeDev(rst) + self.writeDev(reset_packet) def getContext(self): context = {} @@ -649,7 +613,7 @@ def packetHeader(self, d): ) # packet type PACKET_TYPE_KD_STATE_MANIPULATE header += pack("H", len(d)) # sizeof data header += pack("I", self.nextpid) # packet id - header += pack("I", cksum(d)) # checksum of data + header += pack("I", generate_checksum(d)) # checksum of data return header def sendManipulateStatePacket(self, d): From 70ccdc5eb7de0c35a6c15d2e63d429f7725c92a7 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 21:28:35 -0700 Subject: [PATCH 05/30] Fixes pylint warnings. --- windpl.py | 354 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 187 insertions(+), 167 deletions(-) diff --git a/windpl.py b/windpl.py index 79e87a9..89678f1 100755 --- a/windpl.py +++ b/windpl.py @@ -41,19 +41,13 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -# pylint: disable = invalid-name, missing-function-docstring, fixme - -# use IO::Select -# use IO::Socket -# use File::stat -# use Fcntl ':mode' -# use strict +# pylint: disable = invalid-name, missing-function-docstring, too-many-instance-attributes, fixme import argparse -import os +import logging import sys import struct -from struct import pack +from struct import pack # pylint: disable = no-name-in-module from debug_connection import DebugConnection from windpl_extra import logical2physical @@ -80,7 +74,7 @@ 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_MAX: "PACKET_TYPE_MAX", } # PACKET_TYPE_KD_DEBUG_IO apis @@ -140,11 +134,11 @@ def patch_substr(buf, start, length, fmt, data=None): if data is None: return buf[0:start] + fmt + buf[start + length :] - return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] + return buf[0:start] + pack(fmt, data) + buf[start + length :] def unpack(fmt, data): - return struct.unpack(fmt, data)[0] + return struct.unpack(fmt, data)[0] # pylint: disable = no-member def hexformat(buf): @@ -251,23 +245,23 @@ def connect(self, endpoint): # else: # die("dev not a character device or a socket") - print(f"Connecting to '{endpoint}'") + logging.info("Connecting to '%s'", endpoint) self.client = DebugConnection(endpoint) self.client.connect() def run(self): - print("Waiting for target device...") - self.sendReset() + logging.info("Waiting for target device...") + self._sendReset() packets = 0 while True: - self.handlePacket() - print(f"{packets}") + self._handlePacket() + logging.debug("Processed %d packets", packets) packets += 1 # FIXME: windpl_loop.py - def writeDev(self, data): + def _writeDev(self, data): # if False: # if serial: # serial.write(data) @@ -275,77 +269,78 @@ def writeDev(self, data): # self.client.syswrite(data) self.client.write(data) - def handlePacket(self): - ptype, buf = self.getPacket() + def _handlePacket(self): + ptype, buf = self._getPacket() if len(buf) == 0: return None if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: - self.handleStateManipulate(buf) + self._handleStateManipulate(buf) elif ptype == PACKET_TYPE_KD_DEBUG_IO: - self.handleDebugIO(buf) + self._handleDebugIO(buf) elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: - self.handleStateChange(buf) + self._handleStateChange(buf) return ptype, buf - def getPacket(self): + def _getPacket(self): packet_type = None payload = bytearray([]) buf = self.client.read(4) packet_signature = unpack("I", buf) if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): - print("Got packet leader: %08x" % packet_signature) + logging.debug("Got packet leader: %08x", packet_signature) buf = self.client.read(2) packet_type = unpack("H", buf) packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") - print(f"Packet type: {packet_type} ({packet_type_name})") + logging.debug("Packet type: %d (%s)", packet_type, packet_type_name) if packet_type_name == "": - print("!! Unexpected packet type %04x" % packet_type) + logging.critical("!! Unexpected packet type %04x", packet_type) buf = self.client.read(2) data_size = unpack("H", buf) - print(f"Byte count: {data_size}") + logging.debug("Byte count: %d", data_size) buf = self.client.read(4) packet_id = unpack("I", buf) self.nextpid = packet_id - print("Packet ID: %08x" % packet_id) + logging.debug("Packet ID: %08x", packet_id) buf = self.client.read(4) expected_checksum = unpack("I", buf) - print("Checksum: %08x" % expected_checksum) + logging.debug("Checksum: %08x", expected_checksum) if data_size: payload = self.client.read(data_size) payload_checksum = generate_checksum(payload) if payload_checksum != expected_checksum: - print(f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}") - sys.exit(1) + raise Exception( + f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" + ) # send ack if it's a non-control packet if packet_signature == PACKET_LEADER: # packet trailer trail = self.client.read(1) - print(hexformat(trail)) + logging.debug("Trailer: %s", hexformat(trail)) if trail[0] == 0xAA: - print("sending Ack") - self.sendAck() + # logging.debug("sending Ack") + self._sendAck() else: - print("Discarding non-KD packet bytes: %08x" % packet_signature) + logging.warning("Discarding non-KD packet bytes: %08x", packet_signature) return packet_type, payload - def handleDebugIO(self, buf): # pylint: disable = no-self-use + def _handleDebugIO(self, buf): # pylint: disable = no-self-use apiNumber = unpack("I", substr(buf, 0, 4)) if apiNumber == DbgKdPrintStringApi: - print("DBG PRINT STRING: " + substr(buf, 0x10).decode("utf-8")) + print("DbgPrint: " + substr(buf, 0x10).decode("utf-8")) - def handleStateChange(self, buf): + def _handleStateChange(self, buf): newState = unpack("I", substr(buf, 0, 4)) - print("State Change: %08x" % newState) + logging.debug("State Change: %08x", newState) if newState == DbgKdExceptionStateChange: exceptions = { @@ -378,15 +373,16 @@ def handleStateChange(self, buf): parameters = unpack("I", substr(ex, 24, 4)) if code in exceptions: - print("*** %s ", exceptions[code]) + logging.warning("*** %s ", exceptions[code]) else: - print("*** Exception %08x " % code) - print("at %08x\n", address) + logging.warning("*** Exception %08x ", code) + + logging.warning("at %08x\n", address) - print("Exception flags = %08x" % flags) - print("Exception record = %08x" % record) - print("Exception address = %08x" % address) - print("Number parameters = %08x" % parameters) + logging.warning("Exception flags = %08x", flags) + logging.warning("Exception record = %08x", record) + logging.warning("Exception address = %08x", address) + logging.warning("Number parameters = %08x", parameters) self.running = False @@ -398,61 +394,63 @@ def handleStateChange(self, buf): filename = substr(buf, 0x3B8) filename = filename.decode("utf-8").strip() - print("Load Symbols for " + filename + "") + logging.debug("Load Symbols for '%s'", filename) # nothing to do... - self.sendDbgKdContinue2() + self._sendDbgKdContinue2() - def handleStateManipulate(self, buf): + def _handleStateManipulate(self, buf): apiNumber = unpack("I", substr(buf, 0, 4)) - print("State Manipulate: %08x" % apiNumber) + logging.debug("State Manipulate: %08x", apiNumber) if apiNumber == DbgKdWriteBreakPointApi: bp = "%08x" % unpack("I", substr(buf, 16, 4)) handle = unpack("I", substr(buf, 20, 4)) - print("Breakpoint " + handle + " set at " + bp + "") + logging.debug("Breakpoint %d set at %s", handle, bp) self.breakpoints[bp] = handle elif apiNumber == DbgKdRestoreBreakPointApi: handle = unpack("I", substr(buf, 16, 4)) - print("Breakpoint " + handle + " cleared") + logging.debug("Breakpoint %d cleared", handle) elif apiNumber == DbgKdGetVersionApi: version = substr(buf, 16) - print("VERS: " + hexformat(version)) + logging.debug("VERS: %s", hexformat(version)) elif apiNumber == DbgKdReadVirtualMemoryApi: vmem = substr(buf, 56) - print("VMEM:\n" + hexasc(vmem)) + logging.debug("VMEM:\n%s", hexasc(vmem)) elif apiNumber == DbgKdReadPhysicalMemoryApi: pmem = substr(buf, 56) - print("PMEM:\n" + hexasc(pmem)) + logging.debug("PMEM:\n%s", hexasc(pmem)) elif apiNumber == DbgKdReadControlSpaceApi: controlspace = substr(buf, 56) - print("CNTL: " + hexformat(controlspace)) + logging.debug("CNTL: %s", hexformat(controlspace)) else: - print("UNKN: " + hexasc(buf)) + logging.debug("UNKN: %s", hexasc(buf)) - def sendAck(self): + def _sendAck(self): ack = "\x69\x69\x69\x69\x04\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode( "utf-8" ) - print(self.nextpid) + logging.debug("NextPID: %d", self.nextpid) ack = patch_substr(ack, 8, 4, "I", self.nextpid) - print(hexformat(ack)) - self.writeDev(ack) + logging.debug("Ack: %s", hexformat(ack)) + self._writeDev(ack) - def sendReset(self): - reset_packet = pack("IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0) + def _sendReset(self): + reset_packet = pack( + "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0 + ) # print("Sending reset packet\n" # print(hexformat(rst) - self.writeDev(reset_packet) + self._writeDev(reset_packet) - def getContext(self): + def _getContext(self): context = {} - self.sendDbgKdGetContext() - buf = self.waitStateManipulate(DbgKdGetContextApi) + self._sendDbgKdGetContext() + buf = self._waitStateManipulate(DbgKdGetContextApi) if len(buf) <= 204: return None @@ -495,7 +493,7 @@ def getContext(self): context["leftovers"] = ctx[204:] return context - def setContext(self, context): + def _setContext(self, context): ctx = pack("I", context["ContextFlags"]) ctx += pack("I", context["DR0"]) ctx += pack("I", context["DR1"]) @@ -529,14 +527,14 @@ def setContext(self, context): ctx += pack("I", context["ESP"]) ctx += pack("I", context["SS"]) ctx += context["leftovers"] - self.sendDbgKdSetContext(ctx) - self.waitStateManipulate(DbgKdSetContextApi) + self._sendDbgKdSetContext(ctx) + self._waitStateManipulate(DbgKdSetContextApi) - def getVersionInfo(self): - print("getVersionInfo") + def _getVersionInfo(self): + logging.info("getVersionInfo") # os version, protocol version, kernel base, module list, debugger data - self.sendDbgKdGetVersion() - buf = self.waitStateManipulate(DbgKdGetVersionApi) + self._sendDbgKdGetVersion() + buf = self._waitStateManipulate(DbgKdGetVersionApi) if len(buf) > 32: v = substr(buf, 16) osv = "%d.%d" % (unpack("H", substr(v, 4, 2)), unpack("H", substr(v, 6, 2))) @@ -546,28 +544,30 @@ def getVersionInfo(self): modlist = unpack("I", substr(v, 24, 4)) ddata = unpack("I", substr(v, 32, 4)) if pv < 5: - print("Debug protocol version %d not supported" % pv) + logging.critical("Debug protocol version %d not supported", pv) sys.exit() if machinetype and (machinetype != 0x2D): - print("Processor architecture %04x not supported" % machinetype) + logging.critical( + "Processor architecture %04x not supported", machinetype + ) sys.exit() - print("Windows version = %s" % osv) - print("Protocol version = %d" % pv) - print("Kernel base = %08x" % kernbase) - print("Module list = %08x" % modlist) - print("Debugger data = %08x" % ddata) + logging.info("Windows version = %s", osv) + logging.info("Protocol version = %d", pv) + logging.info("Kernel base = %08x", kernbase) + logging.info("Module list = %08x", modlist) + logging.info("Debugger data = %08x", ddata) return (osv, pv, kernbase, modlist, ddata) return ("0.0", 0, 0, 0, 0) - def printVersionData(self): - v = self.getVersionInfo() - print("Windows version = %s" % v[0]) - print("Protocol version = %d" % v[1]) - print("Kernel base = %08x" % v[2]) - print("Module list = %08x" % v[3]) - print("Debugger data = %08x" % v[4]) + def _printVersionData(self): + v = self._getVersionInfo() + logging.info("Windows version = %s", v[0]) + logging.info("Protocol version = %d", v[1]) + logging.info("Kernel base = %08x", v[2]) + logging.info("Module list = %08x", v[3]) + logging.info("Debugger data = %08x", v[4]) # if False: # def getKernelModules(): @@ -606,7 +606,7 @@ def printVersionData(self): # buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii # return buf - def packetHeader(self, d): + def _packetHeader(self, d): header = "\x30\x30\x30\x30".encode("utf-8") # packet leader header += "\x02\x00".encode( "utf-8" @@ -616,15 +616,15 @@ def packetHeader(self, d): header += pack("I", generate_checksum(d)) # checksum of data return header - def sendManipulateStatePacket(self, d): - h = self.packetHeader(d) + def _sendManipulateStatePacket(self, d): + h = self._packetHeader(d) - print("SEND: " + hexformat(h) + hexformat(d)) - self.writeDev(h) - self.writeDev(d) - self.writeDev(bytes([0xAA])) + logging.debug("SEND: " + hexformat(h) + hexformat(d)) + self._writeDev(h) + self._writeDev(d) + self._writeDev(bytes([0xAA])) - def sendDbgKdContinue2(self): + def _sendDbgKdContinue2(self): # print("Sending DbgKdContinue2Api packet\n" d = bytearray([0] * 56) @@ -633,16 +633,16 @@ def sendDbgKdContinue2(self): d = patch_substr(d, 16, 4, "I", 0x00010001) d = patch_substr(d, 24, 4, "I", 0x400) # TraceFlag d = patch_substr(d, 28, 4, "I", 0x01) # Dr7 - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdGetVersion(self): + def _sendDbgKdGetVersion(self): # print("Sending DbgKdGetVersionApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdGetVersionApi) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdWriteBreakPoint(self, bp): + def _sendDbgKdWriteBreakPoint(self, bp): bp = hex(bp) # print("Sending DbgKdWriteBreakPointApi packet\n" @@ -651,31 +651,31 @@ def sendDbgKdWriteBreakPoint(self, bp): d = patch_substr(d, 16, 4, "I", bp) d = patch_substr(d, 20, 4, "I", self.curbp) self.curbp += 1 - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdRestoreBreakPoint(self, bp): + def _sendDbgKdRestoreBreakPoint(self, bp): if bp in self.breakpoints: # print("Sending DbgKdRestoreBreakPointApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdRestoreBreakPointApi) d = patch_substr(d, 16, 4, "I", self.breakpoints[bp]) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) del self.breakpoints[bp] else: - print("Breakpoint not set at bp") + logging.warning("Breakpoint not set at bp") - def sendDbgKdReadControlSpace(self): + def _sendDbgKdReadControlSpace(self): # print("Sending DbgKdReadControlSpaceApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", 0x3137) d = patch_substr(d, 16, 4, "I", 0x02CC) d = patch_substr(d, 24, 4, "I", 84) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdWriteControlSpace(self): + def _sendDbgKdWriteControlSpace(self): # print("Sending DbgKdWriteControlSpaceApi packet\n" d = bytearray([0] * 56) @@ -683,44 +683,51 @@ def sendDbgKdWriteControlSpace(self): d = patch_substr(d, 16, 4, "I", 0x02CC) d = patch_substr(d, 24, 4, "I", 84) d += self.controlspace - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) self.controlspacesent = True - def sendDbgKdGetContext(self): + def _sendDbgKdGetContext(self): # print("Sending DbgKdGetContextApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdGetContextApi) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdSetContext(self, ctx): + def _sendDbgKdSetContext(self, ctx): # print("Sending DbgKdSetContextApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdSetContextApi) d = patch_substr(d, 16, 4, ctx[0:4]) d += ctx - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdReadPhysicalMemory(self, addr, readlen): + def _sendDbgKdReadPhysicalMemory(self, addr, readlen): # print("Sending DbgKdReadPhysicalMemoryApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdReadPhysicalMemoryApi) d = patch_substr(d, 16, 4, "I", addr) d = patch_substr(d, 24, 4, "I", readlen) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) + + def _sendDbgKdWritePhysicalMemory(self, addr, data): + writelen = len(data) + + d = pack("III", DbgKdWritePhysicalMemoryApi, addr, writelen) + d += data + self._sendManipulateStatePacket(d) - def sendDbgKdReadVirtualMemory(self, vaddr, readlen): + def _sendDbgKdReadVirtualMemory(self, vaddr, readlen): # print("Sending DbgKdReadVirtualMemoryApi packet\n" d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdReadVirtualMemoryApi) d = patch_substr(d, 16, 4, "I", vaddr) d = patch_substr(d, 24, 4, "I", readlen) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def sendDbgKdWriteVirtualMemory(self, vaddr, data): + def _sendDbgKdWriteVirtualMemory(self, vaddr, data): writelen = len(data) # print("Sending DbgKdWriteVirtualMemoryApi packet\n" @@ -729,54 +736,54 @@ def sendDbgKdWriteVirtualMemory(self, vaddr, data): d = patch_substr(d, 16, 4, "I", vaddr) d = patch_substr(d, 24, 4, "I", writelen) d += data - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def readDword(self, addr): + def _readDword(self, addr): # print("Reading dword at %08x\n", addr - buf = self.readVirtualMemory(addr, 4) + buf = self._readVirtualMemory(addr, 4) if len(buf) == 4: return unpack("I", buf) return "failed" - def readPhysicalMemory(self, addr, length): + def _readPhysicalMemory(self, addr, length): chunksize = 0x800 # max to request in one packet out = bytearray([]) while length > 0: if length < chunksize: - self.sendDbgKdReadPhysicalMemory(addr, length) - buf = self.waitStateManipulate(DbgKdReadPhysicalMemoryApi) + self._sendDbgKdReadPhysicalMemory(addr, length) + buf = self._waitStateManipulate(DbgKdReadPhysicalMemoryApi) if len(buf) > 56: out += substr(buf, 56) length = 0 else: - self.sendDbgKdReadPhysicalMemory(addr, chunksize) - buf = self.waitStateManipulate(DbgKdReadPhysicalMemoryApi) + self._sendDbgKdReadPhysicalMemory(addr, chunksize) + buf = self._waitStateManipulate(DbgKdReadPhysicalMemoryApi) if len(buf) > 56: out += substr(buf, 56) length -= chunksize addr += chunksize return out - def writePhysicalMemory(self, addr, buf): + def _writePhysicalMemory(self, addr, buf): length = len(buf) chunksize = 0x800 # max to send in one packet offset = 0 # FIXME: Logic sucks here.. while length > 0: if length < chunksize: - self.sendDbgKdWritePhysicalMemory(addr, buf) - self.waitStateManipulate(0x313E) + self._sendDbgKdWritePhysicalMemory(addr, buf) + self._waitStateManipulate(0x313E) length = 0 else: - self.sendDbgKdWritePhysicalMemory(addr, substr(buf, offset, chunksize)) - self.waitStateManipulate(0x313E) + self._sendDbgKdWritePhysicalMemory(addr, substr(buf, offset, chunksize)) + self._waitStateManipulate(0x313E) length -= chunksize offset += chunksize addr += chunksize - def writeVirtualMemory(self, addr, buf): + def _writeVirtualMemory(self, addr, buf): length = len(buf) # FIXME: return unless addr && length chunksize = 0x800 # max to send in one packet @@ -785,14 +792,14 @@ def writeVirtualMemory(self, addr, buf): # FIXME: Logic sucks here.. while length > 0: if length < chunksize: - self.sendDbgKdWriteVirtualMemory(addr, buf) - self.waitStateManipulate(DbgKdWriteVirtualMemoryApi) + self._sendDbgKdWriteVirtualMemory(addr, buf) + self._waitStateManipulate(DbgKdWriteVirtualMemoryApi) length = 0 else: - self.sendDbgKdWriteVirtualMemory( + self._sendDbgKdWriteVirtualMemory( addr, substr(buf, offset, chunksize) ) - self.waitStateManipulate(DbgKdWriteVirtualMemoryApi) + self._waitStateManipulate(DbgKdWriteVirtualMemoryApi) length -= chunksize offset += chunksize addr += chunksize @@ -801,12 +808,12 @@ def writeVirtualMemory(self, addr, buf): # FIXME: Logic sucks here.. if distance_to_page_boundary > length: physaddr = logical2physical(addr) - self.writePhysicalMemory(physaddr, buf) + self._writePhysicalMemory(physaddr, buf) return physaddr = logical2physical(addr) buf = buf[:distance_to_page_boundary] - self.writePhysicalMemory(physaddr, buf) + self._writePhysicalMemory(physaddr, buf) addr += distance_to_page_boundary offset += distance_to_page_boundary @@ -815,31 +822,31 @@ def writeVirtualMemory(self, addr, buf): while remainder > 0: if remainder < 0x1000: physaddr = logical2physical(addr) - self.writePhysicalMemory(physaddr, substr(buf, offset, remainder)) + self._writePhysicalMemory(physaddr, substr(buf, offset, remainder)) remainder = 0 else: physaddr = logical2physical(addr) - self.writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) + self._writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) addr += 0x1000 offset += 0x1000 remainder -= 0x1000 # FIXME: Indentation is horrible - def readVirtualMemory(self, addr, length): + def _readVirtualMemory(self, addr, length): # FIXME: return unless addr && length chunksize = 0x800 # max to request in one packet out = bytearray() if self.pcontext["pid"] == 0: while length > 0: if length < chunksize: - self.sendDbgKdReadVirtualMemory(addr, length) - buf = self.waitStateManipulate(DbgKdReadVirtualMemoryApi) + self._sendDbgKdReadVirtualMemory(addr, length) + buf = self._waitStateManipulate(DbgKdReadVirtualMemoryApi) if len(buf) > 56: out += substr(buf, 56) length = 0 else: - self.sendDbgKdReadVirtualMemory(addr, chunksize) - buf = self.waitStateManipulate(DbgKdReadVirtualMemoryApi) + self._sendDbgKdReadVirtualMemory(addr, chunksize) + buf = self._waitStateManipulate(DbgKdReadVirtualMemoryApi) if len(buf) > 56: out += substr(buf, 56) length -= chunksize @@ -849,58 +856,59 @@ def readVirtualMemory(self, addr, length): distance_to_page_boundary = 0x1000 - (addr & 0xFFF) if distance_to_page_boundary > length: physaddr = logical2physical(addr) - return self.readPhysicalMemory(physaddr, length) + return self._readPhysicalMemory(physaddr, length) physaddr = logical2physical(addr) - buf = self.readPhysicalMemory(physaddr, distance_to_page_boundary) + buf = self._readPhysicalMemory(physaddr, distance_to_page_boundary) addr += distance_to_page_boundary remainder = length - distance_to_page_boundary while remainder > 0: if remainder < 0x1000: physaddr = logical2physical(addr) - buf += self.readPhysicalMemory(physaddr, remainder) + buf += self._readPhysicalMemory(physaddr, remainder) remainder = 0 else: physaddr = logical2physical(addr) - buf += self.readPhysicalMemory(physaddr, 0x1000) + buf += self._readPhysicalMemory(physaddr, 0x1000) addr += 0x1000 remainder -= 0x1000 return buf - def sendDbgKdReboot(self): - print("Sending DbgKdRebootApi packet") + def _sendDbgKdReboot(self): + logging.info("Sending DbgKdRebootApi packet") d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", 0x313B) - self.sendManipulateStatePacket(d) + self._sendManipulateStatePacket(d) - def waitStateManipulate(self, wanted): + def _waitStateManipulate(self, wanted): if self.running: return None - try: - while 1: - (ptype, buf) = self.handlePacket(1) - if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: - api = unpack("I", substr(buf, 0, 4)) - if api == wanted: - break - except timeout: - print("Timeout waiting for %04x packet reply" % wanted) + # FIXME: Implement a timeout. + # try: + while 1: + ptype, buf = self._handlePacket() + if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: + api = unpack("I", substr(buf, 0, 4)) + if api == wanted: + break + # except timeout: + # logging.warning("Timeout waiting for %04x packet reply", wanted) return buf - def getPspCidTable(self): + def _getPspCidTable(self): pspcidtable = 0 save = self.pcontext self.pcontext = self.kernelcontext # this procedure is kernel context only - self.sendDbgKdGetVersion() - buf = self.waitStateManipulate(DbgKdGetVersionApi) + self._sendDbgKdGetVersion() + buf = self._waitStateManipulate(DbgKdGetVersionApi) pddata = unpack("I", substr(buf, 48, 4)) if pddata: # print("Pointer to debugger data struct is at %08x\n", pddata - ddata = self.readDword(pddata) + ddata = self._readDword(pddata) if ddata != "failed": # print("debugger data struct is at %08x\n", ddata - pspcidtable = self.readDword(ddata + 88) + pspcidtable = self._readDword(ddata + 88) if pspcidtable != "failed": # print("PspCidTable is %08x\n", $pspcidtable self.pcontext = save @@ -910,6 +918,9 @@ def getPspCidTable(self): def main(args): + log_level = logging.DEBUG if args.verbose else logging.INFO + logging.basicConfig(level=log_level) + context = DebugContext() context.connect(args.named_pipe) @@ -922,10 +933,19 @@ def main(args): def _parse_args(): parser = argparse.ArgumentParser() + parser.add_argument( "named_pipe", help="The path to the named pipe used by xemu", ) + + parser.add_argument( + "-v", + "--verbose", + help="Enables verbose logging information", + action="store_true", + ) + return parser.parse_args() sys.exit(main(_parse_args())) From 686e25e8273227785d3270e5b42a0f9e82ea8862 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 21:42:11 -0700 Subject: [PATCH 06/30] Adds parameter to create fifos. --- windpl.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/windpl.py b/windpl.py index 89678f1..c66e5ac 100755 --- a/windpl.py +++ b/windpl.py @@ -45,6 +45,8 @@ import argparse import logging +import os +import pathlib import sys import struct from struct import pack # pylint: disable = no-name-in-module @@ -917,10 +919,29 @@ def _getPspCidTable(self): return 0 +def _create_fifos(named_pipe): + + def create(fifo_path): + path = pathlib.Path(fifo_path) + if not path.exists(): + logging.debug("Creating fifo at '%s'", fifo_path) + os.mkfifo(fifo_path, 0o644) + return + + if not path.is_fifo(): + raise Exception(f"'{path.name}' already exists but is not a fifo.") + + create(f"{named_pipe}.in") + create(f"{named_pipe}.out") + + def main(args): log_level = logging.DEBUG if args.verbose else logging.INFO logging.basicConfig(level=log_level) + if args.create_fifo: + _create_fifos(args.named_pipe) + context = DebugContext() context.connect(args.named_pipe) @@ -939,6 +960,13 @@ def _parse_args(): help="The path to the named pipe used by xemu", ) + parser.add_argument( + "-c", + "--create-fifo", + help="Creates the named pipes if they do not already exist.", + action="store_true" + ) + parser.add_argument( "-v", "--verbose", From 7c13de33ba158a9752062367dcbc430c8dc3d6ec Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 22:20:40 -0700 Subject: [PATCH 07/30] Removes unnecessary _writeDev method. --- windpl.py | 71 +++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/windpl.py b/windpl.py index c66e5ac..696c9c7 100755 --- a/windpl.py +++ b/windpl.py @@ -263,14 +263,6 @@ def run(self): # FIXME: windpl_loop.py - def _writeDev(self, data): - # if False: - # if serial: - # serial.write(data) - # else: - # self.client.syswrite(data) - self.client.write(data) - def _handlePacket(self): ptype, buf = self._getPacket() @@ -283,6 +275,8 @@ def _handlePacket(self): self._handleDebugIO(buf) elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: self._handleStateChange(buf) + else: + logging.debug("Ignoring packet") return ptype, buf @@ -326,6 +320,7 @@ def _getPacket(self): # send ack if it's a non-control packet if packet_signature == PACKET_LEADER: # packet trailer + logging.debug("Reading trailer...") trail = self.client.read(1) logging.debug("Trailer: %s", hexformat(trail)) if trail[0] == 0xAA: @@ -342,7 +337,7 @@ def _handleDebugIO(self, buf): # pylint: disable = no-self-use def _handleStateChange(self, buf): newState = unpack("I", substr(buf, 0, 4)) - logging.debug("State Change: %08x", newState) + logging.debug("State Change: New state: %08x", newState) if newState == DbgKdExceptionStateChange: exceptions = { @@ -394,7 +389,7 @@ def _handleStateChange(self, buf): elif newState == DbgKdLoadSymbolsStateChange: # DBGKD_LOAD_SYMBOLS64 - filename = substr(buf, 0x3B8) + filename = buf[0x3B8:-1] filename = filename.decode("utf-8").strip() logging.debug("Load Symbols for '%s'", filename) @@ -431,14 +426,17 @@ def _handleStateManipulate(self, buf): logging.debug("UNKN: %s", hexasc(buf)) def _sendAck(self): - ack = "\x69\x69\x69\x69\x04\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00".encode( - "utf-8" + logging.debug("Acking with next PID: %08x", self.nextpid) + ack_packet = pack( + "IHHII", + CONTROL_PACKET_LEADER, + PACKET_TYPE_KD_ACKNOWLEDGE, + 0, + self.nextpid, + 0, ) - logging.debug("NextPID: %d", self.nextpid) - ack = patch_substr(ack, 8, 4, "I", self.nextpid) - - logging.debug("Ack: %s", hexformat(ack)) - self._writeDev(ack) + logging.debug("Ack: %s", hexformat(ack_packet)) + self.client.write(ack_packet) def _sendReset(self): reset_packet = pack( @@ -447,7 +445,7 @@ def _sendReset(self): # print("Sending reset packet\n" # print(hexformat(rst) - self._writeDev(reset_packet) + self.client.write(reset_packet) def _getContext(self): context = {} @@ -608,27 +606,28 @@ def _printVersionData(self): # buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii # return buf - def _packetHeader(self, d): - header = "\x30\x30\x30\x30".encode("utf-8") # packet leader - header += "\x02\x00".encode( - "utf-8" - ) # packet type PACKET_TYPE_KD_STATE_MANIPULATE - header += pack("H", len(d)) # sizeof data - header += pack("I", self.nextpid) # packet id - header += pack("I", generate_checksum(d)) # checksum of data - return header - def _sendManipulateStatePacket(self, d): - h = self._packetHeader(d) + header = pack( + "IHHII", + PACKET_LEADER, + PACKET_TYPE_KD_STATE_MANIPULATE, + len(d), + self.nextpid, + generate_checksum(d), + ) - logging.debug("SEND: " + hexformat(h) + hexformat(d)) - self._writeDev(h) - self._writeDev(d) - self._writeDev(bytes([0xAA])) + logging.debug( + "Sending manipulate state: header:\n%s\nBody:\n%s", + hexformat(header), + hexformat(d), + ) + self.client.write(header) + self.client.write(d) + self.client.write(bytes([0xAA])) def _sendDbgKdContinue2(self): - # print("Sending DbgKdContinue2Api packet\n" + logging.debug("Sending DbgKdContinue2Api packet") d = bytearray([0] * 56) d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) d = patch_substr(d, 8, 4, "I", 0x00010001) @@ -886,6 +885,7 @@ def _waitStateManipulate(self, wanted): if self.running: return None + logging.debug("Waiting on STATE_MANIPULATE packet") # FIXME: Implement a timeout. # try: while 1: @@ -920,7 +920,6 @@ def _getPspCidTable(self): def _create_fifos(named_pipe): - def create(fifo_path): path = pathlib.Path(fifo_path) if not path.exists(): @@ -964,7 +963,7 @@ def _parse_args(): "-c", "--create-fifo", help="Creates the named pipes if they do not already exist.", - action="store_true" + action="store_true", ) parser.add_argument( From 9a7770cf0e07d697cc26a19f204a8addb7b62161 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 7 Sep 2021 22:30:05 -0700 Subject: [PATCH 08/30] Gives a name to the packet trailer byte. --- windpl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/windpl.py b/windpl.py index 696c9c7..e50308c 100755 --- a/windpl.py +++ b/windpl.py @@ -56,6 +56,7 @@ PACKET_LEADER = 0x30303030 CONTROL_PACKET_LEADER = 0x69696969 +PACKET_TRAILER = 0xAA PACKET_TYPE_UNUSED = 0 PACKET_TYPE_KD_STATE_CHANGE32 = 1 @@ -323,7 +324,7 @@ def _getPacket(self): logging.debug("Reading trailer...") trail = self.client.read(1) logging.debug("Trailer: %s", hexformat(trail)) - if trail[0] == 0xAA: + if trail[0] == PACKET_TRAILER: # logging.debug("sending Ack") self._sendAck() else: @@ -617,13 +618,13 @@ def _sendManipulateStatePacket(self, d): ) logging.debug( - "Sending manipulate state: header:\n%s\nBody:\n%s", + "Sending manipulate state:\nHeader:\n%s\nBody:\n%s", hexformat(header), hexformat(d), ) self.client.write(header) self.client.write(d) - self.client.write(bytes([0xAA])) + self.client.write(bytes([PACKET_TRAILER])) def _sendDbgKdContinue2(self): From a81c5ce1d1dfb5332345b5f44524267f8d4d95c5 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 8 Sep 2021 15:10:44 -0700 Subject: [PATCH 09/30] Adds timing info. --- debug_connection.py | 6 ++- windpl.py | 95 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/debug_connection.py b/debug_connection.py index 6c4e1b3..b32d848 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -55,7 +55,11 @@ def disconnect(self): def recv(self, max_bytes): """Receives up to `max_bytes` bytes from the connection.""" - return os.read(self._connection_read, max_bytes) + 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(self, buf): return os.write(self._connection_write, buf) diff --git a/windpl.py b/windpl.py index e50308c..9a25650 100755 --- a/windpl.py +++ b/windpl.py @@ -50,6 +50,7 @@ import sys import struct from struct import pack # pylint: disable = no-name-in-module +import time from debug_connection import DebugConnection from windpl_extra import logical2physical @@ -126,6 +127,23 @@ DbgKdSetBusDataApi = 0x00003158 DbgKdCheckLowMemoryApi = 0x00003159 +HRESULT_STATUS_SUCCESS = 0x00000000 +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 + def substr(buf, start, length=None): if length is None: @@ -206,6 +224,7 @@ class DebugContext: """Models a KernelDebug session.""" def __init__(self) -> None: + self.start_time = time.perf_counter() self.timeout = 10 # max time to wait on packet self.running = True @@ -259,24 +278,31 @@ def run(self): packets = 0 while True: self._handlePacket() - logging.debug("Processed %d packets", packets) + logging.debug("Processed %d packets\n", packets) packets += 1 # FIXME: windpl_loop.py + @property + def elapsed_time(self): + return time.perf_counter() - self.start_time + def _handlePacket(self): ptype, buf = self._getPacket() - if len(buf) == 0: - return None - if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: self._handleStateManipulate(buf) elif ptype == PACKET_TYPE_KD_DEBUG_IO: self._handleDebugIO(buf) elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: self._handleStateChange(buf) - else: + elif ptype == PACKET_TYPE_KD_RESET: + # TODO: check to see if this reset is in response to a reset sent by the debugger script. + # If not, dump the read buffer and start over. + pass + elif ptype == PACKET_TYPE_KD_RESEND: + logging.warning("!! Ignoring resend request") + elif len(buf): logging.debug("Ignoring packet") return ptype, buf @@ -287,27 +313,33 @@ def _getPacket(self): buf = self.client.read(4) packet_signature = unpack("I", buf) if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): - logging.debug("Got packet leader: %08x", packet_signature) + logging.debug( + "[%d] Got packet leader: %08x (%s)", + int(self.elapsed_time * 1000), + packet_signature, + "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", + ) buf = self.client.read(2) packet_type = unpack("H", buf) packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") - logging.debug("Packet type: %d (%s)", packet_type, packet_type_name) + logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) if packet_type_name == "": logging.critical("!! Unexpected packet type %04x", packet_type) buf = self.client.read(2) data_size = unpack("H", buf) - logging.debug("Byte count: %d", data_size) buf = self.client.read(4) packet_id = unpack("I", buf) self.nextpid = packet_id - logging.debug("Packet ID: %08x", packet_id) buf = self.client.read(4) expected_checksum = unpack("I", buf) - logging.debug("Checksum: %08x", expected_checksum) + + logging.debug("> Packet ID: %08x", packet_id) + logging.debug("> Data size: %d", data_size) + logging.debug("> Checksum: %08x", expected_checksum) if data_size: payload = self.client.read(data_size) @@ -321,12 +353,14 @@ def _getPacket(self): # send ack if it's a non-control packet if packet_signature == PACKET_LEADER: # packet trailer - logging.debug("Reading trailer...") + # logging.debug("Reading trailer...") trail = self.client.read(1) - logging.debug("Trailer: %s", hexformat(trail)) + # logging.debug("Trailer: %x", trail[0]) if trail[0] == PACKET_TRAILER: # logging.debug("sending Ack") self._sendAck() + else: + raise Exception("Invalid packet trailer 0x%x" % trail[0]) else: logging.warning("Discarding non-KD packet bytes: %08x", packet_signature) return packet_type, payload @@ -335,6 +369,8 @@ def _handleDebugIO(self, buf): # pylint: disable = no-self-use apiNumber = unpack("I", substr(buf, 0, 4)) if apiNumber == DbgKdPrintStringApi: print("DbgPrint: " + substr(buf, 0x10).decode("utf-8")) + else: + logging.debug("Ignoring debug IO packet with API number %d", apiNumber) def _handleStateChange(self, buf): newState = unpack("I", substr(buf, 0, 4)) @@ -391,7 +427,7 @@ def _handleStateChange(self, buf): # DBGKD_LOAD_SYMBOLS64 filename = buf[0x3B8:-1] - filename = filename.decode("utf-8").strip() + filename = filename.decode("utf-8") logging.debug("Load Symbols for '%s'", filename) # nothing to do... @@ -436,7 +472,7 @@ def _sendAck(self): self.nextpid, 0, ) - logging.debug("Ack: %s", hexformat(ack_packet)) + # logging.debug("Ack: %s", hexformat(ack_packet)) self.client.write(ack_packet) def _sendReset(self): @@ -444,7 +480,7 @@ def _sendReset(self): "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0 ) - # print("Sending reset packet\n" + logging.debug("Sending reset packet\n\n") # print(hexformat(rst) self.client.write(reset_packet) @@ -618,7 +654,7 @@ def _sendManipulateStatePacket(self, d): ) logging.debug( - "Sending manipulate state:\nHeader:\n%s\nBody:\n%s", + "< manipulate state:\nHeader:\n%s\nBody:\n%s", hexformat(header), hexformat(d), ) @@ -627,14 +663,27 @@ def _sendManipulateStatePacket(self, d): self.client.write(bytes([PACKET_TRAILER])) def _sendDbgKdContinue2(self): - - logging.debug("Sending DbgKdContinue2Api packet") + logging.debug("< Sending DbgKdContinueApi2 packet") d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) - d = patch_substr(d, 8, 4, "I", 0x00010001) - d = patch_substr(d, 16, 4, "I", 0x00010001) - d = patch_substr(d, 24, 4, "I", 0x400) # TraceFlag - d = patch_substr(d, 28, 4, "I", 0x01) # Dr7 + + # Manipulate state packet + d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) # API Number + # d = patch_substr(d, 4, 2, "H", 0x0001) # Processor level + # d = patch_substr(d, 6, 2, "H", 0x0001) # Processor + d = patch_substr(d, 8, 4, "I", HRESULT_STATUS_SUCCESS) # Return status + + # Continue2 subpacket + d = patch_substr(d, 12, 4, "I", HRESULT_STATUS_SUCCESS) # ContinueStatus + d = patch_substr(d, 16, 4, "I", 0x00000000) # TraceFlag + d = patch_substr(d, 20, 4, "I", 0x00000000) # DR7 + d = patch_substr(d, 24, 4, "I", 0x00000001) # CurrentSymbolStart + d = patch_substr(d, 28, 4, "I", 0x00000001) # CurrentSymbolEnd + + # substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 ); + # substr( $d, 8, 4 ) = pack( "I", 0x00010001 ); + # substr( $d, 16, 4 ) = pack( "I", 0x00010001 ); + # substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag + # substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7 self._sendManipulateStatePacket(d) def _sendDbgKdGetVersion(self): From 0439eddb5a6adff3bdd20fcab484b66c3e6ea7b4 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 8 Sep 2021 22:19:02 -0700 Subject: [PATCH 10/30] Adds basic KD sniffer. --- constants.py | 151 +++++++++++++++++++++++ debug_connection.py | 10 +- sniffer.py | 294 ++++++++++++++++++++++++++++++++++++++++++++ util.py | 118 ++++++++++++++++++ windpl.py | 204 ++---------------------------- 5 files changed, 581 insertions(+), 196 deletions(-) create mode 100644 constants.py create mode 100755 sniffer.py create mode 100644 util.py diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..9c96421 --- /dev/null +++ b/constants.py @@ -0,0 +1,151 @@ +"""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. + +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 + +# 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 + +HRESULT_STATUS_SUCCESS = 0x00000000 +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", +} \ No newline at end of file diff --git a/debug_connection.py b/debug_connection.py index b32d848..06717ba 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -2,6 +2,7 @@ import os import pathlib + class DebugConnection: """Models a connection to the target device (e.g. FIFO, socket).""" @@ -17,12 +18,14 @@ def connect(self): path = pathlib.Path(self.endpoint) if not path.exists(): + def require_fifo_exists(fifo_path): path = pathlib.Path(fifo_path) if not path.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'.") + "E.g., '/tmp/foo.in' and '/tmp/foo.out'." + ) if not path.is_fifo(): raise Exception(f"'{path.name}' is not a fifo.") @@ -35,7 +38,7 @@ def require_fifo_exists(fifo_path): require_fifo_exists(read_fifo) flags = os.O_RDWR - if os.name == 'nt': + if os.name == "nt": flags |= os.O_BINARY self._connection_write = os.open(write_fifo, flags) @@ -46,7 +49,8 @@ def require_fifo_exists(fifo_path): if path.is_fifo(): 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'.") + "E.g., '/tmp/foo.in' and '/tmp/foo.out'." + ) else: raise Exception(f"Unsupported connection type {self.endpoint}") diff --git a/sniffer.py b/sniffer.py new file mode 100755 index 0000000..b140d15 --- /dev/null +++ b/sniffer.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 +"""Sniffer proxy to process kernel debug traffic.""" + +# Usage: +# 1. Start the debugger qemu instance with +# `-serial tcp::,server,nowait` +# 2. Start this sniffer, passing the and another arbitrary +# port () +# 3. Start the debuggee qemu instance with +# `-serial tcp::` + +import argparse +import logging +import socket +import socketserver +import sys +import threading +import time + +from constants import * +from util import * + + +class _KDPassthroughSniffer: + def __init__(self, name, read_connection, write_connection): + self.name = name + self.read_connection = read_connection + self.write_connection = write_connection + + def _read(self, wanted): + """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" + total = 0 + ret = bytearray([]) + while total < wanted: + buf = self.read_connection.recv(wanted - total) + count = len(buf) + if count: + total += count + ret += buf + + return ret + + def _read_and_passthrough(self, wanted): + ret = self._read(wanted) + if self.write_connection: + # TODO: Handle exception when the target socket is closed. + self.write_connection.sendall(ret) + return ret + + def read_packet(self): + payload = bytearray([]) + buf = self._read_and_passthrough(4) + packet_signature = unpack("I", buf) + + if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): + logging.debug("\n\n[%s]", self.name) + logging.debug( + "Packet leader: %08x (%s)", + packet_signature, + "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", + ) + + buf = self._read_and_passthrough(2) + packet_type = unpack("H", buf) + packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") + logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) + if packet_type_name == "": + logging.critical("!! Unexpected packet type %04x", packet_type) + + buf = self._read_and_passthrough(2) + data_size = unpack("H", buf) + + buf = self._read_and_passthrough(4) + packet_id = unpack("I", buf) + + buf = self._read_and_passthrough(4) + expected_checksum = unpack("I", buf) + + logging.debug("> Packet ID: %08x", packet_id) + logging.debug("> Data size: %d", data_size) + logging.debug("> Checksum: %08x", expected_checksum) + + if data_size: + payload = self._read_and_passthrough(data_size) + + payload_checksum = generate_checksum(payload) + if payload_checksum != expected_checksum: + raise Exception( + f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" + ) + + # send ack if it's a non-control packet + if packet_signature == PACKET_LEADER: + # packet trailer + # logging.debug("Reading trailer...") + trail = self._read_and_passthrough(1) + # logging.debug("Trailer: %x", trail[0]) + if trail[0] != PACKET_TRAILER: + raise Exception("Invalid packet trailer 0x%x" % trail[0]) + + self._log_packet(packet_type, payload) + else: + logging.warning("Discarding non-KD packet bytes: %08x", packet_signature) + + return True + + def _log_packet(self, packet_type, payload): + if packet_type == PACKET_TYPE_KD_STATE_MANIPULATE: + self._log_state_manipulate(payload) + elif packet_type == PACKET_TYPE_KD_STATE_CHANGE64: + self._log_state_change64(payload) + elif payload: + logging.debug("%s", hexformat(payload)) + + def _log_state_manipulate(self, payload): + apiNumber = unpack("I", substr(payload, 0, 4)) + logging.debug("State Manipulate: %08x", apiNumber) + + if apiNumber == DbgKdWriteBreakPointApi: + bp = "%08x" % unpack("I", substr(payload, 16, 4)) + handle = unpack("I", substr(payload, 20, 4)) + logging.debug("Breakpoint %d set at %s", handle, bp) + elif apiNumber == DbgKdRestoreBreakPointApi: + handle = unpack("I", substr(payload, 16, 4)) + logging.debug("Breakpoint %d cleared", handle) + elif apiNumber == DbgKdGetVersionApi: + version = substr(payload, 16) + logging.debug("VERS: %s", hexformat(version)) + elif apiNumber == DbgKdReadVirtualMemoryApi: + vmem = substr(payload, 56) + logging.debug("VMEM:\n%s", hexasc(vmem)) + elif apiNumber == DbgKdReadPhysicalMemoryApi: + pmem = substr(payload, 56) + logging.debug("PMEM:\n%s", hexasc(pmem)) + elif apiNumber == DbgKdReadControlSpaceApi: + controlspace = substr(payload, 56) + logging.debug("CNTL: %s", hexformat(controlspace)) + else: + logging.debug("UNKN: %s", hexasc(payload)) + + def _log_state_change64(self, payload): + newState = unpack("I", substr(payload, 0, 4)) + logging.debug("State Change: New state: %08x", newState) + + if newState == DbgKdExceptionStateChange: + # DBGKM_EXCEPTION64 + ex = substr(payload, 32) + + code = unpack("I", substr(ex, 0, 4)) + flags = unpack("I", substr(ex, 4, 4)) + record = unpack("I", substr(ex, 8, 4)) + address = unpack("I", substr(ex, 16, 4)) + parameters = unpack("I", substr(ex, 24, 4)) + + if code in STATE_CHANGE_EXCEPTIONS: + logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) + else: + logging.warning("*** Exception %08x ", code) + + logging.warning("at %08x\n", address) + + logging.warning("Exception flags = %08x", flags) + logging.warning("Exception record = %08x", record) + logging.warning("Exception address = %08x", address) + logging.warning("Number parameters = %08x", parameters) + + self.running = False + + # my @v = getVersionInfo() + # version = v[0] + # $kernelbase = v[2] + elif newState == DbgKdLoadSymbolsStateChange: + # DBGKD_LOAD_SYMBOLS64 + + filename = payload[0x3B8:-1] + filename = filename.decode("utf-8") + logging.debug("Load Symbols for '%s'", filename) + + +class _DebuggerConnection: + """Handles connection to the debugger VM.""" + + def __init__(self, host, port): + self.host = host + self.port = port + self.debugger_socket = socket.socket() + self.debugger_socket.connect((host, port)) + + self.debuggee_semaphore = threading.BoundedSemaphore() + self.debuggee = None + + self.sniffer = None + + def register_debuggee(self, handler) -> _KDPassthroughSniffer: + with self.debuggee_semaphore: + self.debuggee = handler + self.sniffer = _KDPassthroughSniffer( + "Debugger", self.debugger_socket, handler.connection + ) + return _KDPassthroughSniffer("Target", handler.connection, self.debugger_socket) + + def unregister_debuggee(self, _handler): + with self.debuggee_semaphore: + self.debuggee = None + + def sendall(self, data, flags=0): + return self.debugger_socket.sendall(data, flags) + + def _debugger_thread_main(self): + while True: + with self.debuggee_semaphore: + sniffer = self.sniffer + if not sniffer: + time.sleep(0.100) + continue + + # TODO: Make this handle unregistration gracefully. + sniffer.read_packet() + + +class _DebuggeeHandler(socketserver.StreamRequestHandler): + """Handles connection to a debuggee VM.""" + + def setup(self) -> None: + super(_DebuggeeHandler, self).setup() + self.debugger = self.server.debugger + self.sniffer = self.debugger.register_debuggee(self) + + def finish(self) -> None: + super(_DebuggeeHandler, self).finish() + self.debugger.unregister_debuggee(self) + + def handle(self) -> None: + print(f"Debuggee connected from {self.client_address[0]}") + + while True: + if not self.sniffer.read_packet(): + break + + +def main(args): + """Main entrypoint""" + logging.basicConfig(level=logging.DEBUG) + + print(f"Connecting to debugger at {args.debugger_ip}:{args.debugger_port}") + debugger_connection = _DebuggerConnection(args.debugger_ip, args.debugger_port) + + print(f"Listening for debuggee at {args.sniffer_ip}:{args.sniffer_port}") + server = socketserver.ThreadingTCPServer( + (args.sniffer_ip, args.sniffer_port), _DebuggeeHandler + ) + server.debugger = debugger_connection + + debugger_thread = threading.Thread( + target=_DebuggerConnection._debugger_thread_main, args=(debugger_connection,) + ) + debugger_thread.start() + server.serve_forever() + + +if __name__ == "__main__": + + def _parse_args(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "debugger_port", + type=int, + help="The port used by the VM hosting the debugger.", + ) + + parser.add_argument( + "sniffer_port", + type=int, + help="The port that the debuggee's COM port should connect to instead of the VM port.", + ) + + parser.add_argument( + "-dip", + "--debugger_ip", + help="The IP address on which the debugger VM is listening.", + default="localhost", + ) + + parser.add_argument( + "-sip", + "--sniffer_ip", + help="The IP address on which the sniffer will listen for the debuggee.", + default="0.0.0.0", + ) + + return parser.parse_args() + + sys.exit(main(_parse_args())) diff --git a/util.py b/util.py new file mode 100644 index 0000000..1104949 --- /dev/null +++ b/util.py @@ -0,0 +1,118 @@ +"""Utility methods""" + +# 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. + +import struct + + +def substr(buf, start, length=None): + if length is None: + return buf[start:] + return buf[start : start + length] + + +def patch_substr(buf, start, length, fmt, data=None): + if data is None: + return buf[0:start] + fmt + buf[start + length :] + + return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] + + +def unpack(fmt, data): + return struct.unpack(fmt, data)[0] # pylint: disable = no-member + + +def hexformat(buf): + length = len(buf) + if length == 0: + return None + + b = "0000 " + c = 0 + for x in buf: + c += 1 + b += "%02x " % x + if (c % 16) == 0 and c < length: + b += "\n%04x " % c + + if b[-1] != "\n": + b += "\n" + + return b + + +def hexasc(buf): + length = len(buf) + if length == 0: + return None + + count = 0 + ascii_string = "" + out = "0000 " + for x in buf: + c = ord(x) + out += "%02x " % c + if 0x1F < c < 0x7F: + ascii_string += x + else: + ascii_string += "." + count += 1 + if (count % 16) == 0: + if count < length: + out += " " + ascii_string + "\n%04x " % count + else: + out += " " + ascii_string + "\n" + ascii_string = "" + + padding = 0 + if ascii_string: + padding = 48 - ((count % 16) * 3) + + out += " " * padding + out += " " + ascii_string + "\n" + return out + + +def generate_checksum(buf): + v = 0 + for b in buf: + v += b + return v diff --git a/windpl.py b/windpl.py index 9a25650..205ee45 100755 --- a/windpl.py +++ b/windpl.py @@ -52,173 +52,11 @@ from struct import pack # pylint: disable = no-name-in-module import time +from constants import * from debug_connection import DebugConnection +from util import * from windpl_extra import logical2physical -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 - -# 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 - -HRESULT_STATUS_SUCCESS = 0x00000000 -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 - - -def substr(buf, start, length=None): - if length is None: - return buf[start:] - return buf[start : start + length] - - -def patch_substr(buf, start, length, fmt, data=None): - if data is None: - return buf[0:start] + fmt + buf[start + length :] - - return buf[0:start] + pack(fmt, data) + buf[start + length :] - - -def unpack(fmt, data): - return struct.unpack(fmt, data)[0] # pylint: disable = no-member - - -def hexformat(buf): - length = len(buf) - if length == 0: - return None - - b = "0000 " - c = 0 - for x in buf: - c += 1 - b += "%02x " % x - if (c % 16) == 0 and c < length: - b += "\n%04x " % c - - if b[-1] != "\n": - b += "\n" - - return b - - -def hexasc(buf): - length = len(buf) - if length == 0: - return None - - count = 0 - ascii_string = "" - out = "0000 " - for x in buf: - c = ord(x) - out += "%02x " % c - if 0x1F < c < 0x7F: - ascii_string += x - else: - ascii_string += "." - count += 1 - if (count % 16) == 0: - if count < length: - out += " " + ascii_string + "\n%04x " % count - else: - out += " " + ascii_string + "\n" - ascii_string = "" - - padding = 0 - if ascii_string: - padding = 48 - ((count % 16) * 3) - - out += " " * padding - out += " " + ascii_string + "\n" - return out - - -def generate_checksum(buf): - v = 0 - for b in buf: - v += b - return v - class DebugContext: """Models a KernelDebug session.""" @@ -310,7 +148,7 @@ def _handlePacket(self): def _getPacket(self): packet_type = None payload = bytearray([]) - buf = self.client.read(4) + buf = self.client._read_and_passthrough(4) packet_signature = unpack("I", buf) if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): logging.debug( @@ -320,21 +158,21 @@ def _getPacket(self): "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", ) - buf = self.client.read(2) + buf = self.client._read_and_passthrough(2) packet_type = unpack("H", buf) packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) if packet_type_name == "": logging.critical("!! Unexpected packet type %04x", packet_type) - buf = self.client.read(2) + buf = self.client._read_and_passthrough(2) data_size = unpack("H", buf) - buf = self.client.read(4) + buf = self.client._read_and_passthrough(4) packet_id = unpack("I", buf) self.nextpid = packet_id - buf = self.client.read(4) + buf = self.client._read_and_passthrough(4) expected_checksum = unpack("I", buf) logging.debug("> Packet ID: %08x", packet_id) @@ -342,7 +180,7 @@ def _getPacket(self): logging.debug("> Checksum: %08x", expected_checksum) if data_size: - payload = self.client.read(data_size) + payload = self.client._read_and_passthrough(data_size) payload_checksum = generate_checksum(payload) if payload_checksum != expected_checksum: @@ -354,7 +192,7 @@ def _getPacket(self): if packet_signature == PACKET_LEADER: # packet trailer # logging.debug("Reading trailer...") - trail = self.client.read(1) + trail = self.client._read_and_passthrough(1) # logging.debug("Trailer: %x", trail[0]) if trail[0] == PACKET_TRAILER: # logging.debug("sending Ack") @@ -377,26 +215,6 @@ def _handleStateChange(self, buf): logging.debug("State Change: New state: %08x", newState) if newState == DbgKdExceptionStateChange: - 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", - } - # DBGKM_EXCEPTION64 ex = substr(buf, 32) @@ -406,8 +224,8 @@ def _handleStateChange(self, buf): address = unpack("I", substr(ex, 16, 4)) parameters = unpack("I", substr(ex, 24, 4)) - if code in exceptions: - logging.warning("*** %s ", exceptions[code]) + if code in STATE_CHANGE_EXCEPTIONS: + logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) else: logging.warning("*** Exception %08x ", code) From b68a13a0d0342d273d094bcf6ef854aae32b3aa0 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 07:26:36 -0700 Subject: [PATCH 11/30] Adds graceful handling of desync in sniffer. --- constants.py | 2 +- sniffer.py | 238 +++++++++++++++++++++++++++++++++------------------ windpl.py | 6 +- 3 files changed, 158 insertions(+), 88 deletions(-) diff --git a/constants.py b/constants.py index 9c96421..1522dce 100644 --- a/constants.py +++ b/constants.py @@ -148,4 +148,4 @@ 0xC0000094: "EXCEPTION_INT_DIVIDE_BY_ZERO", 0xC0000035: "EXCEPTION_INT_OVERFLOW", 0xC00000FD: "EXCEPTION_STACK_OVERFLOW", -} \ No newline at end of file +} diff --git a/sniffer.py b/sniffer.py index b140d15..038508f 100755 --- a/sniffer.py +++ b/sniffer.py @@ -10,7 +10,6 @@ # `-serial tcp::` import argparse -import logging import socket import socketserver import sys @@ -22,11 +21,22 @@ class _KDPassthroughSniffer: - def __init__(self, name, read_connection, write_connection): + def __init__( + self, name, read_connection, write_connection, logger_semaphore, start_time + ): self.name = name self.read_connection = read_connection self.write_connection = write_connection + self._logger_semaphore = logger_semaphore + self._packet_log = [] + + self.start_time = start_time + + @property + def elapsed_time_ms(self): + return int((time.perf_counter() - self.start_time) * 1000) + def _read(self, wanted): """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" total = 0 @@ -47,61 +57,94 @@ def _read_and_passthrough(self, wanted): self.write_connection.sendall(ret) return ret + def _log(self, message, *args): + """Buffers log messages until a packet is fully processed so the messages can be sent to the logger thread.""" + self._packet_log.append(message % args) + + def _flush_log(self): + with self._logger_semaphore: + for line in self._packet_log: + print(line) + print("") + + self._packet_log = [] + + def _read_until_packet_leader(self): + buf = self._read_and_passthrough(4) + packet_signature = unpack("I", buf) + + discarded_bytes = [] + now = self.elapsed_time_ms + + while packet_signature not in (PACKET_LEADER, CONTROL_PACKET_LEADER): + discarded_bytes += buf[0] + buf = buf[1:] + self._read_and_passthrough(1) + packet_signature = unpack("I", buf) + + if discarded_bytes: + self._log( + "[%s] @ %d: Discarded non-KD packet bytes %s", + self.name, + now, + hexformat(discarded_bytes), + ) + self._flush_log() + + return packet_signature + def read_packet(self): payload = bytearray([]) + + packet_signature = self._read_until_packet_leader() + + self._log("[%s] @ %d", self.name, self.elapsed_time_ms) + self._log( + "Packet leader: %08x (%s)", + packet_signature, + "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", + ) + + buf = self._read_and_passthrough(2) + packet_type = unpack("H", buf) + packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") + self._log("> Packet type: %d (%s)", packet_type, packet_type_name) + if packet_type_name == "": + self._log("!! Unexpected packet type %04x", packet_type) + + buf = self._read_and_passthrough(2) + data_size = unpack("H", buf) + buf = self._read_and_passthrough(4) - packet_signature = unpack("I", buf) + packet_id = unpack("I", buf) - if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): - logging.debug("\n\n[%s]", self.name) - logging.debug( - "Packet leader: %08x (%s)", - packet_signature, - "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", + buf = self._read_and_passthrough(4) + expected_checksum = unpack("I", buf) + + self._log("> Packet ID: %08x", packet_id) + self._log("> Data size: %d", data_size) + self._log("> Checksum: %08x", expected_checksum) + + if data_size: + payload = self._read_and_passthrough(data_size) + + payload_checksum = generate_checksum(payload) + if payload_checksum != expected_checksum: + raise Exception( + f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" ) - buf = self._read_and_passthrough(2) - packet_type = unpack("H", buf) - packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") - logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) - if packet_type_name == "": - logging.critical("!! Unexpected packet type %04x", packet_type) - - buf = self._read_and_passthrough(2) - data_size = unpack("H", buf) - - buf = self._read_and_passthrough(4) - packet_id = unpack("I", buf) - - buf = self._read_and_passthrough(4) - expected_checksum = unpack("I", buf) - - logging.debug("> Packet ID: %08x", packet_id) - logging.debug("> Data size: %d", data_size) - logging.debug("> Checksum: %08x", expected_checksum) - - if data_size: - payload = self._read_and_passthrough(data_size) - - payload_checksum = generate_checksum(payload) - if payload_checksum != expected_checksum: - raise Exception( - f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" - ) - - # send ack if it's a non-control packet - if packet_signature == PACKET_LEADER: - # packet trailer - # logging.debug("Reading trailer...") - trail = self._read_and_passthrough(1) - # logging.debug("Trailer: %x", trail[0]) - if trail[0] != PACKET_TRAILER: - raise Exception("Invalid packet trailer 0x%x" % trail[0]) - - self._log_packet(packet_type, payload) - else: - logging.warning("Discarding non-KD packet bytes: %08x", packet_signature) + # send ack if it's a non-control packet + if packet_signature == PACKET_LEADER: + # packet trailer + # self._log("Reading trailer...") + trail = self._read_and_passthrough(1) + # self._log("Trailer: %x", trail[0]) + if trail[0] != PACKET_TRAILER: + raise Exception("Invalid packet trailer 0x%x" % trail[0]) + self._log_packet(packet_type, payload) + + self._flush_log() return True def _log_packet(self, packet_type, payload): @@ -110,37 +153,38 @@ def _log_packet(self, packet_type, payload): elif packet_type == PACKET_TYPE_KD_STATE_CHANGE64: self._log_state_change64(payload) elif payload: - logging.debug("%s", hexformat(payload)) + self._log("%s", hexformat(payload)) def _log_state_manipulate(self, payload): apiNumber = unpack("I", substr(payload, 0, 4)) - logging.debug("State Manipulate: %08x", apiNumber) + self._log("State Manipulate: %08x", apiNumber) + self._log(hexformat(substr(payload, 0, 16))) if apiNumber == DbgKdWriteBreakPointApi: bp = "%08x" % unpack("I", substr(payload, 16, 4)) handle = unpack("I", substr(payload, 20, 4)) - logging.debug("Breakpoint %d set at %s", handle, bp) + self._log("Breakpoint %d set at %s", handle, bp) elif apiNumber == DbgKdRestoreBreakPointApi: handle = unpack("I", substr(payload, 16, 4)) - logging.debug("Breakpoint %d cleared", handle) + self._log("Breakpoint %d cleared", handle) elif apiNumber == DbgKdGetVersionApi: version = substr(payload, 16) - logging.debug("VERS: %s", hexformat(version)) + self._log("VERS: %s", hexformat(version)) elif apiNumber == DbgKdReadVirtualMemoryApi: vmem = substr(payload, 56) - logging.debug("VMEM:\n%s", hexasc(vmem)) + self._log("VMEM:\n%s", hexasc(vmem)) elif apiNumber == DbgKdReadPhysicalMemoryApi: pmem = substr(payload, 56) - logging.debug("PMEM:\n%s", hexasc(pmem)) + self._log("PMEM:\n%s", hexasc(pmem)) elif apiNumber == DbgKdReadControlSpaceApi: controlspace = substr(payload, 56) - logging.debug("CNTL: %s", hexformat(controlspace)) + self._log("CNTL: %s", hexformat(controlspace)) else: - logging.debug("UNKN: %s", hexasc(payload)) + self._log("UNKN: %s", hexasc(payload)) def _log_state_change64(self, payload): newState = unpack("I", substr(payload, 0, 4)) - logging.debug("State Change: New state: %08x", newState) + self._log("State Change: New state: %08x", newState) if newState == DbgKdExceptionStateChange: # DBGKM_EXCEPTION64 @@ -153,16 +197,16 @@ def _log_state_change64(self, payload): parameters = unpack("I", substr(ex, 24, 4)) if code in STATE_CHANGE_EXCEPTIONS: - logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) + self._log("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) else: - logging.warning("*** Exception %08x ", code) + self._log("*** Exception %08x ", code) - logging.warning("at %08x\n", address) + self._log("at %08x\n", address) - logging.warning("Exception flags = %08x", flags) - logging.warning("Exception record = %08x", record) - logging.warning("Exception address = %08x", address) - logging.warning("Number parameters = %08x", parameters) + self._log("Exception flags = %08x", flags) + self._log("Exception record = %08x", record) + self._log("Exception address = %08x", address) + self._log("Number parameters = %08x", parameters) self.running = False @@ -174,7 +218,7 @@ def _log_state_change64(self, payload): filename = payload[0x3B8:-1] filename = filename.decode("utf-8") - logging.debug("Load Symbols for '%s'", filename) + self._log("Load Symbols for '%s'", filename) class _DebuggerConnection: @@ -185,31 +229,54 @@ def __init__(self, host, port): self.port = port self.debugger_socket = socket.socket() self.debugger_socket.connect((host, port)) + self._running = True - self.debuggee_semaphore = threading.BoundedSemaphore() - self.debuggee = None + self._logger_semaphore = threading.BoundedSemaphore() + self._debuggee_semaphore = threading.BoundedSemaphore() + self._debuggee = None - self.sniffer = None + self.debugger_sniffer = None + self.start_time = time.perf_counter() def register_debuggee(self, handler) -> _KDPassthroughSniffer: - with self.debuggee_semaphore: - self.debuggee = handler - self.sniffer = _KDPassthroughSniffer( - "Debugger", self.debugger_socket, handler.connection + with self._debuggee_semaphore: + self._debuggee = handler + self.debugger_sniffer = _KDPassthroughSniffer( + "Debugger", + self.debugger_socket, + handler.connection, + self._logger_semaphore, + self.start_time, ) - return _KDPassthroughSniffer("Target", handler.connection, self.debugger_socket) + + return _KDPassthroughSniffer( + "Target", + handler.connection, + self.debugger_socket, + self._logger_semaphore, + self.start_time, + ) def unregister_debuggee(self, _handler): - with self.debuggee_semaphore: - self.debuggee = None + with self._debuggee_semaphore: + self._debuggee = None + self.debugger_sniffer = None def sendall(self, data, flags=0): return self.debugger_socket.sendall(data, flags) + def stop(self): + with self._debuggee_semaphore: + self._running = False + def _debugger_thread_main(self): while True: - with self.debuggee_semaphore: - sniffer = self.sniffer + with self._debuggee_semaphore: + if not self._running: + break + + sniffer = self.debugger_sniffer + if not sniffer: time.sleep(0.100) continue @@ -240,8 +307,6 @@ def handle(self) -> None: def main(args): """Main entrypoint""" - logging.basicConfig(level=logging.DEBUG) - print(f"Connecting to debugger at {args.debugger_ip}:{args.debugger_port}") debugger_connection = _DebuggerConnection(args.debugger_ip, args.debugger_port) @@ -254,8 +319,13 @@ def main(args): debugger_thread = threading.Thread( target=_DebuggerConnection._debugger_thread_main, args=(debugger_connection,) ) - debugger_thread.start() - server.serve_forever() + + try: + debugger_thread.start() + server.serve_forever() + except KeyboardInterrupt: + debugger_connection.stop() + raise if __name__ == "__main__": diff --git a/windpl.py b/windpl.py index 205ee45..6ea60af 100755 --- a/windpl.py +++ b/windpl.py @@ -267,7 +267,7 @@ def _handleStateManipulate(self, buf): logging.debug("Breakpoint %d cleared", handle) elif apiNumber == DbgKdGetVersionApi: version = substr(buf, 16) - logging.debug("VERS: %s", hexformat(version)) + logging.debug("VERS:\n%s", hexformat(version)) elif apiNumber == DbgKdReadVirtualMemoryApi: vmem = substr(buf, 56) logging.debug("VMEM:\n%s", hexasc(vmem)) @@ -276,9 +276,9 @@ def _handleStateManipulate(self, buf): logging.debug("PMEM:\n%s", hexasc(pmem)) elif apiNumber == DbgKdReadControlSpaceApi: controlspace = substr(buf, 56) - logging.debug("CNTL: %s", hexformat(controlspace)) + logging.debug("CNTL:\n%s", hexformat(controlspace)) else: - logging.debug("UNKN: %s", hexasc(buf)) + logging.debug("UNKN:\n%s", hexasc(buf)) def _sendAck(self): logging.debug("Acking with next PID: %08x", self.nextpid) From d6d9676f7a2fe3fcbf6f38fa6609a6f5671e5c63 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 08:13:29 -0700 Subject: [PATCH 12/30] Adds more symbolic constants. --- constants.py | 43 ++++++++++++++++++++++++++++++++++ sniffer.py | 65 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/constants.py b/constants.py index 1522dce..8c60e25 100644 --- a/constants.py +++ b/constants.py @@ -76,6 +76,11 @@ DbgKdExceptionStateChange = 0x00003030 DbgKdLoadSymbolsStateChange = 0x00003031 +STATE_CHANGE_TABLE = { + DbgKdExceptionStateChange: "DbgKdExceptionStateChange", + DbgKdLoadSymbolsStateChange: "DbgKdLoadSymbolsStateChange", +} + # PACKET_TYPE_KD_STATE_MANIPULATE api numbers # DBGKD_MANIPULATE_STATE64 DbgKdReadVirtualMemoryApi = 0x00003130 @@ -112,7 +117,45 @@ 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 diff --git a/sniffer.py b/sniffer.py index 038508f..3c8a302 100755 --- a/sniffer.py +++ b/sniffer.py @@ -77,7 +77,7 @@ def _read_until_packet_leader(self): now = self.elapsed_time_ms while packet_signature not in (PACKET_LEADER, CONTROL_PACKET_LEADER): - discarded_bytes += buf[0] + discarded_bytes.append(buf[0]) buf = buf[1:] + self._read_and_passthrough(1) packet_signature = unpack("I", buf) @@ -157,8 +157,18 @@ def _log_packet(self, packet_type, payload): def _log_state_manipulate(self, payload): apiNumber = unpack("I", substr(payload, 0, 4)) - self._log("State Manipulate: %08x", apiNumber) - self._log(hexformat(substr(payload, 0, 16))) + self._log( + "State Manipulate: %08x (%s)", + apiNumber, + STATE_MANIPULATE_TABLE.get(apiNumber, ""), + ) + + processor_level = unpack("H", substr(payload, 4, 2)) + processor = unpack("H", substr(payload, 6, 2)) + return_status = unpack("I", substr(payload, 8, 4)) + + # self._log(hexformat(substr(payload, 0, 16))) + self._log(hexformat(payload)) if apiNumber == DbgKdWriteBreakPointApi: bp = "%08x" % unpack("I", substr(payload, 16, 4)) @@ -170,6 +180,7 @@ def _log_state_manipulate(self, payload): elif apiNumber == DbgKdGetVersionApi: version = substr(payload, 16) self._log("VERS: %s", hexformat(version)) + self._log_version(payload[12:]) elif apiNumber == DbgKdReadVirtualMemoryApi: vmem = substr(payload, 56) self._log("VMEM:\n%s", hexasc(vmem)) @@ -182,11 +193,33 @@ def _log_state_manipulate(self, payload): else: self._log("UNKN: %s", hexasc(payload)) + def _log_version(self, payload): + # USHORT MajorVersion; + # USHORT MinorVersion; + # UCHAR ProtocolVersion; + # UCHAR KdSecondaryVersion; + # USHORT Flags; + # USHORT MachineType; + # UCHAR MaxPacketType; + # UCHAR MaxStateChange; + # UCHAR MaxManipulate; + # UCHAR Simulation; + # USHORT Unused[1]; + # ULONG64 KernBase; + # ULONG64 PsLoadedModuleList; + # ULONG64 DebuggerDataList; + pass + def _log_state_change64(self, payload): - newState = unpack("I", substr(payload, 0, 4)) - self._log("State Change: New state: %08x", newState) + new_state = unpack("I", substr(payload, 0, 4)) + self._log( + "State Change: New state: %08x (%s)", + new_state, + STATE_CHANGE_TABLE.get(new_state, ""), + ) + # self._log(hexformat(payload)) - if newState == DbgKdExceptionStateChange: + if new_state == DbgKdExceptionStateChange: # DBGKM_EXCEPTION64 ex = substr(payload, 32) @@ -213,7 +246,7 @@ def _log_state_change64(self, payload): # my @v = getVersionInfo() # version = v[0] # $kernelbase = v[2] - elif newState == DbgKdLoadSymbolsStateChange: + elif new_state == DbgKdLoadSymbolsStateChange: # DBGKD_LOAD_SYMBOLS64 filename = payload[0x3B8:-1] @@ -282,7 +315,11 @@ def _debugger_thread_main(self): continue # TODO: Make this handle unregistration gracefully. - sniffer.read_packet() + try: + sniffer.read_packet() + except OSError as ex: + print(f"Failed to read packet: {ex}") + break class _DebuggeeHandler(socketserver.StreamRequestHandler): @@ -297,11 +334,19 @@ def finish(self) -> None: super(_DebuggeeHandler, self).finish() self.debugger.unregister_debuggee(self) + print(f"Debuggee at {self.client_address[0]} disconnectected\n") + def handle(self) -> None: - print(f"Debuggee connected from {self.client_address[0]}") + print(f"Debuggee connected from {self.client_address[0]}\n") while True: - if not self.sniffer.read_packet(): + try: + self.sniffer.read_packet() + except OSError as ex: + print(f"Failed to read packet from Debuggee {ex}") + break + except ConnectionResetError as ex: + print(f"Debuggee disconnected: {ex}") break From eea1e77432f380f9dcdd53e6e2184dce6491deb1 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 08:49:04 -0700 Subject: [PATCH 13/30] Adds more verbose version logging. --- sniffer.py | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/sniffer.py b/sniffer.py index 3c8a302..d9a2c51 100755 --- a/sniffer.py +++ b/sniffer.py @@ -194,21 +194,36 @@ def _log_state_manipulate(self, payload): self._log("UNKN: %s", hexasc(payload)) def _log_version(self, payload): - # USHORT MajorVersion; - # USHORT MinorVersion; - # UCHAR ProtocolVersion; - # UCHAR KdSecondaryVersion; - # USHORT Flags; - # USHORT MachineType; - # UCHAR MaxPacketType; - # UCHAR MaxStateChange; - # UCHAR MaxManipulate; - # UCHAR Simulation; - # USHORT Unused[1]; - # ULONG64 KernBase; - # ULONG64 PsLoadedModuleList; - # ULONG64 DebuggerDataList; - pass + ( + major_version, + minor_version, + protocol_version, + kd_secondary_version, + flags, + machine_type, + max_packet_type, + max_state_change, + max_manipulate, + simulation, + _unused, + kern_base, + ps_loaded_module_list, + debugger_data_list, + ) = unpack("HHBBHHBBBBHQQQ", payload) + self._log("Version: %d.%d", major_version, minor_version) + self._log("Protocol version: %d", protocol_version) + self._log("KD secondary version: %d", kd_secondary_version) + self._log("Flags: 0x%04x", flags) + self._log("Machine type: %d (0x%04x)", machine_type, machine_type) + self._log("Max packet type: %d", max_packet_type) + self._log("Max state change: %d", max_state_change) + self._log("Max manipulate: %d", max_manipulate) + self._log("Simulation: 0x%02x", simulation) + self._log("Kernel base: %x", kern_base) + self._log("PS loaded module list: %x", ps_loaded_module_list) + self._log("Debugger data list: %x", debugger_data_list) + + print(f"{len(payload)} should == 40") def _log_state_change64(self, payload): new_state = unpack("I", substr(payload, 0, 4)) From 455c725262f28134371ef0a6ddfdbd1c70f59725 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 09:05:43 -0700 Subject: [PATCH 14/30] Renames unpack to make it distinct from struct.unpack. --- sniffer.py | 53 +++++++++++++------------ util.py | 3 +- windpl.py | 112 ++++++++++++++++++++++++++--------------------------- 3 files changed, 86 insertions(+), 82 deletions(-) diff --git a/sniffer.py b/sniffer.py index d9a2c51..398406f 100755 --- a/sniffer.py +++ b/sniffer.py @@ -71,7 +71,7 @@ def _flush_log(self): def _read_until_packet_leader(self): buf = self._read_and_passthrough(4) - packet_signature = unpack("I", buf) + packet_signature = unpack_one("I", buf) discarded_bytes = [] now = self.elapsed_time_ms @@ -79,7 +79,7 @@ def _read_until_packet_leader(self): while packet_signature not in (PACKET_LEADER, CONTROL_PACKET_LEADER): discarded_bytes.append(buf[0]) buf = buf[1:] + self._read_and_passthrough(1) - packet_signature = unpack("I", buf) + packet_signature = unpack_one("I", buf) if discarded_bytes: self._log( @@ -105,20 +105,20 @@ def read_packet(self): ) buf = self._read_and_passthrough(2) - packet_type = unpack("H", buf) + packet_type = unpack_one("H", buf) packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") self._log("> Packet type: %d (%s)", packet_type, packet_type_name) if packet_type_name == "": self._log("!! Unexpected packet type %04x", packet_type) buf = self._read_and_passthrough(2) - data_size = unpack("H", buf) + data_size = unpack_one("H", buf) buf = self._read_and_passthrough(4) - packet_id = unpack("I", buf) + packet_id = unpack_one("I", buf) buf = self._read_and_passthrough(4) - expected_checksum = unpack("I", buf) + expected_checksum = unpack_one("I", buf) self._log("> Packet ID: %08x", packet_id) self._log("> Data size: %d", data_size) @@ -156,44 +156,49 @@ def _log_packet(self, packet_type, payload): self._log("%s", hexformat(payload)) def _log_state_manipulate(self, payload): - apiNumber = unpack("I", substr(payload, 0, 4)) + apiNumber = unpack_one("I", substr(payload, 0, 4)) self._log( "State Manipulate: %08x (%s)", apiNumber, STATE_MANIPULATE_TABLE.get(apiNumber, ""), ) - processor_level = unpack("H", substr(payload, 4, 2)) - processor = unpack("H", substr(payload, 6, 2)) - return_status = unpack("I", substr(payload, 8, 4)) + processor_level = unpack_one("H", substr(payload, 4, 2)) + processor = unpack_one("H", substr(payload, 6, 2)) + return_status = unpack_one("I", substr(payload, 8, 4)) # self._log(hexformat(substr(payload, 0, 16))) self._log(hexformat(payload)) if apiNumber == DbgKdWriteBreakPointApi: - bp = "%08x" % unpack("I", substr(payload, 16, 4)) - handle = unpack("I", substr(payload, 20, 4)) + bp = "%08x" % unpack_one("I", substr(payload, 16, 4)) + handle = unpack_one("I", substr(payload, 20, 4)) self._log("Breakpoint %d set at %s", handle, bp) + elif apiNumber == DbgKdRestoreBreakPointApi: - handle = unpack("I", substr(payload, 16, 4)) + handle = unpack_one("I", substr(payload, 16, 4)) self._log("Breakpoint %d cleared", handle) + elif apiNumber == DbgKdGetVersionApi: - version = substr(payload, 16) - self._log("VERS: %s", hexformat(version)) - self._log_version(payload[12:]) + self._log_version(payload[16:]) + elif apiNumber == DbgKdReadVirtualMemoryApi: vmem = substr(payload, 56) self._log("VMEM:\n%s", hexasc(vmem)) + elif apiNumber == DbgKdReadPhysicalMemoryApi: pmem = substr(payload, 56) self._log("PMEM:\n%s", hexasc(pmem)) + elif apiNumber == DbgKdReadControlSpaceApi: controlspace = substr(payload, 56) self._log("CNTL: %s", hexformat(controlspace)) + else: self._log("UNKN: %s", hexasc(payload)) def _log_version(self, payload): + assert(len(payload) == 40) ( major_version, minor_version, @@ -209,7 +214,7 @@ def _log_version(self, payload): kern_base, ps_loaded_module_list, debugger_data_list, - ) = unpack("HHBBHHBBBBHQQQ", payload) + ) = struct.unpack("HHBBHHBBBBHQQQ", payload) self._log("Version: %d.%d", major_version, minor_version) self._log("Protocol version: %d", protocol_version) self._log("KD secondary version: %d", kd_secondary_version) @@ -223,10 +228,8 @@ def _log_version(self, payload): self._log("PS loaded module list: %x", ps_loaded_module_list) self._log("Debugger data list: %x", debugger_data_list) - print(f"{len(payload)} should == 40") - def _log_state_change64(self, payload): - new_state = unpack("I", substr(payload, 0, 4)) + new_state = unpack_one("I", substr(payload, 0, 4)) self._log( "State Change: New state: %08x (%s)", new_state, @@ -238,11 +241,11 @@ def _log_state_change64(self, payload): # DBGKM_EXCEPTION64 ex = substr(payload, 32) - code = unpack("I", substr(ex, 0, 4)) - flags = unpack("I", substr(ex, 4, 4)) - record = unpack("I", substr(ex, 8, 4)) - address = unpack("I", substr(ex, 16, 4)) - parameters = unpack("I", substr(ex, 24, 4)) + code = unpack_one("I", substr(ex, 0, 4)) + flags = unpack_one("I", substr(ex, 4, 4)) + record = unpack_one("I", substr(ex, 8, 4)) + address = unpack_one("I", substr(ex, 16, 4)) + parameters = unpack_one("I", substr(ex, 24, 4)) if code in STATE_CHANGE_EXCEPTIONS: self._log("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) diff --git a/util.py b/util.py index 1104949..df9b80b 100644 --- a/util.py +++ b/util.py @@ -56,7 +56,8 @@ def patch_substr(buf, start, length, fmt, data=None): return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] -def unpack(fmt, data): +def unpack_one(fmt, data): + assert(len(fmt) == 1) return struct.unpack(fmt, data)[0] # pylint: disable = no-member diff --git a/windpl.py b/windpl.py index 6ea60af..1521e47 100755 --- a/windpl.py +++ b/windpl.py @@ -149,7 +149,7 @@ def _getPacket(self): packet_type = None payload = bytearray([]) buf = self.client._read_and_passthrough(4) - packet_signature = unpack("I", buf) + packet_signature = unpack_one("I", buf) if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): logging.debug( "[%d] Got packet leader: %08x (%s)", @@ -159,21 +159,21 @@ def _getPacket(self): ) buf = self.client._read_and_passthrough(2) - packet_type = unpack("H", buf) + packet_type = unpack_one("H", buf) packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) if packet_type_name == "": logging.critical("!! Unexpected packet type %04x", packet_type) buf = self.client._read_and_passthrough(2) - data_size = unpack("H", buf) + data_size = unpack_one("H", buf) buf = self.client._read_and_passthrough(4) - packet_id = unpack("I", buf) + packet_id = unpack_one("I", buf) self.nextpid = packet_id buf = self.client._read_and_passthrough(4) - expected_checksum = unpack("I", buf) + expected_checksum = unpack_one("I", buf) logging.debug("> Packet ID: %08x", packet_id) logging.debug("> Data size: %d", data_size) @@ -204,25 +204,25 @@ def _getPacket(self): return packet_type, payload def _handleDebugIO(self, buf): # pylint: disable = no-self-use - apiNumber = unpack("I", substr(buf, 0, 4)) + apiNumber = unpack_one("I", substr(buf, 0, 4)) if apiNumber == DbgKdPrintStringApi: print("DbgPrint: " + substr(buf, 0x10).decode("utf-8")) else: logging.debug("Ignoring debug IO packet with API number %d", apiNumber) def _handleStateChange(self, buf): - newState = unpack("I", substr(buf, 0, 4)) + newState = unpack_one("I", substr(buf, 0, 4)) logging.debug("State Change: New state: %08x", newState) if newState == DbgKdExceptionStateChange: # DBGKM_EXCEPTION64 ex = substr(buf, 32) - code = unpack("I", substr(ex, 0, 4)) - flags = unpack("I", substr(ex, 4, 4)) - record = unpack("I", substr(ex, 8, 4)) - address = unpack("I", substr(ex, 16, 4)) - parameters = unpack("I", substr(ex, 24, 4)) + code = unpack_one("I", substr(ex, 0, 4)) + flags = unpack_one("I", substr(ex, 4, 4)) + record = unpack_one("I", substr(ex, 8, 4)) + address = unpack_one("I", substr(ex, 16, 4)) + parameters = unpack_one("I", substr(ex, 24, 4)) if code in STATE_CHANGE_EXCEPTIONS: logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) @@ -254,16 +254,16 @@ def _handleStateChange(self, buf): def _handleStateManipulate(self, buf): - apiNumber = unpack("I", substr(buf, 0, 4)) + apiNumber = unpack_one("I", substr(buf, 0, 4)) logging.debug("State Manipulate: %08x", apiNumber) if apiNumber == DbgKdWriteBreakPointApi: - bp = "%08x" % unpack("I", substr(buf, 16, 4)) - handle = unpack("I", substr(buf, 20, 4)) + bp = "%08x" % unpack_one("I", substr(buf, 16, 4)) + handle = unpack_one("I", substr(buf, 20, 4)) logging.debug("Breakpoint %d set at %s", handle, bp) self.breakpoints[bp] = handle elif apiNumber == DbgKdRestoreBreakPointApi: - handle = unpack("I", substr(buf, 16, 4)) + handle = unpack_one("I", substr(buf, 16, 4)) logging.debug("Breakpoint %d cleared", handle) elif apiNumber == DbgKdGetVersionApi: version = substr(buf, 16) @@ -313,38 +313,38 @@ def _getContext(self): ctx = buf[56:] # print("CTXT: ", hexformat($context) - context["ContextFlags"] = unpack("I", ctx[0:4]) - context["DR0"] = unpack("I", ctx[4:8]) - context["DR1"] = unpack("I", ctx[8 : 8 + 4]) - context["DR2"] = unpack("I", ctx[12 : 12 + 4]) - context["DR3"] = unpack("I", ctx[16 : 16 + 4]) - context["DR6"] = unpack("I", ctx[20 : 20 + 4]) - context["DR7"] = unpack("I", ctx[24 : 24 + 4]) - context["fp.ControlWord"] = unpack("I", ctx[28 : 28 + 4]) - context["fp.StatusWord"] = unpack("I", ctx[32 : 32 + 4]) - context["fp.TagWord"] = unpack("I", ctx[36 : 36 + 4]) - context["fp.ErrorOffset"] = unpack("I", ctx[40 : 40 + 4]) - context["fp.ErrorSelector"] = unpack("I", ctx[44 : 44 + 4]) - context["fp.DataOffset"] = unpack("I", ctx[48 : 48 + 4]) - context["fp.DataSelector"] = unpack("I", ctx[52 : 52 + 4]) + context["ContextFlags"] = unpack_one("I", ctx[0:4]) + context["DR0"] = unpack_one("I", ctx[4:8]) + context["DR1"] = unpack_one("I", ctx[8: 8 + 4]) + context["DR2"] = unpack_one("I", ctx[12: 12 + 4]) + context["DR3"] = unpack_one("I", ctx[16: 16 + 4]) + context["DR6"] = unpack_one("I", ctx[20: 20 + 4]) + context["DR7"] = unpack_one("I", ctx[24: 24 + 4]) + context["fp.ControlWord"] = unpack_one("I", ctx[28: 28 + 4]) + context["fp.StatusWord"] = unpack_one("I", ctx[32: 32 + 4]) + context["fp.TagWord"] = unpack_one("I", ctx[36: 36 + 4]) + context["fp.ErrorOffset"] = unpack_one("I", ctx[40: 40 + 4]) + context["fp.ErrorSelector"] = unpack_one("I", ctx[44: 44 + 4]) + context["fp.DataOffset"] = unpack_one("I", ctx[48: 48 + 4]) + context["fp.DataSelector"] = unpack_one("I", ctx[52: 52 + 4]) context["fp.RegisterArea"] = ctx[56 : 56 + 80] - context["fp.Cr0NpxState"] = unpack("I", ctx[136 : 136 + 4]) - context["GS"] = unpack("I", ctx[140 : 140 + 4]) - context["FS"] = unpack("I", ctx[144 : 144 + 4]) - context["ES"] = unpack("I", ctx[148 : 148 + 4]) - context["DS"] = unpack("I", ctx[152 : 152 + 4]) - context["EDI"] = unpack("I", ctx[156 : 156 + 4]) - context["ESI"] = unpack("I", ctx[160 : 160 + 4]) - context["EBX"] = unpack("I", ctx[164 : 164 + 4]) - context["EDX"] = unpack("I", ctx[168 : 168 + 4]) - context["ECX"] = unpack("I", ctx[172 : 172 + 4]) - context["EAX"] = unpack("I", ctx[176 : 176 + 4]) - context["EBP"] = unpack("I", ctx[180 : 180 + 4]) - context["EIP"] = unpack("I", ctx[184:188]) - context["CS"] = unpack("I", ctx[188:192]) - context["Eflags"] = unpack("I", ctx[192:196]) - context["ESP"] = unpack("I", ctx[196:200]) - context["SS"] = unpack("I", ctx[200:204]) + context["fp.Cr0NpxState"] = unpack_one("I", ctx[136: 136 + 4]) + context["GS"] = unpack_one("I", ctx[140: 140 + 4]) + context["FS"] = unpack_one("I", ctx[144: 144 + 4]) + context["ES"] = unpack_one("I", ctx[148: 148 + 4]) + context["DS"] = unpack_one("I", ctx[152: 152 + 4]) + context["EDI"] = unpack_one("I", ctx[156: 156 + 4]) + context["ESI"] = unpack_one("I", ctx[160: 160 + 4]) + context["EBX"] = unpack_one("I", ctx[164: 164 + 4]) + context["EDX"] = unpack_one("I", ctx[168: 168 + 4]) + context["ECX"] = unpack_one("I", ctx[172: 172 + 4]) + context["EAX"] = unpack_one("I", ctx[176: 176 + 4]) + context["EBP"] = unpack_one("I", ctx[180: 180 + 4]) + context["EIP"] = unpack_one("I", ctx[184:188]) + context["CS"] = unpack_one("I", ctx[188:192]) + context["Eflags"] = unpack_one("I", ctx[192:196]) + context["ESP"] = unpack_one("I", ctx[196:200]) + context["SS"] = unpack_one("I", ctx[200:204]) context["leftovers"] = ctx[204:] return context @@ -392,12 +392,12 @@ def _getVersionInfo(self): buf = self._waitStateManipulate(DbgKdGetVersionApi) if len(buf) > 32: v = substr(buf, 16) - osv = "%d.%d" % (unpack("H", substr(v, 4, 2)), unpack("H", substr(v, 6, 2))) - pv = unpack("H", substr(v, 8, 2)) - machinetype = unpack("H", substr(v, 12, 2)) - kernbase = unpack("I", substr(v, 16, 4)) - modlist = unpack("I", substr(v, 24, 4)) - ddata = unpack("I", substr(v, 32, 4)) + osv = "%d.%d" % (unpack_one("H", substr(v, 4, 2)), unpack_one("H", substr(v, 6, 2))) + pv = unpack_one("H", substr(v, 8, 2)) + machinetype = unpack_one("H", substr(v, 12, 2)) + kernbase = unpack_one("I", substr(v, 16, 4)) + modlist = unpack_one("I", substr(v, 24, 4)) + ddata = unpack_one("I", substr(v, 32, 4)) if pv < 5: logging.critical("Debug protocol version %d not supported", pv) sys.exit() @@ -612,7 +612,7 @@ def _readDword(self, addr): # print("Reading dword at %08x\n", addr buf = self._readVirtualMemory(addr, 4) if len(buf) == 4: - return unpack("I", buf) + return unpack_one("I", buf) return "failed" def _readPhysicalMemory(self, addr, length): @@ -759,7 +759,7 @@ def _waitStateManipulate(self, wanted): while 1: ptype, buf = self._handlePacket() if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: - api = unpack("I", substr(buf, 0, 4)) + api = unpack_one("I", substr(buf, 0, 4)) if api == wanted: break # except timeout: @@ -772,7 +772,7 @@ def _getPspCidTable(self): self.pcontext = self.kernelcontext # this procedure is kernel context only self._sendDbgKdGetVersion() buf = self._waitStateManipulate(DbgKdGetVersionApi) - pddata = unpack("I", substr(buf, 48, 4)) + pddata = unpack_one("I", substr(buf, 48, 4)) if pddata: # print("Pointer to debugger data struct is at %08x\n", pddata ddata = self._readDword(pddata) From 9711eae4e3d8c2b86902e5a151ac5f663793060b Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 09:33:16 -0700 Subject: [PATCH 15/30] Adds TCP support. --- debug_connection.py | 92 +++++++++++++++++++++++++++------------------ windpl.py | 40 ++++++++++++++++---- 2 files changed, 89 insertions(+), 43 deletions(-) diff --git a/debug_connection.py b/debug_connection.py index 06717ba..995732f 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -1,6 +1,8 @@ """Abstract connection interface for kernel debugging.""" + import os import pathlib +import socket class DebugConnection: @@ -11,53 +13,59 @@ def __init__(self, endpoint): self._connection_write = None self._connection_read = None + self._recv = None + self._send = None + def connect(self): - # FIXME: Add support for TCP sockets too - # self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - # self.client.connect(endpoint) - path = pathlib.Path(self.endpoint) - if not path.exists(): + if type(self.endpoint) is tuple: + self._connect_socket(self.endpoint) + else: + self._connect_fifo(self.endpoint) + + def _connect_socket(self, host_port): + self._connection_read = socket.socket() + self._connection_write = self._connection_read + self._connection_read.connect(host_port) + self._recv = self._recv_socket + self._send = self._send_socket - def require_fifo_exists(fifo_path): - path = pathlib.Path(fifo_path) - if not path.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'." - ) + def _connect_fifo(self, path): + """Connects via a pair of .in and .out named pipes.""" - if not path.is_fifo(): - raise Exception(f"'{path.name}' is not a fifo.") + 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'." + ) - # The naming is from the perspective of qemu, so "in" is written to and "out" is read from. - write_fifo = f"{self.endpoint}.in" - read_fifo = f"{self.endpoint}.out" + if not path_object.is_fifo(): + raise Exception(f"'{path_object.name}' is not a fifo.") - require_fifo_exists(write_fifo) - require_fifo_exists(read_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" - flags = os.O_RDWR - if os.name == "nt": - flags |= os.O_BINARY + require_fifo_exists(write_fifo) + require_fifo_exists(read_fifo) - self._connection_write = os.open(write_fifo, flags) - self._connection_read = os.open(read_fifo, flags) + flags = os.O_RDWR + if os.name == "nt": + flags |= os.O_BINARY - return + self._connection_write = os.open(write_fifo, flags) + self._connection_read = os.open(read_fifo, flags) + + self._recv = self._recv_fifo + self._send = self._send_fifo - if path.is_fifo(): - 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'." - ) - else: - raise Exception(f"Unsupported connection type {self.endpoint}") def disconnect(self): pass - def recv(self, max_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: @@ -65,9 +73,21 @@ def recv(self, max_bytes): return bytes_read - def send(self, buf): + 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(buf) + def read(self, wanted): """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" total = 0 @@ -83,7 +103,7 @@ def read(self, wanted): # die("eof") # # print("client.read count %x\n", ord(buf) # # FH.blocking(0) - buf = self.recv(wanted - total) + buf = self._recv(wanted - total) count = len(buf) if count: @@ -96,5 +116,5 @@ def write(self, buffer): """Writes `buffer` to the connection, blocking as necessary.""" while len(buffer): - written = self.send(buffer) + written = self._send(buffer) buffer = buffer[written:] diff --git a/windpl.py b/windpl.py index 1521e47..4cf1b13 100755 --- a/windpl.py +++ b/windpl.py @@ -806,15 +806,22 @@ def main(args): log_level = logging.DEBUG if args.verbose else logging.INFO logging.basicConfig(level=log_level) - if args.create_fifo: - _create_fifos(args.named_pipe) + endpoint = None + if args.named_pipe: + if args.create_fifo: + _create_fifos(args.named_pipe) + endpoint = args.named_pipe + elif args.port: + endpoint = (args.host, args.port) + else: + raise RuntimeError(f"No supported transport method selected.") context = DebugContext() - context.connect(args.named_pipe) + context.connect(endpoint) context.run() - return 1 + return 0 if __name__ == "__main__": @@ -823,8 +830,22 @@ def _parse_args(): parser = argparse.ArgumentParser() parser.add_argument( - "named_pipe", - help="The path to the named pipe used by xemu", + "-p", + "--port", + type=int, + help="The TCP server port used by qemu." + ) + + parser.add_argument( + "--host", + help="The IP address of the host.", + default="localhost" + ) + + parser.add_argument( + "-np", + "--named_pipe", + help="The path to the named pipe used by qemu.", ) parser.add_argument( @@ -841,6 +862,11 @@ def _parse_args(): action="store_true", ) - return parser.parse_args() + args = parser.parse_args() + + if not args.port and not args.named_pipe: + parser.error("At least one of '--port' or '--named-pipe' must be given.") + + return args sys.exit(main(_parse_args())) From ece6cc635b8926c6389b7d8a4facfc8a1285682a Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 10:13:20 -0700 Subject: [PATCH 16/30] Adds server mode. --- debug_connection.py | 19 +- debug_context.py | 789 ++++++++++++++++++++++++++++++++++++++++++++ server.py | 38 +++ sniffer.py | 3 +- util.py | 2 +- windpl.py | 760 +----------------------------------------- 6 files changed, 862 insertions(+), 749 deletions(-) create mode 100644 debug_context.py create mode 100644 server.py diff --git a/debug_connection.py b/debug_connection.py index 995732f..a3489f5 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -1,5 +1,6 @@ """Abstract connection interface for kernel debugging.""" +import logging import os import pathlib import socket @@ -23,16 +24,23 @@ def connect(self): else: self._connect_fifo(self.endpoint) - def _connect_socket(self, host_port): - self._connection_read = socket.socket() + def handle_socket(self, connection): + """Utilizes the given socket as the transport layer.""" + self._connection_read = connection self._connection_write = self._connection_read - self._connection_read.connect(host_port) 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(): @@ -61,7 +69,6 @@ def require_fifo_exists(fifo_path): self._recv = self._recv_fifo self._send = self._send_fifo - def disconnect(self): pass @@ -86,7 +93,7 @@ def _recv_socket(self, max_bytes): def _send_socket(self, buf): """Sends some or all of the given buf, returns the number of bytes actually sent.""" - return self._connection_write(buf) + return self._connection_write.send(buf) def read(self, wanted): """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" @@ -109,6 +116,8 @@ def read(self, wanted): if count: total += count outbuf += buf + else: + raise ConnectionResetError("Failed to read from connection.") return outbuf diff --git a/debug_context.py b/debug_context.py new file mode 100644 index 0000000..0a7d215 --- /dev/null +++ b/debug_context.py @@ -0,0 +1,789 @@ +"""Models a Kernel Debug session.""" + +# 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. + +# pylint: disable = invalid-name, missing-function-docstring, too-many-instance-attributes, fixme + +import logging +from struct import pack # pylint: disable = no-name-in-module +import sys +import time + +from constants import * +from debug_connection import DebugConnection +from util import * +from windpl_extra import logical2physical + + +class DebugContext: + """Models a KernelDebug session.""" + + def __init__(self) -> None: + self.start_time = time.perf_counter() + self.timeout = 10 # max time to wait on packet + self.running = True + + self.kernelcontext = {} + self.kernelcontext["peb"] = 0 + self.kernelcontext["pid"] = 0 + self.kernelcontext["eprocess"] = 0 + self.kernelcontext["dtb"] = 0 + + self.processcontext = {} + self.processcontext["peb"] = 0 + self.processcontext["pid"] = 0 + self.processcontext["eprocess"] = 0 + self.processcontext["dtb"] = 0 + self.pcontext = self.kernelcontext + self.nextpid = 1 + self.breakpoints = {} + + self.curbp = 1 + self.controlspace = 0 + self.controlspacesent = False + + self.client = None + + def set_connection(self, connection: DebugConnection) -> None: + self.client = connection + + def run(self): + logging.info("Waiting for target device...") + self._sendReset() + packets = 0 + while True: + try: + self._handlePacket() + except ConnectionResetError: + logging.info("Client connection closed.") + break + + logging.debug("Processed %d packets\n", packets) + packets += 1 + + # FIXME: windpl_loop.py + + @property + def elapsed_time_ms(self): + return time.perf_counter() - self.start_time + + def _handlePacket(self): + ptype, buf = self._getPacket() + + if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: + self._handleStateManipulate(buf) + elif ptype == PACKET_TYPE_KD_DEBUG_IO: + self._handleDebugIO(buf) + elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: + self._handleStateChange(buf) + elif ptype == PACKET_TYPE_KD_RESET: + # TODO: check to see if this reset is in response to a reset sent by the debugger script. + # If not, dump the read buffer and start over. + pass + elif ptype == PACKET_TYPE_KD_RESEND: + logging.warning("!! Ignoring resend request") + elif len(buf): + logging.debug("Ignoring packet") + + return ptype, buf + + def _read_until_packet_leader(self): + buf = self.client.read(4) + packet_signature = unpack_one("I", buf) + + discarded_bytes = [] + now = self.elapsed_time_ms + + while packet_signature not in (PACKET_LEADER, CONTROL_PACKET_LEADER): + discarded_bytes.append(buf[0]) + buf = buf[1:] + self.client.read(1) + packet_signature = unpack_one("I", buf) + + if discarded_bytes: + logging.debug( + "[%d] Discarded non-KD packet bytes %s", + now, + hexformat(discarded_bytes), + ) + + return packet_signature + + def _getPacket(self): + packet_type = None + payload = bytearray([]) + packet_signature = self._read_until_packet_leader() + + logging.debug( + "[%d] Got packet leader: %08x (%s)", + int(self.elapsed_time_ms * 1000), + packet_signature, + "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", + ) + + buf = self.client.read(2) + packet_type = unpack_one("H", buf) + packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") + logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) + if packet_type_name == "": + logging.critical("!! Unexpected packet type %04x", packet_type) + + buf = self.client.read(2) + data_size = unpack_one("H", buf) + + buf = self.client.read(4) + packet_id = unpack_one("I", buf) + self.nextpid = packet_id + + buf = self.client.read(4) + expected_checksum = unpack_one("I", buf) + + logging.debug("> Packet ID: %08x", packet_id) + logging.debug("> Data size: %d", data_size) + logging.debug("> Checksum: %08x", expected_checksum) + + if data_size: + payload = self.client.read(data_size) + + payload_checksum = generate_checksum(payload) + if payload_checksum != expected_checksum: + raise Exception( + f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" + ) + + # send ack if it's a non-control packet + if packet_signature == PACKET_LEADER: + # packet trailer + # logging.debug("Reading trailer...") + trail = self.client.read(1) + # logging.debug("Trailer: %x", trail[0]) + if trail[0] == PACKET_TRAILER: + # logging.debug("sending Ack") + self._sendAck() + else: + raise Exception("Invalid packet trailer 0x%x" % trail[0]) + + return packet_type, payload + + def _handleDebugIO(self, buf): # pylint: disable = no-self-use + apiNumber = unpack_one("I", substr(buf, 0, 4)) + if apiNumber == DbgKdPrintStringApi: + print("DbgPrint: " + substr(buf, 0x10).decode("utf-8")) + else: + logging.debug("Ignoring debug IO packet with API number %d", apiNumber) + + def _handleStateChange(self, buf): + newState = unpack_one("I", substr(buf, 0, 4)) + logging.debug("State Change: New state: %08x", newState) + + if newState == DbgKdExceptionStateChange: + # DBGKM_EXCEPTION64 + ex = substr(buf, 32) + + code = unpack_one("I", substr(ex, 0, 4)) + flags = unpack_one("I", substr(ex, 4, 4)) + record = unpack_one("I", substr(ex, 8, 4)) + address = unpack_one("I", substr(ex, 16, 4)) + parameters = unpack_one("I", substr(ex, 24, 4)) + + if code in STATE_CHANGE_EXCEPTIONS: + logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) + else: + logging.warning("*** Exception %08x ", code) + + logging.warning("at %08x\n", address) + + logging.warning("Exception flags = %08x", flags) + logging.warning("Exception record = %08x", record) + logging.warning("Exception address = %08x", address) + logging.warning("Number parameters = %08x", parameters) + + self.running = False + + # my @v = getVersionInfo() + # version = v[0] + # $kernelbase = v[2] + elif newState == DbgKdLoadSymbolsStateChange: + # DBGKD_LOAD_SYMBOLS64 + + filename = buf[0x3B8:-1] + filename = filename.decode("utf-8") + logging.debug("Load Symbols for '%s'", filename) + + # nothing to do... + + self._sendDbgKdContinue2() + + def _handleStateManipulate(self, buf): + + apiNumber = unpack_one("I", substr(buf, 0, 4)) + logging.debug("State Manipulate: %08x", apiNumber) + + if apiNumber == DbgKdWriteBreakPointApi: + bp = "%08x" % unpack_one("I", substr(buf, 16, 4)) + handle = unpack_one("I", substr(buf, 20, 4)) + logging.debug("Breakpoint %d set at %s", handle, bp) + self.breakpoints[bp] = handle + elif apiNumber == DbgKdRestoreBreakPointApi: + handle = unpack_one("I", substr(buf, 16, 4)) + logging.debug("Breakpoint %d cleared", handle) + elif apiNumber == DbgKdGetVersionApi: + version = substr(buf, 16) + logging.debug("VERS:\n%s", hexformat(version)) + elif apiNumber == DbgKdReadVirtualMemoryApi: + vmem = substr(buf, 56) + logging.debug("VMEM:\n%s", hexasc(vmem)) + elif apiNumber == DbgKdReadPhysicalMemoryApi: + pmem = substr(buf, 56) + logging.debug("PMEM:\n%s", hexasc(pmem)) + elif apiNumber == DbgKdReadControlSpaceApi: + controlspace = substr(buf, 56) + logging.debug("CNTL:\n%s", hexformat(controlspace)) + else: + logging.debug("UNKN:\n%s", hexasc(buf)) + + def _sendAck(self): + logging.debug("Acking with next PID: %08x", self.nextpid) + ack_packet = pack( + "IHHII", + CONTROL_PACKET_LEADER, + PACKET_TYPE_KD_ACKNOWLEDGE, + 0, + self.nextpid, + 0, + ) + # logging.debug("Ack: %s", hexformat(ack_packet)) + self.client.write(ack_packet) + + def _sendReset(self): + reset_packet = pack( + "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0 + ) + + logging.debug("Sending reset packet\n\n") + # print(hexformat(rst) + self.client.write(reset_packet) + + def _getContext(self): + context = {} + self._sendDbgKdGetContext() + buf = self._waitStateManipulate(DbgKdGetContextApi) + + if len(buf) <= 204: + return None + + ctx = buf[56:] + + # print("CTXT: ", hexformat($context) + context["ContextFlags"] = unpack_one("I", ctx[0:4]) + context["DR0"] = unpack_one("I", ctx[4:8]) + context["DR1"] = unpack_one("I", ctx[8 : 8 + 4]) + context["DR2"] = unpack_one("I", ctx[12 : 12 + 4]) + context["DR3"] = unpack_one("I", ctx[16 : 16 + 4]) + context["DR6"] = unpack_one("I", ctx[20 : 20 + 4]) + context["DR7"] = unpack_one("I", ctx[24 : 24 + 4]) + context["fp.ControlWord"] = unpack_one("I", ctx[28 : 28 + 4]) + context["fp.StatusWord"] = unpack_one("I", ctx[32 : 32 + 4]) + context["fp.TagWord"] = unpack_one("I", ctx[36 : 36 + 4]) + context["fp.ErrorOffset"] = unpack_one("I", ctx[40 : 40 + 4]) + context["fp.ErrorSelector"] = unpack_one("I", ctx[44 : 44 + 4]) + context["fp.DataOffset"] = unpack_one("I", ctx[48 : 48 + 4]) + context["fp.DataSelector"] = unpack_one("I", ctx[52 : 52 + 4]) + context["fp.RegisterArea"] = ctx[56 : 56 + 80] + context["fp.Cr0NpxState"] = unpack_one("I", ctx[136 : 136 + 4]) + context["GS"] = unpack_one("I", ctx[140 : 140 + 4]) + context["FS"] = unpack_one("I", ctx[144 : 144 + 4]) + context["ES"] = unpack_one("I", ctx[148 : 148 + 4]) + context["DS"] = unpack_one("I", ctx[152 : 152 + 4]) + context["EDI"] = unpack_one("I", ctx[156 : 156 + 4]) + context["ESI"] = unpack_one("I", ctx[160 : 160 + 4]) + context["EBX"] = unpack_one("I", ctx[164 : 164 + 4]) + context["EDX"] = unpack_one("I", ctx[168 : 168 + 4]) + context["ECX"] = unpack_one("I", ctx[172 : 172 + 4]) + context["EAX"] = unpack_one("I", ctx[176 : 176 + 4]) + context["EBP"] = unpack_one("I", ctx[180 : 180 + 4]) + context["EIP"] = unpack_one("I", ctx[184:188]) + context["CS"] = unpack_one("I", ctx[188:192]) + context["Eflags"] = unpack_one("I", ctx[192:196]) + context["ESP"] = unpack_one("I", ctx[196:200]) + context["SS"] = unpack_one("I", ctx[200:204]) + context["leftovers"] = ctx[204:] + return context + + def _setContext(self, context): + ctx = pack("I", context["ContextFlags"]) + ctx += pack("I", context["DR0"]) + ctx += pack("I", context["DR1"]) + ctx += pack("I", context["DR2"]) + ctx += pack("I", context["DR3"]) + ctx += pack("I", context["DR6"]) + ctx += pack("I", context["DR7"]) + ctx += pack("I", context["fp.ControlWord"]) + ctx += pack("I", context["fp.StatusWord"]) + ctx += pack("I", context["fp.TagWord"]) + ctx += pack("I", context["fp.ErrorOffset"]) + ctx += pack("I", context["fp.ErrorSelector"]) + ctx += pack("I", context["fp.DataOffset"]) + ctx += pack("I", context["fp.DataSelector"]) + ctx += context["fp.RegisterArea"] + ctx += pack("I", context["fp.Cr0NpxState"]) + ctx += pack("I", context["GS"]) + ctx += pack("I", context["FS"]) + ctx += pack("I", context["ES"]) + ctx += pack("I", context["DS"]) + ctx += pack("I", context["EDI"]) + ctx += pack("I", context["ESI"]) + ctx += pack("I", context["EBX"]) + ctx += pack("I", context["EDX"]) + ctx += pack("I", context["ECX"]) + ctx += pack("I", context["EAX"]) + ctx += pack("I", context["EBP"]) + ctx += pack("I", context["EIP"]) + ctx += pack("I", context["CS"]) + ctx += pack("I", context["Eflags"]) + ctx += pack("I", context["ESP"]) + ctx += pack("I", context["SS"]) + ctx += context["leftovers"] + self._sendDbgKdSetContext(ctx) + self._waitStateManipulate(DbgKdSetContextApi) + + def _getVersionInfo(self): + logging.info("getVersionInfo") + # os version, protocol version, kernel base, module list, debugger data + self._sendDbgKdGetVersion() + buf = self._waitStateManipulate(DbgKdGetVersionApi) + if len(buf) > 32: + v = substr(buf, 16) + osv = "%d.%d" % ( + unpack_one("H", substr(v, 4, 2)), + unpack_one("H", substr(v, 6, 2)), + ) + pv = unpack_one("H", substr(v, 8, 2)) + machinetype = unpack_one("H", substr(v, 12, 2)) + kernbase = unpack_one("I", substr(v, 16, 4)) + modlist = unpack_one("I", substr(v, 24, 4)) + ddata = unpack_one("I", substr(v, 32, 4)) + if pv < 5: + logging.critical("Debug protocol version %d not supported", pv) + sys.exit() + if machinetype and (machinetype != 0x2D): + logging.critical( + "Processor architecture %04x not supported", machinetype + ) + sys.exit() + + logging.info("Windows version = %s", osv) + logging.info("Protocol version = %d", pv) + logging.info("Kernel base = %08x", kernbase) + logging.info("Module list = %08x", modlist) + logging.info("Debugger data = %08x", ddata) + + return (osv, pv, kernbase, modlist, ddata) + return ("0.0", 0, 0, 0, 0) + + def _printVersionData(self): + v = self._getVersionInfo() + logging.info("Windows version = %s", v[0]) + logging.info("Protocol version = %d", v[1]) + logging.info("Kernel base = %08x", v[2]) + logging.info("Module list = %08x", v[3]) + logging.info("Debugger data = %08x", v[4]) + + # if False: + # def getKernelModules(): + # save = pcontext + # pcontext = kernelcontext # this procedure is kernel context only + # modules = [] + # v = getVersionInfo() + # flink = readDword(v[3]) + # modlist = walkList(flink) + # for mod in modlist: + # + # # print("module at %08x\n", $mod + # buf = readVirtualMemory(mod, 0x34) + # if len(buf) == 0x34: + # base = unpack("I", substr(buf, 0x18, 4)) + # if base == 0: + # continue + # entry = unpack("I", substr(buf, 0x1C, 4)) + # size = unpack("I", substr(buf, 0x20, 4)) + # path = substr(buf, 0x24, 8) + # name = substr(buf, 0x2C, 8) + # modules[base]["name"] = unicodeStructToAscii(name) + # modules[base]["path"] = unicodeStructToAscii(path) + # modules[base]["size"] = size + # modules[base]["entry"] = entry + # pcontext = save + # return modules + # + # def unicodeStructToAscii(struct): + # if len(struct) != 8: + # return + # length = unpack("H", substr(struct, 0, 2)) + # vaddr = unpack("I", substr(struct, 4, 4)) + # buf = readVirtualMemory(vaddr, length) + # if len(buf) == length: + # buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii + # return buf + + def _sendManipulateStatePacket(self, d): + header = pack( + "IHHII", + PACKET_LEADER, + PACKET_TYPE_KD_STATE_MANIPULATE, + len(d), + self.nextpid, + generate_checksum(d), + ) + + logging.debug( + "< manipulate state:\nHeader:\n%s\nBody:\n%s", + hexformat(header), + hexformat(d), + ) + self.client.write(header) + self.client.write(d) + self.client.write(bytes([PACKET_TRAILER])) + + def _sendDbgKdContinue2(self): + logging.debug("< Sending DbgKdContinueApi2 packet") + d = bytearray([0] * 56) + + # Manipulate state packet + d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) # API Number + # d = patch_substr(d, 4, 2, "H", 0x0001) # Processor level + # d = patch_substr(d, 6, 2, "H", 0x0001) # Processor + d = patch_substr(d, 8, 4, "I", HRESULT_STATUS_SUCCESS) # Return status + + # Continue2 subpacket + d = patch_substr(d, 12, 4, "I", HRESULT_STATUS_SUCCESS) # ContinueStatus + d = patch_substr(d, 16, 4, "I", 0x00000000) # TraceFlag + d = patch_substr(d, 20, 4, "I", 0x00000000) # DR7 + d = patch_substr(d, 24, 4, "I", 0x00000001) # CurrentSymbolStart + d = patch_substr(d, 28, 4, "I", 0x00000001) # CurrentSymbolEnd + + # substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 ); + # substr( $d, 8, 4 ) = pack( "I", 0x00010001 ); + # substr( $d, 16, 4 ) = pack( "I", 0x00010001 ); + # substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag + # substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7 + self._sendManipulateStatePacket(d) + + def _sendDbgKdGetVersion(self): + + # print("Sending DbgKdGetVersionApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdGetVersionApi) + self._sendManipulateStatePacket(d) + + def _sendDbgKdWriteBreakPoint(self, bp): + bp = hex(bp) + + # print("Sending DbgKdWriteBreakPointApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdWriteBreakPointApi) + d = patch_substr(d, 16, 4, "I", bp) + d = patch_substr(d, 20, 4, "I", self.curbp) + self.curbp += 1 + self._sendManipulateStatePacket(d) + + def _sendDbgKdRestoreBreakPoint(self, bp): + if bp in self.breakpoints: + + # print("Sending DbgKdRestoreBreakPointApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdRestoreBreakPointApi) + d = patch_substr(d, 16, 4, "I", self.breakpoints[bp]) + self._sendManipulateStatePacket(d) + del self.breakpoints[bp] + + else: + logging.warning("Breakpoint not set at bp") + + def _sendDbgKdReadControlSpace(self): + + # print("Sending DbgKdReadControlSpaceApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", 0x3137) + d = patch_substr(d, 16, 4, "I", 0x02CC) + d = patch_substr(d, 24, 4, "I", 84) + self._sendManipulateStatePacket(d) + + def _sendDbgKdWriteControlSpace(self): + + # print("Sending DbgKdWriteControlSpaceApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", 0x3138) + d = patch_substr(d, 16, 4, "I", 0x02CC) + d = patch_substr(d, 24, 4, "I", 84) + d += self.controlspace + self._sendManipulateStatePacket(d) + self.controlspacesent = True + + def _sendDbgKdGetContext(self): + + # print("Sending DbgKdGetContextApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdGetContextApi) + self._sendManipulateStatePacket(d) + + def _sendDbgKdSetContext(self, ctx): + + # print("Sending DbgKdSetContextApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdSetContextApi) + d = patch_substr(d, 16, 4, ctx[0:4]) + d += ctx + self._sendManipulateStatePacket(d) + + def _sendDbgKdReadPhysicalMemory(self, addr, readlen): + + # print("Sending DbgKdReadPhysicalMemoryApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdReadPhysicalMemoryApi) + d = patch_substr(d, 16, 4, "I", addr) + d = patch_substr(d, 24, 4, "I", readlen) + self._sendManipulateStatePacket(d) + + def _sendDbgKdWritePhysicalMemory(self, addr, data): + writelen = len(data) + + d = pack("III", DbgKdWritePhysicalMemoryApi, addr, writelen) + d += data + self._sendManipulateStatePacket(d) + + def _sendDbgKdReadVirtualMemory(self, vaddr, readlen): + + # print("Sending DbgKdReadVirtualMemoryApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdReadVirtualMemoryApi) + d = patch_substr(d, 16, 4, "I", vaddr) + d = patch_substr(d, 24, 4, "I", readlen) + self._sendManipulateStatePacket(d) + + def _sendDbgKdWriteVirtualMemory(self, vaddr, data): + writelen = len(data) + + # print("Sending DbgKdWriteVirtualMemoryApi packet\n" + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", DbgKdWriteVirtualMemoryApi) + d = patch_substr(d, 16, 4, "I", vaddr) + d = patch_substr(d, 24, 4, "I", writelen) + d += data + self._sendManipulateStatePacket(d) + + def _readDword(self, addr): + + # print("Reading dword at %08x\n", addr + buf = self._readVirtualMemory(addr, 4) + if len(buf) == 4: + return unpack_one("I", buf) + return "failed" + + def _readPhysicalMemory(self, addr, length): + chunksize = 0x800 # max to request in one packet + out = bytearray([]) + while length > 0: + + if length < chunksize: + self._sendDbgKdReadPhysicalMemory(addr, length) + buf = self._waitStateManipulate(DbgKdReadPhysicalMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length = 0 + else: + self._sendDbgKdReadPhysicalMemory(addr, chunksize) + buf = self._waitStateManipulate(DbgKdReadPhysicalMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length -= chunksize + addr += chunksize + return out + + def _writePhysicalMemory(self, addr, buf): + length = len(buf) + chunksize = 0x800 # max to send in one packet + offset = 0 + # FIXME: Logic sucks here.. + while length > 0: + if length < chunksize: + self._sendDbgKdWritePhysicalMemory(addr, buf) + self._waitStateManipulate(0x313E) + length = 0 + else: + self._sendDbgKdWritePhysicalMemory(addr, substr(buf, offset, chunksize)) + self._waitStateManipulate(0x313E) + length -= chunksize + offset += chunksize + addr += chunksize + + def _writeVirtualMemory(self, addr, buf): + length = len(buf) + # FIXME: return unless addr && length + chunksize = 0x800 # max to send in one packet + offset = 0 + if self.pcontext["pid"] == 0: + # FIXME: Logic sucks here.. + while length > 0: + if length < chunksize: + self._sendDbgKdWriteVirtualMemory(addr, buf) + self._waitStateManipulate(DbgKdWriteVirtualMemoryApi) + length = 0 + else: + self._sendDbgKdWriteVirtualMemory( + addr, substr(buf, offset, chunksize) + ) + self._waitStateManipulate(DbgKdWriteVirtualMemoryApi) + length -= chunksize + offset += chunksize + addr += chunksize + else: + distance_to_page_boundary = 0x1000 - (addr & 0xFFF) + # FIXME: Logic sucks here.. + if distance_to_page_boundary > length: + physaddr = logical2physical(addr) + self._writePhysicalMemory(physaddr, buf) + return + + physaddr = logical2physical(addr) + buf = buf[:distance_to_page_boundary] + self._writePhysicalMemory(physaddr, buf) + + addr += distance_to_page_boundary + offset += distance_to_page_boundary + remainder = length - distance_to_page_boundary + + while remainder > 0: + if remainder < 0x1000: + physaddr = logical2physical(addr) + self._writePhysicalMemory(physaddr, substr(buf, offset, remainder)) + remainder = 0 + else: + physaddr = logical2physical(addr) + self._writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) + addr += 0x1000 + offset += 0x1000 + remainder -= 0x1000 + + # FIXME: Indentation is horrible + def _readVirtualMemory(self, addr, length): + # FIXME: return unless addr && length + chunksize = 0x800 # max to request in one packet + out = bytearray() + if self.pcontext["pid"] == 0: + while length > 0: + if length < chunksize: + self._sendDbgKdReadVirtualMemory(addr, length) + buf = self._waitStateManipulate(DbgKdReadVirtualMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length = 0 + else: + self._sendDbgKdReadVirtualMemory(addr, chunksize) + buf = self._waitStateManipulate(DbgKdReadVirtualMemoryApi) + if len(buf) > 56: + out += substr(buf, 56) + length -= chunksize + addr += chunksize + return out + + distance_to_page_boundary = 0x1000 - (addr & 0xFFF) + if distance_to_page_boundary > length: + physaddr = logical2physical(addr) + return self._readPhysicalMemory(physaddr, length) + + physaddr = logical2physical(addr) + buf = self._readPhysicalMemory(physaddr, distance_to_page_boundary) + addr += distance_to_page_boundary + remainder = length - distance_to_page_boundary + while remainder > 0: + if remainder < 0x1000: + physaddr = logical2physical(addr) + buf += self._readPhysicalMemory(physaddr, remainder) + remainder = 0 + else: + physaddr = logical2physical(addr) + buf += self._readPhysicalMemory(physaddr, 0x1000) + addr += 0x1000 + remainder -= 0x1000 + return buf + + def _sendDbgKdReboot(self): + logging.info("Sending DbgKdRebootApi packet") + d = bytearray([0] * 56) + d = patch_substr(d, 0, 4, "I", 0x313B) + self._sendManipulateStatePacket(d) + + def _waitStateManipulate(self, wanted): + if self.running: + return None + + logging.debug("Waiting on STATE_MANIPULATE packet") + # FIXME: Implement a timeout. + # try: + while 1: + ptype, buf = self._handlePacket() + if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: + api = unpack_one("I", substr(buf, 0, 4)) + if api == wanted: + break + # except timeout: + # logging.warning("Timeout waiting for %04x packet reply", wanted) + return buf + + def _getPspCidTable(self): + pspcidtable = 0 + save = self.pcontext + self.pcontext = self.kernelcontext # this procedure is kernel context only + self._sendDbgKdGetVersion() + buf = self._waitStateManipulate(DbgKdGetVersionApi) + pddata = unpack_one("I", substr(buf, 48, 4)) + if pddata: + # print("Pointer to debugger data struct is at %08x\n", pddata + ddata = self._readDword(pddata) + if ddata != "failed": + # print("debugger data struct is at %08x\n", ddata + pspcidtable = self._readDword(ddata + 88) + if pspcidtable != "failed": + # print("PspCidTable is %08x\n", $pspcidtable + self.pcontext = save + return pspcidtable + self.pcontext = save + return 0 diff --git a/server.py b/server.py new file mode 100644 index 0000000..69f56c2 --- /dev/null +++ b/server.py @@ -0,0 +1,38 @@ +"""Waits for a serial TCP connection from a qemu instance and hands it off to a debugger.""" +import socketserver + +import debug_connection + + +class _DebuggeeHandler(socketserver.StreamRequestHandler): + """Handles connection to a debuggee VM.""" + + def setup(self) -> None: + super(_DebuggeeHandler, self).setup() + self.debugger = self.server.debugger + client = debug_connection.DebugConnection(self.client_address) + client.handle_socket(self.connection) + + self.debugger.client = client + + def finish(self) -> None: + super(_DebuggeeHandler, self).finish() + print(f"Debuggee at {self.client_address[0]} disconnectected\n") + + def handle(self) -> None: + print(f"Debuggee connected from {self.client_address[0]}\n") + self.debugger.run() + + +def serve(host, port, debugger_connection): + """Listens for connections on the given host:port and hands them to the given debugger context.""" + + print(f"Waiting for debuggee at {host}:{port}") + server = socketserver.ThreadingTCPServer((host, port), _DebuggeeHandler) + server.debugger = debugger_connection + + try: + server.serve_forever() + except KeyboardInterrupt: + print("Shutting down...") + server.shutdown() diff --git a/sniffer.py b/sniffer.py index 398406f..50f29aa 100755 --- a/sniffer.py +++ b/sniffer.py @@ -198,7 +198,7 @@ def _log_state_manipulate(self, payload): self._log("UNKN: %s", hexasc(payload)) def _log_version(self, payload): - assert(len(payload) == 40) + assert len(payload) == 40 ( major_version, minor_version, @@ -388,6 +388,7 @@ def main(args): server.serve_forever() except KeyboardInterrupt: debugger_connection.stop() + server.shutdown() raise diff --git a/util.py b/util.py index df9b80b..c72adb6 100644 --- a/util.py +++ b/util.py @@ -57,7 +57,7 @@ def patch_substr(buf, start, length, fmt, data=None): def unpack_one(fmt, data): - assert(len(fmt) == 1) + assert len(fmt) == 1 return struct.unpack(fmt, data)[0] # pylint: disable = no-member diff --git a/windpl.py b/windpl.py index 4cf1b13..778d1cf 100755 --- a/windpl.py +++ b/windpl.py @@ -41,750 +41,15 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -# pylint: disable = invalid-name, missing-function-docstring, too-many-instance-attributes, fixme - import argparse import logging import os import pathlib import sys -import struct -from struct import pack # pylint: disable = no-name-in-module -import time - -from constants import * -from debug_connection import DebugConnection -from util import * -from windpl_extra import logical2physical - - -class DebugContext: - """Models a KernelDebug session.""" - - def __init__(self) -> None: - self.start_time = time.perf_counter() - self.timeout = 10 # max time to wait on packet - self.running = True - - self.kernelcontext = {} - self.kernelcontext["peb"] = 0 - self.kernelcontext["pid"] = 0 - self.kernelcontext["eprocess"] = 0 - self.kernelcontext["dtb"] = 0 - - self.processcontext = {} - self.processcontext["peb"] = 0 - self.processcontext["pid"] = 0 - self.processcontext["eprocess"] = 0 - self.processcontext["dtb"] = 0 - self.pcontext = self.kernelcontext - self.nextpid = 1 - self.breakpoints = {} - - self.curbp = 1 - self.controlspace = 0 - self.controlspacesent = False - - self.client = None - - def connect(self, endpoint): - # if False: - # ds = stat(dev) or die("$!") - # if S_ISCHR(ds.mode): - # # require Device::SerialPort - # # FIXME: serial = tie( *FH, 'Device::SerialPort', "dev" ) or die("Can't tie: $!" - # serial.baudrate(115200) - # serial.parity("none") - # serial.databits(8) - # serial.stopbits(1) - # serial.handshake("none") - # serial.write_settings or die("failed writing settings") - # FH.blocking(0) - # elif S_ISSOCK(ds.mode): - # client = None # FIXME: IO::Socket::UNIX->new(Type = SOCK_STREAM, Peer = dev) or die("Can't create socket!") - # else: - # die("dev not a character device or a socket") - - logging.info("Connecting to '%s'", endpoint) - - self.client = DebugConnection(endpoint) - self.client.connect() - - def run(self): - logging.info("Waiting for target device...") - self._sendReset() - packets = 0 - while True: - self._handlePacket() - logging.debug("Processed %d packets\n", packets) - packets += 1 - - # FIXME: windpl_loop.py - - @property - def elapsed_time(self): - return time.perf_counter() - self.start_time - - def _handlePacket(self): - ptype, buf = self._getPacket() - - if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: - self._handleStateManipulate(buf) - elif ptype == PACKET_TYPE_KD_DEBUG_IO: - self._handleDebugIO(buf) - elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: - self._handleStateChange(buf) - elif ptype == PACKET_TYPE_KD_RESET: - # TODO: check to see if this reset is in response to a reset sent by the debugger script. - # If not, dump the read buffer and start over. - pass - elif ptype == PACKET_TYPE_KD_RESEND: - logging.warning("!! Ignoring resend request") - elif len(buf): - logging.debug("Ignoring packet") - - return ptype, buf - - def _getPacket(self): - packet_type = None - payload = bytearray([]) - buf = self.client._read_and_passthrough(4) - packet_signature = unpack_one("I", buf) - if packet_signature in (PACKET_LEADER, CONTROL_PACKET_LEADER): - logging.debug( - "[%d] Got packet leader: %08x (%s)", - int(self.elapsed_time * 1000), - packet_signature, - "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", - ) - - buf = self.client._read_and_passthrough(2) - packet_type = unpack_one("H", buf) - packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") - logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) - if packet_type_name == "": - logging.critical("!! Unexpected packet type %04x", packet_type) - - buf = self.client._read_and_passthrough(2) - data_size = unpack_one("H", buf) - - buf = self.client._read_and_passthrough(4) - packet_id = unpack_one("I", buf) - self.nextpid = packet_id - - buf = self.client._read_and_passthrough(4) - expected_checksum = unpack_one("I", buf) - - logging.debug("> Packet ID: %08x", packet_id) - logging.debug("> Data size: %d", data_size) - logging.debug("> Checksum: %08x", expected_checksum) - - if data_size: - payload = self.client._read_and_passthrough(data_size) - - payload_checksum = generate_checksum(payload) - if payload_checksum != expected_checksum: - raise Exception( - f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" - ) - - # send ack if it's a non-control packet - if packet_signature == PACKET_LEADER: - # packet trailer - # logging.debug("Reading trailer...") - trail = self.client._read_and_passthrough(1) - # logging.debug("Trailer: %x", trail[0]) - if trail[0] == PACKET_TRAILER: - # logging.debug("sending Ack") - self._sendAck() - else: - raise Exception("Invalid packet trailer 0x%x" % trail[0]) - else: - logging.warning("Discarding non-KD packet bytes: %08x", packet_signature) - return packet_type, payload - - def _handleDebugIO(self, buf): # pylint: disable = no-self-use - apiNumber = unpack_one("I", substr(buf, 0, 4)) - if apiNumber == DbgKdPrintStringApi: - print("DbgPrint: " + substr(buf, 0x10).decode("utf-8")) - else: - logging.debug("Ignoring debug IO packet with API number %d", apiNumber) - - def _handleStateChange(self, buf): - newState = unpack_one("I", substr(buf, 0, 4)) - logging.debug("State Change: New state: %08x", newState) - - if newState == DbgKdExceptionStateChange: - # DBGKM_EXCEPTION64 - ex = substr(buf, 32) - - code = unpack_one("I", substr(ex, 0, 4)) - flags = unpack_one("I", substr(ex, 4, 4)) - record = unpack_one("I", substr(ex, 8, 4)) - address = unpack_one("I", substr(ex, 16, 4)) - parameters = unpack_one("I", substr(ex, 24, 4)) - - if code in STATE_CHANGE_EXCEPTIONS: - logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) - else: - logging.warning("*** Exception %08x ", code) - - logging.warning("at %08x\n", address) - - logging.warning("Exception flags = %08x", flags) - logging.warning("Exception record = %08x", record) - logging.warning("Exception address = %08x", address) - logging.warning("Number parameters = %08x", parameters) - - self.running = False - - # my @v = getVersionInfo() - # version = v[0] - # $kernelbase = v[2] - elif newState == DbgKdLoadSymbolsStateChange: - # DBGKD_LOAD_SYMBOLS64 - - filename = buf[0x3B8:-1] - filename = filename.decode("utf-8") - logging.debug("Load Symbols for '%s'", filename) - - # nothing to do... - - self._sendDbgKdContinue2() - - def _handleStateManipulate(self, buf): - - apiNumber = unpack_one("I", substr(buf, 0, 4)) - logging.debug("State Manipulate: %08x", apiNumber) - - if apiNumber == DbgKdWriteBreakPointApi: - bp = "%08x" % unpack_one("I", substr(buf, 16, 4)) - handle = unpack_one("I", substr(buf, 20, 4)) - logging.debug("Breakpoint %d set at %s", handle, bp) - self.breakpoints[bp] = handle - elif apiNumber == DbgKdRestoreBreakPointApi: - handle = unpack_one("I", substr(buf, 16, 4)) - logging.debug("Breakpoint %d cleared", handle) - elif apiNumber == DbgKdGetVersionApi: - version = substr(buf, 16) - logging.debug("VERS:\n%s", hexformat(version)) - elif apiNumber == DbgKdReadVirtualMemoryApi: - vmem = substr(buf, 56) - logging.debug("VMEM:\n%s", hexasc(vmem)) - elif apiNumber == DbgKdReadPhysicalMemoryApi: - pmem = substr(buf, 56) - logging.debug("PMEM:\n%s", hexasc(pmem)) - elif apiNumber == DbgKdReadControlSpaceApi: - controlspace = substr(buf, 56) - logging.debug("CNTL:\n%s", hexformat(controlspace)) - else: - logging.debug("UNKN:\n%s", hexasc(buf)) - - def _sendAck(self): - logging.debug("Acking with next PID: %08x", self.nextpid) - ack_packet = pack( - "IHHII", - CONTROL_PACKET_LEADER, - PACKET_TYPE_KD_ACKNOWLEDGE, - 0, - self.nextpid, - 0, - ) - # logging.debug("Ack: %s", hexformat(ack_packet)) - self.client.write(ack_packet) - - def _sendReset(self): - reset_packet = pack( - "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0 - ) - - logging.debug("Sending reset packet\n\n") - # print(hexformat(rst) - self.client.write(reset_packet) - - def _getContext(self): - context = {} - self._sendDbgKdGetContext() - buf = self._waitStateManipulate(DbgKdGetContextApi) - - if len(buf) <= 204: - return None - - ctx = buf[56:] - - # print("CTXT: ", hexformat($context) - context["ContextFlags"] = unpack_one("I", ctx[0:4]) - context["DR0"] = unpack_one("I", ctx[4:8]) - context["DR1"] = unpack_one("I", ctx[8: 8 + 4]) - context["DR2"] = unpack_one("I", ctx[12: 12 + 4]) - context["DR3"] = unpack_one("I", ctx[16: 16 + 4]) - context["DR6"] = unpack_one("I", ctx[20: 20 + 4]) - context["DR7"] = unpack_one("I", ctx[24: 24 + 4]) - context["fp.ControlWord"] = unpack_one("I", ctx[28: 28 + 4]) - context["fp.StatusWord"] = unpack_one("I", ctx[32: 32 + 4]) - context["fp.TagWord"] = unpack_one("I", ctx[36: 36 + 4]) - context["fp.ErrorOffset"] = unpack_one("I", ctx[40: 40 + 4]) - context["fp.ErrorSelector"] = unpack_one("I", ctx[44: 44 + 4]) - context["fp.DataOffset"] = unpack_one("I", ctx[48: 48 + 4]) - context["fp.DataSelector"] = unpack_one("I", ctx[52: 52 + 4]) - context["fp.RegisterArea"] = ctx[56 : 56 + 80] - context["fp.Cr0NpxState"] = unpack_one("I", ctx[136: 136 + 4]) - context["GS"] = unpack_one("I", ctx[140: 140 + 4]) - context["FS"] = unpack_one("I", ctx[144: 144 + 4]) - context["ES"] = unpack_one("I", ctx[148: 148 + 4]) - context["DS"] = unpack_one("I", ctx[152: 152 + 4]) - context["EDI"] = unpack_one("I", ctx[156: 156 + 4]) - context["ESI"] = unpack_one("I", ctx[160: 160 + 4]) - context["EBX"] = unpack_one("I", ctx[164: 164 + 4]) - context["EDX"] = unpack_one("I", ctx[168: 168 + 4]) - context["ECX"] = unpack_one("I", ctx[172: 172 + 4]) - context["EAX"] = unpack_one("I", ctx[176: 176 + 4]) - context["EBP"] = unpack_one("I", ctx[180: 180 + 4]) - context["EIP"] = unpack_one("I", ctx[184:188]) - context["CS"] = unpack_one("I", ctx[188:192]) - context["Eflags"] = unpack_one("I", ctx[192:196]) - context["ESP"] = unpack_one("I", ctx[196:200]) - context["SS"] = unpack_one("I", ctx[200:204]) - context["leftovers"] = ctx[204:] - return context - - def _setContext(self, context): - ctx = pack("I", context["ContextFlags"]) - ctx += pack("I", context["DR0"]) - ctx += pack("I", context["DR1"]) - ctx += pack("I", context["DR2"]) - ctx += pack("I", context["DR3"]) - ctx += pack("I", context["DR6"]) - ctx += pack("I", context["DR7"]) - ctx += pack("I", context["fp.ControlWord"]) - ctx += pack("I", context["fp.StatusWord"]) - ctx += pack("I", context["fp.TagWord"]) - ctx += pack("I", context["fp.ErrorOffset"]) - ctx += pack("I", context["fp.ErrorSelector"]) - ctx += pack("I", context["fp.DataOffset"]) - ctx += pack("I", context["fp.DataSelector"]) - ctx += context["fp.RegisterArea"] - ctx += pack("I", context["fp.Cr0NpxState"]) - ctx += pack("I", context["GS"]) - ctx += pack("I", context["FS"]) - ctx += pack("I", context["ES"]) - ctx += pack("I", context["DS"]) - ctx += pack("I", context["EDI"]) - ctx += pack("I", context["ESI"]) - ctx += pack("I", context["EBX"]) - ctx += pack("I", context["EDX"]) - ctx += pack("I", context["ECX"]) - ctx += pack("I", context["EAX"]) - ctx += pack("I", context["EBP"]) - ctx += pack("I", context["EIP"]) - ctx += pack("I", context["CS"]) - ctx += pack("I", context["Eflags"]) - ctx += pack("I", context["ESP"]) - ctx += pack("I", context["SS"]) - ctx += context["leftovers"] - self._sendDbgKdSetContext(ctx) - self._waitStateManipulate(DbgKdSetContextApi) - - def _getVersionInfo(self): - logging.info("getVersionInfo") - # os version, protocol version, kernel base, module list, debugger data - self._sendDbgKdGetVersion() - buf = self._waitStateManipulate(DbgKdGetVersionApi) - if len(buf) > 32: - v = substr(buf, 16) - osv = "%d.%d" % (unpack_one("H", substr(v, 4, 2)), unpack_one("H", substr(v, 6, 2))) - pv = unpack_one("H", substr(v, 8, 2)) - machinetype = unpack_one("H", substr(v, 12, 2)) - kernbase = unpack_one("I", substr(v, 16, 4)) - modlist = unpack_one("I", substr(v, 24, 4)) - ddata = unpack_one("I", substr(v, 32, 4)) - if pv < 5: - logging.critical("Debug protocol version %d not supported", pv) - sys.exit() - if machinetype and (machinetype != 0x2D): - logging.critical( - "Processor architecture %04x not supported", machinetype - ) - sys.exit() - - logging.info("Windows version = %s", osv) - logging.info("Protocol version = %d", pv) - logging.info("Kernel base = %08x", kernbase) - logging.info("Module list = %08x", modlist) - logging.info("Debugger data = %08x", ddata) - return (osv, pv, kernbase, modlist, ddata) - return ("0.0", 0, 0, 0, 0) - - def _printVersionData(self): - v = self._getVersionInfo() - logging.info("Windows version = %s", v[0]) - logging.info("Protocol version = %d", v[1]) - logging.info("Kernel base = %08x", v[2]) - logging.info("Module list = %08x", v[3]) - logging.info("Debugger data = %08x", v[4]) - - # if False: - # def getKernelModules(): - # save = pcontext - # pcontext = kernelcontext # this procedure is kernel context only - # modules = [] - # v = getVersionInfo() - # flink = readDword(v[3]) - # modlist = walkList(flink) - # for mod in modlist: - # - # # print("module at %08x\n", $mod - # buf = readVirtualMemory(mod, 0x34) - # if len(buf) == 0x34: - # base = unpack("I", substr(buf, 0x18, 4)) - # if base == 0: - # continue - # entry = unpack("I", substr(buf, 0x1C, 4)) - # size = unpack("I", substr(buf, 0x20, 4)) - # path = substr(buf, 0x24, 8) - # name = substr(buf, 0x2C, 8) - # modules[base]["name"] = unicodeStructToAscii(name) - # modules[base]["path"] = unicodeStructToAscii(path) - # modules[base]["size"] = size - # modules[base]["entry"] = entry - # pcontext = save - # return modules - # - # def unicodeStructToAscii(struct): - # if len(struct) != 8: - # return - # length = unpack("H", substr(struct, 0, 2)) - # vaddr = unpack("I", substr(struct, 4, 4)) - # buf = readVirtualMemory(vaddr, length) - # if len(buf) == length: - # buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii - # return buf - - def _sendManipulateStatePacket(self, d): - header = pack( - "IHHII", - PACKET_LEADER, - PACKET_TYPE_KD_STATE_MANIPULATE, - len(d), - self.nextpid, - generate_checksum(d), - ) - - logging.debug( - "< manipulate state:\nHeader:\n%s\nBody:\n%s", - hexformat(header), - hexformat(d), - ) - self.client.write(header) - self.client.write(d) - self.client.write(bytes([PACKET_TRAILER])) - - def _sendDbgKdContinue2(self): - logging.debug("< Sending DbgKdContinueApi2 packet") - d = bytearray([0] * 56) - - # Manipulate state packet - d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) # API Number - # d = patch_substr(d, 4, 2, "H", 0x0001) # Processor level - # d = patch_substr(d, 6, 2, "H", 0x0001) # Processor - d = patch_substr(d, 8, 4, "I", HRESULT_STATUS_SUCCESS) # Return status - - # Continue2 subpacket - d = patch_substr(d, 12, 4, "I", HRESULT_STATUS_SUCCESS) # ContinueStatus - d = patch_substr(d, 16, 4, "I", 0x00000000) # TraceFlag - d = patch_substr(d, 20, 4, "I", 0x00000000) # DR7 - d = patch_substr(d, 24, 4, "I", 0x00000001) # CurrentSymbolStart - d = patch_substr(d, 28, 4, "I", 0x00000001) # CurrentSymbolEnd - - # substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 ); - # substr( $d, 8, 4 ) = pack( "I", 0x00010001 ); - # substr( $d, 16, 4 ) = pack( "I", 0x00010001 ); - # substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag - # substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7 - self._sendManipulateStatePacket(d) - - def _sendDbgKdGetVersion(self): - - # print("Sending DbgKdGetVersionApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdGetVersionApi) - self._sendManipulateStatePacket(d) - - def _sendDbgKdWriteBreakPoint(self, bp): - bp = hex(bp) - - # print("Sending DbgKdWriteBreakPointApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdWriteBreakPointApi) - d = patch_substr(d, 16, 4, "I", bp) - d = patch_substr(d, 20, 4, "I", self.curbp) - self.curbp += 1 - self._sendManipulateStatePacket(d) - - def _sendDbgKdRestoreBreakPoint(self, bp): - if bp in self.breakpoints: - - # print("Sending DbgKdRestoreBreakPointApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdRestoreBreakPointApi) - d = patch_substr(d, 16, 4, "I", self.breakpoints[bp]) - self._sendManipulateStatePacket(d) - del self.breakpoints[bp] - - else: - logging.warning("Breakpoint not set at bp") - - def _sendDbgKdReadControlSpace(self): - - # print("Sending DbgKdReadControlSpaceApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", 0x3137) - d = patch_substr(d, 16, 4, "I", 0x02CC) - d = patch_substr(d, 24, 4, "I", 84) - self._sendManipulateStatePacket(d) - - def _sendDbgKdWriteControlSpace(self): - - # print("Sending DbgKdWriteControlSpaceApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", 0x3138) - d = patch_substr(d, 16, 4, "I", 0x02CC) - d = patch_substr(d, 24, 4, "I", 84) - d += self.controlspace - self._sendManipulateStatePacket(d) - self.controlspacesent = True - - def _sendDbgKdGetContext(self): - - # print("Sending DbgKdGetContextApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdGetContextApi) - self._sendManipulateStatePacket(d) - - def _sendDbgKdSetContext(self, ctx): - - # print("Sending DbgKdSetContextApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdSetContextApi) - d = patch_substr(d, 16, 4, ctx[0:4]) - d += ctx - self._sendManipulateStatePacket(d) - - def _sendDbgKdReadPhysicalMemory(self, addr, readlen): - - # print("Sending DbgKdReadPhysicalMemoryApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdReadPhysicalMemoryApi) - d = patch_substr(d, 16, 4, "I", addr) - d = patch_substr(d, 24, 4, "I", readlen) - self._sendManipulateStatePacket(d) - - def _sendDbgKdWritePhysicalMemory(self, addr, data): - writelen = len(data) - - d = pack("III", DbgKdWritePhysicalMemoryApi, addr, writelen) - d += data - self._sendManipulateStatePacket(d) - - def _sendDbgKdReadVirtualMemory(self, vaddr, readlen): - - # print("Sending DbgKdReadVirtualMemoryApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdReadVirtualMemoryApi) - d = patch_substr(d, 16, 4, "I", vaddr) - d = patch_substr(d, 24, 4, "I", readlen) - self._sendManipulateStatePacket(d) - - def _sendDbgKdWriteVirtualMemory(self, vaddr, data): - writelen = len(data) - - # print("Sending DbgKdWriteVirtualMemoryApi packet\n" - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", DbgKdWriteVirtualMemoryApi) - d = patch_substr(d, 16, 4, "I", vaddr) - d = patch_substr(d, 24, 4, "I", writelen) - d += data - self._sendManipulateStatePacket(d) - - def _readDword(self, addr): - - # print("Reading dword at %08x\n", addr - buf = self._readVirtualMemory(addr, 4) - if len(buf) == 4: - return unpack_one("I", buf) - return "failed" - - def _readPhysicalMemory(self, addr, length): - chunksize = 0x800 # max to request in one packet - out = bytearray([]) - while length > 0: - - if length < chunksize: - self._sendDbgKdReadPhysicalMemory(addr, length) - buf = self._waitStateManipulate(DbgKdReadPhysicalMemoryApi) - if len(buf) > 56: - out += substr(buf, 56) - length = 0 - else: - self._sendDbgKdReadPhysicalMemory(addr, chunksize) - buf = self._waitStateManipulate(DbgKdReadPhysicalMemoryApi) - if len(buf) > 56: - out += substr(buf, 56) - length -= chunksize - addr += chunksize - return out - - def _writePhysicalMemory(self, addr, buf): - length = len(buf) - chunksize = 0x800 # max to send in one packet - offset = 0 - # FIXME: Logic sucks here.. - while length > 0: - if length < chunksize: - self._sendDbgKdWritePhysicalMemory(addr, buf) - self._waitStateManipulate(0x313E) - length = 0 - else: - self._sendDbgKdWritePhysicalMemory(addr, substr(buf, offset, chunksize)) - self._waitStateManipulate(0x313E) - length -= chunksize - offset += chunksize - addr += chunksize - - def _writeVirtualMemory(self, addr, buf): - length = len(buf) - # FIXME: return unless addr && length - chunksize = 0x800 # max to send in one packet - offset = 0 - if self.pcontext["pid"] == 0: - # FIXME: Logic sucks here.. - while length > 0: - if length < chunksize: - self._sendDbgKdWriteVirtualMemory(addr, buf) - self._waitStateManipulate(DbgKdWriteVirtualMemoryApi) - length = 0 - else: - self._sendDbgKdWriteVirtualMemory( - addr, substr(buf, offset, chunksize) - ) - self._waitStateManipulate(DbgKdWriteVirtualMemoryApi) - length -= chunksize - offset += chunksize - addr += chunksize - else: - distance_to_page_boundary = 0x1000 - (addr & 0xFFF) - # FIXME: Logic sucks here.. - if distance_to_page_boundary > length: - physaddr = logical2physical(addr) - self._writePhysicalMemory(physaddr, buf) - return - - physaddr = logical2physical(addr) - buf = buf[:distance_to_page_boundary] - self._writePhysicalMemory(physaddr, buf) - - addr += distance_to_page_boundary - offset += distance_to_page_boundary - remainder = length - distance_to_page_boundary - - while remainder > 0: - if remainder < 0x1000: - physaddr = logical2physical(addr) - self._writePhysicalMemory(physaddr, substr(buf, offset, remainder)) - remainder = 0 - else: - physaddr = logical2physical(addr) - self._writePhysicalMemory(physaddr, substr(buf, offset, 0x1000)) - addr += 0x1000 - offset += 0x1000 - remainder -= 0x1000 - - # FIXME: Indentation is horrible - def _readVirtualMemory(self, addr, length): - # FIXME: return unless addr && length - chunksize = 0x800 # max to request in one packet - out = bytearray() - if self.pcontext["pid"] == 0: - while length > 0: - if length < chunksize: - self._sendDbgKdReadVirtualMemory(addr, length) - buf = self._waitStateManipulate(DbgKdReadVirtualMemoryApi) - if len(buf) > 56: - out += substr(buf, 56) - length = 0 - else: - self._sendDbgKdReadVirtualMemory(addr, chunksize) - buf = self._waitStateManipulate(DbgKdReadVirtualMemoryApi) - if len(buf) > 56: - out += substr(buf, 56) - length -= chunksize - addr += chunksize - return out - - distance_to_page_boundary = 0x1000 - (addr & 0xFFF) - if distance_to_page_boundary > length: - physaddr = logical2physical(addr) - return self._readPhysicalMemory(physaddr, length) - - physaddr = logical2physical(addr) - buf = self._readPhysicalMemory(physaddr, distance_to_page_boundary) - addr += distance_to_page_boundary - remainder = length - distance_to_page_boundary - while remainder > 0: - if remainder < 0x1000: - physaddr = logical2physical(addr) - buf += self._readPhysicalMemory(physaddr, remainder) - remainder = 0 - else: - physaddr = logical2physical(addr) - buf += self._readPhysicalMemory(physaddr, 0x1000) - addr += 0x1000 - remainder -= 0x1000 - return buf - - def _sendDbgKdReboot(self): - logging.info("Sending DbgKdRebootApi packet") - d = bytearray([0] * 56) - d = patch_substr(d, 0, 4, "I", 0x313B) - self._sendManipulateStatePacket(d) - - def _waitStateManipulate(self, wanted): - if self.running: - return None - - logging.debug("Waiting on STATE_MANIPULATE packet") - # FIXME: Implement a timeout. - # try: - while 1: - ptype, buf = self._handlePacket() - if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: - api = unpack_one("I", substr(buf, 0, 4)) - if api == wanted: - break - # except timeout: - # logging.warning("Timeout waiting for %04x packet reply", wanted) - return buf - - def _getPspCidTable(self): - pspcidtable = 0 - save = self.pcontext - self.pcontext = self.kernelcontext # this procedure is kernel context only - self._sendDbgKdGetVersion() - buf = self._waitStateManipulate(DbgKdGetVersionApi) - pddata = unpack_one("I", substr(buf, 48, 4)) - if pddata: - # print("Pointer to debugger data struct is at %08x\n", pddata - ddata = self._readDword(pddata) - if ddata != "failed": - # print("debugger data struct is at %08x\n", ddata - pspcidtable = self._readDword(ddata + 88) - if pspcidtable != "failed": - # print("PspCidTable is %08x\n", $pspcidtable - self.pcontext = save - return pspcidtable - self.pcontext = save - return 0 +import debug_context +import debug_connection +import server def _create_fifos(named_pipe): @@ -806,6 +71,10 @@ def main(args): log_level = logging.DEBUG if args.verbose else logging.INFO logging.basicConfig(level=log_level) + context = debug_context.DebugContext() + if args.serve: + return server.serve(args.host, args.port, context) + endpoint = None if args.named_pipe: if args.create_fifo: @@ -816,9 +85,10 @@ def main(args): else: raise RuntimeError(f"No supported transport method selected.") - context = DebugContext() - context.connect(endpoint) + connection = debug_connection.DebugConnection(endpoint) + connection.connect() + context.set_connection(connection) context.run() return 0 @@ -833,13 +103,19 @@ def _parse_args(): "-p", "--port", type=int, - help="The TCP server port used by qemu." + help="The TCP server port used by qemu.", ) parser.add_argument( "--host", help="The IP address of the host.", - default="localhost" + default="0.0.0.0", + ) + + parser.add_argument( + "--serve", + help="Act as a server (e.g., with qemu `-serial tcp::`)", + action="store_true", ) parser.add_argument( From 6cedc4cd0e7080cd44c116615b6373b4799f0846 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 11:12:54 -0700 Subject: [PATCH 17/30] Factors packet reading into DebugConnection. --- debug_connection.py | 100 ++++++++++++++++++++++++----------- debug_context.py | 124 ++++++++++++++++---------------------------- kd_packet.py | 71 +++++++++++++++++++++++++ server.py | 2 +- 4 files changed, 187 insertions(+), 110 deletions(-) create mode 100644 kd_packet.py diff --git a/debug_connection.py b/debug_connection.py index a3489f5..8e85a73 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -4,6 +4,10 @@ import os import pathlib import socket +import struct + +from constants import PACKET_LEADER, CONTROL_PACKET_LEADER, PACKET_TRAILER +import kd_packet class DebugConnection: @@ -72,46 +76,61 @@ def require_fifo_exists(fifo_path): def disconnect(self): pass - 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") + def _read_packet_leader(self) -> (int, bytearray): + """Reads from the connection until a valid packet leader is found. - return bytes_read + 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] - 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) + 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] - 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 + return packet_leader, discarded_bytes - 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) + 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: - # if False: - # if serial: - # (count, buf) = serial.read(1) - # else: - # # FH.blocking(1) - # count = client.sysread(buf, 1) - # if count == 0: - # die("eof") - # # print("client.read count %x\n", ord(buf) - # # FH.blocking(0) buf = self._recv(wanted - total) - count = len(buf) if count: total += count @@ -127,3 +146,26 @@ def write(self, buffer): while len(buffer): written = self._send(buffer) buffer = buffer[written:] + + 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) diff --git a/debug_context.py b/debug_context.py index 0a7d215..1f90588 100644 --- a/debug_context.py +++ b/debug_context.py @@ -49,6 +49,7 @@ from constants import * from debug_connection import DebugConnection +import kd_packet from util import * from windpl_extra import logical2physical @@ -80,10 +81,10 @@ def __init__(self) -> None: self.controlspace = 0 self.controlspacesent = False - self.client = None + self.connection = None def set_connection(self, connection: DebugConnection) -> None: - self.client = connection + self.connection = connection def run(self): logging.info("Waiting for target device...") @@ -103,10 +104,29 @@ def run(self): @property def elapsed_time_ms(self): - return time.perf_counter() - self.start_time + return int((time.perf_counter() - self.start_time) * 1000) + + def _receive_packet(self) -> kd_packet.KDPacket: + """Receives one KD packet from the connection, discarding partial data.""" + now = self.elapsed_time_ms + packet, discarded_bytes = self.connection.read_packet() + if discarded_bytes: + logging.debug( + "[%d] Discarded non-KD packet bytes %s", + now, + hexformat(discarded_bytes), + ) + if packet.needs_ack: + self._sendAck() + return packet def _handlePacket(self): - ptype, buf = self._getPacket() + """Processes one packet from the connection.""" + packet = self._receive_packet() + self._log_packet(packet, self.elapsed_time_ms) + + ptype = packet.packet_type + buf = packet.payload if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: self._handleStateManipulate(buf) @@ -125,83 +145,27 @@ def _handlePacket(self): return ptype, buf - def _read_until_packet_leader(self): - buf = self.client.read(4) - packet_signature = unpack_one("I", buf) - - discarded_bytes = [] - now = self.elapsed_time_ms - - while packet_signature not in (PACKET_LEADER, CONTROL_PACKET_LEADER): - discarded_bytes.append(buf[0]) - buf = buf[1:] + self.client.read(1) - packet_signature = unpack_one("I", buf) - - if discarded_bytes: - logging.debug( - "[%d] Discarded non-KD packet bytes %s", - now, - hexformat(discarded_bytes), - ) - - return packet_signature - - def _getPacket(self): - packet_type = None - payload = bytearray([]) - packet_signature = self._read_until_packet_leader() - + def _log_packet(self, packet: kd_packet.KDPacket, read_time: int): logging.debug( "[%d] Got packet leader: %08x (%s)", - int(self.elapsed_time_ms * 1000), - packet_signature, - "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", + read_time, + packet.packet_leader, + packet.packet_group_name, ) + logging.debug( + "> Packet type: %d (%s)", packet.packet_type, packet.packet_type_name + ) + if packet.packet_type_name == "": + logging.critical("!! Unexpected packet type %04x", packet.packet_type) + logging.debug("> Packet ID: %08x", packet.packet_id) + logging.debug("> Data size: %d", len(packet.payload)) + logging.debug("> Checksum: %08x", packet.expected_checksum) - buf = self.client.read(2) - packet_type = unpack_one("H", buf) - packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") - logging.debug("> Packet type: %d (%s)", packet_type, packet_type_name) - if packet_type_name == "": - logging.critical("!! Unexpected packet type %04x", packet_type) - - buf = self.client.read(2) - data_size = unpack_one("H", buf) - - buf = self.client.read(4) - packet_id = unpack_one("I", buf) - self.nextpid = packet_id - - buf = self.client.read(4) - expected_checksum = unpack_one("I", buf) - - logging.debug("> Packet ID: %08x", packet_id) - logging.debug("> Data size: %d", data_size) - logging.debug("> Checksum: %08x", expected_checksum) - - if data_size: - payload = self.client.read(data_size) - - payload_checksum = generate_checksum(payload) - if payload_checksum != expected_checksum: + if packet.actual_checksum != packet.expected_checksum: raise Exception( - f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" + f"!! Checksum invalid. Expected {packet.expected_checksum} but calculated {packet.actual_checksum}" ) - # send ack if it's a non-control packet - if packet_signature == PACKET_LEADER: - # packet trailer - # logging.debug("Reading trailer...") - trail = self.client.read(1) - # logging.debug("Trailer: %x", trail[0]) - if trail[0] == PACKET_TRAILER: - # logging.debug("sending Ack") - self._sendAck() - else: - raise Exception("Invalid packet trailer 0x%x" % trail[0]) - - return packet_type, payload - def _handleDebugIO(self, buf): # pylint: disable = no-self-use apiNumber = unpack_one("I", substr(buf, 0, 4)) if apiNumber == DbgKdPrintStringApi: @@ -290,16 +254,16 @@ def _sendAck(self): 0, ) # logging.debug("Ack: %s", hexformat(ack_packet)) - self.client.write(ack_packet) + self.connection.write(ack_packet) def _sendReset(self): reset_packet = pack( - "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x00008080, 0 + "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x0000BEEF, 0 ) logging.debug("Sending reset packet\n\n") # print(hexformat(rst) - self.client.write(reset_packet) + self.connection.write(reset_packet) def _getContext(self): context = {} @@ -478,9 +442,9 @@ def _sendManipulateStatePacket(self, d): hexformat(header), hexformat(d), ) - self.client.write(header) - self.client.write(d) - self.client.write(bytes([PACKET_TRAILER])) + self.connection.write(header) + self.connection.write(d) + self.connection.write(bytes([PACKET_TRAILER])) def _sendDbgKdContinue2(self): logging.debug("< Sending DbgKdContinueApi2 packet") diff --git a/kd_packet.py b/kd_packet.py new file mode 100644 index 0000000..87c08e5 --- /dev/null +++ b/kd_packet.py @@ -0,0 +1,71 @@ +"""Models a Kernel Debug packet.""" + +# 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. + +import constants +import util + + +class KDPacket: + def __init__( + self, packet_leader, packet_type, packet_id, expected_checksum, payload + ): + self.packet_leader = packet_leader + self.packet_type = packet_type + self.packet_id = packet_id + self.expected_checksum = expected_checksum + self.payload = payload + + self.actual_checksum = util.generate_checksum(payload) + + @property + def packet_group_name(self) -> str: + if self.packet_leader == constants.PACKET_LEADER: + return "Packet" + return "ControlPacket" + + @property + def packet_type_name(self) -> str: + return constants.PACKET_TYPE_TABLE.get(self.packet_type, "") + + @property + def needs_ack(self) -> bool: + return self.packet_leader == constants.PACKET_LEADER diff --git a/server.py b/server.py index 69f56c2..d51ad27 100644 --- a/server.py +++ b/server.py @@ -13,7 +13,7 @@ def setup(self) -> None: client = debug_connection.DebugConnection(self.client_address) client.handle_socket(self.connection) - self.debugger.client = client + self.debugger.connection = client def finish(self) -> None: super(_DebuggeeHandler, self).finish() From 2fdcfc2bd9c76816033c017bcc96208bbf252fc6 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 11:39:12 -0700 Subject: [PATCH 18/30] Removes duplication between sniffer and debugger. --- debug_connection.py | 38 ++++++------- debug_context.py | 15 +---- kd_packet.py | 11 ++++ sniffer.py | 130 +++++++++++++------------------------------- 4 files changed, 70 insertions(+), 124 deletions(-) diff --git a/debug_connection.py b/debug_connection.py index 8e85a73..122eda2 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -76,25 +76,6 @@ def require_fifo_exists(fifo_path): def disconnect(self): pass - 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 read_packet(self) -> (kd_packet.KDPacket, bytearray): """Reads a single KD packet from the connection.""" packet_leader, discarded_bytes = self._read_packet_leader() @@ -147,6 +128,25 @@ def write(self, 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) diff --git a/debug_context.py b/debug_context.py index 1f90588..25f3f7e 100644 --- a/debug_context.py +++ b/debug_context.py @@ -146,21 +146,10 @@ def _handlePacket(self): return ptype, buf def _log_packet(self, packet: kd_packet.KDPacket, read_time: int): - logging.debug( - "[%d] Got packet leader: %08x (%s)", - read_time, - packet.packet_leader, - packet.packet_group_name, - ) - logging.debug( - "> Packet type: %d (%s)", packet.packet_type, packet.packet_type_name - ) + logging.debug("[%d]\n%s", read_time, "\n".join(packet.basic_log_info)) + if packet.packet_type_name == "": logging.critical("!! Unexpected packet type %04x", packet.packet_type) - logging.debug("> Packet ID: %08x", packet.packet_id) - logging.debug("> Data size: %d", len(packet.payload)) - logging.debug("> Checksum: %08x", packet.expected_checksum) - if packet.actual_checksum != packet.expected_checksum: raise Exception( f"!! Checksum invalid. Expected {packet.expected_checksum} but calculated {packet.actual_checksum}" diff --git a/kd_packet.py b/kd_packet.py index 87c08e5..2946fd1 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -69,3 +69,14 @@ def packet_type_name(self) -> str: @property def needs_ack(self) -> bool: return self.packet_leader == constants.PACKET_LEADER + + @property + def basic_log_info(self) -> [str]: + return [ + "Got packet leader: %08x (%s)" + % (self.packet_leader, self.packet_group_name), + " Type: %d (%s)" % (self.packet_type, self.packet_type_name), + " ID: %08x" % self.packet_id, + " Data size: %d" % len(self.payload), + " Checksum: %08x" % self.expected_checksum, + ] diff --git a/sniffer.py b/sniffer.py index 50f29aa..7d88365 100755 --- a/sniffer.py +++ b/sniffer.py @@ -16,47 +16,46 @@ import threading import time +import debug_connection +import kd_packet from constants import * from util import * -class _KDPassthroughSniffer: +class TeeConnection: + """Models an interceptor between two connections.""" + + def __init__(self, read_connection, write_connection): + self._read_connection = read_connection + self._write_connection = write_connection + + def recv(self, max_bytes): + """Reads up to `max_bytes`, writes them to the write_connection and returns them.""" + buf = self._read_connection.recv(max_bytes) + if self._write_connection: + self._write_connection.sendall(buf) + return buf + + +class _KDPassthroughSniffer(debug_connection.DebugConnection): def __init__( self, name, read_connection, write_connection, logger_semaphore, start_time ): - self.name = name - self.read_connection = read_connection - self.write_connection = write_connection + super(_KDPassthroughSniffer, self).__init__(name) + + # Create a tee between the read and write connections, then use it as the + # read connection for the DebugConnection superclass. + connection = TeeConnection(read_connection, write_connection) + self.handle_socket(connection) self._logger_semaphore = logger_semaphore self._packet_log = [] - self.start_time = start_time @property def elapsed_time_ms(self): return int((time.perf_counter() - self.start_time) * 1000) - def _read(self, wanted): - """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" - total = 0 - ret = bytearray([]) - while total < wanted: - buf = self.read_connection.recv(wanted - total) - count = len(buf) - if count: - total += count - ret += buf - - return ret - - def _read_and_passthrough(self, wanted): - ret = self._read(wanted) - if self.write_connection: - # TODO: Handle exception when the target socket is closed. - self.write_connection.sendall(ret) - return ret - def _log(self, message, *args): """Buffers log messages until a packet is fully processed so the messages can be sent to the logger thread.""" self._packet_log.append(message % args) @@ -69,85 +68,32 @@ def _flush_log(self): self._packet_log = [] - def _read_until_packet_leader(self): - buf = self._read_and_passthrough(4) - packet_signature = unpack_one("I", buf) - - discarded_bytes = [] - now = self.elapsed_time_ms - - while packet_signature not in (PACKET_LEADER, CONTROL_PACKET_LEADER): - discarded_bytes.append(buf[0]) - buf = buf[1:] + self._read_and_passthrough(1) - packet_signature = unpack_one("I", buf) + def read_packet(self) -> (kd_packet.KDPacket, bytearray): + """Reads a single KD packet from the connection and logs it.""" + packet, discarded_bytes = super(_KDPassthroughSniffer, self).read_packet() if discarded_bytes: self._log( "[%s] @ %d: Discarded non-KD packet bytes %s", - self.name, - now, + self.endpoint, + self.elapsed_time_ms, hexformat(discarded_bytes), ) - self._flush_log() - - return packet_signature - - def read_packet(self): - payload = bytearray([]) - - packet_signature = self._read_until_packet_leader() - self._log("[%s] @ %d", self.name, self.elapsed_time_ms) - self._log( - "Packet leader: %08x (%s)", - packet_signature, - "Packet" if packet_signature == PACKET_LEADER else "ControlPacket", - ) - - buf = self._read_and_passthrough(2) - packet_type = unpack_one("H", buf) - packet_type_name = PACKET_TYPE_TABLE.get(packet_type, "") - self._log("> Packet type: %d (%s)", packet_type, packet_type_name) - if packet_type_name == "": - self._log("!! Unexpected packet type %04x", packet_type) - - buf = self._read_and_passthrough(2) - data_size = unpack_one("H", buf) - - buf = self._read_and_passthrough(4) - packet_id = unpack_one("I", buf) - - buf = self._read_and_passthrough(4) - expected_checksum = unpack_one("I", buf) - - self._log("> Packet ID: %08x", packet_id) - self._log("> Data size: %d", data_size) - self._log("> Checksum: %08x", expected_checksum) + self._log_packet(packet) + self._flush_log() - if data_size: - payload = self._read_and_passthrough(data_size) + return packet, discarded_bytes - payload_checksum = generate_checksum(payload) - if payload_checksum != expected_checksum: - raise Exception( - f"!! Checksum invalid. Expected {expected_checksum} but calculated {payload_checksum}" - ) + def _log_packet(self, packet: kd_packet.KDPacket) -> None: + """Logs information about a KDPacket.""" - # send ack if it's a non-control packet - if packet_signature == PACKET_LEADER: - # packet trailer - # self._log("Reading trailer...") - trail = self._read_and_passthrough(1) - # self._log("Trailer: %x", trail[0]) - if trail[0] != PACKET_TRAILER: - raise Exception("Invalid packet trailer 0x%x" % trail[0]) + self._log("[%s] @ %d", self.endpoint, self.elapsed_time_ms) + self._log("\n".join(packet.basic_log_info)) - self._log_packet(packet_type, payload) - - self._flush_log() - return True + packet_type = packet.packet_type + payload = packet.payload - def _log_packet(self, packet_type, payload): if packet_type == PACKET_TYPE_KD_STATE_MANIPULATE: self._log_state_manipulate(payload) elif packet_type == PACKET_TYPE_KD_STATE_CHANGE64: From 13904a7317dd9c0991abfe8576b0210c282b380e Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 12:25:48 -0700 Subject: [PATCH 19/30] Adds detailed debug logging. --- kd_packet.py | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++ sniffer.py | 128 +----------------------------------------- 2 files changed, 154 insertions(+), 127 deletions(-) diff --git a/kd_packet.py b/kd_packet.py index 2946fd1..0cfe978 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -40,6 +40,8 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +import struct + import constants import util @@ -80,3 +82,154 @@ def basic_log_info(self) -> [str]: " Data size: %d" % len(self.payload), " Checksum: %08x" % self.expected_checksum, ] + + def get_detailed_log(self) -> [str]: + """Returns detailed logging information about this packet.""" + ret = self.basic_log_info + + if self.packet_type == constants.PACKET_TYPE_KD_STATE_MANIPULATE: + ret.append("") + ret.extend(self._log_state_manipulate()) + elif self.packet_type == constants.PACKET_TYPE_KD_STATE_CHANGE64: + ret.append("") + ret.extend(self._log_state_change64()) + elif self.payload: + ret.append("\nPayload:\n%s" % util.hexformat(self.payload)) + + return ret + + def _log_state_manipulate(self) -> []: + apiNumber, processor_level, processor, return_status = struct.unpack( + "IHHI", self.payload[:12] + ) + + ret = [ + "State Manipulate: %08x (%s)" + % (apiNumber, constants.STATE_MANIPULATE_TABLE.get(apiNumber, "")), + "Processor level: %04x" % processor_level, + "Processor: %04x" % processor, + "Return status: %08x" % return_status, + ] + + # self._log(hexformat(substr(payload, 0, 16))) + + if apiNumber == constants.DbgKdWriteBreakPointApi: + bp, handle = struct.unpack("II", self.payload[16:24]) + ret.append("Breakpoint %d set at 0x%08x" % (handle, bp)) + + elif apiNumber == constants.DbgKdRestoreBreakPointApi: + handle = struct.unpack("I", self.payload[16:20])[0] + ret.append("Breakpoint %d (0x%08x) cleared" % (handle, handle)) + + elif apiNumber == constants.DbgKdGetVersionApi: + ret.extend(self._log_version(self.payload[16:])) + + elif apiNumber == constants.DbgKdReadVirtualMemoryApi: + vmem = self.payload[56:] + ret.append("VMEM:\n%s" % util.hexasc(vmem)) + + elif apiNumber == constants.DbgKdReadPhysicalMemoryApi: + pmem = self.payload[56:] + ret.append("PMEM:\n%s" % util.hexasc(pmem)) + + elif apiNumber == constants.DbgKdReadControlSpaceApi: + controlspace = self.payload[56:] + ret.append("CNTL: %s" % util.hexformat(controlspace)) + + else: + ret.append("UNKN: %s" % util.hexasc(self.payload)) + + ret.extend(["\nRaw payload:\n", util.hexformat(self.payload)]) + return ret + + def _log_version(self, version_payload) -> [str]: + assert len(version_payload) == 40 + ( + major_version, + minor_version, + protocol_version, + kd_secondary_version, + flags, + machine_type, + max_packet_type, + max_state_change, + max_manipulate, + simulation, + _unused, + kern_base, + ps_loaded_module_list, + debugger_data_list, + ) = struct.unpack("HHBBHHBBBBHQQQ", version_payload) + + return [ + "Version: %d.%d" % (major_version, minor_version), + "Protocol version: %d" % protocol_version, + "KD secondary version: %d" % kd_secondary_version, + "Flags: 0x%04x" % flags, + "Machine type: %d (0x%04x)" % (machine_type, machine_type), + "Max packet type: %d" % max_packet_type, + "Max state change: %d" % max_state_change, + "Max manipulate: %d" % max_manipulate, + "Simulation: 0x%02x" % simulation, + "Kernel base: %x" % kern_base, + "PS loaded module list: %x" % ps_loaded_module_list, + "Debugger data list: %x" % debugger_data_list, + ] + + def _log_state_change64(self) -> [str]: + ( + new_state, + processor_level, + processor, + num_processors, + thread, + program_counter, + ) = struct.unpack("IHHIQQ", self.payload[:32]) + + ret = [ + "State change: %08x (%s)" + % (new_state, constants.STATE_CHANGE_TABLE.get(new_state, "")), + "Processor level: %04x" % processor_level, + "Processor: %04x" % processor, + "Num processors: %d" % num_processors, + "Thread: %16x" % thread, + "Program counter: %16x" % program_counter, + ] + + # self._log(hexformat(payload)) + + if new_state == constants.DbgKdExceptionStateChange: + # DBGKM_EXCEPTION64 + ex = self.payload[34:] + + code, flags, record, address, parameters = struct.unpack( + "IIQQI", self.payload[:28] + ) + + if code in constants.STATE_CHANGE_EXCEPTIONS: + ret.append("*** %s " % constants.STATE_CHANGE_EXCEPTIONS[code]) + else: + ret.append("*** Exception %08x " % code) + + ret.extend( + [ + "at %08x\n" % address, + "Exception flags = %08x" % flags, + "Exception record = %08x" % record, + "Exception address = %08x" % address, + "Number parameters = %08x" % parameters, + ] + ) + + elif new_state == constants.DbgKdLoadSymbolsStateChange: + ret.extend(self._log_load_symbols_state_change()) + # DBGKD_LOAD_SYMBOLS64 + + filename = self.payload[0x3B8:-1] + filename = filename.decode("utf-8") + ret.append("Load Symbols for '%s'" % filename) + + ret.append("\n\nRaw payload:\n") + ret.append(util.hexformat(self.payload)) + + return ret diff --git a/sniffer.py b/sniffer.py index 7d88365..a813bdc 100755 --- a/sniffer.py +++ b/sniffer.py @@ -89,133 +89,7 @@ def _log_packet(self, packet: kd_packet.KDPacket) -> None: """Logs information about a KDPacket.""" self._log("[%s] @ %d", self.endpoint, self.elapsed_time_ms) - self._log("\n".join(packet.basic_log_info)) - - packet_type = packet.packet_type - payload = packet.payload - - if packet_type == PACKET_TYPE_KD_STATE_MANIPULATE: - self._log_state_manipulate(payload) - elif packet_type == PACKET_TYPE_KD_STATE_CHANGE64: - self._log_state_change64(payload) - elif payload: - self._log("%s", hexformat(payload)) - - def _log_state_manipulate(self, payload): - apiNumber = unpack_one("I", substr(payload, 0, 4)) - self._log( - "State Manipulate: %08x (%s)", - apiNumber, - STATE_MANIPULATE_TABLE.get(apiNumber, ""), - ) - - processor_level = unpack_one("H", substr(payload, 4, 2)) - processor = unpack_one("H", substr(payload, 6, 2)) - return_status = unpack_one("I", substr(payload, 8, 4)) - - # self._log(hexformat(substr(payload, 0, 16))) - self._log(hexformat(payload)) - - if apiNumber == DbgKdWriteBreakPointApi: - bp = "%08x" % unpack_one("I", substr(payload, 16, 4)) - handle = unpack_one("I", substr(payload, 20, 4)) - self._log("Breakpoint %d set at %s", handle, bp) - - elif apiNumber == DbgKdRestoreBreakPointApi: - handle = unpack_one("I", substr(payload, 16, 4)) - self._log("Breakpoint %d cleared", handle) - - elif apiNumber == DbgKdGetVersionApi: - self._log_version(payload[16:]) - - elif apiNumber == DbgKdReadVirtualMemoryApi: - vmem = substr(payload, 56) - self._log("VMEM:\n%s", hexasc(vmem)) - - elif apiNumber == DbgKdReadPhysicalMemoryApi: - pmem = substr(payload, 56) - self._log("PMEM:\n%s", hexasc(pmem)) - - elif apiNumber == DbgKdReadControlSpaceApi: - controlspace = substr(payload, 56) - self._log("CNTL: %s", hexformat(controlspace)) - - else: - self._log("UNKN: %s", hexasc(payload)) - - def _log_version(self, payload): - assert len(payload) == 40 - ( - major_version, - minor_version, - protocol_version, - kd_secondary_version, - flags, - machine_type, - max_packet_type, - max_state_change, - max_manipulate, - simulation, - _unused, - kern_base, - ps_loaded_module_list, - debugger_data_list, - ) = struct.unpack("HHBBHHBBBBHQQQ", payload) - self._log("Version: %d.%d", major_version, minor_version) - self._log("Protocol version: %d", protocol_version) - self._log("KD secondary version: %d", kd_secondary_version) - self._log("Flags: 0x%04x", flags) - self._log("Machine type: %d (0x%04x)", machine_type, machine_type) - self._log("Max packet type: %d", max_packet_type) - self._log("Max state change: %d", max_state_change) - self._log("Max manipulate: %d", max_manipulate) - self._log("Simulation: 0x%02x", simulation) - self._log("Kernel base: %x", kern_base) - self._log("PS loaded module list: %x", ps_loaded_module_list) - self._log("Debugger data list: %x", debugger_data_list) - - def _log_state_change64(self, payload): - new_state = unpack_one("I", substr(payload, 0, 4)) - self._log( - "State Change: New state: %08x (%s)", - new_state, - STATE_CHANGE_TABLE.get(new_state, ""), - ) - # self._log(hexformat(payload)) - - if new_state == DbgKdExceptionStateChange: - # DBGKM_EXCEPTION64 - ex = substr(payload, 32) - - code = unpack_one("I", substr(ex, 0, 4)) - flags = unpack_one("I", substr(ex, 4, 4)) - record = unpack_one("I", substr(ex, 8, 4)) - address = unpack_one("I", substr(ex, 16, 4)) - parameters = unpack_one("I", substr(ex, 24, 4)) - - if code in STATE_CHANGE_EXCEPTIONS: - self._log("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) - else: - self._log("*** Exception %08x ", code) - - self._log("at %08x\n", address) - - self._log("Exception flags = %08x", flags) - self._log("Exception record = %08x", record) - self._log("Exception address = %08x", address) - self._log("Number parameters = %08x", parameters) - - self.running = False - - # my @v = getVersionInfo() - # version = v[0] - # $kernelbase = v[2] - elif new_state == DbgKdLoadSymbolsStateChange: - # DBGKD_LOAD_SYMBOLS64 - - filename = payload[0x3B8:-1] - filename = filename.decode("utf-8") - self._log("Load Symbols for '%s'", filename) + self._log("\n".join(packet.get_detailed_log())) class _DebuggerConnection: From 0249732fa0e998ee07ad28da19c7f488c064eb8d Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 13:00:56 -0700 Subject: [PATCH 20/30] Additional logging for loadsymbols. --- kd_packet.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/kd_packet.py b/kd_packet.py index 0cfe978..4c30c7e 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -222,14 +222,31 @@ def _log_state_change64(self) -> [str]: ) elif new_state == constants.DbgKdLoadSymbolsStateChange: - ret.extend(self._log_load_symbols_state_change()) - # DBGKD_LOAD_SYMBOLS64 + ret.extend(self._log_load_symbols_state_change(self.payload[32:])) - filename = self.payload[0x3B8:-1] - filename = filename.decode("utf-8") - ret.append("Load Symbols for '%s'" % filename) + return ret + + def _log_load_symbols_state_change(self, data): + ret = [] - ret.append("\n\nRaw payload:\n") - ret.append(util.hexformat(self.payload)) + pathname_length, dll_base, process_id, checksum, image_size, unload = struct.unpack( + "IQQIII", data[:36] + ) + ret.extend([ + "Path name length: %d" % pathname_length, + "DLL Base addr: %016x" % dll_base, + "Process ID: %016x" % process_id, + "Checksum: %d (%08x)" % (checksum, checksum), + "Image size: %d" % image_size, + "Unload?: %d" % unload + ]) + + filename = self.payload[0x3B8:-1] # Ignore the null terminator + filename = filename.decode("utf-8") + ret.append("Load Symbols for '%s'" % filename) + ret.append("Remaining payload:\n%s" % util.hexformat(data[32:])) + + ret.append("\n\nRaw payload:\n") + ret.append(util.hexformat(self.payload)) return ret From 360b6bc6b913c2c70127309b2ba2bad1f261efe3 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 13:13:22 -0700 Subject: [PATCH 21/30] Unifies logging. --- debug_context.py | 53 ++---------------------------------------------- kd_packet.py | 5 +---- 2 files changed, 3 insertions(+), 55 deletions(-) diff --git a/debug_context.py b/debug_context.py index 25f3f7e..39392b3 100644 --- a/debug_context.py +++ b/debug_context.py @@ -116,6 +116,7 @@ def _receive_packet(self) -> kd_packet.KDPacket: now, hexformat(discarded_bytes), ) + self.nextpid = packet.packet_id if packet.needs_ack: self._sendAck() return packet @@ -146,7 +147,7 @@ def _handlePacket(self): return ptype, buf def _log_packet(self, packet: kd_packet.KDPacket, read_time: int): - logging.debug("[%d]\n%s", read_time, "\n".join(packet.basic_log_info)) + logging.debug("[%d]\n%s", read_time, "\n".join(packet.get_detailed_log())) if packet.packet_type_name == "": logging.critical("!! Unexpected packet type %04x", packet.packet_type) @@ -164,73 +165,23 @@ def _handleDebugIO(self, buf): # pylint: disable = no-self-use def _handleStateChange(self, buf): newState = unpack_one("I", substr(buf, 0, 4)) - logging.debug("State Change: New state: %08x", newState) if newState == DbgKdExceptionStateChange: # DBGKM_EXCEPTION64 - ex = substr(buf, 32) - - code = unpack_one("I", substr(ex, 0, 4)) - flags = unpack_one("I", substr(ex, 4, 4)) - record = unpack_one("I", substr(ex, 8, 4)) - address = unpack_one("I", substr(ex, 16, 4)) - parameters = unpack_one("I", substr(ex, 24, 4)) - - if code in STATE_CHANGE_EXCEPTIONS: - logging.warning("*** %s ", STATE_CHANGE_EXCEPTIONS[code]) - else: - logging.warning("*** Exception %08x ", code) - - logging.warning("at %08x\n", address) - - logging.warning("Exception flags = %08x", flags) - logging.warning("Exception record = %08x", record) - logging.warning("Exception address = %08x", address) - logging.warning("Number parameters = %08x", parameters) - self.running = False - - # my @v = getVersionInfo() - # version = v[0] - # $kernelbase = v[2] elif newState == DbgKdLoadSymbolsStateChange: # DBGKD_LOAD_SYMBOLS64 - - filename = buf[0x3B8:-1] - filename = filename.decode("utf-8") - logging.debug("Load Symbols for '%s'", filename) - # nothing to do... self._sendDbgKdContinue2() def _handleStateManipulate(self, buf): - apiNumber = unpack_one("I", substr(buf, 0, 4)) - logging.debug("State Manipulate: %08x", apiNumber) if apiNumber == DbgKdWriteBreakPointApi: bp = "%08x" % unpack_one("I", substr(buf, 16, 4)) handle = unpack_one("I", substr(buf, 20, 4)) - logging.debug("Breakpoint %d set at %s", handle, bp) self.breakpoints[bp] = handle - elif apiNumber == DbgKdRestoreBreakPointApi: - handle = unpack_one("I", substr(buf, 16, 4)) - logging.debug("Breakpoint %d cleared", handle) - elif apiNumber == DbgKdGetVersionApi: - version = substr(buf, 16) - logging.debug("VERS:\n%s", hexformat(version)) - elif apiNumber == DbgKdReadVirtualMemoryApi: - vmem = substr(buf, 56) - logging.debug("VMEM:\n%s", hexasc(vmem)) - elif apiNumber == DbgKdReadPhysicalMemoryApi: - pmem = substr(buf, 56) - logging.debug("PMEM:\n%s", hexasc(pmem)) - elif apiNumber == DbgKdReadControlSpaceApi: - controlspace = substr(buf, 56) - logging.debug("CNTL:\n%s", hexformat(controlspace)) - else: - logging.debug("UNKN:\n%s", hexasc(buf)) def _sendAck(self): logging.debug("Acking with next PID: %08x", self.nextpid) diff --git a/kd_packet.py b/kd_packet.py index 4c30c7e..b8aa193 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -244,9 +244,6 @@ def _log_load_symbols_state_change(self, data): filename = self.payload[0x3B8:-1] # Ignore the null terminator filename = filename.decode("utf-8") ret.append("Load Symbols for '%s'" % filename) - ret.append("Remaining payload:\n%s" % util.hexformat(data[32:])) - - ret.append("\n\nRaw payload:\n") - ret.append(util.hexformat(self.payload)) + #ret.append("Remaining payload:\n%s" % util.hexformat(data[32:])) return ret From b4055a1892ca9e9f30d2b46bca544108be76906f Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 14:47:35 -0700 Subject: [PATCH 22/30] Implements sync behavior. --- constants.py | 3 + debug_connection.py | 6 +- debug_context.py | 143 +++++++++++++++++++++++++++++++------------- kd_packet.py | 55 +++++++++++++---- 4 files changed, 148 insertions(+), 59 deletions(-) diff --git a/constants.py b/constants.py index 8c60e25..7c12d3a 100644 --- a/constants.py +++ b/constants.py @@ -40,6 +40,9 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +INITIAL_PACKET_ID = 0x80800000 +SYNC_PACKET_ID = 0x00000800 + PACKET_LEADER = 0x30303030 CONTROL_PACKET_LEADER = 0x69696969 PACKET_TRAILER = 0xAA diff --git a/debug_connection.py b/debug_connection.py index 122eda2..1b71d4b 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -63,12 +63,12 @@ def require_fifo_exists(fifo_path): require_fifo_exists(write_fifo) require_fifo_exists(read_fifo) - flags = os.O_RDWR + flags = 0 if os.name == "nt": flags |= os.O_BINARY - self._connection_write = os.open(write_fifo, flags) - self._connection_read = os.open(read_fifo, flags) + 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 diff --git a/debug_context.py b/debug_context.py index 39392b3..9776f0b 100644 --- a/debug_context.py +++ b/debug_context.py @@ -42,6 +42,7 @@ # pylint: disable = invalid-name, missing-function-docstring, too-many-instance-attributes, fixme +import itertools import logging from struct import pack # pylint: disable = no-name-in-module import sys @@ -74,7 +75,7 @@ def __init__(self) -> None: self.processcontext["eprocess"] = 0 self.processcontext["dtb"] = 0 self.pcontext = self.kernelcontext - self.nextpid = 1 + self.nextpid = INITIAL_PACKET_ID self.breakpoints = {} self.curbp = 1 @@ -88,7 +89,7 @@ def set_connection(self, connection: DebugConnection) -> None: def run(self): logging.info("Waiting for target device...") - self._sendReset() + self._send_reset() packets = 0 while True: try: @@ -97,8 +98,8 @@ def run(self): logging.info("Client connection closed.") break - logging.debug("Processed %d packets\n", packets) packets += 1 + logging.debug("Processed %d packets\n", packets) # FIXME: windpl_loop.py @@ -117,14 +118,14 @@ def _receive_packet(self) -> kd_packet.KDPacket: hexformat(discarded_bytes), ) self.nextpid = packet.packet_id - if packet.needs_ack: - self._sendAck() return packet def _handlePacket(self): """Processes one packet from the connection.""" packet = self._receive_packet() self._log_packet(packet, self.elapsed_time_ms) + if packet.needs_ack: + self._send_ack(packet.packet_id) ptype = packet.packet_type buf = packet.payload @@ -140,7 +141,7 @@ def _handlePacket(self): # If not, dump the read buffer and start over. pass elif ptype == PACKET_TYPE_KD_RESEND: - logging.warning("!! Ignoring resend request") + self._resend_packet() elif len(buf): logging.debug("Ignoring packet") @@ -183,27 +184,67 @@ def _handleStateManipulate(self, buf): handle = unpack_one("I", substr(buf, 20, 4)) self.breakpoints[bp] = handle - def _sendAck(self): - logging.debug("Acking with next PID: %08x", self.nextpid) + def _send_ack(self, packet_id): + ack_packet_id = packet_id & ~SYNC_PACKET_ID + + logging.debug( + "# Acking packet ID %08x with %08x%s", + packet_id, + ack_packet_id, + " [SYNC]" if packet_id != ack_packet_id else "", + ) + ack_packet = pack( "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_ACKNOWLEDGE, 0, - self.nextpid, + ack_packet_id, 0, ) - # logging.debug("Ack: %s", hexformat(ack_packet)) - self.connection.write(ack_packet) - def _sendReset(self): + self._send_packet(ack_packet) + + def _send_packet(self, packet_data): + if type(packet_data) is list: + packet_data = bytearray(itertools.chain.from_iterable(packet_data)) + self.last_packet = packet_data + self.connection.write(self.last_packet) + + def _resend_packet(self): + if self.last_packet: + packet = kd_packet.KDPacket.parse(self.last_packet) + logging.debug("Resending last packet: %s", "\n".join(packet.basic_log_info)) + self._send_packet(self.last_packet) + else: + logging.critical("Resend requested but no packets have been sent!") + + def _send_reset(self): reset_packet = pack( - "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, 0x0000BEEF, 0 + "IHHII", + CONTROL_PACKET_LEADER, + PACKET_TYPE_KD_RESET, + 0, + INITIAL_PACKET_ID | SYNC_PACKET_ID, + 0, ) logging.debug("Sending reset packet\n\n") # print(hexformat(rst) - self.connection.write(reset_packet) + self._send_packet(reset_packet) + + def _send_resend(self): + resend_packet = pack( + "IHHII", + CONTROL_PACKET_LEADER, + PACKET_TYPE_KD_RESEND, + 0, + INITIAL_PACKET_ID, + 0, + ) + logging.debug("Sending resend packet\n\n") + # print(hexformat(rst) + self.connection.write(resend_packet) def _getContext(self): context = {} @@ -367,48 +408,64 @@ def _printVersionData(self): # buf = None # FIXME: =~ s/\x00//g; # ok not really Unicode to Ascii # return buf - def _sendManipulateStatePacket(self, d): + def _sendManipulateStatePacket(self, payload): + pid = self.nextpid header = pack( "IHHII", PACKET_LEADER, PACKET_TYPE_KD_STATE_MANIPULATE, - len(d), - self.nextpid, - generate_checksum(d), + len(payload), + pid, + generate_checksum(payload), ) logging.debug( - "< manipulate state:\nHeader:\n%s\nBody:\n%s", + "Sending manipulate state [%08x]:\nHeader:\n%s\nBody:\n%s", + pid, hexformat(header), - hexformat(d), + hexformat(payload), ) - self.connection.write(header) - self.connection.write(d) - self.connection.write(bytes([PACKET_TRAILER])) + + self._send_packet([header, payload, bytes([PACKET_TRAILER])]) def _sendDbgKdContinue2(self): logging.debug("< Sending DbgKdContinueApi2 packet") - d = bytearray([0] * 56) - # Manipulate state packet - d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) # API Number - # d = patch_substr(d, 4, 2, "H", 0x0001) # Processor level - # d = patch_substr(d, 6, 2, "H", 0x0001) # Processor - d = patch_substr(d, 8, 4, "I", HRESULT_STATUS_SUCCESS) # Return status - - # Continue2 subpacket - d = patch_substr(d, 12, 4, "I", HRESULT_STATUS_SUCCESS) # ContinueStatus - d = patch_substr(d, 16, 4, "I", 0x00000000) # TraceFlag - d = patch_substr(d, 20, 4, "I", 0x00000000) # DR7 - d = patch_substr(d, 24, 4, "I", 0x00000001) # CurrentSymbolStart - d = patch_substr(d, 28, 4, "I", 0x00000001) # CurrentSymbolEnd - - # substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 ); - # substr( $d, 8, 4 ) = pack( "I", 0x00010001 ); - # substr( $d, 16, 4 ) = pack( "I", 0x00010001 ); - # substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag - # substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7 - self._sendManipulateStatePacket(d) + packet = struct.pack( + "IHHIIIIII", + DbgKdContinueApi2, # API Number + 0xFFFF, # Processor level + 0x0000, # Processor + HRESULT_STATUS_SUCCESS, # Return status + HRESULT_DBG_CONTINUE, # ContinueStatus + 0x00000000, # TraceFlag + 0x00000000, # Dr7 + 0x00000001, # CurrentSymbolStart + 0x00000001, # CurrentSymbolEnd + ) + + # Pad to sizeof the union in the DBGKD_MANINPULATE_STATE64 packet. + packet += bytearray([0] * (56 - len(packet))) + # d = bytearray([0] * 56) + # + # d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) # API Number + # d = patch_substr(d, 4, 2, "H", 0xFFFF) # Processor level + # d = patch_substr(d, 6, 2, "H", 0x0000) # Processor + # d = patch_substr(d, 8, 4, "I", HRESULT_STATUS_SUCCESS) # Return status + # + # # Continue2 subpacket + # d = patch_substr(d, 12, 4, "I", HRESULT_STATUS_SUCCESS) # ContinueStatus + # d = patch_substr(d, 16, 4, "I", 0x00000000) # TraceFlag + # d = patch_substr(d, 20, 4, "I", 0x00000000) # DR7 + # d = patch_substr(d, 24, 4, "I", 0x00000001) # CurrentSymbolStart + # d = patch_substr(d, 28, 4, "I", 0x00000001) # CurrentSymbolEnd + + # # substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 ); + # # substr( $d, 8, 4 ) = pack( "I", 0x00010001 ); + # # substr( $d, 16, 4 ) = pack( "I", 0x00010001 ); + # # substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag + # # substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7 + self._sendManipulateStatePacket(packet) def _sendDbgKdGetVersion(self): diff --git a/kd_packet.py b/kd_packet.py index b8aa193..23e8e8a 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -58,6 +58,29 @@ def __init__( self.actual_checksum = util.generate_checksum(payload) + @classmethod + def parse(cls, bytes): + bytes_len = len(bytes) + assert bytes_len >= 16 + ( + packet_leader, + packet_type, + data_size, + packet_id, + expected_checksum, + ) = struct.unpack("IHHII", bytes[:16]) + + if packet_leader == constants.PACKET_LEADER: + payload = bytes[16:-1] # Discard the trailer + assert bytes_len == (data_size + 17) + elif bytes_len: + payload = bytes[16:] + assert bytes_len == (data_size + 16) + else: + payload = [] + + return cls(packet_leader, packet_type, packet_id, expected_checksum, payload) + @property def packet_group_name(self) -> str: if self.packet_leader == constants.PACKET_LEADER: @@ -75,8 +98,7 @@ def needs_ack(self) -> bool: @property def basic_log_info(self) -> [str]: return [ - "Got packet leader: %08x (%s)" - % (self.packet_leader, self.packet_group_name), + "Packet leader: %08x (%s)" % (self.packet_leader, self.packet_group_name), " Type: %d (%s)" % (self.packet_type, self.packet_type_name), " ID: %08x" % self.packet_id, " Data size: %d" % len(self.payload), @@ -229,21 +251,28 @@ def _log_state_change64(self) -> [str]: def _log_load_symbols_state_change(self, data): ret = [] - pathname_length, dll_base, process_id, checksum, image_size, unload = struct.unpack( - "IQQIII", data[:36] + ( + pathname_length, + dll_base, + process_id, + checksum, + image_size, + unload, + ) = struct.unpack("IQQIIB", data[:33]) + ret.extend( + [ + "Path name length: %d" % pathname_length, + "DLL Base addr: %016x" % dll_base, + "Process ID: %016x" % process_id, + "Checksum: %d (%08x)" % (checksum, checksum), + "Image size: %d" % image_size, + "Unload?: %d" % unload, + ] ) - ret.extend([ - "Path name length: %d" % pathname_length, - "DLL Base addr: %016x" % dll_base, - "Process ID: %016x" % process_id, - "Checksum: %d (%08x)" % (checksum, checksum), - "Image size: %d" % image_size, - "Unload?: %d" % unload - ]) filename = self.payload[0x3B8:-1] # Ignore the null terminator filename = filename.decode("utf-8") ret.append("Load Symbols for '%s'" % filename) - #ret.append("Remaining payload:\n%s" % util.hexformat(data[32:])) + # ret.append("Remaining payload:\n%s" % util.hexformat(data[32:])) return ret From 3cdc8ce4560a349d7b9a92e29e26566a058a432a Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 14:55:26 -0700 Subject: [PATCH 23/30] Implements correct packet numbering. --- debug_context.py | 59 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/debug_context.py b/debug_context.py index 9776f0b..62f6889 100644 --- a/debug_context.py +++ b/debug_context.py @@ -75,7 +75,10 @@ def __init__(self) -> None: self.processcontext["eprocess"] = 0 self.processcontext["dtb"] = 0 self.pcontext = self.kernelcontext - self.nextpid = INITIAL_PACKET_ID + + self.remote_pid = INITIAL_PACKET_ID + self.next_pid = INITIAL_PACKET_ID | SYNC_PACKET_ID + self.breakpoints = {} self.curbp = 1 @@ -117,7 +120,18 @@ def _receive_packet(self) -> kd_packet.KDPacket: now, hexformat(discarded_bytes), ) - self.nextpid = packet.packet_id + + if (packet.packet_id & ~SYNC_PACKET_ID) != self.remote_pid: + logging.warning( + "Received packet id %08x but expected %08x", + packet.packet_id, + self.remote_pid, + ) + + # TODO: Send a resend + + self.remote_pid = packet.packet_id ^ 0x01 + return packet def _handlePacket(self): @@ -194,7 +208,7 @@ def _send_ack(self, packet_id): " [SYNC]" if packet_id != ack_packet_id else "", ) - ack_packet = pack( + ack_packet = struct.pack( "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_ACKNOWLEDGE, @@ -220,7 +234,7 @@ def _resend_packet(self): logging.critical("Resend requested but no packets have been sent!") def _send_reset(self): - reset_packet = pack( + reset_packet = struct.pack( "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, @@ -228,20 +242,22 @@ def _send_reset(self): INITIAL_PACKET_ID | SYNC_PACKET_ID, 0, ) + self.next_pid = INITIAL_PACKET_ID ^ 0x01 logging.debug("Sending reset packet\n\n") # print(hexformat(rst) self._send_packet(reset_packet) def _send_resend(self): - resend_packet = pack( + resend_packet = struct.pack( "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESEND, 0, - INITIAL_PACKET_ID, + self.next_pid, 0, ) + self.next_pid ^= 0x01 logging.debug("Sending resend packet\n\n") # print(hexformat(rst) self.connection.write(resend_packet) @@ -360,8 +376,8 @@ def _getVersionInfo(self): logging.info("Module list = %08x", modlist) logging.info("Debugger data = %08x", ddata) - return (osv, pv, kernbase, modlist, ddata) - return ("0.0", 0, 0, 0, 0) + return osv, pv, kernbase, modlist, ddata + return "0.0", 0, 0, 0, 0 def _printVersionData(self): v = self._getVersionInfo() @@ -409,8 +425,9 @@ def _printVersionData(self): # return buf def _sendManipulateStatePacket(self, payload): - pid = self.nextpid - header = pack( + pid = self.next_pid + self.next_pid ^= 0x01 + header = struct.pack( "IHHII", PACKET_LEADER, PACKET_TYPE_KD_STATE_MANIPULATE, @@ -446,25 +463,7 @@ def _sendDbgKdContinue2(self): # Pad to sizeof the union in the DBGKD_MANINPULATE_STATE64 packet. packet += bytearray([0] * (56 - len(packet))) - # d = bytearray([0] * 56) - # - # d = patch_substr(d, 0, 4, "I", DbgKdContinueApi2) # API Number - # d = patch_substr(d, 4, 2, "H", 0xFFFF) # Processor level - # d = patch_substr(d, 6, 2, "H", 0x0000) # Processor - # d = patch_substr(d, 8, 4, "I", HRESULT_STATUS_SUCCESS) # Return status - # - # # Continue2 subpacket - # d = patch_substr(d, 12, 4, "I", HRESULT_STATUS_SUCCESS) # ContinueStatus - # d = patch_substr(d, 16, 4, "I", 0x00000000) # TraceFlag - # d = patch_substr(d, 20, 4, "I", 0x00000000) # DR7 - # d = patch_substr(d, 24, 4, "I", 0x00000001) # CurrentSymbolStart - # d = patch_substr(d, 28, 4, "I", 0x00000001) # CurrentSymbolEnd - - # # substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 ); - # # substr( $d, 8, 4 ) = pack( "I", 0x00010001 ); - # # substr( $d, 16, 4 ) = pack( "I", 0x00010001 ); - # # substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag - # # substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7 + self._sendManipulateStatePacket(packet) def _sendDbgKdGetVersion(self): @@ -546,7 +545,7 @@ def _sendDbgKdReadPhysicalMemory(self, addr, readlen): def _sendDbgKdWritePhysicalMemory(self, addr, data): writelen = len(data) - d = pack("III", DbgKdWritePhysicalMemoryApi, addr, writelen) + d = struct.pack("III", DbgKdWritePhysicalMemoryApi, addr, writelen) d += data self._sendManipulateStatePacket(d) From 21939c4558cb0ea294257e9fdf40a155fe7edeee Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 15:17:31 -0700 Subject: [PATCH 24/30] Improves PACKET_TYPE_KD_DEBUG_IO handling. --- debug_context.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/debug_context.py b/debug_context.py index 62f6889..ab702c0 100644 --- a/debug_context.py +++ b/debug_context.py @@ -147,7 +147,7 @@ def _handlePacket(self): if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: self._handleStateManipulate(buf) elif ptype == PACKET_TYPE_KD_DEBUG_IO: - self._handleDebugIO(buf) + self._handle_debug_io(buf) elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: self._handleStateChange(buf) elif ptype == PACKET_TYPE_KD_RESET: @@ -171,12 +171,28 @@ def _log_packet(self, packet: kd_packet.KDPacket, read_time: int): f"!! Checksum invalid. Expected {packet.expected_checksum} but calculated {packet.actual_checksum}" ) - def _handleDebugIO(self, buf): # pylint: disable = no-self-use - apiNumber = unpack_one("I", substr(buf, 0, 4)) - if apiNumber == DbgKdPrintStringApi: + def _handle_debug_io(self, buf): # pylint: disable = no-self-use + api_number, _processor_level, _processor = struct.unpack("IHH", buf[:8]) + + if api_number == DbgKdPrintStringApi: + string_len = struct.unpack("I", buf[8:12])[0] + payload = buf[16:] + if len(payload) != string_len: + logging.critical( + "Expected debug string of length %d but have %d", + string_len, + len(payload), + ) print("DbgPrint: " + substr(buf, 0x10).decode("utf-8")) + elif api_number == DbgKdGetStringApi: + prompt_len, string_len = struct.unpack("II", buf[8:16]) + logging.debug( + "Ignoring unimplemented Get String api. Prompt %d, String %d", + prompt_len, + string_len, + ) else: - logging.debug("Ignoring debug IO packet with API number %d", apiNumber) + logging.debug("Ignoring debug IO packet with API number %d", api_number) def _handleStateChange(self, buf): newState = unpack_one("I", substr(buf, 0, 4)) From 25bd5e6c0a7a6c8c6171fc6ebd22e362f07c1def Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 15:24:07 -0700 Subject: [PATCH 25/30] Pylint fixes. --- debug_connection.py | 13 +++++-------- debug_context.py | 34 ++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/debug_connection.py b/debug_connection.py index 1b71d4b..f1b55f6 100644 --- a/debug_connection.py +++ b/debug_connection.py @@ -22,8 +22,8 @@ def __init__(self, endpoint): self._send = None def connect(self): - - if type(self.endpoint) is tuple: + """Connects to the debug endpoint.""" + if isinstance(self.endpoint, tuple): self._connect_socket(self.endpoint) else: self._connect_fifo(self.endpoint) @@ -49,8 +49,8 @@ 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'." + "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(): @@ -65,7 +65,7 @@ def require_fifo_exists(fifo_path): flags = 0 if os.name == "nt": - flags |= os.O_BINARY + 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) @@ -73,9 +73,6 @@ def require_fifo_exists(fifo_path): self._recv = self._recv_fifo self._send = self._send_fifo - def disconnect(self): - pass - def read_packet(self) -> (kd_packet.KDPacket, bytearray): """Reads a single KD packet from the connection.""" packet_leader, discarded_bytes = self._read_packet_leader() diff --git a/debug_context.py b/debug_context.py index ab702c0..9a2d5ad 100644 --- a/debug_context.py +++ b/debug_context.py @@ -40,7 +40,7 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -# pylint: disable = invalid-name, missing-function-docstring, too-many-instance-attributes, fixme +# pylint: disable = invalid-name, missing-function-docstring, too-many-instance-attributes, unused-wildcard-import, fixme import itertools import logging @@ -48,10 +48,10 @@ import sys import time -from constants import * +from constants import * # pylint: disable = wildcard-import from debug_connection import DebugConnection import kd_packet -from util import * +from util import * # pylint: disable = wildcard-import from windpl_extra import logical2physical @@ -87,6 +87,8 @@ def __init__(self) -> None: self.connection = None + self.last_packet = None + def set_connection(self, connection: DebugConnection) -> None: self.connection = connection @@ -146,29 +148,41 @@ def _handlePacket(self): if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: self._handleStateManipulate(buf) + elif ptype == PACKET_TYPE_KD_DEBUG_IO: self._handle_debug_io(buf) + elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: self._handleStateChange(buf) + elif ptype == PACKET_TYPE_KD_RESET: - # TODO: check to see if this reset is in response to a reset sent by the debugger script. - # If not, dump the read buffer and start over. - pass + self.next_pid = INITIAL_PACKET_ID | SYNC_PACKET_ID + self.remote_pid = INITIAL_PACKET_ID + elif ptype == PACKET_TYPE_KD_RESEND: self._resend_packet() + elif len(buf): - logging.debug("Ignoring packet") + logging.debug( + "Ignoring packet of type %d (%s) with payload %s", + packet.packet_type, + packet.packet_type_name, + hexformat(buf), + ) return ptype, buf - def _log_packet(self, packet: kd_packet.KDPacket, read_time: int): + def _log_packet( + self, packet: kd_packet.KDPacket, read_time: int + ): # pylint: disable = no-self-use logging.debug("[%d]\n%s", read_time, "\n".join(packet.get_detailed_log())) if packet.packet_type_name == "": logging.critical("!! Unexpected packet type %04x", packet.packet_type) if packet.actual_checksum != packet.expected_checksum: raise Exception( - f"!! Checksum invalid. Expected {packet.expected_checksum} but calculated {packet.actual_checksum}" + "!! Checksum invalid. " + f"Expected {packet.expected_checksum} but calculated {packet.actual_checksum}" ) def _handle_debug_io(self, buf): # pylint: disable = no-self-use @@ -236,7 +250,7 @@ def _send_ack(self, packet_id): self._send_packet(ack_packet) def _send_packet(self, packet_data): - if type(packet_data) is list: + if isinstance(packet_data, list): packet_data = bytearray(itertools.chain.from_iterable(packet_data)) self.last_packet = packet_data self.connection.write(self.last_packet) From e4c0ff1ff9debb00226ce326ea9c5d66ab0c899c Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 15:53:59 -0700 Subject: [PATCH 26/30] Fixes post-reset packet id handling. --- debug_context.py | 12 +++++++-- kd_packet.py | 67 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/debug_context.py b/debug_context.py index 9a2d5ad..3015c54 100644 --- a/debug_context.py +++ b/debug_context.py @@ -138,6 +138,12 @@ def _receive_packet(self) -> kd_packet.KDPacket: def _handlePacket(self): """Processes one packet from the connection.""" + + # TODO: Handle ack packets properly. + # Packets that need an ack should trigger a read & discard loop until + # the ack w/ the same packet id is read (or a reset or resend is + # encountered). At that point next_pid should be ^= 0x01. + packet = self._receive_packet() self._log_packet(packet, self.elapsed_time_ms) if packet.needs_ack: @@ -156,7 +162,7 @@ def _handlePacket(self): self._handleStateChange(buf) elif ptype == PACKET_TYPE_KD_RESET: - self.next_pid = INITIAL_PACKET_ID | SYNC_PACKET_ID + self.next_pid = INITIAL_PACKET_ID self.remote_pid = INITIAL_PACKET_ID elif ptype == PACKET_TYPE_KD_RESEND: @@ -258,8 +264,10 @@ def _send_packet(self, packet_data): def _resend_packet(self): if self.last_packet: packet = kd_packet.KDPacket.parse(self.last_packet) + packet.packet_id = self.next_pid + self.next_pid ^= 1 logging.debug("Resending last packet: %s", "\n".join(packet.basic_log_info)) - self._send_packet(self.last_packet) + self._send_packet(packet.serialize()) else: logging.critical("Resend requested but no packets have been sent!") diff --git a/kd_packet.py b/kd_packet.py index 23e8e8a..468222e 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -40,6 +40,8 @@ # WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +# pylint: disable=too-many-arguments, too-many-locals + import struct import constants @@ -47,6 +49,8 @@ class KDPacket: + """Models a Kernel Debug packet.""" + def __init__( self, packet_leader, packet_type, packet_id, expected_checksum, payload ): @@ -58,9 +62,28 @@ def __init__( self.actual_checksum = util.generate_checksum(payload) + def serialize(self) -> [bytearray]: + """Serializes this packet into a byte array (with trailing marker).""" + header = struct.pack( + "IHHII", + self.packet_leader, + self.packet_type, + len(self.payload), + self.packet_id, + self.actual_checksum, + ) + + ret = [header] + if self.payload: + ret.append(self.payload) + if self.needs_ack: + ret.append(bytes([constants.PACKET_TRAILER])) + return ret + @classmethod - def parse(cls, bytes): - bytes_len = len(bytes) + def parse(cls, buffer): + """Constructs a new KDPacket from the given byte buffer.""" + bytes_len = len(buffer) assert bytes_len >= 16 ( packet_leader, @@ -68,13 +91,13 @@ def parse(cls, bytes): data_size, packet_id, expected_checksum, - ) = struct.unpack("IHHII", bytes[:16]) + ) = struct.unpack("IHHII", buffer[:16]) if packet_leader == constants.PACKET_LEADER: - payload = bytes[16:-1] # Discard the trailer + payload = buffer[16:-1] # Discard the trailer assert bytes_len == (data_size + 17) elif bytes_len: - payload = bytes[16:] + payload = buffer[16:] assert bytes_len == (data_size + 16) else: payload = [] @@ -82,23 +105,27 @@ def parse(cls, bytes): return cls(packet_leader, packet_type, packet_id, expected_checksum, payload) @property - def packet_group_name(self) -> str: + def packet_leader_name(self) -> str: + """Returns the symbolic name for the packet leader.""" if self.packet_leader == constants.PACKET_LEADER: return "Packet" return "ControlPacket" @property def packet_type_name(self) -> str: + """Returns the symbolic name for the packet type.""" return constants.PACKET_TYPE_TABLE.get(self.packet_type, "") @property def needs_ack(self) -> bool: + """Indicates whether an ack should be expected for this packet.""" return self.packet_leader == constants.PACKET_LEADER @property def basic_log_info(self) -> [str]: + """Returns general/common logging information about this packet.""" return [ - "Packet leader: %08x (%s)" % (self.packet_leader, self.packet_group_name), + "Packet leader: %08x (%s)" % (self.packet_leader, self.packet_leader_name), " Type: %d (%s)" % (self.packet_type, self.packet_type_name), " ID: %08x" % self.packet_id, " Data size: %d" % len(self.payload), @@ -121,13 +148,16 @@ def get_detailed_log(self) -> [str]: return ret def _log_state_manipulate(self) -> []: - apiNumber, processor_level, processor, return_status = struct.unpack( + api_number, processor_level, processor, return_status = struct.unpack( "IHHI", self.payload[:12] ) ret = [ "State Manipulate: %08x (%s)" - % (apiNumber, constants.STATE_MANIPULATE_TABLE.get(apiNumber, "")), + % ( + api_number, + constants.STATE_MANIPULATE_TABLE.get(api_number, ""), + ), "Processor level: %04x" % processor_level, "Processor: %04x" % processor, "Return status: %08x" % return_status, @@ -135,26 +165,26 @@ def _log_state_manipulate(self) -> []: # self._log(hexformat(substr(payload, 0, 16))) - if apiNumber == constants.DbgKdWriteBreakPointApi: - bp, handle = struct.unpack("II", self.payload[16:24]) - ret.append("Breakpoint %d set at 0x%08x" % (handle, bp)) + if api_number == constants.DbgKdWriteBreakPointApi: + breakpoint, handle = struct.unpack("II", self.payload[16:24]) + ret.append("Breakpoint %d set at 0x%08x" % (handle, breakpoint)) - elif apiNumber == constants.DbgKdRestoreBreakPointApi: + elif api_number == constants.DbgKdRestoreBreakPointApi: handle = struct.unpack("I", self.payload[16:20])[0] ret.append("Breakpoint %d (0x%08x) cleared" % (handle, handle)) - elif apiNumber == constants.DbgKdGetVersionApi: + elif api_number == constants.DbgKdGetVersionApi: ret.extend(self._log_version(self.payload[16:])) - elif apiNumber == constants.DbgKdReadVirtualMemoryApi: + elif api_number == constants.DbgKdReadVirtualMemoryApi: vmem = self.payload[56:] ret.append("VMEM:\n%s" % util.hexasc(vmem)) - elif apiNumber == constants.DbgKdReadPhysicalMemoryApi: + elif api_number == constants.DbgKdReadPhysicalMemoryApi: pmem = self.payload[56:] ret.append("PMEM:\n%s" % util.hexasc(pmem)) - elif apiNumber == constants.DbgKdReadControlSpaceApi: + elif api_number == constants.DbgKdReadControlSpaceApi: controlspace = self.payload[56:] ret.append("CNTL: %s" % util.hexformat(controlspace)) @@ -164,7 +194,8 @@ def _log_state_manipulate(self) -> []: ret.extend(["\nRaw payload:\n", util.hexformat(self.payload)]) return ret - def _log_version(self, version_payload) -> [str]: + @staticmethod + def _log_version(version_payload) -> [str]: assert len(version_payload) == 40 ( major_version, From e86ade7be1f0d90a32c50c0aaecb8ab6a809cf95 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 9 Sep 2021 15:54:05 -0700 Subject: [PATCH 27/30] Pylint fixes. --- constants.py | 3 +++ kd_packet.py | 6 ++---- server.py | 6 +++--- sniffer.py | 31 +++++++++++++++++++------------ util.py | 44 +++++++++++++++++++++++++------------------- windpl.py | 5 +++-- 6 files changed, 55 insertions(+), 40 deletions(-) diff --git a/constants.py b/constants.py index 7c12d3a..52b2004 100644 --- a/constants.py +++ b/constants.py @@ -40,6 +40,9 @@ # 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 diff --git a/kd_packet.py b/kd_packet.py index 468222e..9a37182 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -166,8 +166,8 @@ def _log_state_manipulate(self) -> []: # self._log(hexformat(substr(payload, 0, 16))) if api_number == constants.DbgKdWriteBreakPointApi: - breakpoint, handle = struct.unpack("II", self.payload[16:24]) - ret.append("Breakpoint %d set at 0x%08x" % (handle, breakpoint)) + brkpt, handle = struct.unpack("II", self.payload[16:24]) + ret.append("Breakpoint %d set at 0x%08x" % (handle, brkpt)) elif api_number == constants.DbgKdRestoreBreakPointApi: handle = struct.unpack("I", self.payload[16:20])[0] @@ -253,8 +253,6 @@ def _log_state_change64(self) -> [str]: if new_state == constants.DbgKdExceptionStateChange: # DBGKM_EXCEPTION64 - ex = self.payload[34:] - code, flags, record, address, parameters = struct.unpack( "IIQQI", self.payload[:28] ) diff --git a/server.py b/server.py index d51ad27..48c17db 100644 --- a/server.py +++ b/server.py @@ -8,7 +8,7 @@ class _DebuggeeHandler(socketserver.StreamRequestHandler): """Handles connection to a debuggee VM.""" def setup(self) -> None: - super(_DebuggeeHandler, self).setup() + super().setup() self.debugger = self.server.debugger client = debug_connection.DebugConnection(self.client_address) client.handle_socket(self.connection) @@ -16,7 +16,7 @@ def setup(self) -> None: self.debugger.connection = client def finish(self) -> None: - super(_DebuggeeHandler, self).finish() + super().finish() print(f"Debuggee at {self.client_address[0]} disconnectected\n") def handle(self) -> None: @@ -25,7 +25,7 @@ def handle(self) -> None: def serve(host, port, debugger_connection): - """Listens for connections on the given host:port and hands them to the given debugger context.""" + """Listens for connections at host:port and hands them to the given debugger context.""" print(f"Waiting for debuggee at {host}:{port}") server = socketserver.ThreadingTCPServer((host, port), _DebuggeeHandler) diff --git a/sniffer.py b/sniffer.py index a813bdc..c3c8630 100755 --- a/sniffer.py +++ b/sniffer.py @@ -9,6 +9,8 @@ # 3. Start the debuggee qemu instance with # `-serial tcp::` +# pylint: disable = too-many-arguments, too-few-public-methods, too-many-instance-attributes + import argparse import socket import socketserver @@ -18,8 +20,7 @@ import debug_connection import kd_packet -from constants import * -from util import * +from util import * # pylint: disable = wildcard-import, unused-wildcard-import class TeeConnection: @@ -41,7 +42,7 @@ class _KDPassthroughSniffer(debug_connection.DebugConnection): def __init__( self, name, read_connection, write_connection, logger_semaphore, start_time ): - super(_KDPassthroughSniffer, self).__init__(name) + super().__init__(name) # Create a tee between the read and write connections, then use it as the # read connection for the DebugConnection superclass. @@ -54,10 +55,11 @@ def __init__( @property def elapsed_time_ms(self): + """Returns the milliseconds elapsed since this sniffer was started.""" return int((time.perf_counter() - self.start_time) * 1000) def _log(self, message, *args): - """Buffers log messages until a packet is fully processed so the messages can be sent to the logger thread.""" + """Buffers log messages for later synchronized output.""" self._packet_log.append(message % args) def _flush_log(self): @@ -70,7 +72,7 @@ def _flush_log(self): def read_packet(self) -> (kd_packet.KDPacket, bytearray): """Reads a single KD packet from the connection and logs it.""" - packet, discarded_bytes = super(_KDPassthroughSniffer, self).read_packet() + packet, discarded_bytes = super().read_packet() if discarded_bytes: self._log( @@ -110,6 +112,7 @@ def __init__(self, host, port): self.start_time = time.perf_counter() def register_debuggee(self, handler) -> _KDPassthroughSniffer: + """Registers a StreamRequestHandler modeling the debuggee.""" with self._debuggee_semaphore: self._debuggee = handler self.debugger_sniffer = _KDPassthroughSniffer( @@ -129,18 +132,22 @@ def register_debuggee(self, handler) -> _KDPassthroughSniffer: ) def unregister_debuggee(self, _handler): + """Unregisters the previously registered debuggee handler.""" with self._debuggee_semaphore: self._debuggee = None self.debugger_sniffer = None def sendall(self, data, flags=0): + """Sends the given data to the debugger.""" return self.debugger_socket.sendall(data, flags) def stop(self): + """Stops this sniffer.""" with self._debuggee_semaphore: self._running = False - def _debugger_thread_main(self): + def debugger_thread_main(self): + """Thread entrypoint for this debugger instance.""" while True: with self._debuggee_semaphore: if not self._running: @@ -164,12 +171,12 @@ class _DebuggeeHandler(socketserver.StreamRequestHandler): """Handles connection to a debuggee VM.""" def setup(self) -> None: - super(_DebuggeeHandler, self).setup() + super().setup() self.debugger = self.server.debugger self.sniffer = self.debugger.register_debuggee(self) def finish(self) -> None: - super(_DebuggeeHandler, self).finish() + super().finish() self.debugger.unregister_debuggee(self) print(f"Debuggee at {self.client_address[0]} disconnectected\n") @@ -180,12 +187,12 @@ def handle(self) -> None: while True: try: self.sniffer.read_packet() - except OSError as ex: - print(f"Failed to read packet from Debuggee {ex}") - break except ConnectionResetError as ex: print(f"Debuggee disconnected: {ex}") break + except OSError as ex: + print(f"Failed to read packet from Debuggee {ex}") + break def main(args): @@ -200,7 +207,7 @@ def main(args): server.debugger = debugger_connection debugger_thread = threading.Thread( - target=_DebuggerConnection._debugger_thread_main, args=(debugger_connection,) + target=_DebuggerConnection.debugger_thread_main, args=(debugger_connection,) ) try: diff --git a/util.py b/util.py index c72adb6..86f3d10 100644 --- a/util.py +++ b/util.py @@ -44,12 +44,14 @@ def substr(buf, start, length=None): + """Returns a slice of the given buf starting at `start` of length `length`.""" if length is None: return buf[start:] return buf[start : start + length] def patch_substr(buf, start, length, fmt, data=None): + """Replaces a subslice of the given buffer using struct.pack format specifiers.""" if data is None: return buf[0:start] + fmt + buf[start + length :] @@ -57,30 +59,33 @@ def patch_substr(buf, start, length, fmt, data=None): def unpack_one(fmt, data): + """Unpacks a single type from the given data using struct.unpack format specifiers.""" assert len(fmt) == 1 return struct.unpack(fmt, data)[0] # pylint: disable = no-member def hexformat(buf): + """Returns a hex dump of the given byte array.""" length = len(buf) if length == 0: return None - b = "0000 " - c = 0 - for x in buf: - c += 1 - b += "%02x " % x - if (c % 16) == 0 and c < length: - b += "\n%04x " % c + ret = "0000 " + offset = 0 + for value in buf: + offset += 1 + ret += "%02x " % value + if (offset % 16) == 0 and offset < length: + ret += "\n%04x " % offset - if b[-1] != "\n": - b += "\n" + if ret[-1] != "\n": + ret += "\n" - return b + return ret def hexasc(buf): + """Returns an ASCII dump of the given byte array.""" length = len(buf) if length == 0: return None @@ -88,11 +93,11 @@ def hexasc(buf): count = 0 ascii_string = "" out = "0000 " - for x in buf: - c = ord(x) - out += "%02x " % c - if 0x1F < c < 0x7F: - ascii_string += x + for value in buf: + codepoint = ord(value) + out += "%02x " % codepoint + if 0x1F < codepoint < 0x7F: + ascii_string += value else: ascii_string += "." count += 1 @@ -113,7 +118,8 @@ def hexasc(buf): def generate_checksum(buf): - v = 0 - for b in buf: - v += b - return v + """Generates the KD checksum of the given byte array.""" + checksum = 0 + for value in buf: + checksum += value + return checksum diff --git a/windpl.py b/windpl.py index 778d1cf..47db9ef 100755 --- a/windpl.py +++ b/windpl.py @@ -67,7 +67,8 @@ def create(fifo_path): create(f"{named_pipe}.out") -def main(args): +def main(args) -> int: + """Main entrypoint""" log_level = logging.DEBUG if args.verbose else logging.INFO logging.basicConfig(level=log_level) @@ -83,7 +84,7 @@ def main(args): elif args.port: endpoint = (args.host, args.port) else: - raise RuntimeError(f"No supported transport method selected.") + raise RuntimeError("No supported transport method selected.") connection = debug_connection.DebugConnection(endpoint) connection.connect() From 2d736b7057324bdf6fd16d50137a4dde91986539 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 10 Sep 2021 15:59:56 -0700 Subject: [PATCH 28/30] Fixes hexasc behavior. --- util.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/util.py b/util.py index 86f3d10..46551ca 100644 --- a/util.py +++ b/util.py @@ -94,10 +94,13 @@ def hexasc(buf): ascii_string = "" out = "0000 " for value in buf: - codepoint = ord(value) + if isinstance(value, str): + codepoint = ord(value) + else: + codepoint = value out += "%02x " % codepoint if 0x1F < codepoint < 0x7F: - ascii_string += value + ascii_string += chr(codepoint) else: ascii_string += "." count += 1 @@ -106,7 +109,7 @@ def hexasc(buf): out += " " + ascii_string + "\n%04x " % count else: out += " " + ascii_string + "\n" - ascii_string = "" + ascii_string = "" padding = 0 if ascii_string: From f35a779e3e110142e1e845d4867d1950e9e16be2 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 10 Sep 2021 17:21:47 -0700 Subject: [PATCH 29/30] Adds handling of duplicate packets. --- debug_context.py | 138 +++++++++++++++++++++++++++++++++-------------- kd_packet.py | 15 ++++-- 2 files changed, 108 insertions(+), 45 deletions(-) diff --git a/debug_context.py b/debug_context.py index 3015c54..a46edb6 100644 --- a/debug_context.py +++ b/debug_context.py @@ -123,18 +123,25 @@ def _receive_packet(self) -> kd_packet.KDPacket: hexformat(discarded_bytes), ) - if (packet.packet_id & ~SYNC_PACKET_ID) != self.remote_pid: - logging.warning( - "Received packet id %08x but expected %08x", - packet.packet_id, - self.remote_pid, - ) + return packet - # TODO: Send a resend + def _read_until_packet(self, expected_packet_type): + """Reads KD packets until the given packet type is found.""" - self.remote_pid = packet.packet_id ^ 0x01 + while True: + packet = self._receive_packet() + if packet.packet_type == expected_packet_type: + return packet - return packet + if packet.packet_type == PACKET_TYPE_KD_RESET: + self._send_reset() + self._resend_packet() + + if packet.packet_type == PACKET_TYPE_KD_RESEND: + self._resend_packet() + + if packet.needs_ack: + self._send_ack(packet.packet_id) def _handlePacket(self): """Processes one packet from the connection.""" @@ -144,15 +151,48 @@ def _handlePacket(self): # the ack w/ the same packet id is read (or a reset or resend is # encountered). At that point next_pid should be ^= 0x01. - packet = self._receive_packet() - self._log_packet(packet, self.elapsed_time_ms) - if packet.needs_ack: - self._send_ack(packet.packet_id) + while True: + packet = self._receive_packet() + self._log_packet(packet, self.elapsed_time_ms) + + if packet.needs_ack: + self._send_ack(packet.packet_id) + + if (packet.packet_id & ~SYNC_PACKET_ID) == self.remote_pid: + self.remote_pid = packet.packet_id ^ 0x01 + break + + logging.warning( + "Received packet id %08x but expected %08x", + (packet.packet_id & ~SYNC_PACKET_ID), + self.remote_pid, + ) ptype = packet.packet_type buf = packet.payload - if ptype == PACKET_TYPE_KD_STATE_MANIPULATE: + if ptype == PACKET_TYPE_KD_ACKNOWLEDGE: + if packet.packet_id == (self.next_pid & ~SYNC_PACKET_ID): + self.next_pid ^= 0x01 + logging.debug( + ">>> Received correct ack packet 0x%x, new next_pid 0x%x", + packet.packet_id, + self.next_pid, + ) + else: + logging.warning( + "Received ack packet with id %08x but expected %08x", + packet.packet_id, + self.next_pid, + ) + + elif ptype == PACKET_TYPE_KD_RESET: + self._send_reset() + + elif ptype == PACKET_TYPE_KD_RESEND: + self._resend_packet() + + elif ptype == PACKET_TYPE_KD_STATE_MANIPULATE: self._handleStateManipulate(buf) elif ptype == PACKET_TYPE_KD_DEBUG_IO: @@ -161,13 +201,6 @@ def _handlePacket(self): elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: self._handleStateChange(buf) - elif ptype == PACKET_TYPE_KD_RESET: - self.next_pid = INITIAL_PACKET_ID - self.remote_pid = INITIAL_PACKET_ID - - elif ptype == PACKET_TYPE_KD_RESEND: - self._resend_packet() - elif len(buf): logging.debug( "Ignoring packet of type %d (%s) with payload %s", @@ -258,34 +291,50 @@ def _send_ack(self, packet_id): def _send_packet(self, packet_data): if isinstance(packet_data, list): packet_data = bytearray(itertools.chain.from_iterable(packet_data)) - self.last_packet = packet_data - self.connection.write(self.last_packet) + self.last_packet = kd_packet.KDPacket.parse(packet_data) + + logging.debug( + "<<< Sending %s with id 0x%x\n", + self.last_packet.packet_type_name, + self.last_packet.packet_id, + ) + + self.connection.write(packet_data) def _resend_packet(self): if self.last_packet: - packet = kd_packet.KDPacket.parse(self.last_packet) - packet.packet_id = self.next_pid - self.next_pid ^= 1 - logging.debug("Resending last packet: %s", "\n".join(packet.basic_log_info)) - self._send_packet(packet.serialize()) + self.last_packet.packet_id = self.next_pid + self.next_pid ^= 0x1 + logging.debug( + "Resending last packet: %s", "\n".join(self.last_packet.basic_log_info) + ) + self.connection.write(self.last_packet.serialize()) else: logging.critical("Resend requested but no packets have been sent!") def _send_reset(self): + self.next_pid = INITIAL_PACKET_ID | SYNC_PACKET_ID + self.remote_pid = INITIAL_PACKET_ID + reset_packet = struct.pack( "IHHII", CONTROL_PACKET_LEADER, PACKET_TYPE_KD_RESET, 0, - INITIAL_PACKET_ID | SYNC_PACKET_ID, + self.next_pid, 0, ) - self.next_pid = INITIAL_PACKET_ID ^ 0x01 logging.debug("Sending reset packet\n\n") # print(hexformat(rst) self._send_packet(reset_packet) + reset_ack = self._read_until_packet(PACKET_TYPE_KD_RESET) + logging.debug( + "Received reciprocal reset with packet id 0x%x", reset_ack.packet_id + ) + self.next_pid = INITIAL_PACKET_ID + def _send_resend(self): resend_packet = struct.pack( "IHHII", @@ -464,7 +513,6 @@ def _printVersionData(self): def _sendManipulateStatePacket(self, payload): pid = self.next_pid - self.next_pid ^= 0x01 header = struct.pack( "IHHII", PACKET_LEADER, @@ -473,14 +521,6 @@ def _sendManipulateStatePacket(self, payload): pid, generate_checksum(payload), ) - - logging.debug( - "Sending manipulate state [%08x]:\nHeader:\n%s\nBody:\n%s", - pid, - hexformat(header), - hexformat(payload), - ) - self._send_packet([header, payload, bytes([PACKET_TRAILER])]) def _sendDbgKdContinue2(self): @@ -489,10 +529,10 @@ def _sendDbgKdContinue2(self): packet = struct.pack( "IHHIIIIII", DbgKdContinueApi2, # API Number - 0xFFFF, # Processor level + 0x0000, # Processor level 0x0000, # Processor HRESULT_STATUS_SUCCESS, # Return status - HRESULT_DBG_CONTINUE, # ContinueStatus + HRESULT_STATUS_SUCCESS, # ContinueStatus 0x00000000, # TraceFlag 0x00000000, # Dr7 0x00000001, # CurrentSymbolStart @@ -503,6 +543,22 @@ def _sendDbgKdContinue2(self): packet += bytearray([0] * (56 - len(packet))) self._sendManipulateStatePacket(packet) + while True: + packet = self._read_until_packet(PACKET_TYPE_KD_ACKNOWLEDGE) + if packet.packet_id == (self.next_pid & ~SYNC_PACKET_ID): + self.next_pid ^= 0x01 + logging.debug( + ">>> Received correct ack packet 0x%x, new next_pid 0x%x", + packet.packet_id, + self.next_pid, + ) + break + else: + logging.warning( + "Received ack packet with id %08x but expected %08x while awaiting ack for continue2", + packet.packet_id, + self.next_pid, + ) def _sendDbgKdGetVersion(self): diff --git a/kd_packet.py b/kd_packet.py index 9a37182..42acc69 100644 --- a/kd_packet.py +++ b/kd_packet.py @@ -62,7 +62,7 @@ def __init__( self.actual_checksum = util.generate_checksum(payload) - def serialize(self) -> [bytearray]: + def serialize(self) -> bytearray: """Serializes this packet into a byte array (with trailing marker).""" header = struct.pack( "IHHII", @@ -73,11 +73,11 @@ def serialize(self) -> [bytearray]: self.actual_checksum, ) - ret = [header] + ret = header if self.payload: - ret.append(self.payload) + ret += self.payload if self.needs_ack: - ret.append(bytes([constants.PACKET_TRAILER])) + ret += bytearray([constants.PACKET_TRAILER]) return ret @classmethod @@ -116,6 +116,11 @@ def packet_type_name(self) -> str: """Returns the symbolic name for the packet type.""" return constants.PACKET_TYPE_TABLE.get(self.packet_type, "") + @property + def is_control(self) -> bool: + """Indicates whether or not this is a CONTROL packet.""" + return self.packet_leader == constants.CONTROL_PACKET_LEADER + @property def needs_ack(self) -> bool: """Indicates whether an ack should be expected for this packet.""" @@ -142,6 +147,8 @@ def get_detailed_log(self) -> [str]: elif self.packet_type == constants.PACKET_TYPE_KD_STATE_CHANGE64: ret.append("") ret.extend(self._log_state_change64()) + elif self.packet_type == constants.PACKET_TYPE_KD_DEBUG_IO: + ret.append("\nPayload:\n%s" % util.hexasc(self.payload)) elif self.payload: ret.append("\nPayload:\n%s" % util.hexformat(self.payload)) From 7bd9ac0018ddb1d426e4a06f0789b8bf81a20578 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 10 Sep 2021 17:23:56 -0700 Subject: [PATCH 30/30] Reduces warning output. --- debug_context.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/debug_context.py b/debug_context.py index a46edb6..2e0ecc5 100644 --- a/debug_context.py +++ b/debug_context.py @@ -162,8 +162,8 @@ def _handlePacket(self): self.remote_pid = packet.packet_id ^ 0x01 break - logging.warning( - "Received packet id %08x but expected %08x", + logging.debug( + "!!! Received packet id %08x but expected %08x", (packet.packet_id & ~SYNC_PACKET_ID), self.remote_pid, ) @@ -180,8 +180,8 @@ def _handlePacket(self): self.next_pid, ) else: - logging.warning( - "Received ack packet with id %08x but expected %08x", + logging.debug( + "!!! Received ack packet with id %08x but expected %08x", packet.packet_id, self.next_pid, ) @@ -554,8 +554,8 @@ def _sendDbgKdContinue2(self): ) break else: - logging.warning( - "Received ack packet with id %08x but expected %08x while awaiting ack for continue2", + logging.debug( + "!!! Received ack packet with id %08x but expected %08x while awaiting ack for continue2", packet.packet_id, self.next_pid, )