Bypassing DEP - The Savant way
Those who follow
my
twitter account might have noticed in the last few weeks an increasing activity regarding writable addresses, 2k3 servers, and DEP bypasses. Well, today I can bring the concluding explanation about all this recent fuzz. For the last 3 or 4 weeks I've been trying to pick a bug in some code and dissect it to build my own exploit from it. Even thought I came across several good choices, I got hooked with
this
one. To find out what the bug was all about, I also came across a couple of public releases that exploited the bug to help on the way. Unfortunately, or I should say fortunately, all the publicly available releases were meant to be used against XP SP2 or older versions of Windows. Since re-covering somebody elses work seemed pretty boring to me, I decided to take the bug for a spin myself and take it a step further.
In essence, the bug is a classical example of a buffer overflow bug in the stack. The user performs some HTTP requests to the server and this answers with the content the user asked for or...doesn't he? As it turns out, if we manage to craft a specially designed non-standard HTTP request, we can manage to sneak some juicy code. But as I said earlier, other publicly available exploits already do so, why bother writing another one then? Thing is, as far as I can tell, no one has yet developed a exploit for one of the most extended Server versions of Windows, this being 2003. I won't delve into the details of what DEP is and how it works. Before getting any further, remember the
conditions
to which you agree by reading this content. The bug on which I elaborate in this post is yet to be fixed.
The project
seems to be abandoned at the time of this writing and even thought a bug tracking
entry
has was opened for this very issue almost 3 months ago, there is no apparent activity in the matter. Also, I don't find ethic to provide a working exploit for the bug for two reasons:
+ + / + + + + + + + + + + '\r\n\r\n'
There a couple of things worth to mention in this method. First, after the DEP disabling code sequence return value, there are three addresses named
referenceable.
At this three offsets, placing 4 byte long writable addresses is mandatory in order to redirect flow where we intend, otherwise some violations happen in the way. Second, remember that the value in is the value that we will XOR in the code. Also, clearly this exploit relies a lot in hardcoded addresses, I understand that portability might be an issue porting the exploit to other versions of the same platform but this was just a proof of concept exercise and not a market exploit.
To finish this lengthy post, I would like to greet Ruben and Joxean from
48bits
for their altruistic support and patience.
You can find a more fun oriented, spanish version of this post
here.
- Anybody with the knowledge to understand this, can create his own exploit from this.
- The bug still isn't fixed in the last release version.
.text:00411A6A mov edx, [ebp+arg_14]
.text:00411A6D add edx, [ebp+var_8]
.text:00411A70 mov eax, [ebp+Str1]
.text:00411A73 add eax, [ebp+var_C]
.text:00411A76 mov cl, [eax]
.text:00411A78 mov [edx], cl ; PWN stackDespite the fact that the bug is a fairly classic stack overflow, as we will soon see, the problem lies in exploiting it. Anyhow, after we exit from
0x00401285,
execution of
0x0040BFA0
continues up to the end where our good old friend
ret
provides us control of execution. Now let's establish the constraints of the area of work. Our request will resemble the shape of a traditional HTTP request:
METHOD
- recv() allows us a maximum of 0x4000 bytes.
- METHOD can have 24 bytes max.
-
can contain up to 250 bytes until (and not including) the return address + 32 more bytes after it.
.text:7C83E413 mov [ebp+var_4], 2
.text:7C83E41A
.text:7C83E41A loc_7C83E41A: ; CODE XREF: sub_7C83592A+45j
.text:7C83E41A push 4
.text:7C83E41C lea eax, [ebp+var_4]
.text:7C83E41F push eax
.text:7C83E420 push 22h
.text:7C83E422 push 0FFFFFFFFh
.text:7C83E424 call ZwSetInformationProcess
.text:7C83E429 jmp finishThis code performs the same thing as the code described in the article. The problem lies in the code that executes after the jump:
.text:7C835975 or byte ptr [esi+37h], 80h
.text:7C835979 pop esi
.text:7C83597A
.text:7C83597A locret_7C83597A: ; CODE XREF: sub_7C83592A+Dj
.text:7C83597A leave
.text:7C83597B retn 4As you can see, there a
leave
instruction right before aret. This effectively sets esp and ebp registers back to what the stack frame of the caller in a single instruction. Unfortunately for us, this is how the code in
0x0040BFA0
returns (and gives us control of execution):
.text:0040C102 pop edi
.text:0040C103 pop esi
.text:0040C104 pop ebx
.text:0040C105 mov esp, ebp
.text:0040C107 pop ebp
.text:0040C108 retnIn our effort to build a sequence of ret2code calls, we need to setup a fake frame in the stack with our return values. However ebp gets pop'd right before our ret value therefore our future fake-frame will be the one pointed by ebp. This might sound like a juicy but keep in mind that stack address generally look like
0x00XXXXXX
and thus, contain null bytes. This means that we need to somehow put a stack value with null bytes in ebp but without interfering with the rest of the exploit. After testing several methods, the one that finally worked works as follows. If we cannot provide an address with NULL bytes, why not use some sort of logical operations to compute the value we desire? What I came up with is this sequence of code inside shell32.dll that after XOR-ing the value in ebp with a constant value, will allow us to have a value we want:
.text:7CA0E9BC or cl, ch
.text:7CA0E9BE xor ebp, 9090FFFFh
.text:7CA0E9C4 nop
.text:7CA0E9C5 nop
.text:7CA0E9C6 nop
.text:7CA0E9C7
.text:7CA0E9C7 loc_7CA0E9C7: ; DATA XREF: .text:off_7C90FAE4o
.text:7CA0E9C7 sub dword ptr [esp+4], 20h
.text:7CA0E9CC jmp sub_7CA0DF22Which jumps to:
.text:7CA0DF22 mov edi, edi
.text:7CA0DF24 push ebp
.text:7CA0DF25 mov ebp, esp
.text:7CA0DF27 push [ebp+arg_8]
.text:7CA0DF2A push [ebp+arg_4]
.text:7CA0DF2D push offset off_7C8DC0A0
.text:7CA0DF32 push [ebp+arg_0]
.text:7CA0DF35 call SHLWAPI_219
.text:7CA0DF3A pop ebp
.text:7CA0DF3B retn 0ChDespite the second chunk of code calling other functions a push ebp in the prolog and a pop ebp in the epilog will keep our newly created value safe. Now we can return to the code sequence that we described earlier that will effectively disable DEP and, since we now control ebp, can continue executing wherever we want without protection restrictions. Following this we simply return to a
jmp esp
address and continue traditional exploitation from then on. The only problem with this particular environment is the fact that we only have 250 bytes of space and the shellcode needs to be alphanumerically encoded. The final scheme of what we should build is the following: