os探索

279 阅读9分钟

1.前言

在家无聊闲翻出来一个以前用过的华硕笔记本电脑

  • 型号:X552MJ2940-554LSF52X10(x64)

  • 序列号:F7N0CV056919272

  • 配置:

    • BIOS(UEFI): American Megatrends Inc. X550MJ.201,2015/1/14
    • 启动:\Device\HarddiskVolume1
    • CPU(4核): Intel(R) Celeron(R) CPU N2940 @1.83Ghz
    • 内存:8G
    • 硬盘:kingston sa400s37480G
    • 光驱:MATSHITA DVD-RAM UJ8G6

这个老家伙开机1分钟起步,电池似乎损坏了,只能插电使用,运行程序特别卡顿,我都想直接丢掉了,转念一想,这不正好让我有机会拿来试试看,能不能自己写个操作系统去用它,反正折腾坏了也不心疼,哈哈~

您各位可能会问了,Why u do that? Just for fun!

20391756699091_.pic-6699114.jpg

2.编写代码

说干就干,这个机器是BIOS(UEFI)模式启动的,直接制作UEFI启动太麻烦了,刚开始哪用搞这么复杂,我直接拿个U盘格式化了,将加载程序先写到它的MBR(主引导扇区),然后设置机器为U盘启动,加载MBR程序,先来看看可不可行,不过在继续之前,最好先把原机器的bios保存下来,网上找了个工具 pan.baidu.com/s/1FBFpPDoG… 来备份程序,使用很简单

截屏2025-09-01 12.01.53.png

先来一点前置补充,在存储介质中存在不同的分区,位于第一位置的即为MBR, 叫做主引导扇区,它是一个拥有512字节的区域,该区域内可包含:

  • 446字节程序
  • 64字节分区表
  • 末尾2字节0x55aa魔数

知道了这些,那我们可以在446字节区域写程序,先来写一个最简单的helloworld:

[org 0x7c00]
; 设置文本模式:实模式
mov ax, 3
int 0x10 ; bios中断程序,用于设置文本显示模式; 初始化段寄存器
mov ax, 0
​
mov si, booting
call print
; 阻塞
jmp $
​
; 定义字符串
booting:
  db "hello,world!", 10, 13, 0
​
print:
  mov ah, 0x0e
.next:
  mov al, [si]
  cmp al, 0
  jz .done
  int 0x10
  inc si
  jmp .next
.done
  ret    
  
; 将后续空间写为0
times 510 - ($ - $$ ) db 0
​
; 表示结束主引导扇区,硬性规定
db 0x55, 0xaa

这段汇编程序中先跳到内存0x7c00处,然后在实模式下利用bios提供的中断程序int 0x10来进行文本显示,后续空间全填0,末尾写0x55aa魔数

机器加电后bios程序就会自动读取u盘MBR的这段程序,然后跳到内存0x7c00处继续执行后面的内容,为什么会跳到这个地址呢?大家可以看看这篇 文章

写好了汇编代码后需要把它编译成机器码,这时候就要用到:

  • 编译器:brew install nasm
  • 编译:nasm -f bin -o boot.bin boot.asm

编译完成了就可以将其写入U盘了,在写之前需要先来处理一下:

  • 插入U盘
  • 列出usb设备:diskutil list
  • 显示详细信息:diskutil info /dev/disk3
  • 格式化usb设备:diskutil eraseDisk FAT32 USBDRIVE MBR /dev/disk3
  • 查看分区:sudo gpt -r show /dev/disk3
  • 卸载usb设备:diskutil unmountDisk /dev/disk3

处理好了以后就可以写入了:sudo dd if=boot.bin of=/dev/rdisk3 bs=1m count=1

截屏2025-09-04 17.11.30.png

为了避免出错重复拔插u盘,最好是先在模拟器上跑一跑,这里就要用到bochs: brew install bochs

它能模拟出一整套的各型号的元件供我们使用,首先要初始化:bochs后选择 "4. Save options to..." 输入配置名称 "bochsrc" , 它会生成默认的bochs配置,为了方便观察要修改:

  • display_library: sdl2 图形化显示
  • boot: disk 硬盘启动
  • ata0-master: type=disk, path="master.img", mode=flat 指定镜像文件
# configuration file generated by Bochs
plugin_ctrl: usb_uhci=false, serial=true, speaker=true, unmapped=true, parallel=true, biosdev=true, iodebug=true, extfpuirq=true
config_interface: textconfig
memory: guest=32, host=32, block_size=128
romimage: file="/usr/local/Cellar/bochs/3.0/share/bochs/BIOS-bochs-latest", address=0x00000000, options=none, flash_data=none
vgaromimage: file="/usr/local/Cellar/bochs/3.0/share/bochs/VGABIOS-lgpl-latest.bin"
​
boot: disk
floppy_bootsig_check: disabled=0
floppya: type=1_44
# no floppyb
ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="master.img", mode=flat
ata0-slave: type=none
ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata1-master: type=none
ata1-slave: type=none
ata2: enabled=false
ata3: enabled=false
optromimage1: file=none
optromimage2: file=none
optromimage3: file=none
optromimage4: file=none
optramimage1: file=none
optramimage2: file=none
optramimage3: file=none
optramimage4: file=none
pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none
vga: extension=vbe, update_freq=10, realtime=1, ddc=builtin, vbe_memsize=16
cpu: count=1, ips=4000000, model=corei7_haswell_4770, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0
print_timestamps: enabled=0
debugger_log: -
magic_break: enabled=1 0x0
port_e9_hack: enabled=false, all_rings=false
iodebug: all_rings=0
private_colormap: enabled=0
clock: sync=none, time0=local, rtc_sync=0
# no cmosimage
log: -
logprefix: %t%e%d
debug: action=ignore
info: action=report
error: action=report
panic: action=ask
keyboard: type=mf, serial_delay=150, paste_delay=100000, user_shortcut=none
mouse: type=ps2, enabled=false, toggle=ctrl+mbutton
com1: enabled=true, mode=null
com2: enabled=false
com3: enabled=false
com4: enabled=false
speaker: enabled=true, mode=gui
parport1: enabled=true, file=none
parport2: enabled=false
display_library: sdl2

这里需要一个img,所以还要去生成img镜像,这时会发现这些指令也太多了,放到makefile中更好处理:

# 定义汇编文件和目标文件
BOOT_ASM = boot.asm
BOOT_BIN = boot.bin
IMG = master.img
USB_DEVICE = /dev/rdisk3  # 替换为你的 USB 设备路径# 指定编译器和模拟器
NASM = nasm
BOCHS = bochs
​
# 默认目标
all: $(IMG)# 编译汇编代码
$(BOOT_BIN): $(BOOT_ASM)
  @echo "Compiling $(BOOT_ASM)..."
  $(NASM) -o $@ $<# 创建磁盘映像
$(IMG): $(BOOT_BIN)
  @echo "Creating $(IMG)..."
  dd if=/dev/zero of=$(IMG) bs=1M count=1
  dd if=$(BOOT_BIN) of=$(IMG) bs=512 count=1 conv=notrunc
​
# 将 bin 文件写入 USB MBR
usb: $(BOOT_BIN)
  @echo "Writing $(BOOT_BIN) to USB MBR..."
  sudo dd if=$(BOOT_BIN) of=$(USB_DEVICE) bs=512 count=1 conv=notrunc
​
# 运行 Bochs 模拟器
run: $(IMG)
  @echo "Running Bochs..."
  $(BOCHS) -q -unlock
​
# 清理生成的文件
clean:
  rm -rf *.bin
  rm -rf *.img
​
# 伪目标
.PHONY: all clean run usb

接着来试一下行不行:

  • make clean
  • make
  • make run

截屏2025-09-04 17.08.43.png

ohhhh,we made it!

下一步就要把它做成u盘启动了:

  • 格式化usb设备:diskutil eraseDisk FAT32 USBDRIVE MBR /dev/disk3
  • 卸载usb设备:diskutil unmountDisk /dev/disk3
  • 做成u盘启动: make usb
# 将 bin 文件写入 USB MBR
usb: $(BOOT_BIN)
  @echo "Writing $(BOOT_BIN) to USB MBR..."
  sudo dd if=$(BOOT_BIN) of=$(USB_DEVICE) bs=512 count=1 conv=notrunc

截屏2025-09-04 17.11.59.png

3.实体机测验

前面已经将加载程序写进了u盘的主引导扇区,并且在bochs试过没问题,那现在就可以去实体机器上试试了

按下开机键后狂按F2, 就能进入华硕bios设置界面,前面说过,UEFI 模式下,系统通常不会从 MBR 启动,而是从 EFI 系统分区(ESP)中的 EFI 可执行文件启动,但我们只写入了MBR,所以需要开启Legacy Boot模式,这样才能从MBR启动,华硕笔记本启用Legacy Boot模式需要:

  • Boot -> Launch CSM: "Enabled"
  • Security boot -> "Disabled" 截屏2025-09-04 17.13.01.png

然后设置u盘启动后F10保存: 截屏2025-09-04 17.13.44.png

最终显示效果: 截屏2025-09-04 17.14.11.png

哈哈,迈出了一小步,搞懂了流程

4.解密bios

我们刚才知道了bios很关键,它会找到启动设备将程序加载到内存后执行,但它具体是怎么实现的呢?

光说不练假把式,所以还记得我们前面备份的bios.bin吗,是时候将其反编译看看里面究竟干什么了,先来安装idapro:mrx.hk/posts/f66fa… , 然后将其导入ida中,得到: 截屏2025-09-04 17.15.34.png 这个文件加载出来有26万行,主要关注*“引导程序”*的逻辑分析,但是要怎么找到呢?引导逻辑有什么特征呢?

