首页 > 其他分享 >PEB及其武器化

PEB及其武器化

时间:2024-02-18 18:33:33浏览次数:26  
标签:__ PPEB 及其 dll 链表 武器化 TEB PEB

本文首发:https://xz.aliyun.com/t/13556

在Windows操作系统中,PEB是指" Process Environment Block ",它是一个数据结构,用于存储有关进程的信息,每个进程都有一个对应的 PEB 结构体。PEB提供了许多关于进程状态和环境的信息,它是用户模式和内核模式之间的一个关键接口。
我们利用 PEB 可以完成很多事情,比如说动态获取 api,进程伪装,反调试等等。

TEB

在了解 PEB 之前我们要先了解什么是 TEB,TEB指的是线程环境块" Thread Environment Block ",用于存储线程状态信息和线程所需的各种数据。每个线程都有一个对应的TEB结构体,并且 TEB 结构的其中一个成员就是 PEB。

分析过程

接下来我们以 32 位为例进行 TEB 和 PEB 的分析,关于 64 位的结论会在下面一并给出。
我们可以通过 https://www.vergiliusproject.com/ 这个网站来分析,也可以选择使用 windbg 来进行分析。

理论学习

我们首先在 vp 中找到 TEB 结构
image.png
下图可以看到,TEB 偏移 0x30 就可以得到 PEB
image.png
所以我们得到一个结论

pTEB->0x30 = PEB

