【逆向篇】分析一段简单的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 version
Release date:      16/10/2013
Author:        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 = 0s
00417002 B2 30                   mov   dl,30h                         //edx = 0x30
00417004 64 8B 12                mov   edx,dword ptr fs:[edx]         //edx = PEB
00417007 8B 52 0C                mov   edx,dword ptr [edx+0Ch]        //edx = _PEB_LDR_DATA
0041700A 8B 52 1C                mov   edx,dword ptr [edx+1Ch]        //edx = InInitializationOrderModuleList.Flink
0041700D 8B 42 08                mov   eax,dword ptr [edx+8]          //eax = _LDR_DATA_TABLE_ENTRY.DllBase
00417010 8B 72 20                mov   esi,dword ptr [edx+20h]        //esi = _LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
00417013 8B 12                   mov   edx,dword ptr [edx]            //edx = _LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks.Flink
00417015 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 = 0
0041702C 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 = 0
0041706D 51                      push  ecx                            //lpMessageText = ecx = "@BrokenByte"
0041706E 50                      push  eax                            //uAction = eax = 0
0041706F FF D7                   call  edi                            //FataAppExitA(0, "@BrokenByte");
*/

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

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

void ShellCodeFunction()
{
    PPEB Peb = NULL;
    PPEB_LDR_DATA LdrData = 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 = 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到函数地址获取)