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 tocall [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!