This post is authored by Marcin Noga with contributions by Holger Unterbrink

Overview
Crash triaging can be a long and complicated process; by using proper tools and having an optimal approach, we can make this a bit easier and less time consuming. In this post we describe a triaging strategy and toolset based on two examples of vulnerability classes:

  • Stack based buffer overflow
  • Heap based buffer overflow / Heap corruption
    As examples we will use real vulnerabilities found by Marcin Noga of Talos earlier this year.

LexMark Perceptive Document Filters XLS Convert Code Execution Vulnerability
Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability

The tools we intend to use:

  • Valgrind
  • Gdb
  • Peda
  • DUMA
  • IDA
  • RR debugger

Triaging Stack Based Buffer Overflow
Using the LexMark Perceptive Document Filters XLS Convert Code Execution Vulnerability as a crash example for our first analysis. Let's check first how it manifests under gdb:

icewall@ubuntu:~/bugs/lexmark$ gdb --nx --quiet --args ./convert config
Reading symbols from ./convert...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/icewall/bugs/lexmark/convert config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".


Program received signal SIGSEGV, Segmentation fault.
0xffffffffff601000 in ?? ()
(gdb) bt 10
#0  0xffffffffff601000 in ?? ()
#1  0x000001c420000000 in ?? ()
#2  0x0000000041c50000 in ?? ()
#3  0x01c700000000c1c6 in ?? ()
#4  0x000001c800000000 in ?? ()
#5  0x0000000001c90000 in ?? ()
#6  0x01cb0000000001ca in ?? ()
#7  0x000001cc00002535 in ?? ()
#8  0x0000000001cd0008 in ?? ()
#9  0xc1cf00000000aece in ?? ()
(More stack frames follow...)
(gdb) x /10i $rip
=> 0xffffffffff601000:    Cannot access memory at address 0xffffffffff601000
   
   
  


We see that the stack has been overwritten which has also destroyed the call stack, so we can’t check what was the last function which caused the crash. Before we take further steps, let us think about the information which is visible by default in gdb. Let's be honest, we see that we don’t see too much. Especially for someone who has spent years debugging app code/malware under OllyDbg, the information presented by default by gdb can be a bit poor. But there are ways to improve it.

One of them is PEDA :

Besides the fact that it presents much better the information regarding registers, stack and code, it provides additional features useful during triage or exploit development.

Have a look at the peda help, it will give you some good ideas about the features peda offers. Let's run our crash again, but this time with peda loaded into gdb:

gdb-peda$ r
Starting program: /home/icewall/bugs/lexmark/convert config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x6732c0 --> 0x7ffff40b2eb0 --> 0x7ffff375dfb0 (<common::IRenderable::SetZIndex(int)>:    mov    DWORD PTR [rdi+0x8],esi)
RBX: 0x199000000000198 
RCX: 0x56 ('V')
RDX: 0x2b ('+')
RSI: 0x7ffffffed110 ("ABCD", 'A' <repeats 39 times>)
RDI: 0x675ea8 ("ABCD", 'A' <repeats 39 times>)
RBP: 0x19a00000000 
RSP: 0x7ffffffed220 --> 0x1c420000000 
RIP: 0xffffffffff601000 
R8 : 0x675ea8 ("ABCD", 'A' <repeats 39 times>)
R9 : 0x7ffff69417b8 --> 0x679770 --> 0x0 
R10: 0x7ffffffecfb0 --> 0x44 ('D')
R11: 0x7ffff670a500 --> 0xfffc7110fffc6f90 
R12: 0x19b0000 
R13: 0x1bf40000003019c 
R14: 0x1c0001e001c 
R15: 0x4201000001c10000
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xffffffffff601000
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffed220 --> 0x1c420000000 
0008| 0x7ffffffed228 --> 0x41c50000 
0016| 0x7ffffffed230 --> 0x1c700000000c1c6 
0024| 0x7ffffffed238 --> 0x1c800000000 
0032| 0x7ffffffed240 --> 0x1c90000 
0040| 0x7ffffffed248 --> 0x1cb0000000001ca 
0048| 0x7ffffffed250 --> 0x1cc00002535 
0056| 0x7ffffffed258 --> 0x1cd0008 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xffffffffff601000 in ?? ()
 
   
  


This is already much better. But let us return to the main problem. How do we find where and how the overflow took place ? How do we stop our application before it happens ?

There are a couple of approaches we can take:

Turning off ASLR (if our target supports it) for example or we can try to setup a write-hardware breakpoint:

e.g. watch *0x7ffffffed218 on a stack address where the malformed return address is located, plus some conditional checks. Still, this can be problematic and stop multiple times before we reach the desired destination address.

What about tracing ? Peda provides a couple of tracing method e.g.

gdb-peda$ trace
trace tracecall traceinst

But it’s slow and with huge and complex targets it will take forever before it triggers our vulnerability. Of course, we could choose a more efficient tracer, but we are looking for a smarter solution.

A good tool which can be helpful, especially during the pre-triage phase is valgrind. This can help us to find anchors from which we can continue our analysis.

Running our target under valgrind instrumentation we can observe the following results:
NOTE: the special switch “exp-sgcheck” is turned on for detecting stack corruptions:

More infos can be found here : http://valgrind.org/docs/manual/sg-manual.html

icewall@ubuntu:~/bugs/lexmark$ valgrind --tool=exp-sgcheck ./convert config/
==13227== exp-sgcheck, a stack and global array overrun detector
==13227== NOTE: This is an Experimental-Class Valgrind Tool
==13227== Copyright (C) 2003-2013, and GNU GPL'd, by OpenWorks Ltd et al.
==13227== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==13227== Command: ./convert config/
==13227== 
==13227== 
==13227== Process terminating with default action of signal 11 (SIGSEGV)
==13227==  Bad permissions for mapped region at address 0xFFFFFFFFFF601000
==13227==    at 0xFFFFFFFFFF601000: ???
==13227==    by 0x1C41FFFFFFF: ???
==13227==    by 0x41C4FFFF: ???
==13227==    by 0x1C700000000C1C5: ???
==13227==    by 0x1C7FFFFFFFF: ???
==13227==    by 0x1C8FFFF: ???
==13227==    by 0x1CB0000000001C9: ???
==13227==    by 0x1CC00002534: ???
==13227==    by 0x1CD0007: ???
==13227==    by 0xC1CF00000000AECD: ???
==13227==    by 0x201D6FFFFFFFF: ???
==13227==    by 0xE000E01FEFFFF: ???
==13227== 
==13227== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 16 from 15)
Segmentation fault
   
  


The “exp-sgcheck” tool is in beta stage and unfortunately, in our case, using valgrind won’t help us much and won't provide too much information. So, what now? The “time travel” debugger `rr` will help us out here.

Making a long story short, `rr` gives the possibility to do reverse code execution, which means we can go back and forth in the executable code while debugging it. This is great, because that means we will be able to return to the place in code where it executed the RET instruction with the malformed return address. Now we can see in which function it happened and we are able to proceed.

First we need to record the execution:

icewall@ubuntu:~/bugs/lexmark$ rr record ./convert config/
rr: Saving the execution of `./convert' to trace directory `/home/icewall/.local/share/rr/convert-15'.

Now with the recorded execution we can start analyzing the root cause of that crash:

icewall@ubuntu:~/bugs/lexmark$ rr replay
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0xffffffffffffffff 
RDX: 0x0 
RSI: 0x0 
RDI: 0x0 
RBP: 0x0 
RSP: 0x7ffe8e800690 --> 0x2 
RIP: 0x7f033d8882d0 --> 0x3768e8e78948 
R8 : 0x0 
R9 : 0x0 
R10: 0x0 
R11: 0x246 
R12: 0x0 
R13: 0x0 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7f033d8882c0:    ret    
   0x7f033d8882c1:    nop    WORD PTR cs:[rax+rax*1+0x0]
   0x7f033d8882cb:    nop    DWORD PTR [rax+rax*1+0x0]
=> 0x7f033d8882d0 <_start>:    mov    rdi,rsp
   0x7f033d8882d3:    call   0x7f033d88ba40 <_dl_start>
   0x7f033d8882d8 <_dl_start_user>:    mov    r12,rax
   0x7f033d8882db:    mov    eax,DWORD PTR [rip+0x221b17]        # 0x7f033daa9df8 <_dl_skip_args>
   0x7f033d8882e1:    pop    rdx
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e800690 --> 0x2 
0008| 0x7ffe8e800698 --> 0x7ffe8e801223 ("./convert")
0016| 0x7ffe8e8006a0 --> 0x7ffe8e80122d --> 0x2f6769666e6f63 ('config/')
0024| 0x7ffe8e8006a8 --> 0x0 
0032| 0x7ffe8e8006b0 --> 0x7ffe8e801235 ("XDG_VTNR=7")
0040| 0x7ffe8e8006b8 --> 0x7ffe8e801240 ("XDG_SESSION_ID=c2")
0048| 0x7ffe8e8006c0 --> 0x7ffe8e801252 ("CLUTTER_IM_MODULE=xim")
0056| 0x7ffe8e8006c8 --> 0x7ffe8e801268 ("SELINUX_INIT=YES")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
(rr) c
Continuing.


Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>:    0x90909090c3087789)
RBX: 0x199000000000198 
RCX: 0x56 ('V')
RDX: 0x2b ('+')
RSI: 0x7ffe8e7ef830 ("ABCD", 'A' <repeats 39 times>)
RDI: 0x1476eb8 ("ABCD", 'A' <repeats 39 times>)
RBP: 0x19a00000000 
RSP: 0x7ffe8e7ef940 --> 0x1c420000000 
RIP: 0xffffffffff601000 
R8 : 0x1476eb8 ("ABCD", 'A' <repeats 39 times>)
R9 : 0x7f033c1c17b8 --> 0x147a780 --> 0x0 
R10: 0x7ffe8e7ef6d0 --> 0x44 ('D')
R11: 0x7f033bf8a500 --> 0xfffc7110fffc6f90 
R12: 0x19b0000 
R13: 0x1bf40000003019c 
R14: 0x1c0001e001c 
R15: 0x4201000001c10000
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xffffffffff601000
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e7ef940 --> 0x1c420000000 
0008| 0x7ffe8e7ef948 --> 0x41c50000 
0016| 0x7ffe8e7ef950 --> 0x1c700000000c1c6 
0024| 0x7ffe8e7ef958 --> 0x1c800000000 
0032| 0x7ffe8e7ef960 --> 0x1c90000 
0040| 0x7ffe8e7ef968 --> 0x1cb0000000001ca 
0048| 0x7ffe8e7ef970 --> 0x1cc00002535 
0056| 0x7ffe8e7ef978 --> 0x1cd0008 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
(rr)   
   
  