我们继续看 PEB 结构,这里我们先关注 0x0c 处的 Ldr,里面存储着有关模块加载的信息。
image.png
点进去看看,又看到三个结构体
image.png这三个结构体的结构都是一样的,都是双向链表,不同之处是加载的模块的顺序不同
image.png
第一个成员 Flink 指向下一个节点,Blink 指向上一个节点,所以这是一个双向链表,当我们从_PEB_LDR_DATA 结构中取到 InInitializationOrderModuleList 结构时,这个结构中的 Flink 指向真正的模块链表,这个真正的链表的每个成员都是一个 LDR_DATA_TABLE_ENTRY 结构。
之前的 _PEB_LDR_DATA 只是一个入口,这个结构只有一个,它不是链表节点,真正的链表节点结构如下图:
image.png
他们之间的对应关系可以由下图来表示,如果学习过链表的概念还是挺好理解的(图片来自https://bbs.kanxue.com/thread-266678.htm
image.png
简化版如下:
image.png
可以看到这是一个以PEB_LDR_DATA为起点的一个闭合环形双向链表。
每个_LDR_DATA_TABLE_ENTRY节点结构中偏移为0x30处的成员为dllName,偏移为0x18处的成员为DllBase。
通过遍历链表,比较dllName字符串内容可以找到目标模块的所属节点。
通过节点成员DllBase可以定位该模块的DOS头起始处。
通过对PE结构的解析可以搜索导出表,从而可以取到指定的导出函数地址。

动手调试

我们接下来利用 windbg 手动走一遍定位 dll 过程。
我们先启动一个程序,我这里启动的是 notepad,然后在 windbg 中附加上去。
image.png
附加之后,使用dt _TEB @$teb 可以查看当前线程 TEB 相关的信息,并且得到 PEB 相关的信息。
image.png
这里直接点过来就可以得到 PEB 相关的信息
image.png
这里注意到上图最下方的 Ldr,点一下就可以得到 Ldr 相关的信息
image.png
我们以第一个链表进行分析,直接点进去,得到 Flink 和 Blink
image.png
同样也可以看到内存地址,我们接下来直接去看内存地址 dt 0x1b678806dc0 _LDR_DATA_TABLE_ENTRY
image.png
这里第一个 FullDllName 是 notepad 的路径,我们接着看下一个节点,直接点击下图红框位置
image.png
点进去得到如下:
image.png
然后用该命令dt 0x1b678806c70 _LDR_DATA_TABLE_ENTRY看下一个节点
image.png
已经得到 ntdll.dll

获取 PEB 的几种方式

还有一个小问题需要解决,那就是我们如何获得TEB 的内存地址呢,在上面分析时我们直接用的是 windbg 中的命令,并没有提到这个问题。
其实,在 x86 下 TEB 结构指针存储在 fs 寄存器中,在 x64 下 TEB 结构指针存储在 gs 寄存器中。另外我们在前面提到过的 x64 和 x86 的区别主要是 TEB 到 PEB 的偏移量不同,在 x64 下该偏移量位 0x60。
也有文章说 fs:[0x18] 是 PEB,接下来我们来看一下该说法原理:
这里我们关注第一个结构体,TIB(线程信息块)
image.png
点进去可以看到里面的一些成员,标出来的 self 就是指向自身的指针,也就是指向 0x0 的ExceptionList
image.png
0x0+0x18=0x18,所以说 fs:[0x18] 是 PEB。
下面简单介绍一下获取 PEB 的几种方式

汇编调用

在 x86 下可以直接内联汇编

__asm
{
    mov eax, dword ptr fs : [00000030h]
    mov peb, eax
}

在 x64 下则需要一个单独的 asm 文件并且进行一些配置,可以参考博客:vs2022 x64 C/C++和汇编混编_vs 嵌入汇编-CSDN博客,代码 demo 如下:

.code
GetPEB PROC
mov rax, gs:[30h] ; TEB from gs in 64 bit only
mov rax, [rax+60h] ; PEB
ret
GetPEB ENDP
end

__readfsdword 与 __readgsqword

可以直接通过 __readfsdword(0x30) 或者 __readgsqword(0x60) 来获取 PEB,demo 如下:

#include <windows.h>
#include <winternl.h>

#ifndef _WIN64
    PPEB pPeb = (PPEB)__readfsdword(0x30);
#else
    PPEB pPeb = (PPEB)__readgsqword(0x60);
#endif 

NtQueryInformationProcess

NtQueryInformationProcess 是一个内核函数,用来查看进程信息,其结构体如下:

__kernel_entry NTSTATUS NtQueryInformationProcess(
  [in]            HANDLE           ProcessHandle,
  [in]            PROCESSINFOCLASS ProcessInformationClass,
  [out]           PVOID            ProcessInformation,
  [in]            ULONG            ProcessInformationLength,
  [out, optional] PULONG           ReturnLength
);

它的第二个参数可以是一个PROCESS_BASIC_INFORMATION的结构体:

typedef struct _PROCESS_BASIC_INFORMATION {
    NTSTATUS ExitStatus;
    PPEB PebBaseAddress;
    ULONG_PTR AffinityMask;
    KPRIORITY BasePriority;
    ULONG_PTR UniqueProcessId;
    ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;

第二个值**PebBaseAddress **指向 PEB 结构。据此我们可以有如下 demo:

#include <windows.h>
#include <winternl.h>

// Define the PROCESS_BASIC_INFORMATION structure
typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

// Define the NtQueryInformationProcess function
typedef NTSTATUS (NTAPI *PNtQueryInformationProcess)(
    HANDLE ProcessHandle,
    PROCESSINFOCLASS ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength
);

int main() {
    // Open the current process
    HANDLE hProcess = GetCurrentProcess();

    // Define a buffer to hold the information
    PROCESS_BASIC_INFORMATION pbi;

    // Load NtQueryInformationProcess dynamically
    HMODULE hNtDll = GetModuleHandle(L"ntdll.dll");
    PNtQueryInformationProcess pNtQueryInformationProcess = 
        (PNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");

    // Call NtQueryInformationProcess to get the PEB address
    NTSTATUS status = pNtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);

    if (NT_SUCCESS(status)) {
        // Access PEB address from the structure
        PPEB pebAddress = pbi.PebBaseAddress;
        wprintf(L"PEB Address: %p\n", pebAddress);
    } else {
        wprintf(L"Error: NtQueryInformationProcess failed with status 0x%X\n", status);
    }

    // Close the process handle
    CloseHandle(hProcess);

    return 0;
}

小结:

通过上述学习,我们已经成功通过 PEB 得到了ntdll.dll,当然也可以获取kerndl32.dll,下面是三个双向链表加载 dll 的顺序:

  • InLoadOrderModuleList 模块加载顺序

notepad.exe ntdll.dll kernel32.dll kernelbase.dll

  • InMemoryOrderModuleList 模块在内存加载顺序

notepad.exe ntdll.dll kernel32.dll kernelbase.dll

  • InInitializationOrderLinks 模块初始化装载顺序

ntdll.dll kernelbase.dll kernel32.dll

还有一张神图:

武器化

接下来介绍 PEB 在免杀中的一些应用。

动态获取 api

我们通过上述的学习已经可以在 windbg 中获取kerndl32.dll 的地址,接下来要做的就是遍历导出表找到所需要的函数,这里需要一些 PE 格式的相关知识,这里不再赘述。
我们的汇编代码如下:
image.png
这是 x64 下的,所以偏移量和 x86 下不同,建议大家对照 vp 再去看一遍,其中 mov rax,[rax]是为了获取下一个节点。
下面是我们验证的代码,比较一下我们获取的函数地址和 GetModuleHandle 函数获取的地址有区别吗,另外不要忘记导出函数。
image.png
可以看到结果是没有问题的
image.png
接下来要做的就是遍历导出表了

PVOID GetAddressFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName)
{
	PVOID get_address = 0;
	DWORD ulFunctionIndex = 0;
	// DOS头
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
	// NT 头
	PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
	// 导出表
	PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
	//导出表有名字的函数的个数
	ULONG NumberOfFunctions = pExportTable->NumberOfFunctions;
	//导出函数名称地址表
	PULONG AddressOfNamesTable = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
	PCHAR Name = NULL;
	//循环导出函数名称地址表
	for (ULONG i = 0; i < NumberOfFunctions; i++) {
		Name = (PCHAR)((PUCHAR)pDosHeader + AddressOfNamesTable[i]);
		//如果找到了
		if (0 == _strnicmp(pszFunctionName, Name, strlen(pszFunctionName))) {
			//找到对应序号
			USHORT ordinal = *(USHORT*)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
			//根据序号找到RVA
			ULONG FuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * ordinal);
			get_address = (PVOID)((PUCHAR)pDosHeader + FuncAddr);
			return get_address;
		}
	}
	return 0;

}

