trigger_str = generate_potential_trigger_string() start = time.time() skt.send(trigger_str) rlist, wlist, xlist = select.select([skt], [], [skt], 1.0) stop = time.time() difference = stop - start
(added 17/10/2009)
While recently working on a heap overflow, I wanted to be able to exploit remote unknown targets that have executable memory. Just for completeness, Exec-shield and PaX would not have prevented exploitation on the distro's I checked, they just required a couple more offsets. Oh, and -D_FORTIFY_SOURCE=2 does not help either.
Anyways, the vulnerability has some specialities which make it slightly more fun than normal.. During input processing, the heap layout can change considerably, with enough massaging, you can either overwrite a structure with a pointer to a structure which has a function pointer, or cause it to write some data we semi control to a pointer we can control. Given the situation, both are relatively easy to exploit, although read only GOT entries will make the latter method harder against unknown targets. (There are function pointers on the .bss, however it requires a lot more massaging and/or luck to hit in that regards).
Because trying X input strings (where X should allow you to hit it with Y probability) for each address would end up being a lot of attempts / crashes, it would be better to try and isolate a single suitable input strings, then loop over potential memory ranges looking for our code. However, the question then is, "Is it feasible to do so?"
In the case of this particular vulnerability, it is feasible to do that via using information gained from how long it takes to shut down the socket / remote process to crash. The information we gather from this is the time between sending the string, and how long it takes for the socket to shut down, which relates to how much processing was done in the remote process.
If it closes very quickly, it implies we have hit a exit(1) code path due to the heap modification early on. If it takes too long, we've hit another exit(1) code path, but after it's done a lot of heap processing first.
If it hits a little bit before our time, it usually means the massaging was off a little bit, a bit after tends to mean the same.. However, there's enough difference to usually identify the ideal case.
Of course, using timing information is only useful in certain situations (ideally, you're close to the target machine, low/little load on each end, network load is low/lowish).. those challenges can be reduced though by owning a machine close to your target. Also it helps if the vulnerability you're targetting gives you useful timing information.
The below graph information was generated via:
trigger_str = generate_potential_trigger_string() start = time.time() skt.send(trigger_str) rlist, wlist, xlist = select.select([skt], [], [skt], 1.0) stop = time.time() difference = stop - start
and doing that 200 times, sorting, and putting the results into a text file, and having gnuplot graph it for us.
While it could probably be argued that python isn't ideal for gathering such precise timing information, we'll ignore that for now. It's working for this demonstration, which is all I care about :p
In the below graph, red crosses are "uninteresting", and green ones are "interesting". The "interesting" state is a crash that's directly related to the function pointer cleanup code. The blue dot is a crash relating to memset() (usually a pointer we control), and purple is an "other" crash (usually due to our pointer not being aligned properly due to allocation layout).
The above graph paints an interesting picture.. at the beginning, there are some very early exit(1) codepaths, then more around the below the 0.005 time marker.. At around the 0.005 and 160 intersect, we start getting crashes due to our input corrupting the processes heap (blue/purple/green) in a useful way and taking successively longer to crash.
Once we have one of the green crashes, we can use that to bruteforce the section of memory that will lead to code execution.
In closing, I hope this brief article shows some of the benefits that timing can provide when suitable and when exploiting targets when you have little information available.