|
Plausible Deniability
Backdoors in software are a venerable trick used for decades now. Even backdoors sometimes have backdoors (the infamous SubSeven "master password", for example). But if you're going to put a backdoor into your remote access trojan (RAT), you risk your reputation when that backdoor is exposed. After all, some of the people you are supplying with your code are likely to be quite paranoid, and insist on checking things out with a debugger before trusting your code to be what it says it is. What we wind up with is a situation similar to shareware protection schemes - you want to obfuscate your secret master password in your trojan so reversers won't find it. But, like all shareware protection schemes, it's only a matter of time before someone figures it out. So, a more subtle approach is needed - one that doesn't require any extra "suspicious" authentication code to be added to the binary. Introducing an intentional buffer overflow into the program is one way to do this. If your cracker is looking for logic flow and password verification schemes instead of buffer overflow conditions, she might pass right over your backdoor without noticing it. And, as an added benefit, if you ARE discovered, you can always claim it was an innocent programming error, and salvage what's left of your reputation by releasing a bugfix. But, while the community has long speculated that this might be a good way to backdoor code, has it ever happened? Well, that's a tough question - as the title of this article states, there is a degree of plausible deniability. We can never know the motive behind the flaw. However, sometimes, an error is just "too good" to be a mistake - so we are left with more suspicion than not. While investigating a system intrusion, I came across a Chinese RAT called WinEggDropShell v1.41. (Note that the WinEggDropShell name comes from the author's handle, "WinEggDrop", and is not related to the IRC-based Eggdrop bot you may be familiar with.) As a matter of routine, I threw it into OllyDbg to find out how it worked. Even though there was documentation available from the author, it was of little use to me, as I don't read Chinese. To start with, WinEggDropShell injects a DLL into the winlogon.exe process. The DLL is packed by EXE32Pack v1.38 on top of Aspack. Now, since Brett Moore told us that it's dangerous to load untrusted DLLs into OllyDbg, we want to avoid any pre-interaction code execution. Fortunately we can avoid this with a simple trick - we'll just tell OllyDbg this is an executable, not a DLL. To do this, we simply load our target DLL into Stud_PE (or your favorite PE editor) and open the PE header for editing (in Stud_PE, "Advanced tree view in hexeditor") ![]() Editing PE characteristics in Stud_PE Find the "Characteristics" node in the treeview, and the hexeditor will highlight the corresponding 16-bit field (little-endian) in the PE header. In this case, the value is 0x2102, and the 14th bit (0x2000) tells the loader this is a dynamic-link library. We can clear that flag by editing the second byte of our highlighted word, changing "0x21" to "0x01". Click "Save to File", exit Stud_PE, rename the file to end in .exe, and load it into OllyDbg. Now it no longer complains about this file being a DLL, and it stops at the header-defined entry point without running the DLL initialization code. This article is not about unpacking, though, so the actual unpacking of the DLL is left as an exercise to the reader :) After unpacking and examining the file in OllyDbg, I located the authentication subroutine: 1000209E PUSH EBP 1000209F MOV EBP,ESP 100020A1 SUB ESP,1AC 100020A7 MOV ECX,6B 100020AC /DEC ECX 100020AD |MOV DWORD PTR SS:[ESP+ECX*4],FFFA5A5A 100020B4 \JNZ SHORT TBack.100020AC 100020B6 PUSH ESI 100020B7 PUSH EDI 100020B8 MOV EDI,DWORD PTR SS:[EBP+8] 100020BB MOV DWORD PTR SS:[EBP+8],EDI 100020BE MOV DWORD PTR SS:[EBP-188],0 100020C8 PUSH TBack.1003EC0D ; /<%s> = "Enter Password:" 100020CD PUSH TBack.10015210 ; |<%s> = "" 100020D2 PUSH TBack.1003EC1D ; |format = "%s%s" 100020D7 LEA EDI,DWORD PTR SS:[EBP-180] ; | 100020DD PUSH EDI ; |s 100020DE CALL TBack.10014E1C ; \sprintf 100020E3 ADD ESP,10 100020E6 LEA EDI,DWORD PTR SS:[EBP-180] 100020EC PUSH EDI ; /Arg2 100020ED PUSH DWORD PTR SS:[EBP+8] ; |Arg1 100020F0 CALL TBack.100067C5 ; \TBack.100067C5 100020F5 ADD ESP,8 100020F8 PUSH 100 ; /n = 100 (256.) 100020FD PUSH 0 ; |c = 00 100020FF LEA EDI,DWORD PTR SS:[EBP-180] ; | 10002105 PUSH EDI ; |s 10002106 CALL TBack.10014DEC ; \memset 1000210B ADD ESP,0C 1000210E CALL TBack.10014978 ; [GetTickCount] 10002113 MOV DWORD PTR SS:[EBP-184],EAX 10002119 /PUSH 80 ; /n = 80 (128.) 1000211E |PUSH 0 ; |c = 00 10002120 |LEA EDI,DWORD PTR SS:[EBP-80] ; | 10002123 |PUSH EDI ; |s 10002124 |CALL TBack.10014DEC ; \memset 10002129 |ADD ESP,0C 1000212C |PUSH 0 ; /Flags = 0 1000212E |PUSH 100 ; |BufSize = 100 (256.) 10002133 |LEA EDI,DWORD PTR SS:[EBP-80] ; | 10002136 |PUSH EDI ; |Buffer 10002137 |PUSH DWORD PTR SS:[EBP+8] ; |Socket 1000213A |CALL TBack.10014718 ; \recv Notice anything odd about the recv call? BufSize is set to 256, and our buffer is pointed to by EDI. But right above that, we see a memset call with EDI as the buffer, but for only a count of 128 bytes. Why zero out only the first half of the buffer? Checking further back in the subroutine we see EDI was loaded with the local stack pointer [EBP-80] - meaning that our buffer was only defined as char[128] at the beginning of the subroutine. So, arguments about whether this was intentional or not aside, we now have the ability to overflow our buffer by 128 bytes by sending a large password string. Of course, what fun would knowing this be without a proof-of-concept exploit?
#!/usr/bin/perl
## usage: ./weds.pl | nc
Short and to the point - it could even have been a Perl one-liner :) There's no need to write remote command shellcode into this exploit, because WinEggDropShell is already a remote command shell! So all we do here is use the overflow to put some code on the stack that jumps directly to the point in the binary where the authentication is flagged as successful, bypassing the password check and giving us access to the command shell. We can't just put the "good" address on the stack as a return address, though; we have to fix up the stack before we do that. So we still need to locate a good old fashioned JMP ESP, which we fortunately find at 0x100039a3. Of course, we know that bot authors like to roll backdoor exploits into their arsenal, but before anyone tries that, note that WinEggDropShell 1.41 is fairly old, and it doesn't listen on a preset port. So they'd be wasting their time. The only thing that remains is the question - can a buffer overflow this obvious be a mistake? |