然后用一小段代码进行验证

	typedef VOID(WINAPI* pSleep)(DWORD);
	pSleep mySleep = (pSleep)GetAddressFromExportTable(GetKernel32(), (PCHAR)"Sleep");
	mySleep(5000);

或者将获取的地址和 GetProcAddress 获取的函数的地址进行比较

	pVirtualAlloc alloc = (pVirtualAlloc)GetAddressFromExportTable(getKernel32(), (PCHAR)"Sleep");
	pVirtualAlloc a = (pVirtualAlloc)GetProcAddress(GetModuleHandle(L"kernel32"), "Sleep");

由下图可以看到是一样的,所以说实现的并没有问题
image.png

进程伪装

我们现在随便启动一个 notepad 的进程,然后用 ProcessExplorer 去观察
image.png
其实我们要做的很简单,就是在 peb 中找到对应的数据结构,然后对其进行修改就实现了我们的进程伪装。
首先是进程映像路径和命令行。
还是在上面 vp 的网站上
image.png
我们找到 ProcessParameters ,然后点进去就可以发现 ImagePathName 和 CommandLine 两项,就对应着进程映像路径和命令行,
image.png
我们红框框了 3 项,还有一个进程当前目录,我们要用 SetCurrentDirectoryW 这个 api 来进行修改,下面是一个简单的 demo

#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <winternl.h>

int main()
{

	// 获取TEB中PEB指针的偏移
#ifdef _M_IX86 
	PPEB PEB = (PPEB)__readfsdword(0x30);
#else
	PPEB PEB = (PPEB)__readgsqword(0x60);
#endif

	// 改变进程当前目录  注意:路径 必须是存在的,否则会报错
	int result = SetCurrentDirectoryW(L"C:\\Users\\James\\");

	//改变进程映像路径和命令行
	WCHAR path[] = L"c:\\windows\\system32\\notepad.exe\0";
	PEB->ProcessParameters->ImagePathName.Buffer = path;
	PEB->ProcessParameters->CommandLine.Buffer = path;

	getchar();

}

可以看到 ProcessExplorer 中恶意程序的图标已经变成记事本,点进去查看发现已经修改成功,实现了进程伪装。(但是有一点,我使用 Process Hacker2 去查看,并没有修改成功)
image.png
image.png

反调试

关于 PEB 反调试的内容,主要涉及的有

  • BeingDebugged :当有调试器附加的时候该位被置为1
  • NtGlobalFlag : 当有调试器附加的时候该位被置为 0x70
  • Heap Flags : 在PEB的ProcessHeap位指向_HEAP结构体,该结构体中有俩个字段( HeapFlags 和 ForceFlags )会受到调试器的影响,如果 HeapFlags 的值大于2,或 ForceFlags 的值大于0时,说明被调试。
  • 堆 Magic 标志 :当进程被调试器调试时该进程堆会被一些特殊的标志填充,这些特殊标记分别是0xABABABAB , 0xFEEEFEEE 。在调试模式下, NtGlobalFlag 的 HEAP_TAIL_CHECKING_ENABLED 标志将被默认设置,堆内存分配会在末尾追加 0xABABABAB 标志进行安全检查,如果NtGlobalFlag设置了HEAP_FREE_CHECKING_ENABLED 标志,那么当需要额外的字节来填充堆块尾部时, 就会使用 0xFEEEFEEE (或一部分) 来填充。

