Skip to content

Commit

Permalink
Automatically crack hashes from a Windows drive 🥳
Browse files Browse the repository at this point in the history
- add the stealdows subcommand
	- retrieve hashes from SAM and SYSTEM registry files
	- automatically find the files on a mounted windows disk
	- option to crack the hashes found using rainbow tables
- set up CI (take 1)
  • Loading branch information
truelossless committed Jul 26, 2022
1 parent 11dd25b commit 8a4ad34
Show file tree
Hide file tree
Showing 10 changed files with 810 additions and 43 deletions.
100 changes: 100 additions & 0 deletions .github/workfows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
on: push

jobs:
build_linux:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v3

- name: Build Dockerfile
run: docker build -t rust-cuda .

- name: Start Docker container
run: docker run rust-cuda -v $PWD:/root/rust-cuda --entrypoint /bin/bash --name builder

- name: Build executable
run: docker exec -w /root/rust-cuda/cli builder cargo build --release

- name: Test
run: |
docker exec -w /root/rust-cuda/commons builder cargo test --release
docker exec -w /root/rust-cuda/cpu builder cargo test --release
docker exec -w /root/rust-cuda/cli builder cargo test --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: cug-linux
path: cli/target/release/cugparck-cli

build_windows:
runs-on: windows-latest
steps:
- name: Clone repository
uses: actions/checkout@v3

- name: Build executable
run: |
cd cli
cargo build --release
- name: Run tests
run: |
cd commons
cargo test --release --no-fail-fast
cd ../cpu
cargo test --release --no-fail-fast
cd ../cli
cargo test --release --no-fail-fast
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: cug-windows
path: cli/target/release/cugparck-cli.exe

upload:
needs: [build_windows, build_linux]
runs-on: ubuntu-latest
steps:
- name: Download Windows artifact
uses: actions/download-artifact@v3
with:
name: cug-windows
- name: Download Linux artifact
uses: actions/download-artifact@v3
with:
name: cug-linux
- name: Get commit infos
id: commit
run: echo "::set-output name=hash::$(echo ${GITHUB_SHA} | cut -c1-8)"
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.commit.outputs.hash }}
release_name: Release ${{ steps.commit.outputs.hash }}
body: This is an automated build for commit ${{ steps.commit.outputs.hash }}.
draft: false
prerelease: true
- name: Upload Linux binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cugparck-cli
asset_name: cug-linux-${{ steps.commit.outputs.hash }}
asset_content_type: application/octet-stream
- name: Upload Windows binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cugparck-cli.exe
asset_name: cug-windows-${{ steps.commit.outputs.hash }}.exe
asset_content_type: application/zip
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ In particular, it implements the following features that may not be present in o
- Multithreaded CPU attack
- NTLM, MD4, MD5, SHA-1, SHA-2, SHA-3 support

- Windows accounts dumping and cracking
- Dump the NTLM hashes from Windows accounts
- Supports legacy RC4 encryption as well as AES encryption (Windows >= 1607)
- Automatically try to crack the dumped hashes

## Installation

No executable is currently provided but I will work on getting a CI pipeline running for at least the Linux and Windows builds.
Expand All @@ -39,7 +44,7 @@ Compiling from source the CLI or library can be tough because a valid CUDA insta

On Windows if you're kind enough to the NVIDIA and LLVM gods no further steps are needed and a `cargo build --release` should do the trick.

