65.9K
CodeProject 正在变化。 阅读更多。
Home

不受限制的低级硬件编程和实验平台(NakedCPU)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (27投票s)

2012年8月9日

CPOL

27分钟阅读

viewsIcon

45867

downloadIcon

76

本文介绍了一个由两台计算机组成的实验平台,用于在不受任何操作系统限制的情况下进行硬件编程和研究。

NakedCPU platform

图1. NakedCPU 和主计算机通过串口连接。NakedCPU 不需要键盘或显示器。

前言

如今,硬件被厚厚的操作系统代码层所掩盖。然而,对我以及根据 Google 搜索结果显示,对许多其他人来说,能够接触并实验硬件将是一个非常令人兴奋的机会。我创建了一个由两台基于 Intel 的计算机通过 RS232 接口连接的探索平台。一台计算机(主计算机)运行 Windows,用于控制第二台计算机。第二台计算机(NakedCPU)基本上没有任何操作系统,因此可以用于真正的底层实验。我的平台曾刊登在《Circuit Cellar Magazine》第259、260期封面上。本文是原始出版物的改编版,可在我的网站上查阅。

引言

看来,PC 的实验仅限于借助大量库和技术开发高级代码软件,这些库和技术将硬件隐藏在层层代码之下。很少能对 PC 硬件进行有限的实验,但是必须安装允许访问硬件的驱动程序,因为操作系统自然不允许我们进行任何底层活动。遗憾的是,这些驱动程序本身就很神秘。可以肯定地说,硬件编程在 80 年代为许多计算机专业人士和爱好者所熟知。后来人们忘记了它,而技术却突飞猛进。在本文中,我尝试弥合时间的鸿沟,并基于最先进的技术和概念重燃对硬件编程的兴趣。俄罗斯有句谚语:“一切新事物实际上都是被遗忘已久的旧事物。”

本文是我对 Intel CPU、芯片组、I/O 控制器和其他基本 PC 设备从不受操作系统和驱动程序遮蔽的底层硬件编程角度感兴趣的结果。该项目的动机是接触那些具有好奇心并会欣赏直接实验 CPU、芯片组和其他硬件的可能性的人。在此,我介绍 NakedCPU:一个提供对硬件和 CPU 完全访问的设施,不受操作系统强加的任何限制。重要的是,处理器不会被 Linux、DOS 或 Windows 遮蔽,它将以其最有趣和最强大的模式运行——保护模式。在文本中,用户被称为探究者,因为 NakedCPU 是为研究人员(即忠实的极客)而非普通用户而设计的。

本文的另一个目标是为探究者提供一个导航硬件文档的路线图,否则这些文档令人困惑且难以查找。我不想重述文档,因为许多计算机概念和技术很快就会过时。然而,有了路线图,将更容易跟踪更新的技术和文档。

有趣的事实

让我们花点时间思考一下现代 Intel CPU 的一个变种,例如 Intel Core 2 Duo。令人印象深刻的是,这款处理器能够消耗高达 75A 的电流 [1]!此外,它不是一个简单的处理器:其文档由 5 卷组成,总页数约为 4200 页 [2]。Intel CPU 并非单独运行:它与芯片组(即图形和内存控制器集线器 (GMCH))接口。芯片组的另一端连接到 I/O 控制器集线器 (ICH)。有趣的是,这种安排类似于我们的大脑、脑干和脊髓组成的神经系统。GMCH 和 ICH 本身就是处理器,包含数百个配置和控制寄存器。GMCH 和 ICH 的文档超过 1400 页 [3, 4]。难怪操作系统会将实际硬件隐藏在厚厚的中间代码层之下!

NakedCPU 的工作原理

NakedCPU 是一个揭示 PC 硬件内部结构的实验平台。使用 NakedCPU 进行实验需要两台计算机(图 1)。一台是主计算机,运行 Windows 和 Visual Studio 软件,其任务是与我们和第二台计算机交互。第二台计算机(即 NakedCPU)通过 RS232 接口连接到主计算机。NakedCPU 计算机通过一小段启动代码(此处提供)启动,使其能够通过 RS232 与主计算机通信。启动后,NakedCPU 期望接收两个独立的字节包:一个是待执行的 Intel CPU 操作码流(即可执行文件),另一个是待处理的数据。可执行文件可以修改内存的任何部分、芯片组寄存器等,甚至覆盖启动代码。换句话说,自由由您掌控。

启动 NakedCPU

NakedCPU 离开某种启动代码将无法运行。启动时,我们必须完成两项任务:将 CPU 切换到保护模式,并开始监听串口以接收两个字节包:可执行文件和数据。为了向 NakedCPU 提供启动代码,最简单的方法是准备一个带有我们自己代码的可引导软盘。当然,也可以将此代码放入硬盘。启动代码(最多 512 字节)用汇编语言编写,并且必须存储在磁盘的扇区 0,即主引导记录 (MBR) 中。使用 MASM 等汇编语言编译器和链接器很困难,因为它们会根据特定的操作系统裁剪可执行文件。但是,有一个二进制编辑器 HexIt [5],它除其他功能外,还允许将汇编命令直接转换为二进制代码。使用此编辑器,创建了未来 MBR 的二进制文件。此文件的内容可以在附录中查看。“MBR 的剖析”提供了内容的详细分解。一个小工具“Firstsectwrite.exe”(参见附录)被编写用于将此文件传输到磁盘的扇区 0。尽管此工具的代码非常简单,但它值得关注。一个 Windows API 调用