有一篇文章已经总结的很好了,demo 上面都有,这里就不再过多赘述了,地址:https://anti-debug.checkpoint.com/techniques/debug-flags.html#manual-checks-peb-beingdebugged-flag

标签:__,PPEB,及其,dll,链表,武器化,TEB,PEB
From: https://www.cnblogs.com/fdxsec/p/18019772

相关文章

  • JDK下载介绍及其选择
    1.JDK简介基本情况JDK是Java语言的软件开发工具包。JDK是整个Java开发的核心,它包括Java运行环境、Java基础类库和Java工具。版本情况按照维护情况分为non-LTS(短期)和LTS(长期)两种;对于短期支持版本(non-LTS)而言,Oracle只会提供6个月的支持维护;而对于长期支持......
  • 大数进阶(2)——反射($\Pi_2$及其上)
    前言\(\Pi_1\)反射看上去行为非常简单,强度也不高,那为什么要用这种奇怪的东西?真正上强度是从\(\Pi_2\)反射开始,随着强度的大幅度提升,序数的行为逐渐复杂,这需要对其行为更深刻的理解,也即对于数学基础的更高要求在进入\(\Pi_2\)反射之前,需要更多的基础来往后推进,否则只会一头雾水......
  • 在k8S中,Kubernetes RBAC及其特点(优势)是什么?
    在Kubernetes(k8S)中,Role-BasedAccessControl(RBAC)是一种强大的权限管理和访问控制机制。它允许集群管理员细粒度地控制用户、组或服务账户对KubernetesAPI资源的访问权限。特点和优势:细粒度授权:RBAC提供了一种基于角色的灵活授权模式,可以根据角色定义不同级别的API访问权限......
  • 微分积分及其运算法则成对列举
         ......
  • 目标检测 | Farthest Point Sampling 及其 CUDA 实现
    FarthestPointSampling及其CUDA实现目录FarthestPointSampling及其CUDA实现概述均匀随机采样FarthestPointSampling(待完成)FarthestPointSampling的并行版本(待完成)概述在深度学习中,在mesh模型(网格模型)上直接学习并预测是一个相当复杂的任务,一方面在于没有高效的......
  • SAT及其能解决的问题及其文献
      %SAT及其能解决的问题TheCDCLSATsolverisanimportanttoolforsolvinglargereal-worldproblems,andhasbeenwidelyusedinsoftwaredebugging,designverification,cryptography,artificialintelligenceandotherfields.Thepowerfulcapabiliti......
  • .net介绍以及其常见漏洞
    前言:本篇来学习.NET项目和其使用的c#的一些通用漏洞1.0.NET的基础介绍.NET是由微软(Microsoft)开发的一个用于构建各种应用程序的开发平台,包括Web应用程序、桌面应用程序、移动应用程序等。它是一个综合性的技术栈,提供了一系列的开发框架、工具和库,使得开发者能够使用多种编......
  • 各类eBPF程序的触发机制及其应用场景
    每一类eBPF程序都有哪些具体的类型,以及这些不同类型的程序都是由哪些事件触发执行的。1、跟踪类eBPF程序跟踪类eBPF程序主要用于从系统中提取跟踪信息,进而为监控、排错、性能优化等提供数据支撑。比如,我们前几讲中的HelloWorld示例就是一个BPF_PROG_TYPE_KPROBE类型的跟......
  • 中文数字的应用及其问题解决之道
    中文数字,也称汉字数字,是中文语言中表示数字的一种方式。它们不仅有着悠久的历史和文化背景,还在日常生活中发挥着重要的作用。本文将探讨中文数字的应用领域,并介绍它们如何解决实际问题。中文数字-阿拉伯数字转换|一个覆盖广泛主题工具的高效在线平台(amd794.com)https:/......
  • 在K8S中,K8S本身优势、适应场景及其特点有什么?
    Kubernetes(简称K8s)作为容器编排领域的事实标准,具有以下显著优势、适应场景及其特点:优势:微服务架构支持:Kubernetes非常适合部署和管理基于微服务的应用程序,每个服务可以独立运行在Pod中,并通过Service进行发现和通信。自动化部署与扩展:自动化的滚动更新、回滚以及水平扩展(HPA......