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..73b5a24 --- /dev/null +++ b/Pipfile @@ -0,0 +1,17 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +black = "*" +pylint = "*" + +[dev-packages] +black = "*" + +[requires] +python_version = "3.9" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..a826029 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,295 @@ +{ + "_meta": { + "hash": { + "sha256": "1fb939a3f88dbcf408171f9451c3d9acbaccb47d5e123cc7842af9844ecc6fe6" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "astroid": { + "hashes": [ + "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c", + "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e" + ], + "markers": "python_version ~= '3.6'", + "version": "==2.7.3" + }, + "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" + }, + "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", + "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" + }, + "pylint": { + "hashes": [ + "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1", + "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852" + ], + "index": "pypi", + "version": "==2.10.2" + }, + "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" + }, + "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", + "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" + }, + "wrapt": { + "hashes": [ + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" + ], + "version": "==1.12.1" + } + }, + "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/constants.py b/constants.py new file mode 100644 index 0000000..52b2004 --- /dev/null +++ b/constants.py @@ -0,0 +1,200 @@ +"""Constants used by the Kernel Debug protocol.""" + +# Copyright (C) 2007 SecureWorks, Inc. +# Copyright (C) 2013 espes +# Copyright (C) 2017 Jannik Vogel +# +# This program is free software subject to the terms of the GNU General +# Public License. You can use, copy, redistribute and/or modify the +# program under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. You should have received a copy of +# the GNU General Public License along with this program. If not, +# please see http://www.gnu.org/licenses/ for a copy of the GNU General +# Public License. +# +# The program is subject to a disclaimer of warranty and a limitation of +# liability, as disclosed below. +# +# Disclaimer of Warranty. +# +# THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +# APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +# HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +# WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU +# ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, CORRECTION OR +# RECOVERY FROM DATA LOSS OR DATA ERRORS. +# +# Limitation of Liability. +# +# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +# WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +# CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +# NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES +# SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +# WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +# Constants are allowed to be camel case to match definitions in ReactOS source. +# pylint: disable = invalid-name + +INITIAL_PACKET_ID = 0x80800000 +SYNC_PACKET_ID = 0x00000800 + +PACKET_LEADER = 0x30303030 +CONTROL_PACKET_LEADER = 0x69696969 +PACKET_TRAILER = 0xAA + +PACKET_TYPE_UNUSED = 0 +PACKET_TYPE_KD_STATE_CHANGE32 = 1 +PACKET_TYPE_KD_STATE_MANIPULATE = 2 +PACKET_TYPE_KD_DEBUG_IO = 3 +PACKET_TYPE_KD_ACKNOWLEDGE = 4 +PACKET_TYPE_KD_RESEND = 5 +PACKET_TYPE_KD_RESET = 6 +PACKET_TYPE_KD_STATE_CHANGE64 = 7 +PACKET_TYPE_MAX = 8 + +PACKET_TYPE_TABLE = { + PACKET_TYPE_UNUSED: "PACKET_TYPE_UNUSED", + PACKET_TYPE_KD_STATE_CHANGE32: "PACKET_TYPE_KD_STATE_CHANGE32", + PACKET_TYPE_KD_STATE_MANIPULATE: "PACKET_TYPE_KD_STATE_MANIPULATE", + PACKET_TYPE_KD_DEBUG_IO: "PACKET_TYPE_KD_DEBUG_IO", + PACKET_TYPE_KD_ACKNOWLEDGE: "PACKET_TYPE_KD_ACKNOWLEDGE", + PACKET_TYPE_KD_RESEND: "PACKET_TYPE_KD_RESEND", + PACKET_TYPE_KD_RESET: "PACKET_TYPE_KD_RESET", + PACKET_TYPE_KD_STATE_CHANGE64: "PACKET_TYPE_KD_STATE_CHANGE64", + PACKET_TYPE_MAX: "PACKET_TYPE_MAX", +} + +# PACKET_TYPE_KD_DEBUG_IO apis +# DBGKD_DEBUG_IO +DbgKdPrintStringApi = 0x00003230 +DbgKdGetStringApi = 0x00003231 + +# PACKET_TYPE_KD_STATE_CHANGE states +# X86_NT5_DBGKD_WAIT_STATE_CHANGE64 +DbgKdExceptionStateChange = 0x00003030 +DbgKdLoadSymbolsStateChange = 0x00003031 + +STATE_CHANGE_TABLE = { + DbgKdExceptionStateChange: "DbgKdExceptionStateChange", + DbgKdLoadSymbolsStateChange: "DbgKdLoadSymbolsStateChange", +} + +# PACKET_TYPE_KD_STATE_MANIPULATE api numbers +# DBGKD_MANIPULATE_STATE64 +DbgKdReadVirtualMemoryApi = 0x00003130 +DbgKdWriteVirtualMemoryApi = 0x00003131 +DbgKdGetContextApi = 0x00003132 +DbgKdSetContextApi = 0x00003133 +DbgKdWriteBreakPointApi = 0x00003134 +DbgKdRestoreBreakPointApi = 0x00003135 +DbgKdContinueApi = 0x00003136 +DbgKdReadControlSpaceApi = 0x00003137 +DbgKdWriteControlSpaceApi = 0x00003138 +DbgKdReadIoSpaceApi = 0x00003139 +DbgKdWriteIoSpaceApi = 0x0000313A +DbgKdRebootApi = 0x0000313B +DbgKdContinueApi2 = 0x0000313C +DbgKdReadPhysicalMemoryApi = 0x0000313D +DbgKdWritePhysicalMemoryApi = 0x0000313E +DbgKdSetSpecialCallApi = 0x00003140 +DbgKdClearSpecialCallsApi = 0x00003141 +DbgKdSetInternalBreakPointApi = 0x00003142 +DbgKdGetInternalBreakPointApi = 0x00003143 +DbgKdReadIoSpaceExtendedApi = 0x00003144 +DbgKdWriteIoSpaceExtendedApi = 0x00003145 +DbgKdGetVersionApi = 0x00003146 +DbgKdWriteBreakPointExApi = 0x00003147 +DbgKdRestoreBreakPointExApi = 0x00003148 +DbgKdCauseBugCheckApi = 0x00003149 +DbgKdSwitchProcessor = 0x00003150 +DbgKdPageInApi = 0x00003151 +DbgKdReadMachineSpecificRegister = 0x00003152 +DbgKdWriteMachineSpecificRegister = 0x00003153 +DbgKdSearchMemoryApi = 0x00003156 +DbgKdGetBusDataApi = 0x00003157 +DbgKdSetBusDataApi = 0x00003158 +DbgKdCheckLowMemoryApi = 0x00003159 + +STATE_MANIPULATE_TABLE = { + DbgKdReadVirtualMemoryApi: "DbgKdReadVirtualMemoryApi", + DbgKdWriteVirtualMemoryApi: "DbgKdWriteVirtualMemoryApi", + DbgKdGetContextApi: "DbgKdGetContextApi", + DbgKdSetContextApi: "DbgKdSetContextApi", + DbgKdWriteBreakPointApi: "DbgKdWriteBreakPointApi", + DbgKdRestoreBreakPointApi: "DbgKdRestoreBreakPointApi", + DbgKdContinueApi: "DbgKdContinueApi", + DbgKdReadControlSpaceApi: "DbgKdReadControlSpaceApi", + DbgKdWriteControlSpaceApi: "DbgKdWriteControlSpaceApi", + DbgKdReadIoSpaceApi: "DbgKdReadIoSpaceApi", + DbgKdWriteIoSpaceApi: "DbgKdWriteIoSpaceApi", + DbgKdRebootApi: "DbgKdRebootApi", + DbgKdContinueApi2: "DbgKdContinueApi2", + DbgKdReadPhysicalMemoryApi: "DbgKdReadPhysicalMemoryApi", + DbgKdWritePhysicalMemoryApi: "DbgKdWritePhysicalMemoryApi", + DbgKdSetSpecialCallApi: "DbgKdSetSpecialCallApi", + DbgKdClearSpecialCallsApi: "DbgKdClearSpecialCallsApi", + DbgKdSetInternalBreakPointApi: "DbgKdSetInternalBreakPointApi", + DbgKdGetInternalBreakPointApi: "DbgKdGetInternalBreakPointApi", + DbgKdReadIoSpaceExtendedApi: "DbgKdReadIoSpaceExtendedApi", + DbgKdWriteIoSpaceExtendedApi: "DbgKdWriteIoSpaceExtendedApi", + DbgKdGetVersionApi: "DbgKdGetVersionApi", + DbgKdWriteBreakPointExApi: "DbgKdWriteBreakPointExApi", + DbgKdRestoreBreakPointExApi: "DbgKdRestoreBreakPointExApi", + DbgKdCauseBugCheckApi: "DbgKdCauseBugCheckApi", + DbgKdSwitchProcessor: "DbgKdSwitchProcessor", + DbgKdPageInApi: "DbgKdPageInApi", + DbgKdReadMachineSpecificRegister: "DbgKdReadMachineSpecificRegister", + DbgKdWriteMachineSpecificRegister: "DbgKdWriteMachineSpecificRegister", + DbgKdSearchMemoryApi: "DbgKdSearchMemoryApi", + DbgKdGetBusDataApi: "DbgKdGetBusDataApi", + DbgKdSetBusDataApi: "DbgKdSetBusDataApi", + DbgKdCheckLowMemoryApi: "DbgKdCheckLowMemoryApi", +} + + +HRESULT_STATUS_SUCCESS = 0x00000000 +HRESULT_STATUS_PENDING = 0x00000103 +HRESULT_STATUS_UNSUCCESSFUL = 0xC0000001 +HRESULT_DBG_EXCEPTION_HANDLED = 0x00010001 +HRESULT_DBG_CONTINUE = 0x00010002 +HRESULT_DBG_REPLY_LATER = 0x40010001 +HRESULT_DBG_UNABLE_TO_PROVIDE_HANDLE = 0x40010002 +HRESULT_DBG_TERMINATE_THREAD = 0x40010003 +HRESULT_DBG_TERMINATE_PROCESS = 0x40010004 +HRESULT_DBG_CONTROL_C = 0x40010005 +HRESULT_DBG_PRINTEXCEPTION_C = 0x40010006 +HRESULT_DBG_RIPEXCEPTION = 0x40010007 +HRESULT_DBG_CONTROL_BREAK = 0x40010008 +HRESULT_DBG_COMMAND_EXCEPTION = 0x40010009 +HRESULT_DBG_EXCEPTION_NOT_HANDLED = 0x80010001 +HRESULT_DBG_NO_STATE_CHANGE = 0xC0010001 +HRESULT_DBG_APP_NOT_IDLE = 0xC0010002 + + +STATE_CHANGE_EXCEPTIONS = { + 0xC0000005: "EXCEPTION_ACCESS_VIOLATION", + 0xC000008C: "EXCEPTION_ARRAY_BOUNDS_EXCEEDED", + 0x80000003: "EXCEPTION_BREAKPOINT", + 0x80000002: "EXCEPTION_DATATYPE_MISALIGNMENT", + 0xC000008D: "EXCEPTION_FLT_DENORMAL_OPERAND", + 0xC000008E: "EXCEPTION_FLT_DIVIDE_BY_ZERO", + 0xC000008F: "EXCEPTION_FLT_INEXACT_RESULT", + 0xC0000030: "EXCEPTION_FLT_INVALID_OPERATION", + 0xC0000091: "EXCEPTION_FLT_OVERFLOW", + 0xC0000032: "EXCEPTION_FLT_STACK_CHECK", + 0xC0000033: "EXCEPTION_FLT_UNDERFLOW", + 0x80000001: "EXCEPTION_GUARD_PAGE", + 0xC000001D: "EXCEPTION_ILLEGAL_INSTRUCTION", + 0xC0000006: "EXCEPTION_IN_PAGE_ERROR", + 0xC0000094: "EXCEPTION_INT_DIVIDE_BY_ZERO", + 0xC0000035: "EXCEPTION_INT_OVERFLOW", + 0xC00000FD: "EXCEPTION_STACK_OVERFLOW", +} diff --git a/debug_connection.py b/debug_connection.py new file mode 100644 index 0000000..f1b55f6 --- /dev/null +++ b/debug_connection.py @@ -0,0 +1,168 @@ +"""Abstract connection interface for kernel debugging.""" + +import logging +import os +import pathlib +import socket +import struct + +from constants import PACKET_LEADER, CONTROL_PACKET_LEADER, PACKET_TRAILER +import kd_packet + + +class DebugConnection: + """Models a connection to the target device (e.g. FIFO, socket).""" + + def __init__(self, endpoint): + self.endpoint = endpoint + self._connection_write = None + self._connection_read = None + + self._recv = None + self._send = None + + def connect(self): + """Connects to the debug endpoint.""" + if isinstance(self.endpoint, tuple): + self._connect_socket(self.endpoint) + else: + self._connect_fifo(self.endpoint) + + def handle_socket(self, connection): + """Utilizes the given socket as the transport layer.""" + self._connection_read = connection + self._connection_write = self._connection_read + self._recv = self._recv_socket + self._send = self._send_socket + + def _connect_socket(self, host_port): + logging.info("Connecting to %s:%d", host_port[0], host_port[1]) + self.handle_socket(socket.socket()) + self._connection_read.connect(host_port) + + def _connect_fifo(self, path): + """Connects via a pair of .in and .out named pipes.""" + + logging.info("Connecting to FIFO at '%s'", path) + + def require_fifo_exists(fifo_path): + path_object = pathlib.Path(fifo_path) + if not path_object.exists(): + raise Exception( + "Qemu requires two pipes with a common base name and '.in' and " + "'.out' suffixes. E.g., '/tmp/foo.in' and '/tmp/foo.out'." + ) + + if not path_object.is_fifo(): + raise Exception(f"'{path_object.name}' is not a fifo.") + + # The naming is from the perspective of qemu, so "in" is written to and "out" is read from. + write_fifo = f"{path}.in" + read_fifo = f"{path}.out" + + require_fifo_exists(write_fifo) + require_fifo_exists(read_fifo) + + flags = 0 + if os.name == "nt": + flags |= os.O_BINARY # pylint: disable = no-member + + self._connection_write = os.open(write_fifo, flags | os.O_WRONLY) + self._connection_read = os.open(read_fifo, flags | os.O_RDONLY) + + self._recv = self._recv_fifo + self._send = self._send_fifo + + def read_packet(self) -> (kd_packet.KDPacket, bytearray): + """Reads a single KD packet from the connection.""" + packet_leader, discarded_bytes = self._read_packet_leader() + + buf = self.read(12) + (packet_type, data_size, packet_id, expected_checksum) = struct.unpack( + "HHII", buf + ) + + if data_size: + payload = self.read(data_size) + else: + payload = bytearray([]) + + # send ack if it's a non-control packet + if packet_leader == PACKET_LEADER: + # packet trailer + # self._log("Reading trailer...") + trail = self.read(1) + # self._log("Trailer: %x", trail[0]) + if trail[0] != PACKET_TRAILER: + raise Exception("Invalid packet trailer 0x%x" % trail[0]) + + return ( + kd_packet.KDPacket( + packet_leader, packet_type, packet_id, expected_checksum, payload + ), + discarded_bytes, + ) + + def read(self, wanted): + """Reads exactly `wanted` bytes from the connection, blocking as necessary.""" + total = 0 + outbuf = bytearray([]) + while total < wanted: + buf = self._recv(wanted - total) + count = len(buf) + if count: + total += count + outbuf += buf + else: + raise ConnectionResetError("Failed to read from connection.") + + return outbuf + + def write(self, buffer): + """Writes `buffer` to the connection, blocking as necessary.""" + + while len(buffer): + written = self._send(buffer) + buffer = buffer[written:] + + def _read_packet_leader(self) -> (int, bytearray): + """Reads from the connection until a valid packet leader is found. + + Returns (packet_leader, discarded_bytes): + - packet_leader: The 4 byte leader that was read + - discarded_bytes: An array of bytes that were read and discarded + before the leader was found. + """ + buf = self.read(4) + packet_leader = struct.unpack("I", buf)[0] + + discarded_bytes = bytearray([]) + while packet_leader not in (PACKET_LEADER, CONTROL_PACKET_LEADER): + discarded_bytes.append(buf[0]) + buf = buf[1:] + self.read(1) + packet_leader = struct.unpack("I", buf)[0] + + return packet_leader, discarded_bytes + + def _recv_fifo(self, max_bytes): + """Receives up to `max_bytes` bytes from the connection.""" + bytes_read = os.read(self._connection_read, max_bytes) + if len(bytes_read) == 0: + print("Failed to read from connection") + + return bytes_read + + def _send_fifo(self, buf): + """Sends some or all of the given buf, returns the number of bytes actually sent.""" + return os.write(self._connection_write, buf) + + def _recv_socket(self, max_bytes): + """Receives up to `max_bytes` bytes from the connection.""" + bytes_read = self._connection_read.recv(max_bytes) + if len(bytes_read) == 0: + print("Failed to read from connection") + return bytes_read + + def _send_socket(self, buf): + """Sends some or all of the given buf, returns the number of bytes actually sent.""" + return self._connection_write.send(buf) diff --git a/debug_context.py b/debug_context.py new file mode 100644 index 0000000..2e0ecc5 --- /dev/null +++ b/debug_context.py @@ -0,0 +1,843 @@ +"""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, unused-wildcard-import, fixme + +import itertools +import logging +from struct import pack # pylint: disable = no-name-in-module +import sys +import time + +from constants import * # pylint: disable = wildcard-import +from debug_connection import DebugConnection +import kd_packet +from util import * # pylint: disable = wildcard-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.remote_pid = INITIAL_PACKET_ID + self.next_pid = INITIAL_PACKET_ID | SYNC_PACKET_ID + + self.breakpoints = {} + + self.curbp = 1 + self.controlspace = 0 + self.controlspacesent = False + + self.connection = None + + self.last_packet = None + + def set_connection(self, connection: DebugConnection) -> None: + self.connection = connection + + def run(self): + logging.info("Waiting for target device...") + self._send_reset() + packets = 0 + while True: + try: + self._handlePacket() + except ConnectionResetError: + logging.info("Client connection closed.") + break + + packets += 1 + logging.debug("Processed %d packets\n", packets) + + # FIXME: windpl_loop.py + + @property + def elapsed_time_ms(self): + 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), + ) + + return packet + + def _read_until_packet(self, expected_packet_type): + """Reads KD packets until the given packet type is found.""" + + while True: + packet = self._receive_packet() + if packet.packet_type == expected_packet_type: + 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.""" + + # 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. + + 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.debug( + "!!! 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_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.debug( + "!!! 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: + self._handle_debug_io(buf) + + elif ptype == PACKET_TYPE_KD_STATE_CHANGE64: + self._handleStateChange(buf) + + elif len(buf): + 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 + ): # 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( + "!! Checksum invalid. " + f"Expected {packet.expected_checksum} but calculated {packet.actual_checksum}" + ) + + 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", api_number) + + def _handleStateChange(self, buf): + newState = unpack_one("I", substr(buf, 0, 4)) + + if newState == DbgKdExceptionStateChange: + # DBGKM_EXCEPTION64 + self.running = False + elif newState == DbgKdLoadSymbolsStateChange: + # DBGKD_LOAD_SYMBOLS64 + # nothing to do... + + self._sendDbgKdContinue2() + + def _handleStateManipulate(self, buf): + apiNumber = unpack_one("I", substr(buf, 0, 4)) + + if apiNumber == DbgKdWriteBreakPointApi: + bp = "%08x" % unpack_one("I", substr(buf, 16, 4)) + handle = unpack_one("I", substr(buf, 20, 4)) + self.breakpoints[bp] = handle + + 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 = struct.pack( + "IHHII", + CONTROL_PACKET_LEADER, + PACKET_TYPE_KD_ACKNOWLEDGE, + 0, + ack_packet_id, + 0, + ) + + self._send_packet(ack_packet) + + def _send_packet(self, packet_data): + if isinstance(packet_data, list): + packet_data = bytearray(itertools.chain.from_iterable(packet_data)) + 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: + 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, + self.next_pid, + 0, + ) + + 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", + CONTROL_PACKET_LEADER, + PACKET_TYPE_KD_RESEND, + 0, + self.next_pid, + 0, + ) + self.next_pid ^= 0x01 + logging.debug("Sending resend packet\n\n") + # print(hexformat(rst) + self.connection.write(resend_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, payload): + pid = self.next_pid + header = struct.pack( + "IHHII", + PACKET_LEADER, + PACKET_TYPE_KD_STATE_MANIPULATE, + len(payload), + pid, + generate_checksum(payload), + ) + self._send_packet([header, payload, bytes([PACKET_TRAILER])]) + + def _sendDbgKdContinue2(self): + logging.debug("< Sending DbgKdContinueApi2 packet") + + packet = struct.pack( + "IHHIIIIII", + DbgKdContinueApi2, # API Number + 0x0000, # Processor level + 0x0000, # Processor + HRESULT_STATUS_SUCCESS, # Return status + HRESULT_STATUS_SUCCESS, # 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))) + + 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.debug( + "!!! Received ack packet with id %08x but expected %08x while awaiting ack for continue2", + packet.packet_id, + self.next_pid, + ) + + 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 = struct.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/kd_packet.py b/kd_packet.py new file mode 100644 index 0000000..42acc69 --- /dev/null +++ b/kd_packet.py @@ -0,0 +1,314 @@ +"""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. + +# pylint: disable=too-many-arguments, too-many-locals + +import struct + +import constants +import util + + +class KDPacket: + """Models a Kernel Debug packet.""" + + 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) + + 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 += self.payload + if self.needs_ack: + ret += bytearray([constants.PACKET_TRAILER]) + return ret + + @classmethod + def parse(cls, buffer): + """Constructs a new KDPacket from the given byte buffer.""" + bytes_len = len(buffer) + assert bytes_len >= 16 + ( + packet_leader, + packet_type, + data_size, + packet_id, + expected_checksum, + ) = struct.unpack("IHHII", buffer[:16]) + + if packet_leader == constants.PACKET_LEADER: + payload = buffer[16:-1] # Discard the trailer + assert bytes_len == (data_size + 17) + elif bytes_len: + payload = buffer[16:] + assert bytes_len == (data_size + 16) + else: + payload = [] + + return cls(packet_leader, packet_type, packet_id, expected_checksum, payload) + + @property + 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 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.""" + 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_leader_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, + ] + + 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.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)) + + return ret + + def _log_state_manipulate(self) -> []: + api_number, processor_level, processor, return_status = struct.unpack( + "IHHI", self.payload[:12] + ) + + ret = [ + "State Manipulate: %08x (%s)" + % ( + api_number, + constants.STATE_MANIPULATE_TABLE.get(api_number, ""), + ), + "Processor level: %04x" % processor_level, + "Processor: %04x" % processor, + "Return status: %08x" % return_status, + ] + + # self._log(hexformat(substr(payload, 0, 16))) + + if api_number == constants.DbgKdWriteBreakPointApi: + 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] + ret.append("Breakpoint %d (0x%08x) cleared" % (handle, handle)) + + elif api_number == constants.DbgKdGetVersionApi: + ret.extend(self._log_version(self.payload[16:])) + + elif api_number == constants.DbgKdReadVirtualMemoryApi: + vmem = self.payload[56:] + ret.append("VMEM:\n%s" % util.hexasc(vmem)) + + elif api_number == constants.DbgKdReadPhysicalMemoryApi: + pmem = self.payload[56:] + ret.append("PMEM:\n%s" % util.hexasc(pmem)) + + elif api_number == 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 + + @staticmethod + def _log_version(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 + 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(self.payload[32:])) + + return ret + + def _log_load_symbols_state_change(self, data): + ret = [] + + ( + 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, + ] + ) + + 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:])) + + return ret diff --git a/server.py b/server.py new file mode 100644 index 0000000..48c17db --- /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().setup() + self.debugger = self.server.debugger + client = debug_connection.DebugConnection(self.client_address) + client.handle_socket(self.connection) + + self.debugger.connection = client + + def finish(self) -> None: + super().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 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) + server.debugger = debugger_connection + + try: + server.serve_forever() + except KeyboardInterrupt: + print("Shutting down...") + server.shutdown() diff --git a/sniffer.py b/sniffer.py new file mode 100755 index 0000000..c3c8630 --- /dev/null +++ b/sniffer.py @@ -0,0 +1,255 @@ +#!/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::` + +# pylint: disable = too-many-arguments, too-few-public-methods, too-many-instance-attributes + +import argparse +import socket +import socketserver +import sys +import threading +import time + +import debug_connection +import kd_packet +from util import * # pylint: disable = wildcard-import, unused-wildcard-import + + +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 + ): + super().__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): + """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 for later synchronized output.""" + 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_packet(self) -> (kd_packet.KDPacket, bytearray): + """Reads a single KD packet from the connection and logs it.""" + packet, discarded_bytes = super().read_packet() + + if discarded_bytes: + self._log( + "[%s] @ %d: Discarded non-KD packet bytes %s", + self.endpoint, + self.elapsed_time_ms, + hexformat(discarded_bytes), + ) + + self._log_packet(packet) + self._flush_log() + + return packet, discarded_bytes + + 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.get_detailed_log())) + + +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._running = True + + self._logger_semaphore = threading.BoundedSemaphore() + self._debuggee_semaphore = threading.BoundedSemaphore() + self._debuggee = None + + self.debugger_sniffer = None + 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( + "Debugger", + self.debugger_socket, + handler.connection, + self._logger_semaphore, + self.start_time, + ) + + return _KDPassthroughSniffer( + "Target", + handler.connection, + self.debugger_socket, + self._logger_semaphore, + self.start_time, + ) + + 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): + """Thread entrypoint for this debugger instance.""" + while True: + with self._debuggee_semaphore: + if not self._running: + break + + sniffer = self.debugger_sniffer + + if not sniffer: + time.sleep(0.100) + continue + + # TODO: Make this handle unregistration gracefully. + try: + sniffer.read_packet() + except OSError as ex: + print(f"Failed to read packet: {ex}") + break + + +class _DebuggeeHandler(socketserver.StreamRequestHandler): + """Handles connection to a debuggee VM.""" + + def setup(self) -> None: + super().setup() + self.debugger = self.server.debugger + self.sniffer = self.debugger.register_debuggee(self) + + def finish(self) -> None: + super().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]}\n") + + while True: + try: + self.sniffer.read_packet() + 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): + """Main entrypoint""" + 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,) + ) + + try: + debugger_thread.start() + server.serve_forever() + except KeyboardInterrupt: + debugger_connection.stop() + server.shutdown() + raise + + +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..46551ca --- /dev/null +++ b/util.py @@ -0,0 +1,128 @@ +"""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): + """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 :] + + return buf[0:start] + struct.pack(fmt, data) + buf[start + length :] + + +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 + + 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 ret[-1] != "\n": + ret += "\n" + + return ret + + +def hexasc(buf): + """Returns an ASCII dump of the given byte array.""" + length = len(buf) + if length == 0: + return None + + count = 0 + ascii_string = "" + out = "0000 " + for value in buf: + if isinstance(value, str): + codepoint = ord(value) + else: + codepoint = value + out += "%02x " % codepoint + if 0x1F < codepoint < 0x7F: + ascii_string += chr(codepoint) + 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): + """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 b483713..47db9ef 100755 --- a/windpl.py +++ b/windpl.py @@ -1,903 +1,149 @@ #!/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 # -# 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 - -import socket +import argparse +import logging +import os +import pathlib import sys -import struct - -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] - -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 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]) - 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): +import debug_context +import debug_connection +import server - 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)) - +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) -> int: + """Main entrypoint""" + 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) - - - -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 + 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: - 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(): + raise RuntimeError("No supported transport method selected.") - #print("Sending DbgKdGetVersionApi packet\n" - d = bytearray([0] * 56) - d = patch_substr( d, 0, 4 , "I", DbgKdGetVersionApi ) - sendManipulateStatePacket(d) + connection = debug_connection.DebugConnection(endpoint) + connection.connect() -def sendDbgKdWriteBreakPoint(bp): - bp = hex(bp) + context.set_connection(connection) + context.run() - #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) + return 0 -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(): +if __name__ == "__main__": - #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 _parse_args(): + parser = argparse.ArgumentParser() -def sendDbgKdWriteControlSpace(): + parser.add_argument( + "-p", + "--port", + type=int, + help="The TCP server port used by qemu.", + ) - #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 - 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) + parser.add_argument( + "--host", + help="The IP address of the host.", + default="0.0.0.0", + ) + parser.add_argument( + "--serve", + help="Act as a server (e.g., with qemu `-serial tcp::`)", + action="store_true", + ) -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 + parser.add_argument( + "-np", + "--named_pipe", + help="The path to the named pipe used by qemu.", + ) -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 0 + 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", + help="Enables verbose logging information", + action="store_true", + ) + args = parser.parse_args() -sendReset() -while(True): - handlePacket() - print("") + if not args.port and not args.named_pipe: + parser.error("At least one of '--port' or '--named-pipe' must be given.") -# FIXME: windpl_loop.py + return 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"