diff --git a/.gitignore b/.gitignore index b5dde8f..a588212 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ Docs\sources_private /NTFS.vcxproj.user /Docs/sources_private /vcpkg_installed +/test.bat diff --git a/LICENSE b/LICENSE index bd80f97..1fa5735 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 thewhiteninja +Copyright (c) 2022 thewhiteninja Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NTFS.vcxproj b/NTFS.vcxproj index 39514a0..6e27601 100644 --- a/NTFS.vcxproj +++ b/NTFS.vcxproj @@ -57,11 +57,18 @@ + + + + + + + @@ -70,26 +77,28 @@ - - - - - - - - + + + + + + + + - + + - - + + - + + @@ -119,14 +128,20 @@ + + + + + + 15.0 diff --git a/NTFS.vcxproj.filters b/NTFS.vcxproj.filters index b96f660..589133d 100644 --- a/NTFS.vcxproj.filters +++ b/NTFS.vcxproj.filters @@ -58,6 +58,9 @@ {c3b20f0c-65ae-4b68-9e00-a7cd05c0b8c9} + + {b75428fc-74c9-4316-9e3f-8f3cca8bfd91} + @@ -189,6 +192,27 @@ Header Files\Compression + + Header Files\Utils + + + Header Files\Utils + + + Header Files\Utils + + + Header Files\Utils + + + Header Files\Utils + + + Header Files\Utils + + + Header Files\Utils + @@ -209,7 +233,7 @@ Source Files\Commands - + Source Files\Commands @@ -224,25 +248,25 @@ Source Files\Commands - + Source Files\Commands - + Source Files\Commands - + Source Files\Commands Source Files\Commands - + Source Files\Commands Source Files\Commands - + Source Files\Commands @@ -311,16 +335,16 @@ Source Files\Commands - + Source Files\Commands Source Files\EFS - + Source Files\Commands - + Source Files\Commands @@ -338,16 +362,16 @@ Source Files\EFS - + Source Files\Commands Source Files\Commands - + Source Files\Commands - + Source Files\Commands @@ -368,6 +392,27 @@ Source Files\Compression + + Source Files\Commands + + + Source Files\Utils + + + Source Files\Utils + + + Source Files\Utils + + + Source Files\Commands + + + Source Files\Utils + + + Source Files\Utils + @@ -376,5 +421,8 @@ Other files + + Rules + \ No newline at end of file diff --git a/README.md b/README.md index 09b8d34..afb21ad 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Language: C++](https://img.shields.io/badge/Language-C%2B%2B-brightgreen.svg?tyle=flat-square)](#) [![x64](https://img.shields.io/badge/Windows-64_bit-0078d7.svg)](#) [![x86](https://img.shields.io/badge/Windows-32_bit-0078d7.svg)](#) -[![v1.5](https://img.shields.io/badge/Version-1.5-ff5733.svg)](#) +[![v1.6](https://img.shields.io/badge/Version-1.6-ff5733.svg)](#) [![Build](https://ci.appveyor.com/api/projects/status/a3cn5dpdv146tdji?svg=true)](https://ci.appveyor.com/project/thewhiteninja/ntfstool) @@ -12,7 +12,7 @@
NTFSTool is a forensic tool focused on [NTFS][10] volumes. -It supports reading partition info (mbr, partition table, vbr) but also information on master file table, bitlocker encrypted volume, EFS encrypted files and more. +It supports reading partition info (MBR, partition table, VBR) but also information on Master File Table, Bitlocker encrypted volume, EFS encrypted files, USN journal and more. Download the latest binaries on [AppVeyor](https://ci.appveyor.com/project/thewhiteninja/ntfstool). @@ -23,8 +23,8 @@ See below for some [examples](#examples) of the features! ### Forensics -NTFSTool displays the complete structure of master boot record, volume boot record, partition table and [MFT][8] file record. -It is also possible to dump any file (even $mft or [SAM][9]) or parse [USN journals][6], [LogFile][7] including streams from Alternate Data Stream ([ADS][5]). +NTFSTool displays the complete structure of master boot record, volume boot record, partition table and [$MFT][8] file record. +It is also possible to dump any file (even $MFT or [SAM][9]) or parse and analyze [USN journal][6], [LogFile][7] including streams from Alternate Data Stream ([ADS][5]). $MFT can be dumped as csv or json with [Zone.Identifier][13] parsing to quickly identify downloaded files. The undelete command will search for any file record marked as "not in use" and allow you to retrieve the file (or part of the file if it was already rewritten). It support input from image file, live disk or virtual like [VeraCrypt][11] and [TrueCrypt][12], but you can also use tools like [OSFMount][3] to mount your disk image. Sparse and compressed files (lznt1, xpress) are also supported. @@ -38,6 +38,7 @@ Sparse and compressed files (lznt1, xpress) are also supported. [10]: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc781134(v=ws.10)?redirectedfrom=MSDN [11]: https://www.veracrypt.fr/en/Home.html [12]: http://truecrypt.sourceforge.net/ +[13]: https://www.digital-detective.net/forensic-analysis-of-zone-identifier-stream/ ### Bitlocker support @@ -59,6 +60,25 @@ More information on [Mimikatz Wiki][4] [4]: https://github.com/gentilkiwi/mimikatz/wiki/howto-~-decrypt-EFS-files#installing-the-pfx +### USN Journal analysis + +USN journal records can be analyzed using custom rules to detect suspicious programs and actions but also to have an overview of the journal (% of file deleted, created ...) + +Example of rules: [Rules/default.json](Rules/default.json) + +``` + { + "id": "ccleaner", + "description": "CCleaner is a disk cleanup tool for temporary junk files, web history, logs and even wiping the disk.", + "severity": "high", + "rule": { + "filename": "(.*)ccleaner\\.exe(-([A-F0-9]{8}).pf)?" + } + } +``` + +See an example of run here: [usn.analyze](#usn-analyze) + ### Shell There is a limited shell with few commands (exit, cd, ls, cat, pwd, cp, quit, rec). @@ -81,23 +101,25 @@ Options can be entered as decimal or hex number with "0x" prefix (ex: inode). | [vbr](#vbr) | Display VBR structure and code for a specidifed volume (ntfs, fat32, fat1x, bitlocker supported) | | [extract](#extract) | Extract a file from a volume. | | [image](#image) | Create an image file of a disk or volume. | +| [mft.dump](#mft-dump) | Dump $MFT file in specified format: csv, json, raw. | | [mft.record](#mft-record) | Display FILE record details for a specified MFT inode. Almost all attribute types supported | | [mft.btree](#mft-btree) | Display VCN content and Btree index for an inode | -| [bitlocker](#bitlocker) | Display detailed information and hash ($bitlocker$) for all VMK. It is possible to test a password or recovery key. If it is correct, the decrypted VMK and FVEK is displayed. | -| [bitdecrypt](#bitdecrypt) | Decrypt a volume to a file using password, recovery key or bek. | +| [bitlocker.info](#bitlocker-info) | Display information and hash ($bitlocker$) for all VMK. Test a password or recovery key. | +| [bitlocker.decrypt](#bitlocker-decrypt) | Decrypt a volume to a file using password, recovery key or bek. | +| [bitlocker.fve](#bitlocker-fve) | Display information for the specified FVE block. | | [efs.backup](#efs-backup) | Export EFS keys in PKCS12 (pfx) format. | | [efs.decrypt](#efs-decrypt) | Decrypt EFS encrypted file using keys in PKCS12 (pfx) format. | | [efs.certificate](#efs-certificate) | List, display and export system certificates (SystemCertificates/My/Certificates). | | [efs.key](#efs-key) | List, display, decrypt and export private keys (Crypto/RSA). | | [efs.masterkey](#efs-masterkey) | List, display and decrypt masterkeys (Protect). | -| [fve](#fve) | Display information for the specified FVE block (0, 1, 2) | | [reparse](#reparse) | Parse and display reparse points from \$Extend\$Reparse. | -| [logfile](#logfile) | Dump $LogFile file in specified format: csv, json, raw. | -| [usn](#usn) | Dump $UsnJrnl file in specified format: csv, json, raw. | +| [logfile.dump](#logfile-dump) | Dump $LogFile file in specified format: csv, json, raw. | +| [usn.analyze](#usn-analyze) | Analyze $UsnJrnl file with specified rules. Output : csv or json. | +| [usn.dump](#usn-dump) | Dump $UsnJrnl file in specified format: csv, json, raw. | | [shadow](#shadow) | List volume shadow snapshots from selected disk and volume. | | [streams](#streams) | Display Alternate Data Streams | | [undelete](#undelete) | Search and extract deleted files for a volume. | -| [shell](#shell-1) | Start a mini Unix-like shell | +| [shell](#shell-1) | Start a limited Unix-like shell | | [smart](#smart) | Display S.M.A.R.T data | @@ -362,6 +384,40 @@ Current third-party libs: +### MFT-dump + + + + + + + +
mft.dump disk=2 volume=2 output=d:\mft.raw
+ + [+] $MFT size : 1.00 MiB + [-] Record size : 1024 + [-] Record count: 1024 + [+] Creating d:\mft.raw + [+] Processing data: 1.00 MiB + [+] Closing volume +
mft.dump disk=2 volume=2 output=d:\mft.csv format=csv
+ + [+] $MFT size : 1.00 MiB + [-] Record size : 1024 + [-] Record count: 1024 + [+] Creating d:\mft.csv + [+] Processing data: 1.00 MiB + [+] Closing volume +
Sample of mft.csv (check the end of the last line for Zone.Identifier data)
RecordIndex,InUse,Type,Filename,Ext,Size,Parents,Time_MFT,Time_Create,Time_Alter,Time_Read,Att_Archive,Att_Compressed,Att_Device,Att_Encrypted,Att_Hidden,Att_Normal,Att_NotIndexed,Att_Offline,Att_Readonly,Att_Reparse,Att_Sparse,Att_System,Att_Temp,USN,Hardlinks,ADS,ZoneId,ReferrerUrl,HostUrl
+0,"True","File","$MFT","",1048576,"5","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","False","False","False","False","True","False","False","False","False","False","False","True","False",0,1,"","","",""
+1,"True","File","$MFTMirr","",4096,"5","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","False","False","False","False","True","False","False","False","False","False","False","True","False",0,1,"","","",""
+2,"True","File","$LogFile","",67108864,"5","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","False","False","False","False","True","False","False","False","False","False","False","True","False",0,1,"","","",""
+3,"True","File","$Volume","",0,"5","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","2022-03-17 01:25:10","False","False","False","False","True","False","False","False","False","False","False","True","False",0,1,"","","",""
+...
+397,"True","File","vswhere.exe",".exe",457824,"103911","2020-10-19 18:42:19","2019-06-11 10:07:50","2019-06-11 10:07:52","2021-12-27 14:54:49","True","False","False","False","False","False","False","False","False","False","False","False","False",35944347632,1,"Zone.Identifier","3","https://github.com/microsoft/vswhere/releases","https://github-production-release-asset-2e65be.s3.amazonaws.com/78482723/06868000-5585-11e9-9001-982f1fcb7ef1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20190611%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190611T100747Z&X-Amz-Expires=300&X-Amz-Signature=bc494e9edaafb03874097ae46466c5562d34252f14f21140c70e6a1a1fc5e5c4&X-Amz-SignedHeaders=host&actor_id=40250307&response-content-disposition=attachment%3B%20filename%3Dvswhere.exe&response-content-type=application%2Foctet-stream"
+
+ + ### MFT-record @@ -565,9 +621,9 @@ Current third-party libs:
mft.record disk=2 volume=1 inode=5 (root folder)
-### Bitlocker +### Bitlocker-Info - + - + - +
bitlocker disk=3 volume=1
bitlocker.info disk=3 volume=1
FVE Version : 2 @@ -612,7 +668,7 @@ Current third-party libs: | | | | 865e805367f7bef1 | +--------------------------------------------------------------------------------------------------------------------+
bitlocker disk=3 volume=1 password=badpassword
bitlocker.info disk=3 volume=1 password=badpassword
FVE Version : 2 @@ -631,7 +687,7 @@ Current third-party libs: | 1 | Password | {2dd368f3-37d7-414f-94e6-3c5b86fadd50} | badpassword | Invalid | +--------------------------------------------------------------------------------+
bitlocker disk=3 volume=1 password=123456789
bitlocker.info disk=3 volume=1 password=123456789
FVE Version : 2 @@ -658,9 +714,9 @@ Current third-party libs:
-### Bitdecrypt +### Bitlocker-Decrypt - +
bitdecrypt disk=3 volume=1 output=decrypted.img fvek=35b8197e6d74d8521f49698d5f5565892cf286ae5323c65631965c905a9d7da4
bitlocker.decrypt disk=3 volume=1 output=decrypted.img fvek=35b8197e6d74d8521f49698d5f5565892cf286ae5323c65631965c905a9d7da4
[+] Opening \\?\Volume{09a02598-0000-0000-0002-000000000000}\ @@ -676,6 +732,132 @@ Current third-party libs:
+### Bitlocker-FVE + + + +
bitlocker.fve disk=3 volume=1 fve_block=2
+ + Signature : -FVE-FS- + Size : 57 + Version : 2 + Current State : ENCRYPTED (4) + Next State : ENCRYPTED (4) + Encrypted Size : 536870400 (512.00 MiBs) + Convert Size : 0 + Backup Sectors : 16 + FVE Block 1 : 0000000002100000 + FVE Block 2 : 00000000059e4000 + FVE Block 3 : 00000000092c8000 + Backup Sectors Offset : 0000000002110000 + + FVE Metadata Header + ------------------- + + Size : 840 + Version : 1 + Header Size : 48 + Copy Size : 840 + Volume GUID : {70a57ea3-9b98-4034-8b6a-645f731e2d1e} + Next Counter : 10 + Algorithm : AES-XTS-128 (8004) + Timestamp : 2020-02-26 16:39:17 + + FVE Metadata Entries (5) + ------------------------ + + +----------------------------------------------------------------------------------------------------------------+ + | Id | Version | Size | Entry Type | Value Type | Value | + +----------------------------------------------------------------------------------------------------------------+ + | 1 | 1 | 72 | Drive Label | Unicode | String : TWN NTFSDRIVE 26/02/2020 | + +----------------------------------------------------------------------------------------------------------------+ + | 2 | 1 | 224 | VMK | VMK | Key ID : {2dd368f3-37d7-414f-94e6-3c5b86f | + | | | | | | add50} | + | | | | | | Last Change : 2020-02-26 16:40:00 | + | | | | | | Protection : Password | + | | | | | | | + | | | | | | Property #1 - Stretch Key - 108 | + | | | | | | -------- | + | | | | | | Encryption : STRETCH KEY | + | | | | | | MAC : daea96439babc5d1e7f20c8860ff1ee9 | + | | | | | | | + | | | | | | Property #1.1 - AES-CCM - 80 | + | | | | | | -------- | + | | | | | | Nonce as Hex : 01d5ecbb00f71550 | + | | | | | | Nonce as Time : 2020-02-26 16:39:59 | + | | | | | | Nonce Counter : 00000002 | + | | | | | | MAC : 1dfebdc79a966e72ca806d6a83d8c7ba | + | | | | | | Key : eb51a188df981b54f51698c76d76a8bb | + | | | | | | d22afbbe27603ea6afc34c077726262e | + | | | | | | 5ba07482053d3c36fdecf80f | + | | | | | | | + | | | | | | Property #2 - AES-CCM - 80 | + | | | | | | -------- | + | | | | | | Nonce as Hex : 01d5ecbb00f71550 | + | | | | | | Nonce as Time : 2020-02-26 16:39:59 | + | | | | | | Nonce Counter : 00000003 | + | | | | | | MAC : 175ec23cd799e2bde9d24bf3697919fe | + | | | | | | Key : b76281568419ec3bee89d1eddccf3169 | + | | | | | | 59c466b6b392f40f0875e58168d868d7 | + | | | | | | 0788bd366bec117b11a9fd6e | + +----------------------------------------------------------------------------------------------------------------+ + | 3 | 1 | 316 | VMK | VMK | Key ID : {19b4a3e2-94b3-452f-a614-6212fae | + | | | | | | b1b9d} | + | | | | | | Last Change : 2020-02-26 16:40:07 | + | | | | | | Protection : Recovery Password | + | | | | | | | + | | | | | | Property #1 - Stretch Key - 172 | + | | | | | | -------- | + | | | | | | Encryption : STRETCH KEY | + | | | | | | MAC : b9963d29e1bad1f42e60c3bfb6e3bef5 | + | | | | | | | + | | | | | | Property #1.1 - AES-CCM - 64 | + | | | | | | -------- | + | | | | | | Nonce as Hex : 01d5ecbb00f71550 | + | | | | | | Nonce as Time : 2020-02-26 16:39:59 | + | | | | | | Nonce Counter : 00000004 | + | | | | | | MAC : 8064d679c7d8d1fa8ae548b0844882c7 | + | | | | | | Key : 18d21021d40e3dc99d38c8dd84faed10 | + | | | | | | 370c32095f4f63261ad8ec40 | + | | | | | | | + | | | | | | Property #1.2 - AES-CCM - 80 | + | | | | | | -------- | + | | | | | | Nonce as Hex : 01d5ecbb00f71550 | + | | | | | | Nonce as Time : 2020-02-26 16:39:59 | + | | | | | | Nonce Counter : 00000005 | + | | | | | | MAC : 3d40f2b5fc0091b894b438763fcdf4cd | + | | | | | | Key : a0af0aeda32d977d26ac76f9fc429668 | + | | | | | | 955d2a6a49fe4e2323751924e47e6c39 | + | | | | | | 8c22f7fcd2d4272003cb7a4e | + | | | | | | | + | | | | | | Property #2 - AES-CCM - 80 | + | | | | | | -------- | + | | | | | | Nonce as Hex : 01d5ecbb00f71550 | + | | | | | | Nonce as Time : 2020-02-26 16:39:59 | + | | | | | | Nonce Counter : 00000006 | + | | | | | | MAC : 3a06a06fdb044d850ecd6faf5cf2aec9 | + | | | | | | Key : 97a43d40c695c6d190eba3956ac7c7b1 | + | | | | | | f5fdbbc7f9a61a77c914fa347479c7ac | + | | | | | | 6124ff46865e805367f7bef1 | + | | | | | | | + | | | | | | Property #3 - Unknown (00000015) | + | | | | | | - 28 | + | | | | | | -------- | + | | | | | | Unknown Value Type (21) | + +----------------------------------------------------------------------------------------------------------------+ + | 4 | 1 | 80 | FKEV | AES-CCM | Nonce as Hex : 01d5ecbb00f71550 | + | | | | | | Nonce as Time : 2020-02-26 16:39:59 | + | | | | | | Nonce Counter : 00000008 | + | | | | | | MAC : 2ff7d7f79920e3509fb8d20cb15b62c8 | + | | | | | | Key : 097169b9a5c41420ed2353a4a4210763 | + | | | | | | a8833d1a4a88c6f7c0c45ec7c0959f25 | + | | | | | | 2c8eac3f306e9fd1e693784a | + +----------------------------------------------------------------------------------------------------------------+ + | 5 | 1 | 100 | Volume Header Block | Offset and Size | Offset : 0000000002110000 | + | | | | | | Size : 0000000000002000 | + +----------------------------------------------------------------------------------------------------------------+ +
+ ### EFS-backup @@ -1108,134 +1290,7 @@ Current third-party libs:
-### FVE - - - -
fve disk=3 volume=1 fve_block=2
- - Signature : -FVE-FS- - Size : 57 - Version : 2 - Current State : ENCRYPTED (4) - Next State : ENCRYPTED (4) - Encrypted Size : 536870400 (512.00 MiBs) - Convert Size : 0 - Backup Sectors : 16 - FVE Block 1 : 0000000002100000 - FVE Block 2 : 00000000059e4000 - FVE Block 3 : 00000000092c8000 - Backup Sectors Offset : 0000000002110000 - - FVE Metadata Header - ------------------- - - Size : 840 - Version : 1 - Header Size : 48 - Copy Size : 840 - Volume GUID : {70a57ea3-9b98-4034-8b6a-645f731e2d1e} - Next Counter : 10 - Algorithm : AES-XTS-128 (8004) - Timestamp : 2020-02-26 16:39:17 - - FVE Metadata Entries (5) - ------------------------ - - +----------------------------------------------------------------------------------------------------------------+ - | Id | Version | Size | Entry Type | Value Type | Value | - +----------------------------------------------------------------------------------------------------------------+ - | 1 | 1 | 72 | Drive Label | Unicode | String : TWN NTFSDRIVE 26/02/2020 | - +----------------------------------------------------------------------------------------------------------------+ - | 2 | 1 | 224 | VMK | VMK | Key ID : {2dd368f3-37d7-414f-94e6-3c5b86f | - | | | | | | add50} | - | | | | | | Last Change : 2020-02-26 16:40:00 | - | | | | | | Protection : Password | - | | | | | | | - | | | | | | Property #1 - Stretch Key - 108 | - | | | | | | -------- | - | | | | | | Encryption : STRETCH KEY | - | | | | | | MAC : daea96439babc5d1e7f20c8860ff1ee9 | - | | | | | | | - | | | | | | Property #1.1 - AES-CCM - 80 | - | | | | | | -------- | - | | | | | | Nonce as Hex : 01d5ecbb00f71550 | - | | | | | | Nonce as Time : 2020-02-26 16:39:59 | - | | | | | | Nonce Counter : 00000002 | - | | | | | | MAC : 1dfebdc79a966e72ca806d6a83d8c7ba | - | | | | | | Key : eb51a188df981b54f51698c76d76a8bb | - | | | | | | d22afbbe27603ea6afc34c077726262e | - | | | | | | 5ba07482053d3c36fdecf80f | - | | | | | | | - | | | | | | Property #2 - AES-CCM - 80 | - | | | | | | -------- | - | | | | | | Nonce as Hex : 01d5ecbb00f71550 | - | | | | | | Nonce as Time : 2020-02-26 16:39:59 | - | | | | | | Nonce Counter : 00000003 | - | | | | | | MAC : 175ec23cd799e2bde9d24bf3697919fe | - | | | | | | Key : b76281568419ec3bee89d1eddccf3169 | - | | | | | | 59c466b6b392f40f0875e58168d868d7 | - | | | | | | 0788bd366bec117b11a9fd6e | - +----------------------------------------------------------------------------------------------------------------+ - | 3 | 1 | 316 | VMK | VMK | Key ID : {19b4a3e2-94b3-452f-a614-6212fae | - | | | | | | b1b9d} | - | | | | | | Last Change : 2020-02-26 16:40:07 | - | | | | | | Protection : Recovery Password | - | | | | | | | - | | | | | | Property #1 - Stretch Key - 172 | - | | | | | | -------- | - | | | | | | Encryption : STRETCH KEY | - | | | | | | MAC : b9963d29e1bad1f42e60c3bfb6e3bef5 | - | | | | | | | - | | | | | | Property #1.1 - AES-CCM - 64 | - | | | | | | -------- | - | | | | | | Nonce as Hex : 01d5ecbb00f71550 | - | | | | | | Nonce as Time : 2020-02-26 16:39:59 | - | | | | | | Nonce Counter : 00000004 | - | | | | | | MAC : 8064d679c7d8d1fa8ae548b0844882c7 | - | | | | | | Key : 18d21021d40e3dc99d38c8dd84faed10 | - | | | | | | 370c32095f4f63261ad8ec40 | - | | | | | | | - | | | | | | Property #1.2 - AES-CCM - 80 | - | | | | | | -------- | - | | | | | | Nonce as Hex : 01d5ecbb00f71550 | - | | | | | | Nonce as Time : 2020-02-26 16:39:59 | - | | | | | | Nonce Counter : 00000005 | - | | | | | | MAC : 3d40f2b5fc0091b894b438763fcdf4cd | - | | | | | | Key : a0af0aeda32d977d26ac76f9fc429668 | - | | | | | | 955d2a6a49fe4e2323751924e47e6c39 | - | | | | | | 8c22f7fcd2d4272003cb7a4e | - | | | | | | | - | | | | | | Property #2 - AES-CCM - 80 | - | | | | | | -------- | - | | | | | | Nonce as Hex : 01d5ecbb00f71550 | - | | | | | | Nonce as Time : 2020-02-26 16:39:59 | - | | | | | | Nonce Counter : 00000006 | - | | | | | | MAC : 3a06a06fdb044d850ecd6faf5cf2aec9 | - | | | | | | Key : 97a43d40c695c6d190eba3956ac7c7b1 | - | | | | | | f5fdbbc7f9a61a77c914fa347479c7ac | - | | | | | | 6124ff46865e805367f7bef1 | - | | | | | | | - | | | | | | Property #3 - Unknown (00000015) | - | | | | | | - 28 | - | | | | | | -------- | - | | | | | | Unknown Value Type (21) | - +----------------------------------------------------------------------------------------------------------------+ - | 4 | 1 | 80 | FKEV | AES-CCM | Nonce as Hex : 01d5ecbb00f71550 | - | | | | | | Nonce as Time : 2020-02-26 16:39:59 | - | | | | | | Nonce Counter : 00000008 | - | | | | | | MAC : 2ff7d7f79920e3509fb8d20cb15b62c8 | - | | | | | | Key : 097169b9a5c41420ed2353a4a4210763 | - | | | | | | a8833d1a4a88c6f7c0c45ec7c0959f25 | - | | | | | | 2c8eac3f306e9fd1e693784a | - +----------------------------------------------------------------------------------------------------------------+ - | 5 | 1 | 100 | Volume Header Block | Offset and Size | Offset : 0000000002110000 | - | | | | | | Size : 0000000000002000 | - +----------------------------------------------------------------------------------------------------------------+ -
- - -### reparse +### Reparse
reparse disk=0 volume=4
@@ -1277,9 +1332,9 @@ Current third-party libs:
-### logfile +### Logfile-dump - + -
logfile disk=4 volume=1 output=logfile.csv format=csv
logfile.dump disk=4 volume=1 output=logfile.csv format=csv
[+] Opening \\?\Volume{00000001-0000-0000-0000-000000000000}\ @@ -1295,48 +1350,92 @@ Current third-party libs: [+] Closing volume
Sample of logfile.csv
    LSN,ClientPreviousLSN,UndoNextLSN,ClientID,RecordType,TransactionID,RedoOperation,UndoOperation,MFTClusterIndex,TargetVCN,TargetLCN
-    5269000,5268967,5268967,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
-    5269019,5269000,5269000,0,1,24,UpdateNonresidentValue,Noop,0,0,37594
-    5269044,5269019,5269019,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
-    5269063,5269044,5269044,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
-    5269082,5269063,5269063,0,1,24,UpdateNonresidentValue,Noop,0,0,37594
-    5269103,5269082,5269082,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
-    5269122,5269103,0,0,1,24,ForgetTransaction,CompensationLogRecord,0,0,18446744073709551615
-    5269133,0,0,0,1,24,UpdateResidentValue,UpdateResidentValue,2,13,43703
+
LSN,ClientPreviousLSN,UndoNextLSN,ClientID,RecordType,TransactionID,RedoOperation,UndoOperation,MFTClusterIndex,TargetVCN,TargetLCN
+5269000,5268967,5268967,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
+5269019,5269000,5269000,0,1,24,UpdateNonresidentValue,Noop,0,0,37594
+5269044,5269019,5269019,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
+5269063,5269044,5269044,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
+5269082,5269063,5269063,0,1,24,UpdateNonresidentValue,Noop,0,0,37594
+5269103,5269082,5269082,0,1,24,SetNewAttributeSizes,SetNewAttributeSizes,2,10,43700
+5269122,5269103,0,0,1,24,ForgetTransaction,CompensationLogRecord,0,0,18446744073709551615
+5269133,0,0,0,1,24,UpdateResidentValue,UpdateResidentValue,2,13,43703
-### usn +### USN-analyze - + + + + +
usn disk=4 volume=1 output=usn.csv format=csv
From dump : usn.analyze from=usn_dump rules=d:\rules.json output=d:\usn_analyze_results.csv format=csv
From running system : usn.analyze disk=4 volume=1 rules=d:\rules.json output=d:\usn_analyze_results.csv format=csv
+ + + [+] Loading rules from: d:\rules.json + [-] 4 rules loaded + [+] Creating d:\usn_analyze_results.csv + [-] Mode: fast + [+] Opening \\?\Volume{498eed94-0000-0000-007e-000000000000}\ + [+] Searching for $Extend\$UsnJrnl + [-] Found in file record: 116 + [-] $J stream size: 31.70 KiBs (could be sparse) + [+] Processing USN records: 192 (31.70 KiBs) - 9 matches + [+] Closing volume + [+] Summary: + +------------------------------------------------------+ + | Index | Category | Value | % | + +------------------------------------------------------+ + | 0 | file creation | 125 | 65.10 | + | 1 | file deletion | 0 | 0.00 | + | 2 | file rename | 8 | 4.17 | + | 3 | latest | 2022-03-28 23:23:12 | | + | 4 | oldest | 2022-03-28 13:20:08 | | + | 5 | records count | 192 | 100.00 | + +------------------------------------------------------+ + [+] Rules results: + +------------------------------------------+ + | Index | Rule ID | Count | % | + +------------------------------------------+ + | 0 | executable-create | 9 | 4.69 | + | 1 | ccleaner | 2 | 1.04 | + | 2 | lsass-dump | 1 | 0.52 | + +------------------------------------------+ +
+ + +### USN-dump + +
usn.dump disk=4 volume=1 output=usn.csv format=csv
- [+] Opening \\?\Volume{00000001-0000-0000-0000-000000000000}\ + [+] Opening \\?\Volume{ee732b26-571c-4516-b8fd-32282aa8e66b}\ [+] Finding $Extend\$UsnJrnl record - [+] Found in file record : 41 - [+] Data stream $J size : 2.66 KiBs - [+] Reading $J - [+] Processing entry : 32 + [+] Found in file record: 88008 + [+] $J stream size: 60.24 GiBs (could be sparse) + [+] Creating d:\mft_c.csv + [+] Loading $MFT records + [+] Processing $MFT records: 1.37 GiB + [+] 1436928 record loaded + [+] Processing entry: 322667 (34.12 MiBs) [+] Closing volume
Sample of usn.csv
MajorVersion,MinorVersion,FileReferenceNumber,FileReferenceSequenceNumber,ParentFileReferenceNumber,ParentFileReferenceSequenceNumber,Usn,Timestamp,Reason,SourceInfo,SecurityId,FileAttributes,Filename
-2,0,53,4,5,5,0,2020-02-26 21:43:36,FILE_CREATE,0,0,DIRECTORY,Nouveau dossier
-2,0,53,4,5,5,96,2020-02-26 21:43:36,FILE_CREATE+CLOSE,0,0,DIRECTORY,Nouveau dossier
-2,0,53,4,5,5,192,2020-02-26 21:43:38,RENAME_OLD_NAME,0,0,DIRECTORY,Nouveau dossier
-2,0,53,4,5,5,288,2020-02-26 21:43:38,RENAME_NEW_NAME,0,0,DIRECTORY,test
-2,0,53,4,5,5,360,2020-02-26 21:43:38,RENAME_NEW_NAME+CLOSE,0,0,DIRECTORY,test
-2,0,53,4,5,5,432,2020-02-26 21:43:39,OBJECT_ID_CHANGE,0,0,DIRECTORY,test
-2,0,53,4,5,5,504,2020-02-26 21:43:39,OBJECT_ID_CHANGE+CLOSE,0,0,DIRECTORY,test
-2,0,54,2,53,4,576,2020-02-26 21:43:41,FILE_CREATE,0,0,ARCHIVE,Nouveau document texte.txt
+2,0,53,4,5,5,0,2020-02-26 21:43:36,FILE_CREATE,0,0,DIRECTORY,volume:\Nouveau dossier +2,0,53,4,5,5,96,2020-02-26 21:43:36,FILE_CREATE+CLOSE,0,0,DIRECTORY,volume:\Nouveau dossier +2,0,53,4,5,5,192,2020-02-26 21:43:38,RENAME_OLD_NAME,0,0,DIRECTORY,volume:\Nouveau dossier +2,0,53,4,5,5,288,2020-02-26 21:43:38,RENAME_NEW_NAME,0,0,DIRECTORY,volume:\test +2,0,53,4,5,5,360,2020-02-26 21:43:38,RENAME_NEW_NAME+CLOSE,0,0,DIRECTORY,volume:\test +2,0,53,4,5,5,432,2020-02-26 21:43:39,OBJECT_ID_CHANGE,0,0,DIRECTORY,volume:\test +2,0,53,4,5,5,504,2020-02-26 21:43:39,OBJECT_ID_CHANGE+CLOSE,0,0,DIRECTORY,volume:\test +2,0,54,2,53,4,576,2020-02-26 21:43:41,FILE_CREATE,0,0,ARCHIVE,volume:\test\Nouveau document texte.txt
-### shadow +### Shadow
shadow disk=0 volume=4
@@ -1370,7 +1469,7 @@ Current third-party libs:
-### streams +### Streams
streams disk=0 volume=4 from=c:\test.pdf
@@ -1387,7 +1486,7 @@ Current third-party libs:
-### undelete +### Undelete
undelete disk=4 volume=1
@@ -1424,7 +1523,7 @@ Current third-party libs:
-### shell +### Shell
shell disk=4 volume=1
@@ -1478,7 +1577,7 @@ Current third-party libs:
-### smart +### SMART
smart disk=1
diff --git a/Rules/default.json b/Rules/default.json new file mode 100644 index 0000000..9af559a --- /dev/null +++ b/Rules/default.json @@ -0,0 +1,132 @@ +[ + { + "id": "mimikatz", + "description": "Mimikatz can be use to dump credentials, lateral movement and privilege escalation.", + "severity": "high", + "rule": { + "filename": "(.*)mimikatz\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "lazagne", + "description": "LaZagne is an open source application used to retrieve passwords on a local computer.", + "severity": "high", + "rule": { + "filename": "(.*)lazagne\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "process-hacker", + "description": "Process Hacker can be use as a recon tool to identify and kill processes like anti-virus.", + "severity": "high", + "rule": { + "filename": "(.*)processhacker\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "processxp", + "description": "Process Explorer can be use as a recon tool to identify and kill processes like anti-virus.", + "severity": "high", + "rule": { + "filename": "(.*)processxp(64(a)?)?\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "processdump", + "description": "Procdump can be use to dump a process like lsass.exe to extract credentials.", + "severity": "high", + "rule": { + "filename": "(.*)procdump\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "lsass-dump", + "description": "Dumped LSASS.exe process.", + "severity": "high", + "rule": { + "filename": "lsass.dmp|lsass.dump" + } + }, + { + "id": "cve-2021-34484", + "description": "UserProfileSvcEoP is a local privilege escalation tool that exploits cve-2021-34484.", + "severity": "high", + "rule": { + "filename": "(.*)userprofilesvceop\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "ccleaner", + "description": "CCleaner is a disk cleanup tool for temporary junk files, web history, logs and even wiping the disk.", + "severity": "high", + "rule": { + "filename": "(.*)ccleaner\\.exe(-([A-F0-9]{8}).pf)?" + } + }, + { + "id": "sam-dump", + "description": "Dump of SAM database.", + "severity": "high", + "rule": { + "filename": "sam\\.hiv|sam\\.dump|sam\\.dmp" + } + }, + { + "id": "hash-txt", + "description": "Dump of account hashes by Mimikatz.", + "severity": "medium", + "rule": { + "filename": "hash\\.txt" + } + }, + { + "id": "jaws", + "description": "JAWS is PowerShell script designed to identify potential privilege escalation vectors on Windows systems.", + "severity": "high", + "rule": { + "filename": "jaws-enum\\.ps1" + } + }, + { + "id": "winpeas", + "description": "WinPEAS is a script that search for possible paths to escalate privileges on Windows hosts.", + "severity": "high", + "rule": { + "filename": "winpeas(.*)\\.(exe|bat)" + } + }, + { + "id": "deleted-document", + "description": "Documents have been deleted (word, excel, powerpoint, txt, ...)", + "severity": "medium", + "rule": { + "filename": "(.*)(\\.doc|\\.docx|\\.xls|\\.xlsx|\\.ppt|\\.pptx|\\.zip|\\.rar|\\.7z)", + "reason": [ + "+FILE_DELETE" + ] + } + }, + { + "id": "deleted-media", + "description": "Media have been deleted (video, audio, images ...)", + "severity": "medium", + "rule": { + "filename": "(.*)(\\.avi|\\.mp4|\\.mkv|\\.m4v|\\.divx|\\.mp3|\\.ogg|\\.flac|\\.jpg|\\.jpeg|\\.gif|\\.png|\\.tiff|\\.psd)", + "reason": [ + "+FILE_DELETE" + ] + } + }, + { + "id": "executable-create", + "description": "Excecutable files have been created (exe, dll, sys, bat, ps1 ...)", + "severity": "low", + "rule": { + "filename": "(.*)(\\.exe|\\.dll|\\.sys|\\.vb?|\\.ps1|\\.bat|\\.scr|\\.com)", + "reason": [ + "+FILE_CREATE", + "-DATA_EXTEND" + ] + } + } +] diff --git a/Sources/Commands/command_bitdecrypt.cpp b/Sources/Commands/command_bitlocker.decrypt.cpp similarity index 86% rename from Sources/Commands/command_bitdecrypt.cpp rename to Sources/Commands/command_bitlocker.decrypt.cpp index c5ed310..4c06c60 100644 --- a/Sources/Commands/command_bitdecrypt.cpp +++ b/Sources/Commands/command_bitlocker.decrypt.cpp @@ -21,8 +21,7 @@ int decrypt_volume(std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts) { - std::cout << std::setfill('0'); - utils::ui::title("Decrypt Bitlocker Volume from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Decrypt Bitlocker Volume for " + disk->name() + " > Volume:" + std::to_string(vol->index())); DWORD sector_size = ((PBOOT_SECTOR_FAT32)vol->bootsector())->bytePerSector; @@ -135,40 +134,43 @@ int decrypt_volume(std::shared_ptr disk, std::shared_ptr vol, std: namespace commands { - namespace bitdecrypt + namespace bitlocker { - int dispatch(std::shared_ptr opts) + namespace decrypt { - std::ios_base::fmtflags flag_backup(std::cout.flags()); - - std::shared_ptr disk = get_disk(opts); - if (disk != nullptr) + int dispatch(std::shared_ptr opts) { - std::shared_ptr volume = disk->volumes(opts->volume); - if (volume != nullptr) + std::ios_base::fmtflags flag_backup(std::cout.flags()); + + std::shared_ptr disk = get_disk(opts); + if (disk != nullptr) { - if (opts->output == "") + std::shared_ptr volume = disk->volumes(opts->volume); + if (volume != nullptr) { - invalid_option(opts, "output", opts->output); + if (opts->output == "") + { + invalid_option(opts, "output", opts->output); + } + if (opts->fvek == "") + { + invalid_option(opts, "fvek", opts->fvek); + } + decrypt_volume(disk, volume, opts); } - if (opts->fvek == "") + else { - invalid_option(opts, "fvek", opts->fvek); + invalid_option(opts, "volume", opts->volume); } - decrypt_volume(disk, volume, opts); } else { - invalid_option(opts, "volume", opts->volume); + invalid_option(opts, "disk", opts->disk); } - } - else - { - invalid_option(opts, "disk", opts->disk); - } - std::cout.flags(flag_backup); - return 0; + std::cout.flags(flag_backup); + return 0; + } } } } \ No newline at end of file diff --git a/Sources/Commands/command_fve.cpp b/Sources/Commands/command_bitlocker.fve.cpp similarity index 93% rename from Sources/Commands/command_fve.cpp rename to Sources/Commands/command_bitlocker.fve.cpp index 82ebd3f..358724d 100644 --- a/Sources/Commands/command_fve.cpp +++ b/Sources/Commands/command_bitlocker.fve.cpp @@ -198,8 +198,8 @@ std::vector get_fve_entry_values(PFVE_ENTRY entry, const std::strin return ret; } -void print_bitlocker_vbr(std::shared_ptr disk, std::shared_ptr vol, unsigned long block_id) { - std::cout << std::setfill('0'); +void print_bitlocker_vbr(std::shared_ptr disk, std::shared_ptr vol, unsigned long block_id) +{ utils::ui::title("FVE Info from " + disk->name() + " > Volume:" + std::to_string(vol->index())); PBOOT_SECTOR_COMMON pbsc = (PBOOT_SECTOR_COMMON)vol->bootsector(); @@ -243,39 +243,44 @@ void print_bitlocker_vbr(std::shared_ptr disk, std::shared_ptr vol } } -namespace commands { - namespace fve { - int dispatch(std::shared_ptr opts) +namespace commands +{ + namespace bitlocker + { + namespace fve { - std::ios_base::fmtflags flag_backup(std::cout.flags()); - - std::shared_ptr disk = get_disk(opts); - if (disk != nullptr) + int dispatch(std::shared_ptr opts) { - std::shared_ptr volume = disk->volumes(opts->volume); - if (volume != nullptr) + std::ios_base::fmtflags flag_backup(std::cout.flags()); + + std::shared_ptr disk = get_disk(opts); + if (disk != nullptr) { - if ((opts->fve_block >= 0) && (opts->fve_block < 3)) + std::shared_ptr volume = disk->volumes(opts->volume); + if (volume != nullptr) { - print_bitlocker_vbr(disk, volume, opts->fve_block); + if ((opts->fve_block >= 0) && (opts->fve_block < 3)) + { + print_bitlocker_vbr(disk, volume, opts->fve_block); + } + else + { + invalid_option(opts, "fve_block", opts->fve_block); + } } else { - invalid_option(opts, "fve_block", opts->fve_block); + invalid_option(opts, "volume", opts->volume); } } else { - invalid_option(opts, "volume", opts->volume); + invalid_option(opts, "disk", opts->disk); } - } - else - { - invalid_option(opts, "disk", opts->disk); - } - std::cout.flags(flag_backup); - return 0; + std::cout.flags(flag_backup); + return 0; + } } } } \ No newline at end of file diff --git a/Sources/Commands/command_bitlocker.cpp b/Sources/Commands/command_bitlocker.info.cpp similarity index 98% rename from Sources/Commands/command_bitlocker.cpp rename to Sources/Commands/command_bitlocker.info.cpp index 8cabaeb..7d7fcc6 100644 --- a/Sources/Commands/command_bitlocker.cpp +++ b/Sources/Commands/command_bitlocker.info.cpp @@ -23,7 +23,6 @@ void print_test_bitlocker_password(std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts) { - std::cout << std::setfill('0'); utils::ui::title("Bitlocker Password Test for " + disk->name() + " > Volume:" + std::to_string(vol->index())); PBOOT_SECTOR_COMMON pbsc = (PBOOT_SECTOR_COMMON)vol->bootsector(); @@ -145,7 +144,6 @@ void print_test_bitlocker_password(std::shared_ptr disk, std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts) { - std::cout << std::setfill('0'); utils::ui::title("Bitlocker Recovery Key Test for " + disk->name() + " > Volume:" + std::to_string(vol->index())); PBOOT_SECTOR_COMMON pbsc = (PBOOT_SECTOR_COMMON)vol->bootsector(); @@ -267,7 +265,6 @@ void print_test_bitlocker_recovery(std::shared_ptr disk, std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts) { - std::cout << std::setfill('0'); utils::ui::title("Bitlocker Encryption Key Test for " + disk->name() + " > Volume:" + std::to_string(vol->index())); PBOOT_SECTOR_COMMON pbsc = (PBOOT_SECTOR_COMMON)vol->bootsector(); @@ -389,8 +386,7 @@ void print_test_bitlocker_bek(std::shared_ptr disk, std::shared_ptr disk, std::shared_ptr vol) { - std::cout << std::setfill('0'); - utils::ui::title("Bitlocker Info from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Bitlocker Info for " + disk->name() + " > Volume:" + std::to_string(vol->index())); PBOOT_SECTOR_COMMON pbsc = (PBOOT_SECTOR_COMMON)vol->bootsector(); if (strncmp((char*)pbsc->oemID, "-FVE-FS-", 8) == 0) @@ -500,7 +496,6 @@ std::string get_guid_from_volume(std::shared_ptr vol) void list_guid_for_all_disks(std::vector> disks) { - std::cout << std::setfill('0'); utils::ui::title("Bitlocker Recovery Key GUIDs"); std::shared_ptr table = std::make_shared(); @@ -584,17 +579,20 @@ namespace commands { namespace bitlocker { - int dispatch(std::shared_ptr opts) + namespace info { - if (opts->password != "" || opts->recovery != "" || opts->bek != "") + int dispatch(std::shared_ptr opts) { - test_password(opts); - } - else - { - print_bitlocker(opts); + if (opts->password != "" || opts->recovery != "" || opts->bek != "") + { + test_password(opts); + } + else + { + print_bitlocker(opts); + } + return 0; } - return 0; } } diff --git a/Sources/Commands/command_efs_backup.cpp b/Sources/Commands/command_efs.backup.cpp similarity index 98% rename from Sources/Commands/command_efs_backup.cpp rename to Sources/Commands/command_efs.backup.cpp index 86468f8..9a680b2 100644 --- a/Sources/Commands/command_efs_backup.cpp +++ b/Sources/Commands/command_efs.backup.cpp @@ -13,8 +13,7 @@ int backup_keys(std::shared_ptr disk, std::shared_ptr vol, std::sh { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Backup certificates and keys from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Backup certificates and keys for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; diff --git a/Sources/Commands/command_efs_certificate.cpp b/Sources/Commands/command_efs.certificate.cpp similarity index 96% rename from Sources/Commands/command_efs_certificate.cpp rename to Sources/Commands/command_efs.certificate.cpp index d5287b0..35eddbf 100644 --- a/Sources/Commands/command_efs_certificate.cpp +++ b/Sources/Commands/command_efs.certificate.cpp @@ -9,8 +9,7 @@ int show_certificate(std::shared_ptr disk, std::shared_ptr vol, st { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Display certificate from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Display certificate for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -137,8 +136,7 @@ int list_certificates(std::shared_ptr disk, std::shared_ptr vol, s { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("List certificates from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("List certificates for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; diff --git a/Sources/Commands/command_efs_decrypt.cpp b/Sources/Commands/command_efs.decrypt.cpp similarity index 98% rename from Sources/Commands/command_efs_decrypt.cpp rename to Sources/Commands/command_efs.decrypt.cpp index 54ade56..e004b93 100644 --- a/Sources/Commands/command_efs_decrypt.cpp +++ b/Sources/Commands/command_efs.decrypt.cpp @@ -101,7 +101,7 @@ int decrypt_file(std::shared_ptr record, std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts) { - utils::ui::title("Decrypt EFS file from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Decrypt EFS file for " + disk->name() + " > Volume:" + std::to_string(vol->index())); if (!commands::helpers::is_ntfs(disk, vol)) return 1; diff --git a/Sources/Commands/command_efs_key.cpp b/Sources/Commands/command_efs.key.cpp similarity index 97% rename from Sources/Commands/command_efs_key.cpp rename to Sources/Commands/command_efs.key.cpp index 27544ad..cc3d2d4 100644 --- a/Sources/Commands/command_efs_key.cpp +++ b/Sources/Commands/command_efs.key.cpp @@ -10,8 +10,7 @@ int decrypt_key(std::shared_ptr disk, std::shared_ptr vol, std::sh { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Decrypt key from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Decrypt key for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -210,8 +209,7 @@ int show_key(std::shared_ptr disk, std::shared_ptr vol, std::share { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Display key from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Display key for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -367,8 +365,7 @@ int list_keys(std::shared_ptr disk, std::shared_ptr vol, std::shar { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("List keys from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("List keys for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; diff --git a/Sources/Commands/command_efs_masterkey.cpp b/Sources/Commands/command_efs.masterkey.cpp similarity index 96% rename from Sources/Commands/command_efs_masterkey.cpp rename to Sources/Commands/command_efs.masterkey.cpp index b9b5700..02a8cc1 100644 --- a/Sources/Commands/command_efs_masterkey.cpp +++ b/Sources/Commands/command_efs.masterkey.cpp @@ -9,8 +9,7 @@ int decrypt_masterkey(std::shared_ptr disk, std::shared_ptr vol, s { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Decrypt masterkey from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Decrypt masterkey for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -65,8 +64,7 @@ int show_masterkey(std::shared_ptr disk, std::shared_ptr vol, std: { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Display masterkey from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Display masterkey for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -208,8 +206,7 @@ int list_masterkeys(std::shared_ptr disk, std::shared_ptr vol, std { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("List masterkeys from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("List masterkeys for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; diff --git a/Sources/Commands/command_extract.cpp b/Sources/Commands/command_extract.cpp index e579427..cd89e4c 100644 --- a/Sources/Commands/command_extract.cpp +++ b/Sources/Commands/command_extract.cpp @@ -20,8 +20,7 @@ int extract_file(std::shared_ptr disk, std::shared_ptr vol, std::s { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Extract file from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Extract file for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -31,6 +30,11 @@ int extract_file(std::shared_ptr disk, std::shared_ptr vol, std::s std::cout << "[-] Record Num : " << record->header()->MFTRecordIndex << " (" << utils::format::hex(record->header()->MFTRecordIndex, true) << ")" << std::endl; + if (stream_name != "") + { + std::cout << "[-] Stream : " << stream_name << std::endl; + } + std::cout << "[-] Destination : " << opts->output << std::endl; PMFT_RECORD_ATTRIBUTE_STANDARD_INFORMATION stdinfo = nullptr; diff --git a/Sources/Commands/command_help.cpp b/Sources/Commands/command_help.cpp index dc8b217..e27086d 100644 --- a/Sources/Commands/command_help.cpp +++ b/Sources/Commands/command_help.cpp @@ -9,7 +9,7 @@ #include #include -#define VERSION "1.5" +#define VERSION "1.6" void usage(const char* binname) { @@ -17,29 +17,31 @@ void usage(const char* binname) std::cerr << std::endl; std::cerr << "Version: " << VERSION << " (Build date: " << __DATE__ << " " << __TIME__ << ")" << std::endl << std::endl; std::cerr << "Commands:" << std::endl; - std::cerr << " info : list and display physical disks and volumes" << std::endl; - std::cerr << " mbr : display master boot record" << std::endl; - std::cerr << " gpt : display GUID partition table" << std::endl; - std::cerr << " vbr : display volume boot record" << std::endl; - std::cerr << " mft.record : display master file table" << std::endl; - std::cerr << " mft.btree : display index btree" << std::endl; - std::cerr << " extract : extract a file" << std::endl; - std::cerr << " bitlocker : display bitlocker GUID/status and test password, recovery or BEK file" << std::endl; - std::cerr << " bitdecrypt : decrypt volume to an image file" << std::endl; - std::cerr << " fve : display FVE metadata" << std::endl; - std::cerr << " efs.backup : Export EFS keys from a volume" << std::endl; - std::cerr << " efs.decrypt : Decrypt EFS encrypted file from backup key" << std::endl; - std::cerr << " efs.certificate : list, display and export system certificates" << std::endl; - std::cerr << " efs.key : list, display, decrypt and export private keys" << std::endl; - std::cerr << " efs.masterkey : list, display and decrypt masterkeys" << std::endl; - std::cerr << " logfile : dump and parse log file" << std::endl; - std::cerr << " usn : dump and parse usn journal" << std::endl; - std::cerr << " shadow : list volume shadow copies" << std::endl; - std::cerr << " reparse : parse and display reparse points" << std::endl; - std::cerr << " undelete : find deleted files" << std::endl; - std::cerr << " shell : start a mini-shell" << std::endl; - std::cerr << " smart : display SMART data" << std::endl; - std::cerr << " help : display this message or command help" << std::endl; + std::cerr << " info : list and display physical disks and volumes" << std::endl; + std::cerr << " mbr : display master boot record" << std::endl; + std::cerr << " gpt : display GUID partition table" << std::endl; + std::cerr << " vbr : display volume boot record" << std::endl; + std::cerr << " mft.btree : display index btree" << std::endl; + std::cerr << " mft.dump : dump MFT (raw, csv, json)" << std::endl; + std::cerr << " mft.record : display master file table" << std::endl; + std::cerr << " extract : extract a file" << std::endl; + std::cerr << " bitlocker.info : display bitlocker GUID/status and test password, recovery or BEK file" << std::endl; + std::cerr << " bitlocker.decrypt: decrypt volume to an image file" << std::endl; + std::cerr << " bitlocker.fve : display FVE metadata" << std::endl; + std::cerr << " efs.backup : Export EFS keys from a volume" << std::endl; + std::cerr << " efs.decrypt : Decrypt EFS encrypted file from backup key" << std::endl; + std::cerr << " efs.certificate : list, display and export system certificates" << std::endl; + std::cerr << " efs.key : list, display, decrypt and export private keys" << std::endl; + std::cerr << " efs.masterkey : list, display and decrypt masterkeys" << std::endl; + std::cerr << " logfile.dump : dump log file (raw, csv, json)" << std::endl; + std::cerr << " usn.dump : dump usn journal (raw, csv, json)" << std::endl; + std::cerr << " usn.analyze : analyze usn journal with specified rules (csv, json)" << std::endl; + std::cerr << " shadow : list volume shadow copies" << std::endl; + std::cerr << " reparse : parse and display reparse points" << std::endl; + std::cerr << " undelete : find deleted files" << std::endl; + std::cerr << " shell : start a mini-shell" << std::endl; + std::cerr << " smart : display SMART data" << std::endl; + std::cerr << " help : display this message or command help" << std::endl; std::cerr << std::endl; std::cerr << "Need help for a command?" << std::endl; std::cerr << " help [command]" << std::endl; @@ -122,47 +124,61 @@ void print_help_mft_btree(const char* name) command_examples(name, "Display Index B-tree for disk 0, volume 2 and from \"c:\\file.bin\"", "mft.btree disk=0 volume=2 from \"c:\\file.bin\""); } +void print_help_mft_dump(const char* name) +{ + command_header("mft.dump"); + command_description(name, "mft.dump [disk id] [volume id] [output] (format)", "Dump $MFT for selected disk, volume and inode/path"); + command_examples(name, "Dump raw $MFT for disk 0, volume 2 to a file", "mft.dump disk=0 volume=2 output=myvolume.mft"); + command_examples(name, "Parse $MFT for disk 0, volume 2 and output results in a CSV file", "mft.dump disk=0 volume=2 output=my_mft.json format=json"); +} + void print_help_bitlocker(const char* name) { command_header("bitlocker"); - command_description(name, "bitlocker [disk id] [volume id] (password | recovery | bek)", "Provides Bitlocker information for selected disk, volume"); - command_examples(name, "Display Bitlocker information for disk 2, volume 4", "bitlocker disk=2 volume=4"); - command_examples(name, "Test a password for encrypted for disk 2 and volume 4", "bitlocker disk=0 volume=2 password=123456"); - command_examples(name, "Test a recovery key for encrypted for disk 2 and volume 4", "bitlocker disk=0 volume=2 recovery=123456-234567-345678-456789-567890-678901-789012-890123"); - command_examples(name, "Test a BEK file for encrypted for disk 2 and volume 4", "bitlocker disk=0 volume=2 bek=H:\\3926293F-E661-4417-A36B-B41175B4D862.BEK"); + command_description(name, "bitlocker.info [disk id] [volume id] (password | recovery | bek)", "Provides Bitlocker information for selected disk, volume"); + command_examples(name, "Display Bitlocker information for disk 2, volume 4", "bitlocker.info disk=2 volume=4"); + command_examples(name, "Test a password for encrypted for disk 2 and volume 4", "bitlocker.info disk=0 volume=2 password=123456"); + command_examples(name, "Test a recovery key for encrypted for disk 2 and volume 4", "bitlocker.info disk=0 volume=2 recovery=123456-234567-345678-456789-567890-678901-789012-890123"); + command_examples(name, "Test a BEK file for encrypted for disk 2 and volume 4", "bitlocker.info disk=0 volume=2 bek=H:\\3926293F-E661-4417-A36B-B41175B4D862.BEK"); } void print_help_bitdecrypt(const char* name) { - command_header("bitdecrypt"); - command_description(name, "bitdecrypt [disk id] [volume id] [fvek] [output]", "Decrypt Bitlocker encrypted volume to a file using the Full Volume Encryption Key (FVEK)"); - command_examples(name, "Decrypt disk 2, volume 4 to decrypted.img", "bitdecrypt disk=2 volume=4 fvek=21DA18B8434D864D11654FE84AAB1BDDF135DFDE912EBCAD54A6D87CB8EF64AC output=decrypted.img"); + command_header("bitlocker.decrypt"); + command_description(name, "bitlocker.decrypt [disk id] [volume id] [fvek] [output]", "Decrypt Bitlocker encrypted volume to a file using the Full Volume Encryption Key (FVEK)"); + command_examples(name, "Decrypt disk 2, volume 4 to decrypted.img", "bitlocker.decrypt disk=2 volume=4 fvek=21DA18B8434D864D11654FE84AAB1BDDF135DFDE912EBCAD54A6D87CB8EF64AC output=decrypted.img"); } void print_help_fve(const char* name) { - command_header("fve"); - command_description(name, "fve [disk id] [volume id] (block)", "Display FVE metadata information for an Bitlocker encrypted volume"); - command_examples(name, "Display FVE metadata for disk 0, volume 1", "fve disk=0 volume=1"); - command_examples(name, "Display FVE metadata for disk 2, volume 4 and FVE block 2", "fve disk=2 volume=4 fve_block=2"); + command_header("bitlocker.fve"); + command_description(name, "bitlocker.fve [disk id] [volume id] (block)", "Display FVE metadata information for an Bitlocker encrypted volume"); + command_examples(name, "Display FVE metadata for disk 0, volume 1", "bitlocker.fve disk=0 volume=1"); + command_examples(name, "Display FVE metadata for disk 2, volume 4 and FVE block 2", "bitlocker.fve disk=2 volume=4 fve_block=2"); } void print_help_logfile(const char* name) { - command_header("logfile"); - command_description(name, "logfile [disk id] [volume id] [output] (format)", "Dump or parse the $LogFile of a NTFS volume "); - command_examples(name, "Dump raw $LogFile for disk 1, volume 2 to log.dat", "logfile disk=1 volume=2 output=log.dat"); - command_examples(name, "Parse logfile for disk 2, volume 4 and output results in csv file", "logfile disk=2 volume=4 output=log.csv format=csv"); + command_header("logfile.dump"); + command_description(name, "logfile.dump [disk id] [volume id] [output] (format)", "Dump and parse $LogFile of a NTFS volume (raw, csv, json)"); + command_examples(name, "Dump raw $LogFile for disk 1, volume 2 to log.dat", "logfile.dump disk=1 volume=2 output=log.dat"); + command_examples(name, "Parse $LogFile for disk 2, volume 4 and output results in csv file", "logfile.dump disk=2 volume=4 output=log.csv format=csv"); } -void print_help_usn(const char* name) +void print_help_usn_dump(const char* name) { - command_header("usn"); - command_description(name, "usn [disk id] [volume id] [output] (format)", "Dump or parse the $UsnJrnl of a NTFS volume (raw, csv, json)"); - command_examples(name, "Dump raw $UsnJrnl for disk 1, volume 2 to usn.dat", "usn disk=1 volume=2 output=usn.dat"); - command_examples(name, "Parse usn journal for disk 2, volume 4 and output results in json file", "usn disk=2 volume=4 output=usn.json format=json"); + command_header("usn.dump"); + command_description(name, "usn.dump [disk id] [volume id] [output] (format)", "Dump and parse $UsnJrnl of a NTFS volume (raw, csv, json)"); + command_examples(name, "Dump raw $UsnJrnl for disk 1, volume 2 to usn.dat", "us.dumpn disk=1 volume=2 output=usn.dat"); + command_examples(name, "Parse $UsnJrnl for disk 2, volume 4 and output results in json file", "usn.dump disk=2 volume=4 output=usn.json format=json"); } +void print_help_usn_analyze(const char* name) +{ + command_header("usn.analyze"); + command_description(name, "usn.analyze [disk id] [volume id] [rules] [output] (format)", "Parse and filter $UsnJrnl of a NTFS volume with specified rules (csv, json)"); + command_examples(name, "Parse and filer $UsnJrnl for disk 2, volume 4 and output results in json file", "usn.analyze disk=2 volume=4 rules=myrules.json output=usn.json format=json"); +} void print_help_efs_masterkey(const char* name) { @@ -283,31 +299,33 @@ namespace commands } else { - if (opts->subcommand == "help") { print_help_help(name.c_str()); return; } - if (opts->subcommand == "info") { print_help_info(name.c_str()); return; } - if (opts->subcommand == "mbr") { print_help_mbr(name.c_str()); return; } - if (opts->subcommand == "gpt") { print_help_gpt(name.c_str()); return; } - if (opts->subcommand == "vbr") { print_help_vbr(name.c_str()); return; } - if (opts->subcommand == "mft") { print_help_mft_record(name.c_str()); return; } - if (opts->subcommand == "btree") { print_help_mft_btree(name.c_str()); return; } - if (opts->subcommand == "extract") { print_help_extract(name.c_str()); return; } - if (opts->subcommand == "bitlocker") { print_help_bitlocker(name.c_str()); return; } if (opts->subcommand == "bitdecrypt") { print_help_bitdecrypt(name.c_str()); return; } - if (opts->subcommand == "fve") { print_help_fve(name.c_str()); return; } - if (opts->subcommand == "image") { print_help_image(name.c_str()); return; } - if (opts->subcommand == "shadow") { print_help_shadow(name.c_str()); return; } - if (opts->subcommand == "logfile") { print_help_logfile(name.c_str()); return; } - if (opts->subcommand == "reparse") { print_help_reparse(name.c_str()); return; } - if (opts->subcommand == "usn") { print_help_usn(name.c_str()); return; } + if (opts->subcommand == "bitlocker") { print_help_bitlocker(name.c_str()); return; } if (opts->subcommand == "efs.backup") { print_help_efs_backup(name.c_str()); return; } if (opts->subcommand == "efs.certificate") { print_help_efs_certificate(name.c_str()); return; } if (opts->subcommand == "efs.decrypt") { print_help_efs_decrypt(name.c_str()); return; } if (opts->subcommand == "efs.key") { print_help_efs_key(name.c_str()); return; } if (opts->subcommand == "efs.masterkey") { print_help_efs_masterkey(name.c_str()); return; } - if (opts->subcommand == "undelete") { print_help_undelete(name.c_str()); return; } + if (opts->subcommand == "extract") { print_help_extract(name.c_str()); return; } + if (opts->subcommand == "fve") { print_help_fve(name.c_str()); return; } + if (opts->subcommand == "gpt") { print_help_gpt(name.c_str()); return; } + if (opts->subcommand == "help") { print_help_help(name.c_str()); return; } + if (opts->subcommand == "image") { print_help_image(name.c_str()); return; } + if (opts->subcommand == "info") { print_help_info(name.c_str()); return; } + if (opts->subcommand == "logfile.dump") { print_help_logfile(name.c_str()); return; } + if (opts->subcommand == "mbr") { print_help_mbr(name.c_str()); return; } + if (opts->subcommand == "mft.btree") { print_help_mft_btree(name.c_str()); return; } + if (opts->subcommand == "mft.dump") { print_help_mft_dump(name.c_str()); return; } + if (opts->subcommand == "mft.record") { print_help_mft_record(name.c_str()); return; } + if (opts->subcommand == "reparse") { print_help_reparse(name.c_str()); return; } + if (opts->subcommand == "shadow") { print_help_shadow(name.c_str()); return; } if (opts->subcommand == "shell") { print_help_shell(name.c_str()); return; } if (opts->subcommand == "smart") { print_help_smart(name.c_str()); return; } if (opts->subcommand == "streams") { print_help_streams(name.c_str()); return; } + if (opts->subcommand == "undelete") { print_help_undelete(name.c_str()); return; } + if (opts->subcommand == "usn.analyze") { print_help_usn_analyze(name.c_str()); return; } + if (opts->subcommand == "usn.dump") { print_help_usn_dump(name.c_str()); return; } + if (opts->subcommand == "vbr") { print_help_vbr(name.c_str()); return; } usage(name.c_str()); } diff --git a/Sources/Commands/command_info.cpp b/Sources/Commands/command_info.cpp index 95316b1..c57db59 100644 --- a/Sources/Commands/command_info.cpp +++ b/Sources/Commands/command_info.cpp @@ -106,7 +106,7 @@ void print_hardware_disk(std::shared_ptr disk) { } partitions->add_item_line(volume->label()); - partitions->add_item_line(utils::strings::join(volume->mountpoints(), ", ")); + partitions->add_item_line(utils::strings::join_vec(volume->mountpoints(), ", ")); partitions->add_item_line(volume->filesystem()); partitions->add_item_line(utils::format::hex(volume->offset())); partitions->add_item_line(utils::format::hex(volume->size()) + " (" + utils::format::size(volume->size()) + ")"); @@ -227,7 +227,7 @@ void print_image_disk(std::shared_ptr disk) { } void print_hardware_volume(std::shared_ptr disk, std::shared_ptr vol) { - utils::ui::title("Info for PhysicalDrive:" + std::to_string(disk->index()) + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Info for " + disk->name() + " > Volume:" + std::to_string(vol->index())); if (vol->serial_number()) { @@ -258,7 +258,7 @@ void print_hardware_volume(std::shared_ptr disk, std::shared_ptr v std::cout << " Mounted : " << (vol->is_mounted() ? "True" : "False"); if (vol->is_mounted()) { - std::cout << " (" << utils::strings::join(vol->mountpoints(), ", ") << ")"; + std::cout << " (" << utils::strings::join_vec(vol->mountpoints(), ", ") << ")"; } std::cout << std::endl; std::cout << " Bitlocker : " << (vol->bitlocker().bitlocked ? "True" : "False"); diff --git a/Sources/Commands/command_logfile.cpp b/Sources/Commands/command_logfile.dump.cpp similarity index 66% rename from Sources/Commands/command_logfile.cpp rename to Sources/Commands/command_logfile.dump.cpp index 4f06f32..c7dbec4 100644 --- a/Sources/Commands/command_logfile.cpp +++ b/Sources/Commands/command_logfile.dump.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include void fixup_sequence(PRECORD_PAGE_HEADER prh) { @@ -76,48 +78,24 @@ std::vector get_log_clients(PRESTART_AREA ra) return ret; } -void print_record_log(HANDLE outputfile, PRECORD_LOG rl, const std::string& format) +void _add_record(std::shared_ptr ffile, PRECORD_LOG rl) { - std::string to_write; - if (format == "csv") - { - std::ostringstream entry; - entry << rl->lsn << ","; - entry << rl->client_previous_lsn << ","; - entry << rl->client_undo_next_lsn << ","; - entry << rl->client_id.client_index << ","; - entry << rl->record_type << ","; - entry << rl->transaction_id << ","; - entry << constants::disk::logfile::operation(rl->redo_operation) << ","; - entry << constants::disk::logfile::operation(rl->undo_operation) << ","; - entry << rl->mft_cluster_index << ","; - entry << rl->target_vcn << ","; - entry << rl->target_lcn; - entry << std::endl; - to_write = entry.str(); - } - if (format == "json") - { - nlohmann::json j; - j["lsn"] = rl->lsn; - j["previous_lsn"] = rl->client_previous_lsn; - j["undonext_lsn"] = rl->client_undo_next_lsn; - j["client_id"] = rl->client_id.client_index; - j["record_type"] = rl->record_type; - j["transaction_id"] = rl->transaction_id; - j["redo_op"] = constants::disk::logfile::operation(rl->redo_operation); - j["undo_op"] = constants::disk::logfile::operation(rl->undo_operation); - j["mft_index"] = rl->mft_cluster_index; - j["target_vcn"] = rl->target_vcn; - j["target_lcn"] = rl->target_lcn; - to_write = j.dump() + ",\n"; - } - - DWORD written = 0; - DWORD write_size = 0; - if (!FAILED(SizeTToDWord(to_write.size(), &write_size))) WriteFile(outputfile, to_write.c_str(), write_size, &written, NULL); + ffile->add_item(rl->lsn); + ffile->add_item(rl->client_previous_lsn); + ffile->add_item(rl->client_undo_next_lsn); + ffile->add_item(rl->client_id.client_index); + ffile->add_item(rl->record_type); + ffile->add_item(rl->transaction_id); + ffile->add_item(constants::disk::logfile::operation(rl->redo_operation)); + ffile->add_item(constants::disk::logfile::operation(rl->undo_operation)); + ffile->add_item(rl->mft_cluster_index); + ffile->add_item(rl->target_vcn); + ffile->add_item(rl->target_lcn); + + ffile->new_line(); } + int print_logfile_records(std::shared_ptr disk, std::shared_ptr vol, const std::string& format, std::string output) { if (!commands::helpers::is_ntfs(disk, vol)) return 1; @@ -134,46 +112,41 @@ int print_logfile_records(std::shared_ptr disk, std::shared_ptr vo ULONG64 total_size = record->datasize(); std::cout << "[+] $LogFile size : " << utils::format::size(total_size) << std::endl; - HANDLE houtput = CreateFileA(output.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); - if (houtput == INVALID_HANDLE_VALUE) - { - std::cout << "[!] Failed to create output file" << std::endl; - return 1; - } + std::cout << "[+] Creating " << output << std::endl; if (format == "raw") { - ULONG64 processed_count = 0; - for (auto& block : record->process_data()) - { - DWORD written = 0; - WriteFile(houtput, block.first, block.second, &written, NULL); - std::cout << "\r[+] Processed data size : " << std::to_string(processed_count) << " (" << std::to_string(100 * processed_count / total_size) << "%)"; - processed_count += block.second; - } - } - else if (format == "csv" || format == "json") - { - if (format == "csv") + HANDLE houtput = CreateFileA(output.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + if (houtput == INVALID_HANDLE_VALUE) { - std::string header = "LSN,ClientPreviousLSN,UndoNextLSN,ClientID,RecordType,TransactionID,RedoOperation,UndoOperation,MFTClusterIndex,TargetVCN,TargetLCN\n"; - DWORD written = 0; - WriteFile(houtput, header.c_str(), static_cast(header.size()), &written, NULL); + std::cout << "[!] Failed to create output file" << std::endl; + return 1; } - if (format == "json") + + ULONG64 processed_size = 0; + + for (auto& block : record->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, 1024 * 1024, true)) { + std::cout << "\r[+] Processing data: " << utils::format::size(processed_size) << " "; + processed_size += block.second; + DWORD written = 0; - WriteFile(houtput, "[\n", 2, &written, NULL); + WriteFile(houtput, block.first, block.second, &written, NULL); } + std::cout << "\r[+] Processing data: " << utils::format::size(processed_size); - std::shared_ptr> logfile = record->data(); + CloseHandle(houtput); - std::cout << "[+] Parsing $LogFile Restart Pages" << std::endl; + std::cout << "[+] Closing volume" << std::endl; + } + else if (format == "json" || format == "csv") + { + std::shared_ptr> logfile = record->data(); PRESTART_PAGE_HEADER newest_restart_header = find_newest_restart_page(logfile->data()); PRESTART_AREA newest_restart_area = POINTER_ADD(PRESTART_AREA, newest_restart_header, newest_restart_header->restart_area_offset); - std::cout << "[+] Newest Restart Page LSN : " << std::to_string(newest_restart_area->current_lsn) << std::endl; + std::cout << "[-] Newest Restart Page LSN : " << std::to_string(newest_restart_area->current_lsn) << std::endl; if (newest_restart_area->flags & MFT_LOGFILE_RESTART_AREA_FLAG_VOLUME_CLEANLY_UNMOUNTED) { @@ -181,7 +154,7 @@ int print_logfile_records(std::shared_ptr disk, std::shared_ptr vo } else { - std::cout << "[+] Volume marked as cleanly unmounted" << std::endl; + std::cout << "[-] Volume marked as cleanly unmounted" << std::endl; } ////////// @@ -189,7 +162,7 @@ int print_logfile_records(std::shared_ptr disk, std::shared_ptr vo DWORD client_i = 1; for (auto& client : get_log_clients(newest_restart_area)) { - std::cout << "[+] Client found : [" << std::to_string(client_i++) << "] " << client << std::endl; + std::cout << "[-] Client found : [" << std::to_string(client_i++) << "] " << client << std::endl; } ////////// @@ -207,17 +180,44 @@ int print_logfile_records(std::shared_ptr disk, std::shared_ptr vo record_page_offsets.push_back(prh); } - std::cout << "[+] $LogFile Record Page Count : " << std::to_string(record_page_offsets.size()) << std::endl; + std::cout << "[-] $LogFile Record Page Count : " << std::to_string(record_page_offsets.size()) << std::endl; ///////// - std::cout << "[+] Parsing $LogFile Records" << std::endl; + std::shared_ptr ffile; + + if (format == "csv") + { + ffile = std::make_shared(output); + } + else + { + ffile = std::make_shared(output); + } + + ffile->set_columns( + { + "LSN", + "ClientPreviousLSN", + "UndoNextLSN", + "ClientID", + "RecordType", + "TransactionID", + "RedoOperation", + "UndoOperation", + "MFTClusterIndex", + "TargetVCN", + "TargetLCN" + } + ); + + std::cout << "[-] Parsing $LogFile Records" << std::endl; Buffer leftover_buffer(8 * 4096); DWORD leftover_size = 0; DWORD leftover_missing_size = 0; - DWORD processed = 0; + for (PRECORD_PAGE_HEADER prh : record_page_offsets) { fixup_sequence(prh); @@ -232,10 +232,10 @@ int print_logfile_records(std::shared_ptr disk, std::shared_ptr vo if (leftover_missing_size == 0) { - PRECORD_LOG prllo = POINTER_ADD(PRECORD_LOG, leftover_buffer.data(), 0); - print_record_log(houtput, prllo, format); + _add_record(ffile, POINTER_ADD(PRECORD_LOG, leftover_buffer.data(), 0)); + processed++; - std::cout << "\r[+] $LogFile Record Count : " << std::to_string(processed); + std::cout << "\r[-] $LogFile Record Count : " << std::to_string(processed) + " "; offset += leftover_missing_size; leftover_size = 0; @@ -276,32 +276,29 @@ int print_logfile_records(std::shared_ptr disk, std::shared_ptr vo } else { - print_record_log(houtput, prl, format); + _add_record(ffile, prl); + processed++; - std::cout << "\r[+] $LogFile Record Count : " << std::to_string(processed); + std::cout << "\r[-] $LogFile Record Count : " << std::to_string(processed) + " "; } } } - if (format == "json") - { - DWORD written = 0; - WriteFile(houtput, "{}]\n", 2, &written, NULL); - } - std::cout << std::endl; } else { std::cout << "[!] Invalid or missing format" << std::endl; + return 2; } - CloseHandle(houtput); - std::cout << "[+] Closing volume" << std::endl; + std::cout << std::endl << "[+] Closing volume" << std::endl; return 0; } -namespace commands { - namespace logfile { +namespace commands +{ + namespace logfile + { int dispatch(std::shared_ptr opts) { std::ios_base::fmtflags flag_backup(std::cout.flags()); diff --git a/Sources/Commands/command_mbr.cpp b/Sources/Commands/command_mbr.cpp index eb0a5f1..2916889 100644 --- a/Sources/Commands/command_mbr.cpp +++ b/Sources/Commands/command_mbr.cpp @@ -18,7 +18,7 @@ void print_mbr(std::shared_ptr disk) { - utils::ui::title("MBR from " + disk->name()); + utils::ui::title("MBR for " + disk->name()); PMBR mbr = disk->mbr(); diff --git a/Sources/Commands/command_btree.cpp b/Sources/Commands/command_mft.btree.cpp similarity index 98% rename from Sources/Commands/command_btree.cpp rename to Sources/Commands/command_mft.btree.cpp index fd5a10d..0d6e9c5 100644 --- a/Sources/Commands/command_btree.cpp +++ b/Sources/Commands/command_mft.btree.cpp @@ -31,7 +31,7 @@ int print_btree_info(std::shared_ptr disk, std::shared_ptr vol, st return 3; } - utils::ui::title("B-tree index (inode:" + std::to_string(record->header()->MFTRecordIndex) + ") from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("B-tree index (inode:" + std::to_string(record->header()->MFTRecordIndex) + ") for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::shared_ptr fr_attributes = std::make_shared(); fr_attributes->set_interline(true); diff --git a/Sources/Commands/command_mft.dump.cpp b/Sources/Commands/command_mft.dump.cpp new file mode 100644 index 0000000..4bfd79c --- /dev/null +++ b/Sources/Commands/command_mft.dump.cpp @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include +#include + +#include "Commands/commands.h" +#include "NTFS/ntfs.h" +#include "NTFS/ntfs_explorer.h" +#include "Utils/constant_names.h" +#include "Utils/table.h" +#include "Utils/utils.h" + +#include "options.h" +#include "Drive/disk.h" +#include "Drive/volume.h" +#include +#include +#include +#include + + +int dump_mft(std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts, const std::string& format, std::string output) +{ + if (!commands::helpers::is_ntfs(disk, vol)) return 1; + + utils::ui::title("MFT Dump (inode:0) for " + disk->name() + " > Volume:" + std::to_string(vol->index())); + + std::shared_ptr explorer = std::make_shared(vol); + + std::shared_ptr record = explorer->mft()->record_from_number(0); + if (record == nullptr) + { + std::cout << "[!] Error accessing record 0" << std::endl; + return 1; + } + + DWORD record_size = explorer->reader()->sizes.record_size; + ULONG64 total_size = record->datasize(); + + std::cout << "[+] $MFT size : " << utils::format::size(total_size) << std::endl; + std::cout << "[-] Record size : " << record_size << std::endl; + std::cout << "[-] Record count: " << (total_size / record_size) << std::endl; + + std::cout << "[+] Creating " << output << std::endl; + + if (format == "raw") + { + HANDLE houtput = CreateFileA(output.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + if (houtput == INVALID_HANDLE_VALUE) + { + std::cout << "[!] Error creating output file" << std::endl; + return 2; + } + + ULONG64 processed_size = 0; + + for (auto& block : record->process_data("", 1024 * 1024, true)) + { + std::cout << "\r[+] Processing data: " << utils::format::size(processed_size) << " "; + processed_size += block.second; + + DWORD written = 0; + WriteFile(houtput, block.first, block.second, &written, NULL); + } + std::cout << "\r[+] Processing data: " << utils::format::size(processed_size); + + CloseHandle(houtput); + } + else if (format == "csv" || format == "json") + { + SYSTEMTIME file_info_creation = { 0 }; + SYSTEMTIME file_info_access = { 0 }; + SYSTEMTIME file_info_modification = { 0 }; + SYSTEMTIME file_info_mft = { 0 }; + unsigned long long file_info_size = 0; + WORD file_info_hardlink_count = 0; + std::set file_info_parentids; + + std::shared_ptr ffile; + + if (format == "csv") + { + ffile = std::make_shared(output); + } + else + { + ffile = std::make_shared(output); + } + + ffile->set_columns( + { + "RecordIndex", + "InUse", + "Type", + "Filename", + "Ext", + "Size", + "Parents", + "Time_MFT", + "Time_Create", + "Time_Alter", + "Time_Read", + "Att_Archive", + "Att_Compressed", + "Att_Device", + "Att_Encrypted", + "Att_Hidden", + "Att_Normal", + "Att_NotIndexed", + "Att_Offline", + "Att_Readonly", + "Att_Reparse", + "Att_Sparse", + "Att_System", + "Att_Temp", + "USN", + "Hardlinks", + "ADS", + "ZoneId", + "ReferrerUrl", + "HostUrl" + } + ); + + std::shared_ptr record = nullptr; + + auto index = 0ULL; + for (index = 0ULL; index < (total_size / record_size); index++) + { + std::cout << "\r[+] Processing data: " << utils::format::size(index * record_size) << " "; + + record = explorer->mft()->record_from_number(index); + + if (record == nullptr || !MFTRecord::is_valid(record->header())) + { + continue; + } + + ffile->add_item(record->header()->MFTRecordIndex); + ffile->add_item(record->header()->flag & FILE_RECORD_FLAG_INUSE ? "True" : "False"); + ffile->add_item(record->header()->flag & FILE_RECORD_FLAG_DIR ? "Directory" : "File"); + + auto path = std::filesystem::path(record->filename()); + + ffile->add_item(utils::strings::to_utf8(path.filename().generic_wstring())); + ffile->add_item(utils::strings::lower(utils::strings::to_utf8(path.extension().generic_wstring()))); + + ffile->add_item(record->datasize()); + + file_info_parentids.clear(); + int att_index = 0; + PMFT_RECORD_ATTRIBUTE_HEADER pattr = record->attribute_header($FILE_NAME, "", att_index); + while (pattr != nullptr) + { + auto pattr_filename = POINTER_ADD(PMFT_RECORD_ATTRIBUTE_FILENAME, pattr, pattr->Form.Resident.ValueOffset); + file_info_parentids.insert(std::to_string(pattr_filename->ParentDirectory.FileRecordNumber)); + att_index++; + if (att_index > 4) break; + pattr = record->attribute_header($FILE_NAME, "", att_index); + } + ffile->add_item(utils::strings::join_set(file_info_parentids, "|")); + + pattr = record->attribute_header($STANDARD_INFORMATION); + if (pattr != nullptr) + { + PMFT_RECORD_ATTRIBUTE_STANDARD_INFORMATION psubattr = POINTER_ADD(PMFT_RECORD_ATTRIBUTE_STANDARD_INFORMATION, pattr, pattr->Form.Resident.ValueOffset); + utils::times::ull_to_systemtime(psubattr->MFTTime, &file_info_mft); + utils::times::ull_to_systemtime(psubattr->CreateTime, &file_info_creation); + utils::times::ull_to_systemtime(psubattr->AlterTime, &file_info_modification); + utils::times::ull_to_systemtime(psubattr->ReadTime, &file_info_access); + + ffile->add_item(utils::times::display_systemtime(file_info_mft)); + ffile->add_item(utils::times::display_systemtime(file_info_creation)); + ffile->add_item(utils::times::display_systemtime(file_info_modification)); + ffile->add_item(utils::times::display_systemtime(file_info_access)); + + ffile->add_item(psubattr->u.Permission.archive ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.compressed ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.device ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.encrypted ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.hidden ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.normal ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.not_indexed ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.offline ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.readonly ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.reparse ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.sparse ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.system ? "True" : "False"); + ffile->add_item(psubattr->u.Permission.temp ? "True" : "False"); + + ffile->add_item(psubattr->USN); + } + else + { + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + + ffile->add_item(); + } + + ffile->add_item(record->header()->hardLinkCount); + + auto ads = record->ads_names(); + ffile->add_item(utils::strings::join_vec(ads, "|")); + + if (std::find(ads.begin(), ads.end(), "Zone.Identifier") != ads.end()) + { + std::shared_ptr zi = std::make_shared(record->data("Zone.Identifier")); + ffile->add_item(zi->get_value("ZoneId")); + ffile->add_item(zi->get_value("ReferrerUrl")); + ffile->add_item(zi->get_value("HostUrl")); + zi = nullptr; + } + else + { + ffile->add_item(); + ffile->add_item(); + ffile->add_item(); + } + + ffile->new_line(); + } + std::cout << "\r[+] Processing data: " << utils::format::size(index * record_size) << " "; + } + else + { + std::cout << "[!] Invalid or missing format"; + } + + std::cout << std::endl << "[+] Closing volume" << std::endl; + + return 0; +} + +namespace commands +{ + namespace mft + { + namespace dump + { + int dispatch(std::shared_ptr opts) + { + std::ios_base::fmtflags flag_backup(std::cout.flags()); + + std::shared_ptr disk = get_disk(opts); + if (disk != nullptr) + { + std::shared_ptr volume = disk->volumes(opts->volume); + if (volume != nullptr) + { + if (opts->output != "") + { + if (opts->format == "") opts->format = "raw"; + + dump_mft(disk, volume, opts, opts->format, opts->output); + } + else + { + invalid_option(opts, "output", opts->output); + } + } + else + { + invalid_option(opts, "volume", opts->volume); + } + } + else + { + invalid_option(opts, "disk", opts->disk); + } + + std::cout.flags(flag_backup); + return 0; + } + } + } +} \ No newline at end of file diff --git a/Sources/Commands/command_mft.cpp b/Sources/Commands/command_mft.record.cpp similarity index 99% rename from Sources/Commands/command_mft.cpp rename to Sources/Commands/command_mft.record.cpp index 7e3369a..4a8b4aa 100644 --- a/Sources/Commands/command_mft.cpp +++ b/Sources/Commands/command_mft.record.cpp @@ -850,7 +850,7 @@ int print_mft_info(std::shared_ptr disk, std::shared_ptr vol, std: std::shared_ptr record = commands::helpers::find_record(explorer, opts); - utils::ui::title("MFT (inode:" + std::to_string(record->header()->MFTRecordIndex) + ") from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("MFT (inode:" + std::to_string(record->header()->MFTRecordIndex) + ") for " + disk->name() + " > Volume:" + std::to_string(vol->index())); commands::mft::print_mft_info_details(record, explorer->reader()->sizes.cluster_size); diff --git a/Sources/Commands/command_reparse.cpp b/Sources/Commands/command_reparse.cpp index 7656100..1102bd5 100644 --- a/Sources/Commands/command_reparse.cpp +++ b/Sources/Commands/command_reparse.cpp @@ -21,7 +21,7 @@ int print_reparse(std::shared_ptr disk, std::shared_ptr vol, const { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - utils::ui::title("Reparse points from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Reparse points for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << vol->name() << std::endl; diff --git a/Sources/Commands/command_shadow.cpp b/Sources/Commands/command_shadow.cpp index ffe7823..71a180f 100644 --- a/Sources/Commands/command_shadow.cpp +++ b/Sources/Commands/command_shadow.cpp @@ -23,7 +23,7 @@ int print_volumeshadow(std::shared_ptr disk, std::shared_ptr vol) if (!commands::helpers::is_ntfs(disk, vol)) return 1; std::cout << std::setfill('0'); - utils::ui::title("Volume Shadow from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Volume Shadow for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << vol->name() << std::endl; diff --git a/Sources/Commands/command_shell.cpp b/Sources/Commands/command_shell.cpp index 4f6f810..ad75a69 100644 --- a/Sources/Commands/command_shell.cpp +++ b/Sources/Commands/command_shell.cpp @@ -196,7 +196,7 @@ int explorer(std::shared_ptr disk, std::shared_ptr vol) if ((entry->name_type() == 2) && (win32_named_entries.find(entry->record_number()) != win32_named_entries.end())) continue; std::shared_ptr entry_rec = explorer->mft()->record_from_number(entry->record_number()); - std::vector ads_names = entry_rec->alternate_data_names(); + std::vector ads_names = entry_rec->ads_names(); tab->add_item_line(std::to_string(entry_rec->header()->MFTRecordIndex)); if (entry_rec->header()->flag & MFT_RECORD_IS_DIRECTORY) @@ -257,7 +257,7 @@ int explorer(std::shared_ptr disk, std::shared_ptr vol) if (stdinfo->u.Permission.system) perms.push_back("Sy"); if (stdinfo->u.Permission.temp) perms.push_back("Tm"); - tab->add_item_line(utils::strings::join(perms, " ")); + tab->add_item_line(utils::strings::join_vec(perms, " ")); } else { diff --git a/Sources/Commands/command_smart.cpp b/Sources/Commands/command_smart.cpp index 2a844e4..bc01399 100644 --- a/Sources/Commands/command_smart.cpp +++ b/Sources/Commands/command_smart.cpp @@ -130,7 +130,7 @@ std::string read_string(PVOID buffer, DWORD len) void print_smart_data(std::shared_ptr disk) { - utils::ui::title("SMART data from " + disk->name()); + utils::ui::title("SMART data for " + disk->name()); HANDLE hDisk = CreateFileA(disk->name().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); if (hDisk != INVALID_HANDLE_VALUE) diff --git a/Sources/Commands/command_streams.cpp b/Sources/Commands/command_streams.cpp index 9b9305f..ea4a6c1 100644 --- a/Sources/Commands/command_streams.cpp +++ b/Sources/Commands/command_streams.cpp @@ -20,8 +20,7 @@ int list_streams(std::shared_ptr disk, std::shared_ptr vol, std::s { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Listing streams from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Listing streams for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << (vol->name().empty() ? reinterpret_cast(vol->parent())->name() : vol->name()) << std::endl; @@ -30,7 +29,7 @@ int list_streams(std::shared_ptr disk, std::shared_ptr vol, std::s std::cout << "[-] Record Num : " << record->header()->MFTRecordIndex << " (" << utils::format::hex(record->header()->MFTRecordIndex, true) << ")" << std::endl; - std::vector ads_names = record->alternate_data_names(); + std::vector ads_names = record->ads_names(); if (ads_names.size() > 0) { diff --git a/Sources/Commands/command_undelete.cpp b/Sources/Commands/command_undelete.cpp index bc99b90..bf25102 100644 --- a/Sources/Commands/command_undelete.cpp +++ b/Sources/Commands/command_undelete.cpp @@ -17,16 +17,6 @@ #include "Utils/table.h" #include -bool valid_record(PMFT_RECORD_HEADER ph) -{ - return ( - (memcmp(ph->signature, "FILE", 4) == 0) && - (ph->attributeOffset > 0x30) && - (ph->attributeOffset < 0x400) && - (POINTER_ADD(PMFT_RECORD_ATTRIBUTE_HEADER, ph, ph->attributeOffset)->TypeCode >= 10) && - (POINTER_ADD(PMFT_RECORD_ATTRIBUTE_HEADER, ph, ph->attributeOffset)->TypeCode <= 100) - ); -} std::string get_full_path(DWORD index, std::map& index_to_name, std::map& index_to_parent) { @@ -101,7 +91,7 @@ int print_deleted_files(std::shared_ptr disk, std::shared_ptr vol, return 1; } - utils::ui::title("Undelete from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Undelete for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << vol->name() << std::endl; @@ -136,7 +126,7 @@ int print_deleted_files(std::shared_ptr disk, std::shared_ptr vol, for (offset = 0; offset <= block.second - record_size; offset += record_size) { PMFT_RECORD_HEADER pmrh = POINTER_ADD(PMFT_RECORD_HEADER, block.first, offset); - if (valid_record(pmrh)) + if (MFTRecord::is_valid(pmrh)) { std::shared_ptr f = explorer->mft()->record_from_number(pmrh->MFTRecordIndex); @@ -260,8 +250,7 @@ int extract_deleted_file(std::shared_ptr disk, std::shared_ptr vol { if (!commands::helpers::is_ntfs(disk, vol)) return 1; - std::cout << std::setfill('0'); - utils::ui::title("Extract deleted file from " + disk->name() + " > Volume:" + std::to_string(vol->index())); + utils::ui::title("Extract deleted file for " + disk->name() + " > Volume:" + std::to_string(vol->index())); std::cout << "[+] Opening " << vol->name() << std::endl; diff --git a/Sources/Commands/command_usn.analyze.cpp b/Sources/Commands/command_usn.analyze.cpp new file mode 100644 index 0000000..bcaed14 --- /dev/null +++ b/Sources/Commands/command_usn.analyze.cpp @@ -0,0 +1,498 @@ +#include "Drive/disk.h" +#include "Utils/utils.h" +#include "options.h" +#include "Commands/commands.h" +#include "NTFS/ntfs.h" +#include "NTFS/ntfs_explorer.h" +#include "Utils/constant_names.h" +#include +#include "Utils/table.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void process_usn_offline(std::shared_ptr> filebuf, std::string from_file, std::shared_ptr output_file, std::shared_ptr usn_rules, std::map& matches, std::shared_ptr usn_stats, bool full_mode) +{ + std::cout << "[-] Mode: " << (full_mode ? "full" : "fast") << std::endl; + if (full_mode) + { + std::cout << "[-] Full mode is disabled for usn dump"; + full_mode = false; + } + + std::cout << "[+] Opening " << from_file << std::endl; + + ULONG64 total_size = filebuf->size(); + ULONG64 filled_size = 0; + + std::cout << "[-] USN dump filesize: " << utils::format::size(total_size) << std::endl; + ULONG64 processed_size = 0; + ULONG64 processed_count = 0; + ULONG64 matches_count = 0; + DWORD cluster_size = 4096; + Buffer clusterBuf((DWORD64)2 * cluster_size); + + for (auto& block : filebuf->process_data(cluster_size)) + { + processed_size += block.second; + + std::cout << "\r[+] Processing USN records: " << std::to_string(processed_count) << " (" << utils::format::size(processed_size) << ") - " << matches_count << " matches "; + + if (filled_size) + { + break; + } + + memcpy(clusterBuf.data() + filled_size, block.first, block.second); + filled_size += block.second; + + PUSN_RECORD_COMMON_HEADER header = (PUSN_RECORD_COMMON_HEADER)clusterBuf.data(); + while ((filled_size > 0) && (header->RecordLength <= filled_size)) + { + switch (header->MajorVersion) + { + case 0: + { + DWORD i = 0; + while ((i < filled_size) && (POINTER_ADD(PWORD, header, i)[0] == 0)) + { + i += 2; + } + header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, i); + filled_size -= i; + break; + } + case 2: + { + PUSN_RECORD_V2 usn_record = (PUSN_RECORD_V2)header; + std::wstring wfilename = std::wstring(usn_record->FileName); + wfilename.resize(usn_record->FileNameLength / sizeof(WCHAR)); + std::string filename = utils::strings::to_utf8(wfilename); + + usn_stats->add_record(filename, usn_record); + + std::vector matched_rules; + for (auto& rule : usn_rules->rules()) + { + if (rule->match(filename, usn_record)) + { + matched_rules.push_back(rule->id()); + matches_count++; + if (matches.find(rule->id()) != matches.end()) + { + matches[rule->id()] += 1; + } + else + { + matches[rule->id()] = 1; + } + } + } + if (!matched_rules.empty()) + { + output_file->add_item(usn_record->MajorVersion); + output_file->add_item(usn_record->MinorVersion); + output_file->add_item(usn_record->FileReferenceNumber & 0xffffffffffff); + output_file->add_item(usn_record->FileReferenceNumber >> 48); + output_file->add_item(usn_record->ParentFileReferenceNumber & 0xffffffffffff); + output_file->add_item(usn_record->ParentFileReferenceNumber >> 48); + output_file->add_item(usn_record->Usn); + + SYSTEMTIME st = { 0 }; + utils::times::ull_to_local_systemtime(usn_record->TimeStamp.QuadPart, &st); + output_file->add_item(utils::times::display_systemtime(st)); + output_file->add_item(constants::disk::usn::reasons(usn_record->Reason)); + output_file->add_item((usn_record->SourceInfo)); + output_file->add_item((usn_record->SecurityId)); + output_file->add_item(constants::disk::usn::fileattributes(usn_record->FileAttributes)); + + output_file->add_item(utils::strings::to_utf8(wfilename)); + + output_file->add_item(utils::strings::join_vec(matched_rules, "|")); + + output_file->new_line(); + } + + processed_count++; + + filled_size -= usn_record->RecordLength; + header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, usn_record->RecordLength); + break; + } + default: + return; + } + } + + if (filled_size <= clusterBuf.size()) + { + memcpy(clusterBuf.data(), header, (size_t)filled_size); + } + } + std::cout << "\r[+] Processing USN records: " << std::to_string(processed_count) << " (" << utils::format::size(processed_size) << ") - " << matches_count << " matches "; +} + +void process_usn_live(std::shared_ptr vol, std::shared_ptr ffile, std::shared_ptr usn_rules, std::map& matches, std::shared_ptr usn_stats, bool full_mode) +{ + std::cout << "[-] Mode: " << (full_mode ? "full" : "fast") << std::endl; + std::shared_ptr path_finder = nullptr; + if (full_mode) + { + path_finder = std::make_shared(vol); + std::cout << "[+] " << path_finder->count() << " $MFT records loaded" << std::endl; + } + + std::cout << "[+] Opening " << vol->name() << std::endl; + std::shared_ptr explorer = std::make_shared(vol); + + std::cout << "[+] Searching for $Extend\\$UsnJrnl" << std::endl; + + std::shared_ptr record = explorer->mft()->record_from_path("\\$Extend\\$UsnJrnl"); + if (record == nullptr) + { + std::cout << "[!] Not found" << std::endl; + return; + } + + std::cout << "[-] Found in file record: " << std::to_string(record->header()->MFTRecordIndex) << std::endl; + + + ULONG64 total_size = record->datasize(MFT_ATTRIBUTE_DATA_USN_NAME, true); + ULONG64 filled_size = 0; + + std::cout << "[-] $J stream size: " << utils::format::size(total_size) << " (could be sparse)" << std::endl; + ULONG64 processed_size = 0; + ULONG64 processed_count = 0; + ULONG64 matches_count = 0; + DWORD cluster_size = explorer->reader()->sizes.cluster_size; + Buffer clusterBuf((DWORD64)2 * cluster_size); + + for (auto& block : record->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, cluster_size, true)) + { + processed_size += block.second; + + std::cout << "\r[+] Processing USN records: " << std::to_string(processed_count) << " (" << utils::format::size(processed_size) << ") - " << matches_count << " matches "; + + if (filled_size) + { + break; + } + + memcpy(clusterBuf.data() + filled_size, block.first, block.second); + filled_size += block.second; + + PUSN_RECORD_COMMON_HEADER header = (PUSN_RECORD_COMMON_HEADER)clusterBuf.data(); + while ((filled_size > 0) && (header->RecordLength <= filled_size)) + { + switch (header->MajorVersion) + { + case 0: + { + DWORD i = 0; + while ((i < filled_size) && (POINTER_ADD(PWORD, header, i)[0] == 0)) + { + i += 2; + } + header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, i); + filled_size -= i; + break; + } + case 2: + { + PUSN_RECORD_V2 usn_record = (PUSN_RECORD_V2)header; + std::wstring wfilename = std::wstring(usn_record->FileName); + wfilename.resize(usn_record->FileNameLength / sizeof(WCHAR)); + std::string filename = utils::strings::to_utf8(wfilename); + + usn_stats->add_record(filename, usn_record); + + std::vector matched_rules; + for (auto& rule : usn_rules->rules()) + { + if (rule->match(filename, usn_record)) + { + matched_rules.push_back(rule->id()); + matches_count++; + if (matches.find(rule->id()) != matches.end()) + { + matches[rule->id()] += 1; + } + else + { + matches[rule->id()] = 1; + } + } + } + if (!matched_rules.empty()) + { + ffile->add_item(usn_record->MajorVersion); + ffile->add_item(usn_record->MinorVersion); + ffile->add_item(usn_record->FileReferenceNumber & 0xffffffffffff); + ffile->add_item(usn_record->FileReferenceNumber >> 48); + ffile->add_item(usn_record->ParentFileReferenceNumber & 0xffffffffffff); + ffile->add_item(usn_record->ParentFileReferenceNumber >> 48); + ffile->add_item(usn_record->Usn); + + SYSTEMTIME st = { 0 }; + utils::times::ull_to_local_systemtime(usn_record->TimeStamp.QuadPart, &st); + ffile->add_item(utils::times::display_systemtime(st)); + ffile->add_item(constants::disk::usn::reasons(usn_record->Reason)); + ffile->add_item((usn_record->SourceInfo)); + ffile->add_item((usn_record->SecurityId)); + ffile->add_item(constants::disk::usn::fileattributes(usn_record->FileAttributes)); + + if (full_mode) + { + ffile->add_item(path_finder->get_file_path(utils::strings::to_utf8(wfilename), usn_record->ParentFileReferenceNumber)); + } + else + { + ffile->add_item(utils::strings::to_utf8(wfilename)); + } + + ffile->add_item(utils::strings::join_vec(matched_rules, "|")); + + ffile->new_line(); + } + + processed_count++; + + filled_size -= usn_record->RecordLength; + header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, usn_record->RecordLength); + break; + } + default: + return; + } + } + + if (filled_size <= clusterBuf.size()) + { + memcpy(clusterBuf.data(), header, (size_t)filled_size); + } + } + std::cout << "\r[+] Processing USN records: " << std::to_string(processed_count) << " (" << utils::format::size(processed_size) << ") - " << matches_count << " matches "; +} + +std::shared_ptr load_rules(std::shared_ptr opts) +{ + std::cout << "[+] Loading rules from: " << opts->rules << std::endl; + return std::make_shared(opts->rules); +} + +int analyze_usn_journal(std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr> filebuf, std::shared_ptr opts) +{ + if (disk == nullptr && vol == nullptr) + { + utils::ui::title("Analyze USN journal for " + opts->from); + } + else + { + if (!commands::helpers::is_ntfs(disk, vol)) return 1; + utils::ui::title("Analyze USN journal for " + disk->name() + " > Volume:" + std::to_string(vol->index())); + } + + std::shared_ptr usn_rules = load_rules(opts); + if (usn_rules && usn_rules->size() > 0) + { + std::cout << "[-] " << usn_rules->size() << " rules loaded" << std::endl; + } + else + { + std::cout << "[!] No rule loaded. Exiting." << std::endl; + return 1; + } + + std::cout << "[+] Creating " << opts->output << std::endl; + + std::shared_ptr output_file = std::make_shared(opts->output); + + output_file->set_columns( + { + "MajorVersion", + "MinorVersion", + "FileReferenceNumber", + "FileReferenceSequenceNumber", + "ParentFileReferenceNumber", + "ParentFileReferenceSequenceNumber", + "Usn", + "Timestamp", + "Reason", + "SourceInfo", + "SecurityId", + "FileAttributes", + "Filename", + "Rules" + } + ); + + std::map matches; + std::shared_ptr usn_stats = std::make_shared(); + + if (filebuf != nullptr) + { + process_usn_offline(filebuf, opts->from, output_file, usn_rules, matches, usn_stats, opts->mode == "full"); + } + else + { + process_usn_live(vol, output_file, usn_rules, matches, usn_stats, opts->mode == "full"); + } + + std::cout << std::endl << "[+] Closing volume" << std::endl; + + std::cout << "[+] Summary:" << std::endl; + + std::shared_ptr summary = std::make_shared(); + summary->set_margin_left(4); + + summary->add_header_line("Index"); + summary->add_header_line("Category"); + summary->add_header_line("Value", utils::ui::TableAlign::RIGHT); + summary->add_header_line("%", utils::ui::TableAlign::RIGHT); + + auto count = usn_stats->get_stat("records count"); + + int index = 0; + std::stringstream ss; + for (auto& element : usn_stats->get_stats()) + { + summary->add_item_line(std::to_string(index++)); + summary->add_item_line(element.first); + + if (usn_stats->is_date(element)) + { + SYSTEMTIME st = { 0 }; + utils::times::ull_to_local_systemtime(element.second, &st); + ss.str(std::string()); + ss << utils::times::display_systemtime(st); + summary->add_item_line(ss.str()); + } + else + { + summary->add_item_line(std::to_string(element.second)); + } + + ss.str(std::string()); + if (usn_stats->is_count(element)) + { + ss << std::fixed << std::setprecision(2) << (100.0 * element.second / count); + } + summary->add_item_line(ss.str()); + + summary->new_line(); + } + summary->render(std::cout); + + if (!matches.empty()) + { + std::cout << "[+] Rules results:" << std::endl; + + std::shared_ptr results = std::make_shared(); + results->set_margin_left(4); + + results->add_header_line("Index"); + results->add_header_line("Rule ID"); + results->add_header_line("Count", utils::ui::TableAlign::RIGHT); + results->add_header_line("%", utils::ui::TableAlign::RIGHT); + + int index = 0; + std::stringstream ss; + for (std::pair element : matches) + { + results->add_item_line(std::to_string(index++)); + results->add_item_line(element.first); + results->add_item_line(std::to_string(element.second)); + + ss.str(std::string()); + ss << std::fixed << std::setprecision(2) << (100.0 * element.second / count); + results->add_item_line(ss.str()); + + results->new_line(); + } + results->render(std::cout); + } + else + { + std::cout << "[+] No match" << std::endl; + } + + return 0; +} + +namespace commands +{ + namespace usn + { + namespace analyze + { + int dispatch(std::shared_ptr opts) + { + std::ios_base::fmtflags flag_backup(std::cout.flags()); + + if (opts->output != "") + { + if (opts->rules != "") + { + if (opts->mode != "full" && opts->mode != "fast") + { + opts->mode = "fast"; + } + } + else + { + invalid_option(opts, "rules", opts->rules); + } + } + else + { + invalid_option(opts, "output", opts->output); + } + + if (opts->from != "") + { + std::shared_ptr> filebuf = Buffer::from_file(utils::strings::from_string(opts->from)); + if (filebuf != nullptr) + { + analyze_usn_journal(nullptr, nullptr, filebuf, opts); + } + else + { + invalid_option(opts, "from", opts->from); + } + } + else + { + std::shared_ptr disk = get_disk(opts); + if (disk != nullptr) + { + std::shared_ptr volume = disk->volumes(opts->volume); + if (volume != nullptr) + { + analyze_usn_journal(disk, volume, nullptr, opts); + } + else + { + invalid_option(opts, "volume", opts->volume); + } + } + else + { + invalid_option(opts, "disk", opts->disk); + } + } + std::cout.flags(flag_backup); + return 0; + } + } + } +} \ No newline at end of file diff --git a/Sources/Commands/command_usn.cpp b/Sources/Commands/command_usn.cpp deleted file mode 100644 index 578c8c3..0000000 --- a/Sources/Commands/command_usn.cpp +++ /dev/null @@ -1,271 +0,0 @@ -#include "Drive/disk.h" -#include "Utils/utils.h" -#include "options.h" -#include "Commands/commands.h" -#include "NTFS/ntfs.h" -#include "NTFS/ntfs_explorer.h" -#include "Utils/constant_names.h" -#include "Utils/table.h" - -#include - -#include -#include -#include -#include -#include -#include - -int print_usn_journal(std::shared_ptr disk, std::shared_ptr vol, const std::string& format, std::string output) -{ - if (!commands::helpers::is_ntfs(disk, vol)) return 1; - - std::cout << std::setfill('0'); - utils::ui::title("USN Journals from " + disk->name() + " > Volume:" + std::to_string(vol->index())); - - DWORD cluster_size = ((PBOOT_SECTOR_NTFS)vol->bootsector())->bytePerSector * ((PBOOT_SECTOR_NTFS)vol->bootsector())->sectorPerCluster; - - std::cout << "[+] Opening " << vol->name() << std::endl; - - std::shared_ptr explorer = std::make_shared(vol); - - std::cout << "[+] Finding $Extend\\$UsnJrnl record" << std::endl; - - std::shared_ptr record = explorer->mft()->record_from_path("\\$Extend\\$UsnJrnl"); - - if (record == nullptr) - { - std::cout << "[!] Not found" << std::endl; - return 2; - } - - std::cout << "[+] Found in file record : " << std::to_string(record->header()->MFTRecordIndex) << std::endl; - - PMFT_RECORD_HEADER record_header = record->header(); - - Buffer clusterBuf((DWORD64)2 * 1024 * 1024); - ULONG64 total_size = record->datasize(MFT_ATTRIBUTE_DATA_USN_NAME, true); - - std::cout << "[+] Virtual $J stream size : " << utils::format::size(total_size) << " (sparse, ~32MiBs on disk by default)" << std::endl; - - ULONG64 processed_size = 0; - ULONG64 processed_count = 0; - ULONG64 filled_size = 0; - - std::cout << "[+] Creating " << output << std::endl; - - HANDLE houtput = CreateFileA(output.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); - if (houtput == INVALID_HANDLE_VALUE) - { - std::cout << "[!] Error creating output file" << std::endl; - return 1; - } - - if (format == "raw") - { - for (auto& block : record->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, 1024 * 1024, true)) - { - std::cout << "\r[+] Processing data: " << utils::format::size(processed_count) << " "; - processed_count += block.second; - - DWORD written = 0; - WriteFile(houtput, block.first, block.second, &written, NULL); - } - } - else if (format == "csv") - { - std::string csv_header = "MajorVersion,MinorVersion,FileReferenceNumber,FileReferenceSequenceNumber,ParentFileReferenceNumber,ParentFileReferenceSequenceNumber,Usn,Timestamp,Reason,SourceInfo,SecurityId,FileAttributes,Filename\n"; - DWORD written = 0; - DWORD read = 0; - DWORD header_size = 0; - if (!FAILED(SizeTToDWord(csv_header.size(), &header_size))) WriteFile(houtput, csv_header.c_str(), header_size, &written, NULL); - - for (auto& block : record->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, cluster_size, true)) - { - read += block.second; - - memcpy(clusterBuf.data() + filled_size, block.first, block.second); - filled_size += block.second; - - PUSN_RECORD_COMMON_HEADER header = (PUSN_RECORD_COMMON_HEADER)clusterBuf.data(); - while ((filled_size > 0) && (header->RecordLength <= filled_size)) - { - switch (header->MajorVersion) - { - case 0: - { - DWORD i = 0; - while ((i < filled_size) && (((PBYTE)header)[i] == 0)) i++; - processed_size += i; - header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, i); - filled_size -= i; - break; - } - case 2: - { - PUSN_RECORD_V2 usn_record = (PUSN_RECORD_V2)header; - processed_size += usn_record->RecordLength; - std::wstring a = std::wstring(usn_record->FileName); - a.resize(usn_record->FileNameLength / sizeof(WCHAR)); - - std::cout << "\r[+] Processing entry: " << std::to_string(++processed_count) << " (" << utils::format::size(read) << ") "; - - std::ostringstream entry; - entry << usn_record->MajorVersion << ","; - entry << usn_record->MinorVersion << ","; - entry << (usn_record->FileReferenceNumber & 0xffffffffffff) << ","; - entry << (usn_record->FileReferenceNumber >> 48) << ","; - entry << (usn_record->ParentFileReferenceNumber & 0xffffffffffff) << ","; - entry << (usn_record->ParentFileReferenceNumber >> 48) << ","; - entry << usn_record->Usn << ","; - SYSTEMTIME st = { 0 }; - utils::times::ull_to_local_systemtime(usn_record->TimeStamp.QuadPart, &st); - entry << utils::times::display_systemtime(st) << ","; - entry << constants::disk::usn::reasons(usn_record->Reason) << ","; - entry << std::to_string(usn_record->SourceInfo) << ","; - entry << std::to_string(usn_record->SecurityId) << ","; - entry << constants::disk::usn::fileattributes(usn_record->FileAttributes) << ","; - entry << utils::strings::to_utf8(a) << std::endl; - - std::string line = entry.str(); - DWORD write_size = 0; - if (!FAILED(SizeTToDWord(line.size(), &write_size))) WriteFile(houtput, line.c_str(), write_size, &written, NULL); - - filled_size -= usn_record->RecordLength; - header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, usn_record->RecordLength); - break; - } - default: - std::cout << std::endl << "[!] Unknown USN record version (" << std::to_string(header->MajorVersion) << ")" << std::endl; - return 1; - } - } - - memcpy(clusterBuf.data(), header, (size_t)filled_size); - } - } - else if (format == "json") - { - DWORD written = 0; - DWORD read = 0; - WriteFile(houtput, "[\n", 2, &written, NULL); - - for (auto& block : record->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, cluster_size, true)) - { - read += block.second; - - PUSN_RECORD_COMMON_HEADER header = (PUSN_RECORD_COMMON_HEADER)clusterBuf.data(); - - memcpy(clusterBuf.data() + filled_size, block.first, block.second); - filled_size += block.second; - - while ((filled_size > 0) && (header->RecordLength <= filled_size)) - { - switch (header->MajorVersion) - { - case 0: - { - DWORD i = 0; - while ((i < filled_size) && (((PBYTE)header)[i] == 0)) i++; - processed_size += i; - header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, i); - filled_size -= i; - break; - } - case 2: - { - PUSN_RECORD_V2 usn_record = (PUSN_RECORD_V2)header; - processed_size += usn_record->RecordLength; - std::wstring a = std::wstring(usn_record->FileName); - a.resize(usn_record->FileNameLength / sizeof(WCHAR)); - - std::cout << "\r[+] Processing entry: " << std::to_string(++processed_count) << " (" << utils::format::size(read) << ") "; - - nlohmann::json entry; - entry["MajorVersion"] = usn_record->MajorVersion; - entry["MinorVersion"] = usn_record->MinorVersion; - entry["FileReferenceNumber"] = (usn_record->FileReferenceNumber & 0xffffffffffff); - entry["FileReferenceSequenceNumber"] = (usn_record->FileReferenceNumber >> 48); - entry["ParentFileReferenceNumber"] = (usn_record->ParentFileReferenceNumber & 0xffffffffffff); - entry["ParentFileReferenceSequenceNumber"] = (usn_record->ParentFileReferenceNumber >> 48); - entry["Usn"] = usn_record->Usn; - SYSTEMTIME st = { 0 }; - utils::times::ull_to_local_systemtime(usn_record->TimeStamp.QuadPart, &st); - entry["Timestamp"] = utils::times::display_systemtime(st); - entry["Reasons"] = constants::disk::usn::reasons(usn_record->Reason); - entry["SourceInfo"] = usn_record->SourceInfo; - entry["SecurityId"] = usn_record->SecurityId; - entry["FileAttributes"] = constants::disk::usn::fileattributes(usn_record->FileAttributes); - entry["FileName"] = utils::strings::to_utf8(a); - - std::string line = entry.dump() + ",\n"; - DWORD line_size = 0; - - if (!FAILED(SizeTToDWord(line.size(), &line_size))) WriteFile(houtput, line.c_str(), line_size, &written, NULL); - - filled_size -= usn_record->RecordLength; - header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, usn_record->RecordLength); - break; - } - default: - std::cout << std::endl << "[!] Unknown USN record version (" << std::to_string(header->MajorVersion) << ")" << std::endl; - return 1; - } - } - - memcpy(clusterBuf.data(), header, filled_size); - } - - WriteFile(houtput, "{}]\n", 2, &written, NULL); - } - else - { - std::cout << "[!] Invalid or missing format" << std::endl; - } - - CloseHandle(houtput); - std::cout << std::endl << "[+] Closing volume" << std::endl; - - return 0; -} - -namespace commands -{ - namespace usn - { - int dispatch(std::shared_ptr opts) - { - std::ios_base::fmtflags flag_backup(std::cout.flags()); - - std::shared_ptr disk = get_disk(opts); - if (disk != nullptr) - { - std::shared_ptr volume = disk->volumes(opts->volume); - if (volume != nullptr) - { - if (opts->output != "") - { - if (opts->format == "") opts->format = "raw"; - - print_usn_journal(disk, volume, opts->format, opts->output); - } - else - { - invalid_option(opts, "output", opts->output); - } - } - else - { - invalid_option(opts, "volume", opts->volume); - } - } - else - { - invalid_option(opts, "disk", opts->disk); - } - - std::cout.flags(flag_backup); - return 0; - } - } -} \ No newline at end of file diff --git a/Sources/Commands/command_usn.dump.cpp b/Sources/Commands/command_usn.dump.cpp new file mode 100644 index 0000000..b46c3f5 --- /dev/null +++ b/Sources/Commands/command_usn.dump.cpp @@ -0,0 +1,256 @@ +#include "Drive/disk.h" +#include "Utils/utils.h" +#include "options.h" +#include "Commands/commands.h" +#include "NTFS/ntfs.h" +#include "NTFS/ntfs_explorer.h" +#include "Utils/constant_names.h" +#include "Utils/table.h" +#include "Utils/path_finder.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +void process_usn(std::shared_ptr explorer, std::shared_ptr record_usn, std::shared_ptr path_finder, bool full_mode, std::shared_ptr ffile) +{ + ULONG64 processed_size = 0; + ULONG64 processed_count = 0; + + Buffer clusterBuf((DWORD64)2 * 1024 * 1024); + ULONG64 filled_size = 0; + + for (auto& block : record_usn->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, explorer->reader()->sizes.cluster_size, true)) + { + processed_size += block.second; + + std::cout << "\r[+] Processing USN records: " << std::to_string(processed_count) << " (" << utils::format::size(processed_size) << ") "; + + if (filled_size) + { + break; + } + + memcpy(clusterBuf.data() + filled_size, block.first, block.second); + filled_size += block.second; + + PUSN_RECORD_COMMON_HEADER header = (PUSN_RECORD_COMMON_HEADER)clusterBuf.data(); + while ((filled_size > 0) && (header->RecordLength <= filled_size)) + { + switch (header->MajorVersion) + { + case 0: + { + DWORD i = 0; + while ((i < filled_size) && (POINTER_ADD(PWORD, header, i)[0] == 0)) + { + i += 2; + } + header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, i); + filled_size -= i; + break; + } + case 2: + { + PUSN_RECORD_V2 usn_record = (PUSN_RECORD_V2)header; + std::wstring a = std::wstring(usn_record->FileName); + a.resize(usn_record->FileNameLength / sizeof(WCHAR)); + + processed_count++; + + ffile->add_item(usn_record->MajorVersion); + ffile->add_item(usn_record->MinorVersion); + ffile->add_item(usn_record->FileReferenceNumber & 0xffffffffffff); + ffile->add_item(usn_record->FileReferenceNumber >> 48); + ffile->add_item(usn_record->ParentFileReferenceNumber & 0xffffffffffff); + ffile->add_item(usn_record->ParentFileReferenceNumber >> 48); + ffile->add_item(usn_record->Usn); + + SYSTEMTIME st = { 0 }; + utils::times::ull_to_local_systemtime(usn_record->TimeStamp.QuadPart, &st); + ffile->add_item(utils::times::display_systemtime(st)); + ffile->add_item(constants::disk::usn::reasons(usn_record->Reason)); + ffile->add_item((usn_record->SourceInfo)); + ffile->add_item((usn_record->SecurityId)); + ffile->add_item(constants::disk::usn::fileattributes(usn_record->FileAttributes)); + if (full_mode) + { + ffile->add_item(path_finder->get_file_path(utils::strings::to_utf8(a), usn_record->ParentFileReferenceNumber)); + } + else + { + ffile->add_item(utils::strings::to_utf8(a)); + } + + ffile->new_line(); + + filled_size -= usn_record->RecordLength; + header = POINTER_ADD(PUSN_RECORD_COMMON_HEADER, header, usn_record->RecordLength); + break; + } + default: + return; + } + } + + if (filled_size <= clusterBuf.size()) + { + memcpy(clusterBuf.data(), header, (size_t)filled_size); + } + } + std::cout << "\r[+] Processing USN records: " << std::to_string(processed_count) << " (" << utils::format::size(processed_size) << ") "; +} + +int print_usn_journal(std::shared_ptr disk, std::shared_ptr vol, std::shared_ptr opts) +{ + if (!commands::helpers::is_ntfs(disk, vol)) return 1; + + utils::ui::title("Dump USN journal for " + disk->name() + " > Volume:" + std::to_string(vol->index())); + + std::cout << "[-] Mode: " << opts->mode << std::endl; + + std::shared_ptr pf = nullptr; + if (opts->mode == "full") + { + pf = std::make_shared(vol); + std::cout << "[+] " << pf->count() << " $MFT records loaded" << std::endl; + } + + std::cout << "[+] Opening " << vol->name() << std::endl; + std::shared_ptr explorer = std::make_shared(vol); + + std::cout << "[+] Finding $Extend\\$UsnJrnl record" << std::endl; + std::shared_ptr record = explorer->mft()->record_from_path("\\$Extend\\$UsnJrnl"); + if (record == nullptr) + { + std::cout << "[!] Not found" << std::endl; + return 2; + } + std::cout << "[+] Found in file record: " << std::to_string(record->header()->MFTRecordIndex) << std::endl; + + ULONG64 total_size = record->datasize(MFT_ATTRIBUTE_DATA_USN_NAME, true); + std::cout << "[+] $J stream size: " << utils::format::size(total_size) << " (could be sparse)" << std::endl; + + std::cout << "[+] Creating " << opts->output << std::endl; + if (opts->format == "raw") + { + HANDLE houtput = CreateFileA(opts->output.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + if (houtput == INVALID_HANDLE_VALUE) + { + std::cout << "[!] Error creating output file" << std::endl; + return 1; + } + + ULONG64 processed_size = 0; + + for (auto& block : record->process_data(MFT_ATTRIBUTE_DATA_USN_NAME, 1024 * 1024, true)) + { + std::cout << "\r[+] Processing USN records: " << utils::format::size(processed_size) << " "; + processed_size += block.second; + + DWORD written = 0; + WriteFile(houtput, block.first, block.second, &written, NULL); + } + std::cout << "\r[+] Processing USN records: " << utils::format::size(processed_size); + + CloseHandle(houtput); + } + else if (opts->format == "csv" || opts->format == "json") + { + std::shared_ptr ffile; + + if (opts->format == "csv") + { + ffile = std::make_shared(opts->output); + } + else + { + ffile = std::make_shared(opts->output); + } + + ffile->set_columns( + { + "MajorVersion", + "MinorVersion", + "FileReferenceNumber", + "FileReferenceSequenceNumber", + "ParentFileReferenceNumber", + "ParentFileReferenceSequenceNumber", + "Usn", + "Timestamp", + "Reason", + "SourceInfo", + "SecurityId", + "FileAttributes", + "Filename" + } + ); + + process_usn(explorer, record, pf, opts->mode == "full", ffile); + } + else + { + std::cout << "[!] Invalid or missing format" << std::endl; + } + + std::cout << std::endl << "[+] Closing volume" << std::endl; + + return 0; +} + +namespace commands +{ + namespace usn + { + namespace dump + { + int dispatch(std::shared_ptr opts) + { + std::ios_base::fmtflags flag_backup(std::cout.flags()); + + std::shared_ptr disk = get_disk(opts); + if (disk != nullptr) + { + std::shared_ptr volume = disk->volumes(opts->volume); + if (volume != nullptr) + { + if (opts->output != "") + { + if (opts->format == "") opts->format = "raw"; + + if (opts->mode != "full" && opts->mode != "fast") + { + opts->mode = "fast"; + } + + print_usn_journal(disk, volume, opts); + } + else + { + invalid_option(opts, "output", opts->output); + } + } + else + { + invalid_option(opts, "volume", opts->volume); + } + } + else + { + invalid_option(opts, "disk", opts->disk); + } + + std::cout.flags(flag_backup); + return 0; + } + } + } +} \ No newline at end of file diff --git a/Sources/Commands/command_vbr.cpp b/Sources/Commands/command_vbr.cpp index 333cb9f..9e02126 100644 --- a/Sources/Commands/command_vbr.cpp +++ b/Sources/Commands/command_vbr.cpp @@ -299,9 +299,9 @@ void print_bootsector_bitlocker(PBOOT_SECTOR_BITLOCKER pbs) print_boot_code((PBYTE)pbs, size_to_disass, 0x7c5a); } -void print_bootsector(std::shared_ptr disk, std::shared_ptr vol) { - std::cout << std::setfill('0'); - utils::ui::title("VBR from " + disk->name() + " > Volume:" + std::to_string(vol->index())); +void print_bootsector(std::shared_ptr disk, std::shared_ptr vol) +{ + utils::ui::title("VBR for " + disk->name() + " > Volume:" + std::to_string(vol->index())); if (vol->filesystem() == "FAT32") print_bootsector_fat32((PBOOT_SECTOR_FAT32)vol->bootsector()); else if (vol->filesystem() == "FAT16") print_bootsector_fat1x((PBOOT_SECTOR_FAT1X)vol->bootsector()); diff --git a/Sources/Commands/commands.h b/Sources/Commands/commands.h index 281c180..332ce55 100644 --- a/Sources/Commands/commands.h +++ b/Sources/Commands/commands.h @@ -82,7 +82,18 @@ namespace commands { namespace usn { - int dispatch(std::shared_ptr opts); + void load_mft(std::shared_ptr explorer, std::unordered_map& map_parent, std::unordered_map& map_name); + + std::string get_file_path(std::unordered_map& map_parent, std::unordered_map& map_name, DWORD64 parent_inode, std::string filename); + + namespace dump + { + int dispatch(std::shared_ptr opts); + } + namespace analyze + { + int dispatch(std::shared_ptr opts); + } } namespace extract @@ -107,6 +118,11 @@ namespace commands { int dispatch(std::shared_ptr opts); } + namespace dump + { + int dispatch(std::shared_ptr opts); + } + namespace btree { int dispatch(std::shared_ptr opts); @@ -119,19 +135,20 @@ namespace commands { int print_mft_info_details(std::shared_ptr record, ULONG32 cluster_size); } - namespace fve - { - int dispatch(std::shared_ptr opts); - } - namespace bitlocker { - int dispatch(std::shared_ptr opts); - } - - namespace bitdecrypt - { - int dispatch(std::shared_ptr opts); + namespace info + { + int dispatch(std::shared_ptr opts); + } + namespace decrypt + { + int dispatch(std::shared_ptr opts); + } + namespace fve + { + int dispatch(std::shared_ptr opts); + } } namespace help diff --git a/Sources/NTFS/ntfs_mft_record.cpp b/Sources/NTFS/ntfs_mft_record.cpp index f6463e0..b60f9d5 100644 --- a/Sources/NTFS/ntfs_mft_record.cpp +++ b/Sources/NTFS/ntfs_mft_record.cpp @@ -127,7 +127,7 @@ ULONG64 MFTRecord::datasize(std::string stream_name, bool real_size) return 0; } -std::map MFTRecord::parse_index_block(std::shared_ptr> pIndexBlock) +std::map MFTRecord::_parse_index_block(std::shared_ptr> pIndexBlock) { std::map mapVCNToIndexBlock; @@ -278,7 +278,7 @@ std::vector> MFTRecord::index() { indexBlocks = attribute_data(pAttrAllocation); - VCNToBlock = parse_index_block(indexBlocks); + VCNToBlock = _parse_index_block(indexBlocks); } else { @@ -292,6 +292,18 @@ std::vector> MFTRecord::index() return ret; } +bool MFTRecord::is_valid(PMFT_RECORD_HEADER pmfth) +{ + return ( + (memcmp(pmfth->signature, "FILE", 4) == 0) && + (pmfth->attributeOffset > 0x30) && + (pmfth->attributeOffset < 0x400) && + (POINTER_ADD(PMFT_RECORD_ATTRIBUTE_HEADER, pmfth, pmfth->attributeOffset)->TypeCode >= 10) && + (POINTER_ADD(PMFT_RECORD_ATTRIBUTE_HEADER, pmfth, pmfth->attributeOffset)->TypeCode <= 100) + ); + +} + std::vector MFTRecord::read_dataruns(PMFT_RECORD_ATTRIBUTE_HEADER pAttribute) { std::vector result; @@ -388,7 +400,7 @@ ULONG64 MFTRecord::data_to_file(std::wstring dest_filename, std::string stream_n DWORD written_block; if (!WriteFile(output, data_block.first, data_block.second, &written_block, NULL)) { - std::cout << "[!] WriteFile failed" << std::endl; + std::cout << "[!] WriteFile failed (0x" << utils::format::hex(GetLastError()) << ")" << std::endl; break; } else @@ -400,12 +412,12 @@ ULONG64 MFTRecord::data_to_file(std::wstring dest_filename, std::string stream_n } else { - std::cout << "[!] CreateFile failed" << std::endl; + std::cout << "[!] CreateFile failed (0x" << utils::format::hex(GetLastError()) << ")" << std::endl; } return written_bytes; } -cppcoro::generator> MFTRecord::process_data_raw(std::string stream_name, DWORD block_size, bool skip_sparse) +cppcoro::generator> MFTRecord::_process_data_raw(std::string stream_name, DWORD block_size, bool skip_sparse) { PMFT_RECORD_ATTRIBUTE_HEADER pAttributeData = attribute_header($DATA, stream_name); if (pAttributeData != NULL) @@ -509,7 +521,7 @@ cppcoro::generator> MFTRecord::process_data_raw(std::str if (pAttributeHeaderRP != NULL) { auto pAttributeRP = attribute_data(pAttributeHeaderRP); - if (pAttributeRP->data()->ReparseTag = IO_REPARSE_TAG_WOF) + if (pAttributeRP->data()->ReparseTag == IO_REPARSE_TAG_WOF) { switch (pAttributeRP->data()->WindowsOverlayFilterBuffer.CompressionAlgorithm) { @@ -617,7 +629,7 @@ cppcoro::generator> MFTRecord::process_data_raw(std::str if (next_inode != _record->data()->MFTRecordIndex) { std::shared_ptr extRecordHeader = _mft->record_from_number(pAttrListI->recordNumber & 0xffffffffffff); - for (std::pair b : extRecordHeader->process_data_raw(stream_name, block_size, skip_sparse)) + for (std::pair b : extRecordHeader->_process_data_raw(stream_name, block_size, skip_sparse)) { co_yield b; } @@ -643,10 +655,10 @@ cppcoro::generator> MFTRecord::process_data_raw(std::str cppcoro::generator> MFTRecord::process_data(std::string stream_name, DWORD block_size, bool skip_sparse) { - ULONG64 final_datasize = datasize("", true); + ULONG64 final_datasize = datasize(stream_name, true); bool check_size = final_datasize != 0; // ex: no real size for usn - for (auto& block : process_data_raw(stream_name, block_size, skip_sparse)) + for (auto& block : _process_data_raw(stream_name, block_size, skip_sparse)) { if (block.second > final_datasize && check_size) { @@ -705,7 +717,7 @@ std::shared_ptr> MFTRecord::data(std::string stream_name, bool rea return ret; } -std::vector MFTRecord::alternate_data_names() +std::vector MFTRecord::ads_names() { std::vector ret; diff --git a/Sources/NTFS/ntfs_mft_record.h b/Sources/NTFS/ntfs_mft_record.h index de668db..28e98b8 100644 --- a/Sources/NTFS/ntfs_mft_record.h +++ b/Sources/NTFS/ntfs_mft_record.h @@ -33,7 +33,9 @@ class MFTRecord MFT* _mft = nullptr; - std::map parse_index_block(std::shared_ptr> pIndexBlock); + std::map _parse_index_block(std::shared_ptr> pIndexBlock); + + cppcoro::generator> _process_data_raw(std::string stream_name = "", DWORD blocksize = 1024 * 1024, bool skip_sparse = false); public: @@ -120,13 +122,13 @@ class MFTRecord ULONG64 data_to_file(std::wstring dest_filename, std::string stream_name = "", bool skip_sparse = false); - cppcoro::generator> process_data_raw(std::string stream_name = "", DWORD blocksize = 1024 * 1024, bool skip_sparse = false); - cppcoro::generator> process_data(std::string stream_name = "", DWORD blocksize = 1024 * 1024, bool skip_sparse = false); - std::vector alternate_data_names(); + std::vector ads_names(); std::vector> index(); + static bool is_valid(PMFT_RECORD_HEADER pmfth); + static std::vector read_dataruns(PMFT_RECORD_ATTRIBUTE_HEADER pAttribute); }; \ No newline at end of file diff --git a/Sources/Utils/buffer.h b/Sources/Utils/buffer.h index 8546598..6fe1461 100644 --- a/Sources/Utils/buffer.h +++ b/Sources/Utils/buffer.h @@ -5,9 +5,10 @@ #include #include - #include +#include + template< typename T> class Buffer { @@ -107,6 +108,14 @@ class Buffer } } + cppcoro::generator> process_data(DWORD block_size) + { + for (DWORD64 offset = 0; offset < _size; offset += block_size) + { + co_yield std::pair(reinterpret_cast(_mem) + offset, static_cast(min(_size - offset, block_size))); + } + } + bool to_file(std::wstring filename) { HANDLE hFile = INVALID_HANDLE_VALUE; diff --git a/Sources/Utils/constant_names.cpp b/Sources/Utils/constant_names.cpp index 1281151..cddf751 100644 --- a/Sources/Utils/constant_names.cpp +++ b/Sources/Utils/constant_names.cpp @@ -154,7 +154,7 @@ std::string constants::disk::smart::capabilities(DWORD cap) if (cap & c.first) ret.push_back(c.second); } - return utils::strings::join(ret, ", "); + return utils::strings::join_vec(ret, ", "); } @@ -578,27 +578,27 @@ std::string constants::disk::usn::reasons(DWORD reason) std::vector ret; const std::map reasons_map = { - { 0x1, "DATA_OVERWRITE"}, - { 0x2, "DATA_EXTEND"}, - { 0x4, "DATA_TRUNCATION"}, - { 0x10, "NAMED_DATA_OVERWRITE"}, - { 0x20, "NAMED_DATA_EXTEND"}, - { 0x40, "NAMED_DATA_TRUNCATION"}, - { 0x100, "FILE_CREATE"}, - { 0x200, "FILE_DELETE"}, - { 0x400, "EA_CHANGE"}, - { 0x800, "SECURITY_CHANGE"}, - { 0x1000, "RENAME_OLD_NAME"}, - { 0x2000, "RENAME_NEW_NAME"}, - { 0x4000, "INDEXABLE_CHANGE"}, - { 0x8000, "BASIC_INFO_CHANGE"}, - { 0x10000, "HARD_LINK_CHANGE"}, - { 0x20000, "COMPRESSION_CHANGE"}, - { 0x40000, "ENCRYPTION_CHANGE"}, - { 0x80000, "OBJECT_ID_CHANGE"}, - { 0x100000, "REPARSE_POINT_CHANGE"}, - { 0x200000, "STREAM_CHANGE"}, - { 0x80000000, "CLOSE"} + { USN_REASON_DATA_OVERWRITE, "DATA_OVERWRITE"}, + { USN_REASON_DATA_EXTEND, "DATA_EXTEND"}, + { USN_REASON_DATA_TRUNCATION, "DATA_TRUNCATION"}, + { USN_REASON_NAMED_DATA_OVERWRITE, "NAMED_DATA_OVERWRITE"}, + { USN_REASON_NAMED_DATA_EXTEND, "NAMED_DATA_EXTEND"}, + { USN_REASON_NAMED_DATA_TRUNCATION, "NAMED_DATA_TRUNCATION"}, + { USN_REASON_FILE_CREATE, "FILE_CREATE"}, + { USN_REASON_FILE_DELETE, "FILE_DELETE"}, + { USN_REASON_EA_CHANGE, "EA_CHANGE"}, + { USN_REASON_SECURITY_CHANGE, "SECURITY_CHANGE"}, + { USN_REASON_RENAME_OLD_NAME, "RENAME_OLD_NAME"}, + { USN_REASON_RENAME_NEW_NAME, "RENAME_NEW_NAME"}, + { USN_REASON_INDEXABLE_CHANGE, "INDEXABLE_CHANGE"}, + { USN_REASON_BASIC_INFO_CHANGE, "BASIC_INFO_CHANGE"}, + { USN_REASON_HARD_LINK_CHANGE, "HARD_LINK_CHANGE"}, + { USN_REASON_COMPRESSION_CHANGE, "COMPRESSION_CHANGE"}, + { USN_REASON_ENCRYPTION_CHANGE, "ENCRYPTION_CHANGE"}, + { USN_REASON_OBJECT_ID_CHANGE, "OBJECT_ID_CHANGE"}, + { USN_REASON_REPARSE_POINT_CHANGE, "REPARSE_POINT_CHANGE"}, + { USN_REASON_STREAM_CHANGE, "STREAM_CHANGE"}, + { USN_REASON_CLOSE, "CLOSE"} }; for (auto& r : reasons_map) @@ -606,7 +606,42 @@ std::string constants::disk::usn::reasons(DWORD reason) if (reason & r.first) ret.push_back(r.second); } - return utils::strings::join(ret, "+"); + return utils::strings::join_vec(ret, "+"); +} + +DWORD constants::disk::usn::reasons_inv(std::string r) +{ + const std::map reasons_map = { + { "DATA_OVERWRITE", USN_REASON_DATA_OVERWRITE}, + { "DATA_EXTEND", USN_REASON_DATA_EXTEND }, + { "DATA_TRUNCATION", USN_REASON_DATA_TRUNCATION}, + { "NAMED_DATA_OVERWRITE", USN_REASON_NAMED_DATA_OVERWRITE}, + { "NAMED_DATA_EXTEND", USN_REASON_NAMED_DATA_EXTEND }, + { "NAMED_DATA_TRUNCATION", USN_REASON_NAMED_DATA_TRUNCATION }, + { "FILE_CREATE", USN_REASON_FILE_CREATE }, + { "FILE_DELETE", USN_REASON_FILE_DELETE }, + { "EA_CHANGE", USN_REASON_EA_CHANGE }, + { "SECURITY_CHANGE", USN_REASON_SECURITY_CHANGE }, + { "RENAME_OLD_NAME", USN_REASON_RENAME_OLD_NAME }, + { "RENAME_NEW_NAME", USN_REASON_RENAME_NEW_NAME }, + { "INDEXABLE_CHANGE", USN_REASON_INDEXABLE_CHANGE}, + { "BASIC_INFO_CHANGE", USN_REASON_BASIC_INFO_CHANGE }, + { "HARD_LINK_CHANGE", USN_REASON_HARD_LINK_CHANGE }, + { "COMPRESSION_CHANGE", USN_REASON_COMPRESSION_CHANGE }, + { "ENCRYPTION_CHANGE", USN_REASON_ENCRYPTION_CHANGE}, + { "OBJECT_ID_CHANGE", USN_REASON_OBJECT_ID_CHANGE }, + { "REPARSE_POINT_CHANGE", USN_REASON_REPARSE_POINT_CHANGE}, + { "STREAM_CHANGE", USN_REASON_STREAM_CHANGE}, + { "CLOSE", USN_REASON_CLOSE } + }; + + auto flag = reasons_map.find(r); + if (flag != reasons_map.end()) + { + return flag->second; + } + + return 0; } std::string constants::disk::usn::fileattributes(DWORD attributes) @@ -638,7 +673,40 @@ std::string constants::disk::usn::fileattributes(DWORD attributes) if (attributes & r.first) ret.push_back(r.second); } - return utils::strings::join(ret, "+"); + return utils::strings::join_vec(ret, "+"); +} + +DWORD constants::disk::usn::fileattributes_inv(std::string a) +{ + std::vector ret; + + const std::map attributes_map = { + { "READONLY", 0x1,}, + { "HIDDEN", 0x2}, + { "SYSTEM", 0x4}, + { "DIRECTORY", 0x10}, + { "ARCHIVE", 0x20}, + { "DEVICE", 0x40}, + { "NORMAL", 0x80}, + { "TEMPORARY", 0x100}, + { "SPARSE_FILE", 0x200}, + { "REPARSE_POINT", 0x400}, + { "COMPRESSED", 0x800}, + { "OFFLINE", 0x1000}, + { "NOT_CONTENT_INDEXED", 0x2000}, + { "ENCRYPTED", 0x4000}, + { "INTEGRITY_STREAM", 0x8000}, + { "VIRTUAL", 0x10000}, + { "NO_SCRUB_DATA", 0x20000}, + }; + + auto flag = attributes_map.find(a); + if (flag != attributes_map.end()) + { + return flag->second; + } + + return 0; } std::string constants::disk::logfile::operation(WORD w) @@ -820,7 +888,7 @@ std::string constants::efs::cert_prop_flags(DWORD f) if (f & CRYPT_MACHINE_KEYSET) ret.push_back("CRYPT_MACHINE_KEYSET"); else ret.push_back("CRYPT_USER_KEYSET"); if (f & CRYPT_SILENT) ret.push_back("CRYPT_SILENT"); - return utils::strings::join(ret, " | "); + return utils::strings::join_vec(ret, " | "); } std::string constants::efs::cert_prop_keyspec(DWORD k) diff --git a/Sources/Utils/constant_names.h b/Sources/Utils/constant_names.h index 03ceb99..fd80035 100644 --- a/Sources/Utils/constant_names.h +++ b/Sources/Utils/constant_names.h @@ -60,7 +60,11 @@ namespace constants { { std::string reasons(DWORD r); + DWORD reasons_inv(std::string r); + std::string fileattributes(DWORD a); + + DWORD fileattributes_inv(std::string a); } namespace logfile diff --git a/Sources/Utils/csv_file.cpp b/Sources/Utils/csv_file.cpp new file mode 100644 index 0000000..a9a6ba9 --- /dev/null +++ b/Sources/Utils/csv_file.cpp @@ -0,0 +1,65 @@ +#include "csv_file.h" +#include + + +std::string CSVFile::_escape(std::string s) +{ + std::stringstream ss; + for (auto& c : s) + { + if (c == '\\') ss << c; + ss << c; + } + return ss.str(); +} + +CSVFile::CSVFile(std::string filename, std::string separator) +{ + _file.open(filename, std::ios::out | std::ios::binary); + _separator = separator; +} + +CSVFile::~CSVFile() +{ + if (_file.is_open()) + { + _file.close(); + } +} + +void CSVFile::set_columns(std::initializer_list columns) +{ + _columns = columns; + + bool first = true; + for (auto& column : _columns) + { + if (!first) _file << _separator; + else first = false; + _file << column; + } + _file << std::endl; +} + +void CSVFile::add_item(std::string item) +{ + if (_current_line.size() < _columns.size()) + { + _current_line.push_back('"' + _escape(item) + '"'); + } +} + +void CSVFile::add_item(unsigned long long item) +{ + if (_current_line.size() < _columns.size()) + { + _current_line.push_back(std::to_string(item)); + } +} + +void CSVFile::new_line() +{ + _file << utils::strings::join_vec(_current_line, _separator); + _file << std::endl; + _current_line.clear(); +} diff --git a/Sources/Utils/csv_file.h b/Sources/Utils/csv_file.h new file mode 100644 index 0000000..c66e28a --- /dev/null +++ b/Sources/Utils/csv_file.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Utils/buffer.h" +#include "Utils/utils.h" +#include "Utils/formatted_file.h" + + +class CSVFile : public FormatteddFile +{ +private: + std::ofstream _file; + std::string _separator = ","; + std::vector _current_line; + + std::string _escape(std::string s); + +public: + explicit CSVFile(std::string filename, std::string separator = ","); + + ~CSVFile(); + + void set_columns(std::initializer_list columns); + + void add_item(std::string item = ""); + + void add_item(unsigned long long item); + + void new_line(); +}; diff --git a/Sources/Utils/formatted_file.h b/Sources/Utils/formatted_file.h new file mode 100644 index 0000000..dd4fc02 --- /dev/null +++ b/Sources/Utils/formatted_file.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + + +class FormatteddFile +{ +protected: + std::vector _columns; + +public: + + virtual void set_columns(std::initializer_list columns) = 0; + + virtual void add_item(std::string item = "") = 0; + + virtual void add_item(unsigned long long item) = 0; + + virtual void new_line() = 0; +}; diff --git a/Sources/Utils/json_file.cpp b/Sources/Utils/json_file.cpp new file mode 100644 index 0000000..cd15686 --- /dev/null +++ b/Sources/Utils/json_file.cpp @@ -0,0 +1,55 @@ +#include "json_file.h" +#include + +JSONFile::JSONFile(std::string filename) +{ + _file.open(filename, std::ios::out | std::ios::binary); + if (_file.is_open()) + { + _file << "["; + } +} + +JSONFile::~JSONFile() +{ + if (_file.is_open()) + { + _file << std::endl << "]"; + _file.close(); + } +} + +void JSONFile::set_columns(std::initializer_list columns) +{ + _columns = columns; +} + +void JSONFile::add_item(std::string item) +{ + if (_current_item.size() < _columns.size()) + { + _current_item[_columns[_current_item.size()]] = utils::strings::str_to_utf8(item); + } +} + +void JSONFile::add_item(unsigned long long item) +{ + if (_current_item.size() < _columns.size()) + { + _current_item[_columns[_current_item.size()]] = std::to_string(item); + } +} + +void JSONFile::new_line() +{ + if (!_first) + { + _file << ","; + } + else + { + _first = false; + } + _file << std::endl << _current_item.dump(4); + _current_item.clear(); +} diff --git a/Sources/Utils/json_file.h b/Sources/Utils/json_file.h new file mode 100644 index 0000000..f3e9393 --- /dev/null +++ b/Sources/Utils/json_file.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "Utils/buffer.h" +#include "Utils/utils.h" +#include "Utils/formatted_file.h" + +class JSONFile : public FormatteddFile +{ +protected: + std::ofstream _file; + nlohmann::json _current_item; + bool _first = true; + +public: + explicit JSONFile(std::string filename); + + ~JSONFile(); + + void set_columns(std::initializer_list columns); + + void add_item(std::string item = ""); + + void add_item(unsigned long long item); + + void new_line(); +}; diff --git a/Sources/Utils/path_finder.cpp b/Sources/Utils/path_finder.cpp new file mode 100644 index 0000000..13515f6 --- /dev/null +++ b/Sources/Utils/path_finder.cpp @@ -0,0 +1,82 @@ +#include "path_finder.h" + +PathFinder::PathFinder(std::shared_ptr volume) +{ + + { + std::cout << "[+] Loading $MFT records" << std::endl; + + std::shared_ptr explorer = std::make_shared(volume); + + _map_name.clear(); + _map_parent.clear(); + + std::shared_ptr record_mft = explorer->mft()->record_from_number(0); + if (record_mft == nullptr) + { + std::cout << "[!] Error accessing record 0" << std::endl; + return; + } + ULONG64 total_size_mft = record_mft->datasize(); + DWORD record_size = explorer->reader()->sizes.record_size; + + std::shared_ptr record = nullptr; + + auto index = 0ULL; + for (index = 0ULL; index < (total_size_mft / record_size); index++) + { + std::cout << "\r[+] Processing $MFT records: " << utils::format::size(index * record_size) << " "; + + record = explorer->mft()->record_from_number(index); + + if (record == nullptr || !MFTRecord::is_valid(record->header())) + { + continue; + } + + ULONGLONG file_info_parentid = 0; + PMFT_RECORD_ATTRIBUTE_HEADER pattr = record->attribute_header($FILE_NAME, "", 0); + if (pattr != nullptr) + { + auto pattr_filename = POINTER_ADD(PMFT_RECORD_ATTRIBUTE_FILENAME, pattr, pattr->Form.Resident.ValueOffset); + file_info_parentid = pattr_filename->ParentDirectory.SequenceNumber << 48 | pattr_filename->ParentDirectory.FileRecordNumber; + } + + ULONGLONG file_record_num = record->header()->sequenceNumber; + file_record_num = file_record_num << 48 | record->header()->MFTRecordIndex; + + _map_parent[file_record_num] = file_info_parentid; + _map_name[file_record_num] = utils::strings::to_utf8(record->filename()); + } + std::cout << "\r[+] Processing $MFT records: " << utils::format::size(index * record_size) << " " << std::endl; + } +} + +std::string PathFinder::get_file_path(std::string filename, DWORD64 parent_inode) +{ + std::string path = filename; + + while ((parent_inode & 0xffffffffffff) != 5) + { + auto tmp = _map_parent.find(parent_inode); + if (tmp != _map_parent.end()) + { + path = _map_name[parent_inode] + "\\" + path; + parent_inode = tmp->second; + } + else + { + break; + } + } + if ((parent_inode & 0xffffffffffff) == 5) + { + path = "volume:\\" + path; + } + else + { + path = "orphan:\\" + path; + } + + return path; +} diff --git a/Sources/Utils/path_finder.h b/Sources/Utils/path_finder.h new file mode 100644 index 0000000..2555a80 --- /dev/null +++ b/Sources/Utils/path_finder.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +class PathFinder { + +private: + std::unordered_map _map_parent; + std::unordered_map _map_name; + +public: + PathFinder(std::shared_ptr volume); + + std::string get_file_path(std::string filename, DWORD64 parent_inode); + + size_t count() { return _map_name.size(); } +}; diff --git a/Sources/Utils/table.cpp b/Sources/Utils/table.cpp index 50d37a6..12659cf 100644 --- a/Sources/Utils/table.cpp +++ b/Sources/Utils/table.cpp @@ -213,12 +213,12 @@ void utils::ui::Table::render(std::ostream& out) } for (i = 0; i < headers.size() - 1; i++) { out.width(column_size[i]); - if (header_line_index < headers[i].size()) out << std::left << headers[i][header_line_index]; + if (header_line_index < headers[i].size()) out << (column_align[i] == TableAlign::LEFT ? std::left : std::right) << headers[i][header_line_index]; else out << std::left << ""; out << " | "; } out.width(column_size[i]); - if (header_line_index < headers[i].size()) out << std::left << headers[i][header_line_index]; + if (header_line_index < headers[i].size()) out << (column_align[i] == TableAlign::LEFT ? std::left : std::right) << headers[i][header_line_index]; else out << std::left << ""; if (border_right) { diff --git a/Sources/Utils/usn_rules.cpp b/Sources/Utils/usn_rules.cpp new file mode 100644 index 0000000..3dc3bd6 --- /dev/null +++ b/Sources/Utils/usn_rules.cpp @@ -0,0 +1,249 @@ +#include "usn_rules.h" + +#include +#include + +USNRules::USNRules(std::string filename) +{ + try + { + std::ifstream ifs(filename); + if (ifs.fail()) + { + std::cout << "[!] Error reading JSON!" << std::endl; + } + else + { + + _file = nlohmann::json::parse(ifs); + + int hy_rule = 0; + for (auto& rule : _file) + { + try + { + hy_rule++; + auto r = std::make_shared(rule); + _rules.push_back(r); + } + catch (std::invalid_argument& ex) + { + std::cout << "[!] Error parsing rule " << hy_rule << ": " << ex.what() << std::endl; + } + } + } + } + catch (nlohmann::json::exception& ex) + { + std::cout << "[!] Error parsing JSON!" << std::endl; + std::cout << " " << ex.what() << std::endl; + } +} + +USNRules::~USNRules() +{ + +} + +bool in_array(const std::string& value, const std::vector& array) +{ + return std::find(array.begin(), array.end(), value) != array.end(); +} + +USNRule::USNRule(nlohmann::json j) +{ + if (j.contains("id")) + { + _id = j["id"]; + } + else + { + throw std::invalid_argument("missing id"); + } + if (j.contains("description")) + { + _description = j["description"]; + } + else + { + throw std::invalid_argument("missing description"); + } + if (j.contains("severity")) + { + _severity = j["severity"]; + } + else + { + throw std::invalid_argument("missing severity"); + } + + if (j["rule"].is_object()) + { + for (auto& it : j["rule"].items()) + { + if (it.key() == "filename") + { + try + { + _a_rules[it.key()] = std::regex(it.value().get()); + } + catch (std::regex_error) + { + throw std::invalid_argument("filename rule \"" + it.value().get() + "\" is not a valid regex"); + } + } + else if (it.key() == "reason") + { + if (it.value().is_array()) + { + DWORD64 mask = 0; + DWORD64 value = 0; + + for (auto& reason : it.value()) + { + std::string& r = reason.get(); + if (r.length() > 1) + { + if (r[0] == '+' || r[0] == '-') + { + DWORD64 r_flag = constants::disk::usn::reasons_inv(r.substr(1)); + if (r_flag) + { + mask |= r_flag; + if (r[0] == '+') + { + value |= r_flag; + } + else + { + value &= ~r_flag; + } + } + else + { + throw std::invalid_argument("invalid usn reason value \"" + r.substr(1) + "\""); + } + } + else + { + throw std::invalid_argument("reason must start with \"+\" or \"-\""); + } + } + else + { + throw std::invalid_argument("invalid reason value \"" + r + "\""); + } + } + _a_rules[it.key()] = value << 32 | mask;; + } + else + { + throw std::invalid_argument("\"reason\" is not an array"); + } + } + else if (it.key() == "attributes") + { + if (it.value().is_array()) + { + DWORD64 mask = 0; + DWORD64 value = 0; + + for (auto& reason : it.value()) + { + std::string& r = reason.get(); + if (r.length() > 1) + { + if (r[0] == '+' || r[0] == '-') + { + DWORD64 r_flag = constants::disk::usn::fileattributes_inv(r.substr(1)); + if (r_flag) + { + mask |= r_flag; + if (r[0] == '+') + { + value |= r_flag; + } + else + { + value &= ~r_flag; + } + } + else + { + throw std::invalid_argument("invalid usn file attribute value \"" + r.substr(1) + "\""); + } + } + else + { + throw std::invalid_argument("file attribute must start with \"+\" or \"-\""); + } + } + else + { + throw std::invalid_argument("invalid file attribute value \"" + r + "\""); + } + } + _a_rules[it.key()] = value << 32 | mask;; + } + else + { + throw std::invalid_argument("\"attributes\" is not an array"); + } + } + else + { + throw std::invalid_argument("unknown rule type \"" + it.key() + "\""); + } + } + if (_a_rules.empty()) + { + throw std::invalid_argument("\"rule\" is empty"); + } + } + else + { + throw std::invalid_argument("\"rule\" is not an object"); + } + +} + +bool USNRule::match(std::string filename, PUSN_RECORD_V2 usn) +{ + std::map::iterator it; + for (it = _a_rules.begin(); it != _a_rules.end(); ++it) + { + if (it->first == "filename") + { + if (!std::regex_match(filename, std::any_cast(it->second))) + { + return false; + } + } + else if (it->first == "reason") + { + DWORD mask = std::any_cast(it->second) & 0xffffffff; + DWORD value = std::any_cast(it->second) >> 32; + + if ((usn->Reason & mask) != value) + { + return false; + } + } + else if (it->first == "attributes") + { + DWORD mask = std::any_cast(it->second) & 0xffffffff; + DWORD value = std::any_cast(it->second) >> 32; + + if ((usn->FileAttributes & mask) != value) + { + return false; + } + } + } + return true; +} + +USNRule::~USNRule() +{ + _a_rules.clear(); +} diff --git a/Sources/Utils/usn_rules.h b/Sources/Utils/usn_rules.h new file mode 100644 index 0000000..132fdcf --- /dev/null +++ b/Sources/Utils/usn_rules.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Utils/buffer.h" +#include "Utils/utils.h" + +#include + + +class USNRule +{ +private: + std::string _id; + std::string _description; + std::string _status; + std::string _severity; + + std::map _a_rules; + +public: + explicit USNRule(nlohmann::json j); + + std::string id() { return _id; }; + std::string description() { return _description; }; + std::string status() { return _status; }; + std::string severity() { return _severity; }; + + bool match(std::string filename, PUSN_RECORD_V2 usn); + + ~USNRule(); +}; + + +class USNRules +{ +private: + nlohmann::json _file; + + std::vector> _rules; + +public: + explicit USNRules(std::string filename); + + std::vector> rules() { return _rules; }; + + unsigned int size() { return static_cast(_rules.size()); } + + ~USNRules(); + +}; diff --git a/Sources/Utils/usn_stats.h b/Sources/Utils/usn_stats.h new file mode 100644 index 0000000..1d84d1d --- /dev/null +++ b/Sources/Utils/usn_stats.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +class USNStats +{ +private: + std::map _stats; + +public: + USNStats() + { + _stats["oldest"] = 0ULL; + _stats["latest"] = 0ULL; + _stats["file creation"] = 0ULL; + _stats["file deletion"] = 0ULL; + _stats["file rename"] = 0ULL; + _stats["records count"] = 0ULL; + } + + void add_record(std::string& filename, PUSN_RECORD_V2 record) + { + if (_stats["latest"] == 0 || _stats["latest"] < record->TimeStamp.QuadPart) + { + _stats["latest"] = record->TimeStamp.QuadPart; + } + if (_stats["oldest"] == 0 || _stats["oldest"] > record->TimeStamp.QuadPart) + { + _stats["oldest"] = record->TimeStamp.QuadPart; + } + if (record->Reason & USN_REASON_FILE_CREATE) _stats["file creation"] += 1; + if (record->Reason & USN_REASON_FILE_DELETE) _stats["file deletion"] += 1; + if (record->Reason & USN_REASON_RENAME_NEW_NAME) _stats["file rename"] += 1; + _stats["records count"] += 1; + } + + int64_t get_stat(std::string cat) + { + if (_stats.find(cat) != _stats.end()) + { + return _stats[cat]; + } + return 0; + } + + std::map& get_stats() + { + return _stats; + } + + bool is_date(std::pair e) + { + return e.first == "oldest" || e.first == "latest"; + } + + bool is_count(std::pair e) + { + return !is_date(e); + } +}; diff --git a/Sources/Utils/utils.cpp b/Sources/Utils/utils.cpp index 620d80d..938ae1d 100644 --- a/Sources/Utils/utils.cpp +++ b/Sources/Utils/utils.cpp @@ -212,16 +212,6 @@ namespace utils } } - std::string join(std::vector items, const std::string separator) - { - std::ostringstream out; - if (items.size() > 0) out << items[0]; - for (unsigned int i = 1; i < items.size(); i++) { - out << separator << items[i]; - } - return out.str(); - } - std::vector split(const std::string& text, TCHAR delimiter) { std::vector result; @@ -623,6 +613,7 @@ namespace utils void title(std::string s, std::ostream& out) { + std::cout << std::setfill('0'); out << s << std::endl; out << line(utils::strings::utf8_string_size(s)); out << std::endl; diff --git a/Sources/Utils/utils.h b/Sources/Utils/utils.h index 3c84a1d..bdd9bc5 100644 --- a/Sources/Utils/utils.h +++ b/Sources/Utils/utils.h @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -63,7 +64,36 @@ namespace utils std::vector split(const std::string& text, TCHAR delimiter); - std::string join(std::vector items, std::string separator); + template + std::string join_vec(std::vector items, const std::string separator) + { + std::ostringstream out; + if (items.size() > 0) out << std::string(items[0]); + for (unsigned int i = 1; i < items.size(); i++) { + out << separator << std::string(items[i]); + } + return out.str(); + } + + template + std::string join_set(std::set items, const std::string separator) + { + std::ostringstream out; + bool first = true; + for (auto& item : items) + { + if (!first) + { + out << separator; + } + else + { + first = false; + } + out << std::string(item); + } + return out.str(); + } } namespace format diff --git a/Sources/Utils/zone_identifier.cpp b/Sources/Utils/zone_identifier.cpp new file mode 100644 index 0000000..8750b70 --- /dev/null +++ b/Sources/Utils/zone_identifier.cpp @@ -0,0 +1,30 @@ +#include "zone_identifier.h" + +utils::dfir::ZoneIdentifier::ZoneIdentifier(std::shared_ptr> data) +{ + auto lines = utils::strings::split(reinterpret_cast(data->data()), '\n'); + for (auto& line : lines) + { + size_t pos = line.find("="); + if (pos != std::string::npos) + { + if (line.back() == '\r') + { + line.pop_back(); + } + _values.insert(std::pair(line.substr(0, pos), line.substr(pos + 1))); + } + } +} + +std::string utils::dfir::ZoneIdentifier::get_value(std::string key) +{ + if (_values.find(key) != _values.end()) + { + return _values[key]; + } + else + { + return ""; + } +} diff --git a/Sources/Utils/zone_identifier.h b/Sources/Utils/zone_identifier.h new file mode 100644 index 0000000..464e14f --- /dev/null +++ b/Sources/Utils/zone_identifier.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +#include "Utils/buffer.h" +#include "Utils/utils.h" + +namespace utils +{ + namespace dfir { + + class ZoneIdentifier + { + private: + std::map _values; + + public: + explicit ZoneIdentifier(std::shared_ptr> data); + + std::string get_value(std::string); + }; + } +} \ No newline at end of file diff --git a/Sources/main.cpp b/Sources/main.cpp index f772fd3..9b2dfd0 100644 --- a/Sources/main.cpp +++ b/Sources/main.cpp @@ -25,33 +25,37 @@ int main(int argc, char** argv) { { commands::help::dispatch(opts); } - else { - try { - if (opts->command == "mbr") commands::mbr::dispatch(opts); - else if (opts->command == "shell") commands::shell::dispatch(opts); - else if (opts->command == "smart") commands::smart::dispatch(opts); - else if (opts->command == "gpt") commands::gpt::dispatch(opts); - else if (opts->command == "usn") commands::usn::dispatch(opts); - else if (opts->command == "extract") commands::extract::dispatch(opts); - else if (opts->command == "vbr") commands::vbr::dispatch(opts); - else if (opts->command == "image") commands::image::dispatch(opts); - else if (opts->command == "undelete") commands::undelete::dispatch(opts); - else if (opts->command == "mft.record") commands::mft::record::dispatch(opts); - else if (opts->command == "mft.btree") commands::mft::btree::dispatch(opts); - else if (opts->command == "shadow") commands::shadow::dispatch(opts); - else if (opts->command == "logfile") commands::logfile::dispatch(opts); - else if (opts->command == "reparse") commands::reparse::dispatch(opts); - else if (opts->command == "streams") commands::streams::dispatch(opts); + else + { + try + { + if (opts->command == "bitlocker.decrypt") commands::bitlocker::decrypt::dispatch(opts); + else if (opts->command == "bitlocker.fve") commands::bitlocker::fve::dispatch(opts); + else if (opts->command == "bitlocker.info") commands::bitlocker::info::dispatch(opts); else if (opts->command == "efs.backup") commands::efs::backup::dispatch(opts); else if (opts->command == "efs.certificate") commands::efs::certificate::dispatch(opts); else if (opts->command == "efs.decrypt") commands::efs::decrypt::dispatch(opts); - else if (opts->command == "efs.masterkey") commands::efs::masterkey::dispatch(opts); else if (opts->command == "efs.key") commands::efs::key::dispatch(opts); - else if (opts->command == "bitdecrypt") commands::bitdecrypt::dispatch(opts); - else if (opts->command == "bitlocker") commands::bitlocker::dispatch(opts); - else if (opts->command == "fve") commands::fve::dispatch(opts); + else if (opts->command == "efs.masterkey") commands::efs::masterkey::dispatch(opts); + else if (opts->command == "extract") commands::extract::dispatch(opts); + else if (opts->command == "gpt") commands::gpt::dispatch(opts); else if (opts->command == "help") commands::help::dispatch(opts); + else if (opts->command == "image") commands::image::dispatch(opts); else if (opts->command == "info") commands::info::dispatch(opts); + else if (opts->command == "logfile.dump") commands::logfile::dispatch(opts); + else if (opts->command == "mbr") commands::mbr::dispatch(opts); + else if (opts->command == "mft.btree") commands::mft::btree::dispatch(opts); + else if (opts->command == "mft.dump") commands::mft::dump::dispatch(opts); + else if (opts->command == "mft.record") commands::mft::record::dispatch(opts); + else if (opts->command == "reparse") commands::reparse::dispatch(opts); + else if (opts->command == "shadow") commands::shadow::dispatch(opts); + else if (opts->command == "shell") commands::shell::dispatch(opts); + else if (opts->command == "smart") commands::smart::dispatch(opts); + else if (opts->command == "streams") commands::streams::dispatch(opts); + else if (opts->command == "undelete") commands::undelete::dispatch(opts); + else if (opts->command == "usn.analyze") commands::usn::analyze::dispatch(opts); + else if (opts->command == "usn.dump") commands::usn::dump::dispatch(opts); + else if (opts->command == "vbr") commands::vbr::dispatch(opts); else { if (opts->command == "") diff --git a/Sources/options.cpp b/Sources/options.cpp index 0586541..01ac9ee 100644 --- a/Sources/options.cpp +++ b/Sources/options.cpp @@ -121,11 +121,13 @@ std::shared_ptr parse_options(int argc, char** argv) { if (is_option(argv[i], "sid")) { read_option_string(argv[i], ret->sid); continue; } if (is_option(argv[i], "masterkey")) { read_option_hexbuffer(argv[i], &ret->masterkey); continue; } if (is_option(argv[i], "pfx")) { read_option_string(argv[i], ret->pfx); continue; } + if (is_option(argv[i], "mode")) { read_option_string(argv[i], ret->mode); continue; } if (is_option(argv[i], "recovery")) { read_option_string(argv[i], ret->recovery); continue; } if (is_option(argv[i], "image")) { read_option_string(argv[i], ret->image); continue; } if (is_option(argv[i], "bek")) { read_option_string(argv[i], ret->bek); continue; } if (is_option(argv[i], "fvek")) { read_option_string(argv[i], ret->fvek); continue; } if (is_option(argv[i], "format")) { read_option_string(argv[i], ret->format); continue; } + if (is_option(argv[i], "rules")) { read_option_string(argv[i], ret->rules); continue; } if (!strncmp(argv[i], "--sam", 5)) { ret->sam = true; continue; } if (!strncmp(argv[i], "--system", 8)) { ret->system = true; continue; } if (is_help(argv[i])) { ret->show_usage = true; continue; } diff --git a/Sources/options.h b/Sources/options.h index 1987c5c..79cefd3 100644 --- a/Sources/options.h +++ b/Sources/options.h @@ -14,6 +14,7 @@ class Options { std::string from; std::string output; std::string format; + std::string rules; std::string password; std::string sid; @@ -22,6 +23,7 @@ class Options { std::string fvek; std::string image; std::string pfx; + std::string mode; std::shared_ptr> masterkey = nullptr; diff --git a/vcpkg.json b/vcpkg.json index 1c50bac..5dd08e8 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "ntfstool", - "version-string": "1.3", + "version-string": "1.6", "description": "NTFSTool is a forensic tool for hard disks and NTFS volumes.", "homepage": "https://github.com/thewhiteninja/ntfstool", "dependencies": [