On Linux your best bet is using Docker to avoid incompabilities between CUDA/GCC/LLVM toolchains. [Follow the instructions here to get started](https://github.com/Rust-GPU/Rust-CUDA/blob/master/guide/src/guide/getting_started.md#docker).
On Linux your best bet is to use Docker to avoid incompabilities between the CUDA/GCC/LLVM toolchains. [Follow the instructions here to get started](https://github.com/Rust-GPU/Rust-CUDA/blob/master/guide/src/guide/getting_started.md#docker).

Note that a specific nightly Rust toolchain is required. It will be downloaded automatically thanks to the `rust-toolchain` file.

Expand All @@ -55,10 +60,10 @@ Besides that, here is a list of things that could be improved/implemented/tested
- AMD support (OpenCL? wgpu?)
- Using the CPU cores during the GPU calculations
- Better error checks and messages
- Better memory management, such as more precise memory predictions for the device's memory and host's RAM. This would allow to schedule better batches on the GPU, and make sure that no memory allocation fails.
- Support for external memory (SSD and/or HDD). I think [6] should be a great startpoint (pun intended).
- Support for distribution. Maybe MPI can be used? [3] Section 4 gives pointers to implement a distributed architecture with filtration.
- Using a `HashSet` instead of a sorted array (This would use more RAM but put less strain on the CPU).
- Better memory management, such as more precise memory predictions for the device's memory and host's RAM. This would allow to schedule better batches on the GPU, and make sure that no memory allocation fails
- Support for external memory (SSD and/or HDD). I think [6] should be a great startpoint (pun intended)
- Support for distribution. Maybe MPI can be used? [3] Section 4 gives pointers to implement a distributed architecture with filtration
- Using a `HashSet` instead of a sorted array (This would use more RAM but put less strain on the CPU)
- Smaller RAM footprint
- Implement checkpoints to speed up the attack phase as described in [7]

Expand Down
15 changes: 10 additions & 5 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
[package]
name = "cugparck-cli"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

[profile.release]
debug = true

[dependencies]
cugparck-commons = { path = "../commons" }
cugparck-cpu = { path = "../cpu" }
color-eyre = "0.5"
sysinfo = "0.24.7"
hex = "0.4.3"
clap = { version = "3.2.8", features = ["derive"] }
indicatif = "0.16.2"
indicatif = "0.16.2"
comfy-table = "5.0.0"
nt-hive = { git = "https://github.com/truelossless/nt-hive" }
md-5 = "0.10.1"
rc4 = "0.1.0"
des = "0.8.1"
aes = "0.8.1"
cbc = "0.1.2"
12 changes: 6 additions & 6 deletions cli/src/attack.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use color_eyre::{eyre::bail, Result};
use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result};
use cugparck_cpu::{CompressedTable, RainbowTableStorage, SimpleTable, TableCluster};

use crate::{load_tables_from_dir, Attack};

pub fn attack(atk: Attack) -> Result<()> {
let (mmaps, is_compressed) = load_tables_from_dir(&atk.dir)?;
pub fn attack(args: Attack) -> Result<()> {
let (mmaps, is_compressed) = load_tables_from_dir(&args.dir)?;

let digest = hex::decode(atk.digest)
let digest = hex::decode(args.digest)
.unwrap()
.as_slice()
.try_into()
Expand All @@ -29,9 +29,9 @@ pub fn attack(atk: Attack) -> Result<()> {
};

if let Some(password) = search {
println!("{password}");
println!("{}", password.green());
} else {
eprintln!("No password found for the given digest");
eprintln!("{}", "No password found for the given digest".red());
}

Ok(())
Expand Down
8 changes: 4 additions & 4 deletions cli/src/compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ use cugparck_cpu::{
CompressedTable, Deserialize, Infallible, RainbowTable, RainbowTableStorage, SimpleTable,
};

pub fn compress(comp: Compress) -> Result<()> {
create_dir_to_store_tables(&comp.out_dir)?;
pub fn compress(args: Compress) -> Result<()> {
create_dir_to_store_tables(&args.out_dir)?;

let (mmaps, is_compressed) = load_tables_from_dir(&comp.in_dir)?;
let (mmaps, is_compressed) = load_tables_from_dir(&args.in_dir)?;

if is_compressed {
bail!("The tables are already compressed");
}

for mmap in mmaps {
let ar = SimpleTable::load(&mmap)?;
let path = comp.out_dir.join(format!("table_{}.rtcde", ar.ctx().tn));
let path = args.out_dir.join(format!("table_{}.rtcde", ar.ctx().tn));

let table: SimpleTable = ar
.deserialize(&mut Infallible)
Expand Down
8 changes: 4 additions & 4 deletions cli/src/decompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ use cugparck_cpu::{
CompressedTable, Deserialize, Infallible, RainbowTable, RainbowTableStorage, SimpleTable,
};

pub fn decompress(decomp: Decompress) -> Result<()> {
create_dir_to_store_tables(&decomp.out_dir)?;
pub fn decompress(args: Decompress) -> Result<()> {
create_dir_to_store_tables(&args.out_dir)?;

let (mmaps, is_compressed) = load_tables_from_dir(&decomp.in_dir)?;
let (mmaps, is_compressed) = load_tables_from_dir(&args.in_dir)?;

if !is_compressed {
bail!("The tables are already decompressed");
}

for mmap in mmaps {
let ar = CompressedTable::load(&mmap)?;
let path = decomp.out_dir.join(format!("table_{}.rt", ar.ctx().tn));
let path = args.out_dir.join(format!("table_{}.rt", ar.ctx().tn));

let table: CompressedTable = ar
.deserialize(&mut Infallible)
Expand Down
26 changes: 13 additions & 13 deletions cli/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ use indicatif::{ProgressBar, ProgressStyle};

use crate::{create_dir_to_store_tables, Generate};

pub fn generate(gen: Generate) -> Result<()> {
create_dir_to_store_tables(&gen.dir)?;
pub fn generate(args: Generate) -> Result<()> {
create_dir_to_store_tables(&args.dir)?;

let ext = if gen.compress { "rtcde" } else { "rt" };
let ext = if args.compress { "rtcde" } else { "rt" };

let ctx_builder = RainbowTableCtxBuilder::new()
.hash(gen.hash_type.into())
.alpha(gen.alpha)
.startpoints(gen.startpoints)
.chain_length(gen.chain_length as usize)
.charset(gen.charset.as_bytes())
.max_password_length(gen.max_password_length);
.hash(args.hash_type.into())
.alpha(args.alpha)
.startpoints(args.startpoints)
.chain_length(args.chain_length as usize)
.charset(args.charset.as_bytes())
.max_password_length(args.max_password_length);

for i in gen.start_from..gen.start_from + gen.table_count {
for i in args.start_from..args.start_from + args.table_count {
let ctx = ctx_builder.table_number(i).build()?;
let table_path = gen.dir.clone().join(format!("table_{i}.{ext}"));
let table_path = args.dir.clone().join(format!("table_{i}.{ext}"));

let table_handle = if gen.cpu {
let table_handle = if args.cpu {
SimpleTable::new_cpu_nonblocking(ctx)
} else {
SimpleTable::new_gpu_nonblocking(ctx)
Expand Down Expand Up @@ -62,7 +62,7 @@ pub fn generate(gen: Generate) -> Result<()> {
let simple_table = table_handle.join()?;

let disk_error = "Unable to store the generated rainbow table to the disk";
if gen.compress {
if args.compress {
simple_table
.into_rainbow_table::<CompressedTable>()
.store(&table_path)
Expand Down
43 changes: 38 additions & 5 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod attack;
mod compress;
mod decompress;
mod generate;
mod stealdows;

use std::{
fs::{self, File},
Expand All @@ -22,6 +23,7 @@ use attack::attack;
use compress::compress;
use decompress::decompress;
use generate::generate;
use stealdows::stealdows;

/// All the hash types supported.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum)]
Expand Down Expand Up @@ -59,7 +61,7 @@ impl From<HashTypeArg> for HashType {
}
}

/// Rainbow table application allowing attacks and GPU-accelerated table generation.
/// Cugparck is a modern rainbow table library & CLI.
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
Expand All @@ -73,6 +75,7 @@ enum Commands {
Generate(Generate),
Compress(Compress),
Decompress(Decompress),
Stealdows(Stealdows),
}

/// Find the password producing a certain hash digest.
Expand All @@ -88,6 +91,7 @@ pub struct Attack {
}

/// Compress a set of rainbow tables using compressed delta encoding.
///
/// Tables are smaller on the disk but slower to search.
#[derive(Args)]
pub struct Compress {
Expand All @@ -101,6 +105,7 @@ pub struct Compress {
}

/// Decompress a set of compressed rainbow tables.
///
/// Decompressed tables are bigger on the disk but faster to search.
#[derive(Args)]
pub struct Decompress {
Expand Down Expand Up @@ -171,6 +176,33 @@ pub struct Generate {
startpoints: Option<usize>,
}

/// Dump and crack NTLM hashes from Windows accounts.
///
/// Note that this cannot be used on a Windows machine to dump the hashes of the same Windows,
/// because the required files are locked by the OS.
#[derive(Args)]
pub struct Stealdows {
/// Search for a specific user.
/// You can specify several users by using multiple times this flag.
#[clap(short, long, value_parser)]
user: Vec<String>,

/// Attempts to crack the hashes dumped using the rainbow table(s) provided as an argument.
/// The hash type of the table(s) must be NTLM.
#[clap(long, value_parser, value_name = "TABLES_DIR")]
crack: Option<PathBuf>,

/// The path to the SAM registry file. If not provided an attempt will be made to find it automatically.
/// This path is usually `C:\Windows\System32\config\SAM`.
#[clap(long, value_parser, requires = "system")]
sam: Option<PathBuf>,

/// The path to the SYSTEM registry file. If not provided an attempt will be made to find it automatically.
/// This path is usually `C:\Windows\System32\config\SYSTEM`.
#[clap(long, value_parser, requires = "sam")]
system: Option<PathBuf>,
}

/// Checks if the charset is made of ASCII characters.
fn check_charset(charset: &str) -> Result<String> {
if !charset.is_ascii() {
Expand Down Expand Up @@ -202,10 +234,11 @@ fn main() -> Result<()> {
let cli = Cli::parse();

match cli.commands {
Commands::Attack(atk) => attack(atk)?,
Commands::Generate(gen) => generate(gen)?,
Commands::Compress(comp) => compress(comp)?,
Commands::Decompress(decomp) => decompress(decomp)?,
Commands::Attack(args) => attack(args)?,
Commands::Generate(args) => generate(args)?,
Commands::Compress(args) => compress(args)?,
Commands::Decompress(args) => decompress(args)?,
Commands::Stealdows(args) => stealdows(args)?,
}

Ok(())
Expand Down
Loading

0 comments on commit 8a4ad34

Please sign in to comment.