• 0day学习笔记(3)Windows定位API引起的惨案(原理)


    段选择器FS与TEB

    • WinNT内核下内存采用保护模式,段寄存器的意义与实模式汇编下的意义不同.另外,FS存的是段选择子,而不是实模式下的高16位基地址。

    • FS寄存器指向当前活动线程的TEB结构(线程结构)

    • 下面为FS寄存器下偏移的相关信息:

      偏移 说明
      000h 指向SEH链指针
      004h 线程堆栈顶部
      008h 线程堆栈底部
      00Ch SubSystemTib
      010h FiberData
      014h ArbitraryUserPointer
      018h FS段寄存器在内存中的镜像地址
      020h 进程PID
      024h 线程ID
      02Ch 指向线程局部存储指针
      030h PEB结构地址(进程结构)
      034h 上个错误号

    PEB

    详细参考https://blog.csdn.net/weixin_44156885/article/details/100729365

    • 使用汇编获取PEB地址的两种方法:
      方法1:
      MOV	EAX,DWORD PTR FS:[30]    ;FS:[30] = address of PEB
      
      方法2:
      MOV	EAX,DWORD PTR FS:[18]    ;FS[18] = address of TEB
      MOV 	EAX,DWORD PTR DS:[EAX+30]   ;DS[EAX+30] = address of PEB
      
    • PEB几个重要成员
    +002	BeingDebugged 	;Uchar(可用于反调试技术)
    ...
    +008	ImageBaseAddress 	 ;Ptr32 Void
    ...
    +00c	Ldr 	 ;Ptr32 _PEB_LDR_DATA(可用于反调试技术)
    ...
    +018	ProcessHeap 	 ;Ptr32 Void(可用于反调试技术)
    ...
    +068	NtGlobalFlag 	  ;uint4B(可用于反调试技术)
    
    PEB.Ldr
    • PEB.Ldr成员是指向_PEB_LDR_DATA结构体的指针,_PEB_LDR_DATA结构体如下。

      +000 Length                         :Uint4B
      +004 Initialized                    :UChar
      +008 SsHandle                       :Ptr32 Void
      +00c InLoadOrderModulelist          :_LIST_ENTRY
      +014 InMemoryOrderModulelist        :_LIST_ENTRY
      +01c InInitializationOrderModulelist:_LIST_ENTRY
      +024 EntryInProgress                :Ptr32 Void
      +028 ShutdownInProgress             :UChar
      +02c ShutdownThreadId               :Ptr32 Void
      
    • 当模块(DLL)加载到进程后,通过PEB.Ldr成员可以直接获得该模块的加载基址,_PEB_LDR_DATA结构体中含有3个_LIST_ENTRY类型的成员,_LIST_ENTRY结构体如下。

      typedef struct LIST_ENTRY{
      struct _LIST_ENTRY *Flink; 
      struct _LIST_ENTRY *Bink;
      }LIST_ENTRY,*PLIST_ENTRY;
      
    • 从上述结构体可以看出,_LIST_ENTRY结构体提供双向链表机制。链表中保存的是_LDR_DATA_TABLE_ENTRY结构体的信息

    • 每个加载到进程中的DLL模块都对应一个_LDR_DATA_TABLE_ENTRY结构体,这些结构体相互链接,最终形成了_LIST_ENTRY双向链表。_PEB_LDR_DATA结构体中存在3种_LIST_ENTRY双向链表,也就是说,存在多个_LDR_DATA_TABLE_ENTRY结构体,并且有三种链接方法可以将它们链接起来。
      结构体如下:

    
    typedef struct _LDR_DATA_TABLE_ENTRY
    {
         LIST_ENTRY InLoadOrderLinks;
         LIST_ENTRY InMemoryOrderLinks;
         LIST_ENTRY InInitializationOrderLinks;
         PVOID DllBase;
         PVOID EntryPoint;
         ULONG SizeOfImage;
         UNICODE_STRING FullDllName;
         UNICODE_STRING BaseDllName;
         ULONG Flags;
         WORD LoadCount;
         WORD TlsIndex;
         union
         {
              LIST_ENTRY HashLinks;
              struct
              {
                   PVOID SectionPointer;
                   ULONG CheckSum;
              };
         };
         union
         {
              ULONG TimeDateStamp;
              PVOID LoadedImports;
         };
         _ACTIVATION_CONTEXT * EntryPointActivationContext;
         PVOID PatchInformation;
         LIST_ENTRY ForwarderLinks;
         LIST_ENTRY ServiceTagLinks;
         LIST_ENTRY StaticLinks;
    } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
    
    • 模块初始化链表 InInitializationOrderModuleList 中按顺序存放着 PE 装入运行时初始化模块的信息

    正文

    • 所有 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)至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址
    - 如下图所示:
    在这里插入图片描述

    • 导出表偏移 0x1C 处的指针指向存储导出函数偏移地址(RVA)的列表。
    • 导出表偏移 0x20 处的指针指向存储导出函数函数名的列表。
    • 函数的 RVA 地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的 RVA。
    • 获得 RVA 后,再加上前边已经得到的动态链接库的加载基址,就获得了所需 API 此刻在内存中的虚拟地址,这个地址就是我们最终在 shellcode 中调用时需要的地址。
    • 按照上面的方法,我们已经可以获得 kernel32.dll 中的任意函数
    • 类似地,我们已经具备了定位 ws2_32.dll 中的 winsock 函数来编写一个能够获得远程 shell 的真正的 shellcode 了。
    • 在摸透了 kernel32.dll 中的所有导出函数之后,结合使用其中的两个函数 LoadLibrary()和 GetProcAddress(),有时可以让定位所需其他 API 的工作变得更加容易。

    本文参考:0day安全,以及众多博客

  • 相关阅读:
    关于 Vue
    HTTP 知识点
    JS 的一些原生属性
    JS知识点-2 通信类
    原生JS知识点
    CSS知识点
    HTML知识点
    关于在Ajax中使用pushstate
    JavaScript 中的 This
    观察者模式
  • 原文地址:https://www.cnblogs.com/l0nmar/p/12553840.html
Copyright © 2020-2023  润新知