前言

针对一个已经学习了Linux Shellcode开发,并开始在Windows上尝试的研究人员来说,这一过程可能要比想象的更加艰难。Windows内核与Linux完全不同。尽管如此,但Linux内核要比Windows更容易理解,原因在于其开源的特性,并且与Windows相比,Linux只具有相当少的功能。另一方面,Windows在过去几年中进行了重大的改进,由于这一改进,新版本与老版本相比已经发生了许多变化。在本文中,我们将专注于对Windows 10 x86进行分析,但其他旧版本可能与之相比没有太多的不同。目前,已经有很多关于PEB LDR的博客文章,但我们还没有看到有任何文章中展现了完整的逻辑,并阐述其本质原因的。大多数研究人员只是通过WinDBG进行分析,并要求读者具备一定程度的后端基础。我撰写这篇文章的主要原因,是希望从C语言转到ASM,并希望我们能共同了解在ASM x86中进行Shellcode开发时,后端的工作原理。

.data段

在我们开始处理Shellcode部分之前,我建议首先应该理解内存是如何工作的,因为我们即将做的一切操作都是在内存中。如果我们已经了解像LPWSTR、LPSTR这样的Windows数据类型,那么无疑是一个好消息,因为我们必须要知道:

标准的C语言并不等同于Windows C编程

接下来,唯一需要重点掌握的,就是基本的Assembly x86。默认情况下,除了系统调用或API调用之外,ASM在Linux或Windows中是相同的。因此,了解寄存器的工作原理就显得非常重要。

最重要的是,我们应该了解如何对二进制文件进行反汇编。我主要使用x32dbg和WinDBG x86。我会同时使用这两个工具进行调试,因为有一些我们不能在x32dbg中完成的事情,在WinDBG x86中是可以的,反之亦然。因此,我们将不断切换使用这两个工具。

.text段

在我们开始使用Shellcode之前,理解其在较低级别的工作方式,这一点非常重要。我们首先将从一个非常简单的例子开始,找到系统的当前主机名。我们来看看下面是使用C语言编写的Windows API示例:

在上图中,我创建了两个变量,分别是compName和compNameSize。这些将是提供给函数GetComputerNameA的参数。请记住,GetComputerNameA和GetComputerNameW有两个相似的函数。W代表宽Unicode字符,而A代表ANSI CHAR字符串。我们将在整个博客系列中使用ANSI。下面是MSDN对GetComputerNameA函数的说明:

BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD nSize);

上面的代码表示,GetComputerNameA接受LPSTR,表示长指针字符串,而LPDWORD则表示长指针双字。一个字的大小是16位,因此DWORD在所有平台上都是32位。现在,如果使用g++编译上述程序,我们将看到如下内容:

现在在这里,在程序的最开始,有#include ,这也就意味着Windows库将被引入到代码中,它应该在这里动态链接默认依赖项。但是,我们不能对ASM进行相同的操作。在ASM的场景中,我们需要动态地找到函数GetComputerNameA所在的地址,在堆栈上加载参数,并调用具有函数指针的寄存器。我们要知道的一件重要事情是,Windows的大多数功能,都是通过三个主要DLL访问的:NTDLL.DLL、Kernel32.DLL和Kernelbase.DLL。因此,无论任何时间执行任何二进制文件,这些都是始终要加载的必要DLL。为了加载函数GetComputerNameA,我们必须找到这个函数所在的DLL,并在那里找到它的基址。接下来,我们在x32dbg上尝试加载任何x86二进制文件,看看能得到什么。我将加载我们编译的上述exe文件,但实际上,我们可以加载任何随机的32位可执行文件,因为我们只会浏览上面提到的那些DLL。使用x32dbg打开exe文件,并导航到Log部分,可以看到加载了这三个DLL,以及其特定的地址:

接下来,我们将导航到突出显示的Symbols部分,可以看到加载的不同DLL的名称。在这里,我们可以浏览DLL,并查看它们提供的所有功能。

现在,如果我们在搜索框中搜索函数GetComputerNameA,它将显示Kernel32.DLL加载该函数。此外,还将打印出函数所在的地址0x74F69AC0。在理论和实际测试中,这一点都能够很好地展现。接下来,让我们通过C编程然后通过ASM来完成,我们要执行的步骤如下:

1. 使用函数LoadLibraryA WinAPI在内存中加载Kernel32.dll;

2. 使用GetProcAddress在Kernel32.dll中找到函数GetComputerNameA的地址;