CreateFile(TEXT("\\\\.\\A:")...)

打开与磁盘的原始通信,在本例中是软盘驱动器 A,以允许写入扇区 0。需要注意的是,此调用只有在管理员帐户下才能成功。本文中使用戴尔 Optiplex 760 计算机进行实验。它有一个通过 USB 连接的软盘驱动器,BIOS 启动选项允许从该驱动器启动计算机。

本文中使用戴尔 Optiplex 760 计算机进行实验。它有一个通过 USB 连接的软盘驱动器,BIOS 启动选项允许从该驱动器启动计算机。

NakedOS

听起来与本文的无操作系统精神相矛盾,但 NakedCPU 是通过一个微小的(262字节长)32位“操作系统”NakedOS 启动的,这使得 NakedCPU 能够通过串口与外界通信。事实上,我们并没有违背真正自由探索的原则,因为 NakedOS 是绝对透明的,其代码在附录中完整呈现。NakedOS 定义了几个内存段(表1),这些段作为探究者可执行文件的初始环境很有用。Intel 文档[2]提供了对保护模式内存段、全局描述符表(GDT)和中断描述符表(IDT)的解释。此外,NakedOS 定义了两个软件中断和一个硬件中断基向量。注意:INT 中断与 DOS 或 BIOS 无关;它们完全由我们的代码定义。

表1. NakedOS 定义的内存段和中断。
Segment Base 大小 描述符,类型
扩展内存 0x100000 ~ 128MB 0x28, 数据
屏幕,字符模式 0x0B8000 4KB 0x20, 数据
目标可执行文件 0x93B 64KB 0x30, 代码 32;0x38, 数据
NakedOS 0x800 315 字节 0x10, 代码 32
堆栈 0x400 1024 字节 0x18, 堆栈 32
系统数据
IDT: 0x3FF-0x200
GDT: 0x1FF-0
0x0 1024 字节 0x8, 数据

中断 Info(信息)
INT 0x20 从串口读取一个数据包;目的地 ES:[EDI];强制条件 DS=ES。数据包的前4个字节表示后续字符串的长度(以字节为单位)。返回时,ECX 包含接收到的字节数。
INT 0x21 向串口发送一个位于 DS:[ESI] 的 ECX 字节字符串。
IRQ0 硬件中断基向量为 0x28。

启动后,NakedOS 立即期待两次事务:一次用于可执行代码,另一次用于数据。每个事务都是通过 RS232 发送的字节流(参见图 2)。第一次事务写入“目标可执行文件”内存段,而第二次事务写入“扩展内存”段。第二次事务完成后,NakedOS 通过一个长跳转将控制权转移给可执行文件

jmp   00030:000000000

从那时起,原则上,NakedOS 占用的任何内存都可以被探究者可执行文件的活动覆盖。当 NakedOS 运行时,硬件中断通常被屏蔽;但是,如果探究者决定解除屏蔽,则会设置 8259 中断控制器(参见下载存档中的附加文件 MBRListingNakedOS.doc)来处理中断。有关中断控制器编程的详细说明可在 I/O 控制器集线器 (ICH) 文档 [4] 中找到。

图 2. NakedOS 事务格式。前 4 个字节指示后续字节流的长度。

NakedCPU Explorer 和其他可执行文件

如何向 NakedCPU 发送可执行代码以进行实验仍然是一个重要问题。回想文章开头,其中提到涉及两台计算机。主计算机有一个 Visual C++ 项目 NakedCPU Explorer,它充当一个“shell”,允许检查和修改芯片组寄存器和内存。代码定义了一个类,该类有一个构造函数,它提供了 __asm{} 括号供探究者填充可执行代码

class Ports : public NakedCPUcode
{
    public:
    Ports()
    {
        DWORD pe, ps;
        __asm
        {
                mov pe, offset end      //save end and start of the code
                mov ps, offset start    //to be sent to the NakedCPU
                jmp end                 //master jumps over the NakedCPU code
        ///////////////////////////////////////////
        start:  mov ax, 0x28            //loading
                mov es, ax              //data
                mov ds, ax              //and stack
                mov ax, 0x18            //segment registers
                mov ss, ax              //initializing
                mov esp, 0x3fe          //stack pointer
                xor edi, edi
                mov eax, 'OLEH'         //NakedCPU Explorer says HELO
                stosd
                xor esi, esi
                mov ecx, 4
                INT 0x21
                ...                     //here goes rest of the code
                _emit 0xEA
                _emit 0x00
                _emit 0x00
                _emit 0x00
                _emit 0x00
                _emit 0x10
                _emit 0x00
        end:    nop
        }
        if(!PrepareCode(ps, pe)) delete this;
    }
};
图3. 派生自 NakedCPUcode 的类的构造函数片段。