Everything looks similar to the "normal" gdb, except for the fact that we now have a set of new commands beginning with `reverse-`:

(rr) reverse-
reverse-continue reverse-finish reverse-next reverse-nexti reverse-search reverse-step reverse-stepi

We would like to go back one instruction in past, just before RET was execute, we do this by using the reverse-stepi (rsi) command:

(rr) rsi
Warning: not running or target is remote
0x00007f03390c55e6 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
(rr) context
$7 = 0x3f69
[----------------------------------registers-----------------------------------]
RAX: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>:    0x90909090c3087789)
RBX: 0x199000000000198 
RCX: 0x56 ('V')
RDX: 0x2b ('+')
RSI: 0x7ffe8e7ef830 ("ABCD", 'A' )
RDI: 0x1476eb8 ("ABCD", 'A' )
RBP: 0x19a00000000 
RSP: 0x7ffe8e7ef938 --> 0xffffffffff601000 
RIP: 0x7f03390c55e6 --> 0x15850ff0163d66c3 
R8 : 0x1476eb8 ("ABCD", 'A' )
R9 : 0x7f033c1c17b8 --> 0x147a780 --> 0x0 
R10: 0x7ffe8e7ef6d0 --> 0x44 ('D')
R11: 0x7f033bf8a500 --> 0xfffc7110fffc6f90 
R12: 0x19b0000 
R13: 0x1bf40000003019c 
R14: 0x1c0001e001c 
R15: 0x4201000001c10000
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7f03390c55e0:    pop    r13
   0x7f03390c55e2:    pop    r14
   0x7f03390c55e4:    pop    r15
=> 0x7f03390c55e6:    ret    
   0x7f03390c55e7:    cmp    ax,0xf016
   0x7f03390c55eb:    jne    0x7f03390c5406
   0x7f03390c55f1:    mov    rax,QWORD PTR [rbp+0x0]
   0x7f03390c55f5:    lea    rbx,[rsp+0x50]
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e7ef938 --> 0xffffffffff601000 
0008| 0x7ffe8e7ef940 --> 0x1c420000000 
0016| 0x7ffe8e7ef948 --> 0x41c50000 
0024| 0x7ffe8e7ef950 --> 0x1c700000000c1c6 
0032| 0x7ffe8e7ef958 --> 0x1c800000000 
0040| 0x7ffe8e7ef960 --> 0x1c90000 
0048| 0x7ffe8e7ef968 --> 0x1cb0000000001ca 
0056| 0x7ffe8e7ef970 --> 0x1cc00002535 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
  
   
  


Perfect! Now we definitely have much more information than before. We see the name of the function where the overflow happens:

reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from
Ok, moving forward, let's try to answer the important question:
When is the return address stored at `0x7ffe8e7ef938` being overwritten ?

We will use rr again. Let's set a hardware-breakpoint-for-write on that stack address and go back. You can achieve this by using the `watch` command and reverse-continue (rc):

(rr) watch *0x7ffe8e7ef938
Hardware watchpoint 1: *0x7ffe8e7ef938

(rr) rc
Continuing.

Warning: not running or target is remote
Hardware watchpoint 1: *0x7ffe8e7ef938

Old value = <unreadable>
New value = 0x390bd6a8

__memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:208
208 ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S: No such file or directory.

Looks promising! Checking the call stack we see:

(rr) bt 5
#0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:208
#1 0x00007f033a20bf67 in ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#2 0x00007f033a1e131d in ISYS_NS::CDataReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#3 0x00007f03390c5606 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#4 0x00007f03390bd6a8 in reader::escher::MsoDispatcher::Dispatch(ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so

(More stack frames follow...)

that overflow took place inside memcpy and all started from:
ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int)

We can predict that the Read and later on the memcpy’s `size` parameter is bigger than the size of the local buffer.

To check the values of these parameters we will move to the moment before :

ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int)

is called. “reverse-finish” is the command we will use this time:

(rr) reverse-finish 
Run back to call of #0  __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:208
Warning: not running or target is remote
0x00007f033a20bf62 in ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so


(rr) reverse-finish 
Run back to call of #0  0x00007f033a20bf62 in ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
Warning: not running or target is remote
0x00007f033a1e131a in ISYS_NS::CDataReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so


(rr) reverse-finish 
Run back to call of #0  0x00007f033a1e131a in ISYS_NS::CDataReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
Warning: not running or target is remote
0x00007f03390c5603 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so


