Vulnerability Development mailing list archives

Re: development of wordpad exploit


From: dullien () GMX DE (Thomas Dullien)
Date: Sat, 20 Nov 1999 16:00:51 +0100


On Fri, 19 Nov 1999 19:45:28 -0800, Blue Boar wrote:

Folks who are AGAINST discussing Windows overflow basics here because
they'd be wildly bored hashing over something they already know, mail me
privately.  I'll use that as a sort of voting mechanism.

Well, I am all for it :)

In case of wordpad, I did a little analysis this afternoon:
;-------- snip -------------------

Allrighty... how do we go about exploiting the Wordpad flaw ?

I will explain what I did so far, and I assume you have a good kernel-debugger
(such as TRW for Win9x or Numega Softice for WinNT), a hex editor and a dis-
assembler handy.

First of all, we need to collect as much data as possible on what exactly is going
on when this overflow occurs.

For this reason, we first use wordpad to create a simple rtf file, containing any
text you wish. Mine looks like this when viewed in notepad:

;--- snip ;>----------
{\rtf1\ansi\deff0\deftab720{\fonttbl{\f0\fswiss MS Sans Serif;}{\f1\froman\fcharset2 Symbol;}{\f2\froman Times New 
Roman;}}
{\colortbl\red0\green0\blue0;}
\deflang1031\pard\plain\f2\fs20 HOLA :)
\par }
;--- snap ;>-----------

Now, the overflow occured in the first line where the text 'ansi' is, according 
to the post to bugtraq. Allrighty, lets see what we can do. Change the string 
"ansi" to "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRR
SSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ111122223333444455556666777799990000". I do this 
to be able to determine how long the overwritten buffer is when EIP is totaled, 
more on this later.

Now, I assume you have a debugger (preferably something like Softice) loaded.
Load this file into wordpad and see what happens:

Wordpad crashes with 0x6B6B6B6B (="kkkk") in EIP.

Lets have a look at our registers and memory for information gathering...

The memory at ESP-18 looks in my case exactly like this:

:dd esp-0x18 l 60
0023:0012EFBC 6A6A6A6A  6B6B6B6B  0000001B  00000212      jjjjkkkk........
0023:0012EFCC 0012EFD4  00000023  6C6C6C6C  6D6D6D6D      ....#...llllmmmm
0023:0012EFDC 6E6E6E6E  6F6F6F6F  70707070  71717171      nnnnooooppppqqqq
0023:0012EFEC 72727272  73737373  74747474  75757575      rrrrssssttttuuuu
0023:0012EFFC 76767676  77777777  78787878  79797979      vvvvwwwwxxxxyyyy
0023:0012F00C 7A7A7A7A  00000200  00000000  00000000      zzzz............

What can we learn from this memory image ? 
1. Obviously our string was stripped of all numbers and converted to lower
   case
2. The last executed instruction must've been a "RET 0x10", since there is
   a 16-byte gap between the "kkkk" which was popped into EIP by the RET
   and the current ESP.
3. The overflow happened in a standard C-convention function probably, as
   usually the last stack-related instruction executed before RET'ing is
   POP EBP, and our EBP when the program crashes is 0x6A6A6A6A.

Now, we would just love to learn where exactly that RET was executed, since
it is always easier seeing if we can exploit something if we have a little
more info on what happens before the crash.

Now, lets try to figure out where that RET came from...

For this purpose, we will truncate our string at the last "j". Why that ?
Well, if the string is copied with a standard string copy routine, only
the lowest-order byte of the return address will be overwritten by a NULL
character which terminates the string. The rest of the return address 
will remain intact, so that we can determine a 255 byte range in which 
our RET has to occur.
Now, in my case I get an exception at an invalid instruction, and the stack
at ESP-0x14 shows me a return address of 0x75023A00. Allrighty, we know
that the last '00' is due to our copied string, so the call to our routine
which doesn't return properly because we smash its stack is somewhere 
between 0x75023A00 and 0x75023AFF. This is inside the address region of
riched32.dll, not riched20.dll. Lets disassemble riched32.dll.

In the address range from where our CALL came, we have only 3 possible calls:
 1.      75023A23:       CALL 7502339E
 2.      75023A5E:       CALL 750238CD
 3.      75023A69:       CALL 750236DF

Well, lets load Wordpad again and set breakpoints on these calls. 
Each time the debugger breaks in, we step over the calls (not into it yet)
to see if the call crashes. On the third break, I stepped over the call at
75023A69 and hello (!), there we have our crash. We now know that the 
exploitable function is the one at 750236DF. Lets narrow it down further 
and look at this function: 

        PUSH    EBP
        MOV     EBP, ESP
        SUB     ESP, 0x24

Well, 36 bytes of stack space are reserved; the stack and local vars look
kinda like this:

        szString        db  35 dup(0)
        variable1       db  ?
        reg_save_EBP    dd  ?
        return_Address  dd  ?

So it becomes clear that when we stuff anything longer than 34 letters into
our buffer, the internal variable1 will get corrupted, if we stuff more than
38 letters into it, we will corrupt EBP, and if we stuff 42 or more letters
into the buffer, we can corrupt the RET address. (Greets to Deep Thought on
this one ;)

Well, lets step through this function a bit...
The function will start copying the string in the file into the local buffer
szString, and will only check the first letter whether it is any of these:
0x27, 0x2A (='*'), 0x2D (='-'), 0x5C (='\'), 0x0A (=LF), 0x0D (=CR), 0x34
(=':'), 0x5F (='_'), 0x7B (="{"), 0x7C (='{'), 0x7D (='}'), 0x7E (='~').

Well well well... we have a nice amount of information here. Now, to safely
turn control to the data we pass onto the stack, we need some kind of 
either JMP ESP or CALL ESP, as none of the registers we have point to the
data buffer we're overflowing.
The opcodes for JMP ESP are 0xFF, 0xE4 and the opcodes for the CALL ESP are
0xFF, 0xD4. Now, lets search in memory for something like this.

Note: In order to create a truly stable exploit, we'd have to find such
data at an address that is _static_ on all service packs. I don't know of
any CALL/JMP ESP's that are static on all service packs, so I can't really
help in this case but to do version-specific work. If anyone winds up with
a nicer solution, please tell me :)

In my case, a nice CALL ESP resides in mfc42u.dll, on my computer (NT4SP5)
at address 75ea991C. Now lets try if we can set EIP to this value. Add the
"kkkk" at the end of our buffer again, then fire up a hex editor and sub-
stitute them for 0x1C, 0x99, 0xea, 0x75.

Now when trying to run this baby, we see that all non-characters are filtered
out. Damn. We have a problem here.

I doubt that this overflow can be easily exploited in a by-the book manner,
as the data we pass on the stack is too heavily filtered in order to be
put to regular use as 'code we inject'... anyone have any suggestions here ?

Thomas Dullien
dullien () gmx de
Win32 Security Consultant ;-> Hire me !


Current thread: