首页 > 代码库 > 【逆向篇】分析一段简单的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到函数地址获取