软件漏洞-使用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;