mirror of
https://github.com/Cisco-Talos/clamav.git
synced 2025-10-19 10:23:17 +00:00
206 lines
7.1 KiB
Perl
206 lines
7.1 KiB
Perl
![]() |
#!/usr/bin/perl -w
|
||
|
use strict;
|
||
|
use Encode qw(encode);
|
||
|
use bigint;
|
||
|
|
||
|
my $clen = 4096; # standard chunk length
|
||
|
my $max_entries = $clen - 22; # max free space for entries in one chunk
|
||
|
|
||
|
sub u1($) { pack 'C', $_[0] }
|
||
|
sub u2($) { pack 'v', $_[0] }
|
||
|
sub u4($) { pack 'V', $_[0] }
|
||
|
sub u8($) { pack 'Q<', $_[0] }
|
||
|
|
||
|
sub guid($) {
|
||
|
my @x = split /-/, $_[0];
|
||
|
return pack 'VvvnH12', (map hex, @x[0..3]), $x[4];
|
||
|
}
|
||
|
|
||
|
sub encint($) {
|
||
|
my ($in, $out) = ($_[0] >> 7, u1($_[0] & 0x7F));
|
||
|
while ($in) {
|
||
|
$out = u1(0x80 | ($in & 0x7F)) . $out;
|
||
|
$in >>= 7;
|
||
|
}
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
sub entry {
|
||
|
my ($name, $section, $offset, $length) = @_;
|
||
|
return encint(length $name)
|
||
|
. $name
|
||
|
. encint($section)
|
||
|
. encint($offset)
|
||
|
. encint($length);
|
||
|
}
|
||
|
|
||
|
sub chunk {
|
||
|
my @entries = @_;
|
||
|
|
||
|
my $chdr = 'PMGL' # PMGL id
|
||
|
. u4(0) # 0x04 free space (FIXUP)
|
||
|
. u4(0) # 0x08 unknown
|
||
|
. u4(-1) # 0x0C previous chunk (FIXUP)
|
||
|
. u4(-1) # 0x10 next chunk (FIXUP)
|
||
|
; # 0x14 SIZEOF
|
||
|
my $cdata = join '', @entries;
|
||
|
|
||
|
my $cfree = $clen - length($cdata) - length($chdr);
|
||
|
die if length($cdata) > $max_entries;
|
||
|
|
||
|
# append empty "free space" area and number of entries
|
||
|
my $chunk = $chdr
|
||
|
. $cdata
|
||
|
. (u1(0) x ($cfree - 2))
|
||
|
. u2(scalar @entries);
|
||
|
|
||
|
# fixup free space in header
|
||
|
substr($chunk, 0x04, 4, u4($cfree));
|
||
|
return $chunk;
|
||
|
}
|
||
|
|
||
|
sub hdr {
|
||
|
my $guid1 = guid('7C01FD10-7BAA-11D0-9E0C-00A0C922E6EC');
|
||
|
my $guid2 = guid('7C01FD11-7BAA-11D0-9E0C-00A0C922E6EC');
|
||
|
return 'ITSF' # 0x00 id
|
||
|
. u4(2) # 0x04 version
|
||
|
. u4(0x58) # 0x08 total header length
|
||
|
. u4(1) # 0x0C unknown
|
||
|
. u4(0) # 0x10 timestamp
|
||
|
. u4(0x409) # 0x14 language (english)
|
||
|
. $guid1 # 0x18 GUID
|
||
|
. $guid2 # 0x28 GUID
|
||
|
. u8(0) # 0x38 hdr0 offset (FIXUP)
|
||
|
. u8(0) # 0x40 hdr0 length (FIXUP)
|
||
|
. u8(0) # 0x48 hdr1 offset (FIXUP)
|
||
|
. u8(0) # 0x50 hdr1 length (FIXUP)
|
||
|
; # SIZEOF: 0x54
|
||
|
}
|
||
|
|
||
|
sub hs0 {
|
||
|
return u4(0x1FE) # 0x00 unknown
|
||
|
. u4(0) # 0x04 unknown
|
||
|
. u8(0) # 0x08 file size (FIXUP)
|
||
|
. u4(0) # 0x10 unknown
|
||
|
. u4(0) # 0x14 unknown
|
||
|
; # SIZEOF: 0x18
|
||
|
}
|
||
|
|
||
|
sub hs1 {
|
||
|
my $cmax = shift;
|
||
|
my $guid3 = guid('5D02926A-212E-11D0-9DF9-00A0C922E6EC');
|
||
|
return 'ITSP' # 0x00 id
|
||
|
. u4(1) # 0x04 unknown
|
||
|
. u4(0x54) # 0x08 dir header length
|
||
|
. u4(0x0A) # 0x0C unknown
|
||
|
. u4($clen) # 0x10 dir chunk size
|
||
|
. u4(2) # 0x14 quickref density
|
||
|
. u4(1) # 0x18 index depth
|
||
|
. u4(-1) # 0x1C root PMGI chunk
|
||
|
. u4(0) # 0x20 first PMGL chunk
|
||
|
. u4($cmax) # 0x24 last PMGL chunk
|
||
|
. u4(-1) # 0x28 unknown
|
||
|
. u4(1) # 0x2C number of chunks
|
||
|
. u4(0x904) # 0x30 language
|
||
|
. $guid3 # 0x34 GUID
|
||
|
. u4(0x54) # 0x44 header length
|
||
|
. u4(-1) # 0x48 unknown
|
||
|
. u4(-1) # 0x4C unknown
|
||
|
. u4(-1) # 0x50 unknown
|
||
|
; # SIZEOF: 0x54
|
||
|
}
|
||
|
|
||
|
sub chm {
|
||
|
my @chunks = @_;
|
||
|
my ($hdr, $hs0, $hs1) = (hdr(), hs0(), hs1($#chunks));
|
||
|
substr($hdr, 0x38, 8, u8(length($hdr))); # hs0 offset
|
||
|
substr($hdr, 0x40, 8, u8(length($hs0))); # hs0 length
|
||
|
substr($hdr, 0x48, 8, u8(length($hdr) + length($hs0))); # hs1 offset
|
||
|
substr($hdr, 0x50, 8, u8(length($hs1))); # hs1 length
|
||
|
substr($hs0, 0x08, 8, u8(length($hdr) + length($hs0) + length($hs1) + ($clen * @chunks))); # chm length
|
||
|
for (my $i = 1; $i <= $#chunks; $i++) {
|
||
|
substr($chunks[$i], 0x0C, 4, u4($i - 1)); # previous chunk
|
||
|
substr($chunks[$i - 1], 0x10, 4, u4($i)); # next chunk
|
||
|
}
|
||
|
return join '', $hdr, $hs0, $hs1, @chunks;
|
||
|
}
|
||
|
|
||
|
# Create a CHM with the filename "::" right at the end of a PMGL chunk
|
||
|
#
|
||
|
# libmspack < 0.9.1alpha calls memcmp() on any entry beginning "::" to see if
|
||
|
# it begins "::DataSpace/Storage/MSCompressed/" (33 bytes), even when the name
|
||
|
# is shorter. If the entry is right at the end of a chunk, we can get libmspack
|
||
|
# to overread past the end of the chunk by up to 28 bytes
|
||
|
sub chm_sysname_overread {
|
||
|
if (open my $fh, '>', 'cve-2019-1010305-name-overread.chm') {
|
||
|
my $sysname = entry('::', 0, 0, 0);
|
||
|
my $padding = entry('x' x $clen, 0, 0, 0);
|
||
|
my $padding_overhead = length($padding) - $clen;
|
||
|
my $maxlen = $max_entries - length($sysname) - $padding_overhead;
|
||
|
$padding = entry('x' x $maxlen, 0, 0, 0);
|
||
|
print $fh chm(chunk($padding, $sysname));
|
||
|
close $fh;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Create a CHM with entries containing unicode character U+100
|
||
|
sub chm_unicode_u100 {
|
||
|
if (open my $fh, '>', 'cve-2018-14682-unicode-u100.chm') {
|
||
|
my $u100 = encode('UTF-8', chr(256));
|
||
|
my $entry1 = entry("1", 0, 1, 1);
|
||
|
my $entry2 = entry($u100, 0, 2, 2);
|
||
|
print $fh chm(chunk($entry1, $entry2));
|
||
|
close $fh;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Create a CHM with ENCINTs that go beyond what 32-bit architectures can handle
|
||
|
sub chm_encints_32bit {
|
||
|
chm_encints('encints-32bit-offsets.chm', 2147483647, 1, 0);
|
||
|
chm_encints('encints-32bit-lengths.chm', 2147483647, 0, 1);
|
||
|
chm_encints('encints-32bit-both.chm', 2147483647, 1, 1);
|
||
|
}
|
||
|
|
||
|
# Create a CHM with ENCINTs that go beyond what 64-bit architectures can handle
|
||
|
sub chm_encints_64bit {
|
||
|
chm_encints('encints-64bit-offsets.chm', 9223372036854775807, 1, 0);
|
||
|
chm_encints('encints-64bit-lengths.chm', 9223372036854775807, 0, 1);
|
||
|
chm_encints('encints-64bit-both.chm', 9223372036854775807, 1, 1);
|
||
|
}
|
||
|
|
||
|
sub chm_encints {
|
||
|
my ($fname, $max_good, $off_val, $len_val) = @_;
|
||
|
my @vals = (
|
||
|
127, 128, # 1->2 byte encoding
|
||
|
16383, 16384, # 2->3 byte encoding
|
||
|
2097151, 2097152, # 3->4 byte encoding
|
||
|
268435455, 268435456, # 4->5 byte encoding
|
||
|
2147483647, 2147483648, # 2^31-1, 2^31 (doesn't fit in 32-bit off_t)
|
||
|
34359738367, 34359738368, # 5->6 byte encoding
|
||
|
4398046511103, 4398046511104, # 6->7 byte encoding
|
||
|
562949953421311, 562949953421312, # 7->8 byte encoding
|
||
|
72057594037927935, 72057594037927936, # 8->9 byte encoding
|
||
|
9223372036854775807, 9223372036854775808, # 2^63-1, 2^63 (doesn't fit in 64-bit off_t)
|
||
|
147573952589676412927, 147573952589676412928, # 9->10 byte encoding
|
||
|
1180591620717411303423, 1180591620717411303424, # 10->11 byte encoding
|
||
|
151115727451828646838271, 151115727451828646838272, # 11->12 byte encoding
|
||
|
19342813113834066795298815, 19342813113834066795298816); # 12->13 byte encoding
|
||
|
my @entries;
|
||
|
my $i = 0;
|
||
|
for my $val (@vals) {
|
||
|
my $name = sprintf '%s%02i', $val <= $max_good ? 'good' : 'bad', $i++;
|
||
|
my $offset = $off_val ? $val : 1;
|
||
|
my $length = $len_val ? $val : 1;
|
||
|
push @entries, entry($name, 0, $offset, $length);
|
||
|
}
|
||
|
if (open my $fh, '>', $fname) {
|
||
|
print $fh chm(chunk(@entries));
|
||
|
close $fh;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
chm_sysname_overread();
|
||
|
chm_unicode_u100();
|
||
|
chm_encints_32bit();
|
||
|
chm_encints_64bit();
|