Reviewing Netopia SW DCS 5.1.2
Today we'll take a step ahead towards 0day exploitation and cover a walkthrough over a public exploit in a real world commercial software. The software in question is the
Motorola Netopia Software Distribution Center Server
5.1.2
which as the name implies is a server to distribute and deploy software across a network. The application comes in three modules: An
Agent
to be installed in each workstation, an
Admin
app to control the network users and the
Server
which storages the software to later be deployed in users boxes. The vulnerable software in question is the latter one which was used as part of the first edition of the
Exploit 4 Food
contest by the folks at
48bits.com.
Among the solution's sent for the contest, we chose the one developed by Tora. Apart from developing a working exploit for the vulnerable software, he included a step-by-step guide on how he accomplished his task. For this article I used this guide as reference myself and I will try to walk the same path Tora did. This time I won't be providing any exploit because it would be quite the same as the one from Tora and it doesn't make sense to release similar tools for the same task. Therefore in a nutshell, I will reverse engineer to find and understand the vulnerable situation and then I will explain how the exploit works.
First of all lets enumerate the tools used to accomplish the task: for debugging purposes we will use the long established
OllyDbg,
for the analysis the undisputable disassembler aka.
IDA
and for exploit writing our old friend
Python.
Since we're making a black-box testing approach here, we have no previous knowledge about the application itself apart from the fact that the challenge mentions a remote exploit as objective.
Well then, since almost every application at the end makes use of the low-level networking functions we will place a breakpoint in the
recv()
call, make use of the software normally and wait till the bell rings. In my particular case, I went straight with IDA. Inside the disassembler we try to find references to recv() and we follow them to see where it gets called. We notice that the only two references the program makes come from the same sub-routine, which begins at
0x00406CA0.
Now from the debugger, we place a breakpoint in the aforementioned address and explore from then on.
Usually this kind of software doesn't directly call
recv()
every time and rather they define a wrapper around it to better suit their purposes. Since it's a network intensive app and the wrapper will be called several times, some debugging will show us that this wrapper begins at
0x004077E0.
As Tora states, this functions simply reads 4 bytes from a socket and treats it as a unsigned int. For easier analysis within IDA, we will call this function
ReceiveDword().
But there's one more function that makes use of
recv()
and gets called to actually read data into a buffer, this one gets called everytime as
call [reg+14h].
Continuing analysis back and forth with Olly and IDA we get to the interesting function and the one that holds the vulnerability. The function begins at
0x00418AA0
and you can get the rather lengthy disassembly
here.
Note that as a result of reverse engineering sessions, functions and variables have been renamed.
Basically this function takes certain data from the socket using
ReceiveDword()
and
call [reg+14h]
to latter decrypt it using certain crypto functions (which we discover via
findcrypt
are part of
Blowfish)
and places it in a structure for further processing. The function starts by initializing some of the ussual stuff in windows applications: sets the stack canary and the SEH handlers as well as initializing the Blowfish implementation. Then it proceeds to zero-out some local buffers that will be used in the handshaking to establish a crypto key for data transaction. Next after calling
ReceiveDword()and
checking that didn't read 0 bytes he reads some data from the network using
call [reg+14h]
and to decrypt it initializes the blowfish key with a hardcoded string (You gotta love tamper-proof software :-). Once this first packet gets read, it proceeds to the second one repeating the process again BUT!, this time uses the data we sent in the first packet to setup the final crypto key (spectators go nuts at this point!). Now since the real data came in the second packet and got decrypted with the data in the first one, the app simply fills in some structures with the data from the second packet and moves them around prior to the function epilogue where Blowfish closes its implementation and the stack canary is checked to make sure we did behave properly.
Ok, and what about the vulnerability you might ask at this point? The thing is everytime
call [reg+14h]
is called underneath it sets up a 10000 byte buffer whereas in our function we have only 256 bytes. Just in case the alarm hasn't gone on in your mind, here's a little snippet of the vulnerable code:
.text:0041894E lea eax, [esp+350h+sizeCryptBuf]
.text:00418952 push eax
.text:00418953 mov [esp+354h+var_4], 0
.text:0041895E call ReceiveDword
.text:00418963 mov eax, [esp+350h+sizeCryptBuf]
.text:00418967 test eax, eax
.text:00418969 jbe short loc_418981 ; jump if we recv 0 bytes
.text:0041896B mov ecx, [esi+20]
.text:0041896E mov edx, [ecx]
.text:00418970 push 10000
.text:00418975 lea edi, [esp+354h+cryptBuf] ; fixed stack buffer
.text:0041897C push edi
.text:0041897D push eax
.text:0041897E call dword ptr [edx+14h] ; indirect call to recv()As you can see, local variable's address is passed as argument to
call [reg+14h]
where read data will be stored. At this point the overflow is pretty clear and all that's left to do now is to think how we will abuse it to execute arbitrary code.
As Tora stated in his solution, although there are several paths from which to take advantage, perhaps the easiest one is to smash the canary and the saved return address all at once because they're both in the stack after the buffers that hold our data. This way, if we overwrite the canary and the saved return address when the epilogue checks for stack smashing, the XOR will result in 0 because both values are the same thus bypassing the stack protection. To really understand how will we smash the buffer we have to notice that the
plainkey
(the one we defined in the first packet) and the
cryptobuf
(this one holds temporary crypted data received from the socket) are both near each other and that
plainkey
is higher memory address than
cryptobuf.
In this situation we will overflow
cryptobuf
to write inside
plainkey.
Therefore when decryption of cryptobuf begins, it will make it till cryptobuf[0x100] which is the same as plainkey[0x0] which is the same as Decrypt(cryptobuf[0x100]). Since no null byte will be waiting there (because we overflowed) it will continue decrypting inside
plainkey
and writing decrypted data outside the boundaries of it (because as
cryptobuf,
plainkey
is also 0x100 byte long): In the interesting data in our stack (canary, saved return address, the return address itself...). To summarize, we will write about 0x130 bytes, from which the first 0x30 will be encrypted
twice
with blowfish because off the situation we just mentioned. This way, when the first 0x30 bytes of
plainkey
get decrypted for the second time, they will be written all over the canary, the saved return address, the current address, etc...That's basically it, as final note just notice that we have to send some specific headers in the beginning of our connection to direct execution flow to our magic function. You can find the results of the challenge alongside Tora's and other peoples solutions
here.
This article doesn't report anything new to the world, it was just a nice exercise for me to practice some windows debugging/disassembling. After all it turned out to be quite funny, I might do some windows works next time as well. Greetings to 48bits guys for setting up the event, for Tora and the rest of participants and even for you reader if you made it this far :-) See you around and keep adding NOPs!