(rr) context
$15 = 0x3f69
[----------------------------------registers-----------------------------------]
RAX: 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>:    0x5c8948f8246c8948)
RBX: 0x7ffe8e7ef830 --> 0x0 
RCX: 0x0 
RDX: 0x400 
RSI: 0x7ffe8e7ef830 --> 0x0 
RDI: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>:    0x5c8948f8246c8948)
RBP: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>:    0x5c8948f8246c8948)
RSP: 0x7ffe8e7ef7e0 --> 0x7f0338df41a8 --> 0xb00120002a9ee 
RIP: 0x7f03390c5603 --> 0x28bda89481050ff 
R8 : 0x3 
R9 : 0x5 
R10: 0x7ffe8e7ef6d0 --> 0x0 
R11: 0x7f033bf8a550 --> 0xfffcb240fffcabbe 
R12: 0x85a 
R13: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>:    0x90909090c3087789)
R14: 0x7ffe8e7ef950 --> 0x400f0160803 
R15: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>:    0x90909090c3087789)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7f03390c55fa:    mov    edx,DWORD PTR [rsi+0x4]
   0x7f03390c55fd:    mov    rdi,rbp
   0x7f03390c5600:    mov    rsi,rbx
=> 0x7f03390c5603:    call   QWORD PTR [rax+0x10]
   0x7f03390c5606:    mov    rdx,rbx
   0x7f03390c5609:    mov    eax,DWORD PTR [rdx]
   0x7f03390c560b:    add    rdx,0x4
   0x7f03390c560f:    lea    ecx,[rax-0x1010101]
Guessed arguments:
arg[0]: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>:    0x5c8948f8246c8948)
arg[1]: 0x7ffe8e7ef830 --> 0x0 
arg[2]: 0x400 
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e7ef7e0 --> 0x7f0338df41a8 --> 0xb00120002a9ee 
0008| 0x7ffe8e7ef7e8 --> 0x1443610 --> 0x7f0338d4f000 --> 0x10102464c457f 
0016| 0x7ffe8e7ef7f0 --> 0x7f0338df60f8 --> 0xb00220008b030 
0024| 0x7ffe8e7ef7f8 --> 0x1443610 --> 0x7f0338d4f000 --> 0x10102464c457f 
0032| 0x7ffe8e7ef800 --> 0xffffffff 
0040| 0x7ffe8e7ef808 --> 0x1443610 --> 0x7f0338d4f000 --> 0x10102464c457f 
0048| 0x7ffe8e7ef810 --> 0x7f0338ddcb98 --> 0xb00220008b0b2 
0056| 0x7ffe8e7ef818 --> 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>:    0x90909090c3087789)
[------------------------------------------------------------------------------]
  
   
  


We found the call instruction where all the bad things happened. We can also check that the stack is not corrupted:

(rr) bt
#0  0x00007f03390c5603 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#1  0x00007f03390bd6a8 in reader::escher::MsoDispatcher::Dispatch(ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#2  0x00007f0339029a5b in reader::excel97_2003::Workbook::Handle(reader::excel97_2003::BIFFRECORDTYPE const&, ISYS_NS::CDataReader&) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#3  0x00007f03390a9c4a in reader::excel97_2003::IBiffEngine::Parse(ISYS_NS::CDataReader&) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#4  0x00007f0339028689 in reader::excel97_2003::Workbook::Workbook(ISYS_NS::CStream*, IStorage*, wchar_t const*, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#5  0x00007f033941e31b in ISYS_NS::LibraryHD::CDocument::openExcel(ISYS_NS::CStream*, IStorage*, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#6  0x00007f033941f138 in ISYS_NS::LibraryHD::CDocument::open(IGR_Stream*, int, wchar_t const*) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#7  0x00007f033941a74a in ISYS_NS::LibraryHD::IGR_HDAPI_Open(IGR_Stream*, int, wchar_t const*, void**, wchar_t*) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#8  0x00007f0339c80f07 in ISYS_NS::exports::IGR_Open_File_FromStream(wchar_t const*, wchar_t const*, ISYS_NS::CStream*, bool, ISYS_NS::exports::Ext_Open_Options*, int, wchar_t const*, int*, int*, void**, int*, int, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#9  0x00007f0339c85dff in ISYS_NS::exports::IGR_Open_Stream_Ex(IGR_Stream*, int, unsigned short const*, int*, int*, void**, Error_Control_Block*) ()
   from /home/icewall/bugs/lexmark/libISYSreaders.so
#10 0x00007f033d44d99b in IGR_Open_Stream_Ex () from /home/icewall/bugs/lexmark/libISYS11df.so
#11 0x00000000004091e0 in processStream(IGR_Stream*, long long&, ToXHTML&, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#12 0x000000000040ab7b in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#13 0x000000000040b7a0 in main ()
#14 0x00007f033be24f45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7ffe8e800698, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffe8e800688)
    at libc-start.c:287
#15 0x00000000004089e9 in _start ()
 
   
  


Also, peda suggests that the function takes 3 parameters:

Guessed arguments:
arg[0]: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>: 0x5c8948f8246c8948)
arg[1]: 0x7ffe8e7ef830 --> 0x0
arg[2]: 0x400
arg[0] : is a this pointer on `CDataReader` object.
arg[1]: some buffer located on stack
arg[2]: integer, which is probably size of data which should be copied

