通用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+2*edi]    //匹配函数的序号
        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  虐黑三爆  阅读(1663)  评论(0编辑  收藏  举报