├── .github └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── samples ├── mbr.raw ├── ntfs.raw └── test_file ├── src ├── bin │ ├── list_files.rs │ ├── read_usn.rs │ └── volume_layout.rs ├── bindings.rs ├── errors.rs ├── lib.rs ├── tsk_fs.rs ├── tsk_fs_attr.rs ├── tsk_fs_dir.rs ├── tsk_fs_file.rs ├── tsk_fs_file_handle.rs ├── tsk_fs_meta.rs ├── tsk_fs_name.rs ├── tsk_img.rs ├── tsk_img_reader.rs ├── tsk_vs.rs ├── tsk_vs_part.rs └── tsk_vs_part_handle.rs ├── tests ├── test_img_readseek.rs ├── test_tsk_fs_attr.rs ├── test_tsk_part_handle.rs └── test_tsk_wrappers.rs └── wrapper.h /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v?[0-9]+.[0-9]+.[0-9]+" 6 | jobs: 7 | create-release: 8 | name: create-release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Create artifacts directory 12 | run: mkdir artifacts 13 | 14 | - name: Get the release version from the tag 15 | if: env.VERSION == '' 16 | run: | 17 | echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 18 | echo "version is: ${{ env.VERSION }}" 19 | 20 | - name: Create GitHub release 21 | id: release 22 | uses: actions/create-release@v1 23 | continue-on-error: true 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | tag_name: ${{ env.VERSION }} 28 | release_name: ${{ env.VERSION }} 29 | 30 | - name: Save release upload URL to artifact 31 | run: echo "${{ steps.release.outputs.upload_url }}" > artifacts/release-upload-url 32 | 33 | - name: Save version number to artifact 34 | run: echo "${{ env.VERSION }}" > artifacts/release-version 35 | 36 | - name: Upload artifacts 37 | uses: actions/upload-artifact@v1 38 | with: 39 | name: artifacts 40 | path: artifacts 41 | 42 | build-windows-release: 43 | name: build-release 44 | needs: ["create-release"] 45 | runs-on: windows-latest 46 | 47 | env: 48 | TARGET_DIR: ./target 49 | RUST_BACKTRACE: 1 50 | MACOSX_DEPLOYMENT_TARGET: 10.9 51 | 52 | steps: 53 | - name: Get release download URL 54 | uses: actions/download-artifact@v1 55 | with: 56 | name: artifacts 57 | path: artifacts 58 | 59 | - name: Set release upload URL and release version 60 | shell: bash 61 | run: | 62 | release_upload_url="$(cat artifacts/release-upload-url)" 63 | echo "RELEASE_UPLOAD_URL=$release_upload_url" >> $GITHUB_ENV 64 | echo "release upload url: $RELEASE_UPLOAD_URL" 65 | release_version="$(cat artifacts/release-version)" 66 | echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV 67 | echo "release version: $RELEASE_VERSION" 68 | 69 | - name: Install LLVM and Clang # required for bindgen to work, see https://github.com/rust-lang/rust-bindgen/issues/1797 70 | uses: KyleMayes/install-llvm-action@latest 71 | with: 72 | version: "11.0" 73 | directory: ${{ runner.temp }}/llvm 74 | 75 | - name: Set LIBCLANG_PATH 76 | run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV 77 | 78 | - uses: actions/checkout@v2 79 | with: 80 | submodules: 'true' 81 | 82 | - name: Build 83 | run: cargo build --release --verbose 84 | 85 | - name: Build archive 86 | shell: bash 87 | run: | 88 | staging="tools-windows" 89 | mkdir -p "$staging" 90 | 91 | cp "target/release/list_files.exe" "$staging/" 92 | cp "target/release/read_usn.exe" "$staging/" 93 | cp "target/release/volume_layout.exe" "$staging/" 94 | 95 | 7z a "$staging.zip" "$staging" 96 | echo "ASSET=$staging.zip" >> $GITHUB_ENV 97 | 98 | - name: Upload release archive 99 | uses: actions/upload-release-asset@v1.0.1 100 | env: 101 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 102 | with: 103 | upload_url: ${{ env.RELEASE_UPLOAD_URL }} 104 | asset_path: ${{ env.ASSET }} 105 | asset_name: ${{ env.ASSET }} 106 | asset_content_type: application/octet-stream 107 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Rust Build and Tests 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - name: Install LLVM and Clang # required for bindgen to work, see https://github.com/rust-lang/rust-bindgen/issues/1797 14 | uses: KyleMayes/install-llvm-action@latest 15 | with: 16 | version: "11.0" 17 | directory: ${{ runner.temp }}/llvm 18 | - name: Set LIBCLANG_PATH 19 | run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV 20 | - uses: actions/checkout@v2 21 | with: 22 | submodules: 'true' 23 | - name: Switch to Nightly 24 | run: rustup default nightly 25 | - name: Build 26 | run: cargo build --verbose 27 | - name: Run tests 28 | run: cargo test --verbose 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | **/*.rs.bk 5 | samples/test_file_copied_using_libtsk_rs -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sleuthkit"] 2 | path = sleuthkit 3 | url = https://github.com/sleuthkit/sleuthkit 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to 7 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | 10 | ## [0.4.0] 11 | ### Added 12 | - `TskImgReadSeek` to implement a custom TskImg from a read/seek trait 13 | 14 | ## [0.3.0] 15 | ### Added 16 | - `TskVsPartHandle` to handle io for the partition volume 17 | 18 | ### Fixed 19 | - Handling of null pointers returned from `tsk_error_get` 20 | 21 | ### Changed 22 | - tests work of of test images instead of logical volume 23 | - renamed `TskImg::from_source` to `TskImg::from_utf8_sing` to better fit with libtsk function name 24 | 25 | ## [0.2.0] 26 | ### Added 27 | - `TskImg::from_tsk_img_info_ptr()` to create a TskImg from `NonNull` 28 | - imported `tsk_img_open_external` tsk function 29 | - imported tsk structs now derive debug and default 30 | 31 | ## [0.1.0] 32 | ### Added 33 | - Initial Version -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tsk" 3 | version = "0.4.0" 4 | authors = ["matthew seyer "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | log = "0.4" 9 | clap = "2" 10 | 11 | [build-dependencies] 12 | bindgen = "0.61" 13 | 14 | [target.'cfg(windows)'.build-dependencies.winreg] 15 | version = "0.7" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libtsk-rs 2 | Wrapper for TSK (Sleuth Kit) which is a C/C++ library. No safty guaranteed. 3 | 4 | *This is early alpha state!* -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | use std::process::Command; 3 | use std::env; 4 | use std::path::PathBuf; 5 | use std::fs; 6 | 7 | #[cfg(target_os = "windows")] 8 | use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; 9 | 10 | fn main() { 11 | println!(r"cargo:rerun-if-changed=wrapper.h"); 12 | println!(r"cargo:rustc-link-lib=libtsk"); 13 | 14 | #[cfg(target_os = "windows")] 15 | windows_setup(); 16 | 17 | let bindings = bindgen::Builder::default() 18 | .header("wrapper.h") 19 | .parse_callbacks(Box::new(bindgen::CargoCallbacks)) 20 | .clang_args(&["-I", "sleuthkit"]) 21 | .derive_debug(true) 22 | .derive_default(true) 23 | 24 | .allowlist_function("tsk_error_get") 25 | 26 | .allowlist_function("tsk_img_open_utf8_sing") 27 | .allowlist_function("tsk_img_open_external") 28 | .allowlist_function("tsk_img_close") 29 | 30 | .allowlist_function("tsk_vs_open") 31 | .allowlist_function("tsk_vs_close") 32 | 33 | .allowlist_function("tsk_vs_part_get") 34 | .allowlist_function("tsk_vs_part_read") 35 | 36 | .allowlist_function("tsk_fs_open_img") 37 | .allowlist_function("tsk_fs_close") 38 | 39 | .allowlist_function("tsk_fs_file_open") 40 | .allowlist_function("tsk_fs_file_open_meta") 41 | .allowlist_function("tsk_fs_file_close") 42 | .allowlist_function("tsk_fs_file_read") 43 | .allowlist_function("tsk_fs_file_read_type") 44 | .allowlist_function("tsk_fs_file_attr_getsize") 45 | .allowlist_function("tsk_fs_file_attr_get_idx") 46 | .allowlist_function("tsk_fs_file_attr_get") 47 | 48 | .allowlist_function("tsk_fs_attr_read") 49 | 50 | .allowlist_function("tsk_fs_dir_open_meta") 51 | .allowlist_function("tsk_fs_dir_open") 52 | .allowlist_function("tsk_fs_dir_close") 53 | .allowlist_function("tsk_fs_dir_get_name") 54 | 55 | .allowlist_type("TSK_FS_TYPE_ENUM") 56 | .allowlist_type("TSK_FS_META_FLAG_ENUM") 57 | .allowlist_type("TSK_FS_ATTR_TYPE_ENUM") 58 | .allowlist_type("TSK_FS_FILE_READ_FLAG_ENUM") 59 | .allowlist_type("TSK_FS_META_TYPE_ENUM") 60 | .rustified_enum("TSK_FS_ATTR_TYPE_ENUM") 61 | .rustified_enum("TSK_FS_META_FLAG_ENUM") 62 | .rustified_enum("TSK_FS_FILE_READ_FLAG_ENUM") 63 | .rustified_enum("TSK_FS_META_TYPE_ENUM") 64 | .generate() 65 | .expect("Unable to generate bindings"); 66 | 67 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 68 | bindings 69 | .write_to_file(out_path.join("bindings.rs")) 70 | .expect("Couldn't write bindings!"); 71 | } 72 | 73 | #[cfg(target_os = "windows")] 74 | fn windows_compile_tsk() { 75 | // First we need the x86 program path 76 | let program_path = PathBuf::from( 77 | std::env::var("ProgramFiles(x86)") 78 | .expect("NO ProgramFiles(x86) ENV.") 79 | ); 80 | // Now we need to compile the vswhere.exe path 81 | let vs_where_path = PathBuf::from(&program_path) 82 | .join(r"Microsoft Visual Studio\Installer\vswhere.exe"); 83 | // Run vswhere.exe to get install path 84 | let output = Command::new(&vs_where_path) 85 | .args(&["-latest", "-property", "installationPath"]) 86 | .output() 87 | .expect(&format!(r"Error executing vswhere: {}", vs_where_path.display())); 88 | 89 | // Get the installation location 90 | let install_path = PathBuf::from( 91 | String::from_utf8_lossy(&output.stdout).trim_end() 92 | ); 93 | 94 | // Append msbuild.exe to install path 95 | let msbuild_path = install_path.join(r"MSBuild\Current\Bin\MSBuild.exe"); 96 | eprintln!("msbuild_path -> {:?}\n", msbuild_path); 97 | 98 | // Fix libtsk.vcxproj (can be removed once https://github.com/sleuthkit/sleuthkit/pull/2205 is merged and released upstream) 99 | let libtsk_vcxproj_contents = fs::read_to_string(r"sleuthkit\win32\libtsk\libtsk.vcxproj").unwrap(); 100 | fs::write(r"sleuthkit\win32\libtsk\libtsk.vcxproj", 101 | libtsk_vcxproj_contents.replace(r#""#, 102 | r#""#)) 103 | .unwrap(); 104 | 105 | 106 | let output = Command::new(&msbuild_path) 107 | .args(&[ 108 | r"-target:libtsk", 109 | r"/p:PlatformToolset=v142", 110 | r"/p:Platform=x64", 111 | r"/p:Configuration=Release_NoLibs", 112 | r"/p:RestorePackages=false", 113 | r"sleuthkit\win32\tsk-win.sln" 114 | ]) 115 | .output() 116 | .expect("failed to build"); 117 | eprintln!("{}", String::from_utf8_lossy(&output.stdout).trim_end()); 118 | } 119 | 120 | 121 | #[cfg(target_os = "windows")] 122 | fn windows_setup() { 123 | windows_compile_tsk(); 124 | println!(r"cargo:rustc-link-search={}\sleuthkit\win32\x64\Release_NoLibs", env::var("CARGO_MANIFEST_DIR").unwrap()); 125 | 126 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 127 | let sdk_key = hklm.open_subkey(r"SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0") 128 | .expect("Microsoft SDK v10 is required."); 129 | eprintln!("sdk_key: {:?}", sdk_key); 130 | 131 | let installation_folder: String = sdk_key.get_value("InstallationFolder") 132 | .expect("Cant get InstallationFolder"); 133 | eprintln!("installation_folder: {}", installation_folder); 134 | 135 | let product_version: String = sdk_key.get_value("ProductVersion") 136 | .expect("Cant get ProductVersion"); 137 | eprintln!("product_version: {}", product_version); 138 | 139 | let sdk_path = format!(r"{}Lib\{}.0\um\x64", &installation_folder, &product_version); 140 | eprintln!("sdk_path: {}", &sdk_path); 141 | 142 | println!(r"cargo:rustc-link-search={}", sdk_path); 143 | println!(r"cargo:rustc-link-lib=Ole32"); 144 | println!(r"cargo:rustc-link-arg=/NODEFAULTLIB:libtsk"); 145 | } -------------------------------------------------------------------------------- /samples/mbr.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicmatt/libtsk-rs/17d00380a74679a8beade1075f42f1684f9ea634/samples/mbr.raw -------------------------------------------------------------------------------- /samples/ntfs.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicmatt/libtsk-rs/17d00380a74679a8beade1075f42f1684f9ea634/samples/ntfs.raw -------------------------------------------------------------------------------- /samples/test_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicmatt/libtsk-rs/17d00380a74679a8beade1075f42f1684f9ea634/samples/test_file -------------------------------------------------------------------------------- /src/bin/list_files.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use tsk::tsk_img::TskImg; 3 | 4 | static VERSION: &str = "0.1.0"; 5 | 6 | 7 | fn is_a_non_negative_number(value: String) -> Result<(), String> { 8 | match value.parse::() { 9 | Ok(_) => Ok(()), 10 | Err(_) => Err("Expected value to be a positive number.".to_owned()), 11 | } 12 | } 13 | 14 | 15 | /// Create and return an App that is used to parse the command line params 16 | /// that were specified by the user. 17 | /// 18 | fn get_argument_parser<'a, 'b>() -> App<'a, 'b> { 19 | let source_arg = Arg::with_name("source") 20 | .short("-s") 21 | .long("source") 22 | .required(true) 23 | .value_name("SOURCE") 24 | .takes_value(true) 25 | .help("The source"); 26 | 27 | let offset_arg = Arg::with_name("offset") 28 | .short("-o") 29 | .long("offset") 30 | .value_name("OFFSET") 31 | .takes_value(true) 32 | .default_value("0") 33 | .validator(is_a_non_negative_number) 34 | .help("The offset of the file system"); 35 | 36 | App::new("list_files") 37 | .version(VERSION) 38 | .author("Matthew Seyer ") 39 | .about("List files of a file system.") 40 | .arg(source_arg) 41 | .arg(offset_arg) 42 | } 43 | 44 | 45 | fn main() { 46 | let arg_parser = get_argument_parser(); 47 | let options = arg_parser.get_matches(); 48 | 49 | let source_location = options.value_of("source").expect("No source was provided!"); 50 | let offset = options 51 | .value_of("offset") 52 | .map(|value| value.parse::().expect("used validator")) 53 | .expect("no offset"); 54 | 55 | let tsk_img = TskImg::from_utf8_sing(source_location) 56 | .expect("Could not create TskImg"); 57 | 58 | let tsk_fs = tsk_img.get_fs_from_offset(offset) 59 | .expect("Could not open TskFs at offset 0"); 60 | 61 | let file_name_iter = tsk_fs.iter_file_names() 62 | .expect("Could not get Fs File iter"); 63 | 64 | let mut file_count = 0; 65 | for (path, fs_name) in file_name_iter { 66 | if let Some(name) = fs_name.name() { 67 | println!("{}/{}", path, name); 68 | } 69 | file_count += 1; 70 | } 71 | eprintln!("file_count: {}", file_count); 72 | } -------------------------------------------------------------------------------- /src/bin/read_usn.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Read, Write, Seek, SeekFrom}; 3 | use clap::{App, Arg}; 4 | use tsk::tsk_img::TskImg; 5 | 6 | static VERSION: &str = "0.1.0"; 7 | 8 | 9 | /// Create and return an App that is used to parse the command line params 10 | /// that were specified by the user. 11 | /// 12 | fn get_argument_parser<'a, 'b>() -> App<'a, 'b> { 13 | let source_arg = Arg::with_name("volume") 14 | .short("-v") 15 | .long("volume") 16 | .required(true) 17 | .value_name("VOLUME") 18 | .takes_value(true) 19 | .help("The volume."); 20 | 21 | let output_arg = Arg::with_name("output") 22 | .short("-o") 23 | .long("output") 24 | .required(true) 25 | .value_name("OUTPUT") 26 | .takes_value(true) 27 | .help("The output file."); 28 | 29 | App::new("read_usn") 30 | .version(VERSION) 31 | .author("Matthew Seyer ") 32 | .about("Read the USN Journal into a file.") 33 | .arg(source_arg) 34 | .arg(output_arg) 35 | } 36 | 37 | 38 | fn main() { 39 | let arg_parser = get_argument_parser(); 40 | let options = arg_parser.get_matches(); 41 | 42 | let output_location = options.value_of("output") 43 | .expect("No output was provided!"); 44 | 45 | let source_location = options.value_of("volume") 46 | .expect("No source was provided!"); 47 | 48 | let tsk_img = TskImg::from_utf8_sing(source_location) 49 | .expect("Could not create TskImg"); 50 | 51 | let tsk_fs = tsk_img.get_fs_from_offset(0) 52 | .expect("Could not open TskFs at offset 0"); 53 | 54 | let file = tsk_fs.file_open("/$Extend/$UsnJrnl") 55 | .expect("Error openting UsnJrnl."); 56 | 57 | for (i, mut attr) in file.get_attr_iter() 58 | .expect("Error getting attribute iterator.") 59 | .enumerate() 60 | { 61 | if let Some(name) = attr.name() { 62 | if name == "$J" { 63 | eprintln!("Attribute index {}: {:?}", i, attr); 64 | let mut start_offset = 0; 65 | if let Some(nrd) = attr.get_non_resident_data() { 66 | // iterate each non-resident data run 67 | for run in nrd.iter() { 68 | eprintln!("{:?}", run); 69 | if run.flags() & 2 > 0 { 70 | // This is a sparse run. We go to the end of it for our starting offst 71 | start_offset = run.len() * tsk_fs.block_size() as u64; 72 | } 73 | } 74 | 75 | eprintln!("USN Data starts at offset {}", start_offset); 76 | } 77 | 78 | let mut file = File::create(output_location) 79 | .expect("Cannot create output file"); 80 | 81 | // Seek data attribute to start of usn data 82 | attr.seek(SeekFrom::Start(start_offset)) 83 | .expect(&format!("Error seeking to offset {}", start_offset)); 84 | 85 | let mut offset = start_offset as usize; 86 | while offset < attr.size() as usize { 87 | let mut buffer = vec![0; 1024]; 88 | let bytes_read = match attr.read(&mut buffer){ 89 | Ok(br) => br, 90 | Err(e) => { 91 | panic!("Error reading from data attribute at offset {}. {:?}", offset, e); 92 | } 93 | }; 94 | file.write(&buffer) 95 | .expect("Error writing to output file."); 96 | 97 | offset += bytes_read; 98 | } 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/bin/volume_layout.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use tsk::tsk_img::TskImg; 3 | 4 | static VERSION: &str = "0.1.0"; 5 | 6 | 7 | /// Create and return an App that is used to parse the command line params 8 | /// that were specified by the user. 9 | /// 10 | fn get_argument_parser<'a, 'b>() -> App<'a, 'b> { 11 | let source_arg = Arg::with_name("source") 12 | .short("-s") 13 | .long("source") 14 | .required(true) 15 | .value_name("SOURCE") 16 | .takes_value(true) 17 | .help("The source"); 18 | 19 | App::new("volume_layout") 20 | .version(VERSION) 21 | .author("Matthew Seyer ") 22 | .about("Print the layout of a given source.") 23 | .arg(source_arg) 24 | } 25 | 26 | 27 | fn main() { 28 | let arg_parser = get_argument_parser(); 29 | let options = arg_parser.get_matches(); 30 | 31 | let source_location = options.value_of("source").expect("No source was provided!"); 32 | let tsk_img = TskImg::from_utf8_sing(source_location) 33 | .expect("Could not create TskImg"); 34 | 35 | let tsk_vs = tsk_img.get_vs_from_offset(0) 36 | .expect("Could not open TskVs at offset 0"); 37 | let part_iter = tsk_vs.get_partition_iter() 38 | .expect("Could not get partition iterator for TskVs"); 39 | 40 | for vs_part in part_iter { 41 | println!("{:?}", vs_part); 42 | } 43 | } -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum ErrorType { 3 | TskFsMeta, 4 | TskFsFile, 5 | LibTskError, 6 | TskFsAttr, 7 | TskFsName, 8 | TskFsDir, 9 | Generic 10 | } 11 | #[derive(Debug)] 12 | pub struct TskError { 13 | pub message: String, 14 | pub kind: ErrorType, 15 | } 16 | impl TskError { 17 | /// Error function for TskFsDir operations 18 | pub fn tsk_fs_dir_error(message: String) -> Self { 19 | Self { 20 | message: message, 21 | kind: ErrorType::TskFsDir, 22 | } 23 | } 24 | 25 | /// Error function for TskFsName operations 26 | pub fn tsk_fs_name_error(message: String) -> Self { 27 | Self { 28 | message: message, 29 | kind: ErrorType::TskFsName, 30 | } 31 | } 32 | 33 | /// Error function for TskFsAttr operations 34 | pub fn tsk_attr_error(message: String) -> Self { 35 | Self { 36 | message: message, 37 | kind: ErrorType::TskFsAttr, 38 | } 39 | } 40 | 41 | /// A Generic error 42 | pub fn generic(message: String) -> Self { 43 | Self { 44 | message: message, 45 | kind: ErrorType::Generic, 46 | } 47 | } 48 | 49 | /// Error originating form a lib tsk call 50 | pub fn lib_tsk_error(message: String) -> Self { 51 | Self { 52 | message: message, 53 | kind: ErrorType::LibTskError, 54 | } 55 | } 56 | 57 | /// Error function for TskFsMeta operations 58 | pub fn tsk_fs_meta_error(message: String) -> Self { 59 | Self { 60 | message: message, 61 | kind: ErrorType::TskFsMeta, 62 | } 63 | } 64 | 65 | /// Error function for TskFsFile operations 66 | pub fn tsk_fs_file_error(message: String) -> Self { 67 | Self { 68 | message: message, 69 | kind: ErrorType::TskFsFile, 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(new_uninit)] 2 | #[macro_use] extern crate log; 3 | 4 | 5 | /// The bindings module are the automated bindgen created bindings 6 | pub mod bindings; 7 | /// Error handling for this crate 8 | pub mod errors; 9 | /// Wrapper for TSK_IMG_INFO 10 | pub mod tsk_img; 11 | /// Wrapper for TSK_VS_INFO 12 | pub mod tsk_vs; 13 | /// Wrapper for TSK_VS_PART_INFO 14 | pub mod tsk_vs_part; 15 | /// Implement handle for TskVsPart 16 | pub mod tsk_vs_part_handle; 17 | /// Wrapper for TSK_FS_INFO 18 | pub mod tsk_fs; 19 | /// Wrapper for TSK_FS_FILE 20 | pub mod tsk_fs_file; 21 | /// Wrapper for TSK_FS_DIR 22 | pub mod tsk_fs_dir; 23 | /// Wrapper for TSK_FS_NAME 24 | pub mod tsk_fs_name; 25 | /// Wrapper for TSK_FS_META 26 | pub mod tsk_fs_meta; 27 | /// FileHandle for a TskFsFile. 28 | pub mod tsk_fs_file_handle; 29 | /// Wrapper for Tsk_FS_ATTR. 30 | pub mod tsk_fs_attr; 31 | /// Custom ReadSeek 32 | pub mod tsk_img_reader; 33 | 34 | pub use tsk_img::TskImg; 35 | pub use tsk_img_reader::{ReadSeek, TskImgReadSeek}; -------------------------------------------------------------------------------- /src/tsk_fs.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::ptr::NonNull; 3 | use crate::{ 4 | errors::TskError, 5 | tsk_img::TskImg, 6 | tsk_fs_file::TskFsFile, 7 | tsk_fs_name::TskFsName, 8 | tsk_fs_dir::{TskFsDir, IntoDirNameIter}, 9 | bindings as tsk 10 | }; 11 | 12 | 13 | /// Wrapper for TSK_FS_INFO 14 | pub struct TskFs { 15 | /// The ptr to the TSK_FS_INFO struct 16 | tsk_fs_ptr: *mut tsk::TSK_FS_INFO, 17 | /// We dont always want to free a file pointer 18 | _release: bool, 19 | } 20 | impl TskFs { 21 | /// Create a TSK_FS_INFO wrapper given the TskImg and offset of the file system 22 | pub fn from_fs_offset(tsk_img: &TskImg, offset: u64) -> Result { 23 | // Get a pointer to the TSK_FS_INFO sturct 24 | let tsk_fs_ptr = unsafe {tsk::tsk_fs_open_img( 25 | tsk_img.handle.as_ptr(), 26 | offset as i64 as _, 27 | 0 28 | )}; 29 | 30 | if tsk_fs_ptr.is_null() { 31 | // Get a ptr to the error msg 32 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 33 | .ok_or( 34 | TskError::lib_tsk_error( 35 | format!("There was an error opening the fs handle at offset {}. (no context)", offset) 36 | ) 37 | )?; 38 | // Get the error message from the string 39 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 40 | // Return an error which includes the TSK error message 41 | return Err(TskError::lib_tsk_error( 42 | format!("There was an error opening the fs handle at offset {}: {}", offset, error_msg) 43 | )); 44 | } 45 | 46 | Ok( Self { tsk_fs_ptr, _release: true } ) 47 | } 48 | 49 | /// Open a file by a given path. (use '/' as separators) 50 | pub fn file_open(&self, path: &str) -> Result { 51 | TskFsFile::from_path(&self, path) 52 | } 53 | 54 | /// Open a file by a given inode. 55 | pub fn file_open_meta(&self, inode: u64) -> Result { 56 | TskFsFile::from_meta(&self, inode) 57 | } 58 | 59 | /// Open a directory by a given path 60 | pub fn dir_open(&self, path: &str) -> Result { 61 | TskFsDir::from_path(self, path) 62 | } 63 | 64 | /// Open a file name iterator based on root inode 65 | pub fn iter_file_names<'fs>(&'fs self) -> Result, TskError> { 66 | let root_inode = unsafe {(*self.tsk_fs_ptr).root_inum}; 67 | let root_dir = TskFsDir::from_meta(self, root_inode)?; 68 | 69 | Ok( FsNameIter { 70 | tsk_fs: self, 71 | dir_iter_stack: vec![root_dir.into_name_iter()], 72 | path_stack: Vec::new() 73 | } ) 74 | } 75 | 76 | /// Open a file name iterator based on path 77 | pub fn iter_file_names_from_inode<'fs>(&'fs self, inode: u64) -> Result, TskError> { 78 | FsNameIter::from_inode(self, inode) 79 | } 80 | 81 | /// Open a file name iterator based on path 82 | pub fn iter_file_names_from_path<'fs>(&'fs self, path: &str) -> Result, TskError> { 83 | FsNameIter::from_path(self, path) 84 | } 85 | 86 | /// Number of blocks in fs. 87 | pub fn block_count(&self) -> u64 { 88 | unsafe { (*self.tsk_fs_ptr).block_count } 89 | } 90 | 91 | /// Number of bytes that precede each block (currently only used for RAW CDs) 92 | pub fn block_pre_size(&self) -> u32 { 93 | unsafe { (*self.tsk_fs_ptr).block_pre_size } 94 | } 95 | 96 | /// Number of bytes that follow each block (currently only used for RAW CDs) 97 | pub fn block_post_size(&self) -> u32 { 98 | unsafe { (*self.tsk_fs_ptr).block_post_size } 99 | } 100 | 101 | /// Size of each block (in bytes) 102 | pub fn block_size(&self) -> u32 { 103 | unsafe { (*self.tsk_fs_ptr).block_size } 104 | } 105 | 106 | /// Size of device block (typically always 512) 107 | pub fn dev_bsize(&self) -> u32 { 108 | unsafe { (*self.tsk_fs_ptr).dev_bsize } 109 | } 110 | 111 | /// Address of first block. 112 | pub fn first_block(&self) -> u64 { 113 | unsafe { (*self.tsk_fs_ptr).first_block } 114 | } 115 | 116 | /// First valid metadata address. 117 | pub fn first_inum(&self) -> u64 { 118 | unsafe { (*self.tsk_fs_ptr).first_inum } 119 | } 120 | 121 | /// Number of metadata addresses. 122 | pub fn inum_count(&self) -> u64 { 123 | unsafe { (*self.tsk_fs_ptr).inum_count } 124 | } 125 | 126 | /// Address of journal inode. 127 | pub fn journ_inum(&self) -> u64 { 128 | unsafe { (*self.tsk_fs_ptr).journ_inum } 129 | } 130 | 131 | /// Address of last block as reported by file system (could be larger than 132 | /// last_block in image if end of image does not exist) 133 | pub fn last_block(&self) -> u64 { 134 | unsafe { (*self.tsk_fs_ptr).last_block } 135 | } 136 | 137 | /// Address of last block – adjusted so that it is equal to the last block 138 | /// in the image or volume (if image is not complete) 139 | pub fn last_block_act(&self) -> u64 { 140 | unsafe { (*self.tsk_fs_ptr).last_block_act } 141 | } 142 | 143 | /// Last valid metadata address. 144 | pub fn last_inum(&self) -> u64 { 145 | unsafe { (*self.tsk_fs_ptr).last_inum } 146 | } 147 | } 148 | impl Into<*mut tsk::TSK_FS_INFO> for &TskFs { 149 | fn into(self) -> *mut tsk::TSK_FS_INFO { 150 | self.tsk_fs_ptr 151 | } 152 | } 153 | impl Into<*mut tsk::TSK_FS_INFO> for TskFs { 154 | fn into(self) -> *mut tsk::TSK_FS_INFO { 155 | self.tsk_fs_ptr 156 | } 157 | } 158 | impl<'fs> Into<&'fs *mut tsk::TSK_FS_INFO> for &'fs TskFs { 159 | fn into(self) -> &'fs *mut tsk::TSK_FS_INFO { 160 | &self.tsk_fs_ptr 161 | } 162 | } 163 | impl Drop for TskFs { 164 | fn drop(&mut self) { 165 | if self._release { 166 | unsafe { tsk::tsk_fs_close(self.tsk_fs_ptr) }; 167 | } 168 | } 169 | } 170 | impl std::fmt::Debug for TskFs { 171 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 172 | f.debug_struct("TskFs") 173 | .field("block_count", &self.block_count()) 174 | .field("block_pre_size", &self.block_pre_size()) 175 | .field("block_post_size", &self.block_post_size()) 176 | .field("dev_bsize", &self.dev_bsize()) 177 | .field("first_block", &self.first_block()) 178 | .field("first_inum", &self.first_inum()) 179 | .field("inum_count", &self.inum_count()) 180 | .field("journ_inum", &self.journ_inum()) 181 | .field("last_block", &self.last_block()) 182 | .field("last_block_act", &self.last_block_act()) 183 | .field("last_inum", &self.last_inum()) 184 | .finish() 185 | } 186 | } 187 | 188 | 189 | #[derive(Debug)] 190 | pub struct FsNameIter<'fs> { 191 | tsk_fs: &'fs TskFs, 192 | dir_iter_stack: Vec>, 193 | path_stack: Vec 194 | } 195 | impl<'fs> FsNameIter<'fs> { 196 | pub fn from_path( 197 | tsk_fs: &'fs TskFs, 198 | path: &str 199 | ) -> Result, TskError> { 200 | let dir = TskFsDir::from_path(tsk_fs, path)?; 201 | 202 | Ok( FsNameIter { 203 | tsk_fs, 204 | dir_iter_stack: vec![dir.into_name_iter()], 205 | path_stack: Vec::new() 206 | } ) 207 | } 208 | 209 | pub fn from_inode( 210 | tsk_fs: &'fs TskFs, 211 | inode: u64 212 | ) -> Result, TskError> { 213 | let dir = TskFsDir::from_meta(tsk_fs, inode)?; 214 | 215 | Ok( FsNameIter { 216 | tsk_fs, 217 | dir_iter_stack: vec![dir.into_name_iter()], 218 | path_stack: Vec::new() 219 | } ) 220 | } 221 | } 222 | impl<'fs> Iterator for FsNameIter<'fs> { 223 | type Item = (String, TskFsName); 224 | 225 | fn next(&mut self) -> Option<(String, TskFsName)> { 226 | let fs = self.tsk_fs; 227 | 228 | loop { 229 | if let Some(current_dir_itr) = self.dir_iter_stack.last_mut() { 230 | if let Some(tsk_fn) = current_dir_itr.next() { 231 | if tsk_fn.is_dir() { 232 | let file_name = match tsk_fn.name() { 233 | Some(n) => n, 234 | None => break 235 | }; 236 | 237 | if &file_name == "." || &file_name == ".." { 238 | continue; 239 | } 240 | 241 | let tsk_fs_dir = match TskFsDir::from_meta(fs, tsk_fn.get_inode()) { 242 | Ok(d) => d, 243 | Err(_e) => continue 244 | }; 245 | 246 | let new_dir_iter = tsk_fs_dir.into_name_iter(); 247 | self.dir_iter_stack.push(new_dir_iter); 248 | 249 | let path = self.path_stack.join("/"); 250 | self.path_stack.push(file_name); 251 | return Some((path, tsk_fn)) 252 | } else { 253 | let path = self.path_stack.join("/"); 254 | return Some((path, tsk_fn)) 255 | } 256 | } else { 257 | self.dir_iter_stack.pop(); 258 | self.path_stack.pop(); 259 | } 260 | } else { 261 | self.dir_iter_stack.pop(); 262 | 263 | // No more dir iterators in stack 264 | if self.dir_iter_stack.is_empty(){ 265 | break; 266 | } 267 | } 268 | } 269 | 270 | None 271 | } 272 | } -------------------------------------------------------------------------------- /src/tsk_fs_attr.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | use std::convert::{TryInto}; 3 | use std::ptr::NonNull; 4 | use std::ffi::CStr; 5 | use crate::{ 6 | errors::TskError, 7 | tsk_fs_file::TskFsFile, 8 | bindings as tsk 9 | }; 10 | 11 | 12 | /// Wrapper for TSK_FS_ATTR_RUN pointer. 13 | /// 14 | pub struct TskFsAttrRun<'dr, 'a, 'f, 'fs> { 15 | _nrd: &'dr NonResidentData<'a, 'f, 'fs>, 16 | tsk_fs_attr_run: *const tsk::TSK_FS_ATTR_RUN 17 | } 18 | impl<'dr, 'a, 'f, 'fs> TskFsAttrRun<'dr, 'a, 'f, 'fs> { 19 | /// Create a TskFsAttrRun from a TSK_FS_ATTR_RUN pointer and NonResidentData. The 20 | /// NonResidentData must last the life time of this TskFsAttrRun. 21 | pub fn from_nrd( 22 | _nrd: &'dr NonResidentData<'a, 'f, 'fs>, 23 | tsk_fs_attr_run: *const tsk::TSK_FS_ATTR_RUN 24 | ) -> Self { 25 | Self { 26 | _nrd, 27 | tsk_fs_attr_run 28 | } 29 | } 30 | 31 | /// Get the starting block address (in file system) of run 32 | /// 33 | pub fn addr(&self) -> u64 { 34 | unsafe { (*self.tsk_fs_attr_run).addr } 35 | } 36 | 37 | /// Number of blocks in run (0 when entry is not in use) 38 | /// 39 | pub fn len(&self) -> u64 { 40 | unsafe { (*self.tsk_fs_attr_run).len } 41 | } 42 | 43 | /// Flags for run. 44 | /// 45 | pub fn flags(&self) -> i32 { 46 | unsafe { (*self.tsk_fs_attr_run).flags } 47 | } 48 | 49 | /// Flags for run. 50 | /// 51 | pub fn flags_str(&self) -> String { 52 | let mut string_vec = Vec::with_capacity(4); 53 | let flags = self.flags(); 54 | 55 | if flags & tsk::TSK_FS_ATTR_RUN_FLAG_ENUM_TSK_FS_ATTR_RUN_FLAG_FILLER > 0 { 56 | string_vec.push("FILLER"); 57 | } 58 | if flags & tsk::TSK_FS_ATTR_RUN_FLAG_ENUM_TSK_FS_ATTR_RUN_FLAG_SPARSE > 0 { 59 | string_vec.push("SPARSE"); 60 | } 61 | if flags & tsk::TSK_FS_ATTR_RUN_FLAG_ENUM_TSK_FS_ATTR_RUN_FLAG_ENCRYPTED > 0 { 62 | string_vec.push("ENCRYPTED"); 63 | } 64 | 65 | string_vec.join(" | ") 66 | } 67 | } 68 | impl <'dr, 'a, 'f, 'fs> std::fmt::Debug for TskFsAttrRun<'dr, 'a, 'f, 'fs> { 69 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 | let flags = format!("[0x{:04x}] {}", self.flags(), self.flags_str()); 71 | f.debug_struct("TskFsAttrRun") 72 | .field("addr", &self.addr()) 73 | .field("flags", &flags) 74 | .field("len", &self.len()) 75 | .finish() 76 | } 77 | } 78 | 79 | 80 | /// An iterator over a TSK_FS_ATTR_RUN pointer which uses the 81 | /// structs next attribute to iterate. 82 | pub struct TskFsAttrRunIterator<'dr, 'a, 'f, 'fs>(TskFsAttrRun<'dr, 'a, 'f, 'fs>); 83 | impl<'dr, 'a, 'f, 'fs> Iterator for TskFsAttrRunIterator<'dr, 'a, 'f, 'fs> { 84 | type Item = TskFsAttrRun<'dr, 'a, 'f, 'fs>; 85 | 86 | fn next(&mut self) -> Option> { 87 | if self.0.tsk_fs_attr_run.is_null() { 88 | return None; 89 | } 90 | 91 | let next = unsafe { 92 | TskFsAttrRun { 93 | _nrd: self.0._nrd, 94 | tsk_fs_attr_run: (*self.0.tsk_fs_attr_run).next as *const tsk::TSK_FS_ATTR_RUN 95 | } 96 | }; 97 | 98 | let current = TskFsAttrRun { 99 | _nrd: self.0._nrd, 100 | tsk_fs_attr_run: self.0.tsk_fs_attr_run, 101 | }; 102 | 103 | self.0 = next; 104 | 105 | Some(current) 106 | } 107 | } 108 | 109 | 110 | /// Wrapper for Non Resident Data 111 | /// 112 | pub struct NonResidentData<'a, 'f, 'fs> { 113 | _tsk_fs_attr: &'a TskFsAttr<'f, 'fs>, 114 | nrd: tsk::TSK_FS_ATTR__bindgen_ty_1 115 | } 116 | impl <'a, 'f, 'fs> NonResidentData<'a, 'f, 'fs> { 117 | /// Create a NonResidentData from a TskFsAttr's lifetime and nrd struct (TSK_FS_ATTR__bindgen_ty_1) 118 | /// Thus, NonResidentData must live for the life time of the attribute it represents. 119 | /// 120 | pub fn new( 121 | _tsk_fs_attr: &'a TskFsAttr<'f, 'fs>, 122 | nrd: tsk::TSK_FS_ATTR__bindgen_ty_1 123 | ) -> Self { 124 | Self { 125 | _tsk_fs_attr, 126 | nrd 127 | } 128 | } 129 | 130 | /// Number of initial bytes in run to skip before content begins. The size field does not include this length. 131 | /// 132 | pub fn skiplen(&self) -> u32 { 133 | self.nrd.skiplen 134 | } 135 | 136 | /// Number of bytes that are allocated in all clusters of non-resident run 137 | /// (will be larger than size - does not include skiplen). This is defined when 138 | /// the attribute is created and used to determine slack space. 139 | /// 140 | pub fn allocsize(&self) -> i64 { 141 | self.nrd.allocsize 142 | } 143 | 144 | /// Number of bytes (starting from offset 0) that have data (including FILLER) 145 | /// saved for them (smaller then or equal to size). This is defined when the attribute is created. 146 | /// 147 | pub fn initsize(&self) -> i64 { 148 | self.nrd.initsize 149 | } 150 | 151 | /// Size of compression units (needed only if NTFS file is compressed) 152 | /// 153 | pub fn compsize(&self) -> u32 { 154 | self.nrd.compsize 155 | } 156 | 157 | /// Get the first data run for this non resident data 158 | /// 159 | pub fn run<'dr>(&'dr self) -> TskFsAttrRun<'dr, 'a, 'f, 'fs> { 160 | TskFsAttrRun::from_nrd( 161 | &self, 162 | self.nrd.run 163 | ) 164 | } 165 | 166 | /// Get a TskFsAttrRunIterator based off this NonResidentData struct 167 | /// 168 | pub fn iter<'dr>(&'dr self) -> TskFsAttrRunIterator<'dr, 'a, 'f, 'fs> { 169 | TskFsAttrRunIterator(self.run()) 170 | } 171 | } 172 | impl <'a, 'f, 'fs> std::fmt::Debug for NonResidentData<'a, 'f, 'fs> { 173 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 174 | f.debug_struct("NonResidentData") 175 | .field("skiplen", &self.nrd.skiplen) 176 | .field("allocsize", &self.nrd.allocsize) 177 | .field("initsize", &self.nrd.initsize) 178 | .field("compsize", &self.nrd.compsize) 179 | .finish() 180 | } 181 | } 182 | 183 | 184 | 185 | /// Wrapper for TSK_FS_ATTR. This maintains a lifetime reference of TskFsFile so 186 | /// that we are guaranteed that the pointers are always valid. Otherwise, we 187 | /// have no safety guarantee that the pointers are still available. 188 | /// 189 | /// `fs => Filesystem lifetime 190 | /// 'f => File lifetime 191 | pub struct TskFsAttr<'f, 'fs>{ 192 | tsk_fs_file: &'f TskFsFile<'fs>, 193 | pub tsk_fs_attr: *const tsk::TSK_FS_ATTR, 194 | _offset: i64 195 | } 196 | impl<'f, 'fs> TskFsAttr<'f, 'fs> { 197 | /// Create a TSK_FS_ATTR wrapper given the TskFsFile and index of the attribute 198 | pub fn from_index( 199 | tsk_fs_file: &'f TskFsFile<'fs>, 200 | tsk_fs_file_attr_get_idx: u16 201 | ) -> Result { 202 | // Get a pointer to the TSK_FS_ATTR sturct 203 | let tsk_fs_attr = unsafe {tsk::tsk_fs_file_attr_get_idx( 204 | tsk_fs_file.into(), 205 | tsk_fs_file_attr_get_idx as _ 206 | )}; 207 | 208 | // Check for error 209 | if tsk_fs_attr.is_null() { 210 | // Get a ptr to the error msg 211 | let error_msg_ptr = unsafe { 212 | NonNull::new(tsk::tsk_error_get() as _) 213 | }.ok_or(TskError::tsk_attr_error(format!( 214 | "There was an error getting the TskFsAttr at index {}", 215 | tsk_fs_file_attr_get_idx 216 | )))?; 217 | 218 | // Get the error message from the string 219 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 220 | return Err( 221 | TskError::tsk_attr_error( 222 | format!( 223 | "There was an error getting the TskFsAttr at index {}: {}", 224 | tsk_fs_file_attr_get_idx, error_msg 225 | ) 226 | ) 227 | ); 228 | } 229 | 230 | Ok( 231 | Self { 232 | tsk_fs_file, 233 | tsk_fs_attr, 234 | _offset: 0 235 | } 236 | ) 237 | } 238 | 239 | /// Create a TSK_FS_ATTR wrapper given the TskFsFile and index of the attribute 240 | pub fn from_default( 241 | tsk_fs_file: &'f TskFsFile<'fs> 242 | ) -> Result { 243 | // Get a pointer to the TSK_FS_ATTR sturct 244 | let tsk_fs_attr = unsafe {tsk::tsk_fs_file_attr_get( 245 | tsk_fs_file.into() 246 | )}; 247 | 248 | // Check for error 249 | if tsk_fs_attr.is_null() { 250 | // Get a ptr to the error msg 251 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 252 | .ok_or(TskError::tsk_attr_error( 253 | format!( 254 | "There was an error getting the default TskFsAttr" 255 | ) 256 | ))?; 257 | 258 | // Get the error message from the string 259 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 260 | return Err( 261 | TskError::tsk_attr_error( 262 | format!( 263 | "There was an error getting the default TskFsAttr: {}", 264 | error_msg 265 | ) 266 | ) 267 | ); 268 | } 269 | 270 | Ok( 271 | Self { 272 | tsk_fs_file, 273 | tsk_fs_attr, 274 | _offset: 0 275 | } 276 | ) 277 | } 278 | 279 | /// Get the name of the attribute if available 280 | pub fn name(&self) -> Option { 281 | // First check if the name is null 282 | if unsafe { (*self.tsk_fs_attr).name }.is_null() { 283 | return None; 284 | } 285 | 286 | let name = unsafe { CStr::from_ptr((*self.tsk_fs_attr).name) }.to_string_lossy(); 287 | Some(name.to_string().clone()) 288 | } 289 | 290 | /// Get the size of this attribute 291 | pub fn size(&self) -> i64 { 292 | unsafe { (*self.tsk_fs_attr).size } 293 | } 294 | 295 | /// Get the type of the attribute 296 | pub fn attr_type(&self) -> tsk::TSK_FS_ATTR_TYPE_ENUM { 297 | unsafe { (*self.tsk_fs_attr).type_ } 298 | } 299 | 300 | /// Get the flags of the attribute 301 | pub fn attr_flags(&self) -> tsk::TSK_FS_ATTR_FLAG_ENUM { 302 | unsafe { (*self.tsk_fs_attr).flags } 303 | } 304 | 305 | /// Get an iterator based off this TskFsAttr struct 306 | pub fn into_iter(self) -> TskFsAttrIterator<'fs, 'f> { 307 | TskFsAttrIterator(self) 308 | } 309 | 310 | /// Get the id of this attribute 311 | pub fn id(&self) -> u16 { 312 | unsafe { (*self.tsk_fs_attr).id as u16 } 313 | } 314 | 315 | /// Get the non-resident data or None if attribute is resident 316 | pub fn get_non_resident_data<'a>(&'a self) -> Option> { 317 | if (self.attr_flags() & tsk::TSK_FS_ATTR_FLAG_ENUM_TSK_FS_ATTR_NONRES) > 0 { 318 | unsafe { Some( 319 | NonResidentData::new( 320 | &self, 321 | (*self.tsk_fs_attr).nrd 322 | ) 323 | )} 324 | } else { 325 | None 326 | } 327 | } 328 | } 329 | impl<'fs, 'f> std::fmt::Debug for TskFsAttr<'fs, 'f> { 330 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 331 | f.debug_struct("TskFsAttr") 332 | .field("id", &self.id()) 333 | .field("name", &self.name()) 334 | .field("type", &self.attr_type()) 335 | .field("size", &self.size()) 336 | .finish() 337 | } 338 | } 339 | impl<'fs, 'f> Read for TskFsAttr<'fs, 'f> { 340 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 341 | let attr_size: usize = self.size() 342 | .try_into() 343 | .unwrap(); 344 | 345 | let read_size: usize = if buf.len() > attr_size { 346 | attr_size 347 | } else { 348 | buf.len() 349 | }; 350 | 351 | // Get a pointer to the TSK_FS_FILE sturct 352 | let bytes_read = unsafe {tsk::tsk_fs_attr_read( 353 | self.tsk_fs_attr, 354 | self._offset, 355 | buf.as_mut_ptr() as _, 356 | read_size, 357 | tsk::TSK_FS_FILE_READ_FLAG_ENUM::TSK_FS_FILE_READ_FLAG_NONE 358 | )}; 359 | 360 | if bytes_read == -1 { 361 | // Get a ptr to the error msg 362 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 363 | .ok_or(std::io::Error::new( 364 | std::io::ErrorKind::Other, 365 | format!( 366 | "tsk_fs_attr_read error with no context. offset: {}; buffer_len: {}; read_size: {}", 367 | self._offset, 368 | buf.len(), 369 | read_size 370 | )))?; 371 | 372 | // Get the error message from the string 373 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 374 | // Return an error which includes the TSK error message 375 | return Err( 376 | std::io::Error::new( 377 | std::io::ErrorKind::Other, 378 | format!("{}", error_msg) 379 | ) 380 | ); 381 | } 382 | // update offset by the number of bytes read 383 | self._offset += TryInto::::try_into(bytes_read) 384 | .unwrap(); 385 | 386 | Ok(bytes_read as usize) 387 | } 388 | } 389 | impl<'fs, 'f> Seek for TskFsAttr<'fs, 'f> { 390 | fn seek(&mut self, pos: SeekFrom) -> std::io::Result { 391 | let attr_size = self.size(); 392 | 393 | match pos { 394 | SeekFrom::Start(o) => { 395 | if o > attr_size as u64 { 396 | return Err( 397 | std::io::Error::new( 398 | std::io::ErrorKind::Other, 399 | format!( 400 | "Offset Start({}) is greater than attribute size {}", 401 | o, 402 | attr_size 403 | ) 404 | ) 405 | ); 406 | } else { 407 | self._offset = match o.try_into() { 408 | Ok(o) => o, 409 | Err(e) => { 410 | return Err( 411 | std::io::Error::new( 412 | std::io::ErrorKind::Other, 413 | format!("Error casting offset to i64: {}", e) 414 | ) 415 | ); 416 | } 417 | } 418 | } 419 | }, 420 | SeekFrom::Current(o) => { 421 | let location = o + self._offset; 422 | if location < 0 { 423 | return Err( 424 | std::io::Error::new( 425 | std::io::ErrorKind::Other, 426 | format!("Cannot seek Current({}) from offset {}", o, self._offset) 427 | ) 428 | ); 429 | } else { 430 | if location > attr_size { 431 | return Err( 432 | std::io::Error::new( 433 | std::io::ErrorKind::Other, 434 | format!( 435 | "Offset Current({}) from {} is greater than attribute size {}", 436 | o, self._offset, attr_size 437 | ) 438 | ) 439 | ); 440 | } else { 441 | self._offset = location; 442 | } 443 | } 444 | }, 445 | SeekFrom::End(o) => { 446 | let location = o + attr_size; 447 | if location < 0 { 448 | return Err( 449 | std::io::Error::new( 450 | std::io::ErrorKind::Other, 451 | format!("Cannot seek End({}) from offset {}", o, self._offset) 452 | ) 453 | ); 454 | } else { 455 | if location > attr_size { 456 | return Err( 457 | std::io::Error::new( 458 | std::io::ErrorKind::Other, 459 | format!( 460 | "Offset Current({}) from {} is greater than attribute size {}", 461 | o, self._offset, attr_size 462 | ) 463 | ) 464 | ); 465 | } else { 466 | self._offset = location; 467 | } 468 | } 469 | } 470 | } 471 | 472 | Ok(self._offset as u64) 473 | } 474 | } 475 | 476 | 477 | /// An iterator over a TSK_FS_ATTR pointer which uses the 478 | /// structs next attribute to iterate. 479 | pub struct TskFsAttrIterator<'fs, 'f>(TskFsAttr<'fs, 'f>); 480 | impl<'fs, 'f> Iterator for TskFsAttrIterator<'fs, 'f> { 481 | type Item = TskFsAttr<'fs, 'f>; 482 | 483 | fn next(&mut self) -> Option> { 484 | if self.0.tsk_fs_attr.is_null() { 485 | return None; 486 | } 487 | 488 | let next = unsafe { 489 | TskFsAttr { 490 | tsk_fs_file: self.0.tsk_fs_file, 491 | tsk_fs_attr: (*self.0.tsk_fs_attr).next as *const tsk::TSK_FS_ATTR, 492 | _offset: 0 493 | } 494 | }; 495 | 496 | let current = TskFsAttr { 497 | tsk_fs_file: self.0.tsk_fs_file, 498 | tsk_fs_attr: self.0.tsk_fs_attr, 499 | _offset: 0 500 | }; 501 | 502 | self.0 = next; 503 | 504 | Some(current) 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/tsk_fs_dir.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::ptr::NonNull; 3 | use crate::{ 4 | errors::TskError, 5 | tsk_fs::TskFs, 6 | tsk_fs_name::TskFsName, 7 | bindings as tsk 8 | }; 9 | 10 | 11 | /// Wrapper for TSK_FS_DIR that implements helper functions. 12 | #[derive(Clone)] 13 | pub struct TskFsDir<'fs> { 14 | /// A TskFsDir can never outlive its TskFs 15 | tsk_fs_info: &'fs TskFs, 16 | /// The ptr to the TSK_FS_DIR struct 17 | tsk_fs_dir_ptr: *mut tsk::TSK_FS_DIR, 18 | _release: bool 19 | } 20 | impl<'fs> TskFsDir<'fs> { 21 | /// Create a TSK_FS_DIR wrapper given TskFs and inode 22 | pub fn from_meta(tsk_fs: &'fs TskFs, inode: u64) -> Result { 23 | // Get a pointer to the TSK_FS_DIR sturct 24 | let tsk_fs_dir_ptr = unsafe {tsk::tsk_fs_dir_open_meta( 25 | tsk_fs.into(), 26 | inode as _ 27 | )}; 28 | 29 | // Ensure that the ptr is not null 30 | if tsk_fs_dir_ptr.is_null() { 31 | // Get a ptr to the error msg 32 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 33 | .ok_or(TskError::lib_tsk_error( 34 | format!("There was an error opening {} as a dir. No context.", inode) 35 | ))?; 36 | 37 | // Get the error message from the string 38 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 39 | // Return an error which includes the TSK error message 40 | return Err(TskError::lib_tsk_error( 41 | format!("There was an error opening {} as a dir: {}", inode, error_msg) 42 | )); 43 | } 44 | 45 | Ok( Self { 46 | tsk_fs_info: tsk_fs, 47 | tsk_fs_dir_ptr, 48 | _release: true 49 | } ) 50 | } 51 | 52 | /// Create a TSK_FS_DIR wrapper given TskFs and path 53 | pub fn from_path(tsk_fs: &'fs TskFs, path: &str) -> Result { 54 | // Create a CString for the provided source 55 | let path_c = CString::new(path) 56 | .map_err(|e| TskError::generic(format!("Unable to create CString from path {}: {:?}", path, e)))?; 57 | 58 | // Get a pointer to the TSK_FS_DIR sturct 59 | let tsk_fs_dir_ptr = unsafe {tsk::tsk_fs_dir_open( 60 | tsk_fs.into(), 61 | path_c.as_ptr() as _ 62 | )}; 63 | 64 | // Ensure that the ptr is not null 65 | if tsk_fs_dir_ptr.is_null() { 66 | // Get a ptr to the error msg 67 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 68 | .ok_or(TskError::lib_tsk_error( 69 | format!("There was an error opening {} as a dir. No context.", path) 70 | ))?; 71 | 72 | // Get the error message from the string 73 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 74 | // Return an error which includes the TSK error message 75 | return Err(TskError::lib_tsk_error( 76 | format!("There was an error opening {} as a dir: {}", path, error_msg) 77 | )); 78 | } 79 | 80 | Ok( Self { 81 | tsk_fs_info: tsk_fs, 82 | tsk_fs_dir_ptr, 83 | _release: true 84 | } ) 85 | } 86 | 87 | /// Get a TskFsName at a given index of the TSK_FS_DIR 88 | pub fn get_name(&self, index: u64) -> Result { 89 | // Get a pointer to the TSK_FS_FILE sturct 90 | let tsk_fs_name = unsafe {tsk::tsk_fs_dir_get_name( 91 | self.tsk_fs_dir_ptr, 92 | index as _ 93 | )}; 94 | 95 | if tsk_fs_name.is_null() { 96 | // Get a ptr to the error msg 97 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 98 | .ok_or(TskError::tsk_fs_name_error( 99 | format!("Error getting TskFsName at index {} from TskFsDir {:?}. No context.", index, &self) 100 | ))?; 101 | // Get the error message from the string 102 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 103 | // Return an error which includes the TSK error message 104 | return Err(TskError::tsk_fs_name_error( 105 | format!("Error getting TskFsName at index {} from TskFsDir {:?}: {}", index, &self, error_msg) 106 | )); 107 | } 108 | 109 | Ok(TskFsName::from_ptr(tsk_fs_name)?) 110 | } 111 | 112 | /// Get an iterator that iterates TskFsNames of this TskFsDir 113 | pub fn get_name_iter<'d>(&'d self) -> DirNameIter<'fs, 'd> { 114 | DirNameIter { 115 | tsk_fs_dir: self, 116 | index: 0 117 | } 118 | } 119 | 120 | /// Get an iterator that iterates TskFsNames of this TskFsDir 121 | pub fn into_name_iter(self) -> IntoDirNameIter<'fs> { 122 | IntoDirNameIter { 123 | tsk_fs_dir: self, 124 | index: 0 125 | } 126 | } 127 | 128 | /// Get the mut ptr for TSK_FS_DIR 129 | pub fn as_mut_ptr(&mut self) -> *mut tsk::TSK_FS_DIR { 130 | self.tsk_fs_dir_ptr 131 | } 132 | 133 | /// Get the underlying TskFs 134 | pub fn get_fs(&'fs self) -> &'fs TskFs { 135 | self.tsk_fs_info 136 | } 137 | } 138 | impl<'fs> Into<*mut tsk::TSK_FS_DIR> for &TskFsDir<'fs> { 139 | fn into(self) -> *mut tsk::TSK_FS_DIR { 140 | self.tsk_fs_dir_ptr 141 | } 142 | } 143 | impl<'fs> Into<*mut tsk::TSK_FS_DIR> for TskFsDir<'fs> { 144 | fn into(self) -> *mut tsk::TSK_FS_DIR { 145 | self.tsk_fs_dir_ptr 146 | } 147 | } 148 | impl<'fs, 'd> Into<&'d *mut tsk::TSK_FS_DIR> for &'d mut TskFsDir<'fs> { 149 | fn into(self) -> &'d *mut tsk::TSK_FS_DIR { 150 | &self.tsk_fs_dir_ptr 151 | } 152 | } 153 | impl<'fs, 'd> Into<&'d *mut tsk::TSK_FS_DIR> for &'d TskFsDir<'fs> { 154 | fn into(self) -> &'d *mut tsk::TSK_FS_DIR { 155 | &self.tsk_fs_dir_ptr 156 | } 157 | } 158 | impl<'fs> std::fmt::Debug for TskFsDir<'fs> { 159 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 160 | f.debug_struct("TskFsDir") 161 | .field("addr", unsafe{&(*self.tsk_fs_dir_ptr).addr}) 162 | .field("seq", unsafe{&(*self.tsk_fs_dir_ptr).seq}) 163 | .field("fs_file", unsafe{&(*self.tsk_fs_dir_ptr).fs_file}) 164 | .field("names_used", unsafe{&(*self.tsk_fs_dir_ptr).names_used}) 165 | .field("names_alloc", unsafe{&(*self.tsk_fs_dir_ptr).names_alloc}) 166 | .field("names", unsafe{&(*self.tsk_fs_dir_ptr).names}) 167 | .finish() 168 | } 169 | } 170 | impl<'fs> Drop for TskFsDir<'fs> { 171 | fn drop(&mut self) { 172 | if self._release { 173 | unsafe { tsk::tsk_fs_dir_close(self.tsk_fs_dir_ptr) }; 174 | } 175 | } 176 | } 177 | 178 | 179 | /// DirNameIter is an iterator for the allocated TskFsName of a given TskFsDir. 180 | #[derive(Debug, Clone)] 181 | pub struct IntoDirNameIter<'fs>{ 182 | tsk_fs_dir: TskFsDir<'fs>, 183 | index: usize 184 | } 185 | impl<'fs> Iterator for IntoDirNameIter<'fs> { 186 | type Item = TskFsName; 187 | 188 | fn next(&mut self) -> Option { 189 | let tsk_fs_dir_ptr: *mut tsk::TSK_FS_DIR = self.tsk_fs_dir.as_mut_ptr(); 190 | let names_used = unsafe { 191 | (*tsk_fs_dir_ptr).names_used 192 | }; 193 | 194 | if self.index < names_used { 195 | // Get the pointer to the TSK_FS_NAME from the names array at the given index 196 | let ptr = unsafe {(*tsk_fs_dir_ptr).names.offset(self.index as isize)}; 197 | 198 | // Create the TskFsName wrapper for TSK_FS_NAME pointer 199 | let name_attr = TskFsName::from_ptr(ptr) 200 | .expect("DirNameIter names ptr is null!"); 201 | 202 | // Update the index for the next fetch 203 | self.index += 1; 204 | 205 | // return the TskFsName 206 | return Some(name_attr); 207 | } 208 | 209 | None 210 | } 211 | } 212 | 213 | 214 | /// DirNameIter is an iterator for the allocated TskFsName of a given TskFsDir. 215 | #[derive(Debug, Clone)] 216 | pub struct DirNameIter<'fs, 'd>{ 217 | tsk_fs_dir: &'d TskFsDir<'fs>, 218 | index: usize 219 | } 220 | impl<'fs, 'd> DirNameIter<'fs, 'd> { 221 | pub fn get_dir(&self) -> &'d TskFsDir<'fs> { 222 | self.tsk_fs_dir 223 | } 224 | } 225 | impl<'fs, 'd> Iterator for DirNameIter<'fs, 'd> { 226 | type Item = TskFsName; 227 | 228 | fn next(&mut self) -> Option { 229 | let tsk_fs_dir_ptr: *mut tsk::TSK_FS_DIR = self.tsk_fs_dir.into(); 230 | 231 | while self.index < unsafe {(*tsk_fs_dir_ptr).names_used} { 232 | // Get the pointer to the TSK_FS_NAME from the names array at the given index 233 | let ptr = unsafe {(*tsk_fs_dir_ptr).names.offset(self.index as isize)}; 234 | 235 | // Create the TskFsName wrapper for TSK_FS_NAME pointer 236 | let name_attr = TskFsName::from_ptr(ptr) 237 | .expect("DirNameIter names ptr is null!"); 238 | 239 | // Update the index for the next fetch 240 | self.index += 1; 241 | 242 | // return the TskFsName 243 | return Some(name_attr); 244 | } 245 | 246 | None 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/tsk_fs_file.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::{null_mut, NonNull}; 2 | use std::ffi::{CStr, CString}; 3 | use crate::{ 4 | errors::TskError, 5 | tsk_fs::TskFs, 6 | tsk_fs_meta::TskFsMeta, 7 | tsk_fs_attr::{TskFsAttr, TskFsAttrIterator}, 8 | tsk_fs_file_handle::TskFsFileHandle, 9 | bindings as tsk 10 | }; 11 | 12 | 13 | /// Wrapper for TSK_FS_FILE. The TSK_FS_FILE can never outlast the TSK_FS 14 | /// 15 | /// 'fs => Filesystem lifetime. 16 | #[derive(Debug)] 17 | pub struct TskFsFile<'fs> { 18 | /// A TskFsFile can never outlive its TSK_FS_INFO 19 | tsk_fs: &'fs TskFs, 20 | /// The ptr to the TSK_FS_FILE struct 21 | tsk_fs_file_ptr: *mut tsk::TSK_FS_FILE, 22 | /// We dont always want to free a file pointer 23 | _release: bool 24 | } 25 | impl<'fs> TskFsFile<'fs> { 26 | /// Create a TSK_FS_FILE wrapper given TskFs and path 27 | pub fn from_path(tsk_fs: &'fs TskFs, path: &str) -> Result { 28 | // Create a CString for the provided source 29 | let path_c = CString::new(path) 30 | .map_err(|e| TskError::generic(format!("Unable to create CString from path {}: {:?}", path, e)))?; 31 | 32 | // Get a pointer to the TSK_FS_FILE sturct 33 | let tsk_fs_file_ptr = unsafe {tsk::tsk_fs_file_open( 34 | tsk_fs.into(), 35 | null_mut(), 36 | path_c.as_ptr() as _ 37 | )}; 38 | 39 | if tsk_fs_file_ptr.is_null() { 40 | // Get a ptr to the error msg 41 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 42 | .ok_or( 43 | TskError::lib_tsk_error( 44 | format!("There was an error opening {}. (no context)", path) 45 | ) 46 | )?; 47 | 48 | // Get the error message from the string 49 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 50 | // Return an error which includes the TSK error message 51 | return Err(TskError::lib_tsk_error( 52 | format!("There was an error opening {}: {}", path, error_msg) 53 | )); 54 | } 55 | 56 | Ok( Self { 57 | tsk_fs, 58 | tsk_fs_file_ptr, 59 | _release: true 60 | } ) 61 | } 62 | 63 | /// Create a TSK_FS_FILE wrapper given TskFs and inode 64 | pub fn from_meta(tsk_fs: &'fs TskFs, inode: u64) -> Result { 65 | // Get a pointer to the TSK_FS_FILE sturct 66 | let tsk_fs_file_ptr = unsafe {tsk::tsk_fs_file_open_meta( 67 | tsk_fs.into(), 68 | null_mut(), 69 | inode as _ 70 | )}; 71 | 72 | if tsk_fs_file_ptr.is_null() { 73 | // Get a ptr to the error msg 74 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 75 | .ok_or( 76 | TskError::lib_tsk_error( 77 | format!("There was an error opening inode {} (no context)", inode) 78 | ) 79 | )?; 80 | 81 | // Get the error message from the string 82 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 83 | // Return an error which includes the TSK error message 84 | return Err(TskError::lib_tsk_error( 85 | format!("There was an error opening inode {}: {}", inode, error_msg) 86 | )); 87 | } 88 | 89 | Ok( Self { 90 | tsk_fs, 91 | tsk_fs_file_ptr, 92 | _release: true 93 | } ) 94 | } 95 | 96 | /// Return the number of attributes in the file. 97 | pub fn attr_getsize(&self) -> Result { 98 | // Get a pointer to the TSK_FS_FILE sturct 99 | let attr_count = unsafe {tsk::tsk_fs_file_attr_getsize( 100 | self.into() 101 | )}; 102 | 103 | if attr_count == -1 { 104 | // Get a ptr to the error msg 105 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 106 | .ok_or( 107 | TskError::lib_tsk_error( 108 | format!("There was an error getting attribute count for indoe {}. (no context)", self.get_meta().unwrap().addr()) 109 | ) 110 | )?; 111 | 112 | // Get the error message from the string 113 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 114 | // Return an error which includes the TSK error message 115 | return Err(TskError::lib_tsk_error( 116 | format!("There was an error getting attribute count for indoe {}: {}", self.get_meta().unwrap().addr(), error_msg) 117 | )); 118 | } 119 | 120 | Ok(attr_count) 121 | } 122 | 123 | /// Get the default TskFsAttr for this TskFsFile 124 | pub fn get_attr(&self) -> Result { 125 | TskFsAttr::from_default(self) 126 | } 127 | 128 | /// Get the TskFsAttr at a given index for this TskFsFile (note this is not the id) 129 | pub fn get_attr_at_index(&self, index: u16) -> Result { 130 | TskFsAttr::from_index(self, index) 131 | } 132 | 133 | /// Get a TskFsAttrIterator for this TskFsFile 134 | pub fn get_attr_iter<'f>(&'fs self) -> Result, TskError> { 135 | let tsk_fs_attr = TskFsAttr::from_index(self, 0)?; 136 | Ok(tsk_fs_attr.into_iter()) 137 | } 138 | 139 | /// Get the TskFsMeta for this TskFsFile 140 | pub fn get_meta(&self) -> Result { 141 | TskFsMeta::from_ptr(unsafe{(*self.tsk_fs_file_ptr).meta}) 142 | } 143 | 144 | /// Get the TskFsFileHandle for this TskFsFile 145 | pub fn get_file_handle( 146 | &'fs self, 147 | tsk_fs_attr: TskFsAttr<'fs, 'fs>, 148 | read_flag: tsk::TSK_FS_FILE_READ_FLAG_ENUM 149 | ) -> Result { 150 | TskFsFileHandle::new(self, tsk_fs_attr, read_flag) 151 | } 152 | 153 | pub fn get_fs(&'fs self) -> &'fs TskFs { 154 | self.tsk_fs 155 | } 156 | } 157 | impl<'fs> Into<*mut tsk::TSK_FS_FILE> for &TskFsFile<'fs> { 158 | fn into(self) -> *mut tsk::TSK_FS_FILE { 159 | self.tsk_fs_file_ptr 160 | } 161 | } 162 | impl<'fs> Drop for TskFsFile<'fs> { 163 | fn drop(&mut self) { 164 | if self._release { 165 | unsafe { tsk::tsk_fs_file_close(self.tsk_fs_file_ptr) }; 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /src/tsk_fs_file_handle.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | use std::convert::TryInto; 3 | use std::io::{Read, Seek, SeekFrom}; 4 | use std::ptr::NonNull; 5 | use std::ffi::CStr; 6 | use super::{ 7 | tsk_fs_file::TskFsFile, 8 | tsk_fs_attr::TskFsAttr, 9 | errors::TskError, 10 | bindings as tsk 11 | }; 12 | 13 | 14 | /// TskFsFileHandle struct is another entry point to read file data. 15 | /// 'f -> TskFsFileHandle can never last longer than the file 16 | /// 'fs -> TskFsFileHandle can never last longer than the file system 17 | /// 18 | pub struct TskFsFileHandle<'f, 'fs>{ 19 | /// The TskFsFile that is being used 20 | tsk_fs_file: &'f TskFsFile<'fs>, 21 | /// The TskFsAttr that contains the information 22 | /// for write operations 23 | tsk_fs_attr: TskFsAttr<'fs, 'f>, 24 | /// The read pointer 25 | _offset: i64, 26 | /// The read flags 27 | read_flag: tsk::TSK_FS_FILE_READ_FLAG_ENUM 28 | } 29 | 30 | impl<'f, 'fs> TskFsFileHandle<'f, 'fs> { 31 | /// Create TskFsFileHandle from TskFsFile, TskFsAttr and read flag. 32 | pub fn new( 33 | tsk_fs_file: &'f TskFsFile<'fs>, 34 | tsk_fs_attr: TskFsAttr<'fs, 'f>, 35 | read_flag: tsk::TSK_FS_FILE_READ_FLAG_ENUM 36 | ) -> Result { 37 | Ok( Self { 38 | tsk_fs_file, 39 | tsk_fs_attr, 40 | _offset: 0, 41 | read_flag 42 | }) 43 | } 44 | } 45 | 46 | impl<'f, 'fs> Read for TskFsFileHandle<'f, 'fs> { 47 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 48 | if self._offset == self.tsk_fs_attr.size() { 49 | return Ok(0); 50 | } 51 | let bytes_read = unsafe{tsk::tsk_fs_file_read_type( 52 | self.tsk_fs_file.into(), 53 | self.tsk_fs_attr.attr_type(), 54 | self.tsk_fs_attr.id(), 55 | self._offset, 56 | buf.as_mut_ptr() as *mut i8, 57 | buf.len(), 58 | self.read_flag 59 | )}; 60 | match bytes_read { 61 | -1 => { 62 | // Get a ptr to the error msg 63 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 64 | .ok_or( 65 | std::io::Error::new( 66 | std::io::ErrorKind::Other, 67 | format!( 68 | "tsk_fs_file_read_type Error. No context." 69 | ) 70 | ) 71 | )?; 72 | 73 | // Get the error message from the string 74 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 75 | return Err( 76 | std::io::Error::new( 77 | std::io::ErrorKind::Other, 78 | format!( 79 | "TskError : {}",error_msg 80 | ) 81 | )); 82 | } 83 | _ => { 84 | self._offset += TryInto::::try_into(bytes_read) 85 | .unwrap(); 86 | return Ok(bytes_read as usize); 87 | } 88 | }; 89 | } 90 | } 91 | impl<'f, 'fs> Seek for TskFsFileHandle<'f, 'fs> { 92 | fn seek(&mut self, pos: SeekFrom) -> std::io::Result{ 93 | match pos { 94 | SeekFrom::Start(o) => { 95 | if o > self.tsk_fs_attr.size() as u64{ 96 | return Err( 97 | std::io::Error::new( 98 | std::io::ErrorKind::Other, 99 | format!( 100 | "Offset Start({}) is greater than attr size {}", o, self.tsk_fs_attr.size() 101 | ) 102 | )); 103 | } 104 | else { 105 | self._offset = o as i64; 106 | return Ok(o); 107 | } 108 | }, 109 | SeekFrom::Current(o) => { 110 | let new_offset = self._offset + o; 111 | if new_offset > self.tsk_fs_attr.size() { 112 | return Err( 113 | std::io::Error::new( 114 | std::io::ErrorKind::Other, 115 | format!( 116 | "Offset Current({}) is greater than attr size. new_offset = {} + {} = {}, self.tsk_fs_attr.size() = {}", o, self._offset, o, new_offset, self.tsk_fs_attr.size() 117 | ) 118 | )); 119 | } 120 | else if new_offset < 0 { 121 | return Err( 122 | std::io::Error::new( 123 | std::io::ErrorKind::Other, 124 | format!("Cannot seek Current({}) from offset {}", o, self._offset) 125 | ) 126 | ); 127 | } 128 | else { 129 | self._offset = new_offset; 130 | } 131 | }, 132 | SeekFrom::End(o) => { 133 | let new_offset = self.tsk_fs_attr.size() + o; 134 | if new_offset > self.tsk_fs_attr.size() { 135 | return Err( 136 | std::io::Error::new( 137 | std::io::ErrorKind::Other, 138 | format!( 139 | "Offset Current({}) is greater than attr size. new_offset = {} + {} = {}, self.tsk_fs_attr.size() = {}", o, self._offset, o, new_offset, self.tsk_fs_attr.size() 140 | ) 141 | )); 142 | } 143 | else if new_offset < 0 { 144 | return Err( 145 | std::io::Error::new( 146 | std::io::ErrorKind::Other, 147 | format!("Cannot seek Current({}) from offset {}", o, self._offset) 148 | ) 149 | ); 150 | } 151 | else { 152 | self._offset = new_offset; 153 | } 154 | } 155 | } 156 | 157 | Ok(self._offset as u64) 158 | } 159 | } -------------------------------------------------------------------------------- /src/tsk_fs_meta.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::ptr::NonNull; 3 | use crate::{ 4 | errors::TskError, 5 | bindings as tsk 6 | }; 7 | 8 | 9 | /// Wrapper for TSK_FS_META 10 | pub struct TskFsMeta(*const tsk::TSK_FS_META); 11 | impl TskFsMeta { 12 | /// Get TskFsMeta wrapper given a TSK_FS_META pointer 13 | pub fn from_ptr(tsk_fs_meta: *const tsk::TSK_FS_META) -> Result { 14 | if tsk_fs_meta.is_null() { 15 | // Get a ptr to the error msg 16 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 17 | .ok_or( 18 | TskError::lib_tsk_error( 19 | format!("Error TSK_FS_META is null. (no context)") 20 | ) 21 | )?; 22 | 23 | // Get the error message from the string 24 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 25 | // Return an error which includes the TSK error message 26 | return Err(TskError::tsk_fs_meta_error( 27 | format!("Error TSK_FS_META is null: {}", error_msg) 28 | )); 29 | } 30 | 31 | Ok(Self(tsk_fs_meta)) 32 | } 33 | 34 | /// Get the size of the file 35 | pub fn size(&self) -> i64 { 36 | unsafe { (*self.0).size } 37 | } 38 | 39 | /// Get the creation time of the file (epoch time) 40 | pub fn crtime(&self) -> i64 { 41 | unsafe { (*self.0).crtime } 42 | } 43 | 44 | /// Get the modification time of the file (epoch time) 45 | pub fn mtime(&self) -> i64 { 46 | unsafe { (*self.0).mtime } 47 | } 48 | 49 | /// Get the access time of the file (epoch time) 50 | pub fn atime(&self) -> i64 { 51 | unsafe { (*self.0).atime } 52 | } 53 | 54 | /// Get the inode 55 | pub fn addr(&self) -> u64 { 56 | unsafe { (*self.0).addr } 57 | } 58 | 59 | /// Get type 60 | pub fn meta_type(&self) -> tsk::TSK_FS_META_TYPE_ENUM { 61 | unsafe { (*self.0).type_ } 62 | } 63 | 64 | /// Get flags 65 | pub fn flags(&self) -> Vec { 66 | let mut flags = vec![]; 67 | match unsafe { (*self.0).flags } { 68 | f if f as i32 & tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_ALLOC as i32 > 0 => flags.push(tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_ALLOC), 69 | f if f as i32 & tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_UNALLOC as i32 > 0 => flags.push(tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_UNALLOC), 70 | f if f as i32 & tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_USED as i32 > 0 => flags.push(tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_USED), 71 | f if f as i32 & tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_UNUSED as i32 > 0 => flags.push(tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_UNUSED), 72 | f if f as i32 & tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_COMP as i32 > 0 => flags.push(tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_COMP), 73 | f if f as i32 & tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_ORPHAN as i32 > 0 => flags.push(tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_ORPHAN), 74 | _ => {} 75 | } 76 | flags 77 | } 78 | 79 | /// Allocation check 80 | pub fn is_unallocated(&self) -> bool { 81 | self.flags().iter().any(|f| match f { 82 | tsk::TSK_FS_META_FLAG_ENUM::TSK_FS_META_FLAG_UNALLOC => true, 83 | _ => false 84 | }) 85 | } 86 | 87 | } 88 | 89 | impl std::fmt::Debug for TskFsMeta { 90 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 91 | f.debug_struct("TskFsMeta") 92 | .field("size", &self.size()) 93 | .field("crtime", &self.crtime()) 94 | .field("mtime", &self.mtime()) 95 | .field("atime", &self.atime()) 96 | .field("addr", &self.addr()) 97 | .field("type", &self.meta_type()) 98 | .field("flags", &self.flags()) 99 | .finish() 100 | } 101 | } -------------------------------------------------------------------------------- /src/tsk_fs_name.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::ptr::NonNull; 3 | use crate::{ 4 | errors::TskError, 5 | bindings as tsk, 6 | tsk_fs::TskFs 7 | }; 8 | 9 | 10 | /// Wrapper for TSK_FS_NAME 11 | pub struct TskFsName(*const tsk::TSK_FS_NAME); 12 | impl TskFsName { 13 | pub fn from_ptr(tsk_fs_name: *const tsk::TSK_FS_NAME) -> Result { 14 | if tsk_fs_name.is_null() { 15 | // Get a ptr to the error msg 16 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 17 | .ok_or( 18 | TskError::lib_tsk_error( 19 | format!("Error TSK_FS_NAME is null. (no context)") 20 | ) 21 | )?; 22 | 23 | // Get the error message from the string 24 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 25 | 26 | // Return an error which includes the TSK error message 27 | return Err(TskError::tsk_fs_name_error( 28 | format!("Error TSK_FS_NAME is null: {}", error_msg) 29 | )); 30 | } 31 | 32 | Ok(Self(tsk_fs_name)) 33 | } 34 | 35 | /// TskFsName represents a directory file 36 | pub fn is_dir(&self) -> bool { 37 | let type_ = unsafe {(*self.0).type_}; 38 | type_ == tsk::TSK_FS_NAME_TYPE_ENUM_TSK_FS_NAME_TYPE_DIR 39 | } 40 | 41 | /// Get the inode for this TSK_FS_NAME 42 | pub fn get_inode(&self) -> u64 { 43 | unsafe {(*self.0).meta_addr} 44 | } 45 | 46 | /// Get the name of the attribute if available 47 | pub fn name(&self) -> Option { 48 | // First check if the name is null 49 | if unsafe { (*self.0).name }.is_null() { 50 | return None; 51 | } 52 | let name = unsafe { CStr::from_ptr((*self.0).name) }.to_string_lossy(); 53 | Some(name.to_string().clone()) 54 | } 55 | 56 | /// Get the short name of the attribute if available 57 | pub fn shrt_name(&self) -> Option { 58 | // First check if the name is null 59 | if unsafe { (*self.0).shrt_name }.is_null() { 60 | return None; 61 | } 62 | let shrt_name = unsafe { CStr::from_ptr((*self.0).shrt_name) }.to_string_lossy(); 63 | Some(shrt_name.to_string().clone()) 64 | } 65 | } 66 | impl std::fmt::Debug for TskFsName { 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | f.debug_struct("TskFsName") 69 | .field("name", &self.name()) 70 | .field("shrt_name", &self.shrt_name()) 71 | .field("meta_addr", &unsafe{(*self.0).meta_addr}) 72 | .field("meta_seq", &unsafe{(*self.0).meta_seq}) 73 | .field("par_addr", &unsafe{(*self.0).par_addr}) 74 | .field("par_seq", &unsafe{(*self.0).par_seq}) 75 | .field("type", &unsafe{(*self.0).type_}) 76 | .field("flags", &unsafe{(*self.0).flags}) 77 | .field("date_added", &unsafe{(*self.0).date_added}) 78 | .finish() 79 | } 80 | } -------------------------------------------------------------------------------- /src/tsk_img.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::ptr::NonNull; 3 | use std::ffi::{CStr, CString, c_void}; 4 | use crate::{ 5 | errors::TskError, 6 | bindings as tsk, 7 | tsk_fs::TskFs, 8 | tsk_vs::TskVs 9 | }; 10 | 11 | 12 | type ReadCallback = Option< 13 | unsafe extern "C" fn( 14 | img: *mut tsk::TSK_IMG_INFO, 15 | off: tsk::TSK_OFF_T, 16 | buf: *mut ::std::os::raw::c_char, 17 | len: usize, 18 | ) -> isize, 19 | >; 20 | type CloseCallback = Option; 21 | type ImgStatCallback = Option; 22 | 23 | 24 | /// Wrapper for TSK_IMG_INFO 25 | #[derive(Debug)] 26 | pub struct TskImg { 27 | /// The ptr to the TSK_IMG_INFO struct 28 | pub handle: NonNull 29 | } 30 | impl TskImg { 31 | /// Create TskImg from custom callbacks 32 | pub fn from_external( 33 | ext_img_info: *mut c_void, 34 | size: i64, 35 | sector_size: u32, 36 | read: ReadCallback, 37 | close: CloseCallback, 38 | imgstat: ImgStatCallback 39 | ) -> Result { 40 | // Open TSK_IMG_INFO based off of callback functions 41 | let tsk_img_info_ptr: *mut tsk::TSK_IMG_INFO = unsafe { 42 | tsk::tsk_img_open_external( 43 | ext_img_info, 44 | size, 45 | sector_size, 46 | read, 47 | close, 48 | imgstat 49 | ) 50 | }; 51 | 52 | let handle = match NonNull::new(tsk_img_info_ptr) { 53 | None => { 54 | // Get a ptr to the error msg 55 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 56 | .ok_or( 57 | TskError::lib_tsk_error( 58 | format!( 59 | "tsk_img_open_external() error. (no context)" 60 | ) 61 | ) 62 | )?; 63 | 64 | // Get the error message from the string 65 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 66 | 67 | // Return an error which includes the TSK error message 68 | return Err(TskError::lib_tsk_error( 69 | format!("There was an error opening the img handle: {}", error_msg) 70 | )); 71 | }, 72 | Some(h) => h 73 | }; 74 | 75 | Ok( Self { handle }) 76 | } 77 | 78 | /// Create a TskImg wrapper from a given TSK_IMG_INFO NonNull pinter. 79 | /// 80 | pub fn from_tsk_img_info_ptr(img_info: NonNull) -> Self { 81 | Self { handle: img_info } 82 | } 83 | 84 | /// Create a TskImg wrapper from a given source. 85 | /// 86 | pub fn from_utf8_sing(path: impl AsRef) -> Result { 87 | // Create a CString for the provided source 88 | let source = CString::new(path.as_ref().to_string_lossy().as_bytes()) 89 | .map_err(|e| TskError::generic(format!("Unable to create CString from source: {:?}", e)))?; 90 | 91 | // Get a pointer to the TSK_IMG_INFO sturct 92 | let tsk_img = unsafe {tsk::tsk_img_open_utf8_sing( 93 | source.as_ptr() as _, 94 | tsk::TSK_IMG_TYPE_ENUM_TSK_IMG_TYPE_RAW_SING, 95 | 0 96 | )}; 97 | 98 | // Ensure that the ptr is not null 99 | let handle = match NonNull::new(tsk_img) { 100 | None => { 101 | // Get a ptr to the error msg 102 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 103 | .ok_or( 104 | TskError::lib_tsk_error( 105 | format!( 106 | "There was an error opening the img handle from {}. (no context)", 107 | path.as_ref() 108 | .to_string_lossy() 109 | ) 110 | ) 111 | )?; 112 | 113 | // Get the error message from the string 114 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 115 | // Return an error which includes the TSK error message 116 | return Err(TskError::lib_tsk_error( 117 | format!("There was an error opening the img handle: {}", error_msg) 118 | )); 119 | }, 120 | Some(h) => h 121 | }; 122 | 123 | Ok( Self { handle } ) 124 | } 125 | 126 | /// Get a TskVs at a given offset 127 | pub fn get_vs_from_offset(&self, offset: u64) -> Result { 128 | TskVs::new(&self, offset) 129 | } 130 | 131 | /// Get a TskFs at a given offset 132 | pub fn get_fs_from_offset(&self, offset: u64) -> Result { 133 | TskFs::from_fs_offset(&self, offset) 134 | } 135 | } 136 | impl Drop for TskImg { 137 | fn drop(&mut self) { 138 | unsafe { tsk::tsk_img_close(self.handle.as_ptr()) }; 139 | } 140 | } -------------------------------------------------------------------------------- /src/tsk_img_reader.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use std::io::{Seek, SeekFrom, Read}; 3 | use std::mem::MaybeUninit; 4 | use std::ptr::NonNull; 5 | use std::ffi::CStr; 6 | use crate::tsk_img::TskImg; 7 | use crate::errors::TskError; 8 | use crate::bindings as tsk; 9 | 10 | 11 | pub trait ReadSeek: Read + Seek { 12 | fn tell(&mut self) -> std::io::Result { 13 | self.seek(SeekFrom::Current(0)) 14 | } 15 | fn stream_len(&mut self) -> std::io::Result { 16 | let old_pos = self.tell()?; 17 | let len = self.seek(SeekFrom::End(0))?; 18 | 19 | // Avoid seeking a third time when we were already at the end of the 20 | // stream. The branch is usually way cheaper than a seek operation. 21 | if old_pos != len { 22 | self.seek(SeekFrom::Start(old_pos))?; 23 | } 24 | 25 | Ok(len) 26 | } 27 | } 28 | impl ReadSeek for T {} 29 | 30 | 31 | /// Read function for TskImgReadSeek 32 | unsafe extern "C" fn img_read( 33 | img: *mut tsk::TSK_IMG_INFO, 34 | off: tsk::TSK_OFF_T, 35 | buf: *mut ::std::os::raw::c_char, 36 | len: usize, 37 | ) -> isize { 38 | if img.is_null() { 39 | error!("img_read contained null TSK_IMG_INFO pointer!"); 40 | return -1; 41 | } 42 | 43 | // Get pointer to TskImgReadSeekInner 44 | let reader = img as *mut TskImgReadSeekInner; 45 | 46 | // Seek inner stream to offset 47 | match (*reader).stream.seek(SeekFrom::Start(off as u64)) { 48 | Ok(pos) => { 49 | if pos != off as u64 { 50 | error!("pos {} is not equal to off {}", pos, off); 51 | return 0; 52 | } 53 | }, 54 | Err(error) => { 55 | eprintln!("Error seeking stream: {:?}", error); 56 | return 0; 57 | } 58 | } 59 | 60 | // Make sure that the buffer's pointer is not null 61 | if buf.is_null() { 62 | error!("img_read contained null buffer!"); 63 | return -1; 64 | } 65 | 66 | // Get slice from buf pointer 67 | let buffer = core::slice::from_raw_parts_mut( 68 | buf as *mut u8, 69 | len as usize 70 | ); 71 | // Read bytes from stream into buffer 72 | let bytes_read = match (*reader).stream.read(buffer) { 73 | Ok(b) => b, 74 | Err(e) => { 75 | error!("{:?}", e); 76 | return 0; 77 | } 78 | }; 79 | 80 | std::mem::forget(buffer); 81 | 82 | // Check if bytes read was not equal to length of data to read 83 | if bytes_read != len { 84 | error!("bytes_read {} is not equal to len {}", bytes_read, len); 85 | return -1; 86 | } 87 | 88 | // Return bytes read 89 | bytes_read.try_into() 90 | .expect("Cannot convert bytes read into usize.") 91 | } 92 | 93 | 94 | unsafe extern "C" fn img_info( 95 | arg1: *mut tsk::TSK_IMG_INFO, 96 | arg2: *mut tsk::FILE 97 | ) { 98 | trace!("img_info() not implemented"); 99 | } 100 | 101 | 102 | unsafe extern "C" fn img_close( 103 | arg1: *mut tsk::TSK_IMG_INFO 104 | ) { 105 | trace!("img_close() not implemented"); 106 | } 107 | 108 | 109 | /// Custom struct that will take a ReadSeek trait to read from 110 | #[repr(C)] 111 | struct TskImgReadSeekInner { 112 | tsk_img_info: tsk::TSK_IMG_INFO, 113 | stream: Box 114 | } 115 | /// TskImgReadSeek uses a boxed read/seek trait and can be turned into a TskImg 116 | pub struct TskImgReadSeek{ 117 | source: String, 118 | inner: *mut MaybeUninit, 119 | tsk_img_info: NonNull 120 | } 121 | impl TskImgReadSeek { 122 | pub fn from_read_seek>( 123 | source: S, 124 | stream: Box, 125 | size: i64 126 | ) -> Result { 127 | let source = source.into(); 128 | let sector_size: std::os::raw::c_uint = 512; 129 | 130 | // Create uninitialized reader 131 | let mut boxed_tsk_reader = Box::::new_uninit(); 132 | // Set the stream 133 | unsafe { std::ptr::addr_of_mut!((*boxed_tsk_reader.as_mut_ptr()).stream).write(Box::new(stream)) }; 134 | // Get the pointer to the uninitialized struct 135 | let tsk_reader_ptr_unint: *mut MaybeUninit = Box::into_raw(boxed_tsk_reader).cast(); 136 | 137 | // Create callback pointers 138 | let read = Some(img_read as unsafe extern "C" fn( 139 | img: *mut tsk::TSK_IMG_INFO, 140 | off: tsk::TSK_OFF_T, 141 | buf: *mut ::std::os::raw::c_char, 142 | len: usize, 143 | ) -> isize); 144 | let close = Some( 145 | img_close as unsafe extern "C" fn( 146 | arg1: *mut tsk::TSK_IMG_INFO 147 | ) 148 | ); 149 | let imgstat = Some( 150 | img_info as unsafe extern "C" fn( 151 | arg1: *mut tsk::TSK_IMG_INFO, 152 | arg2: *mut tsk::FILE 153 | ) 154 | ); 155 | 156 | // Get address of the TSK_IMG_INFO pointer 157 | let img_addr = unsafe{ std::ptr::addr_of_mut!( 158 | (*(*tsk_reader_ptr_unint).as_mut_ptr()).tsk_img_info 159 | )}; 160 | // Open via the external source 161 | let tsk_img_info_ptr: *mut tsk::TSK_IMG_INFO = unsafe { 162 | tsk::tsk_img_open_external( 163 | img_addr as _, 164 | size, 165 | sector_size, 166 | read, 167 | close, 168 | imgstat 169 | ) 170 | }; 171 | 172 | // Get non null pointer 173 | let tsk_img_info_ptr = match NonNull::new(tsk_img_info_ptr) { 174 | None => { 175 | // Get a ptr to the error msg 176 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 177 | .ok_or( 178 | TskError::lib_tsk_error( 179 | format!( 180 | "There was an error opening read/seek img handle from {}. (no context)", 181 | &source 182 | ) 183 | ) 184 | )?; 185 | 186 | // Get the error message from the string 187 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 188 | // Return an error which includes the TSK error message 189 | return Err(TskError::lib_tsk_error( 190 | format!( 191 | "There was an error opening read/seek img handle from {}: {}", 192 | &source, 193 | error_msg 194 | ) 195 | )); 196 | }, 197 | Some(h) => h 198 | }; 199 | 200 | Ok( Self{ 201 | source, 202 | inner: tsk_reader_ptr_unint, 203 | tsk_img_info: tsk_img_info_ptr 204 | }) 205 | } 206 | } 207 | impl std::fmt::Debug for TskImgReadSeek { 208 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 209 | f.debug_struct("TskImgReadSeek") 210 | .field("source", &self.source) 211 | .field("inner", &self.inner) 212 | .field("tsk_img_info", &self.tsk_img_info) 213 | .finish() 214 | } 215 | } 216 | impl<'fs> Into for TskImgReadSeek { 217 | fn into(self) -> TskImg { 218 | TskImg::from_tsk_img_info_ptr(self.tsk_img_info) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/tsk_vs.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::NonNull; 2 | use std::ffi::CStr; 3 | use crate::{ 4 | errors::TskError, 5 | tsk_img::TskImg, 6 | tsk_vs_part::{TskVsPart, TskVsPartIterator}, 7 | bindings as tsk 8 | }; 9 | 10 | 11 | /// Wrapper for TSK_VS_INFO 12 | #[derive(Debug)] 13 | pub struct TskVs { 14 | /// The ptr to the TSK_VS_INFO struct 15 | pub handle: NonNull 16 | } 17 | impl TskVs { 18 | /// Create a TSK_VS_INFO wrapper given the TskImg and offset of the file system 19 | pub fn new(tsk_img: &TskImg, offset: u64) -> Result { 20 | // Get a pointer to the TSK_VS_INFO sturct 21 | let tsk_vs = unsafe {tsk::tsk_vs_open( 22 | tsk_img.handle.as_ptr(), 23 | offset as _, 24 | 0 25 | )}; 26 | 27 | // Ensure that the ptr is not null 28 | let handle = match NonNull::new(tsk_vs) { 29 | None => { 30 | // Get a ptr to the error msg 31 | let error_msg_ptr = unsafe { NonNull::new(tsk::tsk_error_get() as _) } 32 | .ok_or( 33 | TskError::lib_tsk_error( 34 | format!( 35 | "There was an error opening the TSK_VS_INFO handle at offset {}. (no context)", 36 | offset 37 | ) 38 | ) 39 | )?; 40 | 41 | // Get the error message from the string 42 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 43 | // Return an error which includes the TSK error message 44 | return Err(TskError::lib_tsk_error( 45 | format!( 46 | "There was an error opening the TSK_VS_INFO handle at offset {}: {}", 47 | offset, 48 | error_msg 49 | ) 50 | )); 51 | }, 52 | Some(h) => h 53 | }; 54 | 55 | Ok( Self { handle } ) 56 | } 57 | 58 | /// Get a specific TskVsPart at the given index 59 | pub fn get_partition_at_index(&self, index: u64) -> Result { 60 | TskVsPart::new(self, index) 61 | } 62 | 63 | /// Get a partition iterator that yields TskVsPart structs 64 | pub fn get_partition_iter<'vs>(&'vs self) -> Result, TskError> { 65 | let iterator = TskVsPart::new(self, 0)? 66 | .into_iter(); 67 | Ok(iterator) 68 | } 69 | } 70 | impl Drop for TskVs { 71 | fn drop(&mut self) { 72 | unsafe { tsk::tsk_vs_close(self.handle.as_ptr()) }; 73 | } 74 | } -------------------------------------------------------------------------------- /src/tsk_vs_part.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use crate::{ 3 | errors::TskError, 4 | tsk_vs::TskVs, 5 | tsk_vs_part_handle::TskVsPartHandle, 6 | bindings as tsk 7 | }; 8 | 9 | 10 | /// Wrapper for TSK_VS_PART_INFO. 11 | /// The TskVs reference must live for the lifetime of 12 | /// *const tsk::TSK_VS_PART_INFO. 13 | pub struct TskVsPart<'vs> { 14 | tsk_vs: &'vs TskVs, 15 | tsk_part_info: *const tsk::TSK_VS_PART_INFO 16 | } 17 | impl<'vs> TskVsPart<'vs> { 18 | /// Create a TSK_VS_PART_INFO wrapper given the TskImg and offset of the file system 19 | pub fn new(tsk_vs: &'vs TskVs, offset: u64) -> Result { 20 | // Get a pointer to the TSK_VS_PART_INFO sturct 21 | // TODO: HANDLE NULL! 22 | let tsk_vs_part = unsafe {tsk::tsk_vs_part_get( 23 | tsk_vs.handle.as_ptr(), 24 | offset as _ 25 | )}; 26 | 27 | Ok( Self{ 28 | tsk_vs: tsk_vs, 29 | tsk_part_info: tsk_vs_part 30 | }) 31 | } 32 | 33 | /// Get the partition info pointer 34 | pub fn get_part_info(&self) -> *const tsk::TSK_VS_PART_INFO { 35 | self.tsk_part_info 36 | } 37 | 38 | /// Get the start offset 39 | pub fn get_start_offset(&self) -> u64 { 40 | unsafe{*(self.tsk_part_info)}.start * unsafe{*(*self.tsk_part_info).vs}.block_size as u64 41 | } 42 | 43 | /// Get the len in blocks of this partition 44 | pub fn len(&self) -> u64 { 45 | unsafe {*self.tsk_part_info}.len 46 | } 47 | 48 | /// Get the byte size of the volume 49 | pub fn size(&self) -> u64 { 50 | unsafe {*self.tsk_part_info}.len * 51 | unsafe {(*(*self.tsk_part_info).vs).block_size} as u64 52 | } 53 | 54 | /// Get a IO handle to the partition 55 | pub fn get_handle<'p>(&'p self) -> TskVsPartHandle<'vs, 'p> { 56 | TskVsPartHandle::new(&self) 57 | } 58 | 59 | /// Get the description string 60 | pub fn desc(&self) -> String { 61 | let desc = unsafe { CStr::from_ptr((*self.tsk_part_info).desc) }.to_string_lossy(); 62 | desc.to_string().clone() 63 | } 64 | 65 | /// Get an iterator based off this TskVsPart struct 66 | pub fn into_iter(self) -> TskVsPartIterator<'vs> { 67 | TskVsPartIterator(self) 68 | } 69 | } 70 | impl<'vs> Into<*const tsk::TSK_VS_PART_INFO> for &TskVsPart<'vs> { 71 | fn into(self) -> *const tsk::TSK_VS_PART_INFO { 72 | self.tsk_part_info 73 | } 74 | } 75 | impl<'vs> Into<*mut tsk::TSK_VS_PART_INFO> for &TskVsPart<'vs> { 76 | fn into(self) -> *mut tsk::TSK_VS_PART_INFO { 77 | self.tsk_part_info as _ 78 | } 79 | } 80 | impl<'vs> std::fmt::Debug for TskVsPart<'vs> { 81 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 82 | f.debug_struct("TskVsPart") 83 | .field("addr", &(unsafe{*self.tsk_part_info}.addr)) 84 | .field("desc", &self.desc()) 85 | .field("flags", &(unsafe{*self.tsk_part_info}.flags)) 86 | .field("len", &(unsafe{*self.tsk_part_info}.len)) 87 | .field("slot_num", &(unsafe{*self.tsk_part_info}.slot_num)) 88 | .field("start", &(unsafe{*self.tsk_part_info}. start)) 89 | .field("table_num", &(unsafe{*self.tsk_part_info}. table_num)) 90 | .field("tag", &(unsafe{*self.tsk_part_info}.tag)) 91 | .finish() 92 | } 93 | } 94 | 95 | 96 | /// An iterator over a TSK_VS_PART_INFO pointer which uses the 97 | /// structs next attribute to iterate. 98 | pub struct TskVsPartIterator<'vs>(TskVsPart<'vs>); 99 | impl< 'vs> Iterator for TskVsPartIterator< 'vs> { 100 | type Item = TskVsPart<'vs>; 101 | 102 | fn next(&mut self) -> Option> { 103 | // Check that the partition is not null 104 | if self.0.tsk_part_info.is_null() { 105 | return None; 106 | } 107 | 108 | // Get current pointer 109 | let current = self.0.tsk_part_info; 110 | 111 | // Get the next pointer 112 | let next = unsafe { 113 | *self.0.tsk_part_info 114 | }.next as *const tsk::TSK_VS_PART_INFO; 115 | 116 | // Create a TskVsPartion that represents the current node 117 | let cur3nt_vs_part = TskVsPart{ 118 | tsk_vs: self.0.tsk_vs, 119 | tsk_part_info: current 120 | }; 121 | 122 | // Set the iterators pointer to the next node 123 | self.0.tsk_part_info = next; 124 | 125 | // Return current partition 126 | Some(cur3nt_vs_part) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/tsk_vs_part_handle.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | use std::convert::TryInto; 3 | use std::ffi::CStr; 4 | use std::ptr::NonNull; 5 | use std::io::{Read, Seek, SeekFrom}; 6 | use crate::tsk_vs_part::TskVsPart; 7 | use crate::bindings as tsk; 8 | 9 | pub struct TskVsPartHandle<'vs, 'p>{ 10 | /// The TskVs that is being used 11 | tsk_vs_part: &'p TskVsPart<'vs>, 12 | /// The read pointer 13 | _offset: i64 14 | } 15 | impl<'vs, 'p> TskVsPartHandle<'vs, 'p> { 16 | /// Create TskVsPartHandle from TskVsPart 17 | pub fn new( 18 | tsk_vs_part: &'p TskVsPart<'vs> 19 | ) -> Self { 20 | Self { 21 | tsk_vs_part, 22 | _offset: 0 23 | } 24 | } 25 | } 26 | impl<'vs, 'p> Seek for TskVsPartHandle<'vs, 'p> { 27 | fn seek(&mut self, pos: SeekFrom) -> std::io::Result{ 28 | match pos { 29 | SeekFrom::Start(o) => { 30 | if o > self.tsk_vs_part.size() as u64 { 31 | return Err( 32 | std::io::Error::new( 33 | std::io::ErrorKind::Other, 34 | format!( 35 | "Offset Start({}) is greater than partition size {}", o, self.tsk_vs_part.size() 36 | ) 37 | )); 38 | } 39 | else { 40 | self._offset = o as i64; 41 | return Ok(o); 42 | } 43 | }, 44 | SeekFrom::Current(o) => { 45 | let new_offset = self._offset + o; 46 | if new_offset > self.tsk_vs_part.size().try_into().unwrap() { 47 | return Err( 48 | std::io::Error::new( 49 | std::io::ErrorKind::Other, 50 | format!( 51 | "Offset Current({}) is greater than partition size. new_offset = {} + {} = {}, self.tsk_vs_part.size() = {}", 52 | o, 53 | self._offset, 54 | o, 55 | new_offset, 56 | self.tsk_vs_part.size() 57 | ) 58 | )); 59 | } 60 | else if new_offset < 0 { 61 | return Err( 62 | std::io::Error::new( 63 | std::io::ErrorKind::Other, 64 | format!( 65 | "Cannot seek Current({}) from offset {}", 66 | o, 67 | self._offset 68 | ) 69 | ) 70 | ); 71 | } 72 | else { 73 | self._offset = new_offset; 74 | } 75 | }, 76 | SeekFrom::End(o) => { 77 | let new_offset: u64 = self.tsk_vs_part.size() + o as u64; 78 | if new_offset > self.tsk_vs_part.size() { 79 | return Err( 80 | std::io::Error::new( 81 | std::io::ErrorKind::Other, 82 | format!( 83 | "Offset Current({}) is greater than attr size. new_offset = {} + {} = {}, self.tsk_vs_part.size() = {}", 84 | o, 85 | self._offset, 86 | o, 87 | new_offset, 88 | self.tsk_vs_part.size() 89 | ) 90 | )); 91 | } else { 92 | self._offset = new_offset 93 | .try_into() 94 | .expect("Error converting offset into i64."); 95 | } 96 | } 97 | } 98 | 99 | Ok(self._offset as u64) 100 | } 101 | } 102 | impl<'vs, 'p> Read for TskVsPartHandle<'vs, 'p> { 103 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 104 | // byte size has to be in i64 due to _offset requried by tsk_vs_part_read 105 | let part_byte_size = self.tsk_vs_part.size() 106 | .try_into() 107 | .expect("Partition size cannot be converted into i64!"); 108 | 109 | // Check if offset is at end of partition 110 | if self._offset == part_byte_size { 111 | return Ok(0); 112 | } 113 | 114 | // Read bytes 115 | let bytes_read = unsafe{tsk::tsk_vs_part_read( 116 | self.tsk_vs_part.into(), 117 | self._offset, 118 | buf.as_mut_ptr() as *mut i8, 119 | buf.len() 120 | )}; 121 | 122 | match bytes_read { 123 | -1 => { 124 | // Get a ptr to the error msg 125 | let error_msg_ptr = unsafe {NonNull::new(tsk::tsk_error_get() as _)} 126 | .ok_or( 127 | std::io::Error::new( 128 | std::io::ErrorKind::Other, 129 | format!( 130 | "tsk_vs_part_read Error. No context." 131 | ) 132 | ) 133 | )?; 134 | 135 | // Get the error message from the string 136 | let error_msg = unsafe { CStr::from_ptr(error_msg_ptr.as_ptr()) }.to_string_lossy(); 137 | return Err( 138 | std::io::Error::new( 139 | std::io::ErrorKind::Other, 140 | format!( 141 | "tsk_vs_part_read Error : {}", error_msg 142 | ) 143 | )); 144 | } 145 | _ => { 146 | self._offset += TryInto::::try_into(bytes_read) 147 | .unwrap(); 148 | return Ok(bytes_read as usize); 149 | } 150 | }; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/test_img_readseek.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::PathBuf; 3 | use tsk::tsk_img::TskImg; 4 | use tsk::tsk_img_reader::TskImgReadSeek; 5 | 6 | 7 | #[test] 8 | fn test_tsk_reader() { 9 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 10 | let source_size = source.metadata().unwrap().len(); 11 | let handle = File::open(source).expect("Error opening file."); 12 | let boxed_handle = Box::new(handle); 13 | let reader = TskImgReadSeek::from_read_seek( 14 | "Custom File IO", 15 | boxed_handle, 16 | source_size as i64 17 | ).expect("Error creating TskImgReadSeek."); 18 | println!("{:?}", reader); 19 | 20 | let tsk_img: TskImg = reader.into(); 21 | 22 | let tsk_fs = tsk_img.get_fs_from_offset(0) 23 | .expect("Could not open TskFs at offset 0"); 24 | 25 | let mft_fh = tsk_fs.file_open_meta(0) 26 | .expect("Could not open $MFT"); 27 | 28 | // The default will be the first data attribute 29 | let default_attr = mft_fh.get_attr() 30 | .expect("Could get default attribute."); 31 | 32 | // Get the non resident data structure for the defaul attribute 33 | let nrd = default_attr.get_non_resident_data() 34 | .expect("Could not get non resident data struct."); 35 | println!("{:?}", nrd); 36 | 37 | // get the first run 38 | let run = nrd.run(); 39 | println!("{:?}", run); 40 | 41 | // iterate each non-resident data run 42 | for run in nrd.iter() { 43 | // debug print the run 44 | println!("{:?}", run); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/test_tsk_fs_attr.rs: -------------------------------------------------------------------------------- 1 | extern crate tsk; 2 | use tsk::tsk_img::TskImg; 3 | 4 | 5 | #[test] 6 | fn test_tsk_wrappers_non_resident_data() { 7 | let source = r"samples/ntfs.raw"; 8 | let tsk_img = TskImg::from_utf8_sing(source) 9 | .expect("Could not create TskImg"); 10 | println!("{:?}", tsk_img); 11 | 12 | let tsk_fs = tsk_img.get_fs_from_offset(0) 13 | .expect("Could not open TskFs at offset 0"); 14 | 15 | let mft_fh = tsk_fs.file_open_meta(0) 16 | .expect("Could not open $MFT"); 17 | 18 | // The default will be the first data attribute 19 | let default_attr = mft_fh.get_attr() 20 | .expect("Could get default attribute."); 21 | 22 | // Get the non resident data structure for the defaul attribute 23 | let nrd = default_attr.get_non_resident_data() 24 | .expect("Could not get non resident data struct."); 25 | println!("{:?}", nrd); 26 | 27 | // get the first run 28 | let run = nrd.run(); 29 | println!("{:?}", run); 30 | 31 | // iterate each non-resident data run 32 | for run in nrd.iter() { 33 | // debug print the run 34 | println!("{:?}", run); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/test_tsk_part_handle.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::io::{Read, Seek, SeekFrom}; 3 | use tsk::tsk_img::TskImg; 4 | 5 | 6 | #[test] 7 | fn test_tsk_wrappers() { 8 | let source = PathBuf::from(format!("{}/samples/mbr.raw", env!("CARGO_MANIFEST_DIR"))); 9 | 10 | let tsk_img = TskImg::from_utf8_sing(source) 11 | .expect("Could not create TskImg"); 12 | 13 | let tsk_vs = tsk_img.get_vs_from_offset(0) 14 | .expect("Could not open TskVs at offset 0"); 15 | println!("{:?}", tsk_vs); 16 | 17 | let tsk_part = tsk_vs.get_partition_at_index(2) 18 | .expect("Could not get partion at index 2"); 19 | println!("{:?}", tsk_part); 20 | 21 | let mut tsk_part_handle = tsk_part.get_handle(); 22 | tsk_part_handle.seek(SeekFrom::Start(22528)) 23 | .expect("Error seeking to offset."); 24 | 25 | let mut buffer = vec![0_u8; 116]; 26 | tsk_part_handle.read_exact(&mut buffer) 27 | .expect("Error reading bytes."); 28 | let content = String::from_utf8_lossy(&buffer); 29 | println!("{}", content); 30 | 31 | let e = r#"place,user,password 32 | bank,joesmith,superrich 33 | alarm system,-,1234 34 | treasure chest,-,1111 35 | uber secret laire,admin,admin 36 | "#; 37 | 38 | assert_eq!(content, e) 39 | } -------------------------------------------------------------------------------- /tests/test_tsk_wrappers.rs: -------------------------------------------------------------------------------- 1 | extern crate tsk; 2 | use std::path::PathBuf; 3 | use std::io::{Read, Write, Seek, SeekFrom}; 4 | use tsk::tsk_img::TskImg; 5 | use tsk::tsk_fs_dir::TskFsDir; 6 | use tsk::tsk_fs_attr::TskFsAttr; 7 | use tsk::bindings; 8 | use std::fs::File; 9 | 10 | 11 | #[test] 12 | fn test_tsk_wrappers_dir() { 13 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 14 | let tsk_img = TskImg::from_utf8_sing(source) 15 | .expect("Could not create TskImg"); 16 | println!("{:?}", tsk_img); 17 | 18 | let tsk_fs = tsk_img.get_fs_from_offset(0) 19 | .expect("Could not open TskFs at offset 0"); 20 | 21 | let root_fh = TskFsDir::from_meta(&tsk_fs, 5) 22 | .expect("Could not open root folder"); 23 | println!("{:?}", root_fh); 24 | 25 | for name_attr in root_fh.get_name_iter() { 26 | println!("{:?}", name_attr); 27 | } 28 | 29 | let tsk_fs_name = root_fh.get_name(0) 30 | .expect("Error getting name at index 0"); 31 | println!("{:?}", tsk_fs_name); 32 | } 33 | 34 | 35 | #[test] 36 | fn test_tsk_iterate_root() { 37 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 38 | let tsk_img = TskImg::from_utf8_sing(source) 39 | .expect("Could not create TskImg"); 40 | println!("{:?}", tsk_img); 41 | 42 | let tsk_fs = tsk_img.get_fs_from_offset(0) 43 | .expect("Could not open TskFs at offset 0"); 44 | 45 | let file_iter = tsk_fs.iter_file_names() 46 | .expect("Could not get FsNameIter"); 47 | 48 | let mut file_count = 0; 49 | for (path, f) in file_iter { 50 | if file_count == 512 { 51 | break; 52 | } 53 | 54 | if let Some(name) = f.name() { 55 | println!("{}/{}", path, name); 56 | } 57 | 58 | file_count += 1; 59 | } 60 | } 61 | 62 | 63 | #[test] 64 | fn test_tsk_wrappers() { 65 | let source = PathBuf::from(format!("{}/samples/mbr.raw", env!("CARGO_MANIFEST_DIR"))); 66 | let tsk_img = TskImg::from_utf8_sing(source) 67 | .expect("Could not create TskImg"); 68 | 69 | let tsk_vs = tsk_img.get_vs_from_offset(0) 70 | .expect("Could not open TskVs at offset 0"); 71 | println!("{:?}", tsk_vs); 72 | 73 | let part_iter = tsk_vs.get_partition_iter() 74 | .expect("Could not get partition iterator for TskVs"); 75 | for vs_part in part_iter { 76 | println!("{:?}", vs_part); 77 | } 78 | 79 | let tsk_vs_part = tsk_vs.get_partition_at_index(0) 80 | .expect("Could not open TskVsPart at offset 0"); 81 | println!("{:?}", tsk_vs_part); 82 | 83 | let iter = tsk_vs_part.into_iter(); 84 | for vs_part in iter { 85 | println!("{:?}", vs_part); 86 | } 87 | drop(tsk_vs); 88 | 89 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 90 | let tsk_img = TskImg::from_utf8_sing(source) 91 | .expect("Could not create TskImg"); 92 | println!("{:?}", tsk_img); 93 | 94 | let tsk_fs = tsk_img.get_fs_from_offset(0) 95 | .expect("Could not open TskFs at offset 0"); 96 | println!("{:?}", tsk_fs); 97 | 98 | let root_fh = tsk_fs.file_open_meta(5) 99 | .expect("Could not open root folder"); 100 | println!("{:?}", root_fh); 101 | let root_fh_meta = root_fh.get_meta().unwrap(); 102 | assert_eq!(false, root_fh_meta.is_unallocated()); 103 | assert_eq!(5, root_fh_meta.addr()); 104 | 105 | let mut tsk_attr = root_fh.get_attr_at_index(0) 106 | .expect("Unable to get attribute at index 0 for root node."); 107 | let mut buffer = vec![0; tsk_attr.size() as usize]; 108 | let _bytes_read = tsk_attr.read(&mut buffer) 109 | .expect("Error reading attribute!"); 110 | println!("Attribute 0 -> {:02x?}", buffer); 111 | 112 | drop(root_fh); 113 | 114 | let mft_fh = tsk_fs.file_open("/$MFT") 115 | .expect("Could not open $MFT"); 116 | println!("{:?}", mft_fh); 117 | 118 | let mft_fh = tsk_fs.file_open_meta(0) 119 | .expect("Could not open $MFT"); 120 | println!("{:?}", mft_fh); 121 | let mft_fh_meta = mft_fh.get_meta().unwrap(); 122 | assert_eq!(false, mft_fh_meta.is_unallocated()); 123 | 124 | let mut tsk_attr = mft_fh.get_attr() 125 | .expect("Unable to get default attribute."); 126 | let mut buffer = vec![0; 1024]; 127 | let _bytes_read = tsk_attr.read(&mut buffer) 128 | .expect("Error reading attribute!"); 129 | println!("MFT default attribute -> {:02x?}", buffer); 130 | 131 | 132 | let attr_0 = mft_fh.get_attr_at_index(0) 133 | .expect("Could not get attribute 0 for $MFT"); 134 | println!("{:?}", attr_0); 135 | 136 | let attr_99 = mft_fh.get_attr_at_index(99); 137 | assert_eq!(attr_99.is_err(), true); 138 | println!("{:?}", attr_99); 139 | 140 | let attr_iter = mft_fh.get_attr_iter() 141 | .expect("Could not get attribute iterator for $MFT"); 142 | 143 | for attr in attr_iter { 144 | println!("{:?}", attr); 145 | } 146 | } 147 | 148 | 149 | #[test] 150 | fn test_tsk_attr_read_seek() { 151 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 152 | let tsk_img = TskImg::from_utf8_sing(source) 153 | .expect("Could not create TskImg"); 154 | println!("{:?}", tsk_img); 155 | 156 | let tsk_fs = tsk_img.get_fs_from_offset(0) 157 | .expect("Could not open TskFs at offset 0"); 158 | println!("{:?}", tsk_fs); 159 | 160 | let mft_fh = tsk_fs.file_open_meta(0) 161 | .expect("Could not open $MFT"); 162 | println!("{:?}", mft_fh); 163 | let mft_fh_meta = mft_fh.get_meta().unwrap(); 164 | assert_eq!(false, mft_fh_meta.is_unallocated()); 165 | 166 | let mut tsk_attr = mft_fh.get_attr() 167 | .expect("Unable to get default attribute."); 168 | let mut buffer = vec![0; 1024]; 169 | let _pos = tsk_attr.seek(SeekFrom::Start(1024)) 170 | .expect("Error seeking to pos 1024"); 171 | let _bytes_read = tsk_attr.read(&mut buffer) 172 | .expect("Error reading attribute!"); 173 | println!("MFT record at offset 1024 -> {:02x?}", buffer); 174 | } 175 | 176 | 177 | #[test] 178 | fn test_tsk_file_handle_read_seek() { 179 | // Generate the TSK path to fetch 180 | let tsk_file_path_str = "/$MFT"; 181 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 182 | let tsk_img = TskImg::from_utf8_sing(source) 183 | .expect("Could not create TskImg"); 184 | println!("{:?}", tsk_img); 185 | 186 | let tsk_fs = tsk_img.get_fs_from_offset(0) 187 | .expect("Could not open TskFs at offset 0"); 188 | println!("{:?}", tsk_fs); 189 | 190 | println!("Opening '{}'...", tsk_file_path_str); 191 | let test_file = tsk_fs.file_open(&tsk_file_path_str) 192 | .expect(&format!("Could not open '{}'", tsk_file_path_str)); 193 | println!("{:?}", test_file); 194 | 195 | // Get the default attribute 196 | let attr = TskFsAttr::from_default(&test_file).unwrap(); 197 | println!("{:?}", attr); 198 | 199 | // Create a TskFsFileHandle from TskFsFile 200 | let mut test_file_handle = test_file.get_file_handle( 201 | attr, 202 | bindings::TSK_FS_FILE_READ_FLAG_ENUM::TSK_FS_FILE_READ_FLAG_NONE 203 | ).expect("Unable to get default attribute."); 204 | 205 | let mut buf = [0; 4]; 206 | 207 | // Read first byte 208 | test_file_handle.read(&mut buf).unwrap(); 209 | assert_eq!(&buf, b"FILE"); 210 | println!("{:?}", buf); 211 | 212 | // Seek to the last byte 213 | test_file_handle.seek(SeekFrom::End(-4)).unwrap(); 214 | 215 | // Read last byte 216 | test_file_handle.read(&mut buf).unwrap(); 217 | assert_eq!(&buf, b"\x00\x00\x03\x00"); 218 | println!("{:?}", buf); 219 | } 220 | 221 | 222 | #[test] 223 | fn test_tsk_fs_meta(){ 224 | let tsk_file_path_str = "/$MFT"; 225 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 226 | let tsk_img = TskImg::from_utf8_sing(source) 227 | .expect("Could not create TskImg"); 228 | 229 | let tsk_fs = tsk_img.get_fs_from_offset(0) 230 | .expect("Could not open TskFs at offset 0"); 231 | 232 | println!("Opening '{}'...", tsk_file_path_str); 233 | let root_fh = tsk_fs.file_open(&tsk_file_path_str) 234 | .expect("Could not open test_file"); 235 | 236 | println!("Reading file metadata..."); 237 | println!("{:?}",root_fh.get_meta()); 238 | } 239 | 240 | 241 | #[test] 242 | fn test_copy_file(){ 243 | let tsk_file_path_str = "/$MFT"; 244 | let source = PathBuf::from(format!("{}/samples/ntfs.raw", env!("CARGO_MANIFEST_DIR"))); 245 | let tsk_img = TskImg::from_utf8_sing(source) 246 | .expect("Could not create TskImg"); 247 | 248 | let tsk_fs = tsk_img.get_fs_from_offset(0) 249 | .expect("Could not open TskFs at offset 0"); 250 | 251 | println!("Opening '{}'...", tsk_file_path_str); 252 | let test_file = tsk_fs.file_open(&tsk_file_path_str) 253 | .expect("Could not open test_file"); 254 | 255 | // Get the default attribute 256 | let attr = TskFsAttr::from_default(&test_file).unwrap(); 257 | println!("{:?}", attr); 258 | 259 | // Create a TskFsFileHandle from TskFsFile 260 | let mut test_file_handle = test_file.get_file_handle( 261 | attr, 262 | bindings::TSK_FS_FILE_READ_FLAG_ENUM::TSK_FS_FILE_READ_FLAG_NONE 263 | ).expect("Unable to get default attribute."); 264 | 265 | // Specify a buffer of 10 bytes 266 | let mut buf = [0;1024]; 267 | let outfile_path = "test_output"; 268 | println!("Writing to '{}'...", outfile_path); 269 | let mut outfile = File::create(outfile_path).unwrap(); 270 | loop { 271 | let bytes_read = test_file_handle.read(&mut buf).unwrap(); 272 | if bytes_read == 0 {break;} 273 | let bw = outfile.write(&buf[..bytes_read]).unwrap(); 274 | println!("Wrote '{}' bytes", bw); 275 | } 276 | } -------------------------------------------------------------------------------- /wrapper.h: -------------------------------------------------------------------------------- 1 | #include --------------------------------------------------------------------------------