Please take a moment to read http://bit.ly/demandglobalchange, to help share the message and support the initiative to tell our leaders to focus on addressing the global world problems, instead of complaining about the effects of their lack of leadership. Be a leader yourself, and share this with as many people as possible. #demandglobalchange // https://www.facebook.com/demandglobalchange



Please consider donating: https://www.corelan.be/index.php/donate/


8,630 views

Debugging Fun – Putting a process to sleep()

Introduction / Problem description

Recently I played with an older CVE (CVE-2008-0532, http://www.securityfocus.com/archive/1/489463, by FX) and I was having trouble debugging the CGI executable where the vulnerable function was located.

Here’s the problem : The CGI Executable CSUserCGI.exe is a child process of IIS, and only spawns when called by a user. The executable script then quickly closes after serving its purpose… and before we can attach our debugger. So how do we essentially debug this? Would configuring the debugger for JIT (Just In Time) work ?

Let’s see

When we call the CGI script over HTTP we can see it open and close real quick.

Try #1

pic1

pic2

No luck! So How am I going to debug this? There has to be a way.

Putting the process to sleep()

At the time one of my Corelan team mates sinn3r had completed a few HP NNM modules which he encountered similar circumstances. The idea was to put the child process to sleep until I attach a debugger, by inserting some code that would do this:

// (pseudo code):

while (IsDebuggerPresent == false) {
   sleep(1);
}

// Repair the prologue of the entry point you hijacked,
// and then jmp back to the entry point.

Okay made sense, so time to get my hands dirty. I opened the executable in immunity and picked a good function to hook (0x00401010). I wanted to skip some of the initial kernel calls and environment and jump right into main().

pic3

The next thing I needed to was find a place in .text that wasn’t being used by the executable and would not corrupt any other functions, or prevent the executable from working as intended. The goal would be to use the existing call instruction (call 0x00401010 in this case), and change the offset value of that call to make a jump to the location where my custom routine will be placed.

I found a nice spot at 0x00415362 so I would edit the call at 0x00414CD6 to point to that location.

pic4

Now I need to find the location of kernel32.Sleep & kernel32.IsDebuggerPresent. Since this is just a patch I am doing locally on my system there is no need to look for a generic calls to these function, I can just look up the locations in Immunity under executable then names. On my Windows 2003 SP2 system the locations are 0x77e424de & 0x77e5da00. More than likely they will be different on your machine!

pic5

Time to insert my function hook. Note that for this particular exploit there was not a need to jump back to the next instruction after our original (now updated) call but I did it anyways.

First, I patched the call offset (to make it jump to the custom routine, which I’m going to place at 0x00415362)

00414CD6   E8 87060000      CALL CSUserCG.00415362 ; jmp to custom routine

and I placed the custom routine at 0x00415362

00415362   6A 01            PUSH 1
00415364   E8 75D1A277      CALL kernel32.Sleep
00415369   E8 9286A477      CALL kernel32.IsDebuggerPresent
0041536E   83F8 01          CMP EAX,1
00415371  ^75 EF            JNZ SHORT CSuserCG.00415362
00415373   CC               INT3
00415374   83C4 04          ADD ESP,4
00415377   E8 94BCFEFF      CALL CSuserCG.00401010  ;go back
0041537C  ^E9 5AF9FFFF      JMP CSuserCG.00414CDB

pic6

With immunity we can now right click and save the changes to a new name. I decided to name the file NEWCSUserCGI.exe and place it in our CGI script directory (C:\Inetpub\wwwroot\securecgi-bin).

pic7

pic8

I then renamed the original executable to OLDCSUserCGI.exe and then changed NEWCSUserCGI.exe to CSUserCGI.exe (which is the original name of the file)

This time I launched with the proof of concept in our URL and viola the script is still running!

pic9

Now time to see our buffer overflow and to verify our public proof of concept code is working. If we take a look at (http://www.securityfocus.com/archive/1/489463 by FX) we can see that when we supply a long string after “Logout+” we will reach our buffer overflow giving us control of EIP

pic10

The vulnerable function is a subroutine in the function we hooked (0x00401010). First we can see a fixed buffer of 0x60 being setup for our Logout argument.

pic11

pic12

msvcrt.strtok is called and is looking for the first string that ends in “.”

pic13

The string is then copied on the stack and we eventually reach our buffer overflow.

pic14

Woot! This was an easy stack buffer overflow and the final code can be seen here: (https://github.com/rapid7/metasploit-framework/blob/unstable/unstable-modules/exploits/untested/cisco_acs_ucp.rb)

What about automating this?

I thought that this might be a good opportunity to create a script that would automate this process or come up with an alternative. I presented the idea to my teammates before leaving from work and drove home eager to give this a shot over the weekend. Low and behold I must have had a memory lapse and forgot that corelanc0d3r has over 5000 lines of python-fu with immunity (mona.py anyone?) and finished this before I even got home from work! All the credit goes to him for this one.

Here is his automated script that will essentially sleep the application until you attach the debugger and it does it all without calling any kernel32 API calls.

# binary patcher
# will inject routine to make the binary hang
# so you can attach to it with a debugger
#
# corelanc0d3r
# (c) 2012 - www.corelan.be

import sys,pefile,os,binascii

def patch_file(binaryfile):

	routine = "\x33\xc0"		# xor eax,eax
	routine += "\x83\xF8\x00"	# cmp eax,0
	routine += "\x74\xFB"		# JE back to cmp

	print "[+] Opening file %s" % binaryfile
	pe = pefile.PE(binaryfile)

	entrypoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
	base = pe.OPTIONAL_HEADER.ImageBase
	print " - Original Entrypoint : 0x%x" % (base + entrypoint)

	searchend = 0
	startrva = 0
	for section in pe.sections:
		if section.Name.replace('\x00','') == '.text':
			# code segment
			print " - Finding a good spot in code segment at 0x%x" % (base + section.VirtualAddress)
			print " Size : 0x%x" % section.SizeOfRawData
			searchend = section.SizeOfRawData
			startrva = section.VirtualAddress
			#print (section.Name, hex(section.VirtualAddress), hex(section.Misc_VirtualSize), section.SizeOfRawData )
	if searchend > 0:
		cnt = 0
		consecutive = 0
		stopnow = False
		offsethere = 0
		while cnt < searchend and not stopnow:
			thisbyte = pe.get_dword_at_rva(startrva+cnt)
			if thisbyte == 0:
				if offsethere == 0:
					offsethere = startrva+cnt
				consecutive += 1
			else:
				offsethere = 0
				consecutive = 0
			if consecutive >= len(routine)+5:
				stopnow=True
			cnt = cnt + 1
		print " - Found %d consecutive null bytes at offset 0x%x" % (consecutive,offsethere)
		print " Distance from original entrypoint : %x bytes" % (offsethere - entrypoint)
		jmpback = "%x" % (4294967295 - (offsethere - entrypoint + 4 + len(routine)))
		print " Jmpback : 0x%s" % jmpback
		routine += "\xe8"
		routine += binascii.a2b_hex(jmpback[6:8])
		routine += binascii.a2b_hex(jmpback[4:6])
		routine += binascii.a2b_hex(jmpback[2:4])
		routine += binascii.a2b_hex(jmpback[0:2])
		print " - Injecting hang + redirect (%d bytes) at 0x%x" % (len(routine),(base+offsethere))
		pe.set_bytes_at_rva(offsethere,routine)
		print " - Setting new EntryPoint to 0x%x" % (base+offsethere)
		pe.OPTIONAL_HEADER.AddressOfEntryPoint = offsethere
		entrypoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
		print " - Entrypoint now set to : 0x%x" % (base + entrypoint)

		print "[+] Saving file"
		pe.write(filename=binaryfile.replace(".exe","")+"_patched.exe")
		print "[+] Patched."
	else:
		print "[-] No code segment found ?"

if len(sys.argv) == 2:
	target = sys.argv[1]
	if os.path.exists(target):
		patch_file(target)
	else:
		print " ** Unable to find file '%s' **" % target
else:
	print "\nUsage : patchbinary.py filename\r\n"

What corelanc0d3r did was take advantage of pefile, a python module that allows us to read and work with PE (Portable Executable) files. (This module is installed by default on BackTrack and Immunity Debugger, just for your information)

His script will load the executable file and get the original entrypoint of the module.

Next, the script will look for a location in the file that has 12 consecutive null bytes (you could replace this with for example NOPS if needed).

At that location, an custom routine will be placed. This routine will clear out EAX, compare it with 0, and then jump back to the compare statement if the condition is true. Essentially this will be a loop until we attach our debugger. After the conditional jump (which ensures the loop), a call to the original location of the entrypoint is placed. Finally, the entrypoint RVA in the PE file is updated to point at the custom routine.

In other words, when you would run this executable, it would just hang (infinite loop). When attaching a debugger the process will pause. You then only need to find the location where the custom asm routine was inserted (the address is, in fact, right after the updated entrypoint), and either replace the cmp instruction with nops, or just change the cmp eax,0 into cmp eax,1. If you would continue to run the process, the executable would simply start doing what it’s supposed to do. Alternatively, you can just let the application run and then pause (break)… it would halt on the cmp or the jump instruction inside the custom routine.

Lets go ahead now and run this from cmd.exe and see everything work automatically.

After the script finds and writes to a safe place for the loop, it will save a new file with the filename and “_patched.exe”.

If we go ahead and run we can see that it works and does the same thing as my manual patch.

pic15

Now if we rename our patched file and run our proof of concept again we can then attach the debugger and press “pause” to stop the loop. We then break out of the loop and the call back to the original entrypoint will be executed.

pic16

Woot! Looks like everything is working properly. So that’s pretty much it, this is just a short article on a problem I encountered and how it was solved. Note that this technique most likely won’t worked with packed/encoded binaries…

Windbg ?

You can, of course, do the same thing with other debuggers as well. The basic procedure will be exactly the same, you only need to know how to edit the instruction in memory to break out of the loop.

Let’s say you want to change the CMP instruction into CMP EAX,1. This requires us to change one byte in memory (the byte at 0x00415337 in this case).

With the debugger attached (and the process interrupted), simply run the following command:

eb 0x00415337 0x01

eb = edit byte. Other windbg ‘edit’ commands are ew (edit word) and ed (edit dword).

Afer making the change, press F5 (or type ‘g’) to let the process break out of the loop and return to the original entrypoint.

Update (march 1st 2012)

As various people on twitter suggested, you can obviously also use \xeb\xfe as patch routine (which will just jump to itself). Tx @fdfalcon and @pa_kt for the good feedback !


2012, Corelan Team (Lincoln). All rights reserved.

Related Posts:

One Response to Debugging Fun – Putting a process to sleep()

Corelan Live training

Since 2011, Corelan GCV has been teaching live win32 exploit dev classes at various security cons and private companies & organizations.

You can read more about the training and schedules here

Demand Global Change

The world needs your help !

Please take a few moments to read the "Demand Global Change Call For Action" document at
http://bit.ly/demandglobalchange
Read the full document at
http://bit.ly/demandglobalchange_full and share the message with as many people as possible.

Like the Facebook page, and SHARE it with everyone you know.



Donate

Want to support the Corelan Team community ? Click here to go to our donations page.

Want to donate BTC to Corelan Team?



Your donation will help funding server hosting.

Protected by Copyscape Web Plagiarism Tool

Corelan Team Merchandise

You can support Corelan Team by donating or purchasing items from the official Corelan Team merchandising store.

Corelan on IRC

You can chat with us and our friends on #corelan (freenode IRC)

Categories