今天发现了个方法,可以完美地获得skb各个字段的偏移,需要配合上篇文章讲的大杀器“xde反汇编器”。
暂时称这种方法为“模拟法”,网上应该有类似方法,但不知道叫什么名字。
以前,我们获得skb字段的偏移用的是手动波,在函数里找某个指令的特征码,这方法是很不靠谱的。在有了xde以后,我发现,这方法有时候还是不靠谱,例如一个运算需要0x40(%eax),0x50(%ebx),你咋知道哪个先取出来呢?取到哪里?所以,无论是手动波,还是自动波,针对某个指令的查找都是有些不靠谱的。
那咋办?
我们要把对象转为某个函数。看看这个函数。
- void skb_over_panic(struct sk_buff *skb, int sz, void *here)
- {
- printk(KERN_EMERG "skb_over_panic: text:%p len:%d put:%d head:%p "
- "data:%p tail:%p end:%p dev:%s/n",
- here, skb->len, sz, skb->head, skb->data, skb->tail, skb->end,
- skb->dev ? skb->dev->name : "<NULL>");
- BUG();
- }
看看,head, len, data, tail。。全部取了一次。但当初我为啥不选择这个函数来搜索?就是因为不知道它先取那个后取那个。但我们还是有解决办法的。
这个函数在 2.6.18 ~ 2.6.24 压根就没变过,还是在内核符号表里导出的,符合我们的先决条件。
关键的是,这个函数里只调用了一次call。(BUG() 被展开为 asm("ud2a; jmp ."))
如果我们把这个函数内容复制到我们的缓冲区中,然后把那个call printk改成jmp到我们自己的函数里,那不就等于“劫持”了它吗?在我们自己的函数里,就能得到skb各个字段的内容,但不是偏移。
那内容怎么转化成偏移呢?很简单,恰好skb的长度在0x100左右,这真是太好了,这样我们就能放一个特殊的skb在我们的缓冲区里,然后把它的第一个字节的内容设成0x00,第二个字节的内容设成0x01。。。
这样,在我们的“劫持函数”里面,我们就可以获得各个字段的偏移了!!
要考虑的细节问题是skb_over_panic对esp的操作。所以,我们要把skb_over_panic开头(一般对栈的操作都放在开头)那几句push xxx; subl $xxx, %esp给搞掉,全部弄成nop,然后把 call printk 改成 jmp fake_get_skb_offsets。在fake_get_skb_offsets这个“劫持函数”里,再 jmp 回主函数。不就搞定了吗?!
我们要达到的效果就是:
改变前:
- push %esi
- mov %edx,%esi
- push %ebx
- mov %eax,%ebx
- sub $0x24,%esp
- mov $0xc0315823,%edx
- mov 0x14(%eax),%eax
- test %eax,%eax
- je 0x17
- mov %eax,%edx
- mov 0x88(%ebx),%eax
- mov %edx,0x20(%esp)
- mov %esi,0xc(%esp)
- mov %ecx,0x4(%esp)
- mov %eax,0x1c(%esp)
- mov 0x84(%ebx),%eax
- movl $0xc032c660,(%esp)
- mov %eax,0x18(%esp)
- mov 0x90(%ebx),%eax
- mov %eax,0x14(%esp)
- mov 0x8c(%ebx),%eax
- mov %eax,0x10(%esp)
- mov 0x54(%ebx),%eax
- mov %eax,0x8(%esp)
- call 0xffebb3b0 // call printk()
改变后:
- nop
- mov %edx,%esi
- nop
- mov %eax,%ebx
- nop
- nop
- nop // 把对栈的操作都弄成 nop
- mov $0xc0315823,%edx
- mov 0x14(%eax),%eax
- test %eax,%eax
- je 0x17
- mov %eax,%edx
- mov 0x88(%ebx),%eax
- mov %edx,0x20(%esp)
- mov %esi,0xc(%esp)
- mov %ecx,0x4(%esp)
- mov %eax,0x1c(%esp)
- mov 0x84(%ebx),%eax
- movl $0xc032c660,(%esp)
- mov %eax,0x18(%esp)
- mov 0x90(%ebx),%eax
- mov %eax,0x14(%esp)
- mov 0x8c(%ebx),%eax
- mov %eax,0x10(%esp)
- mov 0x54(%ebx),%eax
- mov %eax,0x8(%esp)
- jmp 0x268 // jmp 到我们自己的“劫持函数”
实现一下:
- /* find the offsets of skb */
- jmp 2f
- #define addr_call 0x8
- fake_skb_over_panic:
- /* we will copy the true skb_over_panic here, and change
- 'call printk(...)' to 'jmp fake_get_skb_offsets'
- */
- .fill 0x100
- struct_dism:
- .fill sizeof_dism
- fake_struct_sk_buff:
- /* the content of this struct is:
- 00 01 02 03 ...
- easy to locate offsets :)
- */
- .fill 0x140
- fake_get_skb_offsets:
- /*
- void skb_over_panic(struct sk_buff *skb, int sz, void *here)
- {
- printk(KERN_EMERG "skb_over_panic: text:%p len:%d put:%d head:%p "
- "data:%p tail:%p end:%p dev:%s/n",
- here, skb->len, sz, skb->head, skb->data, skb->tail, skb->end,
- skb->dev ? skb->dev->name : "<NULL>");
- BUG();
- }
- now, we want to fake printk. so:
- skb->len = 0x8(%esp)
- skb->head = 0x10(%esp)
- skb->data = 0x14(%esp)
- skb->tail = 0x18(%esp)
- skb->end = 0x1c(%esp)
- skb->dev = 0x20(%esp)
- */
- GET_ADDR(sk_buff.len, %eax)
- movl 0x8(%esp), %ebx
- andl $0xff, %ebx
- movl %ebx, (%eax)
- GET_ADDR(sk_buff.data, %eax)
- movl 0x14(%esp), %ebx
- andl $0xff, %ebx
- movl %ebx, (%eax)
- GET_ADDR(sk_buff.tail, %eax)
- movl 0x18(%esp), %ebx
- andl $0xff, %ebx
- movl %ebx, (%eax)
- GET_ADDR(sk_buff.end, %eax)
- movl 0x1c(%esp), %ebx
- andl $0xff, %ebx
- movl %ebx, (%eax)
- GET_ADDR(sk_buff.dev, %eax)
- movl 0x20(%esp), %ebx
- andl $0xff, %ebx
- movl %ebx, (%eax)
- jmp fake_get_skb_offsets_ret
- 2: GET_ADDR(fake_struct_sk_buff, %edi)
- xorl %ecx, %ecx
- 1: movb %cl, (%edi)
- incl %edi
- incl %ecx
- cmpl $0x100, %ecx
- jl 1b
- // fill fake sk_buff
- GET_STR("skb_over_panic", %eax)
- movl $14, %edx
- call ksym_lookup
- jz loader_out
- movl %eax, %esi
- GET_ADDR(fake_skb_over_panic, %edi)
- movl $0x100, %ecx
- cld; rep movsb
- GET_ADDR(fake_skb_over_panic, %eax)
- leal 0x100(%eax), %edi
- movl %eax, %esi
- movl $0, addr_call(%esp)
- 1: movl %esi, %eax
- GET_ADDR(struct_dism, %edx)
- call xde_dism
- testl %eax, %eax
- jz loader_out
- movl %eax, %ebp
- cmpb $0xe8, dism_opcode(%edx)
- jz 3f
- cmpb $0x83, dism_opcode(%edx)
- jz 6f
- movb dism_opcode(%edx), %al
- andb $0x50, %al
- cmpb $0x50, %al
- jnz 2f
- cmpl $1, dism_len(%edx)
- jz 7f
- 2: addl %ebp, %esi
- cmpl %edi, %esi
- jl 1b
- jmp 5f
- 3: // meet call printk(...
- cmpl $0, addr_call(%esp)
- jnz 2b
- movl %esi, addr_call(%esp)
- #ifdef _DEBUG_
- movl %esi, 4(%esp)
- DPRINT("meet printk at %lx/n")
- #endif
- jmp 2b
- 6: // meet sub $xx, %esp; fuck it !!; fill it with 'nop'
- movw $0x9090, (%esi)
- movb $0x90, 2(%esi)
- jmp 2b
- 7: // meet push xxx; fuck it too !!
- movb $0x90, (%esi)
- jmp 2b
- 5:
- #ifdef _DEBUG_
- DPRINT("finish./n")
- #endif
- GET_ADDR(fake_get_skb_offsets, %eax)
- movl addr_call(%esp), %ebx
- addl $5, %ebx
- subl %ebx, %eax
- movl %eax, -4(%ebx)
- movb $0xe9, -5(%ebx)
- subl $0x40, %esp
- GET_ADDR(fake_struct_sk_buff, %eax)
- jmp fake_skb_over_panic
- fake_get_skb_offsets_ret:
- addl $0x40, %esp
- #undef addr_call
- // get sk_buff.protocol
- GET_STR("skb_gso_segment", %eax)
- movl $15, %edx
- call ksym_lookup
- jz loader_out
- movw $0xb70f, 4(%esp)
- movl $0x100, %edx
- leal 4(%esp), %ecx
- movl $2, (%esp)
- call memmem
- jz loader_out
- movzbl 3(%eax), %eax
- GET_ADDR(sk_buff.protocol, %ebx)
- movl %eax, (%ebx)
- #ifdef _DEBUG_
- GET_ADDR(sk_buff.len, %eax)
- movl (%eax), %eax
- movl %eax, 0x4(%esp)
- GET_ADDR(sk_buff.data, %eax)
- movl (%eax), %eax
- movl %eax, 0x8(%esp)
- GET_ADDR(sk_buff.tail, %eax)
- movl (%eax), %eax
- movl %eax, 0xc(%esp)
- GET_ADDR(sk_buff.end, %eax)
- movl (%eax), %eax
- movl %eax, 0x10(%esp)
- GET_ADDR(sk_buff.dev, %eax)
- movl (%eax), %eax
- movl %eax, 0x14(%esp)
- GET_ADDR(sk_buff.protocol, %eax)
- movl (%eax), %eax
- movl %eax, 0x18(%esp)
- DPRINT("<3>skb_buff len %lx, data %lx, tail %lx, end %lx, dev %lx, protocol %lx/n")
- #endif
看看效果:
skb_buff len 54, data 90, tail 84, end 88, dev 14, protocol 6a
哈哈,成功了!
这方法不仅通用,而且非常靠谱!!