【逆向篇】分析一段简单的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权威指南》这本书。

时间: 2024-08-10 15:00:21

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

NLP+语篇分析(五)︱中文语篇分析研究现状(CIPS2016)

摘录自:CIPS2016 中文信息处理报告<第三章 语篇分析研究进展.现状及趋势>P21 CIPS2016 中文信息处理报告下载链接:http://cips-upload.bj.bcebos.com/cips2016.pdf NLP词法.句法.语义.语篇综合系列: NLP+词法系列(一)︱中文分词技术小结.几大分词引擎的介绍与比较 NLP+词法系列(二)︱中文分词技术及词性标注研究现状(CIPS2016) NLP+句法结构(三)︱中文句法结构研究现状(CIPS2016) NLP+语义分析(四)

一段简单的汇编程序

.section .data < initialized data here > .section .bss < uninitialized data here > .section .text .global _start _start: < instruction code > 如上述代码所示,汇编程序通常包含3个段(section): data section bss section text section GNU assembler使用.section语句来声

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie

Javac源码简单分析之Javac简单介绍

一.简单介绍 javac 是java语言编程编译器.javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件. 二.源码获取 OpenJDK6源码:http://download.java.net/openjdk/jdk6/ Javac的源码就在OpenJDK源码里面. 或者在CSDN下载:http://download.csdn.net/detail/p_3er/7383741 三.Javac的包 Javac的公共入口点是com.sun.tools.javac

linux设备驱动第三篇:如何实现简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动 都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [email pro

缓冲区溢出分析第04课:ShellCode的编写

前言 ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理.那么下面我们就来编写ShellCode.为了简单起见,这里我只想让程序显示一个对话框: 图1 获取相关函数的地址 那么我们下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框.由于我在这里想要调用MessageBox()这个API函数,所以说首先需要获取该函数的地址,这可以通过编写一个小程序来获取: #inclu

iOS开发UI篇—UIPickerView控件简单介绍

iOS开发UI篇—UIPickerView控件简单介绍 一.UIPickerView 控件 1.简单介绍: 2.示例代码 TXViewController.m文件 1 // Created by 鑫 on 14-10-15. 2 3 // Copyright (c) 2014年 梁镋鑫. All rights reserved. 4 5 // 6 7 8 9 #import "TXViewController.h" 10 11 12 13 @interface TXViewContro

完成一段简单的Python程序,用于实现一个简单的加减乘除计算器功能

#!/bin/usr/env python#coding=utf-8'''完成一段简单的Python程序,用于实现一个简单的加减乘除计算器功能'''try: a=int(raw_input("please input a number:"))except ValueError: print("第一个运算数字输入非数字") try: b=int(raw_input("please input another number:"))except Val

一段简单的代码告诉你什么叫内存溢出

#include <stdio.h> int FooArray[4] = {1, 1, 1, 1}; int VeryImportantValue = 7; void main() { printf("%d\n", VeryImportantValue); for (int i = 0; i <= 4; i++) { FooArray[i] = 4; } printf("%d\n", VeryImportantValue); } 这是个很简单的内存