由于 Microsoft Visual C++ 运行在拥有 Intel CPU 的主 PC 上,编译器会将汇编代码翻译成适当的操作码,这些操作码自然适用于 NakedCPU!具体来说,这个类派生自另一个类 NakedCPUcode,NakedCPUcode 通过从 __asm{} 括号中的代码中提取生成的操作码,并使其可用于发送到 NakedCPU 来执行准备工作。请注意,NakedCPU 只接收“start”和“end”标签之间的代码。重要的是要理解,主计算机不会执行 __asm{} 括号中的代码,它只是跳过它。奇怪的关键字“_emit”允许直接通过其十六进制值放置操作码——出于某种原因,在使用 Visual Studio 编译器时不允许长跳转。

除了 NakedCPU Explorer 之外,任何其他可执行代码都可以准备并发送到 NakedCPU 计算机。NakedCPU Explorer 代码的实际发送通过以下方式完成

Ports ncd;
SerialComm Com1;
if(!ncd.UploadEx(&Com1))
throw 1;

该项目还定义了一个类 SerialComm 和一个函数 SendNakedCPUdataRecvResponse 来发送和接收数据。仔细研究项目中的简单代码有助于理解与 NakedCPU 通信的细节。除了作为示例外,NakedCPU Explorer 还向 NakedCPU 发送一个可执行文件,该文件允许交互式检查和修改各种芯片组和 I/O 控制器寄存器。NakedCPU Explorer 提供八个命令:“write”、“write32”、“read”、“read32”、“pci”、“memread”、“memwrite”和“quit”。前四个命令将询问端口地址,即 CPU I/O 空间中的地址。通过这些命令,NakedCPU 将向 GMCH 或 ICH 寄存器写入和读取一个或四个字节。第五个命令将询问总线 (十进制)、设备 (十进制)、功能 (十进制) 和寄存器 (十六进制) 值。这些值将被打包到端口 0xCF8 中,以打开一个 PCI 配置空间的“窗口”,该窗口可通过端口 0xCFC 访问。有关 PCI 设备寻址的详细信息可在芯片组文档 [3] 中找到。Memread 和 memwrite 允许分别从内存中读取和写入双字。

注意:NakedCPU Explorer 不使用任何隐藏的“辅助”驱动程序或库。代码小巧且对查询者完全透明。

实验

以下部分描述了直接访问硬件和 CPU 的实验。

发出噪音

虽然听起来微不足道,但让 PC 扬声器发出声音涉及理解计时器和一些底层工作。讽刺的是,在 Vista 和 XP 64 位版本上似乎无法使用 Windows API 让扬声器发出蜂鸣声,因为微软认为扬声器硬件已过时 [7]。当然,过去,DOS 程序员一定知道如何做到这一点,但现在似乎已被遗忘。阅读 ICH 文档 [4] 并进行一些实验得出了以下协议

>写入
端口地址:0x61
值:0x3
扬声器已启用。NakedCPU 开始发出连续声音。
>写入
端口地址:0x43
值:0xbe
>写入
端口地址:0x42
值:0x10
>写入
端口地址:0x42
值:0x1
通过写入计时器配置字来更改默认频率。声音停止,因为计时器正在等待接收一个两字节的除数。通过两次事务写入新值 0x0110,之后 NakedCPU 将开始发出高音噪音。
>写入
端口地址:0x42
值:0x0
>写入
端口地址:0x42
值:0x20
写入更大的除数 0x2000 以降低频率。无需写入配置字。

点亮 LED

并行端口越来越过时,但它仍然提供了通过 8 条线读取和发送数据的可能性。奇怪的是,ICH 文档没有提及并行端口的编程。浏览互联网发现,人们对并行端口仍然有一些兴趣,并且可以获得编程信息。将 LED 通过 470 欧姆电阻连接到 D2 端口线,然后按照以下协议操作,该协议演示了并行端口的写入和读取。

>写入
端口地址:0x77A
值:0x34
>写入
端口地址:0x378
值:0x4
通过修改扩展控制寄存器 (0x77A),并行端口进入扩展功能端口模式。D2 线变为高电平,LED 发光。
>写入
端口地址:0x77A
值:0x34
>写入
端口地址:0x37A
值:0x2c
也可以从 LPT 读取。通过向端口控制寄存器 (0x37A) 写入 0x2c,我们使并行端口进行读取。LED 微微发光,表明在读取模式下启用了上拉电阻。

“第一声哭泣”

