Skip to content

Commit 1f7168a

Browse files
committed
Add SMM module with SSA and SMI functionalities
1 parent 4d5be76 commit 1f7168a

File tree

6 files changed

+303
-16
lines changed

6 files changed

+303
-16
lines changed

qiling/loader/pe_uefi.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,6 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context:
188188
ImageHandle = image_base
189189
SystemTable = self.gST
190190

191-
# set InSmm indicator
192-
self.ql.os.in_smm = isinstance(context, SmmContext)
193-
194191
# set effectively active heap
195192
self.ql.os.heap = context.heap
196193

qiling/os/uefi/protocols/EfiSmmBase2Protocol.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ class EFI_SMM_BASE2_PROTOCOL(STRUCT):
2727
"InSmram" : POINTER
2828
})
2929
def hook_InSmm(ql: Qiling, address: int, params):
30-
ql.log.info(f'InSmram = {ql.os.in_smm}')
30+
ql.log.debug(f'InSmram = {ql.os.smm.active}')
3131

32-
write_int8(ql, params["InSmram"], int(ql.os.in_smm))
32+
write_int8(ql, params["InSmram"], int(ql.os.smm.active))
3333

3434
return EFI_SUCCESS
3535

qiling/os/uefi/protocols/EfiSmmCpuProtocol.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ class EFI_SMM_SAVE_STATE_REGISTER(ENUM_UC):
104104
"Buffer" : POINTER # PTR(VOID)
105105
})
106106
def hook_SmmReadSaveState(ql: Qiling, address: int, params):
107+
Width = params['Width']
108+
Register = params['Register']
109+
CpuIndex = params['CpuIndex']
110+
Buffer = params['Buffer']
111+
112+
# currently supporting only one cpu
113+
if CpuIndex > 0:
114+
return EFI_INVALID_PARAMETER
115+
116+
data = ql.os.smm.ssa.read(Register, Width)
117+
ql.mem.write(Buffer, bytes(data))
118+
107119
return EFI_SUCCESS
108120

109121
@dxeapi(params = {
@@ -114,6 +126,18 @@ def hook_SmmReadSaveState(ql: Qiling, address: int, params):
114126
"Buffer" : POINTER # PTR(VOID)
115127
})
116128
def hook_SmmWriteSaveState(ql: Qiling, address: int, params):
129+
Width = params['Width']
130+
Register = params['Register']
131+
CpuIndex = params['CpuIndex']
132+
Buffer = params['Buffer']
133+
134+
# currently supporting only one cpu
135+
if CpuIndex > 0:
136+
return EFI_INVALID_PARAMETER
137+
138+
data = ql.mem.read(Buffer, Width)
139+
ql.os.smm.ssa.write(Register, bytes(data))
140+
117141
return EFI_SUCCESS
118142

119143
class EFI_SMM_CPU_PROTOCOL(STRUCT):

qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,13 @@ def hook_Register(ql: Qiling, address: int, params):
7979
if idx > This.MaximumSwiValue:
8080
return EFI_INVALID_PARAMETER
8181

82-
# prepare the context for the sw smi handler
83-
SwContext = EFI_SMM_SW_CONTEXT()
84-
SwContext.SwSmiCpuIndex = 0
85-
SwContext.CommandPort = idx
86-
SwContext.DataPort = 0
87-
8882
# allocate handle and return it through out parameter
8983
Handle = ql.loader.smm_context.heap.alloc(ql.pointersize)
9084
utils.write_int64(ql, DispatchHandle, Handle)
9185

9286
args = {
93-
'DispatchHandle' : Handle,
94-
'SwRegisterContext' : SwRegisterContext,
95-
'SwContext' : SwContext,
96-
'CommBufferSize' : 0
87+
'DispatchHandle' : Handle,
88+
'RegisterContext' : SwRegisterContext
9789
}
9890

9991
handlers[idx] = (DispatchFunction, args)

