Analysis of CVE-2017-5005: QuickHeal Buffer Overflow
Recently, I hosted an internal CTF event in my company. I wanted to include a challenge which would include some Windows Exploitation. Custom-made binaries are not fun, I wanted a challenge that would be bit realistic but not too difficult. Since we are living in times of ‘Quarantine’, it gave me an idea of having some anti-virus exploitation challenge. While searching for some recent anti-virus exploits, I found QuickHeal’s CVE-2017-5005. I downloaded the PoC, ran it on my test environment of Windows XP SP3 and it didn’t work. After some tinkering, I got it working and found it easy enough to give it as a challenge. Since the CTF has finished now, I’m sharing this technical analysis of the vulnerability.
Mach-O File Format
As mentioned in the original exploit, the vulnerability lies in the LC_UNIXTHREAD.cmdsize
parameter of Mach-O files. To understand what this is, I’ll first give a brief of Mach-O file format.
An overview
A Mach-O file would contain three components:
- Header
- Load Commands
- Data
Header contains some basic information about the file and is not much of our concern here. Load Commands are important because LC_UNIXTHREAD
is one. We will cover them in detail in next section.
Data is basically collection of ‘Segments’. These segments are further a collection of ‘Sections’. Segments are analogous to .text
, .data
, etc. The code or data that an executable usually have will fall into one of the sections in these segments. Again, not going into much details here as it’s not required.
Load Commands
Load Commands are special structures that are sort of commands to the loader. They define the overall structure of the file, location of segments, entrypoint, symbol tables, etc. For all of these tasks, there are different kind of load commands, for instance, we have Segment Load Command that defines segments, Symbol Load Command for symbol table info, Thread Load Command for thread info and dozens more.
A Load Command would contain a minimum of following two integers and can contain some other content depending on its type:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
cmdsize
would define the size of entire command. The cmd
field is a constant for type of load command. You can look at these types in loader.h
file. Some that concerns us are below:
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be mapped */
Thread State
Since the overflow is in LC_UNIXTHREAD
load command, I’ll cover only its internals here. So what exactly does this load command do? If you have some programming background in C language, you’ll know there is a main()
function that defines the entrypoint of the program. In recent MACH-O files, we have a load command LC_MAIN
which defines this entrypoint. In earlier versions, we had LC_UNIXTHREAD
for this. LC_UNIXTHREAD
does this by having a ’thread state’ of entrypoint. Thread state actually is just a snapshot of all the registers, it stores the value of all the registers at a given state.
The LC_UNIXTHREAD
or LC_THREAD
structure looks like this:
struct thread_command {
uint32_t cmd; /* LC_THREAD or LC_UNIXTHREAD */
uint32_t cmdsize; /* total size of this command */
/* uint32_t flavor flavor of thread state */
/* uint32_t count count of longs in thread state */
/* struct XXX_thread_state state thread state for this flavor */
/* ... */
};
Like other load commands, this also contains cmd
and cmdsize
integers. After these two, we have three components- two other integers flavor
and count
, and thread_state
.
There are different type or ‘flavors’ of thread states; flavor
here defines the type of thread state this load command is storing. Here is a list of flavors from thread_status.h
file:
#define x86_THREAD_STATE32 1
#define x86_FLOAT_STATE32 2
#define x86_EXCEPTION_STATE32 3
#define x86_THREAD_STATE64 4
#define x86_FLOAT_STATE64 5
#define x86_EXCEPTION_STATE64 6
LC_UNIXTHREAD
would save x86_THREAD_STATE64
flavor of thread. Let’s quickly look at the associated thread_state
structure from _structs.h
to get an idea of how it looks:
_STRUCT_X86_THREAD_STATE64
{
__uint64_t __rax;
__uint64_t __rbx;
__uint64_t __rcx;
__uint64_t __rdx;
__uint64_t __rdi;
__uint64_t __rsi;
__uint64_t __rbp;
__uint64_t __rsp;
__uint64_t __r8;
__uint64_t __r9;
__uint64_t __r10;
__uint64_t __r11;
__uint64_t __r12;
__uint64_t __r13;
__uint64_t __r14;
__uint64_t __r15;
__uint64_t __rip;
__uint64_t __rflags;
__uint64_t __cs;
__uint64_t __fs;
__uint64_t __gs;
};
The thread state is just collection of 21 integers, one for each register.
Breaking Down the PoC
Now that we have enough theory of Mach-O internals, let’s break the PoC down and look at them in practice. I’ll mainly take help from the combination of macho_parser from penvirus and xxd
to showcase the internals.
The Header and Load Commands
The macho_parser exposes certain APIs that we have to call from a python script. So, let’s write one to extract the header and load commands:
from macho_parser.macho_parser import MachO
with MachO('CVE-2017-5005.mach') as m:
print 'Header:'
print m.get_header()
print ''
print 'Load Commands:'
for c in m.get_load_commands():
print c
This script would give following output:
From this output we can see that we have four load commands with cmd
of 25 or 0x19. Scroll up and check that 0x19 is cmd
for LC_SEGMENT_64
or Segment Load Command. So, we can say that we have four segments in this file. After this, we see that we have a weird value of cmd
but let’s ignore that. Our main concern is LC_UNIXTHREAD
and it should have a cmd
of 0x5.
The LC_UNIXTHREAD
We eventually find the cmd
of 0x5 after few entries. It has a cmdsize
of 1208 but it won’t be a valid value as PoC would be having an inflated value to cause an overflow. Let’s look at this load command closely using xxd
:
Breaking the output down in following pointers:
- Red Underline: The
cmd
andcmdsize
ofLC_UNIXTHREAD
- Yellow Highlight: The
cmd
with value of 0x5. Consumes 4 bytes. - Pink Highlight: The
cmdsize
with value of 0x4b8 or 1208. Consumes 4 bytes.
- Yellow Highlight: The
- Blue Underline: The thread state
- Blue Highlight: The
flavor
. It’s value is 0x4 which corresponds tox86_THREAD_STATE64
. Consumes 4 bytes. - Orange Highlight: The
count
. It’s value is 0x2a or 21. Consumes 4 bytes. - Rest is the
_STRUCT_X86_THREAD_STATE64
, the collection of 21 integers (int64 actually). Consumes 21*8 bytes.
- Blue Highlight: The
- Green and Yellow Underline: Other load commands
In an ideal scenario, cmdsize
should have the length of (4 + 4 + 4 + 4 + 21*8) bytes which equals 184 or 0xb8 bytes. But we have 0x4b8 mentioned there which is resulting in an overflow. How does this overflow look? Let’s see.
The Overflow
Let’s attach our debugger and analyse the crash. Following is the screenshot:
Also, for context, following is the hexdump of the region where shellcode is stored in PoC:
Comparing these two, we can observe that the contents of our PoC are in the stack. ESP
is currently pointing to the NOPs highlighted in green in hexdump. If we look at the stack again, we see the address 10007056
(highlighted in pink in hexdump). EIP
is pointing to a nearby address, so maybe 10007056
was intended to be the address of JMP ESP
and then it would have jumped to the NOPs and started executing the shellcode? Let’s confirm this assumption by placing a breakpoint on 10007056
and running the exploit again.
And the execution hits our breakpoint! We don’t have JMP ESP
at 10007056
, most probably because the PoC was created for a different version of the product. Let’s change the instructions at 10007056
to JMP ESP
and resume the execution.
We can observe that the execution continues and we have a calculator running under the context of vulnerable service.
Exploit Development
Now that we’ve broken down the PoC to understand the point of overflow, let’s replicate the crash and create our own exploit that will run start a bind shell.
If we notice, the location of LC_UNIXTHREAD
was 0xc70 in the PoC (refer to first hexdump). The location of bytes overwriting EIP
is 0xdb0 (refer to the second hexdump).
0xdb0 - 0xc70 = 0x140
So we can assume that QuickHeal service is creating a buffer of maximum 0x140 or 320 bytes for thread_state
of LC_UNIXTHREAD
. Let’s confirm this assumption by creating a PoC with cmdsize
as 320 bytes, then 324 bytes.
Here I’ve modified the cmdsize
to 0x140 (320) bytes.
While scanning this file, we notice the application doesn’t crash.
Now let’s scan a file with cmdsize
of 0x144 (324 bytes).
Aaaand… wait, we don’t have a crash? Our assumption of 320 bytes is wrong? Ummm… Let’s parse our PoC to see if cmdsize
is indeed 324.
Okay, the parser failed. It did show cmdsize
as 324 but the next load command is corrupt? If we look carefully, since we have cmdsize
of LC_UNIXTHREAD
as 324, the parser should be expecting a load command to start at 325th (or 0xc70 + 0x144 = 0xdb4th) byte. What do we have at 0xdb4th byte?
It is 0x74000000 or 1946157056, same as the cmd
of the corrupt load command. So we know the problem- the load commands present after the LC_UNIXTHREAD
load command. Let’s modify our PoC to remove all those load commands. Also, in an anticipation that bytes 320-324 will overwrite the EIP
, I’ll place 0xcccccccc in these 4 bytes.
Double checking with the parser:
Let’s scan this file. And we finally notice the crash, with 0xcccccccc in EIP
.
Cool! Now, all we have to do is increase the cmdsize
to accommodate for our shellcode, replace 0xcccccccc with address to JMP ESP
and place our bind shell afterwards.
The pink highlight is the value of cmdsize
. Green highlight is address to JMP ESP
. Orange highlight is bind shell to port 4444 shellcode. Let’s finally scan this exploit now.
Boom! It’s working!
Related Posts
Windows Exploitation: ASLR Bypass (MS07–017)
In this blog, I will be analysing a long forgotten Windows Animated Cursor Remote Code Execution Vulnerability (CVE-2007–0038) on Windows Vista.
Read moreWindows Exploitation: Dealing with bad characters — QuickZip exploit
When you begin your journey in exploitation, you start with simple buffer overflows, then you deal with SEH, play with egg hunters and so on.
Read moreWindows Exploitation: Egg hunting
Lately, I’ve been exploring the world of Windows exploitation. I was already familiar with the concept of Buffer Overflows, brushed those skills up during OSCP days and now I’m taking steps further.
Read more