├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── pack2-util ├── Cargo.toml └── src │ └── lib.rs └── src ├── cgrams.rs ├── filtermask.rs ├── main.rs ├── statsgen.rs └── unhex.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ahash" 5 | version = "0.3.8" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.11.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 14 | dependencies = [ 15 | "winapi", 16 | ] 17 | 18 | [[package]] 19 | name = "atty" 20 | version = "0.2.14" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 23 | dependencies = [ 24 | "hermit-abi", 25 | "libc", 26 | "winapi", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.0.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 34 | 35 | [[package]] 36 | name = "bitflags" 37 | version = "1.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 40 | 41 | [[package]] 42 | name = "bstr" 43 | version = "0.2.13" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" 46 | dependencies = [ 47 | "lazy_static", 48 | "memchr", 49 | "regex-automata", 50 | ] 51 | 52 | [[package]] 53 | name = "byteorder" 54 | version = "1.3.4" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 57 | 58 | [[package]] 59 | name = "clap" 60 | version = "2.33.3" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 63 | dependencies = [ 64 | "ansi_term", 65 | "atty", 66 | "bitflags", 67 | "strsim", 68 | "textwrap", 69 | "unicode-width", 70 | "vec_map", 71 | ] 72 | 73 | [[package]] 74 | name = "faster-hex" 75 | version = "0.4.1" 76 | source = "git+https://github.com/ggriffiniii/faster-hex#2b165d4496573c3f92547b52225016dcf8a59952" 77 | 78 | [[package]] 79 | name = "hashbrown" 80 | version = "0.8.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" 83 | dependencies = [ 84 | "ahash", 85 | "autocfg", 86 | ] 87 | 88 | [[package]] 89 | name = "heck" 90 | version = "0.3.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 93 | dependencies = [ 94 | "unicode-segmentation", 95 | ] 96 | 97 | [[package]] 98 | name = "hermit-abi" 99 | version = "0.1.15" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 102 | dependencies = [ 103 | "libc", 104 | ] 105 | 106 | [[package]] 107 | name = "lazy_static" 108 | version = "1.4.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 111 | 112 | [[package]] 113 | name = "libc" 114 | version = "0.2.77" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" 117 | 118 | [[package]] 119 | name = "memchr" 120 | version = "2.3.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 123 | 124 | [[package]] 125 | name = "pack2" 126 | version = "0.1.0" 127 | dependencies = [ 128 | "bstr", 129 | "faster-hex", 130 | "hashbrown", 131 | "pack2-util", 132 | "structopt", 133 | ] 134 | 135 | [[package]] 136 | name = "pack2-util" 137 | version = "0.1.0" 138 | dependencies = [ 139 | "faster-hex", 140 | ] 141 | 142 | [[package]] 143 | name = "proc-macro-error" 144 | version = "1.0.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 147 | dependencies = [ 148 | "proc-macro-error-attr", 149 | "proc-macro2", 150 | "quote", 151 | "syn", 152 | "version_check", 153 | ] 154 | 155 | [[package]] 156 | name = "proc-macro-error-attr" 157 | version = "1.0.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 160 | dependencies = [ 161 | "proc-macro2", 162 | "quote", 163 | "version_check", 164 | ] 165 | 166 | [[package]] 167 | name = "proc-macro2" 168 | version = "1.0.21" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" 171 | dependencies = [ 172 | "unicode-xid", 173 | ] 174 | 175 | [[package]] 176 | name = "quote" 177 | version = "1.0.7" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 180 | dependencies = [ 181 | "proc-macro2", 182 | ] 183 | 184 | [[package]] 185 | name = "regex-automata" 186 | version = "0.1.9" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" 189 | dependencies = [ 190 | "byteorder", 191 | ] 192 | 193 | [[package]] 194 | name = "strsim" 195 | version = "0.8.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 198 | 199 | [[package]] 200 | name = "structopt" 201 | version = "0.3.17" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5" 204 | dependencies = [ 205 | "clap", 206 | "lazy_static", 207 | "structopt-derive", 208 | ] 209 | 210 | [[package]] 211 | name = "structopt-derive" 212 | version = "0.4.10" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f" 215 | dependencies = [ 216 | "heck", 217 | "proc-macro-error", 218 | "proc-macro2", 219 | "quote", 220 | "syn", 221 | ] 222 | 223 | [[package]] 224 | name = "syn" 225 | version = "1.0.41" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" 228 | dependencies = [ 229 | "proc-macro2", 230 | "quote", 231 | "unicode-xid", 232 | ] 233 | 234 | [[package]] 235 | name = "textwrap" 236 | version = "0.11.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 239 | dependencies = [ 240 | "unicode-width", 241 | ] 242 | 243 | [[package]] 244 | name = "unicode-segmentation" 245 | version = "1.6.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 248 | 249 | [[package]] 250 | name = "unicode-width" 251 | version = "0.1.8" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 254 | 255 | [[package]] 256 | name = "unicode-xid" 257 | version = "0.2.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 260 | 261 | [[package]] 262 | name = "vec_map" 263 | version = "0.8.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 266 | 267 | [[package]] 268 | name = "version_check" 269 | version = "0.9.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 272 | 273 | [[package]] 274 | name = "winapi" 275 | version = "0.3.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 278 | dependencies = [ 279 | "winapi-i686-pc-windows-gnu", 280 | "winapi-x86_64-pc-windows-gnu", 281 | ] 282 | 283 | [[package]] 284 | name = "winapi-i686-pc-windows-gnu" 285 | version = "0.4.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 288 | 289 | [[package]] 290 | name = "winapi-x86_64-pc-windows-gnu" 291 | version = "0.4.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 294 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pack2" 3 | version = "0.1.0" 4 | authors = ["Michael Sprecher "] 5 | edition = "2018" 6 | 7 | 8 | [workspace] 9 | members = [ 10 | ".", 11 | "pack2-util", 12 | ] 13 | 14 | [dependencies] 15 | pack2-util = { path = "pack2-util", version="0.1.0" } 16 | 17 | bstr = "0.2" 18 | hashbrown = "0.8" 19 | structopt = "0.3" 20 | 21 | faster-hex = { git = "https://github.com/ggriffiniii/faster-hex", branch = "master" } 22 | 23 | [profile.release] 24 | lto = true 25 | opt-level = 3 26 | codegen-units = 1 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Sprecher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pack2 2 | 3 | pack2 is meant to be a replacement for iphelix's [PACK](https://github.com/iphelix). 4 | This is a work in progress. Not all features are available and while being 5 | similiar some will differ slightly. 6 | 7 | ## note 8 | 9 | It's in a totally unfinished state but I wanted to have it public before [CMIYC 2020](https://contest-2020.korelogic.com) 10 | 11 | # requirements 12 | 13 | You need to have Rust installed. The recommended way to do so is using [rustup](https://rustup.rs). 14 | 15 | # building 16 | 17 | ``` 18 | $ cargo build --release 19 | ``` 20 | Your binary will be located at `target/release/pack2` 21 | To squeeze out more optimizations we use some compiler flags in the release profile. 22 | This results in a longer compile time in favor of running a bit faster. 23 | 24 | # design principles 25 | Unless stated otherwise all tools adhere the following rules: 26 | 27 | - If no input file is specified it reads from `stdin` 28 | - If no output file is specified it writes to `stdout` 29 | - Infos (e.g. stats) are always written to `stderr` 30 | - Input lines in the $HEX[] format are always decoded before any further processing 31 | - If at least one character of an output line is outside of \x20 - \x7e it will be encoded in the $HEX[] format 32 | 33 | # usage 34 | ## filtermask 35 | __Note:__ This will fail horribly in many cases. See [#6](https://github.com/hops/pack2/issues/6) for more details. 36 | Filters the input by a given mask, only writing the lines that match the mask. 37 | ``` 38 | $ cat input.txt 39 | test 40 | foobar 41 | Password 42 | 43 | $ pack2 filtermask ?l?l?l?l input.txt 44 | test 45 | ``` 46 | 47 | ## rulegen 48 | There were plans to integrate this as well but since [rulesfinder](https://github.com/synacktiv/rulesfinder) 49 | (also written in Rust) got released there's really no point reinventing the wheel. 50 | 51 | ## statsgen 52 | Generates statistics of a given wordlist. 53 | 54 | ``` 55 | $ pack2 statsgen < input.txt 56 | [+] Analyzed 3 / 3 passwords. 57 | [*] Length distribution: (min: 4 max: 8) 58 | [+] 4: 33.33% (1) 59 | [+] 6: 33.33% (1) 60 | [+] 8: 33.33% (1) 61 | 62 | [*] Charset distribution: count min max 63 | [+] loweralpha: 66.67% 2 4 6 64 | [+] mixedalpha: 33.33% 1 8 8 65 | 66 | [*] Simple masks distribution: count min max 67 | [+] string: 100.00% 3 4 8 68 | 69 | [*] Masks (top 25): 70 | [+] ?l?l?l?l: 33.33% (1) 71 | [+] ?u?l?l?l?l?l?l?l: 33.33% (1) 72 | [+] ?l?l?l?l?l?l: 33.33% (1) 73 | ?l?l?l?l 33.3333 1 74 | ?u?l?l?l?l?l?l?l 33.3333 1 75 | ?l?l?l?l?l?l 33.3333 1 76 | ``` 77 | 78 | You can also provide the `-o` flag to specify the output file. 79 | Type `pack2 help statsgen` to see all options. 80 | 81 | ## unhex 82 | Decodes and writes lines using the $HEX[] format. Lines not using said format 83 | are unaffected and written as is. 84 | ``` 85 | $ echo '$HEX[52c3b67363687469]' | pack2 unhex 86 | Röschti 87 | ``` 88 | -------------------------------------------------------------------------------- /pack2-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pack2-util" 3 | version = "0.1.0" 4 | authors = ["hops "] 5 | edition = "2018" 6 | workspace = ".." 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | faster-hex = { git = "https://github.com/ggriffiniii/faster-hex", branch = "master" } 12 | -------------------------------------------------------------------------------- /pack2-util/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, BufRead, BufReader, BufWriter, ErrorKind, Write}; 3 | use std::path::PathBuf; 4 | use std::process; 5 | 6 | use faster_hex::{hex_decode, hex_encode}; 7 | 8 | #[inline(always)] 9 | pub fn contains_uppercase(line: &[u8]) -> bool { 10 | for c in line { 11 | if *c >= 0x41 && *c <= 0x5a { 12 | return true; 13 | } 14 | } 15 | false 16 | } 17 | 18 | #[inline(always)] 19 | pub fn contains_nonprintable(line: &[u8]) -> bool { 20 | for c in line { 21 | if (*c as i8).wrapping_add(1) < 0x21 { 22 | return true; 23 | } 24 | } 25 | false 26 | } 27 | 28 | pub fn encode_hex_if_needed(line: Vec, out: &mut Vec) { 29 | if contains_nonprintable(&line) { 30 | let mut hex_encoded = vec![0u8; line.len() * 2]; 31 | hex_encode(&line, &mut hex_encoded).unwrap(); 32 | 33 | // clear output buffer 34 | out.clear(); 35 | out.extend_from_slice(b"$HEX["); 36 | out.extend_from_slice(&hex_encoded.as_slice()); 37 | out.push(b']'); 38 | } else { 39 | *out = line 40 | } 41 | } 42 | 43 | pub fn decode_hex_if_needed(mut line: Vec) -> (Vec, usize) { 44 | let mut line_len = line.len(); 45 | if line.starts_with(b"$HEX[") && line.ends_with(b"]") { 46 | line_len = (line_len - 6) / 2; 47 | let mut hex_decoded = vec![0; line_len]; 48 | let res = hex_decode(&line[5..line.len() - 1], &mut hex_decoded); 49 | match res { 50 | Ok(_) => line = hex_decoded, 51 | // not valid $HEX encoding, treat as "normal" password 52 | Err(_) => line_len = line.len(), 53 | } 54 | } 55 | (line, line_len) 56 | } 57 | 58 | pub fn get_reader(input: Option) -> Box { 59 | let reader: Box = match input { 60 | None => Box::new(BufReader::new(io::stdin())), 61 | Some(filename) => Box::new(BufReader::new(File::open(filename).unwrap())), 62 | }; 63 | reader 64 | } 65 | 66 | pub fn get_writer(output: Option) -> Box { 67 | let writer: Box = match output { 68 | None => Box::new(BufWriter::new(io::stdout())), 69 | Some(filename) => Box::new(BufWriter::new(File::create(filename).unwrap())), 70 | }; 71 | writer 72 | } 73 | 74 | #[inline(always)] 75 | pub fn mywrite(data: &mut &[u8], writer: &mut Box) { 76 | match io::copy(data, writer) { 77 | Ok(_) => (), 78 | Err(e) => { 79 | if e.kind() == ErrorKind::BrokenPipe { 80 | process::exit(-1); 81 | } else { 82 | eprintln!("{}", e); 83 | process::exit(-1); 84 | } 85 | } 86 | } 87 | } 88 | 89 | pub fn get_bitmap2string() -> Vec<&'static str> { 90 | let bitmap2string: Vec<&str> = vec![ 91 | "invalid", 92 | "loweralpha", 93 | "upperalpha", 94 | "mixedalpha", 95 | "numeric", 96 | "loweralphanum", 97 | "upperalphanum", 98 | "mixedalphanum", 99 | "special", 100 | "loweralphaspecial", 101 | "upperalphaspecial", 102 | "mixedalphaspecial", 103 | "specialnum", 104 | "loweralphaspecialnum", 105 | "upperalphaspecialnum", 106 | "mixedalphaspecialnum", 107 | "binary", 108 | "loweralphabin", 109 | "upperalphabin", 110 | "mixedalphabin", 111 | "numericbin", 112 | "loweralphanumbin", 113 | "upperalphanumbin", 114 | "mixedalphanumbin", 115 | "specialbin", 116 | "loweralphaspecialbin", 117 | "upperalphaspecialbin", 118 | "mixedalphaspecialbin", 119 | "specialnumbin", 120 | "loweralphaspecialnumbin", 121 | "upperalphaspecialnumbin", 122 | "mixedalphaspecialnumbin", 123 | ]; 124 | bitmap2string 125 | } 126 | 127 | pub const CHAR2MASK: [u8; 256] = [ 128 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 129 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 130 | 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 131 | 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 132 | 0x73, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 133 | 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x73, 0x73, 0x73, 0x73, 0x73, 134 | 0x73, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 135 | 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x73, 0x73, 0x73, 0x73, 0x62, 136 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 137 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 138 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 139 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 140 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 141 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 142 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 143 | 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 144 | ]; 145 | 146 | pub const CHAR2BITMAP: [u8; 256] = [ 147 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 148 | 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 4, 149 | 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 150 | 2, 2, 2, 2, 2, 2, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 151 | 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 152 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 153 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 154 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 155 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 156 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 157 | ]; 158 | 159 | pub const CHAR2SMASK: [u8; 256] = [ 160 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 161 | 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 4, 162 | 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 163 | 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 164 | 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 165 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 166 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 167 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 168 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 169 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 170 | ]; 171 | -------------------------------------------------------------------------------- /src/cgrams.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | use std::path::PathBuf; 3 | 4 | use bstr::{io::BufReadExt, ByteSlice}; 5 | use faster_hex::hex_encode; 6 | use hashbrown::HashMap; 7 | use pack2_util::*; 8 | 9 | pub fn gen_c_grams( 10 | input: Option, 11 | output: Option, 12 | sort: bool, 13 | ignore_case: bool, 14 | normalize: bool, 15 | ) { 16 | let reader = get_reader(input); 17 | let mut writer = get_writer(output); 18 | 19 | let mut c_gram = [0u8; 0xffff]; 20 | let mut out_hex = [0u8; 0xffff * 2]; 21 | let mut c_grams: HashMap, u64> = HashMap::new(); 22 | 23 | let lookup_table; 24 | match ignore_case { 25 | false => lookup_table = CHAR2BITMAP, 26 | true => lookup_table = CHAR2SMASK, 27 | }; 28 | 29 | for result in reader.byte_lines() { 30 | let (line, line_len) = decode_hex_if_needed(result.unwrap()); 31 | if line_len > u16::MAX.into() || line_len == 0 { 32 | continue; 33 | } 34 | 35 | let mut last_charset: u8 = 0; 36 | let mut idx: usize = 0; 37 | 38 | for c in line.iter() { 39 | let cur_charset = lookup_table[*c as usize]; 40 | 41 | if last_charset == cur_charset { 42 | c_gram[idx] = *c; 43 | idx += 1; 44 | continue; 45 | } 46 | if last_charset == 0 { 47 | c_gram[idx] = *c; 48 | idx += 1; 49 | last_charset = cur_charset; 50 | continue; 51 | } 52 | if last_charset != cur_charset { 53 | // || idx_total == line_len { 54 | if sort { 55 | *c_grams.entry(c_gram[..idx].to_vec()).or_insert(0) += 1; 56 | if normalize { 57 | let c_gram_vec = c_gram[..idx].to_vec(); 58 | if contains_uppercase(&c_gram_vec) { 59 | let lower = c_gram_vec.to_lowercase(); 60 | *c_grams.entry(lower).or_insert(0) += 1; 61 | } 62 | } 63 | } else { 64 | // $HEX[] encoding necessary 65 | if last_charset == 16 { 66 | let _ = hex_encode(&c_gram[..idx], &mut out_hex); 67 | let out = &*format!("$HEX[{}]\n", &out_hex[..idx * 2].to_str().unwrap()); 68 | mywrite(&mut out.as_bytes(), &mut writer); 69 | } else { 70 | c_gram[idx] = b'\n'; 71 | mywrite(&mut c_gram[..=idx].as_bytes(), &mut writer); 72 | if normalize { 73 | let c_gram_vec = c_gram[..=idx].to_vec(); 74 | if contains_uppercase(&c_gram_vec) { 75 | let lower = c_gram_vec.to_lowercase(); 76 | mywrite(&mut lower.as_bytes(), &mut writer); 77 | } 78 | } 79 | } 80 | } 81 | c_gram[0] = *c; 82 | idx = 1; 83 | last_charset = cur_charset; 84 | } 85 | } 86 | // TODO: This is really ugly as it's duplicate code form what we have in the above loop. 87 | // Either find a better way to construct the loop or move this code into it's own function. 88 | if sort { 89 | *c_grams.entry(c_gram[..idx].to_vec()).or_insert(0) += 1; 90 | if normalize { 91 | let c_gram_vec = c_gram[..idx].to_vec(); 92 | if contains_uppercase(&c_gram_vec) { 93 | let lower = c_gram_vec.to_lowercase(); 94 | *c_grams.entry(lower).or_insert(0) += 1; 95 | } 96 | } 97 | } else { 98 | // $HEX[] encoding necessary 99 | if last_charset == 16 { 100 | let _ = hex_encode(&c_gram[..idx], &mut out_hex); 101 | let out = &*format!("$HEX[{}]\n", &out_hex[..idx * 2].to_str().unwrap()); 102 | mywrite(&mut out.as_bytes(), &mut writer); 103 | } else { 104 | c_gram[idx] = b'\n'; 105 | mywrite(&mut c_gram[..=idx].as_bytes(), &mut writer); 106 | if normalize { 107 | let c_gram_vec = c_gram[..=idx].to_vec(); 108 | if contains_uppercase(&c_gram_vec) { 109 | let lower = c_gram_vec.to_lowercase(); 110 | mywrite(&mut lower.as_bytes(), &mut writer); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | if sort { 118 | let mut out = Vec::new(); 119 | let mut freq_c_grams = Vec::from_iter(c_grams); 120 | freq_c_grams.sort_by(|&(_, a), &(_, b)| b.cmp(&a)); 121 | 122 | eprintln!("\n[*] c-grams (top 25):"); 123 | let mut top = 0; 124 | 125 | for (c_gram, count) in freq_c_grams { 126 | encode_hex_if_needed(c_gram, &mut out); 127 | if top < 25 { 128 | eprintln!("[+] {: >26}: ({})", &out.to_str().unwrap(), count); 129 | top += 1; 130 | } 131 | let out = &*format!("{}\t{}\n", &out.to_str().unwrap(), count); 132 | mywrite(&mut out.as_bytes(), &mut writer); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/filtermask.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use bstr::{io::BufReadExt, ByteSlice}; 4 | use pack2_util::*; 5 | 6 | // TODO: This is a very basic mask "parser" we should make sure it's a valid mask. 7 | // We don't support ?h, ?H or custom masks yet. 8 | fn parse_mask(mask: String) -> (Vec, usize) { 9 | let mask_len = mask.len() / 2; 10 | let mut ret: Vec = Vec::new(); 11 | for c in mask.chars() { 12 | match c { 13 | 'l' => ret.push(1), 14 | 'u' => ret.push(2), 15 | 'd' => ret.push(4), 16 | 's' => ret.push(8), 17 | 'a' => ret.push(15), 18 | 'b' => ret.push(31), 19 | _ => (), 20 | } 21 | } 22 | 23 | (ret, mask_len) 24 | } 25 | 26 | pub fn filtermask(input: Option, output: Option, mask: String) { 27 | let reader = get_reader(input); 28 | let mut writer = get_writer(output); 29 | let (filter, mask_len) = parse_mask(mask); 30 | 31 | let mut processed = 0; 32 | let mut skipped = 0; 33 | 34 | for result in reader.byte_lines() { 35 | let (mut line, line_len) = decode_hex_if_needed(result.unwrap()); 36 | 37 | if line_len != mask_len { 38 | skipped += 1; 39 | continue; 40 | } 41 | 42 | let mut matched = true; 43 | for (idx, byte) in line.iter().enumerate() { 44 | if filter[idx] & CHAR2BITMAP[*byte as usize] == 0 { 45 | matched = false; 46 | break; 47 | } 48 | } 49 | 50 | if matched { 51 | processed += 1; 52 | line.push(b'\n'); 53 | mywrite(&mut line.as_bytes(), &mut writer); 54 | } 55 | } 56 | let total_lines = processed + skipped; 57 | eprintln!( 58 | "wrote {} out of {} lines. Skipped: {}", 59 | processed, total_lines, skipped 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process; 3 | use structopt::StructOpt; 4 | 5 | mod cgrams; 6 | mod filtermask; 7 | mod statsgen; 8 | mod unhex; 9 | 10 | #[derive(StructOpt, Debug)] 11 | #[structopt(name = "pack2")] 12 | pub enum CmdOpts { 13 | /// Generates statistics from a [input] and writes masks to 14 | /// stats are written to stderr 15 | #[structopt(name = "statsgen")] 16 | Statsgen { 17 | /// Input file, stdin if not present 18 | #[structopt(parse(from_os_str))] 19 | input: Option, 20 | /// Output file, stdout if not present 21 | #[structopt(short, long, parse(from_os_str), display_order = 1)] 22 | output: Option, 23 | /// Separator used in mask output [default: TAB] 24 | #[structopt(short, long, display_order = 2)] 25 | separator: Option, 26 | /// Ignore passwords shorter than 27 | #[structopt(long, default_value = "1", display_order = 13)] 28 | min_length: u16, 29 | /// Ignore passwords longer than 30 | #[structopt(long, default_value = "65535", display_order = 14)] 31 | max_length: u16, 32 | }, 33 | 34 | /// Decodes $HEX[] encoded lines 35 | #[structopt(name = "unhex")] 36 | Unhex { 37 | /// Input file, stdin if not present 38 | #[structopt(parse(from_os_str))] 39 | input: Option, 40 | /// Output file, stdout if not present 41 | #[structopt(short, long, parse(from_os_str))] 42 | output: Option, 43 | }, 44 | /// Filters a wordlist by a given mask 45 | #[structopt(name = "filtermask")] 46 | Filtermask { 47 | /// Mask to filter by 48 | #[structopt(required(true))] 49 | mask: String, 50 | /// Input file, stdin if not present 51 | #[structopt(parse(from_os_str))] 52 | input: Option, 53 | /// Output file, stdout if not present 54 | #[structopt(short, long, parse(from_os_str))] 55 | output: Option, 56 | }, 57 | /// Splits each line on the charset boundry 58 | #[structopt(name = "cgrams")] 59 | Cgrams { 60 | /// Input file, stdin if not present 61 | #[structopt(parse(from_os_str))] 62 | input: Option, 63 | /// Output file, stdout if not present 64 | #[structopt(short, long, parse(from_os_str))] 65 | output: Option, 66 | /// Sort by frequency (slower and memory hungry) 67 | #[structopt(short, long)] 68 | sort: bool, 69 | /// Ignores case e.g. "Hello" will _NOT_ be split into "H" and "ello" 70 | #[structopt(short, long)] 71 | ignore_case: bool, 72 | /// Normalizes "Hello" to "hello", both variants will be used 73 | #[structopt(short, long)] 74 | normalize: bool, 75 | }, 76 | } 77 | 78 | fn main() { 79 | let opt = CmdOpts::from_args(); 80 | match opt { 81 | CmdOpts::Statsgen { 82 | input, 83 | output, 84 | separator, 85 | min_length, 86 | max_length, 87 | } => { 88 | statsgen::gen(input, output, separator, min_length, max_length); 89 | } 90 | CmdOpts::Unhex { input, output } => { 91 | unhex::unhex(input, output); 92 | } 93 | CmdOpts::Filtermask { 94 | input, 95 | output, 96 | mask, 97 | } => { 98 | filtermask::filtermask(input, output, mask); 99 | } 100 | CmdOpts::Cgrams { 101 | input, 102 | output, 103 | sort, 104 | ignore_case, 105 | normalize, 106 | } => { 107 | if !ignore_case && normalize { 108 | eprintln!("--normalize only works together with --ignore-case"); 109 | process::exit(-1); 110 | } 111 | cgrams::gen_c_grams(input, output, sort, ignore_case, normalize); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/statsgen.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | use std::path::PathBuf; 3 | 4 | use bstr::{io::BufReadExt, ByteSlice}; 5 | use hashbrown::HashMap; 6 | use pack2_util::*; 7 | 8 | const DEFAULT_ENCODED: u64 = 0x00000000ffff0000; 9 | 10 | #[inline(always)] 11 | fn encode_count_min_max(count: u32, min: u16, max: u16) -> u64 { 12 | let encoded: u64 = (count as u64) << 32 | (min as u64) << 16 | (max as u64); 13 | encoded 14 | } 15 | 16 | #[inline(always)] 17 | fn decode_count_min_max(encoded: u64) -> (u32, u16, u16) { 18 | let count: u32 = (encoded >> 32) as u32; 19 | let min: u16 = ((encoded >> 16) & 0xffff) as u16; 20 | let max: u16 = (encoded & 0xffff) as u16; 21 | (count, min, max) 22 | } 23 | 24 | pub fn gen( 25 | input: Option, 26 | output: Option, 27 | separator: Option, 28 | min_length: u16, 29 | max_length: u16, 30 | ) { 31 | let bitmap2string = get_bitmap2string(); 32 | let reader = get_reader(input); 33 | 34 | let mut masks: HashMap, u32> = HashMap::new(); 35 | let mut simple_masks: HashMap, u64> = HashMap::new(); 36 | let mut length: HashMap = HashMap::new(); 37 | let mut charsets: HashMap = HashMap::new(); 38 | 39 | let mut processed_lines: usize = 0; 40 | let mut skipped_lined: usize = 0; 41 | let mut min_len: usize = usize::MAX; 42 | let mut max_len: usize = 0; 43 | 44 | let mut mask: Vec = Vec::new(); 45 | let mut simple_mask: Vec = Vec::new(); 46 | 47 | for result in reader.byte_lines() { 48 | let (line, line_len) = decode_hex_if_needed(result.unwrap()); 49 | 50 | if line_len < min_length.into() || line_len > max_length.into() { 51 | skipped_lined += 1; 52 | continue; 53 | } 54 | 55 | if min_len > line_len { 56 | min_len = line_len 57 | } 58 | if line_len > max_len { 59 | max_len = line_len 60 | } 61 | 62 | let mut last: u8 = 0; 63 | 64 | let mut charset: u8 = 0; 65 | let mut skip = false; 66 | 67 | for byte in line.iter() { 68 | mask.push(CHAR2MASK[*byte as usize]); 69 | charset |= CHAR2BITMAP[*byte as usize]; 70 | let char_mapped = CHAR2SMASK[*byte as usize]; 71 | if last == 0 || (last != char_mapped && !skip) { 72 | simple_mask.push(char_mapped); 73 | last = char_mapped; 74 | } 75 | if simple_mask.len() > 4 { 76 | skip = true; 77 | simple_mask.clear(); 78 | simple_mask.push(255); 79 | } 80 | } 81 | 82 | *masks.entry((&*mask).to_vec()).or_insert(0) += 1; 83 | *length.entry(line_len as u16).or_insert(0) += 1; 84 | let charsets_entry = charsets.entry(charset).or_insert(DEFAULT_ENCODED); 85 | let (mut charsets_count, mut charsets_min_len, mut charsets_max_len) = 86 | decode_count_min_max(*charsets_entry); 87 | 88 | charsets_count += 1; 89 | if charsets_min_len as usize > line_len { 90 | charsets_min_len = line_len as u16 91 | } 92 | if line_len > charsets_max_len as usize { 93 | charsets_max_len = line_len as u16 94 | } 95 | 96 | *charsets_entry = encode_count_min_max(charsets_count, charsets_min_len, charsets_max_len); 97 | 98 | let simple_mask_entry = simple_masks 99 | .entry((&*simple_mask).to_vec()) 100 | .or_insert(DEFAULT_ENCODED); 101 | let (mut simple_count, mut simple_min_len, mut simple_max_len) = 102 | decode_count_min_max(*simple_mask_entry); 103 | 104 | simple_count += 1; 105 | if simple_min_len as usize > line_len { 106 | simple_min_len = line_len as u16 107 | } 108 | if line_len > simple_max_len as usize { 109 | simple_max_len = line_len as u16 110 | } 111 | 112 | *simple_mask_entry = encode_count_min_max(simple_count, simple_min_len, simple_max_len); 113 | 114 | mask.clear(); 115 | simple_mask.clear(); 116 | processed_lines += 1; 117 | } 118 | 119 | let total_lines = processed_lines + skipped_lined; 120 | eprintln!( 121 | "[+] Analyzed {} / {} passwords.", 122 | processed_lines, total_lines 123 | ); 124 | 125 | let mut freq_len = Vec::from_iter(length); 126 | freq_len.sort_by(|&(_, a), &(_, b)| b.cmp(&a)); 127 | 128 | eprintln!( 129 | "[*] Length distribution: (min: {} max: {})", 130 | min_len, max_len 131 | ); 132 | for (len, count) in freq_len { 133 | let percent = 100.0 / processed_lines as f64 * count as f64; 134 | eprintln!("[+] {: >26}: {: >6.2}% ({})", len, percent, count); 135 | } 136 | 137 | let mut freq_charsets = Vec::from_iter(charsets); 138 | freq_charsets.sort_by(|&(_, a), &(_, b)| b.cmp(&a)); 139 | 140 | eprintln!("\n[*] Charset distribution: count min max"); 141 | for (charset, encoded) in freq_charsets { 142 | let (count, min_len, max_len) = decode_count_min_max(encoded); 143 | let percent = 100.0 / processed_lines as f64 * count as f64; 144 | eprintln!( 145 | "[+] {: >26}: {: >6.2}% {: >10} {: >5} {: >5}", 146 | bitmap2string[charset as usize], percent, count, min_len, max_len 147 | ); 148 | } 149 | 150 | let mut freq_simple_masks = Vec::from_iter(simple_masks); 151 | freq_simple_masks.sort_by(|&(_, a), &(_, b)| b.cmp(&a)); 152 | 153 | eprintln!("\n[*] Simple masks distribution: count min max"); 154 | for (simple_mask, encoded) in freq_simple_masks { 155 | let (count, min_len, max_len) = decode_count_min_max(encoded); 156 | let percent = 100.0 / processed_lines as f64 * count as f64; 157 | let mut print_simple_mask: Vec<&str> = Vec::new(); 158 | for mapped_mask in simple_mask { 159 | match mapped_mask { 160 | 1 => print_simple_mask.push("string"), 161 | 4 => print_simple_mask.push("digit"), 162 | 8 => print_simple_mask.push("special"), 163 | 16 => print_simple_mask.push("binary"), 164 | 255 => print_simple_mask.push("othermask"), 165 | _ => (), 166 | } 167 | } 168 | 169 | eprintln!( 170 | "[+] {: >26}: {: >6.2}% {: >10} {: >5} {: >5}", 171 | print_simple_mask.join(""), 172 | percent, 173 | count, 174 | min_len, 175 | max_len 176 | ); 177 | } 178 | let mut freq_masks = Vec::from_iter(masks); 179 | freq_masks.sort_by(|&(_, a), &(_, b)| b.cmp(&a)); 180 | 181 | eprintln!("\n[*] Masks (top 25):"); 182 | let mut top = 0; 183 | let mut print_mask = vec![b'?'; max_len * 2]; 184 | 185 | let mut writer = get_writer(output); 186 | 187 | let separator: char = match separator { 188 | None => '\t', 189 | Some(sep) => sep, 190 | }; 191 | 192 | for (mask, count) in freq_masks { 193 | let mut idx = 0; 194 | for c in mask { 195 | print_mask[idx * 2 + 1] = c; 196 | idx += 1; 197 | } 198 | let out_mask = &print_mask[0..idx * 2].to_str().unwrap(); 199 | let percent = 100.0 / processed_lines as f64 * count as f64; 200 | if top < 25 { 201 | eprintln!("[+] {: >26}: {: >6.2}% ({})", out_mask, percent, count); 202 | top += 1; 203 | } 204 | let out = &*format!( 205 | "{}{}{:.4}{}{}\n", 206 | out_mask, separator, percent, separator, count 207 | ); 208 | mywrite(&mut out.as_bytes(), &mut writer); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/unhex.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use bstr::{io::BufReadExt, ByteSlice}; 4 | use pack2_util::*; 5 | 6 | pub fn unhex(input: Option, output: Option) { 7 | let reader = get_reader(input); 8 | let mut writer = get_writer(output); 9 | 10 | for result in reader.byte_lines() { 11 | let (mut line, _line_len) = decode_hex_if_needed(result.unwrap()); 12 | line.push(b'\n'); 13 | mywrite(&mut line.as_bytes(), &mut writer); 14 | } 15 | } 16 | --------------------------------------------------------------------------------