2004-04-12
1. Introduction to part IILast month in the first part of this article series, we discussed some of the preparation and steps that must be taking when analyzing a live Linux system that has been compromised. Now we'll continue our analysis by looking for malicious code on the running system, and then discuss some of the searches that can be done with the data once it has been transferred to our remote host.
Note:
We have to remember that almost any command interpreter writes every typed command to a history file. We don't want to modify the local file system so the best solution is to turn off history recording. Please review part one of this article, for sections 2, 2.1, and 2.2 and 2.3 (steps 1 to 5) before continuing on. 2.3 Data collection for a live system - continuedStep 6: Physical memory image
Additionally, to properly analyze the whole physical memory we have to know some information from the page tables - the data structures that map virtual (linear) addresses to physical addresses. We have to know that physical memory contains of pages of virtual memory. In page tables we can find information about the order of pages (4 KB in Intel processors per page) written to the physical memory. As mentioned earlier, we have to remember that by acquiring volatile memory using software tools we will by nature modify the content of that memory. Even worse, we can overwrite possible evidence. In the example below have I copied a memory image using the /proc pseudo file system.
When copying the whole system's memory we copy both allocated and unallocated data because the process just copies the entire image of physical memory. In step 9 we will try to copy a particular process using the /proc pseudo file system. I have to mention that by using /proc, we can also modify the swap space. By copying the suspect process we will force some pages of the main memory to be read from the swap space, and other pages to write to it. Another important piece to remember is that we will copy only memory allocated by the process data. Step 7: List of modules loaded to kernel memory of an operating system
Unfortunately, some of malicious modules that may be present cannot be listed at all. To verify information about the loaded modules from /proc/modules I will use the method described in a recent issue of Phrack Magazine in the article, "Finding hidden kernel modules (the extrem way)". The hunter.o module checks a chain of modules loaded into the kernel.
I will use the "-f switch" in this case, which forces the loading of the hunter.o because of version mismatch. It happens when the version of the kernel of the compromised system is different from the version of the kernel of the system which the hunter.o module was compiled on. The best solution is to recompile module code for each version of the kernel that it will be linked to. If we know which kernel version is used on the compromised machine we can download the proper source code from www.kernel.org and include the specific header files for that kernel in our hunter.o module.
Now we can compare the results. We also have to pay attention to the size of the modules. Sometimes a malicious code is stuck to a legal module. The last thing which we have to do is copy the symbols exported by kernel modules. Sometimes the weak LKM based rootkit can export its symbols. By analyzing the ksyms file we can detect the presence of an intruder in the system.
We can instead use other tools which detect malicious modules (such as a kstat or kern_check), but unfortunately all of them use the System.map file. This file is generated after kernel compilation. If an administrator doesn't copy this file and doesn't create the checksum we should not trust the system call addresses which are presented there. Even when the system call addresses are valid an intruder can use another sophisticated method of hiding of a malicious module in kernel memory. For instance the adore-ng rootkit replaces the existing handler routines for providing directory listings. If we are sure that system calls addresses are not modified in the System.map file or the ksyms file we can verify if some of system call addresses are not changed in the system call table by the intruder. For more details see the section 2.7. Step 8: The list of active processes
Now, we have to analyze the result from the lsof tool. If any of the active processes are suspicious, now is a good time to copy them. Also, if we see in the results from lsof that the program which initiated the process is deleted by an intruder, we still have a chance to recover it. I will show you how to do this in the next section. I have listed a few examples of suspicious processes below: Step 9: Collecting of suspicious processes
Additionally we can copy just certain data from a process. More information about it will be found in the next section. Step 10: Useful information about the compromised system
Table 3: Useful information about the compromised host
Step 11: Current time
Now we have reached the point where we can switch off the compromised system. Remember not to shut down the system by using any shutdown or init commands. We have to pull the power cable from the system or UPS device. 2.4 File system imagesBefore switching off of the compromised system we could have created a copy of all file systems and swap partitions, but we chosen not to. I suggest performing this task after switching off of the system. This way, we can be sure that the contents of the compromised file system is no longer being modified. Once again, I have to mention that the swap space was modified during our acquisition process and some of evidences could have been overwritten.A next step is to put a bootable media to a drive and to run the operating system from the media. We can use any Linux distribution which will not mount local file systems by default. Now from this point we can make a copy of all local partitions or the whole hard disk. 2.5 Basic data analysisFor the moment let's consider again the process we used in step 8, where we used the lsof tool. Let's look at this situation in more depth and consider two examples:Example one: The program, which has initiated the process, is deleted.
In this case the Linux file, which has initiated the process, is still allocated in a memory. To recover the file we have to know the ID process created by the program. In the /proc/
When we recover the deleted file we can then extract information from it. We can choose one of two options: a static analysis by disassembling it, or a dynamic analysis by running this unknown file in a closely controlled environment.
Example two: The file, opened by an active process, is deleted (for instance a log file).
A fragment of the lsof result is presented below:
To recover this file we have to list the contents of the fd subdirectory (file descriptors) from the /proc/3137 directory.
As we can see, the first file descriptor is deleted. All we have to do is to copy this file to the remote host by using the netcat tool.
This is not the sole method of recovering this file. We can also recover the file during offline analysis when we are looking for unallocated i-nodes and data blocks.
We will gather all printable characters from the image file by using the strings tool. The default settings let us view printable character sequences, which are at least four characters long. We will use the -t switch to add an offset from the beginning of the file.
The grep tool and regular expressions are important in this initial analysis. In a few minutes we can find the evidence of an intrusion. We have to think what kind of data we are going to look for -- for instance, are we looking for commands typed by an intruder, IP addresses, passwords or even decrypted part of malicious code?
Below there are some examples of keywords to look for. We use these to find evidence of intrusion in our kcore_strings file.
In the above result we have listed some of the offsets of the strings. The next step is to open the file with a text editor (such as the less tool) and jump into a place in the file, which is close to the direct offset address. If we have some luck, we will find other commands that were run in the past. But we have to remember that pages of virtual memory in physical memory and the swap space are written in an unorganized manner, and therefore our conclusions could also be completely invalid.
The above example presents some of the commands typed on the compromised system.
To start analysis we need the following information:
The next step is to run the gdb tool as follows:
Now we are ready to start our analysis.
Example one: Verification of system call addresses
Our first step is to find address of the system call table (sys_call_table). Almost in every Linux operating system this information is exported and can be found in the Symbol.map file.
Now, we can list entries from the sys_call_table. Each entry is the address of system call. To proper interpret entries I recommend to look at the entry.S file from the kernel source code of the compromised system. This file contains the proper order of system calls.
The same order is in the sys_call_table. To list first 10 addresses of system calls we do as follows:
The address 0xc01217c0 is the sys_ni_call system call, the address 0xc011ac50 is the sys_exit system call, etc.
If we trust addresses from the System.map or the ksyms files we can compare almost every address. Of course the intruders don't change every address of system calls, there are a few popular such as a sys_read, sys_getdents or sys_write.
Example two: List of active processes
To create the whole list of run processes we have to find the address of the init_task_union struct. This struct points to first process descriptor which is called "process 0" or "swapper".
The next step is to find out how looks this structure. The example of init_task struct can be found in header file sched.h in source code of kernel version of the compromised system. The struct task_struct can also be found in this header file.
For us the most important fields are the prev_task and next_task of each process descriptor (the task_struct type). They help us to create the processes list. The next_task field points to the process descriptor of the next process, the prev_task field points to the process descriptor inserted last in the list.
In the example below I list fragments of process descriptor (the task_struct task), which was found at address: 0xc514c000.
where:
To print the process descriptor of the next process (the task_struct type) we do as follows:
Taking information from each process descriptor we can build the full list of active processes.
|
||||||||||||||||||
|
References
View more articles by Mariusz Burdach on SecurityFocus.
Credits Thanks to Kelly Martin and Dan Hanson for their suggestions, careful reviewing and help.
|
