Please direct all questions about XenAccess to the mailing list: https://lists.sf.net/lists/listinfo/xenaccess-devel
The project was created and is maintained by Bryan D. Payne, who is currently working towards his PhD in Computer Science at Georgia Tech. Bryan may be reached by email at bryan@thepaynes.cc.
XenAccess must take several steps to access a memory page based on a kernel symbol in Linux.
Memory introspection is useful because it allows you to monitor and control an operating system from a protected location. Previous research has shown that introspection can be used for a wide variety of security applications, but more ideas are coming out all the time. Using XenAccess, you can quickly experiment with your new ideas and help advance this new an exciting research direction.
You can also grab the development version directly from the subversion repository. To do this, you will need a subversion client capable of handling SSL. Then, perform the checkout with the following command:
svn co https://xenaccess.svn.sf.net/svnroot/xenaccess/trunk/libxa
If you are just getting started with XenAccess, you probably want to use the latest released version. However, if you need a new feature that hasn't been released, or you are planning on submitting a patch, then you may want to try the development version.
./autogen.sh ./configure make
Note that you can specify options to the configure script to specify, for example, the installation location. For a complete list of configure options, run:
./configure --help
su make install
Note that this will install XenAccess under the install prefix spcified to the configure script. If you did not specify an install prefix, then XenAccess is installed under /usr/local.
<domain name> {
<key> = <value>;
<key> = <value>;
} The domain name is what appears when you use the 'xm list' command. There are 14 different keys available for use. The ostype and sysmap keys are used by both Linux and Windows domains. The available keys are listed below:
ostype Linux or Windows guests are supported. sysmap The path to the System.map file or the exports file (details below). linux_tasks The number of bytes (offset) from the start of the struct until task_struct->tasks from linux/sched.h in the domain's kernel. linux_mm Offset to task_struct->mm. linux_pid Offset to task_struct->pid. linux_pgd Offset to mm_struct->pgd. linux_addr Offset to mm_struct->start_code. win_tasks Offset to EPROCESS->ActiveProcessLinks. win_pdbase Offset to EPROCESS->Pcb->DirectoryTableBase. win_pid Offset to EPROCESS->UniqueProcessId. win_peb Offset to EPROCESS->Peb. win_iba Offset to EPROCESS->Peb->ImageBaseAddress. win_ph Offset to EPROCESS->Peb->ProcessHeap.
Fedora-HVM {
sysmap = "/boot/System.map-2.6.18-1.2798.fc6";
ostype = "Linux";
linux_tasks = 268;
linux_mm = 276;
linux_pid = 312;
linux_pgd = 40;
linux_addr = 132;
}
WinXPSP2 {
ostype = "Windows";
win_tasks = 0x88;
win_pdbase = 0x18;
win_pid = 0x84;
win_peb = 0x1b0;
win_iba = 0x8;
win_ph = 0x18;
} You can specify as many domains as you wish in this configuration file. When you are done creating this file, it must be saved to /etc/xenaccess.conf.
map-addr [domain id, virtual address] Dumps a memory page to stdout based on the provided virtual address. The virtual address must be a kernel virtual address. The page is displayed in a readable format complete with hex, ascii, and offsets. The number printed before the memory page is the offset of the specified address within the page. map-symbol [domain id, kernel symbol] Same as map-addr except you specify a kernel symbol instead of a kernel virtual address. module-list [domain id] Lists the kernel modules installed in the operating system. This is the same list that you would get using 'lsmod' on a Linux system. On Windows, it lists the drivers loaded into the kernel. process-list [domain id] Lists the running processes in the operating system. This is the same list that you would get using 'ps -ef' on a Linux system. On Windows, it is the same list that you would get using the task manager. process-data [domain id, process id] Displays the first memory page of executable content for a given process. The process number, which is provided as a second argument, is the number obtained from using the process-list example above.process-list example displays the processes running in an operating system by walking down the linked list data structure containing process information. For each process, the name and ID are extracted and printed to stdout. To see how this is done, we will step through the code one piece at a time.
#include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/mman.h> #include <stdio.h> #include <xenaccess/xenaccess.h> #include <xenaccess/xa_private.h>
The include list is not too surprising. Note that xa_private.h is included here, but this is only to access the function that prints a memory page to stdout. Most people will not need to include xa_private.h
#define TASKS_OFFSET 24 * 4 #define PID_OFFSET 39 * 4 #define NAME_OFFSET 108 * 4 #define ActiveProcessLinks_OFFSET 0x88 #define UniqueProcessId_OFFSET 0x84 #define ImageFileName_OFFSET 0x174
These offset values are important as they will allow us to find the necessary data within the data structures we traverse. The first three offsets are used for Linux systems and obtained by looking at the definition of task_struct. The second three offsets are used for Windows systems and obtained using windbg to view the EPROCESS struct.
uint32_t dom = atoi(argv[1]);
if (xa_init(dom, &xai) == XA_FAILURE){
perror("failed to init XenAccess library");
goto error_exit;
} Next we read in the domain ID to look at. (Yes, there is no error checking here so the program will seg fault if you fail to specify a domain ID as an argument.) Then we make our first call to XenAccess. This call initializes the instance data structure. Note that we only perform this initialization step once as it is a costly function call.
From this point forward, all of the error checking code will be omited from the sake of clarity. In addition, we will only focus on the Linux version of the code. The Windows version operates in a similar fashion. To see the complete version of the code, look in the examples directory of your copy of XenAccess.
memory = xa_access_kernel_symbol(&xai, "init_task", &offset);
memcpy(&next_process, memory + offset + TASKS_OFFSET, 4);
list_head = next_process;
munmap(memory, xai.page_size); The kernel symbol 'init_task' points to the beginning of the process list in the Linux kernel. So we map this memory location and then copy the pointer to the next process into both the list_head and next_process variables. The task list is a circular linked list, so we will use list_head to know when we have visited every process. The next step here is to unmap the memory, since we are done with this page. This is important to remember since you can only map a limited number of pages at a time. Just as you would free memory after a malloc, you should unmap these pages after you are done using them.
while (1){
memory = xa_access_virtual_address(&xai, next_process, &offset);
memcpy(&next_process, memory + offset, 4);
if (list_head == next_process){
break;
}
name = (char *) (memory + offset + NAME_OFFSET - TASKS_OFFSET);
memcpy(&pid, memory + offset + PID_OFFSET - TASKS_OFFSET, 4);
printf("[%5d] %s\n", pid, name);
munmap(memory, xai.page_size);
} This loop is the bulk of the program. We map the memory page associated with the next process. For this process we check to see if it is the init_task process. If so then we are done. If not, then we print out the process information. Note that we are obtaining this information directly from the task_struct in memory of the running Linux system that we are looking at. When we are done with this memory page, we unmap it and repeat the loop.
if (memory) munmap(memory, xai.page_size);
xa_destroy(&xai); The final step is cleanup. We perform a sanity check to make sure that there mapped memory pages. And then we call xa_destroy to free any memory associated with the XenAccess instance.
[root@bluemoon libxa]# xm list Name ID Mem(MiB) VCPUs State Time(s) Domain-0 0 1229 2 r----- 137356.4 Fedora-HVM 4 384 1 -b---- 2292.6 fc5 5 384 1 -b---- 15.4 [root@bluemoon libxa]#
Then you can run the examples as follows:
[root@bluemoon libxa]# cd examples/ [root@bluemoon examples]# ./module-list 5 ipv6 binfmt_misc lp parport_pc parport nvram usbcore [root@bluemoon examples]# ./module-list 4 autofs4 hidp rfcomm l2cap bluetooth sunrpc ipv6 parport_pc lp parport floppy 8139cp 8139too mii pcspkr serio_raw dm_snapshot dm_zero dm_mirror dm_mod ext3 jbd [root@bluemoon examples]#
Note that the example code works for both para-virtualized (i.e., PV) and fully-virtualized (i.e., HVM) domains. However, the example code uses the offsets you provide in the configuration file, and some hard coded offsets in the example code, to locate information in the running kernels. For this reason you may find that it fails on some kernels.
[root@bluemoon examples]# ./process-list 5 [ 1] init [ 2] migration/0 [ 3] ksoftirqd/0 [ 4] watchdog/0 [ 5] events/0 [ 6] khelper [ 7] kthread [ 8] xenwatch [ 9] xenbus [ 15] kblockd/0 [ 57] pdflush [ 58] pdflush [ 60] aio/0 [ 59] kswapd0 [ 578] kseriod [ 685] kpsmoused [ 710] khubd [ 978] dhclient [ 1006] syslogd [ 1009] klogd [ 1021] sshd [ 1027] mingetty [root@bluemoon examples]# ./process-list 4 [1408237823] ? [14941936] ?S? [ 0] [ 0] ERROR: address not in page table failed to map memory for process list pointer: Success [root@bluemoon examples]#
Here we see that process-list works on one of the domains, but not the other. This is because the examples were written with a specific kernel version in mind. Your code will need to be built to work with the specific system that you plan on viewing. Future versions of XenAccess will provide tools to help simplify the process of finding the right offset values.
Note: This section provides some preliminary ideas and help. If you find other tips that could be added to this section, please send a note to the mailing list.
The first step is to do your costly work during initialization. This includes your call to xa_init. You should call this function once during the initialization of your application and then pass the instance around to all functions that need to use the XenAccess API. XenAccess is designed to do as much work as possible during initialization, so a call to xa_init is costly. If you call it several times then your application performance will certainly suffer.
The second step is to be mindful of what you are doing. For example, an algorithm that searches memory looking for a particular pattern should not be using XenAccess to access each virtual address in the search range independently. Instead, use XenAccess to map a page of memory once. Then look at each address on that page before unmapping it and proceeding to the next page. These considerations can improve your application's performance by several orders of magnitude.
Finally, you can enable the debug output (see Debugging section below) to identify when XenAccess is getting cache hits and cache misses. The default cache size is 25 entries, however some applications may benefit from a larger cache. You can adjust the cache size by changing the XA_CACHE_SIZE variable in xa_private.h and then recompiling XenAccess. However, keep in mind that a larger cache size does not always equal better performance. Experiment to see what works best for your particular application.
XA_DEBUG variable near the top of the xenaccess.h file. After uncommenting this variable, you will need to recompile XenAccess (and, optionally, reinstall XenAccess). With the debug output enabled, you will see lots of information on stdout about XenAccess's operation.If you are requesting help from the mailing list, please send the debug output along with your question as it will be easier to diagnose your problem this way.
When available, source code provides an invaluable resource for understanding how data is organized in memory. When source code is not available, or when need a higher-level understanding of the system's operation, then I recommend finding a useful reference book. The two operating system references that seem to be most useful when working with XenAccess are listed below:
If you want to see the memory maping by your application, consider using the print_hex function, which will require you to include xa_private.h in your application. This function allows you to easily print the hex and ascii values from a region of memory to stdout, which can often simplify debugging.
1.5.5