1use clap::Parser;
2use std::error::Error;
3use std::fmt;
4use std::fs::File;
5use std::io::Read;
6use zip::read::ZipFile;
7
8#[derive(Debug)]
9struct ZipFileConfigError(String);
10
11impl fmt::Display for ZipFileConfigError {
12 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13 write!(f, "ZipFileConfigError: {}", self.0)
14 }
15}
16
17impl Error for ZipFileConfigError {}
18
19#[derive(Parser, Debug)]
20#[clap(author, version, about, long_about = None)]
21pub struct Args {
22 #[clap(short, long)]
24 zip_name: String,
25
26 #[clap(short, long)]
28 file_name: String,
29
30 #[clap(short = 't', long)]
32 file_extension: Option<String>,
33}
34
35pub struct Config {
36 pub archive: zip::ZipArchive<File>,
37 pub file_name: String,
38 pub file_header: Option<Vec<u8>>,
39}
40
41impl Config {
42 pub fn new(args: Args) -> Result<Config, Box<dyn Error>> {
43 let zip_path = std::path::Path::new(&args.zip_name);
44 let zip_file = std::fs::File::open(&zip_path)?;
45 let file_extension = match &args.file_extension {
46 Some(file_extension) => file_extension.to_owned(),
47 None => guess_file_type(&args.file_name)?,
48 };
49 let mut archive = zip::ZipArchive::new(zip_file)?;
50 let file_exists = check_if_file_exists_in_zip(&mut archive, &args.file_name);
51 if file_exists {
52 Ok(Config {
53 archive,
54 file_name: args.file_name,
55 file_header: get_header(&file_extension),
56 })
57 } else {
58 Err(Box::new(ZipFileConfigError(
59 "File does not exist in zip".into(),
60 )))
61 }
62 }
63}
64
65pub fn run<R>(mut config: Config, mut passwords: R) -> Result<String, &'static str>
66where
67 R: Iterator<Item = String>,
68{
69 let header = &config.file_header;
70 if let Some(header) = header {
71 passwords
72 .find(|p| {
73 config
74 .archive
75 .by_name_decrypt(&config.file_name, p.as_bytes())
76 .ok()
77 .and_then(|r| r.into())
78 .map_or(false, |mut file| {
79 is_header_valid(&mut file, header.to_vec())
80 })
81 })
82 .ok_or("Password wasn't found")
83 } else {
84 return Err("Unable to detect file header");
85 }
86}
87
88fn is_header_valid(file: &mut ZipFile, file_header: Vec<u8>) -> bool {
89 let mut actual_header = [0u8; 128];
90 let header = &mut actual_header[..file_header.len()];
91 file.read_exact(header).is_ok() && header == file_header
92}
93
94pub fn get_header(extension: &str) -> Option<Vec<u8>> {
95 match extension {
96 "asf" | "wma" | "wmv" => Some(vec![
97 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62,
98 0xCE, 0x6C,
99 ]),
100 "png" => Some(vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
101 "jpg" => Some(vec![0xFF, 0xD8]),
102 "zip" | "apk" | "jar" => Some(vec![0x50, 0x4B, 0x03, 0x04]),
103 "xml" => Some(vec![0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20]),
104 _ => None,
105 }
106}
107
108fn guess_file_type(file_name: &str) -> Result<String, &'static str> {
109 let ext = file_name
110 .split('.')
111 .next_back()
112 .and_then(|ext| Some(ext.to_string()));
113 match ext {
114 Some(ext) => Ok(ext),
115 None => Err("failed to guess file type"),
116 }
117}
118
119fn check_if_file_exists_in_zip(archive: &mut zip::ZipArchive<File>, file_name: &str) -> bool {
120 let mut file_names = archive.file_names();
121 let file_exits = file_names.find(|&x| x == file_name);
122 match file_exits {
123 Some(_) => true,
124 None => false,
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use std::path::PathBuf;
132
133 #[test]
134 fn test_is_header_valid() {
135 let mut test_zip_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
136 test_zip_path.push("test_data/cats.zip");
137 let zip_file = std::fs::File::open(&test_zip_path).unwrap();
138 let mut archive = zip::ZipArchive::new(&zip_file).unwrap();
139 let mut file = archive.by_name_decrypt("kitten.jpg", b"fun").unwrap();
140 assert_eq!(true, is_header_valid(&mut file, get_header("jpg").unwrap()));
141 }
142
143 #[test]
144 fn test_blitz_zip_with_jpg() {
145 let mut test_zip_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
146 test_zip_path.push("test_data/cats.zip");
147 let config = Config::new(Args {
148 zip_name: test_zip_path.into_os_string().into_string().unwrap(),
149 file_name: String::from("kitten.jpg"),
150 file_extension: Some(String::from("jpg")),
151 })
152 .unwrap();
153 let wordlist = std::fs::read_to_string("test_data/wordlist.txt")
154 .expect("Something went wrong reading the file");
155 let lines = wordlist.lines().map(|x| x.to_string());
156 if let Ok(password) = run(config, lines) {
157 assert_eq!(password, "fun");
158 } else {
159 panic!("password validation logic faild");
160 }
161 }
162
163 #[test]
164 fn test_blitz_zip_without_type_provided() {
165 let mut test_zip_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
166 test_zip_path.push("test_data/cats.zip");
167 let config = Config::new(Args {
168 zip_name: test_zip_path.into_os_string().into_string().unwrap(),
169 file_name: String::from("kitten.jpg"),
170 file_extension: None,
171 })
172 .unwrap();
173 let wordlist = std::fs::read_to_string("test_data/wordlist.txt")
174 .expect("Something went wrong reading the file");
175 let lines = wordlist.lines().map(|x| x.to_string());
176 if let Ok(password) = run(config, lines) {
177 assert_eq!(password, "fun");
178 } else {
179 panic!("password validation logic faild");
180 }
181 }
182}