├── LICENSE ├── Makefile ├── README.md ├── chacha20.c ├── chacha20.h ├── decrypt_rootfs.c ├── decrypt_rsapubkey.c ├── encrypt_rootfs.c └── getrootfskey.py /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | DEPS=chacha20.c chacha20.h 3 | LDFLAGS=-lssl -lcrypto 4 | all: decrypt_rootfs encrypt_rootfs decrypt_rsapubkey 5 | 6 | decrypt_rootfs: decrypt_rootfs.c $(DEPS) 7 | $(CC) $^ -o $@ $(LDFLAGS) 8 | encrypt_rootfs: encrypt_rootfs.c $(DEPS) 9 | $(CC) $^ -o $@ $(LDFLAGS) 10 | decrypt_rsapubkey: decrypt_rsapubkey.c $(DEPS) 11 | $(CC) $^ -o $@ $(LDFLAGS) 12 | 13 | clean: 14 | rm -f decrypt_rootfs encrypt_rootfs decrypt_rsapubkey 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FortiGate rootfs decryption tool (by optistream.io) 2 | 3 | On recent FortiGate firmwares versions, `rootfs.gz` is checked against a signature then decrypted using ChaCha20. 4 | This is implemented inside the kernel (`flatkc`) within function `fgt_verify_decrypt`. 5 | 6 | - `getrootfskey.py`: retrieve encryption key from kernel 7 | - `decrypt_rootfs`/`encrypt_rootfs`: decrypt/encrypt `rootfs.gz` using previously retrieved encryption key 8 | - `decrypt_rsapubkey`: auxiliary tool for decrypting RSA public key embedded in kernel for rootfs signature check (`fgt_verifier_pub_key`) 9 | 10 | Tested on: FortiOS 7.4.2, 7.4.3 (x64 & aarch64) 11 | 12 | ## 0. Build 13 | 14 | ```bash 15 | $ sudo apt install libssl-dev 16 | $ make 17 | ``` 18 | 19 | ## 1. Get rootfs encryption key from kernel 20 | 21 | This script actually analyzes `fgt_verifier_pub_key` function to retrieve encryption seed (using `miasm==0.1.5`): 22 | 23 | ```bash 24 | $ python3 -m venv .venv 25 | $ . .venv/bin/activate 26 | $ pip install miasm 27 | $ python getrootfskey.py flatkc.elf.x64.v7.4.3 28 | Architecture: x86_64 29 | Seed address: 0xffffffff817932e0 30 | Extracted seed: b'4CF7A950B99CF29B0343E7BA6C609E49D9766F16C6D2F075F72AD400542F0765' 31 | ``` 32 | 33 | ## 2. Decrypt/encrypt `rootfs` 34 | 35 | ```bash 36 | $ ./decrypt_rootfs rootfs.gz.x64.v7.4.3 rootfs.gz.x64.v7.4.3.decrypted 4CF7A950B99CF29B0343E7BA6C609E49D9766F16C6D2F075F72AD400542F0765 37 | 833E9BAFBF0C2F581E9A949B13C4352B9D52C72A27B925EA3B46F8236BFF58F1 38 | 8C575F183A1AD583BDDD9A822A8A5D4E312452509E322FA46283A9EAF7E30BF6 39 | rootfs size: 71395069 40 | Decrypting rootfs... 41 | Writing to rootfs.gz.x64.v7.4.3.decrypted... 42 | ``` 43 | 44 | # Links 45 | 46 | [https://www.optistream.io/blogs/tech/fortigate-firmware-analysis](https://www.optistream.io/blogs/tech/fortigate-firmware-analysis) 47 | 48 | [https://github.com/Ginurx/chacha20-c](https://github.com/Ginurx/chacha20-c) 49 | 50 | [https://github.com/cea-sec/miasm](https://github.com/cea-sec/miasm) 51 | 52 | # Author 53 | 54 | [https://optistream.io](https://optistream.io) 55 | -------------------------------------------------------------------------------- /chacha20.c: -------------------------------------------------------------------------------- 1 | #include "chacha20.h" 2 | 3 | 4 | static uint32_t rotl32(uint32_t x, int n) 5 | { 6 | return (x << n) | (x >> (32 - n)); 7 | } 8 | 9 | static uint32_t pack4(const uint8_t *a) 10 | { 11 | uint32_t res = 0; 12 | res |= (uint32_t)a[0] << 0 * 8; 13 | res |= (uint32_t)a[1] << 1 * 8; 14 | res |= (uint32_t)a[2] << 2 * 8; 15 | res |= (uint32_t)a[3] << 3 * 8; 16 | return res; 17 | } 18 | 19 | static void unpack4(uint32_t src, uint8_t *dst) { 20 | dst[0] = (src >> 0 * 8) & 0xff; 21 | dst[1] = (src >> 1 * 8) & 0xff; 22 | dst[2] = (src >> 2 * 8) & 0xff; 23 | dst[3] = (src >> 3 * 8) & 0xff; 24 | } 25 | 26 | static void chacha20_init_block(struct chacha20_context *ctx, uint8_t key[], uint8_t nonce[]) 27 | { 28 | memcpy(ctx->key, key, sizeof(ctx->key)); 29 | memcpy(ctx->nonce, nonce, sizeof(ctx->nonce)); 30 | 31 | const uint8_t *magic_constant = (uint8_t*)"expand 32-byte k"; 32 | ctx->state[0] = pack4(magic_constant + 0 * 4); 33 | ctx->state[1] = pack4(magic_constant + 1 * 4); 34 | ctx->state[2] = pack4(magic_constant + 2 * 4); 35 | ctx->state[3] = pack4(magic_constant + 3 * 4); 36 | ctx->state[4] = pack4(key + 0 * 4); 37 | ctx->state[5] = pack4(key + 1 * 4); 38 | ctx->state[6] = pack4(key + 2 * 4); 39 | ctx->state[7] = pack4(key + 3 * 4); 40 | ctx->state[8] = pack4(key + 4 * 4); 41 | ctx->state[9] = pack4(key + 5 * 4); 42 | ctx->state[10] = pack4(key + 6 * 4); 43 | ctx->state[11] = pack4(key + 7 * 4); 44 | // 64 bit counter initialized to zero by default. 45 | ctx->state[12] = pack4(nonce + 0 * 4); 46 | ctx->state[13] = pack4(nonce + 1 * 4); 47 | ctx->state[14] = pack4(nonce + 2 * 4); 48 | ctx->state[15] = pack4(nonce + 3 * 4); 49 | 50 | memcpy(ctx->nonce, nonce, sizeof(ctx->nonce)); 51 | } 52 | 53 | static void chacha20_block_set_counter(struct chacha20_context *ctx, uint64_t counter) 54 | { 55 | ctx->state[12] = (uint32_t)counter; 56 | ctx->state[13] = pack4(ctx->nonce + 0 * 4) + (uint32_t)(counter >> 32); 57 | } 58 | 59 | static void chacha20_block_next(struct chacha20_context *ctx) { 60 | // This is where the crazy voodoo magic happens. 61 | // Mix the bytes a lot and hope that nobody finds out how to undo it. 62 | for (int i = 0; i < 16; i++) ctx->keystream32[i] = ctx->state[i]; 63 | 64 | #define CHACHA20_QUARTERROUND(x, a, b, c, d) \ 65 | x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16); \ 66 | x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12); \ 67 | x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 8); \ 68 | x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 7); 69 | 70 | for (int i = 0; i < 10; i++) 71 | { 72 | CHACHA20_QUARTERROUND(ctx->keystream32, 0, 4, 8, 12) 73 | CHACHA20_QUARTERROUND(ctx->keystream32, 1, 5, 9, 13) 74 | CHACHA20_QUARTERROUND(ctx->keystream32, 2, 6, 10, 14) 75 | CHACHA20_QUARTERROUND(ctx->keystream32, 3, 7, 11, 15) 76 | CHACHA20_QUARTERROUND(ctx->keystream32, 0, 5, 10, 15) 77 | CHACHA20_QUARTERROUND(ctx->keystream32, 1, 6, 11, 12) 78 | CHACHA20_QUARTERROUND(ctx->keystream32, 2, 7, 8, 13) 79 | CHACHA20_QUARTERROUND(ctx->keystream32, 3, 4, 9, 14) 80 | } 81 | 82 | for (int i = 0; i < 16; i++) ctx->keystream32[i] += ctx->state[i]; 83 | 84 | uint32_t *counter = ctx->state + 12; 85 | // increment counter 86 | counter[0]++; 87 | if (0 == counter[0]) 88 | { 89 | // wrap around occured, increment higher 32 bits of counter 90 | counter[1]++; 91 | // Limited to 2^64 blocks of 64 bytes each. 92 | // If you want to process more than 1180591620717411303424 bytes 93 | // you have other problems. 94 | // We could keep counting with counter[2] and counter[3] (nonce), 95 | // but then we risk reusing the nonce which is very bad. 96 | assert(0 != counter[1]); 97 | } 98 | } 99 | 100 | void chacha20_init_context(struct chacha20_context *ctx, uint8_t key[], uint8_t nonce[])//, uint64_t counter) 101 | { 102 | memset(ctx, 0, sizeof(struct chacha20_context)); 103 | 104 | chacha20_init_block(ctx, key, nonce); 105 | //chacha20_block_set_counter(ctx, counter); 106 | 107 | //ctx->counter = counter; 108 | ctx->position = 64; 109 | } 110 | 111 | void chacha20_xor(struct chacha20_context *ctx, uint8_t *bytes, size_t n_bytes) 112 | { 113 | uint8_t *keystream8 = (uint8_t*)ctx->keystream32; 114 | for (size_t i = 0; i < n_bytes; i++) 115 | { 116 | if (ctx->position >= 64) 117 | { 118 | chacha20_block_next(ctx); 119 | ctx->position = 0; 120 | } 121 | bytes[i] ^= keystream8[ctx->position]; 122 | ctx->position++; 123 | } 124 | } -------------------------------------------------------------------------------- /chacha20.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | struct chacha20_context 13 | { 14 | uint32_t keystream32[16]; 15 | size_t position; 16 | 17 | uint8_t key[32]; 18 | uint8_t nonce[12]; 19 | uint64_t counter; 20 | 21 | uint32_t state[16]; 22 | }; 23 | 24 | void chacha20_init_context(struct chacha20_context *ctx, uint8_t key[], uint8_t nounc[]);//, uint64_t counter); 25 | 26 | void chacha20_xor(struct chacha20_context *ctx, uint8_t *bytes, size_t n_bytes); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif -------------------------------------------------------------------------------- /decrypt_rootfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | gcc decrypt_rootfs.c chacha20.c -o decrypt_rootfs -lssl -lcrypto 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include "chacha20.h" 8 | 9 | void printhex(unsigned char* data, int len) { 10 | int i = 0; 11 | for(; i < len; i++) { 12 | printf("%02X", data[i]); 13 | } 14 | } 15 | 16 | int main(int argc, char **argv) 17 | { 18 | if(argc < 4) { 19 | fprintf(stderr, "Usage: %s rootfs.tgz rootfs.tgz.decrypted \n", argv[0]); 20 | return 1; 21 | } 22 | 23 | if (strlen(argv[3]) != 64) { 24 | fprintf(stderr, "Key must be 64 (32-bytes) hexa chars string\n"); 25 | return 1; 26 | } 27 | 28 | char g_FirmwareSeed[32] = {0}; 29 | char *pos = argv[3]; 30 | for (size_t i = 0; i < sizeof g_FirmwareSeed; i++) { 31 | sscanf(pos, "%2hhx", &g_FirmwareSeed[i]); 32 | pos += 2; 33 | } 34 | 35 | EVP_MD_CTX *mdctx; 36 | unsigned char *md1 = NULL; 37 | unsigned char *md2 = NULL; 38 | 39 | if((mdctx = EVP_MD_CTX_new()) == NULL) 40 | return 1; 41 | 42 | if(EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) 43 | return 1; 44 | 45 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed + 4, 28) != 1) 46 | return 1; 47 | 48 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed, 4) != 1) 49 | return 1; 50 | 51 | if((md1 = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) 52 | return 1; 53 | 54 | if(EVP_DigestFinal_ex(mdctx, md1, NULL) != 1) 55 | return 1; 56 | 57 | EVP_MD_CTX_free(mdctx); 58 | 59 | printhex(md1, 32); 60 | printf("\n"); 61 | 62 | if((mdctx = EVP_MD_CTX_new()) == NULL) 63 | return 1; 64 | 65 | if(EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) 66 | return 1; 67 | 68 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed + 5, 27) != 1) 69 | return 1; 70 | 71 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed, 5) != 1) 72 | return 1; 73 | 74 | if((md2 = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) 75 | return 1; 76 | 77 | if(EVP_DigestFinal_ex(mdctx, md2, NULL) != 1) 78 | return 1; 79 | 80 | EVP_MD_CTX_free(mdctx); 81 | 82 | printhex(md2, 32); 83 | printf("\n"); 84 | 85 | // 86 | // ChaCha20 (custom) 87 | // 88 | 89 | FILE *f = fopen(argv[1], "rb"); 90 | fseek(f, 0, SEEK_END); 91 | long fsize = ftell(f); 92 | fseek(f, 0, SEEK_SET); 93 | // skip trailing signature 94 | fsize -= 256; 95 | printf("rootfs size: %u\n", fsize); 96 | char *data = malloc(fsize); 97 | fread(data, fsize, 1, f); 98 | fclose(f); 99 | 100 | printf("Decrypting rootfs...\n"); 101 | struct chacha20_context ctx; 102 | chacha20_init_context(&ctx, md1, md2); 103 | chacha20_xor(&ctx, data, fsize); 104 | 105 | // Check if GZ 106 | uint16_t magic = *(int16_t *)data; 107 | if (magic != 0x8B1F) { 108 | fprintf(stderr, "Failed to decrypt (not a GZ, magic=%X)\n", magic); 109 | return 1; 110 | } 111 | 112 | printf("Writing to %s...\n", argv[2]); 113 | FILE *f_out = fopen(argv[2], "wb"); 114 | fwrite(data, fsize, 1, f_out); 115 | fclose(f_out); 116 | 117 | return 0; 118 | } -------------------------------------------------------------------------------- /decrypt_rsapubkey.c: -------------------------------------------------------------------------------- 1 | /* 2 | gcc decrypt_rsapubkey.c chacha20.c -o decrypt_rsapubkey -lssl -lcrypto 3 | */ 4 | #include 5 | #include 6 | #include "chacha20.h" 7 | 8 | void printhex(unsigned char* data, int len) { 9 | int i = 0; 10 | for(; i < len; i++) { 11 | printf("%02X", data[i]); 12 | } 13 | } 14 | 15 | int main(int argc, char **argv) 16 | { 17 | if(argc < 3) { 18 | fprintf(stderr, "Usage: %s \n", argv[0]); 19 | return 1; 20 | } 21 | 22 | if (strlen(argv[1]) != 64) { 23 | fprintf(stderr, "Key must be 64 (32-bytes) hexa chars string\n"); 24 | return 1; 25 | } 26 | 27 | if (strlen(argv[2]) != 540) { 28 | fprintf(stderr, "RSA pubkey must be 540 (270 bytes) hexa chars string\n"); 29 | return 1; 30 | } 31 | 32 | char g_FirmwareSeed[32] = {0}; 33 | char g_RSA_PubKey[270] = {0}; 34 | char *pos = argv[1]; 35 | for (size_t i = 0; i < sizeof g_FirmwareSeed; i++) { 36 | sscanf(pos, "%2hhx", &g_FirmwareSeed[i]); 37 | pos += 2; 38 | } 39 | pos = argv[2]; 40 | for (size_t i = 0; i < sizeof g_RSA_PubKey; i++) { 41 | sscanf(pos, "%2hhx", &g_RSA_PubKey[i]); 42 | pos += 2; 43 | } 44 | 45 | EVP_MD_CTX *mdctx; 46 | unsigned char *md1 = NULL; 47 | unsigned char *md2 = NULL; 48 | 49 | if((mdctx = EVP_MD_CTX_new()) == NULL) 50 | return 1; 51 | 52 | if(EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) 53 | return 1; 54 | 55 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed + 3, 29) != 1) 56 | return 1; 57 | 58 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed, 3) != 1) 59 | return 1; 60 | 61 | if((md1 = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) 62 | return 1; 63 | 64 | if(EVP_DigestFinal_ex(mdctx, md1, NULL) != 1) 65 | return 1; 66 | 67 | EVP_MD_CTX_free(mdctx); 68 | 69 | printhex(md1, 32); 70 | printf("\n"); 71 | 72 | if((mdctx = EVP_MD_CTX_new()) == NULL) 73 | return 1; 74 | 75 | if(EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) 76 | return 1; 77 | 78 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed + 1, 31) != 1) 79 | return 1; 80 | 81 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed, 1) != 1) 82 | return 1; 83 | 84 | if((md2 = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) 85 | return 1; 86 | 87 | if(EVP_DigestFinal_ex(mdctx, md2, NULL) != 1) 88 | return 1; 89 | 90 | EVP_MD_CTX_free(mdctx); 91 | 92 | printhex(md2, 32); 93 | printf("\n"); 94 | 95 | // 96 | // ChaCha20 97 | // 98 | 99 | struct chacha20_context ctx; 100 | chacha20_init_context(&ctx, md1, md2); 101 | chacha20_xor(&ctx, g_RSA_PubKey, 270); 102 | 103 | printf("BER-encoded pub key:\n"); 104 | printhex(g_RSA_PubKey, 270); 105 | printf("\n"); 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /encrypt_rootfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | gcc decrypt_rootfs.c chacha20.c -o decrypt_rootfs -lssl -lcrypto 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include "chacha20.h" 8 | 9 | void printhex(unsigned char* data, int len) { 10 | int i = 0; 11 | for(; i < len; i++) { 12 | printf("%02X", data[i]); 13 | } 14 | } 15 | 16 | int main(int argc, char **argv) 17 | { 18 | if(argc < 4) { 19 | fprintf(stderr, "Usage: %s rootfs.tgz rootfs.tgz.encrypted \n", argv[0]); 20 | return 1; 21 | } 22 | 23 | if (strlen(argv[3]) != 64) { 24 | fprintf(stderr, "Key must be 64 (32-bytes) hexa chars string\n"); 25 | return 1; 26 | } 27 | 28 | char g_FirmwareSeed[32] = {0}; 29 | char *pos = argv[3]; 30 | for (size_t i = 0; i < sizeof g_FirmwareSeed; i++) { 31 | sscanf(pos, "%2hhx", &g_FirmwareSeed[i]); 32 | pos += 2; 33 | } 34 | 35 | EVP_MD_CTX *mdctx; 36 | unsigned char *md1 = NULL; 37 | unsigned char *md2 = NULL; 38 | 39 | if((mdctx = EVP_MD_CTX_new()) == NULL) 40 | return 1; 41 | 42 | if(EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) 43 | return 1; 44 | 45 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed + 4, 28) != 1) 46 | return 1; 47 | 48 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed, 4) != 1) 49 | return 1; 50 | 51 | if((md1 = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) 52 | return 1; 53 | 54 | if(EVP_DigestFinal_ex(mdctx, md1, NULL) != 1) 55 | return 1; 56 | 57 | EVP_MD_CTX_free(mdctx); 58 | 59 | printhex(md1, 32); 60 | printf("\n"); 61 | 62 | if((mdctx = EVP_MD_CTX_new()) == NULL) 63 | return 1; 64 | 65 | if(EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) 66 | return 1; 67 | 68 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed + 5, 27) != 1) 69 | return 1; 70 | 71 | if(EVP_DigestUpdate(mdctx, (unsigned char*)g_FirmwareSeed, 5) != 1) 72 | return 1; 73 | 74 | if((md2 = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) 75 | return 1; 76 | 77 | if(EVP_DigestFinal_ex(mdctx, md2, NULL) != 1) 78 | return 1; 79 | 80 | EVP_MD_CTX_free(mdctx); 81 | 82 | printhex(md2, 32); 83 | printf("\n"); 84 | 85 | // 86 | // ChaCha20 (custom) 87 | // 88 | 89 | FILE *f = fopen(argv[1], "rb"); 90 | fseek(f, 0, SEEK_END); 91 | long fsize = ftell(f); 92 | fseek(f, 0, SEEK_SET); 93 | printf("rootfs size: %u\n", fsize); 94 | char *data = malloc(fsize); 95 | memset(data, 0xFF, fsize); 96 | fread(data, fsize, 1, f); 97 | fclose(f); 98 | 99 | printf("Encrypting rootfs...\n"); 100 | struct chacha20_context ctx; 101 | chacha20_init_context(&ctx, md1, md2); 102 | chacha20_xor(&ctx, data, fsize); 103 | 104 | printf("Writing to %s...\n", argv[2]); 105 | FILE *f_out = fopen(argv[2], "wb"); 106 | fwrite(data, fsize, 1, f_out); 107 | 108 | // Add fake trailing signature 109 | char trail_sig[256]; 110 | memset(trail_sig, 0xFF, sizeof(trail_sig)); 111 | fwrite(trail_sig, sizeof(trail_sig), 1, f_out); 112 | fclose(f_out); 113 | 114 | return 0; 115 | } -------------------------------------------------------------------------------- /getrootfskey.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | import sys 4 | import binascii 5 | from miasm.analysis.binary import Container 6 | from miasm.analysis.machine import Machine 7 | from miasm.core.locationdb import LocationDB 8 | from miasm.ir.symbexec import SymbolicExecutionEngine 9 | from miasm.expression.expression import * 10 | 11 | parser = ArgumentParser("Get FortiGate rootfs encryption seed") 12 | parser.add_argument("target_binary", help="Target binary path") 13 | options = parser.parse_args() 14 | 15 | fdesc = open(options.target_binary, 'rb') 16 | loc_db = LocationDB() 17 | cont = Container.from_stream(fdesc, loc_db) 18 | machine = Machine(cont.arch) 19 | 20 | print(f"Architecture: {cont.arch}") 21 | ret_val_reg = None 22 | arg_val_reg = None 23 | match cont.arch: 24 | case "x86_64": 25 | ret_val_reg = machine.mn.regs.RAX 26 | arg_val_reg = machine.mn.regs.RSI 27 | case "aarch64l": 28 | ret_val_reg = machine.mn.regs.X0 29 | arg_val_reg = machine.mn.regs.X1 30 | case _: 31 | sys.stderr.write("OS architecture not supported!") 32 | sys.exit(1) 33 | 34 | mdis = machine.dis_engine(cont.bin_stream, loc_db=cont.loc_db) 35 | addr = loc_db.get_name_offset("fgt_verifier_pub_key") 36 | asmcfg = mdis.dis_multiblock(addr) 37 | lifter = machine.lifter_model_call(mdis.loc_db) 38 | ircfg = lifter.new_ircfg_from_asmcfg(asmcfg) 39 | 40 | symb = SymbolicExecutionEngine(lifter) 41 | all_seeds = list() 42 | while True: 43 | irblock = ircfg.get_block(addr) 44 | if irblock is None: 45 | break 46 | 47 | addr = symb.eval_updt_irblock(irblock, step=False) 48 | if ret_val_reg in symb.symbols.symbols_id: 49 | reg_expr = symb.symbols.symbols_id[ret_val_reg] 50 | if reg_expr.is_function_call(): 51 | target = reg_expr.args[0] 52 | target_func = loc_db.get_offset_location(target.arg) 53 | target_func = list(loc_db.get_location_names(target_func))[0] 54 | if target_func == "sha256_update": 55 | all_seeds.append(symb.symbols.symbols_id[arg_val_reg].arg) 56 | 57 | seed_addr = min(all_seeds) 58 | print(f"Seed address: {hex(seed_addr)}") 59 | 60 | seed_data = cont.executable.get_virt().get(seed_addr, seed_addr + 32) 61 | seed_data = binascii.hexlify(seed_data).upper() 62 | print(f"Extracted seed: {seed_data}") 63 | --------------------------------------------------------------------------------