可以发现这个文件里包括:

  • BIOS_E:0000 到 BIOS_F:FFF8,16 位寻址模式,共 64KB 的地址空间,共14518行
  • BIOS_FLASH:FFC00000 到 BIOS_FLASH:FFFFFFFF,32 位寻址模式,共 4GB 的地址空间,共250306行

在16 位寻址空间的末尾处,有如下的代码:

; 声明 start 是一个公共符号
BIOS_F:FFF0                 public start  
; proc near表示跳转时不会改变代码段寄存器 cs
BIOS_F:FFF0 start           proc near
BIOS_F:FFF0   
; 刷新 CPU 的写缓冲区,并使所有缓存无效
BIOS_F:FFF0                 wbinvd
; 跳转到初始化代码
BIOS_F:FFF2                 jmp     loc_FF7F0
BIOS_F:FFF2 start           endp

它是 BIOS 启动过程的入口点,将代码跳到loc_FF7F0执行,具体代码如下:

BIOS_F:F7F0 loc_FF7F0:
BIOS_F:F7F0                 fninit                                ; 初始化浮点单元
BIOS_F:F7F2                 movd    mm0, eax
BIOS_F:F7F5                 cli                                   ; 关闭中断
BIOS_F:F7F6                 xor     eax, eax                      ; 设置段寄存器
BIOS_F:F7F9                 mov     es, ax
BIOS_F:F7FB                 assume es:BIOS_FLASH
BIOS_F:F7FB                 mov     ax, cs
BIOS_F:F7FD                 mov     ds, ax
BIOS_F:F7FF                 assume ds:BIOS_F
BIOS_F:F7FF                 mov     ax, 0F000h
BIOS_F:F802                 mov     es, ax
BIOS_F:F804                 assume es:BIOS_F
BIOS_F:F804                 mov     al, large byte ptr es:start    ; 检查 BIOS 启动标志
BIOS_F:F80B                 cmp     al, 0EAh
BIOS_F:F80D                 jnz     short loc_FF81E
BIOS_F:F80F                 mov     cx, 1Bh                        ; 检查 CPU 特性
BIOS_F:F812                 rdmsr
BIOS_F:F814                 test    ah, 1
BIOS_F:F817                 jz      short loc_FF85A
BIOS_F:F819                 jmp     far ptr start
BIOS_F:F81E ; --------------------------发送 POST 代码-------------------------------------------------
BIOS_F:F81E
BIOS_F:F81E loc_FF81E:                              ; CODE XREF: BIOS_F:F80D↑j
BIOS_F:F81E                 mov     al, 1
BIOS_F:F820                 out     80h, al         ; manufacture's diagnostic checkpoint (POST code)
BIOS_F:F822                 mov     esi, 0FFFFFF80h
BIOS_F:F828                 db      66h
BIOS_F:F828                 lgdt    fword ptr cs:[si] ; 加载全局描述符表(GDT)的地址, 从代码段(cs)的 [si] 地址处加载一个 48 位的 GDT 指针
BIOS_F:F82D                 mov     eax, cr0
BIOS_F:F830                 or      eax, 3
BIOS_F:F834                 mov     cr0, eax          ; 启用保护模式
BIOS_F:F837                 mov     eax, cr4
BIOS_F:F83A                 or      eax, 600h
BIOS_F:F840                 mov     cr4, eax
BIOS_F:F843                 mov     ax, 18h           ; 设置数据段寄存器
BIOS_F:F846                 mov     ds, ax
BIOS_F:F848                 assume ds:nothing
BIOS_F:F848                 mov     es, ax
BIOS_F:F84A                 assume es:nothing
BIOS_F:F84A                 mov     fs, ax
BIOS_F:F84C                 assume fs:nothing
BIOS_F:F84C                 mov     gs, ax
BIOS_F:F84E                 assume gs:nothing
BIOS_F:F84E                 mov     ss, ax
BIOS_F:F850                 assume ss:nothing
BIOS_F:F850                 mov     esi, 0FFFFFF86h
BIOS_F:F856                 jmp     large fword ptr cs:[si] ; 跳转到代码段的 0xFFFF FF86 地址处的远指针,继续执行后续代码。
BIOS_F:F85A ; ---------------------------------------------------------------------------
BIOS_F:F85A
BIOS_F:F85A loc_FF85A:                              ; CODE XREF: BIOS_F:F817↑j
BIOS_F:F85A                                         ; BIOS_F:F85C↓j
BIOS_F:F85A                 cli
BIOS_F:F85B                 hlt
BIOS_F:F85C ; ---------------------------------------------------------------------------
BIOS_F:F85C                 jmp     short loc_FF85A

简单分析后,可以很明显看到设置全局描述符表(GDT),并启用保护模式的内容,随后BIOS_F:F856 jmp large fword ptr cs:[si]跳转到代码段的 0xFFFFFF86 地址处的远指针,继续执行后续代码