├── .gitattributes ├── libraries └── placeholder ├── docs └── README.md └── bypass.php /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /libraries/placeholder: -------------------------------------------------------------------------------- 1 | # Do NOT remove this folder -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Xiaomi HyperOS BootLoader Bypass 2 | 3 | ![Version: 1.0](https://img.shields.io/badge/Version-1.0-brightgreen?style=for-the-badge) 4 | 5 | 利用漏洞绕过小米 HyperOS 对 BootLoader 解锁账户绑定限制社区等级的 PoC。 6 | 7 | 您可随时向本项目提出改进方案 :) 8 | 9 | ## 💘 php-adb 10 | 11 | 本项目使用了 [php-adb](https://github.com/MlgmXyysd/php-adb) 运行库。 12 | 13 | ## ☕ 支持开发 14 | 15 | ✨ 如果您喜欢我的项目,可以请我喝咖啡: 16 | 17 | - [爱发电](https://afdian.net/@MlgmXyysd) 18 | - [PayPal](https://paypal.me/MlgmXyysd) 19 | - [Patreon](https://www.patreon.com/MlgmXyysd) 20 | 21 | ## ⚠️ 警告 22 | 23 | 解锁 BootLoader 后,你可能会遇到以下情况: 24 | 25 | - 软件或硬件无法正常工作,甚至永久性损坏。 26 | - 设备中存储的数据丢失。 27 | - 信用卡被盗刷,或遭受其他经济损失。 28 | 29 | 如果您遇到上述任何情况,您应该自己承担所有责任,因为这是您在解锁 BootLoader 时可能遇到的风险。这显然不能涵盖所有风险。我们已经警告过您了。 30 | 31 | - 保修丢失。根据小米提供的免责条款,这不仅是基础三包,您购买的一些额外延保(如 Mi Care 或碎屏险)也可能会丢失。 32 | - 像 Samsung Knox 那样的硬件级熔断。TEE 相关功能将永久损坏。除更换主板外,无法恢复。 33 | - 刷入第三方系统后出现功能异常,这可能是因为内核源代码闭源引起。 34 | - 设备或账号因为解锁 BootLoader 被小米封禁。 35 | 36 | 如果您遇到上述任何情况,请您自认倒霉。自从小米限制解锁 BootLoader 后,小米就一直在违背"极客"精神,甚至违背了 GPL。小米对 BootLoader 解锁的限制是无穷尽的,作为开发者,我们对此无能为力。 37 | 38 | ## 📲 前置要求 39 | 40 | - 一个有效的设备: 41 | - 一个未被封禁\*的小米、红米或 POCO 设备。 42 | - 设备正在运行官方版 HyperOS。 43 | - (2023/11/23 更新) 您的设备不会被小米强制验证账户资格。 44 | - 一个有效的 SIM 卡: 45 | - \* 无法使用 SIM 卡的平板电脑除外。 46 | - SIM 卡不得处于停机或无服务状态。 47 | - SIM 卡需要能够连接到互联网。 48 | - 每张有效 SIM 卡在三个月内只能解锁 2 台设备。 49 | - 一个有效的小米账号: 50 | - 一个未被封禁\*的小米账号。 51 | - 每个账号一个月只能解锁一部手机,一年只能解锁三部手机。 52 | - 您已阅读并理解上述 [警告](#%EF%B8%8F-警告)。 53 | 54 | - \* 根据小米提供的解锁说明,某些账号和设备将被禁止使用解锁工具,这被称为"风控"。 55 | 56 | ## ⚙️ 使用教程 57 | 58 | 1. 从 [官方网站](https://www.php.net/downloads) 下载并安装适用于您操作系统的 PHP 8.0+。 59 | 2. 在 `php.ini` 中启用 OpenSSL 和 Curl 扩展。(如果脚本未正常工作,请将 `extension_dir` 设置为 PHP 的 `ext` 文件夹路径。) 60 | 3. 将 [php-adb](https://github.com/MlgmXyysd/php-adb) 中的 `adb.php` 放到目录中。 61 | 4. 下载 [platform-tools](https://developer.android.com/studio/releases/platform-tools),并将其放入 `libraries`。*注意:Mac OS 需要将 `adb` 重命名为 `adb-darwin`。 62 | 5. 打开终端,使用 PHP 解释器执行 [脚本](../bypass.php)。 63 | 64 | - p.s. Releases 已将所需文件和一键脚本打包。 65 | 66 | 6. 多次点击`设置 - 关于手机 - MIUI 版本`启用`开发者选项`。 67 | 7. 在`设置 - 附加设置 - 开发者选项`中启用`OEM 解锁`、`USB 调试`和`USB 调试(安全设置)`。 68 | 8. 登录一个_有效_\*的小米账号。 69 | 9. 通过有线方式将设备连接到电脑。 70 | 10. 选中`始终允许来自此计算机的调试`,然后单击`确定`。 71 | 72 | - \* 请参阅上文的 "[前置要求](#-前置要求)"。 73 | 74 | 11. 等待并按脚本提示操作。 75 | 12. 绑定成功后,您可以使用 [官方解锁工具](https://www.miui.com/unlock/index.html) 查看需要等待的时间。 76 | 13. 在等待期间,请正常使用设备,保持 SIM 卡插入,不要登出小米账号或关闭"查找我的手机",不要重新绑定设备,直到成功解锁。设备将每隔一段时间自动向服务器发送 `HeartBeat` 数据包。 77 | 78 | ## 📖 漏洞分析 79 | 80 | - 维修中... 81 | 82 | ## 🔖 FAQ 83 | 84 | - Q: 为什么解锁工具仍然提醒我等待 168/360(或更长)小时? 85 | - A: 根据原理,该 PoC 只绕过了小米为 HyperOS 额外添加的限制。您仍然需要遵循 MIUI 的限制。 86 | 87 | - Q: 设备显示 "验证失败,请稍后再试"。 88 | - A: 这是正常现象,设备端的绑定请求已被脚本拦截。实际绑定结果以脚本提示为准。 89 | 90 | - Q: 绑定失败,错误代码为 `401`。 91 | - A: 您的小米账号凭据已过期,您需要在设备中登出账号并重新登录。 92 | 93 | - Q: 绑定失败,错误代码为 `20086`。 94 | - A: 您的设备凭据已过期,您可能需要重新启动设备。 95 | 96 | - Q: 绑定失败,错误代码为 `20090` 或 `20091`。 97 | - A: 设备的 `Security Device Credential Manager` 功能已损坏,请联系售后服务寻求支持。 98 | 99 | - Q: 绑定失败,错误代码为 `30001`。 100 | - A: 您的设备已被小米强制验证账户资格。小米早就抛弃了"极客"精神,我们对此无能为力。 101 | 102 | - Q: 绑定失败,错误代码为 `86015`。 103 | - A: 服务器拒绝了本次绑定请求,请重试。 104 | 105 | ## ⚖️ 协议 106 | 107 | 无许可证,您只被允许使用本项目。未经许可,不得删除或更改本软件的所有版权(以及链接等)。本项目所有权利归 [MeowCat Studio](https://github.com/MeowCat-Studio)、[Meow Mobile](https://github.com/Meow-Mobile) 和 [NekoYuzu](https://github.com/MlgmXyysd) 所有。 108 | -------------------------------------------------------------------------------- /bypass.php: -------------------------------------------------------------------------------- 1 | refreshDeviceList(); 93 | $t = array(); 94 | foreach ($s as $d) { 95 | if ($d["status"] === $a::CONNECT_TYPE_DEVICE) { 96 | $t[] = array($d["serial"], $d["transport"]); 97 | } 98 | } 99 | return $t; 100 | } 101 | 102 | /** 103 | * Formatted Log 104 | * @param $m string optional Message 105 | * @param $c string optional Color 106 | * @param $p string optional Indicator 107 | * @param $t string optional Type (Level) 108 | * @author NekoYuzu (MlgmXyysd) 109 | * @date 2022/03/24 14:50:01 110 | */ 111 | 112 | function logf(string $m = "", string $c = "", string $p = "-", string $t = "I"): void 113 | { 114 | switch (strtoupper($c)) { 115 | case "G": 116 | $c = "\033[32m"; 117 | break; 118 | case "R": 119 | $c = "\033[31m"; 120 | break; 121 | case "Y": 122 | $c = "\033[33m"; 123 | break; 124 | default: 125 | $c = ""; 126 | } 127 | switch (strtoupper($t)) { 128 | case "W": 129 | $t = "WARN"; 130 | break; 131 | case "E": 132 | $t = "ERROR"; 133 | break; 134 | case "I": 135 | default: 136 | $t = "INFO"; 137 | } 138 | print(date("[Y-m-d] [H:i:s]") . " [" . $t . "] " . $p . " " . $c . $m . "\033[0m" . PHP_EOL); 139 | } 140 | 141 | /** 142 | * Curl HTTP wrapper function 143 | * @param $url string required Target url 144 | * @param $method string required Request method 145 | * @param $fields array optional Request body 146 | * @param $header array optional Request header 147 | * @param $useForm bool optional Treat request body as urlencoded form 148 | * @return array Curl response 149 | * @author NekoYuzu (MlgmXyysd) 150 | * @date 2023/11/20 23:50:39 151 | */ 152 | 153 | function http(string $url, string $method, array $fields = array(), array $header = array(), bool $useForm = false): array 154 | { 155 | if ($useForm) { 156 | $fields = http_build_query($fields); 157 | } 158 | $curl = curl_init(); 159 | curl_setopt_array($curl, array( 160 | CURLOPT_URL => $url, 161 | CURLOPT_RETURNTRANSFER => true, 162 | CURLOPT_ENCODING => "", 163 | CURLOPT_SSL_VERIFYPEER => false, 164 | CURLOPT_SSL_VERIFYHOST => false, 165 | CURLOPT_MAXREDIRS => 10, 166 | CURLOPT_CONNECTTIMEOUT => 2, 167 | CURLOPT_TIMEOUT => 6, 168 | CURLOPT_CUSTOMREQUEST => $method, 169 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 170 | CURLOPT_POST => $method == "POST", 171 | CURLOPT_POSTFIELDS => $fields, 172 | CURLOPT_HTTPHEADER => $header 173 | )); 174 | 175 | $response = curl_exec($curl); 176 | $info = curl_getinfo($curl); 177 | $info["errno"] = curl_errno($curl); 178 | $info["error"] = curl_error($curl); 179 | $info["request"] = json_encode($fields); 180 | $info["response"] = $response; 181 | curl_close($curl); 182 | return $info; 183 | } 184 | 185 | /** 186 | * HTTP POST wrapper 187 | * @param $_api string required Target endpoint 188 | * @param $data array optional Request body 189 | * @param $header array optional Request header 190 | * @param $useForm bool optional Treat request body as urlencoded form 191 | * @return array Curl response 192 | * @return false Response code is not HTTP 200 OK 193 | * @author NekoYuzu (MlgmXyysd) 194 | * @date 2023/11/20 23:55:41 195 | */ 196 | 197 | function postApi(string $_api, array $data = array(), array $header = array(), bool $useForm = false): array|false 198 | { 199 | $response = http($GLOBALS["api"] . $_api, "POST", $data, $header, $useForm); 200 | if ($response["http_code"] != 200) { 201 | return false; 202 | } 203 | return json_decode($response["response"], true); 204 | } 205 | 206 | /** 207 | * Sign data using HMAC SHA-1 208 | * @param $data string required Data to sign 209 | * @return string Signed hash 210 | * @author NekoYuzu (MlgmXyysd) 211 | * @date 2023/11/21 00:20:56 212 | */ 213 | 214 | function signData(string $data): string 215 | { 216 | return strtolower(bin2hex(hash_hmac("sha1", "POST\n/v1/unlock/applyBind\ndata=" . $data . "&sid=miui_sec_android", $GLOBALS["sign_key"], true))); 217 | } 218 | 219 | /** 220 | * Decrypt data using AES/CBC/PKCS5Padding 221 | * @param $data string required Data to decrypt 222 | * @return string Decrypted data 223 | * @return false Failed to decrypt 224 | * @author NekoYuzu (MlgmXyysd) 225 | * @date 2023/11/21 00:15:30 226 | */ 227 | 228 | function decryptData(string $data): string|false 229 | { 230 | return openssl_decrypt(base64_decode($data), "AES-128-CBC", $GLOBALS["data_pass"], OPENSSL_RAW_DATA, $GLOBALS["data_iv"]); 231 | } 232 | 233 | /*********************** 234 | * Functions End * 235 | ***********************/ 236 | 237 | /********************** 238 | * Banner Start * 239 | **********************/ 240 | 241 | logf("************************************", "g"); 242 | logf("* Xiaomi HyperOS BootLoader Bypass *", "g"); 243 | logf("* By NekoYuzu Version " . $version . " *", "g"); 244 | logf("************************************", "g"); 245 | logf("GitHub: https://github.com/MlgmXyysd"); 246 | logf("XDA: https://xdaforums.com/m/mlgmxyysd.8430637"); 247 | logf("X (Twitter): https://x.com/realMlgmXyysd"); 248 | logf("PayPal: https://paypal.me/MlgmXyysd"); 249 | logf("My Blog: https://www.neko.ink/"); 250 | logf("************************************", "g"); 251 | 252 | /******************** 253 | * Banner End * 254 | ********************/ 255 | 256 | /******************** 257 | * Main Logic * 258 | ********************/ 259 | 260 | logf("Starting ADB server..."); 261 | 262 | $adb = new ADB(__DIR__ . DIRECTORY_SEPARATOR . "libraries"); 263 | 264 | $devices = parseDeviceList($adb); 265 | $devices_count = count($devices); 266 | 267 | while ($devices_count != 1) { 268 | if ($devices_count == 0) { 269 | logf("Waiting for device connection..."); 270 | } else { 271 | logf("Only one device is allowed to connect, disconnect others to continue. Current number of devices: " . $devices_count); 272 | } 273 | sleep(1); 274 | $devices = parseDeviceList($adb); 275 | $devices_count = count($devices); 276 | } 277 | 278 | $device = $devices[0]; 279 | $id = $adb -> getDeviceId($device[1], true); 280 | logf("Processing device " . $device[0] . "(" . $device[1] . ")..."); 281 | 282 | $adb -> clearLogcat($id); 283 | $adb -> runAdb($id . "shell svc data enable"); 284 | 285 | logf("Finding BootLoader unlock bind request..."); 286 | 287 | $focus = $adb -> getCurrentActivity(); 288 | if ($focus[0] != "com.android.settings") { 289 | if ($focus[0] != "NotificationShade") { 290 | $adb -> runAdb($id . "shell am start -a android.settings.APPLICATION_DEVELOPMENT_SETTINGS"); 291 | } 292 | } else { 293 | if ($focus[1] != "com.android.settings.bootloader.BootloaderStatusActivity") { 294 | $adb -> runAdb($id . "shell am start -a android.settings.APPLICATION_DEVELOPMENT_SETTINGS"); 295 | } 296 | } 297 | logf("Now you can bind account in the developer options.", "y", "*"); 298 | 299 | $args = $headers = null; 300 | 301 | $process = proc_open($adb -> bin . " " . $id . "logcat *:S CloudDeviceStatus:V", array( 302 | 1 => ["pipe", "w"] 303 | ), $pipes); 304 | 305 | if (is_resource($process)) { 306 | while (!feof($pipes[1])) { 307 | $output = fgets($pipes[1]); 308 | 309 | if (str_contains($output, "CloudDeviceStatus: args:")) { 310 | if (preg_match("/args:(.*)/", $output, $matches)) { 311 | $args = trim($matches[1]); 312 | } 313 | $adb -> runAdb($id . "shell svc data disable"); 314 | } 315 | 316 | if (str_contains($output, "CloudDeviceStatus: headers:")) { 317 | if (preg_match("/headers:(.*)/", $output, $matches)) { 318 | $headers = trim($matches[1]); 319 | } 320 | logf("Account bind request found! Let's block it."); 321 | break; 322 | } 323 | } 324 | 325 | fclose($pipes[1]); 326 | } 327 | 328 | logf("Refactoring parameters..."); 329 | 330 | $data = json_decode(decryptData($args), true); 331 | 332 | // V816 is the special identity for HyperOS in MIUI version 333 | $data["rom_version"] = str_replace("V816", "V14", $data["rom_version"]); 334 | 335 | $data = json_encode($data); 336 | $sign = signData($data); 337 | 338 | $headers = decryptData($headers); 339 | $cookies = null; 340 | if (preg_match("/Cookie=\[(.*)\]/", $headers, $matches)) { 341 | $cookies = trim($matches[1]); 342 | } 343 | 344 | logf("Sending POST request..."); 345 | $res = postApi("unlock/applyBind", array( 346 | "data" => $data, 347 | "sid" => "miui_sec_android", 348 | "sign" => $sign 349 | ), array( 350 | "Cookie: " . $cookies, 351 | "Content-Type: application/x-www-form-urlencoded" 352 | ), true); 353 | 354 | $adb -> runAdb($id . "shell svc data enable"); 355 | 356 | if (!$res) { 357 | logf("Fail to send request, check your internet connection.", "r", "!"); 358 | exit(); 359 | } 360 | 361 | switch ($res["code"]) { 362 | case 0: 363 | logf("Target account: " . $res["data"]["userId"], "g"); 364 | logf("Account bound successfully, wait time can be viewed in the unlock tool.", "g"); 365 | break; 366 | case 401: 367 | logf("Account credentials have expired, re-login to your account in your phone. (401)", "y"); 368 | break; 369 | case 20086: 370 | logf("Device credentials expired. (20086)", "y"); 371 | break; 372 | case 30001: 373 | logf("Binding failed, this device has been forced to verify the account qualification by Xiaomi. (30001)", "y"); 374 | break; 375 | case 86015: 376 | logf("Fail to bind account, invalid device signature. (86015)", "y"); 377 | break; 378 | default: 379 | logf($res["descEN"] . " (" . $res["code"] . ")", "y"); 380 | } 381 | --------------------------------------------------------------------------------