3. 将GetProcAddress返回值类型转换为接受2个参数的WinAPI函数(因为GetComputerNameA接受2个参数);

4. 为ComputerName及其Length创建缓冲区。

将Address作为函数指针来执行。

访问LoadLibraryA的MSDN页面,可以发现它返回一个HMODULE,这意味着它将一个句柄返回到一个被加载的模块。因此,我们创建了一个变量hmod_libname。类似地,GetProcAddress返回从DLL加载的函数的地址。我们需要将GetProcAddress返回的地址类型转换为GetComputerNameA函数,以使其能够正常工作。为此,我们创建了一个typedef,它基本上复制了函数GetComputerNameA的结构。在上图中,我们加载库Kernel32.dll,并使用GetProcAddress查找函数GetComputerNameA的基址,将地址存储在GetComputerNameProc中。最后,我们创建两个变量CompName和CompNameSize,并使用(*GetComputerNameProc)作为函数指针,执行存储在GetComputerNameProc中的地址,并为其提供所需的变量。上面的代码中,还打印了函数GetComputerNameA的地址。我们尝试对其进行编译,看看结果如何:

不错!地址0x74F69AC0与上面用x32dbg调试时发现的地址一致。

_start

我们接下来进入到有趣的部分。所有DLL及其函数的地址在重新启动时都会发生变化,并且在每个其他系统中都会有所不同。这就是我们无法对ASM代码中的任何地址进行硬编码的原因。但是,主要问题仍然存在,那就是我们如何找到kernel32.dll自身的地址?

我在一开始说过,每个exe都加载了Kernel32.dll、NTDLL.DLL和Kernelbase.dll。事实上,这些DLL是操作系统中非常重要的一部分,每次在执行任何操作时,都会加载这些DLL。因此,这些DLL到内存中的加载顺序总是相同的。然而,这可能因操作系统而异。这就意味着,在Windows XP与Windows 10之间可能有所不同,但所有Windows 10中的加载顺序将保持不变。

所以,我们在继续下一步之前,需要完成下面的工作:

1. 找到Kernel32.dll的加载顺序;

2. 找到Kernel32.dll的地址;

3. 找到GetComputerNameA的地址;

4. 在栈上加载GetComputerNameA的参数;

5. 调用GetComputerNameA函数指针。

可能听起来很容易?我们来实际尝试一下。

查找kernel32.dll的地址并不简单。当我们执行任何exe时,在操作系统中首先创建的就是TEB(线程环境块)和PEB(进程环境块)。

我们的主要关注点在于PEB结构(称为LDR),因为这是与进程相关的所有信息都被加载的地方。从流程参数到流程ID的所有内容都存储在这个位置。在PEB中,有一个名为PEB_LDR_DATA的结构,它包含三个关键部分。这些被称为链接列表(Linked Lists)。

1. InLoadOrderModuleList - 加载模块(exe或dll)的顺序;

2. InMemoryOrderModuleList - 模块(exe或dll)存储在内存中的顺序;

3. InInitializationOrderModuleList - 在进程环境块中初始化模块(exe或dll)的顺序。

在链表中加载模块的顺序是固定的。这意味着,我们如果能够在上面的列表中找到kernel32.dll的顺序,就可以搜索kernel32.dll的地址,并继续进行。现在,我们启动WinDBG x86。如果各位还没有安装WinDBG及其依赖项,你可以在SLAER上找到一篇关于WinDBG的文章。一旦安装WinDBG之后,就可以像我们之前那样打开任意的exe文件。

在WinDBG中加载exe文件后,会显示一些输出。限制,我们将忽略输出内容,并在下面的命令提示符中输入.cls以清除屏幕并重新开始。现在,我们在命令提示符下输入!peb,看看在这里能够得到什么:

如大家所见,我们得到了LDR(PEB结构)的地址,即779E0C40。这非常重要,因为我们要使用该地址来计算前进的地址。接下来,我们输入命令dt nt!_TEB,以查找PEB结构的偏移量。

如我们所见,_PEB位于偏移量0x030的位置。以类似的方式,我们可以使用dt nt!_PEB查看_PEB结构的内容。

_PEB_LDR_DATA的偏移量为0x00c。接下来,我们尝试查找_PEB_LDR_DATA结构中的内容。我们可以用类似的方式实现这一点:

dt nt!_PEB_LDR_DATA

