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....