From here it is quite straightforward. We need to find the source of the `0x400 size` value and in the context of exploitation, determine what data (from what offset ) from files is used during the overflow.

We will show that using another example below.

Triaging heap corruption As an example to work on we will use this vulnerability:

Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability

The crash root cause is well explained in the advisory above and here we will just explain
some of the “magic” lines like:

>>> print allocations['0x010dcf50']["stack"]

or

(gdb) heap /b $rdi
[In-use]
[Address] 0x63ae90
[Size] 40
[Offset] +0

We will also add some more information about other useful tools and techniques. If you are not familiar with the vulnerability, it is highly recommended that you read the advisory first, before going on.

From the advisory we know that `valgrind` can be very useful in cases where it provides information like:

  • where the memory corruption appears + call stack infos
  • how many bytes were used to allocate the buffer + call stack infos

Thanks to this information we have a good starting points for further research, but the question is, can we obtain similar information and more without valgrind? The answer is: Yes, we can.

To find the point when the heap corruption happens we need to instrument the heap. Windows users without additional tools have to use the `GFlags` tool which turns on proper heap instrumentation:

https://msdn.microsoft.com/en-us/library/windows/hardware/ff549561(v=vs.85).aspx

On Linux one of the solutions providing similar functionality is `libduma`:
http://duma.sourceforge.net/


A script to load DUMA under gdb looks like this:

icewall@ubuntu:~/bugs/lexmark$ cat .duma
define duma
set exec-wrapper env LD_PRELOAD=/usr/lib/libduma.so.0.0.0
echo Enabled DUMA\n
end

With duma installed and the script executed, let's re-run our buggy application and see where it stops.

icewall@ubuntu:~/bugs/lexmark$ ./run ~/Advisories/isys/CBFF/Lexmark_Perceptive_Document_Filters_CBFF_Integer_overflow_advisory_POC.bin 
Reading symbols from ./convert...(no debugging symbols found)...done.
gdb-peda$ source .duma
gdb-peda$ duma 
Enabled DUMA
gdb-peda$ r
Starting program: /home/icewall/bugs/lexmark/convert /home/icewall/bugs/lexmark/config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
DUMA 2.5.15 (shared library, NO_LEAKDETECTION)
Copyright (C) 2006 Michael Eddington <meddington@gmail.com>
Copyright (C) 2002-2008 Hayati Ayguen <h_ayguen@web.de>, Procitec GmbH
Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>


Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7ffff3efb800 --> 0x0 
RCX: 0xd0 
RDX: 0x1 
RSI: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0 
RDI: 0x7ffff3efb800 --> 0x0 
RBP: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0 
RSP: 0x7fffffff2650 --> 0x215 
RIP: 0x7ffff7bcb4d8 (mov    BYTE PTR [rbx+rax*1],cl)
R8 : 0x0 
R9 : 0x1 
R10: 0x0 
R11: 0x246 
R12: 0x215 
R13: 0x20000 
R14: 0x215 
R15: 0x7ffff3efb800 --> 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7bcb4cb:    nop    DWORD PTR [rax+rax*1+0x0]
   0x7ffff7bcb4d0:    movzx  ecx,BYTE PTR [rbp+rax*1+0x0]
   0x7ffff7bcb4d5:    add    edx,0x1
=> 0x7ffff7bcb4d8:    mov    BYTE PTR [rbx+rax*1],cl
   0x7ffff7bcb4db:    mov    eax,edx
   0x7ffff7bcb4dd:    cmp    r12,rax
   0x7ffff7bcb4e0:    ja     0x7ffff7bcb4d0
   0x7ffff7bcb4e2:    mov    rax,rbx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffff2650 --> 0x215 
0008| 0x7fffffff2658 --> 0x7ffff3601fb8 --> 0x7ffff6346430 --> 0x7ffff4778da0 (<ISYS_NS::CBufferedReader::~CBufferedReader()>:    mov    QWORD PTR [rsp-0x8],rbp)
0016| 0x7fffffff2660 --> 0x215 
0024| 0x7fffffff2668 --> 0x7ffff47793e5 (jmp    0x7ffff4779369)
0032| 0x7fffffff2670 --> 0xffff2a08 
0040| 0x7fffffff2678 --> 0x1 
0048| 0x7fffffff2680 --> 0x7ffff3424d38 --> 0x7ffff6345530 --> 0x7ffff4747060 (<ISYS_NS::docfile::CIStorageBase::~CIStorageBase()>:    push   rbp)
0056| 0x7fffffff2688 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ffff7bcb4d8 in _duma_memcpy () from /usr/lib/libduma.so.0.0.0
gdb-peda$ bt
#0  0x00007ffff7bcb4d8 in _duma_memcpy () from /usr/lib/libduma.so.0.0.0
#1  0x00007ffff47793e5 in ISYS_NS::CBufferedReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#2  0x00007ffff477f8a8 in ISYS_NS::CIGRStreamStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#3  0x00007ffff4741f0b in ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(unsigned int, void*, bool, unsigned int*) () from /home/icewall/bugs/lexmark/libISYSshared.so
#4  0x00007ffff4742f0e in ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() () from /home/icewall/bugs/lexmark/libISYSshared.so
#5  0x00007ffff4746ca5 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
#6  0x00007ffff4748d36 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#7  0x00007ffff40b6629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#8  0x00007ffff40bce86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#9  0x00007ffff41e722c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#10 0x00007ffff79bc138 in IGR_Get_File_Type () from /home/icewall/bugs/lexmark/libISYS11df.so
#11 0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#12 0x000000000040b7a0 in main ()
#13 0x00007ffff6392f45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7fffffffded8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdec8)
    at libc-start.c:287
