运行 .reboot 之后,目标机就会被重启【参考1】
参考:
1. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-reboot–reboot-target-computer-
运行 .reboot 之后,目标机就会被重启【参考1】
参考:
1. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-reboot–reboot-target-computer-
前面的研究中提到了调用 ConOut 必须满足的2个条件,这次就研究一下其中的另外条件:
“代码起始处的 push rdi , 不一定是RDI,任何8Bytes的寄存器都可以,但是如果没有这语句在调用ConOut的时候这个函数内部会发生错误”
针对这个问题,使用单步跟踪的方式,尝试定位出现问题的位置:
1.CoreStartImage() 函数中的下列代码, 准备跳入 Hello.EFI 代码执行
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
0000000075829870 48 8B 53 38 mov rdx,qword ptr [rbx+38h]
0000000075829874 48 8B CE mov rcx,rsi
0000000075829877 40 88 7B 18 mov byte ptr [rbx+18h],dil
000000007582987B FF 53 20 call qword ptr [rbx+20h]
000000007582987E 48 89 83 A8 00 00 00 mov qword ptr [rbx+0A8h],rax
2.下面是 Hello.EFI 的代码,这次使用的代码我们已经加入了对于 rsp 的调整
Hello.asm
; reserve space for 2 arguments
sub rsp, 2 * 8
; rdx points to the EFI_SYSTEM_TABLE structure
; which is the 2nd argument passed to us by the UEFI firmware
; adding 64 causes rcx to point to EFI_SYSTEM_TABLE.ConOut
mov rcx, [rdx + 64]
; load the address of our string into rdx
lea rdx, [rel strHello]
; EFI_SYSTEM_TABLE.ConOut points to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
; call OutputString on the value in rdx
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
3. 上面的 call 会转入下面的函数中继续
ConsoleLoggerOutputString()
} else {
return (ConsoleLoggerOutputStringSplit(WString, ConsoleInfo));
000000007404CE18 E8 A3 FB FF FF call ConsoleLoggerOutputStringSplit (07404C9C0h)
}
4.跳转到下面的函数继续
ConsoleLoggerOutputStringSplit()
//
// Forward the request to the original ConOut
//
Status = ConsoleInfo->OldConOut->OutputString (ConsoleInfo->OldConOut, (CHAR16*)String);
000000007404C9CA 48 8B 42 58 mov rax,qword ptr [rdx+58h]
000000007404C9CE 48 8B DA mov rbx,rdx
000000007404C9D1 48 8B F9 mov rdi,rcx
000000007404C9D4 48 8B D1 mov rdx,rcx
000000007404C9D7 48 8B C8 mov rcx,rax
000000007404C9DA FF 50 08 call qword ptr [rax+8]
5.上述代码会继续调用下面这个函数
ConSplitterTextOutOutputString()
Status = Private->TextOutList[Index].TextOut->OutputString (
0000000075514940 48 8B 83 E0 00 00 00 mov rax,qword ptr [rbx+0E0h]
0000000075514947 48 8B D7 mov rdx,rdi
000000007551494A 4E 8B 44 30 10 mov r8,qword ptr [rax+r14+10h]
000000007551494F 49 8B C8 mov rcx,r8
0000000075514952 41 FF 50 08 call qword ptr [r8+8]
Private->TextOutList[Index].TextOut,
WString
);
6.上述代码会继续调用下面这个函数
GraphicsConsoleConOutOutputString ()
//
// Forward the request to the original ConOut
//
Status = ConsoleInfo->OldConOut->OutputString (ConsoleInfo->OldConOut, (CHAR16*)String);
0000000073F4C9CA 48 8B 42 58 mov rax,qword ptr [rdx+58h]
0000000073F4C9CE 48 8B DA mov rbx,rdx
0000000073F4C9D1 48 8B F9 mov rdi,rcx
0000000073F4C9D4 48 8B D1 mov rdx,rcx
0000000073F4C9D7 48 8B C8 mov rcx,rax
0000000073F4C9DA FF 50 08 call qword ptr [rax+8]
7.继续执行
ConSplitterTextOutOutputString
Status = Private->TextOutList[Index].TextOut->OutputString (
0000000075394940 48 8B 83 E0 00 00 00 mov rax,qword ptr [rbx+0E0h]
0000000075394947 48 8B D7 mov rdx,rdi
000000007539494A 4E 8B 44 30 10 mov r8,qword ptr [rax+r14+10h]
000000007539494F 49 8B C8 mov rcx,r8
0000000075394952 41 FF 50 08 call qword ptr [r8+8]
Private->TextOutList[Index].TextOut,
WString
);
8. 继续执行
WinNtGopBlt ( )
//LABZ Mart
CopyMem (Blt, VScreen, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) * Width);
000000007542217A 48 85 ED test rbp,rbp
000000007542217D 74 1E je WinNtGopBlt+175h (07542219Dh)
Blt = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) ((UINT8 *) BltBuffer + (DstY * Delta) + DestinationX * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
000000007542217F 48 8B 96 28 02 00 00 mov rdx,qword ptr [rsi+228h]
0000000075422186 4A 8D 0C BB lea rcx,[rbx+r15*4]
000000007542218A 48 03 8C 24 98 00 00 00 add rcx,qword ptr [BltBuffer]
VScreen = &Private->VirtualScreen[(VerticalResolution - SrcY - 1) * HorizontalResolution + SourceX];
0000000075422192 49 03 D6 add rdx,r14
0000000075422195 4C 8B C5 mov r8,rbp
VScreen = &Private->VirtualScreen[(VerticalResolution - SrcY - 1) * HorizontalResolution + SourceX];
0000000075422198 E8 5F 22 00 00 call CopyMem (0754243FCh)
9. 出乎意料的是问题发生在 CopyMem 的函数中
CopyMem()
return InternalMemCopyMem (DestinationBuffer, SourceBuffer, Length);
0000000075424471 4D 8B C6 mov r8,r14
0000000075424474 48 8B D6 mov rdx,rsi
0000000075424477 48 8B CB mov rcx,rbx
000000007542447A E8 81 CB FF FF call InternalMemCopyMem (075421000h)
10.这个函数体结构如下,出现问题的是 movdqa xmmword ptr [rsp+18h],xmm0
InternalMemCopyMem()
InternalMemCopyMem:
00000000749B1000 56 push rsi
00000000749B1001 57 push rdi
00000000749B1002 48 89 D6 mov rsi,rdx
00000000749B1005 48 89 CF mov rdi,rcx
00000000749B1008 4E 8D 4C 06 FF lea r9,[rsi+r8-1]
00000000749B100D 48 39 FE cmp rsi,rdi
00000000749B1010 48 89 F8 mov rax,rdi
00000000749B1013 73 05 jae InternalMemCopyMem+1Ah (0749B101Ah)
00000000749B1015 49 39 F9 cmp r9,rdi
00000000749B1018 73 48 jae InternalMemCopyMem+62h (0749B1062h)
InternalMemCopyMem.0:
00000000749B101A 48 31 C9 xor rcx,rcx
00000000749B101D 48 29 F9 sub rcx,rdi
00000000749B1020 48 83 E1 0F and rcx,0Fh
00000000749B1024 74 0C je InternalMemCopyMem+32h (0749B1032h)
00000000749B1026 4C 39 C1 cmp rcx,r8
00000000749B1029 49 0F 47 C8 cmova rcx,r8
00000000749B102D 49 29 C8 sub r8,rcx
00000000749B1030 F3 A4 rep movs byte ptr [rdi],byte ptr [rsi]
InternalMemCopyMem.1:
00000000749B1032 4C 89 C1 mov rcx,r8
00000000749B1035 49 83 E0 0F and r8,0Fh
00000000749B1039 48 C1 E9 04 shr rcx,4
00000000749B103D 74 2C je InternalMemCopyMem+6Bh (0749B106Bh)
00000000749B103F 66 0F 7F 44 24 18 movdqa xmmword ptr [rsp+18h],xmm0
InternalMemCopyMem.2:
00000000749B1045 F3 0F 6F 06 movdqu xmm0,xmmword ptr [rsi]
00000000749B1049 66 0F E7 07 movntdq xmmword ptr [rdi],xmm0
00000000749B104D 48 83 C6 10 add rsi,10h
00000000749B1051 48 83 C7 10 add rdi,10h
00000000749B1055 E2 EE loop InternalMemCopyMem+45h (0749B1045h)
00000000749B1057 0F AE F0 mfence
00000000749B105A 66 0F 6F 44 24 18 movdqa xmm0,xmmword ptr [rsp+18h]
00000000749B1060 EB 09 jmp InternalMemCopyMem+6Bh (0749B106Bh)
经过上面的分析,出现问题是是 movdqa xmmword ptr [rsp+18h],xmm0 或者 movdqu xmm0,xmmword ptr [rsi] 语句,对于这个指令,资料上【参考1】有如下解释:
MOVDQA – 移动对齐的双四字
将双四字从源操作数(第二个操作数)移到目标操作数(第一个操作数)。此指令可以用于在 XMM 寄存器与 128 位内存位置之间移入/移出双四字,或是在两个 XMM 寄存器之间移动。源操作数或目标操作数是内存操作数时,操作数必须对齐 16 字节边界,否则将生成一般保护性异常 (#GP)。
实际跟踪发现出现问题的时候要么是 rsp 没有16位对齐,要么是 rsi 没有对齐。因此,再回头来看这个问题应该是堆栈没有对齐导致的问题。
“3.栈按照16字节对齐
现在我们应该明白了.在调用一个函数的时候. 使用 *sub rsp,xxx**进行抬栈,函数内部则进行参数赋值.其实也是相当于push了参数.只不过它不像x86一样.在里面进行平栈了.而是外面进行平栈了.那么有个疑问.比如说我们就4个参数. 通过上面来说.我们应该申请 sub rsp,0x20个字节才对.在CALL的时候x86 x64都是一样的会将返回地址入栈. 那为什么要rsp,0x28.这样的话会多申请一个参数的值哪.
原因是这样的.栈要按照16字节对齐进行申请.
那么还有人会说.按照16字节对齐,那么我们的参数已经是16字节对齐了.比如为我们4个寄存器申请预留空间. rsp,0x20. (4 * 8 = 32 = 16j进制的 0x20) 那为什么还是会申请 rsp,0x28个字节,并且不对齐.其实是这样的.当我们在 Call函数的时候.返回地址会入栈.如果按照我们之前申请的rsp,0x20个字节的话.那么当
返回地址入栈之后,现在总共抬栈大小是 0x28个字节.并不是16进制对齐. 但是当我们一开始就申请0x28个字节.当返回地址入栈.那么就是0x28+8 = 0x30个字节. 0x30个字节不是正好跟16字节对齐吗.所以我们的疑问也就没有了.
所以申请了0x28个字节,其实多出了的8字节是要跟返回地址一样.进行栈对齐使用.
那么申请的这个8字节空间,是没有用的.只是为了对齐使用.“【参考2】
为了验证上面的说法对此,跟踪前面提到的每一次调用进行检查:
函数 | 对齐 |
Hello.asm | RSP = 00000184E41CF5C8 |
ConsoleLoggerOutputString() | RSP = 00000184E41CF578 |
ConsoleLoggerOutputStringSplit() | RSP = 00000184E41CF548 |
ConSplitterTextOutOutputString() | RSP = 00000184E41CF4F8 |
GraphicsConsoleConOutOutputString () | RSP = 00000184E41CF438 |
FlushCursor() | RSP = 00000184E41CF158 |
GraphicsConsoleConOutOutputString () | RSP = 00000184E41CF0C8 |
CopyMem() | RSP = 00000184E41CF098 |
InternalMemCopyMem() | RSP = 00000184E41CF080RDX = 00000184E84EE86C |
上面提到堆栈并不是调用到每个函数之后的,而是经过每个函数调整的堆栈,因为调用参数不同,因此弹出的参数数量会有差别。例如下面这个函数,进入之后会通过三条语句对堆栈进行调整,我们检查的位置就在 sub rsp,20h之后。
EFI_STATUS
ConsoleLoggerOutputStringSplit(
IN CONST CHAR16 *String,
IN CONSOLE_LOGGER_PRIVATE_DATA *ConsoleInfo
)
{
0000000073F2C9C0 48 89 5C 24 08 mov qword ptr [rsp+8],rbx
0000000073F2C9C5 57 push rdi
0000000073F2C9C6 48 83 EC 20 sub rsp,20h
EFI_STATUS Status;
//
// Forward the request to the original ConOut
//
Status = ConsoleInfo->OldConOut->OutputString (ConsoleInfo->OldConOut, (CHAR16*)String);
所以可以使用之前提到的方法多PUSH 一个 RXX寄存器,或者直接 SUB rsp,x8h 同样可以解决。
参考:
前面的研究中提到了调用 ConOut 必须满足的2个条件,这次就研究一下其中的一个条件:
“实践发现在调用 SystemTable->ConOut 的时候会损坏堆栈,比如下面的调用方式(按道理2个参数的情况下是通过寄存器来进行参数传递的)
push rdx
Call ConOut
pop rdx
运行之后会发现 rdx 值发生了变化”
首先需要明确的是:堆栈生长方向是从高向低。比如:PUSH RAX执行之后,会将RSP=RSP-8, RAX值存放在 RSP 给出的位置,然后;POP 动作与之相反。
开始跟踪代码,首先在调用处停下来:
执行Call 命令之后,RSP=RSP-8, 然后将 call 后面的下一条指令地址存放在 [RSP ] 之中。此时堆栈如下(简单起见,认为下面是高地址,上面是低地址。当前的IP和堆栈IP 都记作2Bytes方便查看):
跳转到了我们的 ASM 代码中
继续运行 call qword ptr [rcx+8] 之后堆栈情况:
特别注意,这里有一个 mov qword ptr [rsp+8],rbx, 运行之后会将前面一个的返回地址覆盖掉(就是 call qword ptr [rbx+20h] 指令(RIP=989B)的下一条地址)。因此,无论后面如何,这里都不可能正常返回。再进一步,观察到每个函数进入之后都有 sub esp,xxx 这样的操作,咨询天杀他回复“64位的函数调用中,参数的传递,也是要通过栈传递的,call子函数前将数据放到栈中再调用。小于四个参数的情况,一般使用RCX、RDX、R8、R9传递,但是也需要在栈中保留相应的空间。”换句话说,小于4个参数的情况会直接使用寄存器传递参数,但是同样要在堆栈上预留出这几个参数的空间。经过研究在【参考1】有如下描述:
1.在调用函数之前,会申请参数预留空间.(rcx,rdx,r8,r9)
2.函数内部,会将寄存器传参的值(rcx,rdx,r8,r9)保存到我们申请的预留空间中.
上面这两步其实就相当于x86下的 push r9 push r8 push rdx,push rcx
3.调用约定是 __fastcall.传参有rcx rdx,平栈是按照c调用约定平栈. 也就是调用者平栈.
因此,我们之前编写调用 ConOut 的方法是错误的,必须按照规则在堆栈上预留2个参数的空间。 从前面可以看到,堆栈的生长方向是由高到低,因此用 sub rsp,16 即可预留2个8Bytes。
上面就是发生堆栈损坏的原因,本质是我们违反了编译器的约定导致的。
参考:
1. https://www.cnblogs.com/iBinary/p/10959444.html x64汇编第三讲,64位调用约定与函数传参.
最近根据【参考1】研究了一下通过 WinDBG 查看 ACPI Table 的方法,需要特别注意的是,必须按照顺序执行才能取得需要查看的信息。
1.运行!rsdt命令,感觉是让 WinDBG 进行查找的动作
||0:6: kd> !rsdt
Sorry: Unable to get ACPI!AcpiInformation.
Searching for RSDP.*************************************************************************
*** ***
*** ***
*** Either you specified an unqualified symbol, or your debugger ***
*** doesn't have full symbol information. Unqualified symbol ***
*** resolution is turned off by default. Please either specify a ***
*** fully qualified symbol module!symbolname, or enable resolution ***
*** of unqualified symbols by typing ".symopt- 100". Note that ***
*** enabling unqualified symbol resolution with network symbol ***
*** server shares in the symbol path may cause the debugger to ***
*** appear to hang for long periods of time when an incorrect ***
*** symbol name is typed or the network symbol server is down. ***
*** ***
*** For some commands to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: hal!_RSDT_32 ***
*** ***
*************************************************************************
.........................................0x0effe0: Read 0x000020 of 0x000024 bytes
Could not locate the RSDT pointer
2. !acpicache 命令,显示当前系统 Cache 的ACPI Table
||0:6: kd> !acpicache
Dumping cached ACPI tables...
XSDT @(fffff7e200004018) Rev: 0x1 Len: 0x000104 TableID: CFL-ULT
DBGP @(fffff7e200005018) Rev: 0x1 Len: 0x000034 TableID: CFL-ULT
MCFG @(fffff7e200006018) Rev: 0x1 Len: 0x00003c TableID: CFL-ULT
FACP @(fffff7e200050018) Rev: 0x6 Len: 0x000114 TableID: CFL-ULT
APIC @(fffff7e200051018) Rev: 0x3 Len: 0x00012c TableID: CFL-ULT
BOOT @(fffff7e200054018) Rev: 0x1 Len: 0x000028 TableID: TIANO
DMAR @(fffff7e200055018) Rev: 0x1 Len: 0x0000a8 TableID: CFL
HPET @(fffff7e20007e018) Rev: 0x1 Len: 0x000038 TableID: CFL-ULT
FPDT @(ffffe3020d0fe9e8) Rev: 0x1 Len: 0x000044 TableID: CFL-ULT
DSDT @(ffffe3020d400018) Rev: 0x2 Len: 0x045806 TableID: CFL-ULT
SSDT @(ffffe3020d0a7018) Rev: 0x2 Len: 0x001b1c TableID: CpuSsdt
SSDT @(ffffe3020f31f018) Rev: 0x2 Len: 0x0031c6 TableID: SaSsdt
SSDT @(ffffe3020f322208) Rev: 0x2 Len: 0x00045a TableID: Tpm2Tabl
SSDT @(ffffe3020f322698) Rev: 0x2 Len: 0x000046 TableID: MeSsdt
TPM2 @(ffffe3020f322708) Rev: 0x3 Len: 0x000034 TableID: CFL
UEFI @(ffffe3020f322768) Rev: 0x1 Len: 0x000042 TableID: CFL-ULT
SSDM @(ffffe3020f3227d8) Rev: 0x1 Len: 0x000055 TableID:
SSDT @(ffffe3020f322858) Rev: 0x2 Len: 0x000538 TableID: PerfTune
ECDT @(ffffe3020f322db8) Rev: 0x1 Len: 0x000069 TableID: CFL-ULT
SSDT @(ffffe3020d05e018) Rev: 0x2 Len: 0x002f5c TableID: CnlU_Rvp
SSDT @(ffffe3020e7fa978) Rev: 0x2 Len: 0x000fae TableID: Ther_Rvp
SSDT @(ffffe3020d05b018) Rev: 0x2 Len: 0x0029c5 TableID: xh_whld4
SSDT @(ffffe3020d0a5018) Rev: 0x2 Len: 0x001b67 TableID: UsbCTabl
LPIT @(ffffe3020d0a6ba8) Rev: 0x1 Len: 0x00005c TableID: CFL-ULT
WSMT @(ffffe3020d0a6c38) Rev: 0x1 Len: 0x000028 TableID: CFL-ULT
SSDT @(ffffe3020d058018) Rev: 0x2 Len: 0x0027de TableID: PtidDevc
SSDT @(ffffe3020d0a3018) Rev: 0x2 Len: 0x00149f TableID: TbtTypeC
DBG2 @(ffffe3020d0a4548) Rev: 000 Len: 0x000054 TableID: CFL-ULT
NHLT @(ffffe3020d0a1018) Rev: 000 Len: 0x001783 TableID: CFL
BGRT @(ffffe3020d0a2908) Rev: 0x1 Len: 0x000038 TableID: CFL-ULT
3. !fadt 命令,显示 FADT 的信息
||0:6: kd> !fadt
FADT -- fffff8036da7aee0
FADT revision is 6, which is not understood by this debugger
HEADER - fffff8036da7aee0
Signature: FACP
Length: 0x00000114
Revision: 0x06
Checksum: 0xa1
OEMID: INTEL
OEMTableID: CFL-ULT
OEMRevision: 0x20170001
CreatorID: INTL
CreatorRev: 0x20160422
FADT - BODY - fffff8036da7af04
FACS: 0x8ca12000
DSDT: 0x8cbb6000
Int Model: Dual PIC
SCI Vector: 0x009
SMI Port: 0x000000b2
ACPI On Value: 0x0f0
ACPI Off Value: 0x0f1
SMI CMD For S4 State: 0x0f2
PM1A Event Block: 0x00001800
PM1B Event Block: 0x00000000
PM1 Event Length: 0x004
PM1A Control Block: 0x00001804
PM1B Control Block: 0x00000000
PM1 Control Length: 0x002
PM2 Control Block: 0x00001850
PM2 Control Length: 0x001
PM Timer Block: 0x00001808
PM Timer Length: 0x004
GP0 Block: 0x00001860
GP0 Length: 0x020
GP1 Block: 0x00000000
GP1 Length: 0x00000000
GP1 Base: 0x00000010
C2 Latency: 0x00065
C3 Latency: 0x003e9
Memory Flush Size: 0x00000
Memory Flush Stride: 0x00000
Duty Cycle Index: 0x001
Duty Cycle Index Width: 0x003
Day Alarm Index: 0x00d
Month Alarm Index: 0x000
Century byte (CMOS): 0x032
Boot Architecture: 0x0001
The machine does not contain a legacy i8042
Flags: 0x0020c4b5
WRITEBACKINVALIDATE_WORKS .................. SET
WRITEBACKINVALIDATE_DOESNT_INVALIDATE ...... CLEAR
SYSTEM_SUPPORTS_C1 ......................... SET
P_LVL2_UP_ONLY ............................. CLEAR
PWR_BUTTON_GENERIC ......................... SET
SLEEP_BUTTON_GENERIC ....................... SET
RTC_WAKE_GENERIC ........................... CLEAR
RTC_WAKE_FROM_S4 ........................... SET
TMR_VAL_EXT ................................ CLEAR
DCK_CAP .................................... CLEAR
RESET_CAP .................................. SET
RESET_VALUE: 6
RESET_REG: System I/O - 0000000000000cf9
SEALED_CASE_CAP ............................ CLEAR
HEADLESS_CAP ............................... CLEAR
CPU_SW_SLP_CAP ............................. CLEAR
PCI_EXP_WAK ................................ SET
USE_PLATFORM_CLOCK ......................... SET
RTC_WAKE_VALID_FROM_S4 ..................... CLEAR
REMOTE_POWER_ON_CAPABLE .................... CLEAR
FADT_FORCE_CLUSTERED_APIC_MODE ............. CLEAR
FADT_FORCE_APIC_PHYSICAL_DESTINATION_MODE .. CLEAR
ACPI_HARDWARE_NOT_PRESENT .................. CLEAR
AOAC_CAPABLE_PLATFORM ...................... SET
上述实验平台为 WHL HDK,有兴趣的朋友可以试试。
参考:
1. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/other-acpi-debugging-extensions
前面对于 RU.EFI 的研究告诉我们对于 X86 来说,访问硬件信息需要的基本操作有:
1. PCI 信息的访问
2. 访问IO Port 直接访问
3. IO Port Index/Data 方式的访问
4. Memory 的访问
5. Memory Index/Data 方式的访问
6. MSR 的访问
掌握了上述的访问方法就可以访问到 X86 上的全部空间和寄存器。下面逐项介绍在 WinDBG 中的访问方法。因为大多数情况下,WinDBG 是用于调试 Windows 软件本身而不是硬件,因此很多操作都是来自个人总结如果有错误或者遗漏,恳请及时指出。
1.PCI的访问
a. !pcitree 命令可以用来查看当前系统中的PCI总线和设备信息【参考1】
实例如下
0: kd> !pcitree
SYMSRV: BYINDEX: 0x5
C:\ProgramData\Dbg\sym
pci.pdb
96732E11A7284081C982C9A015D949A81
SYMSRV: UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb - path not found
SYMSRV: UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pd_ - path not found
SYMSRV: UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\file.ptr - path not found
SYMSRV: RESULT: 0x80070003
SYMSRV: BYINDEX: 0x6
C:\ProgramData\Dbg\sym*https://msdl.microsoft.com/download/symbols
pci.pdb
96732E11A7284081C982C9A015D949A81
SYMSRV: UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb - path not found
SYMSRV: UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pd_ - path not found
SYMSRV: UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\file.ptr - path not found
SYMSRV: HTTPGET: /download/symbols/pci.pdb/96732E11A7284081C982C9A015D949A81/pci.pdb
SYMSRV: HttpQueryInfo: 801900c8 - HTTP_STATUS_OK
SYMSRV: pci.pdb from https://msdl.microsoft.com/download/symbols:copied
SYMSRV: PATH: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb
SYMSRV: RESULT: 0x00000000
DBGHELP: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb cached to C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb
DBGHELP: pci - public symbols
C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb
Bus 0x0 (FDO Ext ffffb106414d5a20)
(d=0, f=0) 80863e34 devext 0xffffb1063bfef1b0 devstack 0xffffb1063bfef060 0600 Bridge/HOST to PCI
(d=2, f=0) 80863ea0 devext 0xffffb1063bfee1b0 devstack 0xffffb1063bfee060 0300 Display Controller/VGA
(d=8, f=0) 80861911 devext 0xffffb1063bfed1b0 devstack 0xffffb1063bfed060 0880 Base System Device/'Other' base system device
(d=12, f=0) 80869df9 devext 0xffffb1063bfec1b0 devstack 0xffffb1063bfec060 1180 Unknown Base Class/Unknown Sub Class
(d=13, f=0) 80869dfc devext 0xffffb1063bfeb1b0 devstack 0xffffb1063bfeb060 0700 Simple Serial Communications Controller/Serial Port
(d=14, f=0) 80869ded devext 0xffffb1063bfea1b0 devstack 0xffffb1063bfea060 0c03 Serial Bus Controller/USB
(d=14, f=2) 80869def devext 0xffffb1063bfe91b0 devstack 0xffffb1063bfe9060 0500 Memory Controller/RAM
(d=15, f=0) 80869de8 devext 0xffffb1063b4751b0 devstack 0xffffb1063b475060 0c80 Serial Bus Controller/Unknown Sub Class
(d=16, f=0) 80869de0 devext 0xffffb1063b4671b0 devstack 0xffffb1063b467060 0780 Simple Serial Communications Controller/'Other'
(d=1d, f=0) 80869db0 devext 0xffffb1063b4691b0 devstack 0xffffb1063b469060 0604 Bridge/PCI to PCI
Bus 0x1 (FDO Ext ffffb106414e1a20)
(d=0, f=0) 144da802 devext 0xffffb106415d81b0 devstack 0xffffb106415d8060 0108 Mass Storage Controller/Unknown Sub Class
(d=1f, f=0) 80869d84 devext 0xffffb1063b47b740 devstack 0xffffb1063b47b5f0 0601 Bridge/PCI to ISA
(d=1f, f=3) 80869dc8 devext 0xffffb106414d01b0 devstack 0xffffb106414d0060 0401 Multimedia Device/Audio
(d=1f, f=4) 80869da3 devext 0xffffb106415c21b0 devstack 0xffffb106415c2060 0c05 Serial Bus Controller/Unknown Sub Class
(d=1f, f=5) 80869da4 devext 0xffffb106415c91b0 devstack 0xffffb106415c9060 0c80 Serial Bus Controller/Unknown Sub Class
Total PCI Root busses processed = 1
Total PCI Segments processed = 1
如果运行时出现类似下面的提示,需要使用.reload pci.sys 加载一下对应的 symbol
0: kd> !pcitree
Error retrieving address of PciFdoExtensionListHead
b. !pci 命令,参数比较多,这里直接给出常用的命令【参考2】
!pci 2 ff //列出当前系统中全部 PCI 设备
0: kd> !pci 2 ff
PCI Segment 0 Bus 0
00:0 8086:3e34.0b Cmd[0006:.mb...] Sts[0090:c....] Intel Host Bridge SubID:8086:7270
02:0 8086:3ea0.00 Cmd[0400:......] Sts[0010:c....] Intel VGA Compatible Controller SubID:8086:2212
08:0 8086:1911.00 Cmd[0000:......] Sts[0010:c....] Intel Other System Peripheral SubID:8086:7270
12:0 8086:9df9.30 Cmd[0002:.m....] Sts[0010:c....] Intel Other Signal Processing Controller SubID:8086:7270
13:0 8086:9dfc.30 Cmd[0000:......] Sts[0010:c....] Intel Serial Controller SubID:8086:7270
14:0 8086:9ded.30 Cmd[0406:.mb...] Sts[0290:c....] Intel Class:c:3:30 SubID:8086:7270
14:2 8086:9def.30 Cmd[0006:.mb...] Sts[0010:c....] Intel RAM Controller
15:0 8086:9de8.30 Cmd[0400:......] Sts[0010:c....] Intel Class:c:80:0 SubID:8086:7270
16:0 8086:9de0.30 Cmd[0406:.mb...] Sts[0010:c....] Intel Other Communications Controller SubID:8086:7270
1d:0 8086:9db0.f0 Cmd[0407:imb...] Sts[0010:c....] Intel PCI-PCI Bridge 0->0x1-0x1
1f:0 8086:9d84.30 Cmd[0407:imb...] Sts[0000:.....] Intel ISA Bridge SubID:8086:7270
1f:3 8086:9dc8.30 Cmd[0406:.mb...] Sts[0010:c....] Intel Audio Device SubID:8086:7270
1f:4 8086:9da3.30 Cmd[0000:......] Sts[0280:.....] Intel SMBus Controller SubID:8086:7270
1f:5 8086:9da4.30 Cmd[0402:.m....] Sts[0000:.....] Intel Class:c:80:0 SubID:8086:7270
PCI Segment 0 Bus 0x1
00:0 144d:a802.01 Cmd[0406:.mb...] Sts[0010:c....] Class:1:8:2 SubID:144d:a801
!pci f 0 2 0 0 0x200 //查看Bus 0, Device 2,function 0 上从 0 到 0x200的寄存器
0: kd> !pci f 0 2 0 0 0x200
PCI Segment 0 Bus 0
02:0 8086:3ea0.00 Cmd[0400:......] Sts[0010:c....] Intel VGA Compatible Controller SubID:8086:2212
cf8:80001000 IntPin:1 IntLine:0 Rom:0 cis:0 cap:40
MEM[0]:cf000004 MPF[2]:d000000c IO[4]:ffc1
00000000: 3ea08086 00100400 03000000 00000000
00000010: cf000004 00000000 d000000c 00000000
00000020: 0000ffc1 00000000 00000000 22128086
00000030: 00000000 00000040 00000000 00000100
00000040: 010c7009 7a6160b1 9615808c 00000000
00000050: 000001c1 00008031 00000000 8e000001
00000060: 00010000 00000000 00000000 00000000
00000070: 0092ac10 10008000 00000000 00000000
00000080: 00000000 00000000 00000000 00000000
00000090: 00000000 00000000 00000000 00000000
000000a0: 00000000 00000000 00000000 0000d005
000000b0: fee00358 00000000 00000000 00000000
000000c0: 00000000 00000000 00000000 00000000
000000d0: 00220001 00000003 00000000 00000000
000000e0: 00000000 00000000 00008000 00000000
000000f0: 00000000 00000000 00000000 8cb33018
00000100: 2001001b 00001400 00000000 00000000
00000110: 00000000 00000000 00000000 00000000
00000120: 00000000 00000000 00000000 00000000
00000130: 00000000 00000000 00000000 00000000
00000140: 00000000 00000000 00000000 00000000
00000150: 00000000 00000000 00000000 00000000
00000160: 00000000 00000000 00000000 00000000
00000170: 00000000 00000000 00000000 00000000
00000180: 00000000 00000000 00000000 00000000
00000190: 00000000 00000000 00000000 00000000
000001a0: 00000000 00000000 00000000 00000000
000001b0: 00000000 00000000 00000000 00000000
000001c0: 00000000 00000000 00000000 00000000
000001d0: 00000000 00000000 00000000 00000000
000001e0: 00000000 00000000 00000000 00000000
000001f0: 00000000 00000000 00000000 00000000
00000200: 0001000f
2. IO Port 的访问
IO port to read and write【参考3】
Function | Command | Description / mnemonic |
Read IO port | ib, iw, id | Input from port (byte, word, double word) |
Write IO port | ob, ow, od | Output to port (byte, word, double word) |
实例如下 :访问70/71 上面的 CMOS,这个位置是当前的Second 所以过了一段时间会有变化
0: kd> !ob 70 0
0: kd> !ib 71
00000071: 000000000059
0: kd> !ob 70 0
0: kd> !ib 71
00000071: 000000000004
3. Memory 的访问
对于我们来说关注点通常只是物理内存,可以通过 !d* 命令来访问到【参考1】
实例如下 :!db 0xFFFFFF00 l 0x100 //按照Byte访问0xFFFFFF00,长度为 0x100 字节
目标机上同时使用 RW Everything 查看,可以看到结果相同:
4. MSR 的访问
可以通过 rdmsr 和 wrmsr 来实现,例如:
0: kd> rdmsr 0x2ff
msr[2ff] = 00000000`00000c06
此外,CPUID也可以被看作是一种MSR 可以用 CPUID 指令进行访问【参考4】:
0: kd> !cpuid
CP F/M/S Manufacturer MHz
0 6,142,11 GenuineIntel 1992
1 6,142,11 GenuineIntel 1992
2 6,142,11 GenuineIntel 1992
3 6,142,11 GenuineIntel 1992
4 6,142,11 GenuineIntel 1992
5 6,142,11 GenuineIntel 1992
6 6,142,11 GenuineIntel 1992
7 6,142,11 GenuineIntel 1992
不过看起来能够提供的信息还是比较有限的。
参考:
Subst 命令是一个很老的 DOS命令,通过它可以将一个目录映射为一个盘符。
例如,我本机当前只有一个盘符c:
通过 subst d: 201903, 即可将 buildbs\201903 这个目录映射为 D:
再次查看会多出一个 d: 盘符
对于我们BIOS编译来说,有时候会发生BUILD Debug版本的时候 Code 爆掉了,超过容量大小的限制。其中很大原因是目录过长。代码中很多DEBUG宏使用了 __FILE__ 定义,在编译中会展开为当前文件的完整路径,凭空要多出十几个字节。累积下来数量可观。为此,可以将你的编路径映射为一个盘符然后进入盘符编译。这样编译过程中路径是 D: 这样可以减少尺寸:
使用完毕后,可以使用 subst 盘符 /d 来取消映射。
前一段时间参加创客嘉年华,惊奇的发现他们在这个活动上有一个摊位,更惊奇的是他们的摊位主打竟然是玩具。然后和他们聊了两句发现他们这些年转型向玩具市场进军。
转过天,我修理东西,拿出了他们家生产的螺丝刀,发现竟然生锈了。
除了这一套螺丝刀,我还有一个游标卡尺也是他们家的
工具一直存放在家里,看起他们他们用的材质是很有问题的。如果非要加一个条件的话那就是:宝工产品不适合南方地区使用(作为东北人,过了山海关都算是南方)。
前面介绍了如何使用汇编语言直接编写UEFI Application,这里我偶然发现一个问题:用汇编生成的EFI 文件中,DOS 头部看起来多了一些东西。比如十六进制查看之前的 NasmUEFI.EFI 文件:
对比 Hello.EFI:
可以看到有些内容被清空为0x00. 首先怀疑的是编译过程中有工具来完成这个动作。于是,重新编译 Hello.EFI 观察到在末期有下面的操作:
Generating code
Finished generating code
"GenFw" -e UEFI_APPLICATION -o c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\Hello\Hello\DEBUG\Hello.efi c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\Hello\Hello\DEBUG\Hello.dll
这个操作输入的是hello.dll 输出为 hello.efi,观察发现DLL 中还存在一些字符串,到了EFI 中会消失。接下来在窗口中直接运行 GenFw 来从 DLL生成EFI文件,最终确认是这个工具来完成移除多余的字符工作的,准确的说只是用0xFF覆盖,并没有引起文件长度的变化。
接下来查看 GenFW 的代码, \BaseTools\Source\C\GenFw\GenFw.c 其中下面就是我们需要找到的位置:
//
// Zero all unused fields of the DOS header
//
if (DosHdr != NULL) {
memcpy (&BackupDosHdr, DosHdr, sizeof (EFI_IMAGE_DOS_HEADER));
memset (DosHdr, 0, sizeof (EFI_IMAGE_DOS_HEADER));
DosHdr->e_magic = BackupDosHdr.e_magic;
DosHdr->e_lfanew = BackupDosHdr.e_lfanew;
for (Index = sizeof (EFI_IMAGE_DOS_HEADER); Index < (UINT32 ) DosHdr->e_lfanew; Index++) {
FileBuffer[Index] = (UINT8) DosHdr->e_cp;
}
}
特别提醒:目前提供的编译辅助工具都是 X86 版本的,因此需要在 x86 Native Tools Command Prompt 窗口下编译,如果使用X64会出现编译报错。
本文只是为了简单试验,并没有考虑一些特殊情况。针对上面的代码进行修改,插入一句话即可:
strcpy(&FileBuffer[sizeof (EFI_IMAGE_DOS_HEADER)],"www.lab-z.com");
这样,再次使用 GenFw 来进行DLL到EFI的转换,可以看到 EFI 文件中DOS Header后面被插入了我们期望的字符串。
通过上述的方法可以在 EFI 文件中自动加入自定义的字符串,通过这样的方法可以制作一些特殊的 EFI 文件,比如只能在特别BIOS上运行的 EFI文件。
使用汇编语言来编写UEFI Application 完全没有问题,理论上编写 DXE Driver 也没有问题。最近抽空研究了一下这个问题。
从网上的资料来说,可以使用 FASM来完成这个工作,在http://x86asm.net/articles/uefi-programming-first-steps/ 可以看到一篇名为 “UEFI Programming – First Steps”的文章。但是经过我的实验使用 FASM 最出来的EFI 在文件头部上(PE Header)就存在很大的问题,比如: Image Size 给出来的不正确,还有 BaseImage 给出的不正确。有可能是我没有使用正确的参数导致的,但是看起来研究会很麻烦,并且最终能否成功严重存疑于是放弃了。
接下来研究如何使用 Nasm 来实现编写一个 UEFI Application。在 https://hackerpulp.com/os/os-development-windows-1-building-uefi-applications-nasm/ 和 https://github.com/BrianOtto/nasm-uefi 找到了一个例子,经过修改可以正常工作。
1.原文是编写一个 BootLoader,所以最后完成显示之后Hang 住即可,这里我们需要正常返回,最终ASM 代码如下:
; Copyright 2018-2019 Brian Otto @ https://hackerpulp.com
;
; Permission to use, copy, modify, and/or distribute this software for any
; purpose with or without fee is hereby granted, provided that the above
; copyright notice and this permission notice appear in all copies.
;
; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
; AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
; PERFORMANCE OF THIS SOFTWARE.
; generate 64-bit code
bits 64
; contains the code that will run
section .text
; allows the linker to see this symbol
global _start
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001729
struc EFI_TABLE_HEADER
.Signature RESQ 1
.Revision RESD 1
.HeaderSize RESD 1
.CRC32 RESD 1
.Reserved RESD 1
endstruc
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
struc EFI_SYSTEM_TABLE
.Hdr RESB EFI_TABLE_HEADER_size
.FirmwareVendor RESQ 1
.FirmwareRevision RESD 1
.ConsoleInHandle RESQ 1
.ConIn RESQ 1
.ConsoleOutHandle RESQ 1
.ConOut RESQ 1
.StandardErrorHandle RESQ 1
.StdErr RESQ 1
.RuntimeServices RESQ 1
.BootServices RESQ 1
.NumberOfTableEntries RESQ 1
.ConfigurationTable RESQ 1
endstruc
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1016807
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
.Reset RESQ 1
.OutputString RESQ 1
.TestString RESQ 1
.QueryMode RESQ 1
.SetMode RESQ 1
.SetAttribute RESQ 1
.ClearScreen RESQ 1
.SetCursorPosition RESQ 1
.EnableCursor RESQ 1
.Mode RESQ 1
endstruc
_start:
push rax ;ConOut requires a push here. I don't know why
; reserve space for 4 arguments
sub rsp, 4 * 8
; rdx points to the EFI_SYSTEM_TABLE structure
; which is the 2nd argument passed to us by the UEFI firmware
; adding 64 causes rcx to point to EFI_SYSTEM_TABLE.ConOut
mov rcx, [rdx + 64]
; load the address of our string into rdx
lea rdx, [rel strHello]
; EFI_SYSTEM_TABLE.ConOut points to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
; call OutputString on the value in rdx
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
add rsp, 4 * 8
pop rax
ret
codesize equ $ - $$
; contains nothing - but it is required by UEFI
section .reloc
; contains the data that will be displayed
section .data
; this must be a Unicode string
strHello db __utf16__ `Hello World !\n\r\0`
datasize equ $ - $$
上面代码很简单,就是根据传入的参数调用 EFI_SYSTEM_TABLE.ConOut 来完成字符串显示。特别的,根据之前的经验要在调用ConOut的时候多向堆栈压入一个8字节内容,否则模拟器会崩溃。
2.使用 Nasm(推荐使用 NASM 2.14 win64 或者更高版本)编译生成 OBJ 文件:
nasm -f win64 nasmuefi.asm
3.使用 Link 来生成 EFI 文件。原文使用的参数不全,导致编译出来的EFI 文件无法运行。经过研究可以使用下面的参数:
link /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /DLL /ENTRY:_start /SUBSYSTEM:EFI_APPLICATION /SAFESEH:NO /BASE:0 /DRIVER NasmUEFI.obj
(可以加入/DEBUG 生成带有 PDB文件名信息的EFI 文件)
这样我们得到了 NasmUEFI.EFI 文件,大小是 768 bytes.
可以在NT32 模拟器上运行,同样的我在实体机上验证过也可以正常运行。
可以看到,汇编语言可以用来编写UEFI Application,相比C语言来说复杂的多。
完整的代码下载:
目标:对于一个已经存在,但是没有 Source Code 的 EFI Application ,通过一种方法来插入代码实现在运行时显示自定义字符串。
前面关于 EFI 文件的研究提到对于一个 EFI Application 来说,运行后会把全部内容加载到内存中。如果我们能在EFI中找到足够大的 “缝隙” ,可以将代码插其中然后通过修改EntryPoint 处加入跳转到缝隙处我们的代码执行之后再跳转会继续执行。
经过观察,我发现“MZ”标志后有一段可以使用的“缝隙”,这次试验就在这里加入代码完成。
首先遇到的问题是:我们需要插入什么样的代码来完成显示。众所周知,UEFI 下需要使用ConOut的OutputString来实现显示。当然无法手工直接编写汇编语句,于是在 \MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c文件中,直接插入要显示的代码:
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"www.lab-z.com\n\r");
EFI_STATUS Status;
if (_gUefiDriverRevision != 0) {
//
// Make sure that the EFI/UEFI spec revision of the platform is >= EFI/UEFI spec revision of the application.
//
if (SystemTable->Hdr.Revision < _gUefiDriverRevision) {
return EFI_INCOMPATIBLE_VERSION;
}
}
…….省略……
我们通过在编译过程中插入 /FAsc /Od 指令的方法强制生成汇编代码。查看上面代码生成的内容如下:
$LN25:
00000 48 89 5c 24 08 mov QWORD PTR [rsp+8], rbx
00005 57 push rdi
00006 48 83 ec 20 sub rsp, 32 ; 00000020H
; 46 :
; 47 : SystemTable->ConOut->OutputString(SystemTable->ConOut,L"www.lab-z.com\n\r");
0000a 48 8b 42 40 mov rax, QWORD PTR [rdx+64]
0000e 48 8b da mov rbx, rdx
00011 48 8b f9 mov rdi, rcx
00014 48 8d 15 00 00
00 00 lea rdx, OFFSET FLAT:??_C@_1CA@KGEHCEOJ@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AN?$AA?$AA@
0001b 48 8b c8 mov rcx, rax
0001e ff 50 08 call QWORD PTR [rax+8]
; File c:\buildbs\201903\mdepkg\library\uefibootservicestablelib\uefibootservicestablelib.c
最开始处标记的代码编译器生成的,不对应任何C语句。其中的 lea rdx,Offset 部分没有在编译阶段生成,所以为 00。从上面可以看出来,UEFI 调用Application 之后,ImageHandle 作为第一个参数放在RCX中,RDX中存放的第二个参数是指向SystemTable的指针。
接下来,我们修改Application 的 EntryPoint 为跳转,即从 0x2C0跳转到0x002。为了简单起见,使用NASM设计代码如下:
[BITS 64]
org 2C0h
jmp 0x2
Nasm 编译后再反编译,获得机器码如下:
C:\NASM>ndisasm -b 64 inst
00000000 E93DFDFFFF jmp qword 0xfffffffffffffd42
接下来我们需要恢复现场,去掉前面ApplicationEntryPoint.c 插入的代码后再重新编译检查EFI中需要替换的代码。同样查看生成的ApplicationEntryPoint.cod文件:
_ModuleEntryPoint PROC ; COMDAT
; File c:\buildbs\201903\mdepkg\library\uefibootservicestablelib\uefibootservicestablelib.c
; 62 : gBS = SystemTable->BootServices;
00000 48 8b 42 60 mov rax, QWORD PTR [rdx+96]
00004 48 89 05 00 00
00 00 mov QWORD PTR gBS, rax
; File c:\buildbs\201903\mdepkg\library\uefiruntimeservicestablelib\uefiruntimeservicestablelib.c
; 46 : gRT = SystemTable->RuntimeServices;
0000b 48 8b 42 58 mov rax, QWORD PTR [rdx+88]
0000f 48 89 05 00 00
00 00 mov QWORD PTR gRT, rax
; File c:\buildbs\201903\mdepkg\library\uefibootservicestablelib\uefibootservicestablelib.c
; 50 : gImageHandle = ImageHandle;
00016 48 89 0d 00 00
00 00 mov QWORD PTR gImageHandle, rcx
就是说,我们需要将原来在 0x2C0处的值替换成跳转的代码 E9 7D FD FF FF 。原来处于0x2C0处的机器可以从COD文件中看到(特别注意,我们打开 EFI看到0x2C0处的机器是 48 8B 42 60 48 89 05 45 1B 00 00,这是OBJ文件在Link 之后的结果,和CL.EXE 直接生成的有所差别)。
之后,我们需要跳转到它的下一条语句(mov QWORD PTR gBS, rax这个的下一条)在 0x2C0+ 0xB。
有了上面的经验接下来设计位于 0x02 处的代码:
BITS 64
DEFAULT REL
nop
nop ;还可以使用ORG 2 设定编译的起始偏移,这里使用NOP 是为了便于反编译观察
;EFI文件之前EnteryPoint处的动作我们需要重新做一次
DB 0x48,0x8b,0x42,0x60 ; mov rax, QWORD PTR [rdx+96]
DB 0x48,0x89,0x05,0x03,0x1E,0x00,0x00 ;mov QWORD PTR gBS, rax
push rdi ;为了保证OutputString()正常工作,需要多Push 8Bytes
push rcx
push rdx
sub rsp,80 ;从实践上来看,下面的函数会损坏堆栈
mov rax,[dword rdx+64] ;SystemTable->ConOut
lea rdx,[MyString]
mov rcx,rax
call [rax+8] ;Call SystemTable->ConOut->OutputString()
add rsp,80
pop rdx
pop rcx
pop rdi
jmp 0x2CB
MyString:
db "www.lab-z.com",0
直接使用 Nasm inst.asm 即可编译,编译后再使用 ndisasm -b 64 inst反编译查看:
可以看到反编译后,前面的指令都是能够和汇编代码对应上的。接下来就是直接将上面的代码写入从 0x02开始的EFI文件中。
之后可以在 NT32模拟器上运行,同样也可以在实体机上运行:
接下来介绍解决这个问题的时候遇到的各种坑:
1.0x3C 处的0xB8,这是属于Dos Header 上的 E_lfanew ,给出了 PE Header 的起始位置,这里是不可以覆盖的,否则会产生 Load Error。如果你的代码很大,有可能会不小心覆盖掉,这样会导致EFI Image 加载错误。这也是为什么这次试验字符串放置在0x40的原因;
2.代码起始处的 push rdi , 不一定是RDI,任何8Bytes的寄存器都可以,但是如果没有这语句在调用ConOut的时候这个函数内部会发生错误;
3.实践发现在调用 SystemTable->ConOut 的时候会损坏堆栈,比如下面的调用方式(按道理2个参数的情况下是通过寄存器来进行参数传递的):
push rdx
Call ConOut
pop rdx
执行后会导致 rdx 内容的损坏。因此,这里采用先做PUSH,之后 sub rsp,80 把堆栈指针移开这样的操作来避免堆栈内容的损坏。
4.修改之前的 EFI文件EntryPoint 处的 48 89 05 45 1B 00 00 (mov QWORD PTR gBS, rax),这是一个相对的操作,搬移到其他地址之后需要重新修正。比如,之前这个指令位于 0x2C4:
实际访问到的位置在 0x2C4 + 7 (这个指令的长度) + 0x1B45=0x1E10;当这个指令被移动到0x006 处执行时,需要重新计算其中的偏移: 0x1E10-7-0x006=0x1E03,所以对应机器码如下:
上述使用【参考1】提供的反编译工具。此外,还有 ConOut 中字符串偏移的计算方法与此类似,有兴趣的朋友可以手工试验。
上面的问题都可以使用之前提到的动态调试方法进行查看验证,特别是一些偏移计算上,如果不是很确定可以通过 UltraEdit 进行编辑不断试验和调整。
本文提到的Hello.EFI 原始文件和修改后的文件下载:
参考: