◆ Solaris 2.x 可加载系统调用模块 作者:FooPirata < mailto: Unknown > 整理:小四 < mailto: scz@nsfocus.com > 出处:Kernel Mumblings 日期:2001-06-06 本文目的在于快速介绍Solaris 2内核结构,介绍一些相关的Kernel Hacking工 具。调试SLKM,不可避免地会碰上系统崩溃,这里还将介绍这种情形下如何处理。 与心爱的Linux(鬼话,谁心爱Linux了)不同,即使Solaris x86版,也没有提供 源码。需要花费大量金钱从Sun公司购买源码,可惜我们没有这笔钱。两个顶级黑客 介绍了Kernel Hacking之后,我对此非常感兴趣。可能你并不知道他们,TeleMig 和 Snoopy(我对老外名字一向记不准确,并且他们化名太多)。 为了方便朋友们实践调试,我以SPARC/Solaris 2.6为蓝本翻译、测试,7下的系 统调用接口有变化,<>在这里也没有专门强调,不知道为什么。 感兴趣的自行观察/usr/include/sys/systm.h,或做其他方式的Kernel Hacking。 介绍 ==== 内核是Unix系统的一部分,负责内存管理、I/O、定时器(调度)、线程。 为什么要Kernel Hacking? 可能最冠冕堂皇的理由是好奇。必须承认,肯定有人在他们肮脏的脑海中带着对 黑暗的向往,任何以破坏系统为目的而关注内核的朋友看过本文后注定失望。 CPU将在两种模式下工作:用户模式和内核模式。二者之间的区别在于用户模式 下进程使用局部数据、局部映射文件和局部堆栈。内核模式下所有进程共用一个内核 映象。 每次我们从用户模式切换进入内核模式或者反过来,都将发生上下文(context) 切换。这个过程花费特别大,降低了系统性能,所以绝大多数现代内核设计人员采用 汇编语言编写这部分代码。 每次发生系统调用,用户进程将从用户模式切换进入内核模式。系统调用和其他 函数一样以函数调用方式提供,通过syscall(3B)切入内核,参看syscall(3B)手册页 -------------------------------------------------------------------------- SunOS/BSD 兼容库函数 syscall(3B) 名字 syscall - 间接系统调用 摘要 /usr/ucb/cc [ flag ... ] file ... #include int syscall ( number, arg, ... ); 描述 syscall()完成指定编号的系统调用功能,形参arg变长,根据不同的 系统调用编号而不同。参看头文件/usr/include/sys/syscall.h 返回值 -1 发生错误,外部全局变量errno被设置 文件 参看 intro(2)、pipe(2) 注意 仅当在BSD平台上编写应用程序时使用这种接口。多线程应用程序中 不要使用这种接口。 警告 pipe(2)的返回值无法用单一硬件寄存器存放,syscall()接口不能用 于此类系统调用 许多系统以库函数封装方式提供,其man手册中介绍的行为可能并不 适合syscall()调用方式,因为缺少了库函数封装。不推荐使用 syscall()接口。 译注 如果编写socket shellcode for sparc,可能需要使用syscall()接 口,参看<> : clr %g1 : ta 8 -------------------------------------------------------------------------- 系统调用名与系统调用号之间的映射关系由/etc/name_to_sysnum文件定义 系统调用机制 ============ ++++++++++++++ + 用户进程 + ++++++++++++++ | | 系统调用( X, .... ) | | V 用户模式 -------------------------------------------------------- 内核模式 sysent[X].sy_callc() -------------------------------------------------------- 所有系统调用相关信息集中在/usr/include/sys/systm.h文件中 -------------------------------------------------------------------------- /* * Structure of the system-entry table. * * Changes to struct sysent should maintain binary compatibility with * loadable system calls, although the interface is currently private. * * This means it should only be expanded on the end, and flag values * should not be reused. * * It is desirable to keep the size of this struct a power of 2 for quick * indexing. */ struct sysent { char sy_narg; /* total number of arguments */ char sy_flags; /* various flags as defined below */ int (*sy_call)(); /* argp, rvalp-style handler */ krwlock_t *sy_lock; /* lock for loadable system calls */ longlong_t (*sy_callc)(); /* C-style call hander or wrapper */ }; extern struct sysent sysent[]; extern struct sysent nosys_ent; /* entry for invalid system call */ #define NSYSCALL 250 /* number of system calls */ /* * sy_flags values * Values 1, 2, and 4 were used previously for SETJUMP, ASYNC, and IOSYS. */ #define SE_LOADABLE 0x08 /* syscall is loadable */ #define SE_LOADED 0x10 /* syscall is completely loaded */ #define SE_NOUNLOAD 0x20 /* syscall never needs unload */ #define SE_ARGC 0x40 /* syscall takes C-style args */ -------------------------------------------------------------------------- extern struct sysent sysent[]定义了内核结构数组,一个元素对应一个系统调用。 数组下标即系统调用号,由/etc/name_to_sysnum文件确定。这样做的好处在于不需 要内核源代码,也不需要重新编译内核,就可以增加系统调用。许多系统调用以动态 可加载模块方式实现,相应系统调用第一次被激活时动态加载进入系统。可加载系统 调用存放在/kernel/sys和/usr/kernel/sys目录下。 下表SPARC/Solaris 2.6的可加载系统调用: -------------------------------------------------------------------------- /kernel/sys/ c2audit doorfs inst_sync kaio msgsys nfs pipe pset rpcmod semsys shmsys /usr/kernel/sys/ sysacct -------------------------------------------------------------------------- sysent结构的sy_narg成员指明了系统调用形参个数。sy_flags成员对应一组标志, 系统调用是否可动态加载,一次性加载完整还是之后加载其他部分,是否可卸载,是 否支持C风格的形参传递。 Solaris众多美好特性之一是支持可加载内核模块。对于Linux世界来说,这很习以为 常了,但是高端系统(比如SPARC/Solaris)上的用户可能把这个特性当做新生事物。 我们可以编写自己的系统调用,并且方便地加入到内核中去。现在来看一个庸俗的例 子,一个显示"Hello World"的可加载系统调用。 首先,看一下/etc/name_to_sysnum: -------------------------------------------------------------------------- ... ... mount 21 umount 22 setuid 23 getuid 24 stime 25 alarm 27 fstat 28 pause 29 ... ... -------------------------------------------------------------------------- 缺省情况下,179、180、181、182、183这5个系统调用未被实现,初始化成 nosys_ent对应的值。我喜欢180,编辑这个文件,增加如下行: mySyscall 180 保存这个文件并重启系统。必须重启系统,以便内核读取该文件为新的系统调用分配 内存空间。 好了,下面是源码mySyscall.c: -------------------------------------------------------------------------- /* * gcc -D_KERNEL -c mySyscall.c * ld -r -o mySyscall mySyscall.o * * these are all the includes normally needed to a general sys call * we don't use many of them, but this is the normal load you'll see * on a more evolved syscall */ #include #include #include #include #include #include #include #include #include #include #include #include /* our entry point */ static int mySyscall ( void ); static struct sysent mySysent = { 0, /* number of arguments */ 0, /* load flags */ ( int ( * ) () )&mySyscall, /* the function */ ( krwlock_t * )NULL, /* kernel lock */ }; /* * this is the dynamic load & link stuff. It is sort of fill-in-the-blanks * and seems to be standard for all system calls */ extern struct mod_ops mod_syscallops; static struct modlsys modlsys = { &mod_syscallops, /* define loader routines */ "My little system call", /* decsriptive string */ &mySysent /* pointer to our sysent structure */ }; static struct modlinkage modlinkage = { MODREV_1, /* loader revision number */ (void *)&modlsys, /* start of list of things to load here */ 0 /* end of list */ }; /* end of dynamic load stuff */ /* * this is a counter on the instances of the loaded call. this is needed * in case we want to unload but it is still in use, or something. notice * that it is static on purpose */ static int refcnt = 0; /* * this little routine is the load entry point. when we instruct the * system to load our loadable system call, the kernel will call this * function - so, if you need any initialization done, here is the place * for it */ int _init ( void ) { printf( "MY SYSTEM CALL INITIALIZED\n" ); return( mod_install( &modlinkage ) ); } /* * here we do the opposite of _init, and deallocate any resources we may have * been using before unloading the system call code */ int _fini ( void ) { /* * in case we ask to have the syscall unloaded while it is still * in use, we refuse the download with a BUSY return code */ if ( refcnt != 0 ) { return( EBUSY ); } printf( "MY SYSTEM CALL REMOVED\n" ); return( mod_remove( &modlinkage ) ); } /* * tools like modinfo will return information about loadable modules * installed. this answers to those requests. */ int _info ( struct modinfo * modinfop ) { printf( "REQUEST INFO ABOUT MY SYSTEM CALL\n" ); return( mod_info( &modlinkage, modinfop ) ); } /* * here we do the real magic. one work of advice concerning which functions * you can use here - anything that compiles without an explicit library * addition will do just fine */ static int mySyscall ( void ) { printf( "Hello World\n" ); return( 0 ); } -------------------------------------------------------------------------- $ gcc -D_KERNEL -c mySyscall.c $ ld -r -o mySyscall mySyscall.o 你是root吗?什么?你不是root,难道你以为这篇文章教你怎么获取root吗?嘿嘿 现在你有一个名为mySyscall的文件,下面是一个小测试程序: -------------------------------------------------------------------------- /* gcc -o mySyscallTest mySyscallTest.c */ #include int main ( int argc, char * argv[] ) { int i = syscall( 180 ); } -------------------------------------------------------------------------- # modload ./mySyscall # ./mySyscallTest 观察/var/adm/messages尾部新数据,SYSCALL SLKM的输出信息都送往该文件了,主 控台(console或者xconsole)上也能看到调试信息的输出。 译注:直接观察/var/adm/messages或者dmesg查看,都不是很理想。考虑使用 microcat在华中站Security版提供过的getlog.c,效果非常理想。 尽管目前printf()和uprintf()存在,但是如果编写Solaris DDI-compliant的 驱动程序,就不应该使用它们。 应该使用cmn_err(9F)。 如果你得到这样的错误信息"can't load module: Out of memory or no room in system tables",可能是你忘记修改/etc/name_to_sysnum,或者修改后忘记重启系 统了。 用modinfo命令确认mySyscall的ID号,用modunload卸载可加载系统调用模块 # modunload -i 今天到此为止,下次我们将讨论adb、kadb以及如何调试SLKM,可能还要做针对已有 系统调用的hook操作。 <待续>