处理器在开机后执行的第一条指令是什么?文档[2]说,处理器从地址 0xFFFFFFF0 读取其第一条指令,即 4GB 以下 16 个字节。尝试使用调试器检查此地址是徒劳的(已测试,无效)。为了达到这个高地址,它位于高位 BIOS 的范围内,为 NakedCPU 准备了一个小的可执行文件。该可执行文件定义了一个寻址高位 BIOS 的内存段,并将 4GB 以下 16 个字节的内容发送回主计算机。正如预期,有一个短跳转,大约向下 30KB。可执行文件经过修改,可以下载 4GB 以下到顶部的整个 30KB 内存块。特别好奇的探究者欢迎调查已保存并可供下载的内容。总的印象是,可以看到许多对 PCI 总线的访问和对 CPUID 指令的调用。这当然是有道理的,因为必须设置各种设备,并且 BIOS 正在尝试确定正在使用的处理器。

网络

通过网络通信使用媒体访问控制器(MAC)完成。文档可在英特尔网页上查阅。阅读 IEEE 802.3-2008 标准的前三章也有助于了解底层网络术语以及通过线路发送的数据包格式 [8]。

我们将再次使用 NakedCPU Explorer 调查媒体访问控制器的内部结构并进行一些实验。MAC 需要内存中的数据结构和通过 I/O 地址空间的配置事务。首先,我们必须确定 MAC 的 I/O 地址,这称为基地址 2 (BAR2)。该地址存储在 PCI 配置空间的 bus 0, device 25, function 0 (B0:D25:F0) 寄存器 0x18 中。顺便说一句,文档中引用同一个寄存器存在混淆。ICH 将此特定寄存器称为 MBARC [4],而 MAC 文档 [6] 将其称为 BAR2。通过 NakedCPU Explorer 执行 PCI 事务,如下所示

>pci
输入总线设备功能 0x寄存器:0 25 0 0x18
>read32
端口地址:0xcfc
ecc1
PCI事务由两步组成:定义位置和读取内容。

数字 0xECC1 意味着 I/O 地址实际上是 0xECC0,其第 0 位硬编码为 1,表示该地址确实位于 I/O 空间中,而不是内存映射的 [6]。后者的指示很重要,因为与 MAC 的所有配置和通信也可以使用内存映射寄存器完成,这更快,但对于我们的实验,使用 I/O 空间就足够了,因为它更简单,并且与内存映射操作达到相同的结果。

为了与 MAC 交互,查询者将 MAC 内寄存器的地址写入 BAR2 I/O 地址 (0xECC0)。之后,BAR2+0x4 (0xECC4) 成为该 MAC 寄存器值的窗口。需要注意的是,BAR2 和 BAR2+0x4 只接受 32 位双字读/写操作。MAC 寄存器有大量的位需要处理,有些位相互依赖。仅仅通过查看寄存器的十六进制值很难理解其设置。Excel 工作表 InterpretRegister.xls(可供下载)包含一个宏,可在此情况下提供很大帮助。具体来说,包含位描述的表格应从文档 PDF 中复制粘贴,寄存器的十六进制值将转换为二进制 1 和 0,并显示在描述文本旁边的相应单元格中,参见图 4。

图 4. 工作表片段显示了值 0x4100240(黄色阴影)分解为位。位出现在单个字段的文本描述之后。

让我们检查控制 CTRL(0x0) 和状态 STATUS(0x8) 寄存器;寄存器地址在括号中给出。拔掉网线后通电

>写入32
端口地址:0xecc0
值:0
>read32
端口地址:0xecc4
100240

这种特定的位组合,除其他外,决定了速度和全/半双工的自动配置是否启用。


>写入32
端口地址:0xecc0
值:0x8
>read32
端口地址:0xecc4
80080600

这些位表示没有建立链接,但初始化已完成。

将主计算机与 NakedCPU 连接后,CTRL 寄存器如预期保持不变,而状态寄存器变为 0x80080683。新值表示全双工通信,已建立链接,速度为 1Gbps。运行 Windows XP 的主计算机报告了相同的通信参数,这表明 NakedCPU 网络接口能够在硬件级别与主计算机的接口协商。

接收和解释网络数据包

在本节中,我们将实验读取源自主计算机的网络数据包。我们发现,当主计算机通过网线检测到活动的 NakedCPU 时,Windows 开始生成 DHCP 请求。这些请求是尝试获取 IP 地址和其他高级网络设置,因为 Windows 错误地假设 NakedCPU 是一个路由器或网络服务器。虽然 Windows 搞错了,但这对于我们的实验来说完全没问题,因为我们可以捕获这些数据包并检查它们。

MAC 使用直接内存访问来存储接收到的数据。我们必须创建几个描述符,这些描述符将告诉 MAC 将数据写入何处。因此,需要两个内存范围:一个用于描述符,另一个用于数据包。参考表 1,可以看到地址 0x100000 上方有一块内存区域可用于查询者的数据。考虑到 NakedCPU Explorer 使用一小部分内存来存储传入命令,我们可以安全地使用 0x100500 以上的地址。对于初始实验来说,创建两个描述符就足够了。一个描述符是一个包含四个双字(16 字节)的数据结构。前两个双字是存储数据包的 64 位物理地址。通过 NakedCPU Explorer 写入内存位置的能力,我们可以在地址 0x100500 处创建描述符,指向位于地址 0x101000 和 0x101200 的两个 512 字节长的缓冲区。描述符的集合称为“队列”。MAC 完成存储数据包后,它将更新描述符以指示接收到的数据包大小、错误和其他几个参数。

