├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.sh ├── info ├── http_server.md ├── sdcard_hack.md └── upgrade_hack.md ├── installer ├── config.inc.TEMPLATE ├── remote_install.sh ├── telnet_install.sh └── wyze_updater.py ├── release ├── wyze_hacks_0_3_00.zip ├── wyze_hacks_0_4_00.zip ├── wyze_hacks_0_4_01.zip ├── wyze_hacks_0_4_02.zip ├── wyze_hacks_0_4_03.zip ├── wyze_hacks_0_4_04.zip ├── wyze_hacks_0_5_01.zip ├── wyze_hacks_0_5_02.zip ├── wyze_hacks_0_5_03.zip ├── wyze_hacks_0_5_04.zip ├── wyze_hacks_0_5_05.zip ├── wyze_hacks_0_5_06.zip ├── wyze_hacks_0_5_07.zip └── wyze_hacks_0_5_08.zip ├── src ├── .gitignore ├── Makefile ├── dummymmc │ ├── .gitignore │ ├── Makefile │ ├── compile.sh │ └── dummy_mmc.c └── utils │ ├── Makefile │ ├── hackutils │ ├── hackutils.c │ ├── libhacks.c │ ├── libhacks.h │ └── libhacks.so ├── tools └── v3 │ ├── README.md │ ├── build.sh │ ├── firmwares │ └── 4.36.0.228 │ │ ├── demo_wcv3.bin │ │ └── info.txt │ └── rcS └── wyze_hack ├── bin ├── audioplay ├── blkid ├── hackutils ├── libhacks.so ├── mount ├── reboot └── tf_prepare ├── hack_ver.inc ├── install_snd ├── begin.wav ├── failed.wav └── finished.wav ├── main.sh ├── rootfs └── 4.36.0.112 │ ├── rootfs.bin │ └── versions.txt ├── stub.sh ├── upgrade.sh └── upgraderun.sh /.gitignore: -------------------------------------------------------------------------------- 1 | @eaDir 2 | .vscode 3 | release/* 4 | !release/*.zip 5 | installer/config.inc 6 | installer/version.ini 7 | installer/FIRMWARE_660R.bin 8 | installer/firmware.bin 9 | installer/Upgrade 10 | installer/.tokens 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "WyzeUpdater"] 2 | path = WyzeUpdater 3 | url = https://github.com/HclX/WyzeUpdater.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 HclX 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 | # WyzeHacks 2 | This project contains a set of scripts trying to provide additional features 3 | not implemented by the official firmware. Currently, it provides the following 4 | functions: 5 | 1. Enable telnetd on your camera. 6 | 2. Customize the default root password for telnet login. 7 | 3. Redirect all the recordings to an NFS share. 8 | 4. Redirect console logs into an NFS share. 9 | 5. Automatically reboot the camera at certain time. 10 | 6. Automatically archive the recordings. 11 | 12 | # Release notes 13 | ## 0.5.08 14 | * Fixing device hostname 15 | * Including device model in hostname 16 | * BREAKING CHANGE: The custom hostname config variable is renamed to `CUSTOM_HOSTNAME` due to naming conflict with some device models 17 | * Merged PR130 for large NFS partition support 18 | 19 | ## 0.5.07 20 | * Fixing the "network disconnected" issue on latest v2/pan beta firmware. 21 | 22 | ## 0.5.06 23 | * Support v3 cameras with root fs version 4.36.0.112 and 4.36.0.56. 24 | 25 | ## 0.5.05: 26 | * Including v3 init into the default installation process. 27 | 28 | ## 0.5.04: 29 | * Fixing hack not persisting through 4.X.6.241 update. 30 | * Getting rid of all the init file specific modifications. 31 | * Supporting the new logging method used in 4.X.6.241. 32 | * Stopping spamming log messages. 33 | 34 | ## 0.5.03: 35 | * Fixing broken SD card simuation on latest v3 firmware (4.36.0.252) 36 | 37 | ## 0.5.02: 38 | * Fixing an install issue with RTSP firmware. 39 | * Fixing an issue with NFS connectivity detection. 40 | * Adding a customizable threshold for NFS connectivity detection. 41 | * Making sure telnetd is enabled before NFS mount. 42 | 43 | ## 0.5.01: 44 | * Auto update and auto config are now checked every minute. 45 | * Fixing config update mechanism. 46 | * Changing auto reboot to use local time. 47 | * Moving logs to a different directory. 48 | * Support telnetd and physical SD card when NFS_ROOT is not specified. 49 | * Refreshing ifconfig.txt every minute. 50 | * Better method for SD card insertion simulation. 51 | * Removing dummymmc kernel module and uevent_recv/uevent_send binaries. 52 | * Adding uninstallation method. 53 | * Moving v2 installation location from /params to /configs. 54 | * WIP v3 camera support (require special steps, see tools/v3/README.md). 55 | * Tested on firmware 4.9.6.224 (V2), 4.6.10.224 (PAN) and 4.36.0.228 (V3). 56 | * Reducing wyzehack binary size by excluding some sound files. 57 | 58 | ## 0.4.04: 59 | * Adding support for latest firmware (4.9.6.218, 4.10.6.218). 60 | * Adding support of custom script. 61 | * Adding support for automatic config and wyzehack update. 62 | * Adding support of customized hostname. 63 | 64 | Note: Wyze made some changes in version 4.x.6.218 cuasing it really hard to 65 | install wyze hacks non-intrusively. So I have to make some version specific 66 | changes, and there may be more version specific changes coming later if they 67 | changed it again. This means we may need more frequently releases than before 68 | (worst case for every stable firmware). This is why I added auto-update 69 | mechanism in this release to make my (and others) lives easier in future. 70 | 71 | ## 0.4.03: 72 | * Adding notification volume control. 73 | * Reducing the emulated SD card to 128GB to avoid issues. 74 | 75 | ## 0.4.02: 76 | * Adding an option to prevent wifi disconnecting. 77 | * Integrating TOTP based 2FA. 78 | * Supporting camera directory name containing space characters 79 | * Detecting stale NFS connection. 80 | 81 | ## 0.4.01: 82 | * Detecting NFS unmount and automatically reboot the camera. 83 | * Fixing SD card size to properly support large NFS shares. 84 | * Fixing config.inc.TEMPLATE comments 85 | 86 | ## 0.4.00: 87 | * Support firmware version 4.9.6.156 (WyzeCamV2) and 4.10.6.156 (WyzeCam Pan). 88 | * Due to new firmware blocking SD card installation, a new remote installation 89 | method has to be used. 90 | * SD card size emulation to make sure the hack works with large NFS shares. 91 | * Support NFS mount command line options in config file. 92 | 93 | # Download and installation 94 | The latest release archive can be found in the [release folder] 95 | (https://github.com/HclX/WyzeHacks/tree/master/release). You will need to unzip 96 | the release archive before proceed. Depending on the firmware version your 97 | camera is running, you may have two or three different approaches to install the 98 | hack. 99 | 100 | ## Remote install using remote_install.sh 101 | This installation method emulates the Wyze App protocol to push the update to a 102 | running camera. You will need the following for installation: 103 | * A Linux-like environment with Python3.7 installed. If you are using linux, it 104 | shouldn't be a problem. MacOS might also work but I didn't test that. WSL on 105 | Windows, unfortunately, doesn't work due to its isolated network settings. 106 | * The target camera should be in the same network to which your Linux 107 | environment connects. If your camera is running in isolated vLAN, you may need 108 | to adjust it temporarily. 109 | * The target camera should be in a working condition which means you can see it 110 | alive from the Wyze App. 111 | 112 | To do the installation, please follow these steps: 113 | 1. Unzip your release archive a directory, and change your current working 114 | directory to there. 115 | 2. Rename "config.inc.TEMPLATE" to "config.inc", and then update the content 116 | properly. 117 | 3. Run "./remote_install.sh" 118 | 4. First time, it will ask your Wyze account and password, and it may also ask 119 | for 2FA authentication. 120 | 5. The login credentials will be stored in a local file named ".tokens" for 121 | future use so you don't need to enter username and password and 2FA everytime. 122 | Make sure you don't share this file with anyone you don't trust. 123 | 6. The token seems to have an expiration period. So next time if you run into 124 | error with something like "Access token error" please delete ".tokens" file and 125 | restart. 126 | 7. Once correctly authenticated, it will go through all the Wyze Cameras under 127 | your account, asking you for each of them if you want to push the wyzehack onto 128 | that camera. Enter 'Y' if yes, otherwise 'N'. Press "Ctrl + C" twice to 129 | interrupt the process. 130 | 8. Once confirmed, you will hear "installation begins" from the target camera, 131 | and then "installation finished" confirming the installation. 132 | 9. When you are done, make sure delete ".tokens" file from that archive folder 133 | to avoid accidental leak of your logins. 134 | 135 | ## SD card install 136 | SD card install only works when your camera is running firmware before version 137 | 4.9.6.156 (WyzeCamV2) or 4.10.6.156 (WyzeCam Pan). You will need an SD card for 138 | this purpose. Size of the SD card doesn't matter. 139 | 140 | To do the installation, follow these steps: 141 | 1. Prepare an SD card with FAT32 format. 142 | 2. Extract the release archive to the root directory of SD card. 143 | 3. Make a copy of config.inc-TEMPLATE to the root of SD card, rename it to 144 | config.inc, and update the content accordingly. 145 | 4. Insert the SD card into camera. 146 | 5. Wait until it says "installation finished, please remove the SD card" 147 | 6. Make sure you remove the SD card before the camera reboots as I've seen 148 | strange behavior with SD card in it. 149 | 7. Now you should have telnet, and also the NFS recording functionality. 150 | 151 | ## Remote install using telnet_install.sh 152 | This method works when you have telnet access, which is usually enabled after 153 | you installed older version wyze hacks. 154 | 155 | Here are the steps to follow: 156 | 1. Unzip the release archive to a NFS share where you can access from the 157 | camera. 158 | 2. telnet into the device, and run telnet_install.sh from the NFS share. 159 | 3. Wait until it says "installation finished, please remove the SD card" 160 | 4. The device should reboot by itself in less than a minute and you should have 161 | the latest hack installed. 162 | 163 | ### Automatically install a release on NFS share 164 | In version 0.4.04 I added the "auto-update" mechanism allowing the camera to 165 | automatically install a release from NFS share. This feature is by default 166 | disabled so you have to manually enable it. Please check the config.inc.TEMPLATE 167 | on how to use it. 168 | 169 | # Uninstalling the wyzehacks 170 | You can use one of the following ways to uninstall this hack: 171 | ## Use the built-in uninstall feature 172 | Starting with version 0.5.01, you can tell the camera to uninstall wyzehacks by 173 | placing a file at the following location: 174 | `/wyzehacks/uninstall` 175 | Once the uninstallation finished, you will see a file `uninstall.done` 176 | confirming the success of uninstallation, or a file `uninstall.failed` telling 177 | something went wrong with the uninstallation. 178 | 179 | ## Do a manual telnet uninstall 180 | With wyzehacks installed, you should have the telnet access available. You can 181 | log into the camera and perform the following steps (for v1, v2 and PAN): 182 | ``` 183 | cp /system/init/app_init_orig.sh /system/init/app_init.sh 184 | rm /params/wyze_hack.* 185 | ``` 186 | Once you verified the above commands finished successfully you are no longer 187 | having any wyzehacks related stuff on the camera. 188 | 189 | NOTE: To telnet into the camera, you need the login with root user and its 190 | password, default one for v1/v2 is `ismart12`. PAN's default password is unknown 191 | so you will have to set your password shadow to allow you login. Check the 192 | `PASSWD_SHADOW` section in `config.inc.TEMPLATE` on how to do that. 193 | 194 | ## Perform a SD card firmware recovery 195 | This removes the wyzehacks boot straping code from the camera so that it will 196 | not be loaded by the camera firmware. However, your configuration file remains 197 | on the camera. Luckily there is not much sensentive information in that file. 198 | 199 | 200 | # Features: 201 | ## NFS share naming: 202 | The per camera NFS share was named by the camera's MAC address, but it's very 203 | inconvinent to manage if you have many cameras. As a result, a recent change 204 | now supports customizing folder names. All you need is renaming the folder 205 | from desktop to whatever you want and reboot the camera. The camera should 206 | remember whatever folder is using and will continue recording into the renamed 207 | folder. This feature is automatically enabled with latest version so no need 208 | to change anything in the configuration file to use this feature. 209 | ## Auto rebooting: 210 | Now you can control rebooting your camera at a specific time of each day. I 211 | found it's useful when you notice sometimes the device is not behaving very 212 | well after running for a couple days. You will need to uncomment a variable in 213 | the configuration file to use this feature. 214 | ## Password shadowing: 215 | The default root password for WyzeCam is wellknown. Since this hack will 216 | enable telnetd it may pose a new threat. To avoid that, customize the password 217 | to something only you know. You will need to uncomment a variable in the 218 | configuration file to use this feature. 219 | ## Log syncing: 220 | For debugging purpose, I improved the log syncing mechanism so now you can 221 | sync the boot console log into the NFS share at almost realtime. However, 222 | this has some security concerns as the boot log contains your credentials. 223 | To avoid that, this feature is only enabled if you uncomment a variable in 224 | the configuration file. 225 | 226 | !!!NEVER SHARE YOUR BOOT LOG FILE WITH OTHERS OR YOUR ACCOUNT CAN BE 227 | COMPROMISED!!! 228 | ## Auto archiving: 229 | I noticed after accumulating many days of recordings, the camera is behaving 230 | strangely when you try to playback the recordings from Wyze app. To avoid 231 | that, now you can enable "auto archiving" feature by uncommenting a config 232 | variable. Older recordings will be moved to a different location which is not 233 | discoverable by the Wyze app. You can always review them from your desktop. 234 | ## SD card size emulation 235 | People reports the hack doesn't work with NFS shares bigger than a certain 236 | size. So I added a SD card size emulation so that no matter how big your NFS 237 | share is, the hack will always report an acceptable size with used space set 238 | to zero unless your free space is less than 16GB, in which case, it will 239 | report a 16GB SD card with the correct free space information. 240 | 241 | # Disclaimer: 242 | * This is just a personal fun project without extensive test, so use it at your 243 | own risk. 244 | * Be nice to Wyze: Because of their effort we have a cheap platform to play 245 | around. If you run into issues after installing this hack, make sure you verify 246 | the issue is not caused by the hack itself before you call Wyze's customer 247 | support or return the device. 248 | * The NFS share will need to be writable. Things can happen to the files in that 249 | share. For example, if you do a "format SD card" from the camera. In theory the 250 | operations should be limitted to the specific directory mapped for a particular 251 | camera, but I wouldn't rely on that. It's always good to setup a separate NFS 252 | share just for this purpose and isolate it from all your other important 253 | documents. 254 | * It is usually tested working on latest stable firmware and I don't have time 255 | to do full testing for every older versions. 256 | * Once installed, physical SD card will no longer be recognized by the camera. 257 | * The log file contains sensitive account information, so do not share with 258 | others unless you don't mind your account being compromised. 259 | * This hack enables telnetd on your camera. Since the username/password is 260 | wellknown, this may be a security concern if others can connect into your wifi 261 | network. 262 | * I didn't test what will happen if your NFS server dies, so you are on your 263 | own... 264 | 265 | # FAQ: 266 | ## My camera died, how can I recover it? 267 | You will need to perform a SD card recovery with the following steps: 268 | 1. Download the matching firmware from wyze.com 269 | 2. Extract it to the root directory of an SD card, and make sure it's named 270 | "demo.bin". 271 | 3. Unplug the camera, and then insert the SD card 272 | 4. Hold the reset button while you plugin the camera 273 | 5. Wait for a couple seconds, release the reset button 274 | 6. After some time, your camera should have the factory firmware installed. 275 | 7. Please note this doesn't erase your configurations, which needs to be done 276 | through a factory reset method. 277 | 278 | ## How do I uninstall the hack? 279 | To uninstall the hack, I recommend you go through the SD card recovery method 280 | in #1 and then perform a factory reset. 281 | 282 | ## It keeps saying "installation failed", anyway to debug? 283 | On SD card there should be a file named "install.log" containing every command 284 | executed during the installation. It should give you a rough idea why it's 285 | failing. The most likely failure reason would be missing configuration file. 286 | Depending on how you install, you need to put the config.inc file to the right 287 | location for the installer to pickup. 288 | 289 | ## My NFS share has more than 1TB space, why does it say only 128GB in the app? 290 | The firwmare is designed to handle SD card, which usually has a much smaller 291 | size. With large size like this, the firmware will behave incorrectly. To avoid 292 | issues, the hack limits the emulated SD card to maximum 512GB. If your NFS share 293 | has more than 16GB free space, you will see an SD card with the size set to your 294 | available free space (capped to 128). If your share has a free space lower 295 | than 16GB, the device will see a 16GB SD card, with free space set to your 296 | actual free space. 297 | 298 | ## I don't get anything on my NFS share. What can go wrong? 299 | If you hear the "installation finished successfully" message, but still don't 300 | get any video recordings in the expected NFS share, it's very likely something 301 | wrong with your NFS share configuration. A couple ways to debug this: 302 | 1. Check the SD card information from the app, if it says SD card not installed, 303 | there is something wrong with your NFS config. 304 | 2. At this moment, you should have telnet enabled, try telnet into the camera 305 | and run "mount" to see if you have the NFS share correctly mounted. 306 | 3. If mount shows no NFS file system is mounted, try to mount the share manually 307 | with the following commands and see what error message you get: 308 | ``` 309 | source /params/wyze_hack.cfg 310 | mount $NFS_OPTIONS $NFS_ROOT /mnt 311 | ``` 312 | 5. At this point you will need to figure out what's wrong with your config file, 313 | and try to see if you can fix it by tweaking NFS_ROOT and NFS_OPTIONS in file 314 | `/params/wyze_hack.cfg`. You can edit this file over telnet with `vi` command. 315 | 316 | ## I see files but no recordings on my NFS shares. 317 | People report this kind of behavior and it seems to be caused by NFS share with 318 | size larger than a certain amount. Try make a smaller share or use "NFS quota" 319 | to limit the size. Issue #19 might be a related one. 320 | 321 | ## Known issues. 322 | 1. With release 0.4.00 and firmware 4.9.6.156 (WyzeCamV2) and 4.10.6.156 323 | (WyzeCam Pan), I noticed sometimes the NFS share will be unmounted. The root 324 | cause is still unclear, but I believe it may have something to do with 325 | firmware's bad SD card detection. 326 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | cd $(dirname $0) 4 | 5 | . wyze_hack/hack_ver.inc 6 | 7 | echo "Building release $WYZEHACK_VER ..." 8 | 9 | RELEASE=`echo "$WYZEHACK_VER"|tr '.' '_'` 10 | rm ./release/wyze_hacks_$RELEASE.zip 2>/dev/null || true 11 | 12 | mkdir -p .tmp/Upgrade 13 | echo FWGRADEUP=app > .tmp/Upgrade/PARA 14 | sed "s/__WYZEHACK_VER__/$WYZEHACK_VER/g" ./wyze_hack/stub.sh > .tmp/Upgrade/wyze_hack.sh 15 | tar \ 16 | --sort=name \ 17 | --owner=root:0 \ 18 | --group=root:0 \ 19 | --mtime='1970-01-01' \ 20 | --dereference \ 21 | -cz -O -C wyze_hack \ 22 | ./main.sh \ 23 | ./hack_ver.inc \ 24 | ./bin \ 25 | >>.tmp/Upgrade/wyze_hack.sh 26 | 27 | chmod a+x .tmp/Upgrade/wyze_hack.sh 28 | cp -r \ 29 | ./wyze_hack/upgraderun.sh \ 30 | ./wyze_hack/upgrade.sh \ 31 | ./wyze_hack/install_snd \ 32 | ./wyze_hack/rootfs \ 33 | .tmp/Upgrade/ 34 | 35 | tar \ 36 | --sort=name \ 37 | --owner=root:0 \ 38 | --group=root:0 \ 39 | --mtime='1970-01-01' \ 40 | --dereference -cf ./installer/FIRMWARE_660R.bin -C ./.tmp Upgrade 41 | rm -rf .tmp 42 | 43 | MD5=`md5sum ./installer/FIRMWARE_660R.bin | grep -oE "^[0-9a-f]*"` 44 | 45 | cat > ./installer/version.ini << EOL 46 | [SD] 47 | version=9.9.9.9 48 | type=1 49 | md5=$MD5 50 | EOL 51 | 52 | zip -r -j ./release/wyze_hacks_$RELEASE.zip ./installer -x *.inc *.tokens @ 53 | 54 | rm -rf ./release/release_$RELEASE 55 | unzip -q ./release/wyze_hacks_$RELEASE.zip -d ./release/release_$RELEASE 56 | if [ -f ./installer/config.inc ];then 57 | cp ./installer/config.inc ./release/release_$RELEASE 58 | fi 59 | echo "Release $WYZEHACK_VER build finished." -------------------------------------------------------------------------------- /info/http_server.md: -------------------------------------------------------------------------------- 1 | * Discovery 2 | 3 | By scanning the open ports of the wyze camera, I accidently discovered there is an http server running. This server allows you download any content on the SD card. I assume this is used to download the timelapse video, which is the reason why it requires your phone to be in the same network for doing that. 4 | 5 | * Analysis 6 | 7 | So I did some quick analysis on the http server. It's using a software called "boa". The server will allow you download any files in the SD card, as long as you know the path. The SD card is symbolic linked as "/tmp/www/SDPath", where "/tmp/www" is the document root directory of the http service. This means, for example, to download file "alarm/20200403/20200403_10_00_25.jpg", you will need to browse to "http://\/SDPath/alarm/20200403/20200403_17_00_25.jpg" 8 | 9 | This will be very useful to access the recordings on SD card. However, requiring knowlege of the file path makes this useless. Luckily, there is something else: There is a CGI script, called "hello.cgi" allowing you to browse "any" directory on the camera. So you need to specify a "name" parameter as the path of the directory, relative to the SD card root. So if I do "http://\/cgi-bin/hello.cgi?name=/alarm/20200403/", it will list all the files and sub-directories under that directory. 10 | 11 | * Automated SD card content downloading 12 | 13 | By combining the "hello.cgi" and the web server, we can build a simple script iterating all the directories and then download all the files. There is really no complexity of this script other than a nice user interface. 14 | 15 | * Security concerns 16 | 17 | Having a open http server running on the camera can be a security risk. To me the risk is kind of acceptable, because if someone can get into my homenetwork, then there are much bigger issues I need to worry about. Anway, it seems Wyze already noticed this can be a security concern and they are improving it: In older versions I remember this service is always running, but now it only starts when you go to the "view album" page under the timelapse menu from the app. So this serves as some sort of indirect authentication. The server will be shutdown after a certain time to avoid being misused. 18 | 19 | 20 | -------------------------------------------------------------------------------- /info/sdcard_hack.md: -------------------------------------------------------------------------------- 1 | The SD card file management has a command injection vulnerability: When the 2 | SD card is almost full, it will try to delete the older recordings. This is 3 | done by enumerating all the directories under "/record". However, it only 4 | looks for directories with 8 characters in the directory name. They are sorted 5 | by the alphabet order to find the oldest one. Once found, "rm -rf \" 6 | will be executed. The directory name is not sanitized before runing the 7 | command, leading to command injection. 8 | 9 | However, because of the 8-character directory name limitation, this exploit is 10 | not easy to user. So far, I found only one way to start "telnetd" using this: 11 | 12 | 1. Prepare a small size SD card, larger ones are OK but you will need more 13 | time to fill it. 14 | 2. Format the SD card 15 | 3. Fill the SD card with garbage data to almost full, not sure exactly how 16 | much, but for 1GB card, ~60% capacity is good enough. 17 | 4. Create a directory with path "/record/;telnetd" on the card. 18 | 5. Insert the card into the camera, wait for a couple minutes 19 | 6. Your camera should be running "telnetd" now, you can login with user name 20 | "root" and password "ismart12" 21 | 22 | Once you get a root shell, you can persist the telnetd by either modifying the 23 | starting script, or simply "echo 1>/configs/.Server_config" to tell the camera 24 | app to start telnetd for you. 25 | 26 | 27 | -------------------------------------------------------------------------------- /info/upgrade_hack.md: -------------------------------------------------------------------------------- 1 | WyzeCam support SD card based firmware upgrade and there is no signature 2 | check or anything. So this feature can be used to run arbitrary command 3 | if you have physical access to the device. 4 | 5 | This example shows how to enable telnetd using this method. To do this, 6 | following these steps: 7 | 1. Prepare an SD card with FAT format. 8 | 2. Copy copy the FIRMWARE_660R.bin and version.ini to the root directory 9 | of the SD card. 10 | 3. Insert the card into the camera and wait for a couple seconds. 11 | 4. You should now have telnetd running on the camera. 12 | 5. The camera is at a pending reboot state, so you may want to reboot 13 | the device manually to get it out of the state. Before doing that, make 14 | sure you remove the SD card otherwise you will get into upgrade loop. 15 | 6. The script is also generating non-empty /configs/.Server_config file 16 | to persist the telnetd across reboots. 17 | 18 | To create your own firmware file, moidfy the content in "app" sub-directory, 19 | and run build.sh to regenerate the firwmare and version.ini files. 20 | -------------------------------------------------------------------------------- /installer/config.inc.TEMPLATE: -------------------------------------------------------------------------------- 1 | # USER CONFIGURATIONS -- TO BE MODIFIED BY USER 2 | # Make your own copy of this file and put it under the root of SD card 3 | # with name "config.inc" when installing the hack. 4 | 5 | # You need to modify NFS_ROOT to point to your NFS share. The recordings 6 | # will be stored under $NFS_ROOT/WyzeCams/. 7 | export NFS_ROOT='192.168.1.10:/volume1' 8 | 9 | # Some NFS configurations may need special mount options. You can specify 10 | # them here. The default value is verified on my camera with a NFS v3 share 11 | # on my home server. 12 | # export NFS_OPTIONS='-o nolock,rw,noatime,nodiratime' 13 | 14 | # Timeout threshold for NFS connectivity check. If the NFS share is not 15 | # available for more than '$NFS_TIMEOUT' seconds, a reboot will be initiated 16 | # trying to resolve the network connectivity. The default value is 15 seconds. 17 | # export NFS_TIMEOUT=15 18 | 19 | # Uncomment and modify the AUTO_REBOOT variable to automatically reboot 20 | # the camera at a specific minute. Time is specified in "HH:MM" format 21 | # in local time. 22 | # export REBOOT_AT='13:16' 23 | 24 | # Uncomment and modify the ARCHIVE_OLDER_THAN variable to automatically 25 | # archive recording clips and alarm images if it's a certain days older. 26 | # export ARCHIVE_OLDER_THAN=5 27 | 28 | # Uncomment the following variable to sync the console log to the camera 29 | # folder in NFS share. Use with caution as the log contains sensitive 30 | # information and will cause your account compromise if leaked. 31 | # export SYNC_BOOT_LOG=1 32 | 33 | # In my setup I noticed at some occasions the camera starts rebooting frequently 34 | # which seems to be a result of dropping wifi connections. Running a "ping" 35 | # command in background seems to be solving this issue. Uncomment this variable 36 | # to enable this fix. 37 | # export PING_KEEPALIVE=1 38 | 39 | # Uncomment to enable voice notification when NFS network connection lost. The 40 | # value should be in range of 0 and 100 41 | # export NOTIFICATION_VOLUME=80 42 | 43 | # Uncomment and update this value to customize the hostname of the camera. By 44 | # default it will be set to -. 45 | # export CUSTOM_HOSTNAME='' 46 | 47 | # Uncomment this to enable automatically updating config.inc file based on file 48 | # /wyzehacks/config.new. This will be checked every one minute. 49 | # export AUTO_CONFIG=1 50 | 51 | # Uncomment this to enable automatically updating wyzehacks. To update, you need 52 | # to extract the latest wyzehacks release archive to $UPDATE_DIR and name it as 53 | # "release_?_?_??". The updating script will search for latest version based on 54 | # the folder name every one minute. 55 | # export AUTO_UPDATE=1 56 | 57 | # By default, variable "UPDATE_DIR" is set to "/mnt/WyzeCams/wyzehacks", which 58 | # is /WyzeCams/wyzehacks. If auto update is enabled, it will search 59 | # for updates in this directory. The default value allows all cameras share the 60 | # same update. You can customize this directory for a specific camera if you 61 | # want it to use different update (for example, testing dev builds). Some thing 62 | # under "/media/mmc" would be a good candidate because it maps to the camera 63 | # folder. 64 | # export UPDATE_DIR='/media/mmc/wyzehacks' 65 | 66 | # Uncomment and update this to run a custom script after the NFS mount finishes. 67 | # The script must already exists and executable. You can always use "/media/mmc" 68 | # to refer the camera folder on the NFS share. Or use "/mnt" to refer the root 69 | # of NFS share. The value of the variable can only contain script file name, no 70 | # command line arguments. The script will be executed in background. 71 | # export CUSTOM_SCRIPT='/media/mmc/scripts/myscript.sh' 72 | # export CUSTOM_SCRIPT='/mnt/WyzeCams/scripts/myscript.sh' 73 | 74 | # !!!ADVANCED USER!!! 75 | # This changes the default root password to whatever you specified. To 76 | # generate your own password hash, use the following command from a 77 | # linux environment: 78 | # openssl passwd -1 -salt 79 | # Use the result to replace sample hash ($1$MYSALT$3Sy1OLRk4kTa7P6fvzwp71) 80 | # export PASSWD_SHADOW='root:$1$MYSALT$3Sy1OLRk4kTa7P6fvzwp71:10933:0:99999:7:::' 81 | -------------------------------------------------------------------------------- /installer/remote_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd $(dirname $0) 3 | 4 | cp ./FIRMWARE_660R.bin ./firmware.bin 5 | 6 | if [ -f config.inc ]; then 7 | echo "Found local config file, including into firmware update archive..." 8 | mkdir -p ./Upgrade 9 | cp config.inc ./Upgrade 10 | tar -rvf ./firmware.bin Upgrade/config.inc 11 | fi 12 | 13 | DEBUG="" 14 | if [ "$1" = "--debug" ]; then 15 | DEBUG="$1" 16 | fi 17 | 18 | python3 -m pip install requests 19 | python3 ./wyze_updater.py --token ~/.wyze_token $DEBUG update \ 20 | -m WYZEC1-JZ -m WYZECP1_JEF -m WYZE_CAKP2JFUS -m WYZEDB3 -f ./firmware.bin -p 11808 21 | -------------------------------------------------------------------------------- /installer/telnet_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPT_DIR=`readlink -f $0` 3 | SCRIPT_DIR=`dirname $SCRIPT_DIR` 4 | 5 | OLD_WYZEHACKS_DIR=/system/wyze_hack 6 | 7 | tar -xf $SCRIPT_DIR/FIRMWARE_660R.bin -C /tmp/ 8 | 9 | if [ -f $OLD_WYZEHACKS_DIR/config.inc ]; 10 | then 11 | cp $OLD_WYZEHACKS_DIR/config.inc /tmp/Upgrade/ 12 | fi 13 | 14 | if [ -f $SCRIPT_DIR/config.inc ]; 15 | then 16 | cp $SCRIPT_DIR/config.inc /tmp/Upgrade/ 17 | fi 18 | 19 | /tmp/Upgrade/upgraderun.sh 20 | -------------------------------------------------------------------------------- /installer/wyze_updater.py: -------------------------------------------------------------------------------- 1 | ../WyzeUpdater/wyze_updater.py -------------------------------------------------------------------------------- /release/wyze_hacks_0_3_00.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_3_00.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_4_00.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_4_00.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_4_01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_4_01.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_4_02.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_4_02.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_4_03.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_4_03.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_4_04.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_4_04.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_01.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_02.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_02.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_03.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_03.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_04.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_04.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_05.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_05.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_06.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_06.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_07.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_07.zip -------------------------------------------------------------------------------- /release/wyze_hacks_0_5_08.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/release/wyze_hacks_0_5_08.zip -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .tmp 3 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: driver utils clean clean_all 2 | 3 | all: utils 4 | 5 | driver: | .tmp/.toolchain .tmp/.kernel 6 | make -C dummymmc CROSS_COMPILE=$(CURDIR)/.tmp/mips-gcc472-glibc216-64bit-master/bin/mips-linux-gnu- KDIR=$(CURDIR)/.tmp/kernel-master 7 | 8 | utils: | .tmp/.toolchain 9 | make -C utils CROSS_COMPILE=$(CURDIR)/.tmp/mips-gcc472-glibc216-64bit-master/bin/mips-linux-gnu- 10 | 11 | clean: 12 | # make -C dummymmc clean CROSS_COMPILE=$(CURDIR)/.tmp/mips-gcc472-glibc216-64bit-master/bin/mips-linux-gnu- KDIR=$(CURDIR)/.tmp/kernel-master 13 | make -C utils clean CROSS_COMPILE=$(CURDIR)/.tmp/mips-gcc472-glibc216-64bit-master/bin/mips-linux-gnu- 14 | 15 | clean_all: |clean 16 | rm -rf .tmp 17 | 18 | .tmp/.kernel: .tmp/kernel.zip 19 | unzip -q $< -d .tmp 20 | touch $@ 21 | 22 | .tmp/.toolchain: .tmp/toolchain.zip 23 | unzip -q $< -d .tmp 24 | touch $@ 25 | 26 | .tmp/kernel.zip: | .tmp 27 | wget -O $@ https://github.com/Dafang-Hacks/kernel/archive/master.zip 28 | 29 | .tmp/toolchain.zip: | .tmp 30 | wget -O $@ https://github.com/Dafang-Hacks/mips-gcc472-glibc216-64bit/archive/master.zip 31 | 32 | .tmp: 33 | mkdir .tmp -------------------------------------------------------------------------------- /src/dummymmc/.gitignore: -------------------------------------------------------------------------------- 1 | modules.order 2 | Module.symvers 3 | *.cmd 4 | .tmp_versions 5 | dummy_mmc.mod.c 6 | -------------------------------------------------------------------------------- /src/dummymmc/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE?=$(CURDIR)/../.tmp/mips-gcc472-glibc216-64bit-master/bin/mips-linux-gnu- 2 | KDIR?=$(CURDIR)/../.tmp/kernel-master 3 | 4 | MODULE_NAME := dummy_mmc 5 | 6 | all: modules 7 | 8 | .PHONY: modules clean 9 | 10 | #$(MODULE_NAME)-objs := dummy_mmc.o 11 | obj-m := $(MODULE_NAME).o 12 | 13 | modules: 14 | @$(MAKE) -C $(KDIR) modules_prepare 15 | @$(MAKE) -C $(KDIR) M=$(shell pwd) $@ 16 | 17 | clean: 18 | @$(MAKE) -C $(KDIR) clean 19 | @rm -rf *.o *~ .depend .*.cmd *.mod.c .tmp_versions *.ko *.symvers modules.order 20 | @rm -f dummy_mmc.ko 21 | -------------------------------------------------------------------------------- /src/dummymmc/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export CROSS_COMPILE=$(pwd)/../toolchain/bin/mips-linux-gnu- 3 | pushd ../kernel 4 | make modules_prepare 5 | popd 6 | make clean 7 | make 8 | -------------------------------------------------------------------------------- /src/dummymmc/dummy_mmc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dummy_mmc.c 3 | * 4 | * Copyright (C) 2012 Ingenic Semiconductor Co., Ltd. 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 2 as 8 | * published by the Free Software Foundation. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct proc_dir_entry *g_sinfo_proc; 16 | static __init int init_sinfo(void) 17 | { 18 | g_sinfo_proc = proc_mkdir("jz/mmc0", 0); 19 | if (!g_sinfo_proc) { 20 | printk("err: jz_proc_mkdir failed\n"); 21 | return -1; 22 | } 23 | 24 | return 0; 25 | } 26 | 27 | static __exit void exit_sinfo(void) 28 | { 29 | proc_remove(g_sinfo_proc); 30 | // misc_deregister(&misc_sinfo); 31 | } 32 | 33 | module_init(init_sinfo); 34 | module_exit(exit_sinfo); 35 | 36 | MODULE_DESCRIPTION("A dummy driver to create /proc/jz/mmc0 directory."); 37 | MODULE_LICENSE("GPL"); 38 | -------------------------------------------------------------------------------- /src/utils/Makefile: -------------------------------------------------------------------------------- 1 | CC = $(CROSS_COMPILE)gcc 2 | CPLUSPLUS = $(CROSS_COMPILE)g++ 3 | LD = $(CROSS_COMPILE)ld 4 | AR = $(CROSS_COMPILE)ar cr 5 | STRIP = $(CROSS_COMPILE)strip 6 | CFLAGS = $(INCLUDES) -O2 -Wall -march=mips32r2 -muclibc -std=c99 7 | LDFLAG = -muclibc -Wl,-gc-sections 8 | 9 | 10 | all: libhacks.so hackutils 11 | 12 | clean: 13 | @rm -rf *.o libhacks.so hackutils 14 | 15 | libhacks.so: libhacks.c 16 | $(CC) $(CFLAGS) --shared -fPIC -ldl $^ -o $@ 17 | 18 | hackutils: hackutils.c 19 | $(CC) $(CFLAGS) -L. -Wl,-rpath='$$ORIGIN' -lhacks $^ -o $@ -------------------------------------------------------------------------------- /src/utils/hackutils: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/src/utils/hackutils -------------------------------------------------------------------------------- /src/utils/hackutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "libhacks.h" 5 | 6 | int main(int argc, char* argv[]) { 7 | if (argc < 2) { 8 | printf("hackutils init|mmc_insert|mmc_remove [args]\n"); 9 | return -1; 10 | } 11 | 12 | if (strcmp(argv[1], "init") == 0) { 13 | return hack_init(); 14 | } else if (strcmp(argv[1], "mmc_insert") == 0) { 15 | return hack_nfs_event(1); 16 | } else if (strcmp(argv[1], "mmc_remove") == 0) { 17 | return hack_nfs_event(0); 18 | } else { 19 | return -1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/libhacks.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "libhacks.h" 20 | 21 | 22 | #define NLINK_MSG_LEN 1024 23 | 24 | #define SHM_KEY 0x11223344 25 | #define HACKDATA_MAGIC 0xDEADBEEF 26 | #define MAX_PATH 255 27 | 28 | #define ARRAY_SIZE(x) sizeof(x)/sizeof(x[0]) 29 | 30 | const char* const mmc_gpio_paths[] = { 31 | // 4.36.0.228 and older use GPIO50 to detect MMC insertion. 32 | "/sys/class/gpio/gpio50/value", 33 | // 4.36.0.252 and later uses GPIO62 to detect MMC insertion. 34 | "/sys/class/gpio/gpio62/value" 35 | }; 36 | 37 | typedef struct { 38 | uint32_t magic; 39 | uint32_t nfs_mounted; 40 | char mmc_gpio_redir[MAX_PATH]; 41 | } hackdata_t; 42 | 43 | hackdata_t* g_hackdata; 44 | static void __attribute__((constructor)) init() { 45 | int shmid = shmget(SHM_KEY, sizeof(*g_hackdata), IPC_CREAT); 46 | if (shmid < 0) { 47 | perror("shmget failed"); 48 | abort(); 49 | } 50 | 51 | g_hackdata = (hackdata_t*)shmat(shmid, NULL, 0); 52 | if (g_hackdata == NULL) { 53 | perror("shmat failed"); 54 | abort(); 55 | } 56 | 57 | if (g_hackdata->magic != HACKDATA_MAGIC) { 58 | memset(g_hackdata, 0, sizeof(*g_hackdata)); 59 | g_hackdata->magic = HACKDATA_MAGIC; 60 | } 61 | } 62 | 63 | #define DLSYM(func) \ 64 | static PFN_##func s_pfn = NULL; \ 65 | if (s_pfn == NULL) { \ 66 | s_pfn = (PFN_##func)dlsym(RTLD_NEXT, #func); \ 67 | if (s_pfn == NULL) { \ 68 | perror("dlsym returns NULL for '" #func "'"); \ 69 | abort(); \ 70 | } \ 71 | } 72 | 73 | int mount(const char *source, const char *target, 74 | const char *filesystemtype, unsigned long mountflags, const void *data) { 75 | 76 | typedef int (*PFN_mount)(const char *, const char *, const char *, unsigned long, const void *); 77 | DLSYM(mount); 78 | 79 | printf("mount(source=%s, target=%s, filesystemtype=%s)\n", source, target, filesystemtype); 80 | if (strcmp(target, "/media/mmc") == 0 || 81 | strcmp(target, "/media/mmcblk0p1") == 0) { 82 | printf("mount(%s, %s, %s) ==> 0\n", source, target, filesystemtype); 83 | return 0; 84 | } else { 85 | return s_pfn(source, target, filesystemtype, mountflags, data); 86 | } 87 | } 88 | 89 | int umount(const char *target) { 90 | typedef int (*PFN_umount)(const char*); 91 | DLSYM(umount); 92 | 93 | if (strcmp(target, "/media/mmc") == 0 || 94 | strcmp(target, "/media/mmcblk0p1") == 0) { 95 | printf("umount(%s) ==> 0\n", target); 96 | return 0; 97 | } else { 98 | return s_pfn(target); 99 | } 100 | } 101 | 102 | int statfs(const char *path, struct statfs *buf) { 103 | typedef int (*PFN_statfs)(const char *path, struct statfs *buf); 104 | DLSYM(statfs); 105 | 106 | int ret = s_pfn(path, buf); 107 | if (strncmp(path, "/media/mmc", strlen("/media/mmc")) != 0) { 108 | return ret; 109 | } 110 | 111 | if (ret) { 112 | perror("statfs failed.\n"); 113 | return ret; 114 | } 115 | 116 | printf( 117 | "statfs('%s'), orignal return values: " 118 | "f_type=%0lX, f_bsize=%lX, f_bfree=%lX, " 119 | "f_bavail=%lX, f_blocks=%lX\n", 120 | path, 121 | buf->f_type, buf->f_bsize, buf->f_bfree, 122 | buf->f_bavail, buf->f_blocks); 123 | 124 | if (buf->f_type == TMPFS_MAGIC) { 125 | printf("Not NFS share\n"); 126 | return ret; 127 | } 128 | 129 | unsigned long blocks_per_gb = 0x40000000 / buf->f_bsize; 130 | if (buf->f_bavail > blocks_per_gb * 16) { 131 | // If there are more than 16GB free space, we will emulate an 132 | // empty SD card whose total space equals to the free space up 133 | // to 128GB 134 | if (buf->f_bavail > blocks_per_gb * 128) { 135 | // Limit free space to 128GB 136 | buf->f_bavail = blocks_per_gb * 128; 137 | } 138 | buf->f_blocks = buf->f_bfree = buf->f_bavail; 139 | } else { 140 | // If there are less than 16GB free space, we will emulate an 141 | // SD card of 16GB, with free space to whatever left. 142 | buf->f_blocks = blocks_per_gb * 16; 143 | buf->f_bfree = buf->f_bavail; 144 | } 145 | 146 | printf( 147 | "statfs('%s'), modified return values: " 148 | "f_type=%lX, f_bsize=%lX, f_bfree=%lX, " 149 | "f_bavail=%lX, f_blocks=%lX\n", 150 | path, 151 | buf->f_type, buf->f_bsize, buf->f_bfree, 152 | buf->f_bavail, buf->f_blocks); 153 | return ret; 154 | } 155 | 156 | int access(const char *pathname, int mode) { 157 | typedef int (*PFN_access)(const char*, int); 158 | DLSYM(access); 159 | 160 | if (strcmp(pathname, "/proc/jz/mmc0") == 0 || 161 | strcmp(pathname, "/dev/mmcblk0p1") == 0 || 162 | strcmp(pathname, "/dev/mmcblk0") == 0) { 163 | return g_hackdata->nfs_mounted ? 0 : ENOENT; 164 | } else { 165 | return s_pfn(pathname, mode); 166 | } 167 | } 168 | 169 | int open(char * file, int oflag) { 170 | typedef int (*PFN_open)(const char*, int); 171 | DLSYM(open); 172 | 173 | if (g_hackdata->mmc_gpio_redir[0]) { 174 | for (int i = 0; i < ARRAY_SIZE(mmc_gpio_paths); i ++) { 175 | if (strcmp(file, mmc_gpio_paths[i]) == 0) { 176 | return s_pfn(g_hackdata->mmc_gpio_redir, oflag); 177 | } 178 | } 179 | } 180 | return s_pfn(file, oflag); 181 | } 182 | 183 | int hack_init() { 184 | if (g_hackdata->magic != HACKDATA_MAGIC) { 185 | return -1; 186 | } 187 | 188 | const char* p = getenv("MMC_GPIO_REDIR"); 189 | if (p) { 190 | strncpy(g_hackdata->mmc_gpio_redir, p, 191 | sizeof(g_hackdata->mmc_gpio_redir)); 192 | } 193 | return 0; 194 | } 195 | 196 | static int uevent_send(const char* msg) { 197 | int fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); 198 | if (fd < 0) { 199 | perror("socket failed"); 200 | return -1; 201 | } 202 | 203 | /* Declare for src NL sockaddr, dest NL sockaddr, nlmsghdr, iov, msghr */ 204 | struct sockaddr_nl src_addr; 205 | src_addr.nl_family = AF_NETLINK; //AF_NETLINK socket protocol 206 | src_addr.nl_pid = getpid(); //application unique id 207 | src_addr.nl_groups = 1; //specify not a multicast communication 208 | 209 | //attach socket to unique id or address 210 | bind(fd, (struct sockaddr *)&src_addr, sizeof(src_addr)); 211 | 212 | struct sockaddr_nl dest_addr; 213 | dest_addr.nl_family = AF_NETLINK; // protocol family 214 | dest_addr.nl_pid = 0; //destination process id 215 | dest_addr.nl_groups = 1; 216 | 217 | ssize_t size = sendto(fd, msg, strlen(msg) + 1, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); 218 | close(fd); 219 | 220 | if (size != strlen(msg) + 1) { 221 | perror("sendto failed"); 222 | return -1; 223 | } 224 | 225 | return 0; 226 | } 227 | 228 | #define MMC_INSERT_UEVENT_MSG "add@/devices/platform/jzmmc_v1.2.0/mmc_host/mmc0/mmc0:e624/block/mmcblk0/mmcblk0p1" 229 | #define MMC_REMOVE_UEVENT_MSG "add@/devices/platform/jzmmc_v1.2.0/mmc_host/mmc0/mmc0:e624/block/mmcblk0/mmcblk0p1" 230 | 231 | int hack_nfs_event(int nfs_mounted) { 232 | g_hackdata->nfs_mounted = nfs_mounted; 233 | uevent_send(nfs_mounted ? MMC_INSERT_UEVENT_MSG : MMC_REMOVE_UEVENT_MSG); 234 | 235 | return 0; 236 | } 237 | -------------------------------------------------------------------------------- /src/utils/libhacks.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_HACKS_H__ 2 | #define __LIB_HACKS_H__ 3 | 4 | int hack_init(); 5 | int hack_nfs_event(int nfs_mounted); 6 | 7 | #endif //__LIB_HACKS_H__ 8 | -------------------------------------------------------------------------------- /src/utils/libhacks.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/src/utils/libhacks.so -------------------------------------------------------------------------------- /tools/v3/README.md: -------------------------------------------------------------------------------- 1 | # Background 2 | Wyze hack relies on replacing `/system/init/app_init.sh` to persist itself and 3 | get loaded. This means at installation time it needs to be able to write to 4 | `/system/init`. This works for v1, v2 and PAN camera because `/system/` is part 5 | of `app` image, which is a `j2ffs` type image. This type is mounted writable. 6 | 7 | However, with v3, the `app` image is now a `sqashfs`. This type of image is 8 | always mounted readonly. This makes writing to `/system/` impossible at runtime. 9 | So the only way to modify this directory is to modify the device image offline, 10 | which basically means generating a new `demo.bin` file and flashing it. But even 11 | this is not possible: V3 camera introduced signature verification on `demo.bin`. 12 | Unless we know Wyze's private signing key, or we found a vulnerabilty in the 13 | related code, a modified `demo.bin` won't be accepted. 14 | 15 | Luckily, the signature verification is only for SD card recovery, not for 16 | booting. So once you find a way to modify the flash content, the bootloader 17 | will happily load whatever in the flash. That leads to this method. 18 | 19 | # Details 20 | In this method, we will use Wyze's update mechanism to push special update onto 21 | the camera to `fix` or `initialize` the camera so it can load wyze hack binary. 22 | The update contains a modified `rootfs` image, and a script to burn the image 23 | to the rootfs partition. Once applied, the modification we added will search for 24 | installed hack and load it at boot time. 25 | 26 | The reason we modify `rootfs` instead of `app` partition, is that `app` 27 | partition updates frequently, which means the modification will be lost when an 28 | update is applied, but `rootfs` partition doesn't seem to be updated at all, so 29 | this allows our modification persist through updates. 30 | 31 | # Installation 32 | The `initialization` of v3 camera's `rootfs` has been integrated into the wyze 33 | hack installation script, so no special treatment is needed. However, it's worth 34 | mention first time installation on v3 camera will be much longer due to the 35 | rootfs flashing process. 36 | 37 | # Some notes 38 | * If you have a v3 camera with serial cable wired, you can login with `root` and 39 | password `WYom2020` from the serial console. 40 | -------------------------------------------------------------------------------- /tools/v3/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | build_root() { 4 | DEMO_IN=$1 5 | ROOTFS_OUT=$2 6 | 7 | if [ ! -f "$DEMO_IN" ];then 8 | echo "Input file [$DEMO_IN] doesn't exist" 9 | return 1 10 | fi 11 | 12 | echo "Processing input image $DEMO_IN..." 13 | 14 | TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX) 15 | echo "Using temporary directory $TMP_DIR..." 16 | 17 | KERNEL_OFFSET=$((0x000040)) 18 | ROOTFS_OFFSET=$((0x1F0040)) 19 | APPFS_OFFSET=$((0x5C0040)) 20 | 21 | #dd if=${DEMO_IN} of=$TMP_DIR/kernel.bin skip=$KERNEL_OFFSET count=$(($ROOTFS_OFFSET-$KERNEL_OFFSET)) bs=1 22 | dd if=${DEMO_IN} of=$TMP_DIR/rootfs.bin skip=$ROOTFS_OFFSET count=$(($APPFS_OFFSET-$ROOTFS_OFFSET)) bs=1 >/dev/null 2>&1 23 | #dd if=${DEMO_IN} of=$TMP_DIR/appfs.bin skip=$APPFS_OFFSET bs=1 24 | 25 | unsquashfs -d $TMP_DIR/rootfs $TMP_DIR/rootfs.bin >/dev/null 26 | ROOTFS_VER=$(grep -i AppVer $TMP_DIR/rootfs/usr/app.ver | sed -E 's/^.*=[[:space:]]*([0-9.]+)[[:space:]]*$/\1/g') 27 | echo "Root FS version is '$ROOTFS_VER'." 28 | 29 | if [ -f $ROOTFS_OUT/$ROOTFS_VER.bin ]; then 30 | echo "File $ROOTFS_OUT/$ROOTFS_VER.bin already exists..." 31 | else 32 | cp ./rcS $TMP_DIR/rootfs/etc/init.d/rcS 33 | touch $TMP_DIR/rootfs/etc/init.d/.wyzehacks 34 | mksquashfs $TMP_DIR/rootfs/ $TMP_DIR/rootfs2.bin -noappend -comp xz >/dev/null 35 | 36 | 37 | #ORIG_SIZE=$(wc -c < $TMP_DIR/rootfs.bin) 38 | #NEW_SIZE=$(wc -c < $TMP_DIR/rootfs2.bin) 39 | #dd if=/dev/zero bs=1 count=$(($ORIG_SIZE - $NEW_SIZE)) >> $TMP_DIR/rootfs2.bin 40 | mkdir -p $ROOTFS_OUT 41 | cp $TMP_DIR/rootfs2.bin $ROOTFS_OUT/$ROOTFS_VER.bin 42 | echo "Result image generated as '$ROOTFS_OUT/$ROOTFS_VER.bin'." 43 | fi 44 | rm -rf $TMP_DIR 45 | } 46 | 47 | set -e 48 | cd $(dirname $0) 49 | 50 | for dir in firmwares/*/; do 51 | build_root ${dir}demo_wcv3.bin ../../wyze_hack/rootfs 52 | done 53 | -------------------------------------------------------------------------------- /tools/v3/firmwares/4.36.0.228/demo_wcv3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/tools/v3/firmwares/4.36.0.228/demo_wcv3.bin -------------------------------------------------------------------------------- /tools/v3/firmwares/4.36.0.228/info.txt: -------------------------------------------------------------------------------- 1 | The following firmware releases are using the same root fs: 2 | 4.36.0.125 3 | 4.36.0.252 4 | -------------------------------------------------------------------------------- /tools/v3/rcS: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Set mdev 4 | echo /sbin/mdev > /proc/sys/kernel/hotplug 5 | /sbin/mdev -s && echo "mdev is ok......" 6 | 7 | echo " __________________________________ 8 | | | 9 | | | 10 | | | 11 | | | 12 | | _ _ _ _ | 13 | || | | |_ _ __ _| | __ _(_) | 14 | || |_| | | | |/ _| | | _ / _| | | | 15 | || _ | |_| | (_| | |_| | (_| | | | 16 | ||_| |_|\__,_|\__,_|_____|\__,_|_| | 17 | | | 18 | | | 19 | |_____2020_WYZE_CAM_V3_@HUALAI_____| 20 | " 21 | 22 | # create console and null node for nfsroot 23 | #mknod -m 600 /dev/console c 5 1 24 | #mknod -m 666 /dev/null c 1 3 25 | 26 | # Set Global Environment 27 | export PATH=/bin:/sbin:/usr/bin:/usr/sbin 28 | export PATH=/system/bin:$PATH 29 | export LD_LIBRARY_PATH=/system/lib 30 | export LD_LIBRARY_PATH=/thirdlib:$LD_LIBRARY_PATH 31 | 32 | # networking 33 | ifconfig lo up 34 | #ifconfig eth0 192.168.1.80 35 | 36 | # Set the system time from the hardware clock 37 | #hwclock -s 38 | 39 | # Mount driver partition 40 | mount -t squashfs /dev/mtdblock3 /system 41 | 42 | # Mount configs partition 43 | mount -t jffs2 /dev/mtdblock6 /configs 44 | 45 | # Run init script 46 | if [ -f /configs/wyze_hack.sh ]; then 47 | /configs/wyze_hack.sh & 48 | elif [ -f /system/init/app_init.sh ]; then 49 | /system/init/app_init.sh & 50 | fi 51 | 52 | # Run liteOta upgrade app 53 | #/liteOta & 54 | -------------------------------------------------------------------------------- /wyze_hack/bin/audioplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/wyze_hack/bin/audioplay -------------------------------------------------------------------------------- /wyze_hack/bin/blkid: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if echo "$@" | grep -q "/dev/mmcblk0p1"; 3 | then 4 | echo '/dev/mmcblk0p1: UUID="98BF-D9A9" TYPE="vfat"' 5 | else 6 | /sbin/blkid "$@" 7 | fi 8 | -------------------------------------------------------------------------------- /wyze_hack/bin/hackutils: -------------------------------------------------------------------------------- 1 | ../../src/utils/hackutils -------------------------------------------------------------------------------- /wyze_hack/bin/libhacks.so: -------------------------------------------------------------------------------- 1 | ../../src/utils/libhacks.so -------------------------------------------------------------------------------- /wyze_hack/bin/mount: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if echo "$@" | grep -q "/dev/mmcblk0p1"; 3 | then 4 | echo "Skipping SD card mounting..." 5 | exit 6 | fi 7 | 8 | if echo "$@" | grep -q "remount,rw /media/mmcblk0p1"; 9 | then 10 | echo "Skipping SD card remounting..." 11 | exit 12 | fi 13 | 14 | echo "Not SD card, passing through..." 15 | /bin/mount "$@" 16 | -------------------------------------------------------------------------------- /wyze_hack/bin/reboot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPT_DIR=$(dirname $(readlink -f $0)) 3 | $SCRIPT_DIR/../main.sh reboot $@ 4 | -------------------------------------------------------------------------------- /wyze_hack/bin/tf_prepare: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Calling tf_prepare $@" 3 | exit 0 4 | -------------------------------------------------------------------------------- /wyze_hack/hack_ver.inc: -------------------------------------------------------------------------------- 1 | export WYZEHACK_VER=0.5.08 2 | -------------------------------------------------------------------------------- /wyze_hack/install_snd/begin.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/wyze_hack/install_snd/begin.wav -------------------------------------------------------------------------------- /wyze_hack/install_snd/failed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/wyze_hack/install_snd/failed.wav -------------------------------------------------------------------------------- /wyze_hack/install_snd/finished.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/wyze_hack/install_snd/finished.wav -------------------------------------------------------------------------------- /wyze_hack/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ -z $WYZEHACK_DBG ] || set -x 3 | 4 | export WYZEHACK_DIR=$(dirname $(readlink -f $0)) 5 | export WYZEAPP_VER=$(grep -i AppVer /system/bin/app.ver | sed -E 's/^.*=[[:space:]]*([0-9.]+)[[:space:]]*$/\1/g') 6 | export WYZEHACK_CFG=/configs/wyze_hack.cfg 7 | export WYZEHACK_BIN=/configs/wyze_hack.sh 8 | 9 | if grep "wyze_hack.sh" /etc/init.d/rcS; then 10 | export WYZEINIT_SCRIPT=/system/init/app_init.sh 11 | else 12 | export WYZEINIT_SCRIPT=/system/init/app_init_orig.sh 13 | fi 14 | 15 | case $WYZEAPP_VER in 16 | 3.9.*) 17 | # Cam V1 18 | export DEVICE_ID=$(grep -oE "NETRELATED_MAC=[A-F0-9]{12}" /params/config/.product_config | sed 's/NETRELATED_MAC=//g') 19 | export DEVICE_MODEL="V1" 20 | export SPEAKER_GPIO=63 21 | ;; 22 | 23 | 4.9.*) 24 | # Cam V2 25 | export DEVICE_ID=$(grep -oE "NETRELATED_MAC=[A-F0-9]{12}" /params/config/.product_config | sed 's/NETRELATED_MAC=//g') 26 | export DEVICE_MODEL="V2" 27 | export SPEAKER_GPIO=63 28 | ;; 29 | 30 | 4.28.*) 31 | # Cam V2 RTSP 32 | export DEVICE_ID=$(grep -oE "NETRELATED_MAC=[A-F0-9]{12}" /params/config/.product_config | sed 's/NETRELATED_MAC=//g') 33 | export DEVICE_MODEL="V2" 34 | export SPEAKER_GPIO=63 35 | ;; 36 | 37 | 4.10.*) 38 | # Cam PAN 39 | export DEVICE_ID=$(grep -oE "NETRELATED_MAC=[A-F0-9]{12}" /params/config/.product_config | sed 's/NETRELATED_MAC=//g') 40 | export DEVICE_MODEL="PAN" 41 | export SPEAKER_GPIO=63 42 | ;; 43 | 44 | 4.29.*) 45 | # Cam PAN RTSP 46 | export DEVICE_ID=$(grep -oE "NETRELATED_MAC=[A-F0-9]{12}" /params/config/.product_config | sed 's/NETRELATED_MAC=//g') 47 | export DEVICE_MODEL="PAN" 48 | export SPEAKER_GPIO=63 49 | ;; 50 | 51 | 4.25.*) 52 | # Doorbell 53 | export DEVICE_ID=$(grep -E -o CONFIG_INFO=[0-9A-F]+ /params/config/.product_config | cut -c 13-24) 54 | export DEVICE_MODEL="DB" 55 | export SPEAKER_GPIO=63 56 | ;; 57 | 58 | 4.36.*) 59 | # Cam V3 60 | export DEVICE_ID=$(grep -E -o CONFIG_INFO=[0-9A-F]+ /configs/.product_config | cut -c 13-24) 61 | export DEVICE_MODEL="V3" 62 | export SPEAKER_GPIO=63 63 | export MMC_GPIO_REDIR="$WYZEHACK_DIR/mmc_gpio_value.txt" 64 | ;; 65 | 66 | *) 67 | # Unknown 68 | export WYZEINIT_SCRIPT=/system/init/app_init.sh 69 | ;; 70 | esac 71 | 72 | # Hack vesion 73 | source $WYZEHACK_DIR/hack_ver.inc 74 | 75 | # User configuration 76 | [ -f $WYZEHACK_CFG ] && source $WYZEHACK_CFG 77 | [ -z $WYZEHACK_DBG ] || set -x 78 | 79 | # Default values 80 | export NFS_TIMEOUT="${NFS_TIMEOUT:-15}" 81 | export NFS_OPTIONS="${NFS_OPTIONS:--o nolock,rw,noatime,nodiratime}" 82 | export UPDATE_DIR="${UPDATE_DIR:-/mnt/WyzeCams/wyzehacks}" 83 | 84 | # These features rely on NFS 85 | if [ -z "$NFS_ROOT" ]; then 86 | export ARCHIVE_OLDER_THAN= 87 | export SYNC_BOOT_LOG= 88 | export AUTO_UPDATE= 89 | export AUTO_CONFIG= 90 | fi 91 | 92 | play_sound() { 93 | echo "1">/sys/class/gpio/gpio${SPEAKER_GPIO}/value 94 | $WYZEHACK_DIR/bin/audioplay $@ 1>/dev/null 2>&1 95 | echo "0">/sys/class/gpio/gpio${SPEAKER_GPIO}/value 96 | } 97 | 98 | set_passwd() { 99 | /bin/umount /etc 100 | rm -rf /tmp/etc 101 | cp -r /etc /tmp/ 102 | echo $1 >/tmp/etc/shadow 103 | /bin/mount -o bind /tmp/etc /etc 104 | } 105 | 106 | wait_wlan() { 107 | while true 108 | do 109 | if ifconfig wlan0 | grep "inet addr"; 110 | then 111 | break 112 | fi 113 | 114 | echo "WyzeHack: wlan0 not ready yet..." 115 | sleep 10 116 | done 117 | } 118 | 119 | init_rootfs() { 120 | if grep "wyze_hack.sh" /etc/init.d/rcS; then 121 | echo "WyzeHack: Device already initialized" 122 | return 0 123 | fi 124 | 125 | if [ "$DEVICE_MODEL" != "V3" ]; then 126 | echo "WyzeHack: No need to initialize root fs" 127 | return 0 128 | fi 129 | 130 | ROOTFS_VER=$(grep -i AppVer /usr/app.ver | sed -E 's/^.*=[[:space:]]*([0-9.]+)[[:space:]]*$/\1/g') 131 | for x in $1/* 132 | do 133 | if grep -F "[$ROOTFS_VER]" $x/versions.txt; then 134 | ROOTFS_BIN=$x/rootfs.bin 135 | break 136 | fi 137 | done 138 | 139 | if [ -z $ROOTFS_BIN ]; then 140 | echo "WyzeHack: No root fs image found for version $ROOTFS_VER" 141 | return 1 142 | fi 143 | if [ ! -f $ROOTFS_BIN ]; then 144 | echo "WyzeHack: Root fs image $ROOTFS_BIN does not exist" 145 | return 1 146 | fi 147 | 148 | # "hook_init" checks /etc/init.d/rcS to detect if rootfs is hard modified 149 | # and it will skip the hook process if it is. However, since we bind mount 150 | # /etc changes in this step will not show up in /etc/init.d/rcS. So to avoid 151 | # that, we output some dummy lines to skip hook process. 152 | echo "#wyze_hack.sh" >> /tmp/etc/init.d/rcS 153 | 154 | sync 155 | echo "WyzeHack: writing rootfs image $ROOTFS_BIN ..." 156 | flashcp -v $ROOTFS_BIN /dev/mtd2 157 | sync 158 | } 159 | 160 | hook_init() { 161 | if grep "wyze_hack.sh" /etc/init.d/rcS; then 162 | echo "WyzeHack: hard modified, no need to hook ..." 163 | return 0 164 | fi 165 | 166 | if [ ! -f $WYZEHACK_BIN ]; 167 | then 168 | echo "WyzeHack: wyze hack main binary not found: $WYZEHACK_BIN" 169 | return 1 170 | fi 171 | 172 | if [ ! -f $WYZEHACK_CFG ]; 173 | then 174 | echo "WyzeHack: wyze hack config file not found: $WYZEHACK_CFG" 175 | return 1 176 | fi 177 | 178 | local SYSTEM_DIR=${1:-/system} 179 | if [ ! -L $SYSTEM_DIR/init/app_init.sh ]; 180 | then 181 | echo "WyzeHack: backing up app_init.sh ..." 182 | cp $SYSTEM_DIR/init/app_init.sh $SYSTEM_DIR/init/app_init_orig.sh 183 | fi 184 | 185 | local APP_INIT=$(readlink $SYSTEM_DIR/init/app_init.sh) 186 | if [ "$APP_INIT" != "$WYZEHACK_BIN" ]; 187 | then 188 | echo "WyzeHack: setting up symlink ..." 189 | ln -s -f $WYZEHACK_BIN $SYSTEM_DIR/init/app_init.sh 190 | fi 191 | 192 | ls -la $SYSTEM_DIR/init 193 | return 0 194 | } 195 | 196 | log_init() { 197 | LOG_CNT=0 198 | LOG_DIR=/media/mmcblk0p1/wyzehacks/log 199 | mkdir -p $LOG_DIR 200 | 201 | if [ -z "$SYNC_BOOT_LOG" ]; 202 | then 203 | # This is to record device reboot time when log sync is not enabled 204 | LOG_CNT=$(ls $LOG_DIR/reboot_* | wc -l) 205 | let LOG_CNT=$LOG_CNT+1 206 | touch $LOG_DIR/reboot_$LOG_CNT 207 | return 208 | fi 209 | 210 | # Wait until the NTP client updated local time, so we can have correct log 211 | # timestamp 212 | touch $LOG_DIR/.timestamp 213 | while true 214 | do 215 | sleep 5 216 | touch /tmp/.timestamp 217 | if [ /tmp/.timestamp -ot $LOG_DIR/.timestamp ]; 218 | then 219 | echo "WyzeHack: Waiting for system clock sync..." 220 | continue 221 | fi 222 | 223 | break 224 | done 225 | 226 | LOG_TS=$(date +"%Y_%m_%d_%H%M%S") 227 | } 228 | 229 | log_sync() { 230 | local LOGSIZE=$(wc /tmp/boot.log -c| awk '{print $1}') 231 | if [ "$LOGSIZE" -gt "1000000" ]; 232 | then 233 | kill -9 $LOG_TAIL_PID 234 | unset LOG_TAIL_PID 235 | cp /tmp/boot.log /tmp/boot1.log 236 | echo "WyzeHack: Log truncated" > /tmp/boot.log 237 | fi 238 | 239 | if [ -z "$LOG_TAIL_PID" ]; 240 | then 241 | tail -n +0 -f /tmp/boot.log > $LOG_DIR/boot_${LOG_TS}_${LOG_CNT}.log 2>&1 & 242 | LOG_TAIL_PID=$! 243 | let LOG_CNT=$LOG_CNT+1 244 | fi 245 | } 246 | 247 | do_archive() { 248 | mkdir -p /media/mmcblk0p1/archive/record 249 | mkdir -p /media/mmcblk0p1/archive/alarm 250 | 251 | for DAY in $(ls -d /media/mmcblk0p1/record/???????? 2>/dev/null| sort | head -n -$ARCHIVE_OLDER_THAN 2>/dev/null| grep -oE "[0-9]{8}$"); 252 | do 253 | local SRC_DIR=/media/mmcblk0p1/record/$DAY 254 | local DST_DIR=/media/mmcblk0p1/archive/record/$DAY 255 | 256 | if [ ! -d $SRC_DIR ]; 257 | then 258 | continue 259 | fi 260 | 261 | for SRC_FILE in $(find "$SRC_DIR" -name *.mp4 2>/dev/null| grep -oE "[0-9]{2}/[0-9]{2}\.mp4$"); 262 | do 263 | local DST_FILE=$(echo "${DAY}_${SRC_FILE}" | sed 's,/,_,g') 264 | if [ ! -d $DST_DIR ]; 265 | then 266 | mkdir -p $DST_DIR 267 | fi 268 | 269 | mv -n $SRC_DIR/$SRC_FILE $DST_DIR/$DST_FILE 270 | done 271 | rmdir $SRC_DIR/* $SRC_DIR 272 | done 273 | 274 | for DAY in $(ls -d /media/mmcblk0p1/alarm/???????? 2>/dev/null| sort | head -n -$ARCHIVE_OLDER_THAN 2>/dev/null| grep -oE "[0-9]{8}$"); 275 | do 276 | local SRC_DIR=/media/mmcblk0p1/alarm/$DAY 277 | local DST_DIR=/media/mmcblk0p1/archive/alarm/$DAY 278 | 279 | mv -n $SRC_DIR $DST_DIR 280 | done 281 | } 282 | 283 | check_config() { 284 | if [ ! -f "/media/mmc/wyzehacks/config.new" ];then 285 | echo "WyzeHack: New config file not found, skipping..." 286 | return 0 287 | fi 288 | 289 | echo "WyzeHack: Found new config file, checking..." 290 | sed 's/\r$//' /media/mmc/wyzehacks/config.new > /tmp/tmp_config 291 | echo "WyzeHack: New config content:" 292 | echo "WyzeHack: =====================" 293 | cat /tmp/tmp_config 294 | echo "WyzeHack: =====================" 295 | 296 | echo "WyzeHack: Applying new config" 297 | cp /tmp/tmp_config $WYZEHACK_CFG 298 | rm /media/mmc/wyzehacks/config.new 299 | return 1 300 | } 301 | 302 | check_update() { 303 | echo "WyzeHack: AUTO_UPDATE enabled, checking for update in $UPDATE_DIR..." 304 | local LATEST_UPDATE=$(ls -d $UPDATE_DIR/release_?_?_?? | sort -r | head -1) 305 | if [ -z "$LATEST_UPDATE" ]; then 306 | echo "WyzeHack: Found no updates, skipping..." 307 | return 0 308 | fi 309 | 310 | echo "WyzeHack: Found update $LATEST_UPDATE, checking..." 311 | local UPDATE_FLAG=$LATEST_UPDATE/${DEVICE_ID}.done 312 | if [ -f "$UPDATE_FLAG" ]; then 313 | echo "WyzeHack: Update $LATEST_UPDATE already installed, skipping..." 314 | return 0 315 | fi 316 | 317 | echo "WyzeHack: Installing update from $LATEST_UPDATE..." 318 | touch $UPDATE_FLAG 319 | 320 | if ! $LATEST_UPDATE/telnet_install.sh; then 321 | echo "WyzeHack: Failed to install update..." 322 | return 0 323 | fi 324 | 325 | echo "WyzeHack: Update installed." 326 | return 1 327 | } 328 | 329 | check_reboot() { 330 | # Unfortunately we don't have cron job or at command in this environment, so use 331 | # a poorman's implementation 332 | local CUR_TIME=$(date +"%H:%M") 333 | if [ "$CUR_TIME" = "$REBOOT_AT" ]; 334 | then 335 | return 1 336 | else 337 | return 0 338 | fi 339 | } 340 | 341 | check_uninstall() { 342 | if [ -f /media/mmc/wyzehacks/uninstall ]; then 343 | echo "WyzeHack: Uninstalling wyze hacks..." 344 | rm -f /media/mmc/wyzehacks/uninstall 345 | if cp $WYZEINIT_SCRIPT /system/init/app_init.sh; 346 | then 347 | rm -f $WYZEHACK_BIN 348 | rm -f $WYZEHACK_CFG 349 | echo "Uninstallation completed" > /media/mmc/wyzehacks/uninstall.done 350 | return 1 351 | else 352 | echo "Uninstallation failed" > /media/mmc/wyzehacks/uninstall.failed 353 | fi 354 | fi 355 | return 0 356 | } 357 | 358 | mount_nfs() { 359 | local NFS_MOUNT="/bin/mount $NFS_OPTIONS" 360 | local RETRY_COUNT=0 361 | while true 362 | do 363 | # We will try mount the NFS for 10 times, and fail if still not available 364 | let RETRY_COUNT=$RETRY_COUNT+1 365 | if [ $RETRY_COUNT -gt 100 ]; then 366 | return 1 367 | fi 368 | 369 | if ! /bin/mount | grep -q "$NFS_ROOT on /mnt"; 370 | then 371 | echo "WyzeHack: $NFS_ROOT not mounted, try mounting to /mnt..." 372 | if ! $NFS_MOUNT $NFS_ROOT /mnt; 373 | then 374 | echo "WyzeHack: [$NFS_MOUNT $NFS_ROOT /mnt] failed, will retry..." 375 | sleep 10 376 | continue 377 | fi 378 | fi 379 | 380 | local CAM_DIR=/mnt/WyzeCams/$DEVICE_ID 381 | for DIR in /mnt/WyzeCams/*/; 382 | do 383 | if [ -f "$DIR/.mac_$DEVICE_ID" ]; 384 | then 385 | CAM_DIR="$DIR" 386 | break 387 | fi 388 | done 389 | 390 | echo "WyzeHack: Mounting directory $CAM_DIR as SD card" 391 | if [ ! -d "$CAM_DIR" ]; 392 | then 393 | echo "WyzeHack: Creating data directory [$CAM_DIR]" 394 | if ! mkdir -p "$CAM_DIR"; 395 | then 396 | echo "WyzeHack: [mkdir -p $CAM_DIR] failed, will retry..." 397 | sleep 1 398 | continue 399 | fi 400 | fi 401 | 402 | echo "WyzeHack: Mounting camera directory $NFS_ROOT/$CAM_DIR on /media/mmcblk0p1" 403 | mkdir -p /media/mmcblk0p1 404 | if ! mount -o bind "$CAM_DIR" /media/mmcblk0p1; 405 | then 406 | echo "WyzeHack: mount $CAM_DIR as /media/mmcblk0p1 failed, will retry..." 407 | sleep 5 408 | continue 409 | fi 410 | 411 | echo "WyzeHack: Mounting camera directory $NFS_ROOT/$CAM_DIR on /media/mmc" 412 | mkdir -p /media/mmc 413 | if ! mount -o bind "$CAM_DIR" /media/mmc; 414 | then 415 | echo "WyzeHack: mount $CAM_DIR as /media/mmc failed, will retry..." 416 | sleep 5 417 | continue 418 | fi 419 | 420 | break 421 | done 422 | 423 | echo "WyzeHack: Notifying iCamera about SD card insertion event..." 424 | if [ ! -z $MMC_GPIO_REDIR ]; then 425 | echo "0" > $MMC_GPIO_REDIR 426 | fi 427 | $WYZEHACK_DIR/bin/hackutils mmc_insert 428 | 429 | # Mark this directory for this camera 430 | touch /media/mmcblk0p1/.mac_$DEVICE_ID 431 | 432 | return 0 433 | } 434 | 435 | # Detecting NFS share mount failure 436 | check_nfs() { 437 | /bin/mount > /tmp/mount.txt 438 | if ! grep "/media/mmcblk0p1 type nfs" /tmp/mount.txt > /dev/null 2>&1; 439 | then 440 | echo "WyzeHack: NFS no longer mounted as /media/mmcblk0p1" 441 | return 1 442 | fi 443 | 444 | if ! grep "/media/mmc type nfs" /tmp/mount.txt > /dev/null 2>&1; 445 | then 446 | echo "WyzeHack: NFS no longer mounted as /media/mmc" 447 | return 1 448 | fi 449 | 450 | # A new version BusyBox changed the command line format of timeout, causing 451 | # lots of false alarms. 452 | if timeout --help 2>&1| grep -F '[-t SECS]' > /dev/null; then 453 | TIMEOUT_ARGS="-t $NFS_TIMEOUT" 454 | else 455 | TIMEOUT_ARGS="$NFS_TIMEOUT" 456 | fi 457 | 458 | if ! timeout $TIMEOUT_ARGS ls /media/mmcblk0p1 > /dev/null 2>&1; 459 | then 460 | echo "WyzeHack: NFS no longer mounted as /media/mmcblk0p1" 461 | return 1 462 | fi 463 | 464 | return 0 465 | } 466 | 467 | sys_monitor() { 468 | while true; do 469 | local REBOOT_FLAG=0 470 | if ! pgrep -f telnetd >/dev/null 2>&1; then 471 | echo "WyzeHack: Starting telnetd..." 472 | busybox telnetd 473 | fi 474 | 475 | if [ ! -z "$ARCHIVE_OLDER_THAN" ]; then 476 | do_archive 477 | fi 478 | 479 | if [ "$SYNC_BOOT_LOG" = "1" ]; then 480 | if pidof syslogd > /dev/null 2>&1 && \ 481 | ! pidof logread > /dev/null 2>&1 ; then 482 | logread 483 | logread -f & 484 | fi 485 | log_sync 486 | fi 487 | 488 | if [ "$AUTO_UPDATE" = "1" ]; then 489 | check_update 490 | let REBOOT_FLAG=$REBOOT_FLAG+$? 491 | fi 492 | 493 | if [ "$AUTO_CONFIG" = "1" ]; then 494 | check_config 495 | let REBOOT_FLAG=$REBOOT_FLAG+$? 496 | fi 497 | 498 | if [ ! -z "$REBOOT_AT" ]; then 499 | check_reboot 500 | let REBOOT_FLAG=$REBOOT_FLAG+$? 501 | fi 502 | 503 | if ! check_uninstall; then 504 | let REBOOT_FLAG=$REBOOT_FLAG+1 505 | fi 506 | 507 | if [ ! -z "$NFS_MOUNTED" ]; then 508 | ifconfig > /media/mmcblk0p1/ifconfig.txt 2>&1 509 | if ! check_nfs; then 510 | if [ ! -z "$NOTIFICATION_VOLUME" ]; 511 | then 512 | play_sound /usr/share/notify/CN/user_need_check.wav $NOTIFICATION_VOLUME 513 | fi 514 | let REBOOT_FLAG=$REBOOT_FLAG+1 515 | fi 516 | fi 517 | 518 | if [ "$REBOOT_FLAG" != "0" ];then 519 | break 520 | fi 521 | 522 | sleep 60 523 | done 524 | 525 | echo "WyzeHack: Rebooting..." 526 | killall sleep >/dev/null 2>&1 527 | sync 528 | sleep 10 529 | /sbin/reboot 530 | } 531 | 532 | cmd_reboot() { 533 | set -x 534 | echo "WyzeHack: Camera is rebooting in 10 seconds ..." 535 | if [ ! -z $IN_UPDATE ]; 536 | then 537 | echo "WyzeHack: In update, checking system partition ..." 538 | cd / 539 | umount -f /system || echo "WyzeHack: umount system failed ..." 540 | mount -t jffs2 /dev/mtdblock4 /system || echo "WyzeHack: mount system failed ..." 541 | fi 542 | 543 | hook_init || echo "WyzeHack: hook_init failed ..." 544 | 545 | killall sleep >/dev/null 2>&1 546 | sync 547 | sleep 10 548 | /sbin/reboot $@ 549 | } 550 | 551 | cmd_run() { 552 | # Run original script when no config file is found or unknown device model 553 | if [ ! -f "$WYZEHACK_CFG" ] || \ 554 | [ -z "$DEVICE_MODEL" ]; then 555 | $WYZEINIT_SCRIPT & 556 | return 0 557 | fi 558 | 559 | # Run original script with reboot hook when device is in the middle of 560 | # upgrade 561 | if [ -f /system/.upgrade ] || \ 562 | [ -f /configs/.upgrade ]; then 563 | export IN_UPDATE=1 564 | export PATH=$WYZEHACK_DIR/bin:$PATH 565 | export LD_LIBRARY_PATH=$WYZEHACK_DIR/bin:$LD_LIBRARY_PATH 566 | $WYZEINIT_SCRIPT & 567 | return 0 568 | fi 569 | 570 | # Log syncing 571 | if [ ! -z "$SYNC_BOOT_LOG" ]; then 572 | exec >> /tmp/boot.log 2>&1 573 | fi 574 | 575 | # Customize password 576 | if [ ! -z "$PASSWD_SHADOW" ]; then 577 | set_passwd $PASSWD_SHADOW 578 | fi 579 | 580 | echo "WyzeHack: WyzeApp version: $WYZEAPP_VER" 581 | echo "WyzeHack: WyzeHack version: $WYZEHACK_VER" 582 | 583 | # Set hostname 584 | if [ -z "${CUSTOM_HOSTNAME}" ]; then 585 | CUSTOM_HOSTNAME="WyzeCam${DEVICE_MODEL}-$(echo -n $DEVICE_ID | tail -c 4)" 586 | fi 587 | hostname ${CUSTOM_HOSTNAME} 588 | 589 | if [ -z "$NFS_ROOT" ]; then 590 | # No NFS_ROOT specified, skipping all the MMC spoofing thing and run 591 | # original init script 592 | $WYZEINIT_SCRIPT& 593 | else 594 | # MMC detection hook init 595 | export PATH=$WYZEHACK_DIR/bin:$PATH 596 | export LD_LIBRARY_PATH=$WYZEHACK_DIR/bin:$LD_LIBRARY_PATH 597 | if [ ! -z $MMC_GPIO_REDIR ];then 598 | echo "1" > $MMC_GPIO_REDIR 599 | fi 600 | 601 | # libsetunbuf.so is used as LD_PRELOAD for later v2 firmwares, so 602 | # replace it with ours 603 | ln -s $WYZEHACK_DIR/bin/libhacks.so $WYZEHACK_DIR/bin/libsetunbuf.so 604 | $WYZEHACK_DIR/bin/hackutils init 605 | LD_PRELOAD=$WYZEHACK_DIR/bin/libhacks.so $WYZEINIT_SCRIPT & 606 | fi 607 | 608 | # Wait until WIFI is connected 609 | wait_wlan 610 | 611 | # This seems to be useful to prevent reboot caused by wifi dropping. 612 | if [ "$PING_KEEPALIVE" = "1" ];then 613 | GATEWAY_IP=`route -n | grep "UG" | awk -F' ' '{print $2}'` 614 | echo "WyzeHack: Trying to ping gateway $GATEWAY_IP..." 615 | ping $GATEWAY_IP 2>&1 >/dev/null & 616 | fi 617 | 618 | # Starting telnetd first 619 | busybox telnetd 620 | 621 | if [ -z "$NFS_ROOT" ]; then 622 | echo "WyzeHack: NFS_ROOT not defined, skipping NFS mount..." 623 | else 624 | if mount_nfs; then 625 | NFS_MOUNTED=1 626 | 627 | # Copy some information to the NFS share 628 | mkdir -p /media/mmcblk0p1/wyzehacks 629 | cp $WYZEHACK_CFG /media/mmcblk0p1/wyzehacks/config.inc 630 | 631 | # Initializing logging 632 | log_init 633 | else 634 | echo "WyzeHack: NFS mount failed, rebooting..." 635 | /sbin/reboot 636 | fi 637 | fi 638 | 639 | # Custom script 640 | if [ -f "$CUSTOM_SCRIPT" ]; then 641 | echo "WyzeHack: Starting custom script: $CUSTOM_SCRIPT" 642 | $CUSTOM_SCRIPT & 643 | else 644 | echo "WyzeHack: Custom script not found: $CUSTOM_SCRIPT" 645 | fi 646 | 647 | sys_monitor 648 | } 649 | 650 | cmd_install() { 651 | set -x 652 | local SD_DIR=/media/mmcblk0p1 653 | if [ ! -d $SD_DIR ]; 654 | then 655 | SD_DIR=/media/mmc 656 | fi 657 | 658 | if [ -d $SD_DIR ]; 659 | then 660 | exec >$SD_DIR/install.log 2>&1 661 | fi 662 | 663 | echo "WyzeHack: Starting wyze hack installer..." 664 | 665 | play_sound $THIS_DIR/install_snd/begin.wav 50 666 | 667 | if [ -f $SD_DIR/debug/.copyfiles ]; 668 | then 669 | echo "WyzeHack: Copying files for debugging purpose..." 670 | rm -rf $SD_DIR/debug/system 671 | rm -rf $SD_DIR/debug/etc 672 | 673 | # Copying system and etc back to SD card for analysis 674 | cp -rL /system $SD_DIR/debug 675 | cp -rL /etc $SD_DIR/debug 676 | fi 677 | 678 | if ! pgrep -f telnetd > /dev/null 2>&1; then 679 | # Swapping shadow file so we can telnetd in without password. This 680 | # is for debugging purpose. 681 | set_passwd 'root::10933:0:99999:7:::' 682 | 683 | # Always try to enable telnetd 684 | echo "WyzeHack: Enabling telnetd..." 685 | busybox telnetd 686 | fi 687 | 688 | if [ -z "$WYZEAPP_VER" ]; 689 | then 690 | echo "WyzeHack: Wyze version not found!!!" 691 | play_sound $THIS_DIR/install_snd/failed.wav 50 692 | return 1 693 | fi 694 | 695 | echo "WyzeHack: Current Wyze software version is $WYZEAPP_VER" 696 | echo "WyzeHack: Installing WyzeHacks version $THIS_VER" 697 | 698 | # Updating user config if exists 699 | if [ -f "/params/wyze_hack.cfg" ]; 700 | then 701 | echo "WyzeHack: Migrating from /params to /configs" 702 | cp /params/wyze_hack.cfg $WYZEHACK_CFG 703 | rm /params/wyze_hack.* 704 | fi 705 | 706 | if [ -f $THIS_DIR/config.inc ]; 707 | then 708 | echo "WyzeHack: Use config file $THIS_DIR/config.inc" 709 | sed 's/\r$//' $THIS_DIR/config.inc > $WYZEHACK_CFG 710 | fi 711 | 712 | if [ -f "$SD_DIR/config.inc" ]; 713 | then 714 | echo "WyzeHack: Use config file $SD_DIR/config.inc" 715 | sed 's/\r$//' $SD_DIR/config.inc > $WYZEHACK_CFG 716 | fi 717 | 718 | if [ ! -f "$WYZEHACK_CFG" ]; 719 | then 720 | echo "WyzeHack: Configuration file not found, aborting..." 721 | play_sound $THIS_DIR/install_snd/failed.wav 50 722 | return 1 723 | fi 724 | 725 | if [ -z "$DEVICE_MODEL" ]; 726 | then 727 | echo "WyzeHack: Unknown device model, aborting..." 728 | play_sound $THIS_DIR/install_snd/failed.wav 50 729 | return 1 730 | fi 731 | 732 | # Copying wyze_hack scripts 733 | echo "WyzeHack: Copying wyze hack binary..." 734 | cp $THIS_BIN $WYZEHACK_BIN 735 | 736 | if ! init_rootfs $THIS_DIR/rootfs; then 737 | echo "WyzeHack: Init root fs failed, aborting..." 738 | play_sound $THIS_DIR/install_snd/failed.wav 50 739 | return 1 740 | fi 741 | 742 | # Hook app_init.sh 743 | echo "WyzeHack: Hooking up boot script..." 744 | if ! hook_init; 745 | then 746 | echo "WyzeHack: Hooking up boot script failed" 747 | play_sound $THIS_DIR/install_snd/failed.wav 50 748 | return 1 749 | fi 750 | 751 | play_sound $THIS_DIR/install_snd/finished.wav 50 752 | 753 | rm $SD_DIR/version.ini.old > /dev/null 2>&1 754 | mv $SD_DIR/version.ini $SD_DIR/version.ini.old > /dev/null 2>&1 755 | /sbin/reboot 756 | } 757 | 758 | cmd_test() { 759 | echo "WyzeHack: test: args=$@" 760 | } 761 | 762 | ACTION="cmd_${0##*/}" 763 | if [ "$ACTION" = "cmd_main.sh" ]; 764 | then 765 | ACTION="cmd_${1:-run}" 766 | [ "$#" -gt 1 ] && shift 767 | fi 768 | 769 | $ACTION $@ 770 | -------------------------------------------------------------------------------- /wyze_hack/rootfs/4.36.0.112/rootfs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HclX/WyzeHacks/0ca2fc03af156a364165d1a082c8c6fbfc2e1fb9/wyze_hack/rootfs/4.36.0.112/rootfs.bin -------------------------------------------------------------------------------- /wyze_hack/rootfs/4.36.0.112/versions.txt: -------------------------------------------------------------------------------- 1 | [4.36.0.112] 2 | [4.36.0.56] 3 | -------------------------------------------------------------------------------- /wyze_hack/stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export THIS_BIN=$(readlink -f $0) 3 | export THIS_DIR=$(dirname $THIS_BIN) 4 | export THIS_MD5=`md5sum $THIS_BIN | grep -oE "^[0-9a-f]*"` 5 | export THIS_VER=__WYZEHACK_VER__ 6 | 7 | ACTION=${1:-run} 8 | [ "$#" -gt 1 ] && shift 9 | 10 | WYZEHACK_DIR=${TMP:-/tmp}/wyze_hack/$ACTION 11 | mkdir -p $WYZEHACK_DIR 12 | 13 | PAYLOAD_START=`awk '/^#__PAYLOAD_BELOW__/ {print NR + 1; exit 0; }' $0` 14 | tail -n+$PAYLOAD_START $0 | gzip -cd | tar x -f - -C $WYZEHACK_DIR 15 | 16 | exec sh $WYZEHACK_DIR/main.sh $ACTION $@ 17 | #__PAYLOAD_BELOW__ 18 | -------------------------------------------------------------------------------- /wyze_hack/upgrade.sh: -------------------------------------------------------------------------------- 1 | upgraderun.sh -------------------------------------------------------------------------------- /wyze_hack/upgraderun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPT_DIR=$(dirname $(readlink -f $0)) 3 | $SCRIPT_DIR/wyze_hack.sh install 4 | --------------------------------------------------------------------------------