├── .gitignore ├── Cargo.toml ├── README.md ├── src └── main.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *~ 3 | *.patch 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "patchv380" 3 | version = "0.1.1" 4 | authors = ["bcaller "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | byteorder = "1.3.4" 11 | clap = "2" 12 | md-5 = "0.8.0" 13 | tempfile = "3.1.0" 14 | 15 | [profile.release] 16 | opt-level = 'z' # Optimize for size. 17 | lto = true 18 | panic = 'abort' 19 | codegen-units = 1 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v380-ipcam-firmware-patch 2 | 3 | Patch the WiFi Smart Net Camera v380 4 | 5 | 1. Extract files from firmware patches 6 | 2. Write your own firmware patches 7 | 8 | ## Installation 9 | 10 | Download binary from the [releases page](https://github.com/bcaller/v380-ipcam-firmware-patch/releases) or clone and build with `cargo`. 11 | 12 | ## Usage 13 | 14 | ### Read 15 | 16 | ``` 17 | $ patchv380 read AK3918E-V200_V.2.5.9.5/updatepatch/5d4315195544f84f54a52ac757ce200e.patch 18 | Number of files: 21 19 | exshell_afu.sh (1307 bytes, Script) 20 | exshell_bfu.sh (606 bytes, Script) 21 | IMG_KER_11 (2095680 bytes, Kernel Image) 22 | IMG_MVS_mvs_v200_2595.sqsh4 (1024000 bytes, mtd3 Image) 23 | isp_gc1034_quanjing.conf.gz (14875 bytes, Other) 24 | isp_gc1034_yaotouji.conf.gz (14875 bytes, Other) 25 | isp_sc1035_quanjing.conf.gz (21981 bytes, Other) 26 | isp_sc1035_yaotouji.conf.gz (21981 bytes, Other) 27 | isp_sc1135T.conf.gz (24938 bytes, Other) 28 | isp_sc1135_quanjing.conf.gz (21970 bytes, Other) 29 | isp_sc1135_yaotouji.conf.gz (14613 bytes, Other) 30 | isp_sc1145_qiangji_180.conf.gz (13954 bytes, Other) 31 | isp_sc1145_yaotouji.conf.gz (13943 bytes, Other) 32 | isp_sc1235_quanjing.conf.gz (24308 bytes, Other) 33 | isp_sc1235_yaotouji.conf.gz (15459 bytes, Other) 34 | isp_sc1245_quanjing.conf.gz (15083 bytes, Other) 35 | isp_sc1245_yaotouji.conf.gz (14565 bytes, Other) 36 | prerun (53336 bytes, Other) 37 | sf_stfailed_pwd_err_cn.wav (19896 bytes, Sound) 38 | sf_stfailed_pwd_err_en.wav (22520 bytes, Sound) 39 | stopallapp.sh (200 bytes, Other) 40 | ``` 41 | 42 | Add `-e ./unpackhere` to extract the files. 43 | 44 | ### Write 45 | 46 | Make a firmware patch containing 2 files: 47 | 48 | ``` 49 | patchv380 write stuff/exshell_bfu.sh /path/to/IMG_RFS_stuff.sq4 50 | ``` 51 | 52 | You need to then move the `???.patch` file to `/sdcard/updatepatch/???.patch` and put in `/sdcard/localupdate.conf`: 53 | 54 | ``` 55 | [PATCH] 56 | patchmd5=??? 57 | ``` 58 | 59 | If you've already applied a patch, you might need `-h V380E2_CA` or whatever the ipcam's logs say you need. 60 | 61 | ## Hints 62 | 63 | `exshell_bfu.sh` is run as root at the very start of the update process. 64 | 65 | Blog post: [How to change the v380 root password](https://ℬ㏒.㎈ℓℯℛ.ⓧⓨℤ/v380-ipcam-firmware-patching). 66 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | 4 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 5 | use clap::AppSettings; 6 | use md5::{Md5, Digest}; 7 | use std::fs; 8 | use std::fs::File; 9 | use std::io; 10 | use std::io::prelude::*; 11 | use std::io::SeekFrom; 12 | use std::path::Path; 13 | use std::str; 14 | use tempfile::tempfile; 15 | 16 | 17 | fn main() -> io::Result<()> { 18 | let matches = clap_app!(patcher => 19 | (version: "1.0") 20 | (author: "Ben Caller ") 21 | (name: "v380 Patcher") 22 | (about: "Patch WiFi Smart Net Camera v380") 23 | (@subcommand read => 24 | (about: "List files in patch, optionally extracting contents") 25 | (@arg PATCH_FILE: * +takes_value "Patch file to read") 26 | (@arg extract_dir: -e +takes_value "Directory to extract files to") 27 | ) 28 | (@subcommand write => 29 | (about: "Write a patch file") 30 | (@arg INPUT: ... * +takes_value "Files to add to patch") 31 | (@arg hwname: -h +takes_value "hwname of device") 32 | ) 33 | ) 34 | .setting(AppSettings::SubcommandRequired) 35 | .get_matches(); 36 | if let Some(subm) = matches.subcommand_matches("read") { 37 | read( 38 | subm.value_of("PATCH_FILE").unwrap(), 39 | subm.value_of("extract_dir"), 40 | )?; 41 | } else if let Some(subm) = matches.subcommand_matches("write") { 42 | write( 43 | subm.values_of("INPUT").unwrap().collect(), 44 | subm.value_of("hwname").unwrap_or("V380E2_C"), 45 | )?; 46 | } 47 | Ok(()) 48 | } 49 | 50 | fn filename_to_type(filename: &str) -> &str { 51 | match &filename[..3] { 52 | "IMG" => match &filename[4..7] { 53 | "KER" => "Kernel Image", 54 | "RFS" => "mtd1 Image", 55 | "USR" => "mtd2 Image", 56 | "MVS" => "mtd3 Image", 57 | "EXT" => "mtd4 Image", 58 | "JFS" => "mtd5 Image", 59 | _ => "Unknown Image", 60 | }, 61 | "sf_" => "Sound", 62 | "exs" if &filename[3..4] == "h" => "Script", 63 | _ => "Other", 64 | } 65 | } 66 | 67 | pub fn read(patch_file: &str, extract_dir: Option<&str>) -> io::Result<()> { 68 | let mut f = File::open(&patch_file)?; 69 | f.seek(SeekFrom::Start(0x18))?; 70 | let n_sections = f.read_u32::()?; 71 | f.seek(SeekFrom::Start(0x80))?; 72 | println!("Number of files: {}", n_sections); 73 | for _i in 0..n_sections { 74 | let mut filename_buffer = vec![0; 0x38]; 75 | f.read_exact(&mut filename_buffer)?; 76 | let null_terminated = filename_buffer.splitn(2, |c| *c == 0u8).next().unwrap(); 77 | let filename = str::from_utf8(&null_terminated).unwrap(); 78 | let length = f.read_u32::()?; 79 | f.seek(SeekFrom::Current(0x04))?; 80 | println!("{} ({} bytes, {})", filename, length, filename_to_type(filename)); 81 | match extract_dir { 82 | Some(dir) => { 83 | let path = Path::new(dir).join(filename); 84 | let mut extract_f = File::create(path)?; 85 | let mut adapter = f.take(length as u64); 86 | io::copy(&mut adapter, &mut extract_f)?; 87 | f = adapter.into_inner(); 88 | } 89 | _ => { 90 | f.seek(SeekFrom::Current(length as i64))?; 91 | } 92 | } 93 | } 94 | Ok(()) 95 | } 96 | 97 | fn write_zeroes(f: &mut dyn Write, n: usize) -> io::Result<()> { 98 | let zero = vec![0; n]; 99 | f.write_all(&zero) 100 | } 101 | 102 | fn write_string(f: &mut dyn Write, s: &str, n: usize) -> io::Result<()> { 103 | let s = s.as_bytes(); 104 | f.write_all(s)?; 105 | write_zeroes(f, n - s.len())?; 106 | Ok(()) 107 | } 108 | 109 | pub fn write(input: Vec<&str>, hwname: &str) -> io::Result<()> { 110 | let mut f = HashingWriter::new(tempfile()?); 111 | 112 | f.write_u32::(0x0a)?; 113 | write_string(&mut f, hwname, 0x10)?; 114 | f.write_u32::(0x1f4b59)?; // This might need to be configurable 115 | let n_files = input.len() as u32; 116 | f.write_u32::(n_files)?; 117 | write_zeroes(&mut f, 0x80 - 0x1c)?; 118 | 119 | for filename in input { 120 | let filesize = fs::metadata(filename)?.len() as u32; 121 | let basename = Path::new(filename).file_name().unwrap().to_str().unwrap(); 122 | write_string(&mut f, basename, 0x38)?; 123 | f.write_u32::(filesize)?; 124 | write_zeroes(&mut f, 4)?; 125 | 126 | let mut input_f = File::open(filename)?; 127 | io::copy(&mut input_f, &mut f)?; 128 | } 129 | 130 | let digest = f.hash.result(); 131 | let filename = format!("{:x}.patch", digest); 132 | // Copy temporary file as it may be on different disk 133 | let mut f = f.underlying_writer; 134 | f.seek(SeekFrom::Start(0))?; 135 | io::copy(&mut f, &mut File::create(filename)?)?; 136 | println!("{:x}", digest); 137 | Ok(()) 138 | } 139 | 140 | struct HashingWriter { 141 | underlying_writer: W, 142 | hash: Md5, 143 | } 144 | 145 | impl HashingWriter { 146 | pub fn new(wtr: W) -> HashingWriter { 147 | HashingWriter { 148 | underlying_writer: wtr, 149 | hash: Md5::new(), 150 | } 151 | } 152 | } 153 | 154 | impl Write for HashingWriter { 155 | fn write(&mut self, buf: &[u8]) -> io::Result { 156 | self.hash.input(buf); 157 | self.underlying_writer.write(buf) 158 | } 159 | 160 | fn flush(&mut self) -> io::Result<()> { 161 | self.underlying_writer.flush() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi", 10 | ] 11 | 12 | [[package]] 13 | name = "atty" 14 | version = "0.2.14" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 17 | dependencies = [ 18 | "hermit-abi", 19 | "libc", 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "1.2.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 28 | 29 | [[package]] 30 | name = "block-buffer" 31 | version = "0.7.3" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 34 | dependencies = [ 35 | "block-padding", 36 | "byte-tools", 37 | "byteorder", 38 | "generic-array", 39 | ] 40 | 41 | [[package]] 42 | name = "block-padding" 43 | version = "0.1.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 46 | dependencies = [ 47 | "byte-tools", 48 | ] 49 | 50 | [[package]] 51 | name = "byte-tools" 52 | version = "0.3.1" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 55 | 56 | [[package]] 57 | name = "byteorder" 58 | version = "1.3.4" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 61 | 62 | [[package]] 63 | name = "cfg-if" 64 | version = "0.1.10" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 67 | 68 | [[package]] 69 | name = "clap" 70 | version = "2.33.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 73 | dependencies = [ 74 | "ansi_term", 75 | "atty", 76 | "bitflags", 77 | "strsim", 78 | "textwrap", 79 | "unicode-width", 80 | "vec_map", 81 | ] 82 | 83 | [[package]] 84 | name = "digest" 85 | version = "0.8.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 88 | dependencies = [ 89 | "generic-array", 90 | ] 91 | 92 | [[package]] 93 | name = "generic-array" 94 | version = "0.12.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 97 | dependencies = [ 98 | "typenum", 99 | ] 100 | 101 | [[package]] 102 | name = "getrandom" 103 | version = "0.1.15" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 106 | dependencies = [ 107 | "cfg-if", 108 | "libc", 109 | "wasi", 110 | ] 111 | 112 | [[package]] 113 | name = "hermit-abi" 114 | version = "0.1.17" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 117 | dependencies = [ 118 | "libc", 119 | ] 120 | 121 | [[package]] 122 | name = "libc" 123 | version = "0.2.80" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 126 | 127 | [[package]] 128 | name = "md-5" 129 | version = "0.8.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" 132 | dependencies = [ 133 | "block-buffer", 134 | "digest", 135 | "opaque-debug", 136 | ] 137 | 138 | [[package]] 139 | name = "opaque-debug" 140 | version = "0.2.3" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 143 | 144 | [[package]] 145 | name = "patchv380" 146 | version = "0.1.1" 147 | dependencies = [ 148 | "byteorder", 149 | "clap", 150 | "md-5", 151 | "tempfile", 152 | ] 153 | 154 | [[package]] 155 | name = "ppv-lite86" 156 | version = "0.2.10" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 159 | 160 | [[package]] 161 | name = "rand" 162 | version = "0.7.3" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 165 | dependencies = [ 166 | "getrandom", 167 | "libc", 168 | "rand_chacha", 169 | "rand_core", 170 | "rand_hc", 171 | ] 172 | 173 | [[package]] 174 | name = "rand_chacha" 175 | version = "0.2.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 178 | dependencies = [ 179 | "ppv-lite86", 180 | "rand_core", 181 | ] 182 | 183 | [[package]] 184 | name = "rand_core" 185 | version = "0.5.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 188 | dependencies = [ 189 | "getrandom", 190 | ] 191 | 192 | [[package]] 193 | name = "rand_hc" 194 | version = "0.2.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 197 | dependencies = [ 198 | "rand_core", 199 | ] 200 | 201 | [[package]] 202 | name = "redox_syscall" 203 | version = "0.1.57" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 206 | 207 | [[package]] 208 | name = "remove_dir_all" 209 | version = "0.5.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 212 | dependencies = [ 213 | "winapi", 214 | ] 215 | 216 | [[package]] 217 | name = "strsim" 218 | version = "0.8.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 221 | 222 | [[package]] 223 | name = "tempfile" 224 | version = "3.1.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 227 | dependencies = [ 228 | "cfg-if", 229 | "libc", 230 | "rand", 231 | "redox_syscall", 232 | "remove_dir_all", 233 | "winapi", 234 | ] 235 | 236 | [[package]] 237 | name = "textwrap" 238 | version = "0.11.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 241 | dependencies = [ 242 | "unicode-width", 243 | ] 244 | 245 | [[package]] 246 | name = "typenum" 247 | version = "1.12.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 250 | 251 | [[package]] 252 | name = "unicode-width" 253 | version = "0.1.8" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 256 | 257 | [[package]] 258 | name = "vec_map" 259 | version = "0.8.2" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 262 | 263 | [[package]] 264 | name = "wasi" 265 | version = "0.9.0+wasi-snapshot-preview1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 268 | 269 | [[package]] 270 | name = "winapi" 271 | version = "0.3.9" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 274 | dependencies = [ 275 | "winapi-i686-pc-windows-gnu", 276 | "winapi-x86_64-pc-windows-gnu", 277 | ] 278 | 279 | [[package]] 280 | name = "winapi-i686-pc-windows-gnu" 281 | version = "0.4.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 284 | 285 | [[package]] 286 | name = "winapi-x86_64-pc-windows-gnu" 287 | version = "0.4.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 290 | --------------------------------------------------------------------------------