>内存写入
0x 地址高于 0x100000:0x100500
双字数量:8
0x101000
0
0
0
0x101200
0
0
0

命令“memwrite”要求提供地址和要写入的双字数量。请注意,写入 0x100000 以下将导致通用保护错误并重新启动 NakedCPU。

MAC 必须知道描述符的位置、接收缓冲区的大小以及要接收的数据包类型。这些信息必须存储在几个 MAC 寄存器中。RDBAL0(0x2800) 和 RDBAH0(0x2804) - 分别是队列基址的 64 位物理地址的低位和高位。RDLEN0(0x2808) - 为队列分配的内存缓冲区的长度。RDH0(0x2810) 和 RDT0(0x2818) - 分别是头指针和尾指针。RFCTL(0x5008) - 接收过滤器控制寄存器。RXCSUM(0x5000) - 接收校验和控制寄存器。在设置这些寄存器之前,CTRL(0) 寄存器的位 26 必须设置为 1,这将导致 MAC 重置。设置所有寄存器后,通过写入 RCTL(0x100) 来设置“启用”位、接收缓冲区接收模式的大小和描述符类型来启动数据接收。

对于 CTRL、RCTL、RFCTL 和 RXCSUM 寄存器,工作表 InterpretRegister.xls 显示了将在我们的实验中使用的值(和位状态)。RDLEN 的值含义有些令人困惑。根据文档,队列缓冲区的长度必须是 128 的倍数,这意味着队列中必须至少有八个描述符(128 / 16)。但是,我们只有两个描述符。我通过实验确定,告诉 MAC 队列缓冲区比实际需要的更大并不是问题,只要 RDT0 寄存器指向实际队列的末尾。因此,对于我们的特定实验,我们必须设置 RDLEN = 0x80,RDT0 = 0x2。

在 NakedCPU 接收数据包之前,主计算机不应发送 DHCP 数据包。为了抑制源自主计算机的 DHCP 数据包,应在管理员帐户下的 Windows 命令行工具中输入以下内容

ipconfig /release

接下来,将表 2 的列按 I - V 的顺序复制并粘贴到 NakedCPU Explorer 中。请注意,在第 4 步结束时,MAC 已准备好启用接收。该步骤以从状态寄存器读取结束,结果应为 0x80683。此值与之前描述的值(0x80080683,第 8 页)相似,不同之处在于位 31 已清除,这表示 DMA 时钟不能降低到其值的 ¼。MAC 改变 DMA 时钟“想法”的原因尚不清楚,但这与我们的实验无关。

表 2. 要粘贴到 NakedCPU Explorer 中的命令和值。
V
写入32
0xecc0
0
写入32
0xecc4
0x4100240
写入32
0xecc0
0x2800
写入32
0xecc4
0x100500

写入32
0xecc0
0x2804
写入32
0xecc4
0
写入32
0xecc0
0x2808
写入32
0xecc4
0x80

写入32
0xecc0
0x2810
写入32
0xecc4
0
写入32
0xecc0
0x2818
写入32
0xecc4
0x2

写入32
0xecc0
0x5008
写入32
0xecc4
0x8000
写入32
0xecc0
0x8
读取32
0xecc4


写入32
0xecc0
0x100
写入32
0xecc4
0x402800A






要启动主计算机发送数据包,请在管理员帐户下的命令行窗口中键入以下内容

ipconfig /renew

从网络读取后,MAC 更新了两个描述符。使用 memread 命令,通过从地址 0x100500 开始读取 8 个双字来观察新值。您会看到描述符的地址字段已更改,并且出现了两个附加值。图 5 显示了我的计算机产生的新值。有关字段的详细信息在 MAC 文档 [6] 中提供。简而言之,数据包长度为 342 字节 (0x156);没有发生错误,描述符显示为“完成”,并且整个数据包能够适应缓冲区 (0x20073)。

描述符 1 描述符 2
0
0
f8270fe8
f8270fe9
20073
20073
156
156
图 5. 读取网络数据包后更新的描述符。

MAC 将实际的两个数据包分别存储在地址 0x101000 和 0x101200;它们的内容在 InterpretRegister.xls 的“数据包”工作表中。图 6 显示了存储数据数组的开头和传输顺序。前六个红色标记的字节是目标(广播)地址,后面是源地址和两字节的长度/类型字段。后一个字段以最高有效字节优先传输 [8],使其值为 0x0800。如果长度/类型字段的值小于或等于 0x05DC,则它指示数据包的长度,否则指示其类型 (Ethertype)。standards.ieee.org 网站理论上提供了特定的 Ethertype 值,但实际上不可能找到实际的值列表。幸运的是,维基百科指向了 ieee.org 网站内部的一个确切 URL [9]。根据标准,互联网协议 (IP) 指定的 Ethertype 为 0x0800。显然,下一步是研究互联网协议的格式,该协议在 RFC894 中描述,该协议指向 RFC791 [10]。

