软件漏洞-使用TEB PEB查找核心模块

之前写的shellcode的函数地址都是固定的,比如调用MessageBox的时候是一个具体的地址,这样其实不太好,所以这里有一个别的方式来处理

Windows编程中的重点dll文件

Kernel32.dll:内存操作相关的都在里面,像什么线程进程

User32.dll:窗口程序专用的dll,里面封装了大量窗口操作的dll

ntdll.dll:是ring0的大门,无论是kernel还是User最终都会调用ntdll.dll

介绍

该结构体中包含进程中运行线程的各种信息,每个线程都对应一个TEB结构体。 不同OS中TEB结构的形态略微不同。

TEB 线程环境块

其实就是一个结构体,这个结构体中保存了线程中的各种信息

结构体中有非常多的成员,其中用户模式调试中起着重要作用的成员有两个:

+0 NtTib : _NT_TIB
...
+0X30 ProcessEnvironmentBlock : Ptr32_PEB

TEB的第一个字段_NT_TIB

typedef struct _NT_TIB {
 struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
 //这个结构体是用于操作系统的SEH(Windows的异常处理机制,大量用出反调试)
 PVOID StackBase;
 PVOID StackLimit;
 PVOID SubSystemTib;
 union {
  PVOID FiberData;
  DWORD Version;
 };
 PVOID ArbitraryUserPointer;
 struct _NT_TIB *Self;//指向自己的指针
};

+0x30 字段PEB

Process Envirorment Block

进程环境块,存放进程相关信息,我们需要的东西在这个东西里面,但是我们还是需要先拿到TEB线程环境块,才能拿到进程PEB进程环境块

PEB的访问

分析API NtCurrentTeb();

该API会返回一个TEB结构体指针

反汇编该API

 

 

通过这里可以得到fs这个寄存器保存的是TEB的首地址,然后因为TEB偏移30个字节就可以得到PEB,所以这里的地址应该就是FS:[0X30]==PEB,这里是相对基址寻址

PEB

PEB有很多,这里拿最有用的

 

 

 

Ldr是一个指针

_PEB_LDR_DATA

 

 

当dll文件加载后ldr会存放模块信息,通过_LIST_ENTRY双向链表可以遍历所有模块

 

 

InitalizationOrderMoudleList;这一个结构体是顺序不会改变的初始化dll,用起来比较方便,初始化排序的dll文件顺序是ntdll,kernel32.dll或者kernerbase.dll(kernel32.dll和kernerbase.dll是差不多的,有的都有拿到谁都一样)

但是呢,链表肯定是有指针域和作用域的,所以这里其实,最外层还有个结构体,里面包含了这个_LIST_ENTRY和一些作用域,这个Windows没有开源,但是是大神逆向出来的结果

它的上层结构体呢是这个

 

 

DLLBase

DLLBase也就是模块基址,也就是GetModuleHandle的返回地址,根据这个,也就是可以得到一个PE文件的首地址,那么就可以通过这个得到他的导出表,从而来定位函数的地址

 

总体访问逻辑

fs这个寄存器基本上不会改变是属于非常内核的东西,所以可以这样来处理

mov esi,fs:[0x30];//因为fs存放的是TEB的首地址,这里是PEB的首地址

mov esi,[esi+0xc];//把ldr地址给esi

mov esi,[esi+0x1c]//这里得到的是初始化排序的dll的链表结构体的//第一个也就是InitalizationOrderMoudleList也就是初始化排序的dll

mov esi,[esi];//通过链表指针指向了下一个DLL文件信息

 

总结

因为要调用的函数的地址是有可能变化的,所以最好的处理方式就是让每次加载的地址都是对应的地址,由于这些函数是用dll来动态连接的,所以只要拿到对应的dll就可以了

这些信息首先要通过TEB线程环境块,然后这个线程环境块的首地址保存在fs:[0x0]里面,然后通过偏移访问得到PEB的首地址也就是进程环境块的首地址,进程环境块里面又有一个ldr字段是用来存放如何排序的dll的方式的首地址的dll的,dll通过双向链表来连接,返回的是经过排序后的第一个dll,然后一般用的是InitializationOrderMoudleList这个来访问,这个顺序第一个是ntdll,第二个是kernel32.dll或者kernerbase.dll,但是这个定义排序DLL的结构体是一个双向链表他是另外一个大结构体里的字段,而这个大结构体就是存放着dll信息的结构体,而这个双向链表呢就是指向的是大结构体的首地址,所以这里直接用拿到的排序的链表的首地址,就是下一个dll信息的结构体的地址,最后根据dll信息的结构体的字段,首地址再偏移八个地址就可以得到DLLBase,也就是得到了模块的地址(也就是GetMdouleHandle的返回地址)通过这个可以通过PE文件结构解析来得到导入表然后得到具体的函数的地址,就可以来动态获得地址了。

mov esi,fs:[0x30];//因为fs存放的是TEB的首地址,这里是PEB的首地址

mov esi,[esi+0xc];//把ldr地址给esi

mov esi,[esi+0x1c]
//这里得到的是初始化排序的dll的链表结构体的
//第一个也就是InitalizationOrderMoudleList
//也就是初始化排序的dll

mov esi,[esi];//通过链表指针指向了下一个DLL文件信息

引申用汇编语言实现字符串比较函数

因为在通过导入表拿到具体函数名称的地址的时候,肯定会有一个循环比对,来查看是否有对应的函数名称的的名称,很正常我们会想到一些字符串比对函数,比如strcmp,但是必须得用汇编来实现,因为你不知道你要入侵的程序是什么样子的,所以这里引入用汇编语言来实现字符串比较函数

        Lea esi,[字符串A首地址] ;
Lea edi, [字符串B首地址];
Mov ecx, 循环次数;
Repe Cmpsb;
je Cmp_equal;//跳转相等
mov eax, 0;
Cmp_equal...;
mov eax, 1;