/* * Extract component parts of MS CHM files * * Copyright (C) 2004-2005 trog@uncon.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) #if HAVE_MMAP #if HAVE_SYS_MMAN_H #include #else /* HAVE_SYS_MMAN_H */ #undef HAVE_MMAP #endif /* HAVE_SYS_MMAN_H */ #endif /* HAVE_MMAP */ #else/* PACKED */ #undef HAVE_MMAP #endif #include "others.h" #include "mspack.h" #include "cltypes.h" #include "chmunpack.h" #ifndef HAVE_ATTRIB_PACKED #define __attribute__(x) #endif #ifdef HAVE_PRAGMA_PACK #pragma pack(1) #endif #ifdef HAVE_PRAGMA_PACK_HPPA #pragma pack 1 #endif #ifndef O_BINARY #define O_BINARY 0 #endif #define CHM_ITSF_MIN_LEN (0x60) typedef struct itsf_header_tag { unsigned char signature[4] __attribute__ ((packed)); int32_t version __attribute__ ((packed)); int32_t header_len __attribute__ ((packed)); uint32_t unknown __attribute__ ((packed)); uint32_t last_modified __attribute__ ((packed)); uint32_t lang_id __attribute__ ((packed)); unsigned char dir_clsid[16] __attribute__ ((packed)); unsigned char stream_clsid[16] __attribute__ ((packed)); uint64_t sec0_offset __attribute__ ((packed)); uint64_t sec0_len __attribute__ ((packed)); uint64_t dir_offset __attribute__ ((packed)); uint64_t dir_len __attribute__ ((packed)); uint64_t data_offset __attribute__ ((packed)); } itsf_header_t; #define CHM_ITSP_LEN (0x54) typedef struct itsp_header_tag { unsigned char signature[4] __attribute__ ((packed)); int32_t version __attribute__ ((packed)); int32_t header_len __attribute__ ((packed)); int32_t unknown1 __attribute__ ((packed)); uint32_t block_len __attribute__ ((packed)); int32_t blockidx_intvl __attribute__ ((packed)); int32_t index_depth __attribute__ ((packed)); int32_t index_root __attribute__ ((packed)); int32_t index_head __attribute__ ((packed)); int32_t index_tail __attribute__ ((packed)); int32_t unknown2 __attribute__ ((packed)); uint32_t num_blocks __attribute__ ((packed)); uint32_t lang_id __attribute__ ((packed)); unsigned char system_clsid[16] __attribute__ ((packed)); unsigned char unknown4[16] __attribute__ ((packed)); } itsp_header_t; #define CHM_CHUNK_HDR_LEN (0x14) typedef struct chunk_header_tag { unsigned char signature[4] __attribute__ ((packed)); uint32_t free_space __attribute__ ((packed)); uint32_t unknown __attribute__ ((packed)); int32_t block_prev __attribute__ ((packed)); int32_t block_next __attribute__ ((packed)); unsigned char *chunk_data; uint16_t num_entries; } chunk_header_t; typedef struct file_list_tag { unsigned char *name; uint64_t section; uint64_t offset; uint64_t length; struct file_list_tag *next; } file_list_t; #define CHM_CONTROL_LEN (0x18) typedef struct lzx_control_tag { uint32_t length __attribute__ ((packed)); unsigned char signature[4] __attribute__ ((packed)); uint32_t version __attribute__ ((packed)); uint32_t reset_interval __attribute__ ((packed)); uint32_t window_size __attribute__ ((packed)); uint32_t cache_size __attribute__ ((packed)); } lzx_control_t; /* Don't need to include rt_offset in the strucuture len*/ #define CHM_RESET_TABLE_LEN (0x24) typedef struct lzx_reset_table_tag { uint32_t num_entries __attribute__ ((packed)); uint32_t entry_size __attribute__ ((packed)); uint32_t table_offset __attribute__ ((packed)); uint64_t uncom_len __attribute__ ((packed)); uint64_t com_len __attribute__ ((packed)); uint64_t frame_len __attribute__ ((packed)); off_t rt_offset __attribute__ ((packed)); } lzx_reset_table_t; typedef struct lzx_content_tag { uint64_t offset; uint64_t length; } lzx_content_t; #ifdef HAVE_PRAGMA_PACK #pragma pack() #endif #ifdef HAVE_PRAGMA_PACK_HPPA #pragma pack #endif #define chm_endian_convert_16(x) le16_to_host(x) #define chm_endian_convert_32(x) le32_to_host(x) #define chm_endian_convert_64(x) le64_to_host(x) /* Read in a block of data from either the mmap area or the given fd */ static int chm_read_data(int fd, unsigned char *dest, off_t offset, off_t len, unsigned char *m_area, off_t m_length) { if ((offset < 0) || (len < 0) || ((offset+len) < 0)) { return FALSE; } if (m_area != NULL) { if ((offset+len) > m_length) { return FALSE; } memcpy(dest, m_area+offset, len); } else { if (lseek(fd, offset, SEEK_SET) != offset) { return FALSE; } if (cli_readn(fd, dest, len) != len) { return FALSE; } } return TRUE; } static uint64_t chm_copy_file_data(int ifd, int ofd, uint64_t len) { unsigned char data[8192]; uint64_t count, rem; unsigned int todo; rem = len; while (rem > 0) { todo = MIN(8192, rem); count = cli_readn(ifd, data, todo); if (count != todo) { return len-rem; } if (cli_writen(ofd, data, count) != (int64_t)count) { return len-rem-count; } rem -= count; } return len; } static void free_file_list(file_list_t *file_l) { file_list_t *next; while (file_l) { next = file_l->next; if (file_l->name) { free(file_l->name); } free(file_l); file_l = next; } } static void itsf_print_header(itsf_header_t *itsf_hdr) { if (!itsf_hdr) { return; } cli_dbgmsg("---- ITSF ----\n"); cli_dbgmsg("Signature:\t%c%c%c%c\n", itsf_hdr->signature[0], itsf_hdr->signature[1],itsf_hdr->signature[2],itsf_hdr->signature[3]); cli_dbgmsg("Version:\t%d\n", itsf_hdr->version); cli_dbgmsg("Header len:\t%ld\n", itsf_hdr->header_len); cli_dbgmsg("Lang ID:\t%d\n", itsf_hdr->lang_id); cli_dbgmsg("Sec0 offset:\t%llu\n", itsf_hdr->sec0_offset); cli_dbgmsg("Sec0 len:\t%llu\n", itsf_hdr->sec0_len); cli_dbgmsg("Dir offset:\t%llu\n", itsf_hdr->dir_offset); cli_dbgmsg("Dir len:\t%llu\n", itsf_hdr->dir_len); if (itsf_hdr->version > 2) { cli_dbgmsg("Data offset:\t%llu\n\n", itsf_hdr->data_offset); } } static int itsf_read_header(int fd, itsf_header_t *itsf_hdr, unsigned char *m_area, off_t m_length) { #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) if (!chm_read_data(fd, (unsigned char *) itsf_hdr, 0, CHM_ITSF_MIN_LEN, m_area, m_length)) { return FALSE; } #else if (cli_readn(fd, &itsf_hdr->signature, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsf_hdr->version, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsf_hdr->header_len, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsf_hdr->unknown, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsf_hdr->last_modified, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsf_hdr->lang_id, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsf_hdr->dir_clsid, 16) != 16) { return FALSE; } if (cli_readn(fd, &itsf_hdr->stream_clsid, 16) != 16) { return FALSE; } if (cli_readn(fd, &itsf_hdr->sec0_offset, 8) != 8) { return FALSE; } if (cli_readn(fd, &itsf_hdr->sec0_len, 8) != 8) { return FALSE; } if (cli_readn(fd, &itsf_hdr->dir_offset, 8) != 8) { return FALSE; } if (cli_readn(fd, &itsf_hdr->dir_len, 8) != 8) { return FALSE; } if (itsf_hdr->version > 2) { if (cli_readn(fd, &itsf_hdr->data_offset, 8) != 8) { return FALSE; } } #endif if (memcmp(itsf_hdr->signature, "ITSF", 4) != 0) { cli_dbgmsg("ITSF signature mismatch\n"); return FALSE; } itsf_hdr->version = chm_endian_convert_32(itsf_hdr->version); itsf_hdr->header_len = chm_endian_convert_32(itsf_hdr->header_len); itsf_hdr->last_modified = chm_endian_convert_32(itsf_hdr->last_modified); itsf_hdr->lang_id = chm_endian_convert_32(itsf_hdr->lang_id); itsf_hdr->sec0_offset = chm_endian_convert_64(itsf_hdr->sec0_offset); itsf_hdr->sec0_len = chm_endian_convert_64(itsf_hdr->sec0_len); itsf_hdr->dir_offset = chm_endian_convert_64(itsf_hdr->dir_offset); itsf_hdr->dir_len = chm_endian_convert_64(itsf_hdr->dir_len); if (itsf_hdr->version > 2) { itsf_hdr->data_offset = chm_endian_convert_64(itsf_hdr->data_offset); } return TRUE; } static void itsp_print_header(itsp_header_t *itsp_hdr) { if (!itsp_hdr) { return; } cli_dbgmsg("---- ITSP ----\n"); cli_dbgmsg("Signature:\t%c%c%c%c\n", itsp_hdr->signature[0], itsp_hdr->signature[1],itsp_hdr->signature[2],itsp_hdr->signature[3]); cli_dbgmsg("Version:\t%d\n", itsp_hdr->version); cli_dbgmsg("Block len:\t%ld\n", itsp_hdr->block_len); cli_dbgmsg("Block idx int:\t%d\n", itsp_hdr->blockidx_intvl); cli_dbgmsg("Index depth:\t%d\n", itsp_hdr->index_depth); cli_dbgmsg("Index root:\t%d\n", itsp_hdr->index_root); cli_dbgmsg("Index head:\t%u\n", itsp_hdr->index_head); cli_dbgmsg("Index tail:\t%u\n", itsp_hdr->index_tail); cli_dbgmsg("Num Blocks:\t%u\n", itsp_hdr->num_blocks); cli_dbgmsg("Lang ID:\t%lu\n\n", itsp_hdr->lang_id); } static int itsp_read_header(int fd, itsp_header_t *itsp_hdr, off_t offset, unsigned char *m_area, off_t m_length) { #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) if (!chm_read_data(fd, (unsigned char *) itsp_hdr, offset, CHM_ITSP_LEN, m_area, m_length)) { return FALSE; } #else if (lseek(fd, offset, SEEK_SET) != offset) { return FALSE; } if (cli_readn(fd, &itsp_hdr->signature, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->version, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->header_len, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->unknown1, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->block_len, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->blockidx_intvl, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->index_depth, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->index_root, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->index_head, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->index_tail, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->unknown2, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->num_blocks, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->lang_id, 4) != 4) { return FALSE; } if (cli_readn(fd, &itsp_hdr->system_clsid, 16) != 16) { return FALSE; } if (cli_readn(fd, &itsp_hdr->unknown4, 16) != 16) { return FALSE; } #endif if (memcmp(itsp_hdr->signature, "ITSP", 4) != 0) { cli_dbgmsg("ITSP signature mismatch\n"); return FALSE; } itsp_hdr->version = chm_endian_convert_32(itsp_hdr->version); itsp_hdr->header_len = chm_endian_convert_32(itsp_hdr->header_len); itsp_hdr->block_len = chm_endian_convert_32(itsp_hdr->block_len); itsp_hdr->blockidx_intvl = chm_endian_convert_32(itsp_hdr->blockidx_intvl); itsp_hdr->index_depth = chm_endian_convert_32(itsp_hdr->index_depth); itsp_hdr->index_root = chm_endian_convert_32(itsp_hdr->index_root); itsp_hdr->index_head = chm_endian_convert_32(itsp_hdr->index_head); itsp_hdr->index_tail = chm_endian_convert_32(itsp_hdr->index_tail); itsp_hdr->num_blocks = chm_endian_convert_32(itsp_hdr->num_blocks); itsp_hdr->lang_id = chm_endian_convert_32(itsp_hdr->lang_id); if ((itsp_hdr->version != 1) || (itsp_hdr->header_len != CHM_ITSP_LEN)) { cli_dbgmsg("ITSP header mismatch\n"); return FALSE; } return TRUE; } static uint64_t read_enc_int(unsigned char **start, unsigned char *end) { uint64_t retval=0; unsigned char *current; current = *start; if (current > end) { return 0; } do { if (current > end) { return 0; } retval = (retval << 7) | (*current & 0x7f); } while (*current++ & 0x80); *start = current; return retval; } /* Read chunk entries */ /* Note: the file lists end up in reverse order to the order in the chunk */ static int read_chunk_entries(unsigned char *chunk, uint32_t chunk_len, uint16_t num_entries, file_list_t *file_l, file_list_t *sys_file_l) { unsigned char *current, *end; uint64_t name_len; file_list_t *file_e; end = chunk + chunk_len; current = chunk + CHM_CHUNK_HDR_LEN; while (num_entries--) { if (current > end) { cli_dbgmsg("read chunk entries failed\n"); return FALSE; } file_e = (file_list_t *) cli_malloc(sizeof(file_list_t)); if (!file_e) { return FALSE; } file_e->next = NULL; name_len = read_enc_int(¤t, end); if (((current + name_len) > end) || ((current + name_len) < chunk)) { cli_dbgmsg("Bad CHM name_len detected\n"); free(file_e); return FALSE; } if (name_len > 0xFFFFFF) { cli_dbgmsg("CHM file name too long: %llu\n", name_len); file_e->name = (unsigned char *) cli_strdup("truncated"); if (!file_e->name) { free(file_e); return FALSE; } } else { file_e->name = (unsigned char *) cli_malloc(name_len+1); if (!file_e->name) { free(file_e); return FALSE; } strncpy(file_e->name, current, name_len); file_e->name[name_len] = '\0'; } current += name_len; file_e->section = read_enc_int(¤t, end); file_e->offset = read_enc_int(¤t, end); file_e->length = read_enc_int(¤t, end); if ((name_len >= 2) && (file_e->name[0] == ':') && (file_e->name[1] == ':')) { file_e->next = sys_file_l->next; sys_file_l->next = file_e; } else { file_e->next = file_l->next; file_l->next = file_e; } cli_dbgmsg("Section: %llu Offset: %llu Length: %llu, Name: %s\n", file_e->section, file_e->offset, file_e->length, file_e->name); } return TRUE; } static void print_chunk(chunk_header_t *chunk) { cli_dbgmsg("---- Chunk ----\n"); cli_dbgmsg("Signature:\t%c%c%c%c\n", chunk->signature[0], chunk->signature[1],chunk->signature[2],chunk->signature[3]); cli_dbgmsg("Free Space:\t%u\n", chunk->free_space); if (memcmp(chunk->signature, "PMGL", 4) == 0) { cli_dbgmsg("Prev Block:\t%d\n", chunk->block_prev); cli_dbgmsg("Next Block:\t%d\n", chunk->block_next); cli_dbgmsg("Num entries:\t%d\n\n", chunk->num_entries); } return; } static int read_chunk(int fd, off_t offset, uint32_t chunk_len, unsigned char *m_area, off_t m_length, file_list_t *file_l, file_list_t *sys_file_l) { chunk_header_t *chunk_hdr; int retval = FALSE; if (chunk_len < 8 || chunk_len > 33554432) { return FALSE; } chunk_hdr = (chunk_header_t *) cli_malloc(sizeof(chunk_header_t)); if (!chunk_hdr) { return FALSE; } chunk_hdr->chunk_data = (unsigned char *) cli_malloc(chunk_len); if (!chunk_hdr->chunk_data) { free(chunk_hdr); return FALSE; } #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) /* 8 bytes reads the signature and the free_space */ if (!chm_read_data(fd, chunk_hdr->signature, offset, 8, m_area, m_length)) { goto abort; } if (!chm_read_data(fd, chunk_hdr->chunk_data, offset, chunk_len, m_area, m_length)) { goto abort; } #else if (lseek(fd, offset, SEEK_SET) != offset) { goto abort; } if (cli_readn(fd, chunk_hdr->chunk_data, chunk_len) != chunk_len) { goto abort; } if (lseek(fd, offset, SEEK_SET) != offset) { goto abort; } if (cli_readn(fd, &chunk_hdr->signature, 4) != 4) { goto abort; } if (cli_readn(fd, &chunk_hdr->free_space, 4) != 4) { goto abort; } #endif chunk_hdr->free_space = chm_endian_convert_32(chunk_hdr->free_space); if (memcmp(chunk_hdr->signature, "PMGL", 4) == 0) { #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) if (!chm_read_data(fd, (unsigned char *) &chunk_hdr->unknown, offset+8, 12, m_area, m_length)) { goto abort; } #else if (cli_readn(fd, &chunk_hdr->unknown, 4) != 4) { goto abort; } if (cli_readn(fd, &chunk_hdr->block_next, 4) != 4) { goto abort; } if (cli_readn(fd, &chunk_hdr->block_prev, 4) != 4) { goto abort; } #endif chunk_hdr->block_next = chm_endian_convert_32(chunk_hdr->block_next); chunk_hdr->block_prev = chm_endian_convert_32(chunk_hdr->block_prev); chunk_hdr->num_entries = (uint16_t)((((uint8_t const *)(chunk_hdr->chunk_data))[chunk_len-2] << 0) | (((uint8_t const *)(chunk_hdr->chunk_data))[chunk_len-1] << 8)); read_chunk_entries(chunk_hdr->chunk_data, chunk_len, chunk_hdr->num_entries, file_l, sys_file_l); } else if (memcmp(chunk_hdr->signature, "PMGI", 4) != 0) { goto abort; } print_chunk(chunk_hdr); retval=TRUE; abort: free(chunk_hdr->chunk_data); free(chunk_hdr); return retval; } static void print_sys_control(lzx_control_t *lzx_control) { if (!lzx_control) { return; } cli_dbgmsg("---- Control ----\n"); cli_dbgmsg("Length:\t\t%lu\n", lzx_control->length); cli_dbgmsg("Signature:\t%c%c%c%c\n", lzx_control->signature[0], lzx_control->signature[1],lzx_control->signature[2],lzx_control->signature[3]); cli_dbgmsg("Version:\t%d\n", lzx_control->version); cli_dbgmsg("Reset Interval:\t%d\n", lzx_control->reset_interval); cli_dbgmsg("Window Size:\t%d\n", lzx_control->window_size); cli_dbgmsg("Cache Size:\t%d\n\n", lzx_control->cache_size); } static lzx_control_t *read_sys_control(int fd, itsf_header_t *itsf_hdr, file_list_t *file_e, unsigned char *m_area, off_t m_length) { off_t offset; lzx_control_t *lzx_control; if (file_e->length != 28) { return NULL; } offset = itsf_hdr->data_offset + file_e->offset; if (offset < 0) { return NULL; } lzx_control = (lzx_control_t *) cli_malloc(sizeof(lzx_control_t)); if (!lzx_control) { return NULL; } #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) if (!chm_read_data(fd, (unsigned char *) lzx_control, offset, CHM_CONTROL_LEN, m_area, m_length)) { goto abort; } #else if (lseek(fd, offset, SEEK_SET) != offset) { goto abort; } if (cli_readn(fd, &lzx_control->length, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_control->signature, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_control->version, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_control->reset_interval, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_control->window_size, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_control->cache_size, 4) != 4) { goto abort; } #endif lzx_control->length = chm_endian_convert_32(lzx_control->length); lzx_control->version = chm_endian_convert_32(lzx_control->version); lzx_control->reset_interval = chm_endian_convert_32(lzx_control->reset_interval); lzx_control->window_size = chm_endian_convert_32(lzx_control->window_size); lzx_control->cache_size = chm_endian_convert_32(lzx_control->cache_size); if (strncmp("LZXC", lzx_control->signature, 4) != 0) { cli_dbgmsg("bad sys_control signature"); goto abort; } switch(lzx_control->version) { case 1: break; case 2: lzx_control->reset_interval *= LZX_FRAME_SIZE; lzx_control->window_size *= LZX_FRAME_SIZE; break; default: cli_dbgmsg("Unknown sys_control version:%d\n", lzx_control->version); goto abort; } print_sys_control(lzx_control); return lzx_control; abort: free(lzx_control); return NULL; } static void print_sys_content(lzx_content_t *lzx_content) { if (!lzx_content) { return; } cli_dbgmsg("---- Content ----\n"); cli_dbgmsg("Offset:\t%llu\n", lzx_content->offset); cli_dbgmsg("Length:\t%llu\n\n", lzx_content->length); } static lzx_content_t *read_sys_content(int fd, itsf_header_t *itsf_hdr, file_list_t *file_e) { lzx_content_t *lzx_content; lzx_content = (lzx_content_t *) cli_malloc(sizeof(lzx_content_t)); if (!lzx_content) { return NULL; } lzx_content->offset = itsf_hdr->data_offset + file_e->offset; lzx_content->length = file_e->length; print_sys_content(lzx_content); return lzx_content; } static void print_sys_reset_table(lzx_reset_table_t *lzx_reset_table) { if (!lzx_reset_table) { return; } cli_dbgmsg("---- Reset Table ----\n"); cli_dbgmsg("Num Entries:\t%lu\n", lzx_reset_table->num_entries); cli_dbgmsg("Entry Size:\t%lu\n", lzx_reset_table->entry_size); cli_dbgmsg("Table Offset:\t%lu\n", lzx_reset_table->table_offset); cli_dbgmsg("Uncom Len:\t%llu\n", lzx_reset_table->uncom_len); cli_dbgmsg("Com Len:\t%llu\n", lzx_reset_table->com_len); cli_dbgmsg("Frame Len:\t%llu\n\n", lzx_reset_table->frame_len); } static lzx_reset_table_t *read_sys_reset_table(int fd, itsf_header_t *itsf_hdr, file_list_t *file_e, unsigned char *m_area, off_t m_length) { off_t offset; lzx_reset_table_t *lzx_reset_table; if (file_e->length < 40) { return NULL; } /* Skip past unknown entry in offset calc */ offset = itsf_hdr->data_offset + file_e->offset + 4; if (offset < 0) { return NULL; } lzx_reset_table = (lzx_reset_table_t *) cli_malloc(sizeof(lzx_reset_table_t)); if (!lzx_reset_table) { return NULL; } /* Save the entry offset for later use */ lzx_reset_table->rt_offset = offset-4; #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA) if (!chm_read_data(fd, (unsigned char *) lzx_reset_table, offset, CHM_RESET_TABLE_LEN, m_area, m_length)) { goto abort; } #else if (lseek(fd, offset, SEEK_SET) != offset) { goto abort; } if (cli_readn(fd, &lzx_reset_table->num_entries, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_reset_table->entry_size, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_reset_table->table_offset, 4) != 4) { goto abort; } if (cli_readn(fd, &lzx_reset_table->uncom_len, 8) != 8) { goto abort; } if (cli_readn(fd, &lzx_reset_table->com_len, 8) != 8) { goto abort; } if (cli_readn(fd, &lzx_reset_table->frame_len, 8) != 8) { goto abort; } #endif lzx_reset_table->num_entries = chm_endian_convert_32(lzx_reset_table->num_entries); lzx_reset_table->entry_size = chm_endian_convert_32(lzx_reset_table->entry_size); lzx_reset_table->table_offset = chm_endian_convert_32(lzx_reset_table->table_offset); lzx_reset_table->uncom_len = chm_endian_convert_64(lzx_reset_table->uncom_len); lzx_reset_table->com_len = chm_endian_convert_64(lzx_reset_table->com_len); lzx_reset_table->frame_len = chm_endian_convert_64(lzx_reset_table->frame_len); if (lzx_reset_table->frame_len != LZX_FRAME_SIZE) { cli_dbgmsg("bad sys_reset_table frame_len: 0x%x\n",lzx_reset_table->frame_len); goto abort; } if ((lzx_reset_table->entry_size != 4) && (lzx_reset_table->entry_size != 8)) { cli_dbgmsg("bad sys_reset_table entry_size: 0x%x\n",lzx_reset_table->entry_size); goto abort; } print_sys_reset_table(lzx_reset_table); return lzx_reset_table; abort: free(lzx_reset_table); return NULL; } /* *****************************************************************/ /* This section interfaces to the mspack files. As such, this is a */ /* little bit dirty compared to my usual code */ #define CHM_SYS_CONTROL_NAME "::DataSpace/Storage/MSCompressed/ControlData" #define CHM_SYS_CONTENT_NAME "::DataSpace/Storage/MSCompressed/Content" #define CHM_SYS_RESETTABLE_NAME "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable" static int chm_decompress_stream(int fd, const char *dirname, itsf_header_t *itsf_hdr, file_list_t *file_l, file_list_t *sys_file_l, unsigned char *m_area, off_t m_length) { file_list_t *entry; lzx_content_t *lzx_content=NULL; lzx_reset_table_t *lzx_reset_table=NULL; lzx_control_t *lzx_control=NULL; int window_bits, count, length, tmpfd, ofd, retval=FALSE; uint64_t com_offset; struct lzx_stream * stream; unsigned char filename[1024]; snprintf(filename, 1024, "%s/clamav-unchm.bin", dirname); tmpfd = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU); if (tmpfd<0) { cli_dbgmsg("open failed for %s\n", filename); return FALSE; } entry = sys_file_l->next; while (entry) { if (strcmp(entry->name, CHM_SYS_CONTROL_NAME) == 0) { lzx_control = read_sys_control(fd, itsf_hdr, entry, m_area, m_length); } else if (strcmp(entry->name, CHM_SYS_CONTENT_NAME) == 0) { lzx_content = read_sys_content(fd, itsf_hdr, entry); } else if (strcmp(entry->name, CHM_SYS_RESETTABLE_NAME) == 0) { lzx_reset_table = read_sys_reset_table(fd, itsf_hdr, entry, m_area, m_length); } entry = entry->next; } if (!lzx_content || !lzx_reset_table || !lzx_control) { goto abort; } switch (lzx_control->window_size) { case 0x008000: window_bits = 15; break; case 0x010000: window_bits = 16; break; case 0x020000: window_bits = 17; break; case 0x040000: window_bits = 18; break; case 0x080000: window_bits = 19; break; case 0x100000: window_bits = 20; break; case 0x200000: window_bits = 21; break; default: cli_dbgmsg("bad control window size: 0x%x\n", lzx_control->window_size); goto abort; } if (lzx_control->reset_interval % LZX_FRAME_SIZE) { cli_dbgmsg("bad reset_interval: 0x%x\n", lzx_control->window_size); goto abort; } length = lzx_reset_table->uncom_len; length += lzx_control->reset_interval; length &= -lzx_control->reset_interval; com_offset = lzx_content->offset; cli_dbgmsg("Compressed offset: %llu\n", com_offset); stream = lzx_init(fd, tmpfd, window_bits, lzx_control->reset_interval / LZX_FRAME_SIZE, 4096, length, NULL, NULL); lseek(fd, com_offset, SEEK_SET); if (!stream) { cli_dbgmsg("lzx_init failed\n"); goto abort; } lzx_decompress(stream, length); lzx_free(stream); entry = file_l->next; close(tmpfd); /* Reopen the file for reading */ tmpfd = open(filename, O_RDONLY|O_BINARY); if (tmpfd < 0) { cli_dbgmsg("re-open output failed\n"); goto abort; } /* Delete the file */ unlink(filename); count=0; while(entry) { if (entry->section != 1) { entry = entry->next; continue; } if (lseek(tmpfd, entry->offset, SEEK_SET) != (off_t)entry->offset) { cli_dbgmsg("seek in output failed\n"); entry = entry->next; continue; } snprintf(filename, 1024, "%s/%d-%llu.chm", dirname, count, entry->offset); ofd = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU); if (ofd < 0) { entry = entry->next; continue; } if (chm_copy_file_data(tmpfd, ofd, entry->length) != entry->length) { cli_dbgmsg("failed to copy %lu bytes\n", entry->length); } close(ofd); entry = entry->next; count++; } close(tmpfd); tmpfd=-1; retval = TRUE; abort: if (tmpfd>=0) { close(tmpfd); } if (lzx_content) { free(lzx_content); } if (lzx_reset_table) { free(lzx_reset_table); } if (lzx_control) { free(lzx_control); } return retval; } /* ************ End dirty section ********************/ int chm_unpack(int fd, const char *dirname) { int retval=FALSE; unsigned char *m_area=NULL; off_t m_length=0, offset; file_list_t *file_l, *sys_file_l; struct stat statbuf; itsf_header_t itsf_hdr; itsp_header_t itsp_hdr; uint32_t num_chunks; /* These two lists contain the list of files and system files in the archive. The first entry in the list is an empty entry */ file_l = (file_list_t *) cli_malloc(sizeof(file_list_t)); if (!file_l) { return FALSE; } file_l->next = NULL; file_l->name = NULL; sys_file_l = (file_list_t *) cli_malloc(sizeof(file_list_t)); if (!sys_file_l) { free(file_l); return FALSE; } sys_file_l->next = NULL; sys_file_l->name = NULL; #ifdef HAVE_MMAP if (fstat(fd, &statbuf) == 0) { if (statbuf.st_size < CHM_ITSF_MIN_LEN) { goto abort; } m_length = statbuf.st_size; m_area = (unsigned char *) mmap(NULL, m_length, PROT_READ, MAP_PRIVATE, fd, 0); if (m_area == MAP_FAILED) { m_area = NULL; } } #endif if (!itsf_read_header(fd, &itsf_hdr, m_area, m_length)) { goto abort; } itsf_print_header(&itsf_hdr); if (!itsp_read_header(fd, &itsp_hdr, itsf_hdr.dir_offset, m_area, m_length)) { goto abort; } itsp_print_header(&itsp_hdr); offset = itsf_hdr.dir_offset+CHM_ITSP_LEN; /* TODO: need to check this first calculation, currently have no files of this type */ if (itsp_hdr.index_head > 0) { offset += itsp_hdr.index_head * itsp_hdr.block_len; } num_chunks = itsp_hdr.index_tail - itsp_hdr.index_head + 1; /* Versions before 3 didn't have a data_offset */ /* TODO: need to check this calculation, currently have no files of this type */ if (itsf_hdr.version < 3) { itsf_hdr.data_offset = itsf_hdr.dir_offset + CHM_ITSP_LEN + (itsp_hdr.block_len*itsp_hdr.num_blocks); } while (num_chunks) { if (!read_chunk(fd, offset, itsp_hdr.block_len, m_area, m_length, file_l, sys_file_l)) { goto abort; } num_chunks--; offset += itsp_hdr.block_len; } chm_decompress_stream(fd, dirname, &itsf_hdr, file_l, sys_file_l, m_area, m_length); /* Signal success */ retval = TRUE; abort: free_file_list(file_l); free_file_list(sys_file_l); #ifdef HAVE_MMAP if (m_area) { munmap(m_area, m_length); } #endif return retval; }