图 6. NakedCPU 接收到的 DHCP 数据包的开头。

IP 报头字段的传输方式与前面描述的长度/类型字段相似,即最高有效位和最高有效字节优先。与直观的表示法不同,直观的表示法中位 0 是最低有效位,而 RFC791 提供了相反的表示法,位 0 是最高有效位。表 3 显示了接收到的数据包的延续及其特定值与 IP 报头前 32 位字段的分配。重要字段是 IHL 和总长度。IHL 指示报头中 32 位字的个数。在我们的例子中是 5,这意味着根据 RFC791,选项和填充字段被省略了。有趣的是,MAC 报告接收到 342 字节,而 IP 报头指示 328 (0x0148) 字节。14 字节的差异完全合理,因为它们构成了长度/类型字段的两个字节加上目标和源硬件地址的 2*6 字节。

IP 报头的其他所有字段都可依此示例轻松映射。具体来说,字段值为:Identification - 0x0fe8(数据包 1),0x0fe9(数据包 2);Flags and Fragment Offset - 0;Time to Live - 0x80;协议 - 0x11;Header Checksum - 0x29be(数据包 1),0x29bd(数据包 2);源 IP 地址 - 0.0.0.0;目标 IP 地址 - 255.255.255.255。可以理解,主 PC 在进行动态主机配置过程时正在请求 IP 地址,因此源 IP 地址全为零。对于目标地址,地址全为 255.255.255.255,这是一个广播地址,类似于硬件广播地址(6 字节,全 255)。

表 3. IP 报头的前 32 位字。顶行 - 位号,左列 - 接收到的数据包片段。数据包的下划线值被分解为字段。
0 - 3 4-7 8-15 16-31
...
00450008
E80F4801
...
版本
0x4
互联网报头长度(IHL)
0x5
服务类型
00
总长度
0x0148

协议字段的值为 0x11 (17);根据 RFC790,它指的是用户数据报协议 (UDP)——下一个数据封装层——该协议在 RFC768 中描述。根据该文档,UDP 报头包含四个 16 位字。按照传输顺序,这些字是源端口、目的端口、长度和校验和。我的实验中 NakedCPU 接收到的值为源端口 - 0x0044,目的端口 - 0x0043,长度 - 0x0134,校验和 - 0x5c1a(数据包 1)和 0x581a(数据包 2)。308 (0x134) 字节的长度是合理的,因为 IP 报头报告的总数据报长度为 328 字节减去 IP 报头占用的 20 字节。源端口和目的端口号本应在 RFC790 中解释,但结果是,一长串其他文档使该文档过时。在链的末尾,建议查看互联网号码分配机构 (IANA) 的网站,不幸的是,该网站上的信息组织不善。然而,在过时的 RFC 中,可以找到端口号 67 (0x43) 和 68 (0x44) 分别对应于引导协议服务器和客户端端口。引导协议在 RFC1542 中描述,该文档指向 DHCP 协议,参见 RFC2131。本节讨论的所有字段的摘要在图 7 中提供。

图 7. 接收到的数据包中以太网、IP 和 UDP 协议的数据字段摘要。

结论和未来展望

我们可以在没有任何旨在让我们的生活“更轻松”的未知中间代码层的情况下,直接实验 Intel CPU 和其他 PC 硬件。迄今为止,处理器本身拥有最全面的文档 [2]。其他硬件文档不完善,这就是为什么需要付出大量努力从互联网上收集信息并直接进行实验的原因。处理硬件的旧书以 DOS 为导向,并且严重过时。新硬件被隐藏在未知代码层之后。

理论上,NakedCPU 平台使开发人员能够仅使用所需组件创建特定任务的应用程序。例如,如果我们要谈论一个大型数据库,则无需支持 GUI、USB 即插即用、声卡、.NET 和许多其他东西。NakedCPU 平台的额外好处是免疫病毒。就像在生物学中,灵活的病毒攻击进化良好的生物体一样,计算机病毒攻击发展良好的操作系统。而使用 NakedCPU,特定的任务专用解决方案可能非常独特;因此,病毒制造者将根本没有足够的信息来探索潜在的安全漏洞。

原始出版物

原始出版物可在我的网站上查阅。点击图片

Part1 Part2

参考文献

  1. Intel Core 2 Duo 处理器 E8000 和 E7000 系列。数据手册。2009 年 6 月。PDF
  2. Intel 64 和 IA-32 架构软件开发人员手册,所有卷合集 PDF
  3. Intel 4 系列芯片组家族。数据手册 PDF
  4. Intel I/O 控制器集线器 10 (ICH10) 系列。数据手册。PDF
  5. M Klasson。HexIt - 十六进制编辑器 在此
  6. Intel I/O 控制器集线器 8/9/10 和 82566/82567/82562V 软件开发人员手册。PDF
  7. MSDN,Beep 函数。在此
  8. 信息技术 IEEE 标准 - 系统间电信和信息交换 - 局域网和城域网 - 具体要求。第 3 部分:载波侦听多路访问/冲突检测 (CSMA/CD) 访问方法和物理层规范。IEEE Std 802.3 -2008。在此
  9. 维基百科指向的以太网类型值列表。在此
  10. 所有 RFC 文档均可在 http://www.ietf.org/rfc/rfcZZZZ.txt 查阅,其中 ZZZZ 是文档的四位数编号,如有必要,前面补零。也可在此处查阅 所有 RFC

