通用shellcode

所有 win_32 程序都会加载 ntdll.dll 和 kernel32.dll 这两个最基础的动态链接库。如果想要
在 win_32 平台下定位 kernel32.dll 中的 API 地址,可以采用如下方法。

  1. 首先通过段选择字 FS 在内存中找到当前的线程环境块 TEB。

  2. 线程环境块偏移位置为 0x30 的地方存放着指向进程环境块 PEB 的指针。

  3. 进程环境块中偏移位置为 0x0C 的地方存放着指向 PEB_LDR_DATA 结构体的指针,
    其中,存放着已经被进程装载的动态链接库的信息。

  4. PEB_LDR_DATA 结构体偏移位置为 0x1C 的地方存放着指向模块初始化链表的头指
    针 InInitializationOrderModuleList。

  5. 模块初始化链表 InInitializationOrderModuleList 中按顺序存放着 PE 装入运行时初始化
    模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernel32.dll。

  6. 找到属于 kernel32.dll 的结点后,在其基础上再偏移 0x08 就是 kernel32.dll 在内存中的
    加载基地址。

  7. 从 kernel32.dll 的加载基址算起,偏移 0x3C 的地方就是其 PE 头。

  8. PE 头偏移 0x78 的地方存放着指向函数导出表的指针。

  9. 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址

fs是什么?TEB是什么?

  • fs是一个寄存器,只不过不可见
  • 在NT内核系统中fs寄存器指向TEB结构
  • TEB+0x30处指向PEB结构

TEB结构如下:

//
// Thread Environment Block (TEB)
//
typedef struct _TEB
{
NT_TIB Tib; /* 00h /
PVOID EnvironmentPointer; /
1Ch /
CLIENT_ID Cid; /
20h /
PVOID ActiveRpcHandle; /
28h /
PVOID ThreadLocalStoragePointer; /
2Ch /
struct _PEB ProcessEnvironmentBlock; / 30h /
ULONG LastErrorValue; /
34h /
ULONG CountOfOwnedCriticalSections; /
38h /
PVOID CsrClientThread; /
3Ch /
struct _W32THREAD
Win32ThreadInfo; /
40h /
ULONG User32Reserved[0x1A]; /
44h /
ULONG UserReserved[5]; /
ACh /
PVOID WOW32Reserved; /
C0h /
LCID CurrentLocale; /
C4h /
ULONG FpSoftwareStatusRegister; /
C8h /
PVOID SystemReserved1[0x36]; /
CCh /
LONG ExceptionCode; /
1A4h */
struct _ACTIVATION_CONTEXT_STACK ActivationContextStackPointer; / 1A8h /
UCHAR SpareBytes1[0x28]; /
1ACh /
GDI_TEB_BATCH GdiTebBatch; /
1D4h /
CLIENT_ID RealClientId; /
6B4h /
PVOID GdiCachedProcessHandle; /
6BCh /
ULONG GdiClientPID; /
6C0h /
ULONG GdiClientTID; /
6C4h /
PVOID GdiThreadLocalInfo; /
6C8h /
ULONG Win32ClientInfo[62]; /
6CCh /
PVOID glDispatchTable[0xE9]; /
7C4h /
ULONG glReserved1[0x1D]; /
B68h /
PVOID glReserved2; /
BDCh /
PVOID glSectionInfo; /
BE0h /
PVOID glSection; /
BE4h /
PVOID glTable; /
BE8h /
PVOID glCurrentRC; /
BECh /
PVOID glContext; /
BF0h /
NTSTATUS LastStatusValue; /
BF4h /
UNICODE_STRING StaticUnicodeString; /
BF8h /
WCHAR StaticUnicodeBuffer[0x105]; /
C00h /
PVOID DeallocationStack; /
E0Ch /
PVOID TlsSlots[0x40]; /
E10h /
LIST_ENTRY TlsLinks; /
F10h /
PVOID Vdm; /
F18h /
PVOID ReservedForNtRpc; /
F1Ch /
PVOID DbgSsReserved[0x2]; /
F20h /
ULONG HardErrorDisabled; /
F28h /
PVOID Instrumentation[14]; /
F2Ch /
PVOID SubProcessTag; /
F64h /
PVOID EtwTraceData; /
F68h /
PVOID WinSockData; /
F6Ch /
ULONG GdiBatchCount; /
F70h /
BOOLEAN InDbgPrint; /
F74h /
BOOLEAN FreeStackOnTermination; /
F75h /
BOOLEAN HasFiberData; /
F76h /
UCHAR IdealProcessor; /
F77h /
ULONG GuaranteedStackBytes; /
F78h /
PVOID ReservedForPerf; /
F7Ch /
PVOID ReservedForOle; /
F80h /
ULONG WaitingOnLoaderLock; /
F84h /
ULONG SparePointer1; /
F88h /
ULONG SoftPatchPtr1; /
F8Ch /
ULONG SoftPatchPtr2; /
F90h */
PVOID TlsExpansionSlots; / F94h /
ULONG ImpersionationLocale; /
F98h /
ULONG IsImpersonating; /
F9Ch /
PVOID NlsCache; /
FA0h /
PVOID pShimData; /
FA4h /
ULONG HeapVirualAffinity; /
FA8h /
PVOID CurrentTransactionHandle; /
FACh /
PTEB_ACTIVE_FRAME ActiveFrame; /
FB0h /
PVOID FlsData; /
FB4h /
UCHAR SafeThunkCall; /
FB8h /
UCHAR BooleanSpare[3]; /
FB9h */
} TEB, *PTEB;

PEB结构

typedef struct _PEB
{
UCHAR InheritedAddressSpace; // 00h
UCHAR ReadImageFileExecOptions; // 01h
UCHAR BeingDebugged; // 02h
UCHAR Spare; // 03h
PVOID Mutant; // 04h
PVOID ImageBaseAddress; // 08h
PPEB_LDR_DATA Ldr; // 0Ch
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
PVOID SubSystemData; // 14h
PVOID ProcessHeap; // 18h
PVOID FastPebLock; // 1Ch
PPEBLOCKROUTINE FastPebLockRoutine; // 20h
PPEBLOCKROUTINE FastPebUnlockRoutine; // 24h
ULONG EnvironmentUpdateCount; // 28h
PVOID* KernelCallbackTable; // 2Ch
PVOID EventLogSection; // 30h
PVOID EventLog; // 34h
PPEB_FREE_BLOCK FreeList; // 38h
ULONG TlsExpansionCounter; // 3Ch
PVOID TlsBitmap; // 40h
ULONG TlsBitmapBits[0x2]; // 44h
PVOID ReadOnlySharedMemoryBase; // 4Ch
PVOID ReadOnlySharedMemoryHeap; // 50h
PVOID* ReadOnlyStaticServerData; // 54h
PVOID AnsiCodePageData; // 58h
PVOID OemCodePageData; // 5Ch
PVOID UnicodeCaseTableData; // 60h
ULONG NumberOfProcessors; // 64h
ULONG NtGlobalFlag; // 68h
UCHAR Spare2[0x4]; // 6Ch
LARGE_INTEGER CriticalSectionTimeout; // 70h
ULONG HeapSegmentReserve; // 78h
ULONG HeapSegmentCommit; // 7Ch
ULONG HeapDeCommitTotalFreeThreshold; // 80h
ULONG HeapDeCommitFreeBlockThreshold; // 84h
ULONG NumberOfHeaps; // 88h
ULONG MaximumNumberOfHeaps; // 8Ch
PVOID** ProcessHeaps; // 90h
PVOID GdiSharedHandleTable; // 94h
PVOID ProcessStarterHelper; // 98h
PVOID GdiDCAttributeList; // 9Ch
PVOID LoaderLock; // A0h
ULONG OSMajorVersion; // A4h
ULONG OSMinorVersion; // A8h
ULONG OSBuildNumber; // ACh
ULONG OSPlatformId; // B0h
ULONG ImageSubSystem; // B4h
ULONG ImageSubSystemMajorVersion; // B8h
ULONG ImageSubSystemMinorVersion; // C0h
ULONG GdiHandleBuffer[0x22]; // C4h
PVOID ProcessWindowStation; // ???
} PEB, *PPEB;

PEB_LDR_DATA结构

typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

LIST_ENTRY结构

typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

  • 导出表偏移 0x1C 处的指针指向存储导出函数偏移地址(RVA)的列表。

  • 导出表偏移 0x20 处的指针指向存储导出函数函数名的列表。

  • 函数的 RVA 地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位
    到所需的函数是第几个,然后在地址列表中找到对应的 RVA。

  • 获得 RVA 后,再加上前边已经得到的动态链接库的加载基址,就获得了所需 API 此刻
    在内存中的虚拟地址,这个地址就是我们最终在 shellcode 中调用时需要的地址。

按照上面的方法,我们已经可以获得 kernel32.dll 中的任意函数。类似地,我们已经具备了
定位 ws2_32.dll 中的 winsock 函数来编写一个能够获得远程 shell 的真正的 shellcode 了。

其实,在摸透了 kernel32.dll 中的所有导出函数之后,结合使用其中的两个函数 LoadLibrary()
和 GetProcAddress(),有时可以让定位所需其他 API 的工作变得更加容易。

image

int main()
{
_asm
{
mov eax, fs:[0x30] ;PEB的地址
mov eax, [eax + 0x0c] ;Ldr的地址
mov esi, [eax + 0x1c] ;Flink地址
lodsd
mov eax, [eax + 0x08] ;eax就是kernel32.dll的地址
}
return 0;
}

一个弹出对话框,然后退出程序的shellcode

  • MessageBoxA 位于 user32.dll 中,用于弹出消息框。
  • ExitProcess 位于 kernel32.dll 中,用于正常退出程序。
  • LoadLibraryA 位于 kernel32.dll 中。并不是所有的程序都会装载 user32.dll,所以在我们调用MessageBoxA 之前,应该先使用 LoadLibrary(“user32.dll” )装载其所属的动态链接库

为了让 shellcode 更加通用,能被大多数缓冲区容纳,我们总是希望 shellcode 尽可能短。因此,,一般情况下并不会“MessageBoxA”这么长的字符串去进行直接比较。所以会对所需的API函数 名进行hash运算,这样只要比较 hash 所得的摘要就能判定是不是我们所需的API了

本次使用的hash算法

include <stdio.h>

DWORD GetHash(char fun_name)
{
DWORD digest=0;
while(
fun_name)
{
digest=((digest<<25)|(digest>>7)); //循环右移7位
/*
movsx eax,byte ptr[esi]
cmp al,ah
jz compare_hash
--> ror edx,7 ;这一行我看了俩小时,最后才意识到是((循环))右移,不是单纯的 >>7
add edx,eax
inc esi
jmp hash_loop
这是我第一次知道汇编也有比C语言方便的时候
*/
digest+= *fun_name ; //累加
fun_name++;
}
return digest;
}
main()
{
DWORD hash;
hash= GetHash("AddAtomA");
printf("%#x\n",hash);
}

当 shellcode 是利用异常处理机制而植入的时候,往往会产生标志位的变化,使 shellcode 中的字串处理方向发生变化而产生错误(如指令 LODSD)。为了增加 shellcode的通用性,在push之前应当先用 CLD 将 DF 位清零

CLD ;清空标志位DF
push 0x1E380A6A ;压入MessageBoxA的hash-->user32.dll
push 0x4FD18963 ;压入ExitProcess的hash-->kernel32.dll
push 0x0C917432 ;压入LoadLibraryA的hash-->kernel32.dll
mov esi,esp ;esi=esp,指向堆栈中存放LoadLibraryA的地址
lea edi,[esi-0xc] ;函数地址的开始

抬高栈顶,保护 shellcode 不被入栈数据破坏。

xor ebx,ebx
mov bh, 0x04
sub esp, ebx

定位kernel32.dll的基地址

mov ebx,fs:[edx+0x30] ;PEB
mov ecx,[ebx+0xC] ;PEB_LDR_DATA
mov ecx,[ecx+0x1C] ;InInitializationOrderModuleList
mov ecx,[ecx] ;进入链表第一个就是ntdll.dll
mov ebp,[ecx+0x8] ;ebp= kernel32.dll的基地址

下面找函数表的地址

mov eax,[ebp+0x3C] //dll的PE头 ebp是pe文件的开头位置(MZ)
mov ecx,[ebp+eax+0x78] //导出表的指针
add ecx,ebp //ecx=0x78C00000+0x262c
mov ebx,[ecx+0x20] //导出函数名列表指针
add ebx,ebp //导出函数名列表指基地址

导出表的结构

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;         //PE文件的模块名
DWORD Base;          //模块基地址
DWORD NumberOfFunctions;  //总的导出函数的个数
DWORD NumberOfNames;    //有名称的函数的个数(这一行我真不想写,没用,还可能会误导初学者),有的导出函数没有名字的只有序号
DWORD AddressOfFunctions; //导出地址表
DWORD AddressOfNames; //函数名表
DWORD AddressOfNameOrdinals; //函数序号,并不一定是连续的,但一般和导出地址表是一一对应的
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

在导出表中搜索AIP的逻辑可以设计如图

image

具体代码如下

include <stdio.h>

include <windows.h>

int main()
{
__asm
{
CLD //清空标志位DF
push 0x1E380A6A //压入MessageBoxA的hash-->user32.dll
push 0x4FD18963 //压入ExitProcess的hash-->kernel32.dll
push 0x0C917432 //压入LoadLibraryA的hash-->kernel32.dll
mov esi,esp //esi=esp,指向堆栈中存放LoadLibraryA的地址
lea edi,[esi-0xc] //空出8字节应该是为了兼容性
//开辟一些栈空间
xor ebx,ebx
mov bh,0x04
sub esp,ebx //esp-=0x400
//
压入"user32.dll"
mov bx,0x3233
push ebx //"\0 32"
push 0x72657375 //"user"
push esp
xor edx,edx //edx=0
//======找kernel32.dll的基地址
mov ebx,fs:[edx+0x30] //[TEB+0x30]-->PEB
mov ecx,[ebx+0xC] //[PEB+0xC]--->PEB_LDR_DATA
mov ecx,[ecx+0x1C] //[PEB_LDR_DATA+0x1C]--->InInitializationOrderModuleList
mov ecx,[ecx] //进入链表第一个就是ntdll.dll
mov ebp,[ecx+0x8] //ebp= kernel32.dll的基地址

//======是否找到了自己所需全部的函数
find_lib_functions:
lodsd //eax=[esi],esi+=4
cmp eax,0x1E380A6A //与MessageBoxA的hash比较
jne find_functions
xchg eax,ebp //-------------------------------------------------> |
call [edi-0x8] //LoadLibraryA("user32") |
xchg eax,ebp //ebp=userl32.dll的基地址,eax=MessageBoxA的hash <-- |

//======导出函数名列表指针
find_functions:
pushad //保护寄存器
mov eax,[ebp+0x3C] //dll的PE头
mov ecx,[ebp+eax+0x78] //导出表的指针
add ecx,ebp //ecx=导出表的基地址
mov ebx,[ecx+0x20] //导出函数名列表指针
add ebx,ebp //ebx=导出函数名列表指针的基地址
xor edi,edi

//======找下一个函数名
next_function_loop:
inc edi
mov esi,[ebx+edi*4] //从列表数组中读取
add esi,ebp //esi = 函数名称所在地址
cdq //edx = 0

//函数名的hash运算
hash_loop:
movsx eax,byte ptr[esi]
cmp al,ah //字符串结尾就跳出当前函数
jz compare_hash
ror edx,7
add edx,eax
inc esi
jmp hash_loop
//
比较找到的当前函数的hash是否是自己想找的
compare_hash:
cmp edx,[esp+0x1C] //lods pushad后,栈+1c为LoadLibraryA的hash
jnz next_function_loop
mov ebx,[ecx+0x24] //ebx = 顺序表的相对偏移量
add ebx,ebp //顺序表的基地址
mov di,[ebx+2edi] //匹配函数的序号
mov ebx,[ecx+0x1C] //地址表的相对偏移量
add ebx,ebp //地址表的基地址
add ebp,[ebx+4
edi] //函数的基地址
xchg eax,ebp //eax<==>ebp 交换

pop edi
stosd //把找到的函数保存到edi的位置
push edi

popad
cmp eax,0x1e380a6a //messagebox的hash
jne find_lib_functions

//======让他做些自己想做的事
function_call:
xor ebx,ebx
push ebx
push 0x74736577
push 0x6c696166 //push "failwest"
mov eax,esp
push ebx
push eax
push eax
push ebx
call [edi-0x04] //MessageBoxA(NULL,"failwest","failwest",NULL)
push ebx
call [edi-0x08] //ExitProcess(0);
nop
nop
nop
nop
}
return 0;
}

posted @ 2019-01-22 10:26  虐黑三爆  阅读(1775)  评论(0)    收藏  举报