Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //
- // vpgatherqq.cpp
- // Compile with msvc, targetting x64.
- //
- // @sixtyvividtails, 2024
- //
- // Demo: using vpgatherqq instruction to cancel impeding pagefault.
- // See end of this file for draw.io diagram.
- //
- // Instruction features:
- // ③: catch PAGE_GUARD or invalid access
- // ⓪: do previously illegal reads at high IRQL, safely
- //
- #include <conio.h>
- #include <stdio.h>
- #include <stdint.h>
- #include <immintrin.h>
- #include <phnt/phnt.h> // https://github.com/mrexodia/phnt-single-header
- #include <Windows.h>
- //----------------------------------------------------------------------------------------------------------------------
- // Constants.
- //----------------------------------------------------------------------------------------------------------------------
- #define PAGE_SIZE 0x1000
- #define PF_AVX2_INSTRUCTIONS_AVAILABLE 40 // 20Hx+
- #define KI_USER_SHARED_DATA_R0 0xFFFFF78000000000UI64
- constexpr int vpgather_instruction_size = 6;
- constexpr char long_line[] = "--------------------------------------------------------------------------------"
- "----------------------------------\n";
- #ifndef _M_X64
- // feel free to try x32 though
- #error unsupported arch
- #endif
- //----------------------------------------------------------------------------------------------------------------------
- // Helpers.
- //----------------------------------------------------------------------------------------------------------------------
- template <class D> requires requires(D d) { d(); }
- class Fin
- {
- private:
- [[msvc::no_unique_address]]
- D _destroyer;
- public:
- Fin(Fin&&) = delete; // implicitly removes all ctrs and assignments
- constexpr Fin(D destroyerFunctor): _destroyer{(D&&)destroyerFunctor} {}
- constexpr ~Fin() noexcept(noexcept(_destroyer())) { _destroyer(); }
- };
- #define CAT5(a, b, c, d, e) CAT5_(a, b, c, d, e)
- #define CAT5_(a, b, c, d, e) a ## b ## c ## d ## e
- #define MAKE_UNIQUE_NAME(prefix) CAT5(prefix, __, __COUNTER__, __xyz_unique__, __LINE__)
- #define FIN(...) auto MAKE_UNIQUE_NAME(xyz_$fin) = ::Fin([&]{ __VA_ARGS__; })
- static bool is_avx2_available()
- {
- if (USER_SHARED_DATA->ProcessorFeatures[PF_AVX2_INSTRUCTIONS_AVAILABLE])
- return true;
- // if OS is below 20HX, this kuser bit may be absent; use cpuid
- int regs[4];
- __cpuidex(regs, 0, 0);
- if (regs[0] < 7)
- return false;
- __cpuidex(regs, 7, 0);
- return (regs[1] >> 5) & 1; // AVX2: cpuid(7, 0).EBX[5]
- }
- _Success_(return)
- static bool get_cpu_brand_string(
- _Always_(_Post_z_) _Out_z_cap_post_count_(brand_buf_size, 48) char* brand,
- size_t brand_buf_size)
- {
- if (brand_buf_size < 48) [[unlikely]]
- {
- if (brand_buf_size)
- *brand = {};
- return false;
- }
- for (int i = 0; i < 3; ++i)
- {
- int regs[4];
- __cpuidex(regs, 0x8000'0002 + i, 0);
- ((uint32_t*)(brand))[i*4 + 0] = regs[0];
- ((uint32_t*)(brand))[i*4 + 1] = regs[1];
- ((uint32_t*)(brand))[i*4 + 2] = regs[2];
- ((uint32_t*)(brand))[i*4 + 3] = regs[3];
- }
- brand[47] = {}; // should be null-terminated, but we enforce that
- return true;
- }
- static bool is_admin()
- {
- return NtDeleteFile(&(OBJECT_ATTRIBUTES&)(const OBJECT_ATTRIBUTES&)(OBJECT_ATTRIBUTES
- {
- .Length{sizeof(OBJECT_ATTRIBUTES)},
- .ObjectName{&(UNICODE_STRING&)(const UNICODE_STRING&)(UNICODE_STRING
- {
- 22, 22, (WCHAR*)L"\\SystemRoot" // used to be "\\??\\C:\\", but it could cause BSOD (lol)
- })}
- })) != STATUS_ACCESS_DENIED;
- }
- static void print_banner()
- {
- char cpu_brand[48];
- get_cpu_brand_string(cpu_brand, sizeof(cpu_brand));
- SYSTEM_KERNEL_VA_SHADOW_INFORMATION kvas{};
- NtQuerySystemInformation(SystemKernelVaShadowInformation, &kvas, sizeof(kvas), {});
- printf("vpgatherqq test 1.1 x%u\n"
- "OS: %u.%u.%u %04X, %u cores, CPU: %s\n"
- "KVAS: %u, user-global: %u, shadow pcid: %u\n",
- 8 * (uint32_t)sizeof(size_t),
- USER_SHARED_DATA->NtMajorVersion, USER_SHARED_DATA->NtMinorVersion,
- USER_SHARED_DATA->NtBuildNumber, USER_SHARED_DATA->ImageNumberLow, USER_SHARED_DATA->ActiveProcessorCount,
- cpu_brand,
- kvas.KvaShadowEnabled, kvas.KvaShadowUserGlobal, kvas.KvaShadowPcid);
- if (kvas.KvaShadowEnabled && !is_admin())
- printf("WARNING: kvas enabled, and process is not elevated: ring0 inaccessible. You may want to rerun "
- "as admin.\n");
- printf("\n");
- }
- //----------------------------------------------------------------------------------------------------------------------
- // Code.
- //----------------------------------------------------------------------------------------------------------------------
- #if 0
- // intrinsics produce fine code with /arch:AVX2, but since we can't predict registers used, we'll use asm instead
- __declspec(noinline)
- static uint64_t __fastcall read_using_vpgatherqq(const void* sus_address, const void* known_good_address,
- _In_opt_ const uint16_t* seg_ss = nullptr)
- {
- UNREFERENCED_PARAMETER(seg_ss);
- const int64_t* src_base = nullptr; // set src base address to null for simplicity
- __m128i mask = _mm_set1_epi64x(-1); // set entire xmm reg to fff...ff
- __m128i index = _mm_set_epi64x((uint64_t)known_good_address, (uint64_t)sus_address);
- __m128i dst = _mm_set_epi64x(0x5555'6666'7777'8888, 0x1111'2222'3333'4444);
- _ReadWriteBarrier(); // ensure compiler abides
- constexpr int scale = 1; // we can scale indeces by 1/2/4/8
- dst = _mm_mask_i64gather_epi64(dst, src_base, index, mask, scale);
- return dst.m128i_u64[1];
- }
- #endif
- #pragma const_seg(push, ".text") // put constants below into executable section
- #ifdef _M_X64
- // ### common part
- // 00: 48 b8 44 44 33 33 22 movabs rax, 0x1111222233334444
- // 0A: c4 e1 f9 6e c0 vmovq xmm0, rax
- // 0F: 48 b8 88 88 77 77 66 movabs rax, 0x5555666677778888
- // 19: c4 e3 f9 22 c0 01 vpinsrq xmm0, xmm0, rax, 0x1
- // 1F: 31 c0 xor eax, eax
- // 21: c4 e1 f9 6e ca vmovq xmm1, rdx
- // 26: c4 e3 f1 22 c9 01 vpinsrq xmm1, xmm1, rcx, 0x1
- // 2C: c5 e9 76 d2 vpcmpeqd xmm2, xmm2, xmm2
- //
- // ### v1, debug regs
- // 30: c4 e2 e9 91 04 08 vpgatherqq xmm0, [rax+xmm1], xmm2
- // 36: c4 e3 f9 16 c0 01 vpextrq rax, xmm0, 0x1
- // 3C: C3 ret
- //
- // ### v2, trap flag
- // 30: 9c pushf
- // 31: 80 4c 24 01 01 or byte ptr [rsp+0x1], 0x1
- // 36: 9d popf
- // 37: c4 e2 e9 91 04 08 vpgatherqq xmm0, [rax+xmm1], xmm2
- // 3D: c4 e3 f9 16 c0 01 vpextrq rax, xmm0, 0x1
- // 43: c3 ret
- //
- // ### v3, movss
- // 30: 41 8e 10 mov ss, [r8]
- // 33: c4 e2 e9 91 04 08 vpgatherqq xmm0, [rax+xmm1], xmm2
- // 39: c4 e3 f9 16 c0 01 vpextrq rax, xmm0, 0x1
- // 3F: C3 ret
- const uint8_t asm_read_using_vpgatherqq_v1_dr[] =
- {
- 0x48, 0xB8, 0x44, 0x44, 0x33, 0x33, 0x22, 0x22, 0x11, 0x11, 0xC4, 0xE1, 0xF9, 0x6E, 0xC0, 0x48,
- 0xB8, 0x88, 0x88, 0x77, 0x77, 0x66, 0x66, 0x55, 0x55, 0xC4, 0xE3, 0xF9, 0x22, 0xC0, 0x01, 0x31,
- 0xC0, 0xC4, 0xE1, 0xF9, 0x6E, 0xCA, 0xC4, 0xE3, 0xF1, 0x22, 0xC9, 0x01, 0xC5, 0xE9, 0x76, 0xD2,
- 0xC4, 0xE2, 0xE9, 0x91, 0x04, 0x08, 0xC4, 0xE3, 0xF9, 0x16, 0xC0, 0x01, 0xC3
- };
- const uint8_t asm_read_using_vpgatherqq_v2_tf[] =
- {
- 0x48, 0xB8, 0x44, 0x44, 0x33, 0x33, 0x22, 0x22, 0x11, 0x11, 0xC4, 0xE1, 0xF9, 0x6E, 0xC0, 0x48,
- 0xB8, 0x88, 0x88, 0x77, 0x77, 0x66, 0x66, 0x55, 0x55, 0xC4, 0xE3, 0xF9, 0x22, 0xC0, 0x01, 0x31,
- 0xC0, 0xC4, 0xE1, 0xF9, 0x6E, 0xCA, 0xC4, 0xE3, 0xF1, 0x22, 0xC9, 0x01, 0xC5, 0xE9, 0x76, 0xD2,
- 0x9C, 0x80, 0x4C, 0x24, 0x01, 0x01, 0x9D, 0xC4, 0xE2, 0xE9, 0x91, 0x04, 0x08, 0xC4, 0xE3, 0xF9,
- 0x16, 0xC0, 0x01, 0xC3
- };
- const uint8_t asm_read_using_vpgatherqq_v3_movss[] =
- {
- 0x48, 0xB8, 0x44, 0x44, 0x33, 0x33, 0x22, 0x22, 0x11, 0x11, 0xC4, 0xE1, 0xF9, 0x6E, 0xC0, 0x48,
- 0xB8, 0x88, 0x88, 0x77, 0x77, 0x66, 0x66, 0x55, 0x55, 0xC4, 0xE3, 0xF9, 0x22, 0xC0, 0x01, 0x31,
- 0xC0, 0xC4, 0xE1, 0xF9, 0x6E, 0xCA, 0xC4, 0xE3, 0xF1, 0x22, 0xC9, 0x01, 0xC5, 0xE9, 0x76, 0xD2,
- 0x41, 0x8E, 0x10, 0xC4, 0xE2, 0xE9, 0x91, 0x04, 0x08, 0xC4, 0xE3, 0xF9, 0x16, 0xC0, 0x01, 0xC3
- };
- // 00: 31 c0 xor eax, eax
- // 02: c4 e1 f9 6e cc vmovq xmm1, rsp
- // 07: c4 e3 f1 22 c9 01 vpinsrq xmm1, xmm1, rcx, 0x1
- // 0D: c5 e9 76 d2 vpcmpeqd xmm2, xmm2, xmm2
- // 11: 9c pushf
- // 12: 80 4c 24 01 01 or byte ptr [rsp+0x1], 0x1
- // 17: 9d popf
- // 18: c4 e2 e9 91 04 08 vpgatherqq xmm0, [rax+xmm1], xmm2
- // 1E: c3 ret
- const uint8_t asm_read_test_using_vpgatherqq[] =
- {
- 0x31, 0xC0, 0xC4, 0xE1, 0xF9, 0x6E, 0xCC, 0xC4, 0xE3, 0xF1, 0x22, 0xC9, 0x01, 0xC5, 0xE9, 0x76,
- 0xD2, 0x9C, 0x80, 0x4C, 0x24, 0x01, 0x01, 0x9D, 0xC4, 0xE2, 0xE9, 0x91, 0x04, 0x08, 0xC3
- };
- // ### common part
- // 00: c5 fc 10 02 vmovups ymm0, [rdx]
- // 04: c5 fc 10 4a 20 vmovups ymm1, [rdx+0x20]
- // 09: c5 fc 10 52 40 vmovups ymm2, [rdx+0x40]
- //
- // ### v1, tf
- // 0E: 9c pushf
- // 0F: 80 4c 24 01 01 or [rsp+0x1], 0x1
- // 14: 9d popf
- // 15: c4 e2 6d 90 04 09 vpgatherdd ymm0, [rcx+ymm1], ymm2
- // 1B: 90 nop
- // 1C: c3 ret
- //
- // ### v2, movss
- // 0E: 8e 52 60 mov ss, [rdx+0x60]
- // 11: c4 e2 6d 90 04 09 vpgatherdd ymm0, [rcx+ymm1], ymm2
- // 17: 90 nop
- // 18: c3 ret
- //
- // ### v3, tf + movss
- // 0E: 9c pushf
- // 0F: 80 4c 24 01 01 or [rsp+0x1], 0x1
- // 14: 9d popf
- // 15: 8e 52 60 mov ss, [rdx+0x60]
- // 18: c4 e2 6d 90 04 09 vpgatherdd ymm0, [rcx+ymm1], ymm2
- // 1E: 90 nop
- // 1F: c3 ret
- const uint8_t asm_interrupt_test_using_vpgatherdd_v1_tf[] =
- {
- 0xC5, 0xFC, 0x10, 0x02, 0xC5, 0xFC, 0x10, 0x4A, 0x20, 0xC5, 0xFC, 0x10, 0x52, 0x40, 0x9C, 0x80,
- 0x4C, 0x24, 0x01, 0x01, 0x9D, 0xC4, 0xE2, 0x6D, 0x90, 0x04, 0x09, 0x90, 0xC3
- };
- const uint8_t asm_interrupt_test_using_vpgatherdd_v2_movss[] =
- {
- 0xC5, 0xFC, 0x10, 0x02, 0xC5, 0xFC, 0x10, 0x4A, 0x20, 0xC5, 0xFC, 0x10, 0x52, 0x40, 0x8E, 0x52,
- 0x60, 0xC4, 0xE2, 0x6D, 0x90, 0x04, 0x09, 0x90, 0xC3
- };
- const uint8_t asm_interrupt_test_using_vpgatherdd_v3_tf_movss[] =
- {
- 0xC5, 0xFC, 0x10, 0x02, 0xC5, 0xFC, 0x10, 0x4A, 0x20, 0xC5, 0xFC, 0x10, 0x52, 0x40, 0x9C, 0x80,
- 0x4C, 0x24, 0x01, 0x01, 0x9D, 0x8E, 0x52, 0x60, 0xC4, 0xE2, 0x6D, 0x90, 0x04, 0x09, 0x90, 0xC3
- };
- #elif defined(_M_IX86) // #ifdef _M_X64
- // 00: b8 44 44 33 33 mov eax, 0x33334444
- // 05: c5 f9 6e c0 vmovd xmm0, eax
- // 09: b8 22 22 11 11 mov eax, 0x11112222
- // 0E: c4 e3 79 22 c0 01 vpinsrd xmm0, xmm0, eax, 0x1
- // 14: b8 88 88 77 77 mov eax, 0x77778888
- // 19: c4 e3 79 22 c0 02 vpinsrd xmm0, xmm0, eax, 0x2
- // 1F: b8 66 66 55 55 mov eax, 0x55556666
- // 24: c4 e3 79 22 c0 03 vpinsrd xmm0, xmm0, eax, 0x3
- // 2A: 31 c0 xor eax, eax
- // 2C: c5 f9 6e ca vmovd xmm1, edx
- // 30: c4 e3 71 22 c9 02 vpinsrd xmm1, xmm1, ecx, 0x2
- // 36: c5 e9 76 d2 vpcmpeqd xmm2, xmm2, xmm2
- // 3A: c4 e2 e9 91 04 08 vpgatherqq xmm0, [eax+xmm1], xmm2
- // 40: c4 e3 79 16 c0 02 vpextrd eax, xmm0, 0x2
- // 46: c3 ret
- const uint8_t asm_read_using_vpgatherqq_v1_dr[] =
- {
- 0xB8, 0x44, 0x44, 0x33, 0x33, 0xC5, 0xF9, 0x6E, 0xC0, 0xB8, 0x22, 0x22, 0x11, 0x11, 0xC4, 0xE3,
- 0x79, 0x22, 0xC0, 0x01, 0xB8, 0x88, 0x88, 0x77, 0x77, 0xC4, 0xE3, 0x79, 0x22, 0xC0, 0x02, 0xB8,
- 0x66, 0x66, 0x55, 0x55, 0xC4, 0xE3, 0x79, 0x22, 0xC0, 0x03, 0x31, 0xC0, 0xC5, 0xF9, 0x6E, 0xCA,
- 0xC4, 0xE3, 0x71, 0x22, 0xC9, 0x02, 0xC5, 0xE9, 0x76, 0xD2, 0xC4, 0xE2, 0xE9, 0x91, 0x04, 0x08,
- 0xC4, 0xE3, 0x79, 0x16, 0xC0, 0x02, 0xC3
- };
- // actually, don't bother with x32
- #error unsupported arch
- #else // #elif defined(_M_IX86)
- #error unsupported arch
- #endif
- #pragma const_seg(pop) // revert constants location
- uint64_t FASTCALL read_using_vpgatherqq(const void* sus_address, const void* known_good_address,
- _In_opt_ const void* segss = nullptr);
- void FASTCALL read_test_using_vpgatherqq(const void* sus_address);
- void FASTCALL interrupt_test_using_vpgatherdd(const void* base_address, const void* ymm_data);
- __declspec(noinline)
- static NTSTATUS test_vpgatherqq_worker(int test, _Inout_ int* unexpecteds)
- {
- switch (test)
- {
- case 0:
- printf("%stest 0: data breakpoint on known_data\n", long_line);
- break;
- case 1:
- printf("%stest 1: no breakpoints, just setting trap flag before vpgatherqq\n", long_line);
- break;
- case 2:
- printf("%stest 2: data breakpoint on known_data; movss right before vpgatherqq\n", long_line);
- break;
- case 3:
- printf("%stest 3: data breakpoint on ss location; movss right before vpgatherqq\n", long_line);
- break;
- case 4:
- printf("%stest 4: data breakpoints on known_data and ss location; movss right before vpgatherqq\n", long_line);
- break;
- default:
- printf("%stest %i: UNEXPECTED\n", long_line, test);
- return STATUS_ASSERTION_FAILURE;
- }
- // note we don't even have to touch this address, as it'll be right-most in the index register
- static constinit uint64_t known_good_data = 0xAAAA'1111'4444'3333;
- static constinit uint64_t ss_value = 0x2B; // KGDT64_R3_DATA|RPL_MASK; gotta be 8-aligned coz we set 8-break on it
- // prepare suspicious address
- void* sus_address_r3{};
- SIZE_T size = 2 * PAGE_SIZE;
- NTSTATUS st = NtAllocateVirtualMemory(NtCurrentProcess(), &sus_address_r3, 0, &size, MEM_RESERVE|MEM_COMMIT,
- PAGE_READWRITE);
- if (FAILED(st))
- return st;
- FIN(SIZE_T zero{}; NtFreeVirtualMemory(NtCurrentProcess(), &sus_address_r3, &zero, MEM_RELEASE));
- // prepare #DB trap
- CONTEXT ctx
- {
- .ContextFlags = CONTEXT_DEBUG_REGISTERS,
- .Dr0 = (size_t)&known_good_data,
- .Dr2 = (size_t)&ss_value,
- .Dr7 = (test == 0 || test == 2)? 0x0003'0001: (test == 3)? 0x0030'0010: (test == 4)? 0x0033'0011: 0u,
- };
- st = NtSetContextThread(NtCurrentThread(), &ctx);
- if (FAILED(st))
- return st;
- ctx.Dr7 = 0;
- FIN(NtSetContextThread(NtCurrentThread(), &ctx));
- const void* segss_ptr = (test == 2 || test == 3 || test == 4)? &ss_value: nullptr;
- auto* read_using_vpgatherqq_fn = (decltype(&read_using_vpgatherqq))(
- (test == 0)? asm_read_using_vpgatherqq_v1_dr:
- (test == 1)? asm_read_using_vpgatherqq_v2_tf:
- asm_read_using_vpgatherqq_v3_movss);
- void* sus_address{};
- for (int subtest = 0; subtest < 13; ++subtest)
- {
- bool should_be_readable = false;
- printf("[>] test %u.%02u: ", test, subtest);
- switch (subtest)
- {
- case 0:
- // initial try, do nothing
- sus_address = sus_address_r3;
- printf("using ring3 sus address: %p\n", sus_address);
- break;
- case 1:
- // do nothing; this demonstrates we have not triggered #PF through initial read attempt
- printf("changing nothing (ensuring our observation had not side-effects)...\n");
- break;
- case 2:
- {
- uint64_t x = 0xAAAA'5555'8888'7777;
- printf("writing new value to sus address: %016I64X...\n", x);
- *(uint64_t*)sus_address = x;
- should_be_readable = true;
- break;
- }
- case 3:
- {
- printf("applying PAGE_GUARD to sus address...\n");
- SIZE_T gsize = PAGE_SIZE;
- ULONG old_prot;
- st = NtProtectVirtualMemory(NtCurrentProcess(), &sus_address, &gsize, PAGE_READWRITE|PAGE_GUARD, &old_prot);
- if (FAILED(st))
- return st;
- break;
- }
- case 4:
- // do nothing; this demonstrates PAGE_GUARD was not removed on our read attempt
- printf("changing nothing...\n");
- break;
- case 5:
- {
- // interesting fact: removing PAGE_GUARD in explicit way will not make PTE valid
- printf("removing PAGE_GUARD...\n");
- SIZE_T gsize = PAGE_SIZE;
- ULONG old_prot;
- st = NtProtectVirtualMemory(NtCurrentProcess(), &sus_address, &gsize, PAGE_READWRITE, &old_prot);
- if (FAILED(st))
- return st;
- break;
- }
- case 6:
- printf("touching address after PAGE_GUARD removal...\n");
- *(volatile char*)sus_address;
- should_be_readable = true;
- break;
- case 7:
- sus_address = (char*)nullptr - 2;
- printf("using wrapped-around sus_address: %p\n", sus_address);
- break;
- case 8:
- sus_address = (void*)0x0000'8888'7777'0000;
- printf("using non-cannonical sus_address: %p\n", sus_address);
- break;
- case 9:
- sus_address = (void*)0x9999'8888'7777'0000;
- printf("using non-cannonical sus_address: %p\n", sus_address);
- break;
- case 10:
- sus_address = (void*)KI_USER_SHARED_DATA_R0;
- printf("==> using ring0 kuser sus_address: %p\n", sus_address);
- break;
- case 11:
- sus_address = (char*)KI_USER_SHARED_DATA_R0 + PAGE_SIZE;
- printf("==> using ring0 invalid sus_address: %p\n", sus_address);
- break;
- case 12:
- printf("applying PAGE_GUARD to sus address for eflags.RF test...\n");
- sus_address = sus_address_r3;
- SIZE_T gsize = PAGE_SIZE;
- ULONG old_prot;
- st = NtProtectVirtualMemory(NtCurrentProcess(), &sus_address, &gsize, PAGE_READWRITE|PAGE_GUARD, &old_prot);
- if (FAILED(st))
- return st;
- break;
- }
- M128A xmm0{.Low{0x11}}, xmm2{.Low{0x22}};
- uint64_t sus_data = 0;
- bool debug_break_okay = false;
- bool second_try_for_test12 = false;
- NTSTATUS exception_code = STATUS_SUCCESS;
- [&]() __declspec(noinline, guard(nocf))
- {
- __try
- {
- sus_data = read_using_vpgatherqq_fn(sus_address, &known_good_data, segss_ptr);
- }
- __except ([&](EXCEPTION_POINTERS* exptrs) -> LONG
- {
- auto* exr = exptrs->ExceptionRecord;
- auto* ctx = exptrs->ContextRecord;
- #ifdef _M_X64
- M128A* xmm = &ctx->Xmm0;
- #else
- M128A xmmDummy[2]{{.Low{0x1111}}, {.Low{0x2222}}};
- M128A* xmm = (M128A*)((char*)&ctx->ExtendedRegisters + 160); // according to fxsave instruction format
- if ((ctx->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == 0)
- xmm = xmmDummy;
- #endif
- NTSTATUS expectedCode = STATUS_SINGLE_STEP;
- if (second_try_for_test12)
- expectedCode = STATUS_GUARD_PAGE_VIOLATION;
- if (test == 3)
- {
- expectedCode = (subtest == 3 || subtest == 12)?
- STATUS_GUARD_PAGE_VIOLATION:
- STATUS_ACCESS_VIOLATION;
- }
- if ((NTSTATUS)exr->ExceptionCode != expectedCode)
- {
- *unexpecteds += 1;
- printf("[!] UNEXPECTED exception: %08X at %p; rva %06I64X; DR7: %08X, DR6: %08X\n"
- " xmm0 %016I64X'%016I64X, xmm2 %016I64X'%016I64X\n",
- exr->ExceptionCode, exr->ExceptionAddress,
- (uint64_t)((size_t)exr->ExceptionAddress - (size_t)&__ImageBase),
- (uint32_t)ctx->Dr7, (uint32_t)ctx->Dr6,
- xmm[0].High, xmm[0].Low, xmm[2].High, xmm[2].Low);
- return EXCEPTION_CONTINUE_SEARCH;
- }
- if (subtest == 12 && !second_try_for_test12)
- {
- second_try_for_test12 = true;
- printf("[ ] retrying after exception, now with eflags.RF set\n");
- ctx->EFlags |= 0x10000u; // set resume flag
- return EXCEPTION_CONTINUE_EXECUTION;
- }
- if (test == 3)
- ctx->Rip += vpgather_instruction_size; // skip it
- xmm0 = xmm[0];
- xmm2 = xmm[2];
- debug_break_okay = true;
- exception_code = exr->ExceptionCode;
- return EXCEPTION_EXECUTE_HANDLER;
- }(GetExceptionInformation()))
- {
- NOTHING;
- }
- }();
- bool sus_data_read = xmm2.High == 0 && debug_break_okay;
- if (debug_break_okay)
- sus_data = xmm0.High; // real value if sus_data_read, otherwise just original xmm0 value
- bool expected = !should_be_readable ^ sus_data_read;
- bool reverse_debug_expectation = (test == 3) && subtest != 3 && (subtest <= 6 || subtest == 12);
- if (debug_break_okay && reverse_debug_expectation)
- {
- *unexpecteds += 1;
- printf("[!] UNEXPECTED: debug break triggered for test 3\n");
- }
- else if (!debug_break_okay && !reverse_debug_expectation)
- {
- *unexpecteds += 1;
- printf("[!] UNEXPECTED: debug break failed (should not happen)\n");
- }
- else if (sus_data_read)
- printf("[%c] %016I64X, was read; xmm0 %016I64X'%016I64X, xmm2 %016I64X'%016I64X\n",
- expected? '+': '!', sus_data, xmm0.High, xmm0.Low, xmm2.High, xmm2.Low);
- else
- printf("[%c] %016I64X, wasn't read; xmm0 %016I64X'%016I64X, xmm2 %016I64X'%016I64X\n",
- expected? ' ': '!', sus_data, xmm0.High, xmm0.Low, xmm2.High, xmm2.Low);
- if (debug_break_okay
- && (!expected || xmm2.Low != 0 || xmm0.Low != 0xAAAA111144443333
- || (should_be_readable && (xmm2.High != 0 || xmm0.High != 0xAAAA555588887777))
- || (!should_be_readable && (xmm2.High != UINT64_MAX || xmm0.High != 0x5555666677778888))))
- {
- *unexpecteds += 1;
- printf("[!] UNEXPECTED: unexpected state or data ^^^\n");
- }
- }
- return STATUS_SUCCESS;
- }
- static NTSTATUS test_vpgatherqq()
- {
- printf(long_line);
- printf("running regular vpgatherqq tests...\n");
- int unexpecteds = 0;
- for (int test_type = 0; test_type < 5; ++test_type)
- {
- int unexpecteds0 = unexpecteds;
- NTSTATUS st = test_vpgatherqq_worker(test_type, &unexpecteds);
- if (FAILED(st))
- {
- printf("[x] UNEXPECTED: vpgatherqq test %i failed: %08X\n", test_type, st);
- if (unexpecteds == unexpecteds0)
- ++unexpecteds;
- }
- }
- printf(long_line);
- if (unexpecteds == 0)
- printf("[+] all vpgatherqq tests done, all data and states were expected.\n");
- else
- printf("[!] all vpgatherqq tests done, got %u UNEXPECTED states. Your system is strange af, please ping "
- "@sixtyvividtails.\n", unexpecteds);
- printf(long_line);
- return unexpecteds == 0? STATUS_SUCCESS: STATUS_WAKE_SYSTEM; // infostatus
- }
- static NTSTATUS run_tests()
- {
- if (!is_avx2_available())
- return printf("[x] avx2 is not available\n"), STATUS_NOT_SUPPORTED;
- auto* prev_filter = SetUnhandledExceptionFilter([](EXCEPTION_POINTERS* exptrs) -> LONG
- {
- auto* exr = exptrs->ExceptionRecord;
- auto* ctx = exptrs->ContextRecord;
- printf("[x] UNEXPECTED: unhandled exception %08X, rip %p/%06X, please ping @sixtyvividtails\n",
- exr->ExceptionCode, (void*)ctx->Rip, (UINT)(ctx->Rip - (SIZE_T)&__ImageBase));
- return EXCEPTION_EXECUTE_HANDLER;
- });
- NTSTATUS st0 = test_vpgatherqq(); // regular
- //NTSTATUS st1 = test_vpgatherdd(); // against interrupts
- NTSTATUS st1 = STATUS_SUCCESS;
- SetUnhandledExceptionFilter(prev_filter);
- return FAILED(st0)? st0: FAILED(st1)? st1: st0 == STATUS_SUCCESS? st1: st0;
- }
- int __cdecl wmain()
- {
- print_banner();
- NTSTATUS st = run_tests();
- ULONG dummy;
- if (GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dummy) && GetConsoleProcessList(&dummy, 1) <= 1)
- {
- printf("exit status: %08X; press any key to continue...\n", st);
- _flushall();
- int c = _getch();
- if (!c || c == 0xE0) // arrow or function key, need to read one more
- (void)_getch();
- }
- return st;
- }
- //----------------------------------------------------------------------------------------------------------------------
- // Extra.
- //----------------------------------------------------------------------------------------------------------------------
- #if 0
- Sample output:
- vpgatherqq test 1.1 x64
- OS: 10.0.20348 8664, 16 cores, CPU: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
- KVAS: 0, user-global: 0, shadow pcid: 0
- ------------------------------------------------------------------------------------------------------------------
- running regular vpgatherqq tests...
- ------------------------------------------------------------------------------------------------------------------
- test 0: data breakpoint on known_data
- [>] test 0.00: using ring3 sus address: 000002AA5D180000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.01: changing nothing (ensuring our observation had not side-effects)...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.02: writing new value to sus address: AAAA555588887777...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 0.03: applying PAGE_GUARD to sus address...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.04: changing nothing...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.05: removing PAGE_GUARD...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.06: touching address after PAGE_GUARD removal...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 0.07: using wrapped-around sus_address: FFFFFFFFFFFFFFFE
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.08: using non-cannonical sus_address: 0000888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.09: using non-cannonical sus_address: 9999888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.10: ==> using ring0 kuser sus_address: FFFFF78000000000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.11: ==> using ring0 invalid sus_address: FFFFF78000001000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 0.12: applying PAGE_GUARD to sus address for eflags.RF test...
- [ ] retrying after exception, now with eflags.RF set
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- ------------------------------------------------------------------------------------------------------------------
- test 1: no breakpoints, just setting trap flag before vpgatherqq
- [>] test 1.00: using ring3 sus address: 000002AA5D180000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.01: changing nothing (ensuring our observation had not side-effects)...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.02: writing new value to sus address: AAAA555588887777...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 1.03: applying PAGE_GUARD to sus address...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.04: changing nothing...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.05: removing PAGE_GUARD...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.06: touching address after PAGE_GUARD removal...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 1.07: using wrapped-around sus_address: FFFFFFFFFFFFFFFE
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.08: using non-cannonical sus_address: 0000888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.09: using non-cannonical sus_address: 9999888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.10: ==> using ring0 kuser sus_address: FFFFF78000000000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.11: ==> using ring0 invalid sus_address: FFFFF78000001000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 1.12: applying PAGE_GUARD to sus address for eflags.RF test...
- [ ] retrying after exception, now with eflags.RF set
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- ------------------------------------------------------------------------------------------------------------------
- test 2: data breakpoint on known_data; movss right before vpgatherqq
- [>] test 2.00: using ring3 sus address: 000002AA5D180000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.01: changing nothing (ensuring our observation had not side-effects)...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.02: writing new value to sus address: AAAA555588887777...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 2.03: applying PAGE_GUARD to sus address...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.04: changing nothing...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.05: removing PAGE_GUARD...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.06: touching address after PAGE_GUARD removal...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 2.07: using wrapped-around sus_address: FFFFFFFFFFFFFFFE
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.08: using non-cannonical sus_address: 0000888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.09: using non-cannonical sus_address: 9999888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.10: ==> using ring0 kuser sus_address: FFFFF78000000000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.11: ==> using ring0 invalid sus_address: FFFFF78000001000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 2.12: applying PAGE_GUARD to sus address for eflags.RF test...
- [ ] retrying after exception, now with eflags.RF set
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- ------------------------------------------------------------------------------------------------------------------
- test 3: data breakpoint on ss location; movss right before vpgatherqq
- [>] test 3.00: using ring3 sus address: 000002AA5D180000
- [ ] 0000000000000000, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- [>] test 3.01: changing nothing (ensuring our observation had not side-effects)...
- [ ] 0000000000000000, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- [>] test 3.02: writing new value to sus address: AAAA555588887777...
- [!] AAAA555588887777, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- [>] test 3.03: applying PAGE_GUARD to sus address...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 3.04: changing nothing...
- [ ] AAAA555588887777, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- [>] test 3.05: removing PAGE_GUARD...
- [ ] AAAA555588887777, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- [>] test 3.06: touching address after PAGE_GUARD removal...
- [!] AAAA555588887777, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- [>] test 3.07: using wrapped-around sus_address: FFFFFFFFFFFFFFFE
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 3.08: using non-cannonical sus_address: 0000888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 3.09: using non-cannonical sus_address: 9999888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 3.10: ==> using ring0 kuser sus_address: FFFFF78000000000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 3.11: ==> using ring0 invalid sus_address: FFFFF78000001000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 3.12: applying PAGE_GUARD to sus address for eflags.RF test...
- [ ] retrying after exception, now with eflags.RF set
- [ ] AAAA555588887777, wasn't read; xmm0 0000000000000000'0000000000000011, xmm2 0000000000000000'0000000000000022
- ------------------------------------------------------------------------------------------------------------------
- test 4: data breakpoints on known_data and ss location; movss right before vpgatherqq
- [>] test 4.00: using ring3 sus address: 000002AA5D180000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.01: changing nothing (ensuring our observation had not side-effects)...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.02: writing new value to sus address: AAAA555588887777...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 4.03: applying PAGE_GUARD to sus address...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.04: changing nothing...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.05: removing PAGE_GUARD...
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.06: touching address after PAGE_GUARD removal...
- [+] AAAA555588887777, was read; xmm0 AAAA555588887777'AAAA111144443333, xmm2 0000000000000000'0000000000000000
- [>] test 4.07: using wrapped-around sus_address: FFFFFFFFFFFFFFFE
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.08: using non-cannonical sus_address: 0000888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.09: using non-cannonical sus_address: 9999888877770000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.10: ==> using ring0 kuser sus_address: FFFFF78000000000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.11: ==> using ring0 invalid sus_address: FFFFF78000001000
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- [>] test 4.12: applying PAGE_GUARD to sus address for eflags.RF test...
- [ ] retrying after exception, now with eflags.RF set
- [ ] 5555666677778888, wasn't read; xmm0 5555666677778888'AAAA111144443333, xmm2 FFFFFFFFFFFFFFFF'0000000000000000
- ------------------------------------------------------------------------------------------------------------------
- [+] all vpgatherqq tests done, all data and states were expected.
- ------------------------------------------------------------------------------------------------------------------
- #endif
- // Beautiful draw.io diagram. Note: not an exploit.
- // https://viewer.diagrams.net/?tags=%7B%7D&lightbox=1&highlight=0000ff&edit=_blank&layers=1&nav=1&title=vpgatherqq12.drawio.png&dark=auto#R%3Cmxfile%20scale%3D%221%22%20border%3D%220%22%3E%3Cdiagram%20name%3D%22Page-1%22%20id%3D%22ee5OSJtYosg32hOfD3Tv%22%3E7T1Zl6O4ub%2FGpzMP5QNiMTxWVXdNcs%2FMTSed5E7ykoMNdjGNjRtwLfPrryQQBi0gzGoX7nO6WGwhpG9fF9rj%2Fu3nyDk%2B%2Fxq6XrAAivu20D4vAACKqcA%2F6Mp7ekVVNSu9sot8N72mnC988%2F%2Fw0otGdvHku16c%2FTa9lIRhkPjH8sVNeDh4m6R0zYmi8LX8tW0YuKULR2fnMRe%2BbZyAvfp%2Fvps8p1ctsDpf%2F7Pn757Jk1XTTu%2FsHfLl7PXiZ8cNXwuXtC8L7TEKwyQ92r89egFavPKyPAnu5hOLvEMi84O3x%2F8%2B%2FNgpP%2F%2Fh%2F8f529vP%2Ft%2Fcr%2F9zp%2BogHefFCU7ZKy%2BAGcARH1z%2FBU07ec%2FWwvxxQnN92IaH5C7GG3UPv6BqR7jbD%2FAo8A%2Fe3XO2GviWCqdk4Jvkx%2FBol%2F3FD4mPzoH7lE0YhFE6TLRb%2F2kFF%2FwRHp%2F%2F%2FsQdV13Cm49f%2F4l%2B5jlujEDPSRy09VG4h38wYLhu5MWxh%2B76hySEf972e2VJJgUXMZ1Xea7wMl6T8tUOlsnU6pYJDSb1FECeQq2famjpwhUPfhJNaFW1b87%2BCA8O6%2Fh40bnyd7gv8E8YuV6ENgDtwukQH72Nv%2FU9t7gL6WsPtAtGLbBK74Im2AURFDdCHYBA%2FC%2BHOIlOm8QPDxjUEz8qLN066nC%2FavZjMqD5CJ8HaSECp3CboTRC9oObniDMPx0hNfDQhe0pCN5nWKuBNW2JAUD7jH6YQBaPeWiUUVf%2FsINH3w%2Fh6%2BFuF4bumbQisusF%2FotHQ2WDFxKtTK971dl2iAYadhFA6WEg8d7Q9edkH8ALKjx0An93gMeBt0V34IYlPpR%2B7rPLSYiIwOuzn3jfjs4GDfOKoEB7iMLTwfWQbKFk089kN1XLzh9TeITP1Uz8ya4%2FOXs%2FQOLgP9enQ3KCM%2FzmHGIySHiK8GOekwSKeMBAy2FAoQb9h74QLyGk7QLPOfrxcoNYOryxifFXn7bp0PAwH9wAD9nwrMCUyVDopb23wqVMgPrZC%2FdeEr0j8pHe1TNZLhNmV0Ympb6eRUONfOe5JBZmUqGTiaO7fOizxAYPMqGtgQDHim8lztB2%2F%2Fe%2B66IfNwMBLT%2FPHq3KbH0BXr6Y6N9VgARRVDKQAICBCMyyaIgAVk8AYQrl%2BSOX8LDcQBFyg5fjDmo1XvTjB19MEEkOGfHc5Bt8HlhTFNPUtOKleuoIVnVEttUoqeCQE9R1Le195L27QfMO7utvt6a53Xb0%2BpHzJiescaeiu%2BhfZzsBl1BeeuROaLNRlM1maqChNgEN47MAOiQxY3qvD5q8%2FvnysTuZ5DKeBFbwHD3rG7HDSDGlaTMfi5JHLIb5GDzmQ0SU7qURsTVpvaijrhj03DjBysXOjxNsHki1DTToj9cwQgadceBH1RfYWrVBSg8Ep%2FPZP5CMjL%2BeXfh7ttIKDXFkyIKkk7G%2Fq4dEoNslUNRsVg4CJguKWm%2BCsV5t2GTIYg2Fw9C5d%2BLvKSXex2s%2FIZYGz9k8oz%2BBt%2FcwPfcOzjrA5sUgTI1dW0zlI6gbH8NDpjifv58ZIS%2BTl7hoNYRyODlEgtzy6hHJUJQ6RAK2PiQiGRchUlkWpfAojqBco6yd2CuYjKRMOeml2MNYg%2F7DiKPkjCL298fA3%2FjJ%2BwShngLZTL69epAFRhlkVaAtWcOIanMkEa03SUSsB%2FOhVsImpzdVClJhmogueCgXU3vX33jp4ivhEVlqnABtkBIjxyP6yvo9%2FeUTxPQnK9Uh5FADbv4aqvQuXvzkmeUql7ynrH7W5aqKCIiIx6WUAFKBfeGd201BoOsocYqOJUfi1ImNDTmkfQOCpq6pZWKjskqPRpzwJaVH7YvUWDKk5puXnI7TApPuzLQlAc66Cjii7LbqSpcz3PanO9scMKIAwzu49yioBZ4dwoNXhpHyPsOvPvkBuRdGyXO4Q3yGgEEQvt4f%2FL2D%2FQRn0BiLMsRJFH7PQ2yU5cqo2mbPLUXtsJtctIBwNpFci7wALsCLVxqct7PZE76GPuahNNCQn6SMIftWMRaH%2FJDYbYC51PnwR4ZKnGjnJcxQGLTyF70c2ojg1SLuxxBwzft%2F%2FQZuldBNn66pxAh4DrhjCBvPRQm0nuiaVmOIkTBHKzVGbWEQWdOBqj01tlr21Mg6XSTDyaRxI0p3rQtnPeBYUkpqaRFjuCaYKxYlbcrSorAiAE8C6E1n1RrprK2BnrIYThpEeza8N%2BILVwXUAIwN1CsGqA34%2BYQCgj6t4OeTBT8MaJXBgQcwBaijVd99%2BILs70Qu9iAyFM%2FDxEkK564XeMVzz%2FWLp0G4%2BZ5PJAszL9zeQiG7AFZrxzUcJxdoizFQuq3q1sgAVoFHalfyR1k%2FBxpHsTI4%2Bnl%2FEMjq5yr8fELI8UmDn086%2FNwMBLqGZ7k6DwItsNZGj8LrA%2BY0TamHOXNQqscq87RnG8Xkvj47ifeCzlpz1oaaUQEsHvA%2FCohvwFKoUkBh8AgRL1izL6AgFkihJkT2%2BCyrbeDrI%2FCgZTcFfj6V%2F5OU2a6VrG237haqXxyy9vho209PBRTJFu326Zymjk3nOOljXN%2FuTN96p28aV9AalL6xon4aTjMVy8Y5RqRAWzJD%2BgczdgBzZL1Qr%2FGaNWCGsmFTUPat8lVLmlfWzub7DoPdHZ3IhtyOOH%2FNNtMDoBn8NFWr3vBS5%2FH%2BcBKA63jWdsNVrTeWt95%2BRAkA8NyWw0oArKaDoyQx%2FO9jFICBAzPSQMhZDOg7Jy0PDixF3g4pBhCZY6bsM2WfKfvFdlMeZR%2FUbmqIHbdFaKWiH8sO0KlI%2FkKfZuCsveAhR3xKKUjv4koV1J0bVw80fWT1gMgwxeAnd%2BeRrTvHM305Xy2ARDnm6Zcwx9TfvSR5zwDFOUHJpASNcP2i998WJIIKnvwbnSwNcvr5rXjz83t2Vo5eAiyF2243G9vmUTjbxgQeUslT9FIC58lBU0yeKaQYWUxdGrtU9cVM%2BauN45IO0GpH6dgUBx8C0puM%2BGE6UABxSsHSMSliRa6pDaSTFlHamfRQH41SH0x9I4LFdrtWREZjRXl8%2FAiCBWM05vlBBlUZDak4l9SOXIiwl8%2BBQIPzUy76imvpQS1lJIyy736SHOJy4zVX2uUBZW88gDVel3iAiFBLMoWsOl0j2nwzRNhS1lzt7uMQ4TyVYDpEmLW%2F8wt5tYLCqwpNoXGmD0DQ9XJ4ts5z4XIJX2%2BAwBpw41PcEwQ8PX02McZzaYEytuozBAQASh7jQgAvQr8%2FXZtn6GmhfCuXKd%2BsEs0AiWuuTcPksZgtmG4mda3ebGrXqTeTebdIIhJpsZ%2F%2F3o%2FRm4Ibx105XNFkrZuGon8AcmSSsJEKcrQa0uxs1tSbaFE88xWFJL06h7xuRJSWIkaFJGS0yxaPXp%2FyihV7vIhAWXtYMXU2UDGNfSQ%2Bd14idLRaoDwDJ63glqD5zpgq9W7B2UlAVF1OcX%2B4VGNpaQFlRbA9i4o9YxHWUAjyYvxBGOzcLO5oUCmqwp0FJ48DY1J25SFMEogEE%2FYptNCceNg1qOZk1oWw1efmWQI4%2FOspgeuLAFzG3LS2PcUxyj6Iix99j%2B%2F3V2pb%2BODB35RSc9uUKu10XqxEMOmCbiAv%2BNFpGZH8KZOmSlQZEUORrAi%2B6o0odVtGxHXi59yjPkkOMpFaILptln9yeS0QZqiea4GoSms9vv8SDWNkq19ID%2BeE9UYUtKw0mSpLQQfVmVSFNYfew88nnLaOktVx2npLO9R03HNik%2FzTkzW%2BSb4XbxxVY9UELMwNq0qoCmv1wkCHM9VRfjrOVL8ZoJPwBJbdAWMTul48Qcb0wJA1GOG89SkGyNwY39Pl%2BF5vaRuq0lv6Zgs5qkUG55XDBxUhZZLokAJ8WMPSBlGmVyeW1z%2B8KMxaQKDtnClO%2FxBlsRRnWIhSWb0z88hlzVqLEUkjAkSpfR3HDE%2FUOP36YYSRSTgGLa5MAvqDEnEeUtfdgzdw1SMcMIw9TJgmiWwOI8Kj%2Fhn9%2B5jwaI8Pj%2BIAhcqYYHMNmnZ6y3vmtiuxXzeKdM6JaCAcbC8Obq7sptfNK8jaF8UDwR%2Fh3zVN3m07f6VydZi1ROQrRsQJB7MgBrm4oMXDoHAlKqcLPx2hA%2BXJaxBjL3L6vTpoxFQaGUn%2FUUmaB08S4Tn4ue6zK6b9qqIsy%2B61Fcc4rCnDyqzikJpq6s%2BvUlCSd%2BvIZyU%2BqfamMT7VNqtuNYoIK%2BuJ3CxbDYRf9bI%2Bqe8yjGxFRm4RsKYKoBEuMqLom7eUhy4MBJ%2F%2FJdCJGrXSXVqbnjdnw2SuCqWv4Ph%2FXG%2BkcMBXWdLfo9dKO6s144QtYvRE69xUNzPU9A3Pf%2Fkvmu2fe94%2FbB1otIN1HuQRNs7NNq5o6hhg1xSm7zcyNvPbfsuwH9u%2BQJyrAyFJk2LdMJ0sJNsnXc0XTGk6o%2Fa7VXh6a61OCMyGlsFwfgCsKmA%2BRh5iSMh8gjgtJOM%2BwlnXW592i3PtY0Sh%2FoTVl014Ctwp7vaVb4QTYBXx9xOuOZ12y6yly7IL7W0DZxcva0hpdRBli7VAbRHxEpADEX3t6n3%2F8TTWqzbY9q5edoHi6rchRuKX485Jnr3ox4%2BfLluB3nAO1OJc19JHX%2BPuw5d6saRaWuHzY8PCinGn%2FLg1dLmRxKvyIKn77XwkwqRSLzZe2%2Fn0xNxMHmgp5F6XwpKithtlPiflTVEUDZVzVD8iBHiHNE1LgVLeE7JZryPP%2BX4RGAyo67RYIGDlPFuSec8CcGcCcCry0k0IFOeAjK2ork%2B5GBRvvbrbjSuiWW9h1APxKAghfNs10PUt6FHu8Eo9GkeXO9LpTExouH0WVOhMggeRqJR4Raj7AuWNHyMg72YLwIbTF7Uz5KUKDI%2BOvdeiNUwPAX33TRnF2j0YDh79QxzNWDgAFqbzwfhIXHrqRGFebe6aG3SeV6R%2BsAi32R%2B9H%2B6ihfO1DrVQ4Aav8XeXqAUW00ItUDyaPmPBeQj4504Q3IWHaSp3FWaIxh7ESWmnV2IUcHAACjYNoG%2BnoZNpREyxGlgTOj2pbehtpeXFH%2BIw6pu9yRM0yaouQ0ylNX%2FK2YWE5jryu2YhOl1P6FJLUlQyAI0Pkn0A5qUqQmUDn6EXB0VxPY6wOCIhj5LNZCRb6eDVFrXi6HT%2BmnxXoPwaHlC5XVEhkuzs0m5IxWzcAEkBd64Tff8TQkhv625tsqeaulJNVUN770SbbPIouHYXOHFMpsRpevALXiocdMtUDHBsa6tr%2BR1SMSDt2oNmRpKCs5LDaJ14Ab%2Bimnm9BfGig2xfOojmNaj0TtVi6%2BPlbZ7K0bz9hfPqDDp8pC5O3puf4HksVc3MztFU1KWi2tn5eS7o5L1w8tWLfLgPqB%2FCdLM56ntCZVBYX9s6CzTvrrY1v94W3WhPIw0zmxbuAmp5IF2zywP1XbZL55XtotHtwlJv6OSrk0DoO%2BArQOEWZE8%2FCP6C8PX%2B4O%2FhzoSHKQPs2OXiSDVTtZzZZJFk%2FKZQSA80ePE40ilwyCqolwwpSkPcwA%2F76C94nP4smq2qoLKTnkYV1E7nxauMPtdBHZZYNqjOTNU%2B5xZC5TU46q0Qqqq3Ls%2BcZzZeZODsujiFuoQ3%2F7I%2Fei5S2ICydU4BQsVj5L3AjUO46x8ghz5tMA9GOLo%2FBh46qQvW76lqemeLjRqNdGRNBvLWZEP7STShygDCtn77p2xjXS%2BAAgbGMqx0ow1Gu%2FwKof%2FO9bZwVm72AllLCWzGRkRt2ZBidrZTWnd2f02wUyJ8aYSkACNTCV28N29zyo4hUzx6iMz3u5CgYiE%2FFMg%2FwilnNCzcLrDjUVmQeE0JG1zrtcj8jCuz3UIop6PrJLiCEGSnie8EwfuMizW4qCFc%2FOrsvAJT2%2BJA1FQ29VPWdiaD6ElpQx0oNX1Gj8vyC%2FA3EeJCDjnastczK1nJoCKGvOlzu4olk556I9piCWHjW3gGimoRhjurZqtUV4mnVbBDP%2BLN5WRM%2FNCdhwnxxjlsoKTh1YXojQDNPYJi1XMfc5ZE6A4mOJmc5rmL2tAYwWXhq1TNT5aF9bh%2B1UMTPT5OPMetU0EaX56KK46yAuSFQkX1IydnEW3Sgqms5K8MjpKvWaySn9d16V7LJ%2FbY6mbyWDHiJCGmMT9SQgG5lCJ%2BKp%2BQXDZMBtLh08iihMO1ptBQzHrSV3NDsfQu0%2FmS11DM5JQ%2F6q%2B4mKFWA7OEmCFqqEoA9xkysADbEjI27x9eQlS5P42lLpToiBOoyRTM4JJGIvEEpmSsxQ0QKOQo1X2%2BYsCme1IRE2td0Txg9AXXSp0pluxxQQiB74%2FA9BL%2FiOpoVPEDRleQFIXWeSzMHS0VmVlUi2pnAhfQDL5UJCEU8rSNooCjKArKHC%2F8J4lOV9sgxNkaW25nZtQCAAtTBGYySPmQHUNMyWrI%2FbGs2nbNM2rPqD2jdi1qM8U4x0dt0va9WrWqRTTa4T93%2BPtgHf70ybX4s1ijAe7uh8gP7u6H%2B%2FzdDMV9uP9s3N%2FzKK6p2yhO4vbIqTG9Fn8Wq9zPLf5unKub02vxZwEGDOcWfwMxvtF7%2FFlsyPjc429EAFFXlNDPmupW2qDUgY3mppv8pcL33K9veOjgNOwbGDpYY8%2FcsG9kL9b0GvZZ4hY5c8O%2BDweP4zfss9jGxjKm5rlhH99%2B3fIVvoCFZS%2FuUXhvXbxdeVKIDvTf%2Ba4yv%2BmSHltXAhGXt9CTgKHinbSzXjdvjwNteHOaO%2FeNK7namlTnPpN8ayDxVRyGUITQ02Hz7Bx2FYGvI4oSipJVGpAUJYi2fANQxdh0x%2B9XZ7OOhI683yn1K%2FxOLfGdnp3joqAuZfaaz17zKdvXx%2Fea233EcLagI5c87VLq0kF4aLfxOkxdTdHjJXp81Yu8rRc%2Bm8vT06en0n%2F18vgtkrzHxy8GfPeZ5FVLQRMgeaxLsT3Jm8PWP1jYOlNCZPy4dZu1ZJdrBrw4ke%2FgvPVx4AHkTo4PWHOGynOwOJZmTR%2BWEIotzZyISaGgwE%2FsJKXQLZ2vVUXeUSAn3B0jb%2Bu%2FYT8JhN60fEyhVkwsInLM1PfOwd96aVdZH%2B6ug4lrWizDWfuBn7xLj1Ur8dFi3oqIeYUDYR5vRfED4yEtHpDAH6D57719iOEqLTi1yPL50Jjw%2FXBf8V6CUusoQRnry%2FGmPGujLM7n3igeqbkCtNfKeK8qts0gPlA5diC9L0YBTDZs4qUlmiuAj%2BYiJE9Lr4%2FGiOpMldcsmJTlElu3lyyn4VXW7Y3R5GEfjV2ahsFT4YPQERu70fc55UAkKob0eEm6YEnVcugu%2BscuxylOWWQMMQJ7AZo%2BHHlXMYt1%2FapGkwOj6lgoWORVRjcoqRIEIDhpGBycBCQSZRBnAJDLl7n%2F129g0VZ0EPmV2hSsaK1CDGNMyd6gAyCyKBgyLQ4MkSLLJRDSl71JEiuxCnHkShGs6Atq637BqR25krnU%2BIpkXbH0ms67yBLoNuI%2FOw0Jiyk1HYuZ0mXKWkfTFEVPqGZDW3AOOlUQIE13cjtrR%2FVIHvGHIkhppf5yDRK1oALxQieyBhGCzLsJ2IC7I1uAavRgazqHbg0aYgc4eQUNqZa4BNywREsCw5G5O3U3nQ8E7bQFyLrfYwx%2FuoQMyj%2F9fdSn%2F1HxdDqZYGTidXk0Dp9%2B0dRrbBLUhcBt2Uva7KKoPD1YZ%2BmO2pu0xElXGdruEm9wm4zZ8NK74UVVNDCu3UVTappd5CrenaHOWt4kxCXDooCISEbFLCqVp%2BT1BkSsVxH7bpSp0RDc6%2BrGgkhpmgKJCqvzD0pTiDh%2Fueyci7Z0uJNsl5nLu7S4RWGuxTg%2FOhqnXjseU3IsdorZwHMH7iRp78YqsmPbbIsxJlDae%2Biq0pFqlvVX0qilFOfBwUCtNwwUh2zOGDhj4A1ioE3JRID1oHNRsC9FTgM8FKSgA0X%2FHiewK0VfllwVsJLFUn4DkduyGCO7yCx4YtOgUt5YWzclTYN6X%2BINWNUViOzZeifXiYTvIEZCMHLIMDQXOXglsiQbNBiZzDuvVlk2DS9luO6dq7ztooErzYEXhLsPFYprsERaUXCKhJC1TICtsNSnhiDXc5UBSEt02P01WP%2F4Zfey%2B%2F2Pr4fw63H%2F9Y6k%2BnXU%2F5bXPpx0cL5TlpASLM4tnOFtQL4g38I570ytwuH0Rbk7Nch7Vwv6U%2BMzesim%2BzmR%2Frd3YAlM3TJ0e4WydTTV1rw7tVyQH6hLk2pmK9sV14Drqdvnj0UPXLxJ0hcG6pirGTyOOEFZp0dBBfVDLwsqrBUG8GhJb0Y5FbQXU%2Bp9mFX8EXFHlKEuVPAu4LmZFa89z616iGUJJIaaMev9f6Oph%2FrkUK%2BGlDTi0gNjFi824HI2fU3bMhFmW45zo9OlZHkqZbajh%2Bmda%2FKqin00rsnYbThBr4Mit1GT8TSzzZltTpA%2BmwKUnBDbNHjFim6cbZoz2%2ByabVaYX5v6tmSivV2WSv1oTDWn4SNSUbSLnI%2BIjo%2Fg%2BW2mhYCNfUaaTrkWCLLWEMjeYo41TqbEPnyJZVhvavYp2cLrK0TYQoenKIw%2FCN0pTUdKKFEsLI91MhX%2FUOcvkCzOIX7XZlVGRONs9scpbVSchFOazuBwE56Sps61kXxAJEDuigm7RZfqVPNWZKW6ioBXq1MHvVF31rnjaySfc4yarkS3mXoAbRe8njIzqIDtjEwUnxKvrxCxZaHh90j5%2FG%2F1H59ffn%2F937%2F%2B0%2Fz16fvzL3c82zwFB42KnJULimWqU4hEuwS9sz49hal5ZUSq90zegr2I1LzmM10IbNxNFKeIzV0Bbr0rgE1nDw3aFoAbPSARcNbAvAK%2F%2BuQH5F4YJc%2FhLjw4Adn5IHy9P%2Fh7B9cNG98iQ8c6rCrrMoxtjyEFvqwLTTAWMJfl0AJmqO7MMFxgEzuXZWmfoZbS1AyVT%2Fr28Rre5VUwn8neGHXwp0f3WtsDJ1ZfAvAuLiZXX0JnpjTJ%2BhJWQ717Li8xNifvvLyEQtMse8UreDYo0RJ3FKsySonKncVJGHmVqDHXO2v08LneWVWOSAdIqZp0FrPGK1bVW8EzLlLOlarmSlViSWKuVDWLEiSxNqda6mpsSaLDXN85HuIjx0NQnYcgR%2BZUoR4UssVGpuvkx7NmP2v2Mzvukh0rTA03E4zOkCXSQ6YQ109rdRPL%2FFdpdgRkjTa95eeSdM458%2F863nnO%2FP9gmf%2FV9Hi6if%2FkYUMl%2FpuLUuK%2F2jLxHyzKif%2FpeO0T%2Fyu3c2wfPvGILg1DtVVdVxTb1FQd8LL%2BDeX8UemaQw1KAGjFJH%2Bz0VN6jgdQxQ4NeTZR0eKqYXABVmJw49TCgUj1yGg2uXD%2Fr98K5HnNIdm8plkdNP1q%2B9Lz0B1CQflZza6BGlhpfFkWCPu8PMiia%2FcXvHWPM5vA0L2inkJGPB9Mf9aGlo1YOJDH6oauYB40yrizx0VT4ds2GlH5sxO%2Fpswfced0sb%2B8bZwXJ4E7gq8aIlWmwPwvVRvr5QEBue4UYVHx8w%2FN%2FBusVblI%2FDSZTonHSDBcOjbk2%2Ff3wPnu3f2WIkAWuv8f76DjC0jwfVzIxa%2Bw%2BW7U6yeR%2F93LATFwccPwXxx0DT1KLb5J6buFG%2FIGna6tEbKmcNakUDBEZIl407GVohcPnLUXPDib7zv8JlTiTXo3jOB2UXfQEn57dlysxqdx9P7xz9lqbv0gKLz4E%2Fp8WdDJPcjl%2BOXh6cFesIq%2BphXCjjNHANMLu2DdzY25%2Bvka8RaYOQi061ZAp%2B3wkvGANagZly1E8dK42Wy3yXi33PTEUE06%2FpLbcNbiJmR20aaADwW8YpuXh2BWdpyd4wQvBR7TYIJ3uZ1RVcXgQU9vTj7As9gyIu%2FcGnUqrmLj7Beu7o7K8xR30h2VD0atg7cmFuIyh5zOIadzjEuHhMu0dZpwDdoelU%2B2Wkfmzd1RO3%2F6%2B6hPn7uj3lJ3VHNV9i4P3BuVT3TYuLqX5r1KZ739cr2d2690YLWddLGcG5ZejQhjaGyYLq9nKeBIMKAvMOquiujcIa9eSZlAts%2FH7pBn2Iz9Y8AulXwUFIeMzSg4o%2BAHQMFB21TycZCX8k6Bx5yrUperYtoGQ1y5ecE6T1Tuzc8JeAX3ryR1Q5SusqDiMbZB6DSurXslayDKMnHDEwTMOX%2Flg%2BSv5DS6Wf6KXsoHUNQBCY%2FRbSnKqnQWkp9CWk7qi4szWZSlbeeJKx23sKzc2IlkstzRWjpQliYogJC2Kg8pnbiiLVdgdYZGi35KKXFFpzpk9py4QsStqUtAfYovKiu%2BcLpXmlzhRe%2BJhmhiz3j3fbjq9S6Q6V2KXJRys85exza8vOvWXq3WgheM0KxZ2NHtS%2BS4%2Fd5g1QSuWWuwgbGd51C%2BXGK4om2ZCPOnTROXNgcDNCvprz0Yf1lvrYYf4F1cTC6gaq4ZNMdTXYUz0qTy5idQC1gT2%2BkuiUSvLgY8sIVmKgbjLiDH4MRD8GKIVZUrPPUGPzrPkfbRVVh%2BK%2BlhpVp9yF7Ssw4767C3qsPmFG7KOqx%2By%2F2tK7dl1mE71WENnjHkmnXYOSloTgqaldhOldgVI%2B6qbEvTYdtwzWVvOwklslglk42VHjiSyLjBwreLOZJojiT6WJFEOYm%2BnkgiXprG5TrVrUYSkY2diCLGCSRSOwkk0msCiUpgOnAgkdE6nWFiWtvseZw9j7PS1qXSRrc8NMHIKltdggABnzMKpAmxRYGz1tauqqKcpUewuIe4hioZGrQ4Xou%2B0k%2BRkGIhnMN%2FHb1W7L8l7y%2F%2BC9xUxw9iTEua0bSGb64I31x5RHTs4d%2BITC6zEr8v6hLUT6jbdOnLKy6Ukp6nI143R37StiAvg0jEk6Lj2FpxsL9C%2FBJgPzyNQgQKZ%2FkGLvXzr6HroW%2F8Pw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement