将功能从用户空间复制到内核并执行

时间:2016-01-21 22:19:51

标签: c linux linux-kernel

首先,我这样做很有趣,所以不要评判我。

我所做的是将函数指针从用户空间传递给内核,使用copy_from_user将函数体复制到内核中的静态数组,然后开始跳转到该数组中执行。

内核中的

static char handler_text[PAGE_SIZE] __page_aligned_data;
copy_from_user((void *)handler_text , (const void __user *)my_handler , PAGE_SIZE);
((void (*)())(handler_text))();

在用户空间中,此功能的作用非常简单,如下所示

void my_handler(){
volatile unsigned long * p = (volatile unsigned long *)0xF0000c10;
*p = 0x0000000;
}

10000938 <my_handler>: 
10000938:   3d 20 f0 00     lis     r9,-4096 
1000093c:   39 40 00 00     li      r10,0 
10000940:   61 29 0c 10     ori     r9,r9,3088 
10000944:   91 49 00 00     stw     r10,0(r9) 
10000948:   4e 80 00 20     blr 
1000094c:   00 01 88 08     .long 0x18808

问题是我第一次这样做总是生成一个糟糕的。但是第二次我这样做以及之后,问题就消失了,而且不再有Oops了。我可以清楚地看到函数是由内核通过读取内存来执行的。我正在运行PowerPc目标,因此Oops显示异常为700,这是程序异常。从Oops中,我可以看到指令转储,其中nip(after)与my_handler完全相同。

Instruction dump:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 <3d20f000> 39400000 61290c10 91490000

我无法理解它。有人可以吗?谢谢

2 个答案:

答案 0 :(得分:4)

我讨厌劝阻一个令人钦佩的观念,但如果没有一些严肃的额外工作,你想要做的事情很难(如果不是不可能的话)。

您的功能已链接到用户空间中的F位置。您将其复制到静态数组位置的内核空间:AA可能在内核的数据部分,因此可能无法执行。此外,您的功能在错误的位置链接(例如F != A)。

此外,即使您的函数可以链接到正确的位置A,您如何处理其中的符号重定位(例如,如果它调用printk,您如何重新链接其中的地址?函数匹配实际的printk地址)?

创建内核模块并加载(通过modprobe)更容易,你可以做任何你想做的事。

旁注:这是一个巨大的安全vulernability。一个类似的用于&#34; Stuxnet&#34;蠕虫穿透Windows。

<强>更新

在异常事件之后,转移[及时]长。到那时,它具有正确的数据,因此转储显示当前状态,可以这么说,但在所讨论的确切周期上发生了什么[由于这个&#34;自我修改的本质&#34;码]。

但是,当最初执行时,它可能有垃圾(即700)。我不确定PPC,但其他拱门有单独的inst和数据缓存。无序执行。数据将位于数据高速缓存中,但不一定位于inst高速缓存[或队列]中。而且,他们倾向于独立运作以提高速度[&#34; Harvard&#34;体系结构]。

(例如)在x86上,设置静态区域后,必须刷新/同步,以便exec单元重新获取该区域。否则,它可能已经推测性地预取了指令数据(例如,它并不期望它是&#34;自我修改&#34;)数据不是什么预计[可能是0x00000000]。

考虑:copy_from_user所需数据在数据缓存中后,但尚未尚未刷新到RAM。执行单元[和inst cache]没有来自静态区域的任何数据,将从RAM中获取。因为自修改代码很少见,所以inst和数据缓存互相窥探[它会减慢速度]。

因此,执行单元从RAM(例如0x00000000)获取数据而不是加载的数据[在数据缓存中仅 ]。

第二次工作是因为执行单元提取的数据来自第一次尝试期间的数据[有时间刷新到RAM]。也就是说,静态区域现在已经填充,第二个copy_from_user实际上是NOP。

A&#34;验尸&#34;如上所述,该地区的转储无法显示出这种差异。

答案 1 :(得分:4)

想出来。事实证明这是缓存的事情。谢谢Ctx和Craig,我添加了一个

flush_dcache_icache_page(virt_to_page((unsigned long)(handler_text)));

copy_from_user((void *)handler_text , (const void __user *)my_handler , PAGE_SIZE);

现在一切都很好。在我问这个问题之前,我只尝试了flush_dcache_page,但它没有用。所以我必须刷新dcache和icache来使这个工作。再次感谢。