#14 0x00000000004089e9 in _start ()
 
   
  


We see that the application stopped in the same place where `valgrind` reported the first out-of-bound-write. So we know the place where the corruption happens, but we would also like to know where the corrupted buffer was allocated and what size it has.

To achieve that information we wrote a simple script which you can find here:

https://gist.github.com/icewall/aadd126fa8081263593be17a20a40523

It collects information about:

  • the size of a particular buffer
  • the call stack  Let's run the application once again with `duma` and this script turned on:
gdb-peda$ source .duma
gdb-peda$ duma
Enabled DUMA
gdb-peda$ b main
Breakpoint 1 at 0x40b0b4
gdb-peda$ r
Starting program: /home/icewall/bugs/lexmark/convert /home/icewall/bugs/lexmark/config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
DUMA 2.5.15 (shared library, NO_LEAKDETECTION)
Copyright (C) 2006 Michael Eddington <meddington@gmail.com>
Copyright (C) 2002-2008 Hayati Ayguen <h_ayguen@web.de>, Procitec GmbH
Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>


[----------------------------------registers-----------------------------------]
RAX: 0x40b0b0 (<main>:    push   rbp)
RBX: 0x0 
RCX: 0x2 
RDX: 0x7fffffffdef0 --> 0x7fffffffe2b0 ("XDG_VTNR=7")
RSI: 0x7fffffffded8 --> 0x7fffffffe24f ("/home/icewall/bugs/lexmark/convert")
RDI: 0x2 
RBP: 0x7fffffffddf0 --> 0x0 
RSP: 0x7fffffffddf0 --> 0x0 
RIP: 0x40b0b4 (push   r15)
R8 : 0x7ffff3587bf0 --> 0x7ffff354cbf0 --> 0x7ffff38c9bf0 --> 0x7ffff38bbbf0 --> 0x7ffff3842bf0 --> 0x7ffff3810bf0 --> 0x7ffff39ebbf0 --> 0x7ffff39c1bf0 --> 0x7ffff399fbf0 --> 0x7ffff3a8abf0 --> 0x7ffff3e2ebf0 --> 0x7ffff6730e80 --> 0x0 
R9 : 0x1 
R10: 0x0 
R11: 0x246 
R12: 0x4089c0 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffded0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40b0ab:    jmp    0x40a40b
   0x40b0b0 <main>:    push   rbp
   0x40b0b1 <main+1>:    mov    rbp,rsp
=> 0x40b0b4:    push   r15
   0x40b0b6:    mov    r15d,edi
   0x40b0b9:    push   r14
   0x40b0bb:    push   r13
   0x40b0bd:    push   r12
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddf0 --> 0x0 
0008| 0x7fffffffddf8 --> 0x7ffff6392f45 (mov    edi,eax)
0016| 0x7fffffffde00 --> 0x0 
0024| 0x7fffffffde08 --> 0x7fffffffded8 --> 0x7fffffffe24f ("/home/icewall/bugs/lexmark/convert")
0032| 0x7fffffffde10 --> 0x200000000 
0040| 0x7fffffffde18 --> 0x40b0b0 (<main>:    push   rbp)
0048| 0x7fffffffde20 --> 0x0 
0056| 0x7fffffffde28 --> 0x94f83eede6f24ac4 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value


Breakpoint 1, 0x000000000040b0b4 in main ()
  
   
  


It’s important to turn on or to load the “allocation tracing script” after we land in the main function, because otherwise we are tracing irrelevant information.

gdb-peda$ tracealloc 
Error: missing argument
Trace malloc chunks allocation
Usage:
    tracealloc [start|stop|status]
    tracealloc info addr
gdb-peda$ tracealloc start
Breakpoint 2 at 0x7ffff63f3660: malloc. (3 locations)
gdb-peda$ c
Continuing.


Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7ffff3efb800 --> 0x0 
RCX: 0xd0 
RDX: 0x1 
RSI: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0 
RDI: 0x7ffff3efb800 --> 0x0 
RBP: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0 
RSP: 0x7fffffff2650 --> 0x215 
RIP: 0x7ffff7bcb4d8 (mov    BYTE PTR [rbx+rax*1],cl)
R8 : 0x0 
R9 : 0x1 
R10: 0x0 
R11: 0x246 
R12: 0x215 
R13: 0x20000 
R14: 0x215 
R15: 0x7ffff3efb800 --> 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7bcb4cb:    nop    DWORD PTR [rax+rax*1+0x0]
   0x7ffff7bcb4d0:    movzx  ecx,BYTE PTR [rbp+rax*1+0x0]
   0x7ffff7bcb4d5:    add    edx,0x1