附录

MBR 剖析

描述 MBR 位置
处理器在开机后仍处于实模式时确定当前地址。BIOS 已将 MBR 加载到内存中的某个位置,并将控制权转移给我们的代码。当前地址对于定位伪描述符的物理地址是必要的,伪描述符又定义了全局描述符表 (GDT) 的物理地址和限制。 0x3E - 0x4D
LGDT 指令(加载 GDT 寄存器)正在加载伪描述符,该伪描述符指向 GDT。 0x52
GDT 和中断描述符表 (IDT) 被复制到新的内存位置,从线性地址 0x0 开始。GDT 和 IDT 定义了处理器在保护模式下操作的内存段。 0x57 - 0x64
MBR 包含一个非常微小的 32 位保护模式“操作系统”,我们称之为 NakedOS。 0x80 - 0x186
NakedOS 被复制到从线性地址 0x800 开始的新内存位置。 0x65 - 0x71
通过使用 LMSW 指令调整机器状态字来切换到保护模式。 0x72 - 0x78
将控制权转移给 NakedOS。 0x7B
设置 8259 中断控制器 0xF5 - 0x105
将控制权转移给查询者的可执行文件 0x106

关键数据结构

结构 MBR 位置
伪描述符 IDT 0x194
伪描述符 GDT 0x1BA
空描述符 0x1C0

MBR 列表:NakedOS