在这里,我们可以看到InLoadOrderModuleList位于偏移量0x00c处,InMemoryOrderModuleList位于偏移量0x014处,InInitializationOrderModuleList位于偏移量0x01c处。此外,如果要查看每个列表所在的地址,可以使用我们此前找到的地址779E0C40(LDR的地址)以及命令dt nt!_PEB_LDR_DATA 779E0C40。这将向我们显示链接列表的相应起始地址和结束地址,如下所示:

有一个地方,可能会被一些人误解,就是上图中展示出InMemoryOrderModuleList的类型为_LIST_ENTRY,但在MSDN上已经另有说明:

因此,MSDN声明它是LDR_DATA_TABLE_ENTRY类型而不是_LIST_ENTRY类型。我们尝试查看结构中加载的模块,并指定该结构的起始地址为0x7041e8,以便可以看到加载的模块的基址。需要注意的是,0x7041e8是此结构的地址,因此第一个条目将比此地址少8个字节。因此,我们的命令是:

dt nt!_LDR_DATA_TABLE_ENTRY 0x7041e8-8

第一个出现的BaseDllName是gethost.exe。这就是我之前执行的exe文件。此外,我们可以看到现在InMemoryOrderLinks的地址是0x7040e0。偏移量0x018处的DllBase中包含BaseDllName的基址。现在,我们下一个加载的模块必须距离0x7040e0有8个字节,也就是0x7040e0-8。

dt nt!_LDR_DATA_TABLE_ENTRY 0x7040e0-8

所以,我们的第二个模块是ntdll.dll,它的地址是0x778c000,下一个模块位于0x704690之后的8个字节。所以,我们的下一个命令是:

dt nt!_LDR_DATA_TABLE_ENTRY 0x704690-8

由此,就得到了第三个模块Kernel32.dll,其地址是0x74f50000,其偏移量时0x018。模块加载的顺序总是固定的,至少这适用于Windows 10、Windows 7、Windows 8(包括8.1)。因此,当我们编写ASM时,我们可以遍历整个PEB LDR结构体,并找到Kernel32.dll的地址,并将其加载到我们的Shellcode中。以类似的方式,我们还可以找到Kernelbase.dll的地址,这是第四个模块。

现在,我们总结一下需要进行的工作:

1. PEB位于距离文件段寄存器偏移量为0x030的位置;

2. LDR位于偏移量为PEB + 0x00C的位置;

3. InMemoryOrderModuleList位于偏移量LDR + 0x014的位置;

4. 第一个模块入口是exe本身;

5. 第二个模块入口是ntdll.dll;

6. 第三个模块入口是kernel32.dll;

7. 第四个模块入口是Kernelbase.dll。

我们现在最感兴趣的,就是Kernel32.dll。每次加载DLL时,地址都将存储在DllBase的偏移量0x018的位置。我们链接列表的起始地址将存储在InMemoryOrderLinks的偏移量中,即0x008。因此,偏移量之间的关系将是 DllBase – InMemoryOrderLinks = 0x018 – 0x008 = 0x10。因此,Kernel32.dll的偏移量将是LDR + 0x10。更详细的理解,可以在下图中看到,这张图是我从这里偷过来的。

现在,如果我们在ASM中做同样的工作,将会是如下所示:

我们使用NASM来编译,并在x32dbg中加载它。大家可以从这里下载NASM。

实际上,一旦我们的最后一条指令被运行,就应该在EAX寄存器中加载Kernel32.dll的地址。我们来看看它在x32dbg中看起来是否相同。

如我们所见,在最后一条指令之后,加载到EAX中的地址与我们在下面使用lm命令在WinDBG中看到的地址相同,都是74F50000,这是Kernel32.dll的地址。

现在,我们已经有了Kernel32.dll的地址,下一步就是使用LoadLibraryA找到GetComputerNameA的地址,并调用该函数。我们将在下一篇文章中重点讨论这一问题,完善ASM代码,实现获取计算机名称并将其打印在屏幕上,然后打印到Shellcode部分。