=> 0x7ffff7bcb4d8:    mov    BYTE PTR [rbx+rax*1],cl
   0x7ffff7bcb4db:    mov    eax,edx
   0x7ffff7bcb4dd:    cmp    r12,rax
   0x7ffff7bcb4e0:    ja     0x7ffff7bcb4d0
   0x7ffff7bcb4e2:    mov    rax,rbx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffff2650 --> 0x215 
0008| 0x7fffffff2658 --> 0x7ffff3601fb8 --> 0x7ffff6346430 --> 0x7ffff4778da0 (<ISYS_NS::CBufferedReader::~CBufferedReader()>:    mov    QWORD PTR [rsp-0x8],rbp)
0016| 0x7fffffff2660 --> 0x215 
0024| 0x7fffffff2668 --> 0x7ffff47793e5 (jmp    0x7ffff4779369)
0032| 0x7fffffff2670 --> 0xffff2a08 
0040| 0x7fffffff2678 --> 0x1 
0048| 0x7fffffff2680 --> 0x7ffff3424d38 --> 0x7ffff6345530 --> 0x7ffff4747060 (<ISYS_NS::docfile::CIStorageBase::~CIStorageBase()>:    push   rbp)
0056| 0x7fffffff2688 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ffff7bcb4d8 in _duma_memcpy () from /usr/lib/libduma.so.0.0.0
  
   
  


Because we use this script quite often we decided to integrate it with `PEDA` so instead of loading it via : "source allocation_trace_gist.py" there is the line: "tracealloc start"

To get information about a particular buffer, we use the "tracealloc info …" command, instead of calling `allocations` dictionary in its raw form.

Ok, let's check the obtained information:

gdb-peda$ tracealloc info $rbx
Size of allocation : 0 ( 0x7ffff3efb800 )
#0  0x00007ffff7bcd350 in malloc () from /usr/lib/libduma.so.0.0.0
#1  0x00007ffff4742eaa in ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() () from /home/icewall/bugs/lexmark/libISYSshared.so
#2  0x00007ffff4746ca5 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
#3  0x00007ffff4748d36 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#4  0x00007ffff40b6629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#5  0x00007ffff40bce86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#6  0x00007ffff41e722c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#7  0x00007ffff79bc138 in IGR_Get_File_Type () from /home/icewall/bugs/lexmark/libISYS11df.so
#8  0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#9  0x000000000040b7a0 in main ()
#10 0x00007ffff6392f45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7fffffffded8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdec8) at libc-start.c:287
#11 0x00000000004089e9 in _start ()
  
   
  


Ok, so we obtain the same information like the ones provided by `valgrind`. We see the size of the memory allocation was zero and the call stack which points to the allocation had been placed inside the ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors function.

Beside the script we mentioned above, we can also obtain some information about the heap and its allocated buffers using the following tools:

https://github.com/rogerhu/gdb-heap
https://github.com/cloudburst/libheap

With a special distinction for CoreAnalyzer
http://core-analyzer.sourceforge.net/

This project is quite old but still very useful in providing a lot of information related to the heap.
Almost all `heap` commands you can find in Marcin Noga's advisories are related to this tool.

After we know the place where the buffer with size 0 is allocated, we can investigate the value(s) used to calculate its size. In the advisory we used the `allocation tracing script` to find the places where these values are initialized, but this time we will use `rr debugger` for fun and profit.

(rr) b ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors

Part of decompiled code:

Line 1     _DWORD *__fastcall ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors(struct_this *this)
Line 2     {
Line 3       struct_this *v1; // rbp@1
Line 4       void *_buff; // rax@3
Line 5       unsigned int __sectMiniFatStart; // ebx@3
Line 6       unsigned int index; // er12@5
Line 7       unsigned int v5; // eax@9
Line 8       bool v6; // cf@9
Line 9       bool v7; // zf@9
Line 10      _DWORD *result; // rax@11
Line 11      _DWORD *v9; // rbx@11
Line 12      unsigned int v10; // er12@15
Line 13      int v11; // edx@16
Line 14      unsigned int v12; // edi@18
Line 15      _DWORD *v13; // r13@18
Line 16      unsigned int v14; // ebx@19
Line 17      unsigned int v15; // er14@21
Line 18      unsigned int v16; // er12@22
Line 19      void *v17; // rax@27
Line 20      _QWORD *v18; // rax@31
Line 21
Line 22      if ( this->_csectMiniFat > 0x10000u )
Line 23        this->_csectMiniFat = 0x10000;
Line 24      _buff = malloc((unsigned int)(this->dword214 * this->_csectMiniFat));
Line 25      __sectMiniFatStart = this->_sectMiniFatStart;
Line 26      this->buff = _buff;
Line 27      if ( __sectMiniFatStart <= 0xFFFFFFF9 && this->_csectMiniFat )
Line 28      {
Line 29        index = 0;
Line 30        do
Line 31        {
Line 32          if ( !(unsigned __int8)ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(
Line 33                                   (ISYS_NS::docfile::CIStorageBase *)this,
Line 34                                   __sectMiniFatStart,
Line 35                                   (char *)this->buff + this->dword214 * index,
Line 36                                   1,
Line 37                                   0LL) )

   
  


We move to the line right after the memory allocation (when line 24 is executed):

(rr) 
[----------------------------------registers-----------------------------------]
RAX: 0x6f4ef0 --> 0x7fbec23677d8 --> 0x7fbec23677c8 --> 0x7fbec23677b8 --> 0x6f65d0 --> 0x0 
RBX: 0x7fff95efb420 --> 0x7fff95efbeb0 --> 0x7fbec1f7e7f0 --> 0x7fbec03b79a0 (:CIGRStreamStream::~CIGRStreamStream()>:    0x5c8948f8246c8948)
RCX: 0x7fbec2367760 --> 0x100000000 
RDX: 0x6f4ef0 --> 0x7fbec23677d8 --> 0x7fbec23677c8 --> 0x7fbec23677b8 --> 0x6f65d0 --> 0x0 
RSI: 0x40080d78 
RDI: 0x0 
RBP: 0x6f6310 --> 0x7fbec1f7d530 --> 0x7fbec037f060 (:docfile::CIStorageBase::~CIStorageBase()>:    0xec8348fb89485355)
RSP: 0x7fff95efb130 --> 0x7fff95efb420 --> 0x7fff95efbeb0 --> 0x7fbec1f7e7f0 --> 0x7fbec03b79a0 (:CIGRStreamStream::~CIGRStreamStream()>:    0x5c8948f8246c8948)
RIP: 0x7fbec037aeaa --> 0x258858948505d8b 
R8 : 0x0 
R9 : 0x2 
R10: 0x7fff95efaf20 --> 0x0 
R11: 0x7fbec037ae00 --> 0x48c2048d49118b48 
R12: 0x0 
R13: 0x0 
R14: 0x6f6538 --> 0x0 
R15: 0x6f6588 --> 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7fbec037ae9b:    mov    edi,DWORD PTR [rbp+0x54]
   0x7fbec037ae9e:    imul   edi,DWORD PTR [rbp+0x214]
   0x7fbec037aea5:    call   0x7fbec0363d78 <malloc@plt>
=> 0x7fbec037aeaa:    mov    ebx,DWORD PTR [rbp+0x50]
   0x7fbec037aead:    mov    QWORD PTR [rbp+0x258],rax
   0x7fbec037aeb4:    cmp    ebx,0xfffffff9
   0x7fbec037aeb7:    ja     0x7fbec037b04d
   0x7fbec037aebd:    mov    ecx,DWORD PTR [rbp+0x54]
[------------------------------------stack-------------------------------------]
  
   
  


Beside using the `allocation script`, core analyzer and the other mentioned tools, glibc also provides us with a couple of interesting functions related to the heap. One of them gives us the option to check the size of the heap buffer. You can do this by using the `malloc_usable_size` function.

(rr) p malloc_usable_size($rax)
$34 = 0x28

So we can see that malloc allocates 0x28 bytes for our buffer.
Finding values for fields we are interested in is also easy, e.g.

[rbp+0x54]
[rbp+0x214]

we are using `rr` again. Setting hardware breakpoints on those addresses and "reverse continue" will show us the places where they get initialized:

(rr) p $rbp+0x214
$42 = (void *) 0x6f6524
(rr) delete breakpoints
(rr) watch *0x6f6524
Hardware watchpoint 3: *0x6f6524
(rr) rc
Continuing.
Warning: not running or target is remote
Hardware watchpoint 3: *0x6f6524




Old value = <unreadable>
New value = 0x0
0x00007fbec037e721 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
(rr) bt
#0  0x00007fbec037e721 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
#1  0x00007fbec0380d36 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#2  0x00007fbebfcee629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#3  0x00007fbebfcf4e86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#4  0x00007fbebfe1f22c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#5  0x00007fbec35f4138 in IGR_Get_File_Type () from /home/icewall/bugs/lexmark/libISYS11df.so
#6  0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#7  0x000000000040b7a0 in main ()
#8  0x00007fbec1fcaf45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7fff95f068f8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff95f068e8)
   at libc-start.c:287
#9  0x00000000004089e9 in _start ()
  
   
  


Check the code around:

0x00007fbec037e709: call 0x7fbec035aef8 <ISYS_NS::docfile::CIStorageBase::Read_Header()@plt>
0x00007fbec037e70e: test al,al
0x00007fbec037e710: je 0x7fbec037e73d
0x00007fbec037e712: movsx ecx,WORD PTR [rbp+0x32]
0x00007fbec037e716: mov eax,0x1
0x00007fbec037e71b: mov edx,eax
0x00007fbec037e71d: shl edx,cl
0x00007fbec037e71f: mov ecx,edx

=> 0x00007fbec037e721: mov DWORD PTR [rbp+0x214],edx
This shows that we land in the same place as found during our analysis in the advisory using a different method. For the rest of the fields we are interested in we would do the same steps again.

That's it folks, hope you enjoyed our walk through post. Happy reversing....