qiling/os/uefi/smm.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#!/usr/bin/python3
2+
3+
from typing import Any, Callable, Iterator, Mapping, Tuple
4+
5+
from unicorn.unicorn_const import UC_PROT_ALL, UC_PROT_NONE
6+
from unicorn.x86_const import *
7+
8+
from qiling import Qiling
9+
from qiling.os.const import POINTER
10+
from qiling.os.memory import QlMemoryHeap
11+
from qiling.os.uefi import utils
12+
from qiling.os.uefi.context import SmmContext
13+
from qiling.os.uefi.protocols.EfiSmmCpuProtocol import EFI_SMM_SAVE_STATE_REGISTER
14+
from qiling.os.uefi.protocols.EfiSmmSwDispatch2Protocol import EFI_SMM_SW_REGISTER_CONTEXT, EFI_SMM_SW_CONTEXT
15+
16+
class SaveStateArea:
17+
# SSA map for x64; note that it does not include all register enumerated in
18+
# EFI_SMM_SAVE_STATE_REGISTER, but only the most commonly used ones
19+
#
20+
# see: Intel SDM vol. 3 chapter 30.4.1.1
21+
offsets = {
22+
EFI_SMM_SAVE_STATE_REGISTER.GDTBASE : 0x7E8C,
23+
EFI_SMM_SAVE_STATE_REGISTER.IDTBASE : 0x7E94,
24+
EFI_SMM_SAVE_STATE_REGISTER.LDTBASE : 0x7E9C,
25+
EFI_SMM_SAVE_STATE_REGISTER.GDTLIMIT: 0x7DD0,
26+
EFI_SMM_SAVE_STATE_REGISTER.IDTLIMIT: 0x7DD8,
27+
EFI_SMM_SAVE_STATE_REGISTER.LDTLIMIT: 0x7DD4,
28+
# EFI_SMM_SAVE_STATE_REGISTER.LDTINFO : ?,
29+
30+
EFI_SMM_SAVE_STATE_REGISTER.ES : 0x7FA8,
31+
EFI_SMM_SAVE_STATE_REGISTER.CS : 0x7FAC,
32+
EFI_SMM_SAVE_STATE_REGISTER.SS : 0x7FB0,
33+
EFI_SMM_SAVE_STATE_REGISTER.DS : 0x7FB4,
34+
EFI_SMM_SAVE_STATE_REGISTER.FS : 0x7FB8,
35+
EFI_SMM_SAVE_STATE_REGISTER.GS : 0x7FBC,
36+
EFI_SMM_SAVE_STATE_REGISTER.LDTR_SEL: 0x7FC0,
37+
EFI_SMM_SAVE_STATE_REGISTER.TR_SEL : 0x7FC4,
38+
EFI_SMM_SAVE_STATE_REGISTER.DR7 : 0x7FC8,
39+
EFI_SMM_SAVE_STATE_REGISTER.DR6 : 0x7FD0,
40+
EFI_SMM_SAVE_STATE_REGISTER.R8 : 0x7F54,
41+
EFI_SMM_SAVE_STATE_REGISTER.R9 : 0x7F4C,
42+
EFI_SMM_SAVE_STATE_REGISTER.R10 : 0x7F44,
43+
EFI_SMM_SAVE_STATE_REGISTER.R11 : 0x7F3C,
44+
EFI_SMM_SAVE_STATE_REGISTER.R12 : 0x7F34,
45+
EFI_SMM_SAVE_STATE_REGISTER.R13 : 0x7F2C,
46+
EFI_SMM_SAVE_STATE_REGISTER.R14 : 0x7F24,
47+
EFI_SMM_SAVE_STATE_REGISTER.R15 : 0x7F1C,
48+
EFI_SMM_SAVE_STATE_REGISTER.RAX : 0x7F5C,
49+
EFI_SMM_SAVE_STATE_REGISTER.RBX : 0x7F74,
50+
EFI_SMM_SAVE_STATE_REGISTER.RCX : 0x7F64,
51+
EFI_SMM_SAVE_STATE_REGISTER.RDX : 0x7F6C,
52+
EFI_SMM_SAVE_STATE_REGISTER.RSP : 0x7F7C,
53+
EFI_SMM_SAVE_STATE_REGISTER.RBP : 0x7F84,
54+
EFI_SMM_SAVE_STATE_REGISTER.RSI : 0x7F8C,
55+
EFI_SMM_SAVE_STATE_REGISTER.RDI : 0x7F94,
56+
EFI_SMM_SAVE_STATE_REGISTER.RIP : 0x7FD8,
57+
58+
EFI_SMM_SAVE_STATE_REGISTER.RFLAGS : 0x7FE8,
59+
EFI_SMM_SAVE_STATE_REGISTER.CR0 : 0x7FF8,
60+
EFI_SMM_SAVE_STATE_REGISTER.CR3 : 0x7FF0,
61+
EFI_SMM_SAVE_STATE_REGISTER.CR4 : 0x7E40
62+
}
63+
64+
def __init__(self, ql: Qiling):
65+
self.ql = ql
66+
67+
self.ssa_base = ql.loader.smm_context.smram_base + 0x8000
68+
self.ssa_size = 0x8000
69+
70+
# map smram save state area, but do not make it available just yet
71+
if ql.mem.is_available(self.ssa_base, self.ssa_size):
72+
ql.mem.map(self.ssa_base, self.ssa_size, UC_PROT_NONE, '[SMRAM SSA]')
73+
74+
def read(self, regidx: EFI_SMM_SAVE_STATE_REGISTER, width: int) -> bytes:
75+
"""Retrieve a register value from SMM save state area.
76+
"""
77+
78+
reg = self.ssa_base + SaveStateArea.offsets[regidx]
79+
80+
return self.ql.mem.read(reg, width)
81+
82+
def write(self, regidx: EFI_SMM_SAVE_STATE_REGISTER, data: bytes) -> None:
83+
"""Replace a register value in SMM save state area.
84+
"""
85+
86+
reg = self.ssa_base + SaveStateArea.offsets[regidx]
87+
88+
self.ql.mem.write(reg, data)
89+
90+
class SmmEnv:
91+
SSA_REG_MAP = {
92+
UC_X86_REG_ES : (4, EFI_SMM_SAVE_STATE_REGISTER.ES),
93+
UC_X86_REG_CS : (4, EFI_SMM_SAVE_STATE_REGISTER.CS),
94+
UC_X86_REG_SS : (4, EFI_SMM_SAVE_STATE_REGISTER.SS),
95+
UC_X86_REG_DS : (4, EFI_SMM_SAVE_STATE_REGISTER.DS),
96+
UC_X86_REG_FS : (4, EFI_SMM_SAVE_STATE_REGISTER.FS),
97+
UC_X86_REG_GS : (4, EFI_SMM_SAVE_STATE_REGISTER.GS),
98+
UC_X86_REG_R8 : (8, EFI_SMM_SAVE_STATE_REGISTER.R8),
99+
UC_X86_REG_R9 : (8, EFI_SMM_SAVE_STATE_REGISTER.R9),
100+
UC_X86_REG_R10 : (8, EFI_SMM_SAVE_STATE_REGISTER.R10),
101+
UC_X86_REG_R11 : (8, EFI_SMM_SAVE_STATE_REGISTER.R11),
102+
UC_X86_REG_R12 : (8, EFI_SMM_SAVE_STATE_REGISTER.R12),
103+
UC_X86_REG_R13 : (8, EFI_SMM_SAVE_STATE_REGISTER.R13),
104+
UC_X86_REG_R14 : (8, EFI_SMM_SAVE_STATE_REGISTER.R14),
105+
UC_X86_REG_R15 : (8, EFI_SMM_SAVE_STATE_REGISTER.R15),
106+
UC_X86_REG_RAX : (8, EFI_SMM_SAVE_STATE_REGISTER.RAX),
107+
UC_X86_REG_RBX : (8, EFI_SMM_SAVE_STATE_REGISTER.RBX),
108+
UC_X86_REG_RCX : (8, EFI_SMM_SAVE_STATE_REGISTER.RCX),
109+
UC_X86_REG_RDX : (8, EFI_SMM_SAVE_STATE_REGISTER.RDX),
110+
UC_X86_REG_RSP : (8, EFI_SMM_SAVE_STATE_REGISTER.RSP),
111+
UC_X86_REG_RBP : (8, EFI_SMM_SAVE_STATE_REGISTER.RBP),
112+
UC_X86_REG_RSI : (8, EFI_SMM_SAVE_STATE_REGISTER.RSI),
113+
UC_X86_REG_RDI : (8, EFI_SMM_SAVE_STATE_REGISTER.RDI),
114+
UC_X86_REG_RIP : (8, EFI_SMM_SAVE_STATE_REGISTER.RIP),
115+
UC_X86_REG_EFLAGS : (8, EFI_SMM_SAVE_STATE_REGISTER.RFLAGS),
116+
UC_X86_REG_CR0 : (8, EFI_SMM_SAVE_STATE_REGISTER.CR0),
117+
UC_X86_REG_CR3 : (8, EFI_SMM_SAVE_STATE_REGISTER.CR3),
118+
UC_X86_REG_CR4 : (8, EFI_SMM_SAVE_STATE_REGISTER.CR4)
119+
}
120+
121+
def __init__(self, ql: Qiling):
122+
self.ql = ql
123+
self.ssa = SaveStateArea(ql)
124+
125+
# by default the system is out of smm
126+
self.active = False
127+
128+
def __mapped_smram_ranges(self) -> Iterator[Tuple[int, int]]:
129+
"""Iterate through all mapped ranges enclosed within SMRAM.
130+
"""
131+
132+
context: SmmContext = self.ql.loader.smm_context
133+
134+
smram_lbound = context.smram_base
135+
smram_ubound = smram_lbound + context.smram_size
136+
137+
for lbound, ubound, *_ in self.ql.mem.get_mapinfo():
138+
if (smram_lbound <= lbound) and (ubound <= smram_ubound):
139+
yield lbound, ubound
140+
141+
def enter(self) -> None:
142+
"""Enter SMM.
143+
144+
Save CPU state and unlock SMM resources.
145+
"""
146+
147+
self.ql.log.info(f'Entering SMM')
148+
149+
assert not self.active, 'SMM is not reentrant'
150+
151+
# unlock smram ranges for access
152+
for lbound, ubound in self.__mapped_smram_ranges():
153+
self.ql.mem.protect(lbound, ubound - lbound, UC_PROT_ALL)
154+
155+
# write cpu state to ssa (partially)
156+
# that can take place only after smram ranges have been unlocked
157+
for ucreg, (width, regidx) in SmmEnv.SSA_REG_MAP.items():
158+
val = self.ql.reg.read(ucreg)
159+
160+
pack = {
161+
8 : self.ql.pack64,
162+
4 : self.ql.pack32,
163+
2 : self.ql.pack16,
164+
1 : self.ql.pack8
165+
}[width]
166+
167+
self.ssa.write(regidx, pack(val))
168+
169+
# let os know that the code is now executing in smm
170+
self.active = True
171+
172+
def leave(self) -> None:
173+
"""Leave SMM.
174+
175+
Restore CPU state and lock SMM resources.
176+
"""
177+
178+
self.ql.log.info(f'Leaving SMM')
179+
180+
# restore cpu state from ssa (partially)
181+
# that can take place only before smram ranges have been locked
182+
for ucreg, (width, regidx) in SmmEnv.SSA_REG_MAP.items():
183+
data = self.ssa.read(regidx, width)
184+
185+
unpack = {
186+
8 : self.ql.unpack64,
187+
4 : self.ql.unpack32,
188+
2 : self.ql.unpack16,
189+
1 : self.ql.unpack8
190+
}[width]
191+
192+
self.ql.reg.write(ucreg, unpack(data))
193+
194+
# lock smram ranges for access
195+
for lbound, ubound in self.__mapped_smram_ranges():
196+
self.ql.mem.protect(lbound, ubound - lbound, UC_PROT_NONE)
197+
198+
# let os know that the code is no longer executing in smm
199+
self.active = False
200+
201+
def invoke_swsmi(self, cpu: int, idx: int, entry: int, args: Mapping[str, Any], *, onexit: Callable[[Qiling], None] = None) -> None:
202+
"""Invoke a native SWSMI handler.
203+
204+
Args:
205+
cpu: initiating logical processor index
206+
idx: swsmi index
207+
entry: swsmi handler entry point
208+
args: data arguments collected on handler registration
209+
onexit: optionally specify a method to call on handler exit
210+
"""
211+
212+
ql = self.ql
213+
heap: QlMemoryHeap = self.ql.loader.smm_context.heap
214+
215+
self.enter()
216+
217+
DispatchHandle = args['DispatchHandle']
218+
Context = heap.alloc(EFI_SMM_SW_REGISTER_CONTEXT.sizeof())
219+
CommBuffer = heap.alloc(EFI_SMM_SW_CONTEXT.sizeof())
220+
CommBufferSize = heap.alloc(ql.pointersize)
221+
222+
# setup Context
223+
args['RegisterContext'].saveTo(ql, Context)
224+
225+
# setup CommBuffer
226+
SmmSwContext = EFI_SMM_SW_CONTEXT()
227+
SmmSwContext.SwSmiCpuIndex = cpu
228+
SmmSwContext.CommandPort = idx
229+
SmmSwContext.DataPort = 0
230+
SmmSwContext.saveTo(ql, CommBuffer)
231+
232+
# setup CommBufferSize
233+
utils.ptr_write64(ql, CommBufferSize, SmmSwContext.sizeof())
234+
235+
# clean up handler resources
236+
def __cleanup(ql: Qiling):
237+
ql.log.info(f'Leaving SWSMI handler {idx:#04x}')
238+
239+
# unwind ms64 shadow space
240+
ql.reg.arch_sp += (4 * ql.pointersize)
241+
242+
# release handler resources
243+
heap.free(DispatchHandle)
244+
heap.free(Context)
245+
heap.free(CommBuffer)
246+
heap.free(CommBufferSize)
247+
248+
# release hook
249+
heap.free(cleanup_trap)
250+
hret.remove()
251+
252+
self.leave()
253+
254+
# if specified, call on-exit callback
255+
if onexit:
256+
onexit(ql)
257+
258+
# hook returning from swsmi handler
259+
cleanup_trap = heap.alloc(ql.pointersize)
260+
hret = ql.hook_address(__cleanup, cleanup_trap)
261+
262+
ql.log.info(f'Entering SWSMI handler {idx:#04x}')
263+
264+
# invoke the swsmi handler
265+
ql.os.fcall.call_native(entry, (
266+
(POINTER, DispatchHandle),
267+
(POINTER, Context),
268+
(POINTER, CommBuffer),
269+
(POINTER, CommBufferSize)
270+
), cleanup_trap)

qiling/os/uefi/uefi.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
from qiling.os.os import QlOs, QlOsUtils
1515
from qiling.os.fcall import QlFunctionCall, TypedArg
1616
from . import guids_db
17+
from qiling.os.uefi.smm import SmmEnv
1718

1819
class QlOsUefi(QlOs):
1920
def __init__(self, ql: Qiling):
2021
super().__init__(ql)
2122

2223
self.entry_point = 0
2324
self.running_module: str
24-
self.in_smm: bool
25+
self.smm: SmmEnv
2526
self.PE_RUN: bool
2627
self.heap: QlMemoryHeap # Will be initialized by the loader.
2728

@@ -195,6 +196,9 @@ def emu_error(self):
195196
self.ql.mem.show_mapinfo()
196197

197198
def run(self):
199+
# TODO: this is not the right place for this
200+
self.smm = SmmEnv(self.ql)
201+
198202
self.notify_before_module_execution(self.running_module)
199203

200204
if self.ql.entry_point is not None:

0 commit comments

Comments
 (0)