c++ dll返回容器_Windows x86 Shellcode开发:寻找Kernel32.dll地址相关推荐

  1. c++ dll返回容器_OPENCV与LABVIEW的结合(DLL调用)

    第一次观看我文章的朋友,可以关注,点赞,转发一下,每天分享各种干货技术和程序猿趣事 前言 为什么会有把二者结合这个想法,主要是在接触过这两种工具后,发现它们对图像处理有自己独特的优势,但也有自己的缺点 ...

  2. 寻找kernel32.dll的地址

    为了寻找kernel32.dll的地址,可以直接输出,也可以通过TEB,PEB等查找. 寻找TEB: dt _TEB nt!_TEB +0x000 NtTib : _NT_TIB +0x01c Env ...

  3. Windows Dll Injection、Process Injection、API Hook、DLL后门/恶意程序入侵技术

    catalogue 1. 引言 2. 使用注册表注入DLL 3. 使用Windows挂钩来注入DLL 4. 使用远程线程来注入DLL 5. 使用木马DLL来注入DLL 6. 把DLL作为调试器来注入 ...

  4. 重定位(搜索KERNEL32.DLL得到API地址)

    1 ;-------------------------------- 2 ;动态加载功能实现 3 ;moriarty 4 ;2012/04/13 5 ;----------------------- ...

  5. c++ 跳转到上级目录_Windows漏洞利用开发 第4部分:使用跳转定位Shellcode 模块

    概观 在第2和第3部分中,我们构建并改进了ASX To MP3转换器的一个漏洞利用.尽管它存在缺陷,但就漏洞而言,它非常简单直接-- 直接利用指向我们shellcode的寄存器从而直接跳转到EIP覆盖 ...

  6. 【逆向知识】开发WinDBG扩展DLL

    如何开发WinDbg扩展DLL WinDbg扩展DLL是一组导出的回调函数,用于实现用户定义的命令.以便从内存转储中提取特定的信息.扩展dll由调试器引擎加载,可以在执行用户模式或内核模式调试时提供自 ...

  7. Windows Shellcode开发[2]

    0 前言 在这部分我们将了解正确编写Windows系统shellcode所需的信息:进程环境块和PE文件格式.上一部分在这里:Windows Shellcode开发[1] 1 进程环境块 在Windo ...

  8. Windows Shellcode开发[3]

    0 前言及编写shellcode准备工作 在Windows Shellcode开发介绍的最后一部分,我们将编写一个简单的改变鼠标左右键的shellcode.我们需要用到两个函数:SwapMouseBu ...

  9. 安装Windows Lua5.1 x86|x64 开发环境(Windows Lua LuaRocks msvc)

    安装Windows Lua5.1 x86|x64 开发环境 1. 背景 1.1 二进制版本问题 1.2 luajit的兼容性 2.兼容安装Lua5.1 x86 环境 3.编译安装Lua5.1 x64环 ...

最新文章

  1. 及cp含义_当我们谈论CP时,我们在谈论什么?
  2. 自动化运维工具之puppet简单实用
  3. sql server 关联left join条件on和where条件的区别
  4. [JZOJ5866]【NOIP2018模拟9.13】指引
  5. jboss drools_JBoss Drools –入门
  6. canvas绘制线条1像素的问题
  7. abstract class和interface
  8. 数据库 chapter 10 数据库恢复技术
  9. 识别图片验证码内容 -- ddddocr识别
  10. 服务器安装linux系统教程
  11. EBS R12.2 ADOP (R12.2 AD Online Patching) - 3
  12. 关于log4j:WARN No appenders could be found for logger (org.apache.hadoop.metrics2.li)的问题
  13. ios自建服务器降级,iOS14降级操作步骤 iOS14怎么降级到iOS13
  14. Starvis星光全彩摄像机技术
  15. 11,你听说过vue过渡动画了嘛?没有吧? 众里寻他千百度,百度不一定全面?
  16. Mysql自定义函数:身份证号码的真实性判定
  17. 计算广告——读书笔记(二)
  18. 【鸿蒙】HarmonyOS认证学习资料整理
  19. 朋友去华为面试,轻松拿到26K的Offer,羡慕了......
  20. android 地图导航开发思路,基础功能-导航组件-开发指南-Android 导航SDK | 高德地图API...

热门文章

  1. linux创建逻辑目录,Linux创建逻辑卷
  2. java mvc web_JavaWeb MVC
  3. java抽象方法实例_Java 抽象类和抽象方法实例
  4. Apache Spark开发介绍
  5. Linux系统LVM增加新硬盘实现根文件系统扩容
  6. 整理了一个带语法高亮显示,及到处html功能的richtextbox控件
  7. python创建画布与子图_python实现在一个画布上画多个子图
  8. flask 写数据mysql_flask 创建数据提交到mysql中的方式
  9. python获取局域网在线主机_pythond的icmp广播报获取局域网主机IP
  10. [k8s]kubelet重启后无法启动