-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* UEFI inter-module example (initial to-do commit) * g * added uefi emulation * Amend the inter-example --------- Co-authored-by: Maxhonin <[email protected]>
- Loading branch information
1 parent
4139d13
commit 5e379c3
Showing
10 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
### Общее описание | ||
|
||
Этот пример демонстрирует фаззинг кода UEFI, | ||
где происходит взаимодействие между встроенными модулями. | ||
Это осуществляется на основе частичной эмуляции с помощью Dual-Emu. | ||
|
||
### Описание целевого кода | ||
|
||
Целевой код - это фрагмент из модуля консоли UEFI, | ||
который взаимодействует с модулем клавиатуры. | ||
Исходный код - функция `ShellPromptForResponse` | ||
в файле `ShellPkg/Library/UefiShellLib/UefiShellLib.c` | ||
(https://github.com/tianocore/edk2). | ||
Функция ожидает события клавиатуры (нажатие клавиши), | ||
а затем обрабатывает их. | ||
Начальная точка - вход в эту функцию, | ||
конечная точка - возврат из неё. | ||
Входные данные подаются целевому коду | ||
через функцию `ReadKeyStroke` (API модуля клавиатуры). | ||
|
||
### Сборка | ||
|
||
Рекомендуется выполнять сборку и запуск на машине с Ubuntu 22.04. | ||
После установки зависимостей и подготовки репозитория | ||
(https://github.com/tianocore/edk2.git, версия `edk2-stable202311`) | ||
нужно ввести команды сборки UEFI | ||
и запуска в эмуляторе: | ||
``` | ||
$ EmulatorPkg/build.sh | ||
$ EmulatorPkg/build.sh run | ||
``` | ||
|
||
Перед сборкой можно для удобства добавить в код печать адресов | ||
интересующих функций (например, см. `patch.txt`). | ||
|
||
После запуска нужно поставить точку останова на целевую функцию, | ||
достичь её и сделать снимок состояния (регистры и память). | ||
Т.е. вводятся следующие команды в консоль GDB: | ||
``` | ||
run | ||
... | ||
<Ctrl+C> | ||
break * &ShellPromptForResponse | ||
continue | ||
... | ||
info registers | ||
info proc mappings | ||
// нужна память по адресам 0x4??????? | ||
dump memory mem.40000000 0x40000000 0x40020000 | ||
... | ||
``` | ||
|
||
Чтобы достигнуть начальную точку, можно, например, | ||
дождаться появления консоли UEFI | ||
и ввести команду `mm 0x40000000`. | ||
|
||
### Общая схема эмуляции и фаззинга | ||
|
||
Здесь основой фаззинга является частичная эмуляция - | ||
выполнение интересующего фрагмента кода из начального состояния | ||
на основе скрипта пользователя. | ||
Снимок начального состояния (регистры и память) сохранён в папке `dump`. | ||
Этот снимок получен с помощью эмулятора. | ||
Пользовательский скрипт `script.py` использует API Dual-Emu, | ||
чтобы указать снимок, конечную точку, входные данные и запустить эмуляцию. | ||
В данном примере целевая функция эмулируется с параметром `ShellPromptResponseTypeYesNoAllCancel`; | ||
при эмуляции подменяются функции ввода-вывода, в том числе `ReadKeyStroke`, | ||
чтобы ввести один символ и завершить ввод. | ||
Пользователь может отдельно запустить эмуляцию (конкретное или символьное выполнение) | ||
на некотором образце входных данных, | ||
а также может запустить фаззинг с DSE. | ||
|
||
### Запуск | ||
|
||
Для запуска примера не требуется компиляция ПО и получение снимка - | ||
в файлах примера уже присутствует готовый к запуску снимок и всё необходимое. | ||
|
||
Рекомендации по подготовке Dual-Emu к запуску | ||
можно посмотреть [здесь](../Common/README.md). | ||
|
||
Запуск конкретного выполнения: | ||
|
||
``` | ||
/path/to/python3 ./script.py --qiling -i ./input | ||
``` | ||
|
||
Запуск символьного выполнения: | ||
|
||
``` | ||
/path/to/python3 ./script.py --angr -i ./input -o ./out | ||
``` | ||
|
||
Запуск фаззинга: | ||
|
||
``` | ||
rm -rf out; /path/to/crusher/bin_x86-64/fuzz_manager --start 4 --eat-cores 1 --dse-cores 1 -i ./in -o ./out -I dualemu -T dualemu -t 5000 -- ./script.py | ||
``` | ||
|
||
|
31 changes: 31 additions & 0 deletions
31
Examples/Crusher/Linux/UEFI/DualEmuInterMod/dump/info.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"sections": { | ||
"0x40000000": "mem.40000000", | ||
"0x41000000": "mem.41000000", | ||
"0x44000000": "mem.44000000", | ||
"0x48000000": "mem.48000000" | ||
}, | ||
"arch": "x86_64", | ||
"os": "linux", | ||
"rootfs": "/path/to/rootfs", | ||
"regs": { | ||
"rax": "0x0", | ||
"rbx": "0x1", | ||
"rcx": "0x2", | ||
"rdx": "0x0", | ||
"rsi": "0xff", | ||
"rdi": "0x48d739e8", | ||
"rbp": "0x44f90420", | ||
"rsp": "0x44f90288", | ||
"r8": "0x44f90300", | ||
"r9": "0x0", | ||
"r10": "0x8", | ||
"r11": "0x246", | ||
"r12": "0x0", | ||
"r13": "0x0", | ||
"r14": "0x0", | ||
"r15": "0x40000003", | ||
"rip": "0x48d0f479", | ||
"eflags": "0x246" | ||
} | ||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
X |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
X |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
diff --git a/ShellPkg/Library/UefiShellLib/UefiShellLib.c b/ShellPkg/Library/UefiShellLib/UefiShellLib.c | ||
index 746c9ccece..c42c54eb29 100644 | ||
--- a/ShellPkg/Library/UefiShellLib/UefiShellLib.c | ||
+++ b/ShellPkg/Library/UefiShellLib/UefiShellLib.c | ||
@@ -3528,6 +3528,16 @@ ShellPromptForResponse ( | ||
IN OUT VOID **Response OPTIONAL | ||
) | ||
{ | ||
+ static int first = 1; | ||
+ if (first) { | ||
+ typedef unsigned long long ull; | ||
+ ShellPrintEx (-1, -1, L"\n"); | ||
+ ShellPrintEx (-1, -1, L"&ShellPromptForResponse = 0x%llX\n", (ull) &ShellPromptForResponse); | ||
+ ShellPrintEx (-1, -1, L"gBS->WaitForEvent = 0x%llX\n", (ull) gBS->WaitForEvent); | ||
+ ShellPrintEx (-1, -1, L"gST->ConIn->ReadKeyStroke = 0x%llX\n", (ull) gST->ConIn->ReadKeyStroke); | ||
+ ShellPrintEx (-1, -1, L"ShellPrintEx = 0x%llX\n", (ull) &ShellPrintEx); | ||
+ first = 0; | ||
+ } | ||
EFI_STATUS Status; | ||
EFI_INPUT_KEY Key; | ||
UINTN EventIndex; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
from pathlib import Path | ||
import dual_emu | ||
import os, sys | ||
import json | ||
import qiling | ||
|
||
dump_path = Path(__file__).parent / 'dump' / 'info.json' | ||
args = dual_emu.parse_args_cli(input_file_required=True) | ||
|
||
arch_name = None | ||
if args.qiling: | ||
arch_name = "x8664" | ||
if args.angr: | ||
arch_name = "x86_64" | ||
# set the "arch" field in the config | ||
os.system(f'sed -i \' s/"arch":.*/"arch": "{arch_name}",/ \' dump/info.json') | ||
|
||
orig_qiling = qiling.Qiling | ||
def wrap_qiling(*args, **kw): | ||
ql = orig_qiling(*args, **kw) | ||
ql.mem.unmap_all() # to remove unneeded internal mappings | ||
return ql | ||
qiling.Qiling = wrap_qiling | ||
|
||
# return address | ||
ex = [0x48D3F48E] | ||
emu = dual_emu.make_emulator_with_input(args.angr, args.input, dump_file=dump_path, exits=ex, | ||
lighthouse_out_path=args.lighthouse) | ||
if args.qiling: | ||
emu.hook_fuzzer_start(emu.start_addr) | ||
|
||
# NOTE: considering the UEFI calling convention and 2-byte chars | ||
|
||
# at ShellPromptForResponse | ||
def entry_hook(emu): | ||
print('Entry Hook') | ||
# set Type to ShellPromptResponseTypeYesNoAllCancel | ||
emu.write_reg('rcx', 4) | ||
|
||
emu.hook_addr(emu.start_addr, entry_hook) | ||
|
||
# helper to return from a function | ||
def retfun(emu, val=None): | ||
sp = emu.read_reg_i("rsp") | ||
ra = emu.read_mem_u64(sp) | ||
if val != None: | ||
emu.write_reg("rax", val) | ||
emu.write_reg("rip", ra) | ||
emu.write_reg("rsp", sp + 8) | ||
|
||
# replacing WaitForEvent | ||
def WaitForEvent(emu): | ||
print("WaitForEvent") | ||
# skipping this function | ||
retfun(emu) | ||
|
||
emu.hook_addr(0x44F9D2C3, WaitForEvent) | ||
|
||
# replacing ReadKeyStroke | ||
def ReadKeyStroke(emu): | ||
# put the first input byte to Key.UnicodeChar and return EFI_SUCCESS | ||
print("ReadKeyStroke") | ||
ptr = emu.read_reg_i("rdx") | ||
emu.cur_input.mark_symbolic_span(0, 1) | ||
emu.write_mem_b(ptr+0x2, emu.cur_input.get_span(0, 1)) | ||
retfun(emu, 0) | ||
|
||
emu.hook_addr(0x4407A669, ReadKeyStroke) | ||
|
||
# replacing ShellPrintEx | ||
def ShellPrintEx(emu): | ||
# printing the given message to the screen | ||
sys.stdout.write("ShellPrintEx: ") | ||
fmt = emu.read_reg_i("r8") | ||
arg = emu.read_reg_i("r9") | ||
i = 0 | ||
while (True): | ||
ch = emu.read_mem_u16(fmt + i) | ||
if (ch == ord('\n')): | ||
sys.stdout.write(" <\\n> ") | ||
i += 2 | ||
elif (ch == ord('\r')): | ||
sys.stdout.write(" <\\r> ") | ||
i += 2 | ||
elif (ch == ord('%')): | ||
if (nch := emu.read_mem_u16(fmt+i+2) == ord('c')): | ||
sys.stdout.write(chr(arg)) | ||
i += 4 | ||
else: | ||
sys.stdout.write(chr(ch)) | ||
i += 2 | ||
elif (ch == ord('\0')): | ||
break | ||
else: | ||
sys.stdout.write(chr(ch)) | ||
i += 2 | ||
sys.stdout.write('\n') | ||
retfun(emu) | ||
|
||
emu.hook_addr(0x48D0C5A3, ShellPrintEx) | ||
|
||
flag = False | ||
|
||
# replacing ShellGetExecutionBreakFlag | ||
def ShellGetExecutionBreakFlag(emu): | ||
print("ShellGetExecutionBreakFlag") | ||
# for the first time return false, for the next times return true | ||
global flag | ||
if (not flag): | ||
flag = True | ||
retfun(emu, 0) | ||
else: | ||
retfun(emu, 1) | ||
|
||
emu.hook_addr(0x48D09C6E, ShellGetExecutionBreakFlag) | ||
|
||
# replacing AllocateZeroPool | ||
def AllocateZeroPool(emu): | ||
print("AllocateZeroPool") | ||
# return any unused address | ||
retfun(emu, 0x40000000) | ||
|
||
emu.hook_addr(0x48D09D3C, AllocateZeroPool) | ||
|
||
emu.run() | ||
|
||
if args.angr: | ||
if emu.sim_man.errored: | ||
print('Crashes:') | ||
print(emu.sim_man.errored) | ||
|
||
dual_emu.dump_new_inputs(emu, args.out_dir, clean=True) |