Address   Opcode                        Mnemonic
00000000  EB3C                          jmp     short 00000003E
.............some remnants from FAT...............................
0000003E  FA                            cli
0000003F  33C0                          xor     ax,ax
00000041  8ED0                          mov     ss,ax
00000043  BC007C                        mov     sp,07C00
00000046  16                            push    ss
00000047  07                            pop     es
00000048  0E                            push    cs
00000049  1F                            pop     ds
0000004A  E80000                        call    00000004D
0000004D  89E5                          mov     bp,sp
0000004F  8B5E00                        mov     bx,[bp+000]
00000052  0F01976D01                    lgdt    q.[bx+0016D]
00000057  89DE                          mov     si,bx
00000059  81C67301                      add     si,0173
0000005D  B94000                        mov     cx,040
00000060  31FF                          xor     di,di
00000062  FC                            cld
00000063  F3                            repe
00000064  A4                            movsb
00000065  BF0008                        mov     di,0800
00000068  89DE                          mov     si,bx
0000006A  83C633                        add     si,033
0000006D  B93B01                        mov     cx,013B
00000070  F3                            repe
00000071  A4                            movsb
00000072  0F01E0                        smsw    ax
00000075  0D0100                        or      ax,01
00000078  0F01F0                        lmsw    ax
0000007B  EA00001000                    jmp     00010:00000
-----from here 32-bit code segment ------------------------------
00000080  66B81800                      mov     ax,018
00000084  8ED0                          mov     ss,ax
00000086  BCFE030000                    mov     esp,03FE
0000008B  66B80800                      mov     ax,08
0000008F  8EC0                          mov     es,ax
00000091  E492                          in      al,092
00000093  0C02                          or      al,02
00000095  E692                          out     092,al
00000097  66BAFB03                      mov     dx,03FB
0000009B  B083                          mov     al,083
0000009D  EE                            out     dx,al
0000009E  66BAF803                      mov     dx,03F8
000000A2  66B80600                      mov     ax,06
000000A6  66EF                          out     dx,ax
000000A8  66BAFB03                      mov     dx,03FB
000000AC  B003                          mov     al,03
000000AE  EE                            out     dx,al
000000AF  31C0                          xor     eax,eax
000000B1  BF00020000                    mov     edi,0200
000000B6  B900020000                    mov     ecx,0200
000000BB  F3                            repe
000000BC  AA                            stosb
000000BD  BF00030000                    mov     edi,0300
000000C2  BE14010000                    mov     esi,0114
000000C7  0E                            push    cs
000000C8  1F                            pop     ds
000000C9  0F011E                        lidt    q.[esi]
000000CC  83C606                        add     esi,06
000000CF  B904000000                    mov     ecx,04
000000D4  F3                            repe
000000D5  A5                            movsd
000000D6  66B83800                      mov     ax,038
000000DA  8EC0                          mov     es,ax
000000DC  8ED8                          mov     ds,ax
000000DE  31FF                          xor     edi,edi
000000E0  CD20                          int     020
000000E2  66B82800                      mov     ax,028
000000E6  8EC0                          mov     es,ax
000000E8  8ED8                          mov     ds,ax
000000EA  31FF                          xor     edi,edi
000000EC  CD20                          int     020
000000EE  6631C0                        xor     ax,ax
000000F1  8EC0                          mov     es,ax
000000F3  8ED8                          mov     ds,ax
000000F5  66BA2000                      mov     dx,020
000000F9  B011                          mov     al,011  //ICW1
000000FB  EE                            out     dx,al
000000FC  B028                          mov     al,028  //ICW2
000000FE  42                            inc     edx   //IRQ0 base addr 0x28
000000FF  EE                            out     dx,al
00000100  B004                          mov     al,04   //ICW3: Slave
00000102  EE                            out     dx,al   //connected to pin 2
00000103  B001                          mov     al,01   //ICW4
00000105  EE                            out     dx,al
00000106  EA000000003000                jmp     00030:000000000
.............random bytes......................................
00000140  66BAFD03                      mov     dx,03FD
00000144  EC                            in      al,dx
00000145  A801                          test    al,01
00000147  74FB                          jz      short 000000144
00000149  66BAF803                      mov     dx,03F8
0000014D  EC                            in      al,dx
0000014E  AA                            stosb
0000014F  50                            push    eax
00000150  66BAFD03                      mov     dx,03FD
00000154  EC                            in      al,dx
00000155  A820                          test    al,020
00000157  74FB                          jz      short 000000154
00000159  58                            pop     eax
0000015A  66BAF803                      mov     dx,03F8
0000015E  EE                            out     dx,al
0000015F  C3                            ret
00000160  57                            push    edi
00000161  B904000000                    mov     ecx,04
00000166  E8D5FFFFFF                    call    000000140
0000016B  E2F9                          loop    000000166
0000016D  5F                            pop     edi
0000016E  8B0F                          mov     ecx,[edi]
00000170  85C9                          test    ecx,ecx
00000172  7409                          jz      short 00000017D
00000174  51                            push    ecx
00000175  E8C6FFFFFF                    call    000000140
0000017A  E2F9                          loop    000000175
0000017C  59                            pop     ecx
0000017D  CF                            iretd
0000017E  AC                            lodsb
0000017F  E8CBFFFFFF                    call    00000014F
00000184  E2F8                          loop    00000017E
00000186  CF                            iretd
.............random bytes.........................................
00000194  FF01
00000196  0002
00000198  0000
0000019A  E000
0000019C  1000
0000019E  008E0000
000001A2  FE00
000001A4  1000
000001A6  008E0000
000001AA  0000
000001AC  0000
000001AE  0000
000001B0  0000
000001B2  0000
000001B4  0000
000001B6  0000
000001B8  0000
000001BA  FF01
000001BC  0000
000001BE  0000
000001C0  0000
000001C2  0000
000001C4  0000
000001C6  0000
000001C8  FF03
000001CA  0000
000001CC  00920000
000001D0  0A13
000001D2  0008
000001D4  009A4000
000001D8  FF03
000001DA  0004
000001DC  00924000
000001E0  FF0F
000001E2  00800B92
000001E6  0000
000001E8  007800
000001EB  0010
000001ED  92
000001EE  8000FF
000001F1  FF3B
000001F3  0900
000001F5  98
000001F6  40
000001F7  00FF
000001F9  FF3B
000001FB  0900
000001FD  92
000001FE  0000

Firstsectwrite

#include "stdafx.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR destDisk[] = TEXT("\\\\.\\X:");
    cerr << "\n firstsectwrite.exe E: nakedos_x_y.bin";
    if (argc != 3)
    {
        cerr << "\nInput parameters error";
        return -1;
    }

    if(_tcslen(argv[1]) != 2)
    {
        cerr << "\nInput parameters error";
        return -1;
    }
    _tcscpy(&destDisk[4], argv[1]);

    HANDLE hD=CreateFile(destDisk,GENERIC_WRITE, FILE_SHARE_WRITE, NULL, 
           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING, NULL);
    HANDLE hS=CreateFile(argv[2],GENERIC_READ, NULL, NULL, 
                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if((INVALID_HANDLE_VALUE == hS) || (INVALID_HANDLE_VALUE == hD))
    {
        cerr << "error";
        return -1;
    }
    BYTE buf[512];
    BYTE bufproof[512];
    DWORD dwCopied;
    ReadFile(hS, buf, 512, &dwCopied, NULL);
    WriteFile(hD, buf, 512, &dwCopied, NULL);
    CloseHandle(hD);
    CloseHandle(hS);

    hD=CreateFile(destDisk,GENERIC_READ, FILE_SHARE_WRITE, NULL, 
         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING, NULL);

    if(INVALID_HANDLE_VALUE == hD)
    {
        cerr << "error";
        return -1;
    }
    ReadFile(hD, bufproof, 512, &dwCopied, NULL);
    if(!equal(buf, buf + 512, bufproof))
    {
        cerr << "write error";
        return -1;
    }
    return 0;
}
© . All rights reserved.