Chinaunix

标题: 在内核窥视用户态 [打印本页]

作者: zyr-linux    时间: 2010-08-07 10:39
标题: 在内核窥视用户态
在内核窥视用户态

首先,环境:VMware Server上运行的ubuntu10.4,arch为x86_64。


先看下面这个程序:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>


  4. int dataA;
  5. char bufA[1000];

  6. int main()
  7. {
  8.         int dataB;

  9.         int i_GetChar;

  10.         char * bufB = NULL;

  11.         bufB = malloc(1500);

  12.         if (NULL == bufB)
  13.         {
  14.                 printf("malloc failed\n");

  15.                 return 0;
  16.         }

  17.         dataA = 0X55aa;

  18.         dataB = 0Xaa55;

  19.         memcpy(bufA, "bufA org data", strlen("bufA org data"));

  20.         memcpy(bufB, "bufB org data", strlen("bufB org data"));

  21.         printf("bufA = %p, bufB = %p, &dataA = %p, &dataB = %p\n", bufA, bufB, &dataA, &dataB);
  22.        

  23.         for ( ; ; )
  24.         {
  25.                 printf("get char(p:print; e:exit):");
  26.                
  27.                 i_GetChar = getchar();

  28.                 if ('p' == i_GetChar)
  29.                 {
  30.                         printf("bufA: %s\n", bufA);
  31.                         printf("bufA: %s\n", bufB);
  32.                         printf("dataA: 0x%x\n", dataA);
  33.                         printf("dataA: 0x%x\n", dataB);
  34.                 }
  35.                 else if ('e' == i_GetChar)
  36.                 {
  37.                         break;
  38.                 }
  39.         }

  40.         free(bufB);
  41.        
  42.         return 0;
  43. }
复制代码
这个程序中有:
一个int型的全局变量dataA;
一个int型的局部变量dataB;
一个char型的全局数组bufA;
一段malloc的空间bufB。

程序先输出以上变量的地址,然后按"p"输出一次内容,按"e"退出程序。



接下来,简单的分析一下:
bufA = 0x601080, bufB = 0x6a0010, &dataA = 0x601468, &dataB = 0x7fff337284ac
仅从地址上看,他们就在不同的内存区域。
bufA和dataA是全局变量,在数据区;
bufB是malloc来的,在堆中;
dataB是局部变量,在进程的运行栈中。

抄一段linux自带的关于x86_64下地址空间的说明:
0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm
hole caused by [48:63] sign extension
ffff800000000000 - ffff80ffffffffff (=40 bits) guard hole
ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory
ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole
ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space
ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole
ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB)
... unused hole ...
ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0
ffffffffa0000000 - fffffffffff00000 (=1536 MB) module mapping space


可见,这些变量的地址都在user space中。

对各个用户态进程来说,地址空间都是0000000000000000 - 00007fffffffffff,而不会冲突;
因为这只是虚拟地址,每个用户态进程都拥有自身的页表,相同的虚拟地址地址经过不同的页表转换为不同的物理地址;
而内核的页表并不映射user space,这些后面会用到。
作者: zyr-linux    时间: 2010-08-07 10:41
本帖最后由 zyr-linux 于 2010-08-07 10:48 编辑

内核中,每个进程有个结构体存放相关信息:struct task_struct;
struct task_struct内容很多,现在只找和内存资源相关的struct mm_struct *mm;

struct mm_struct中内容很多也很多,目前关心的是下面几个:
1,保存了进程使用的各个地址区域(vma)的struct vm_area_struct * mmap;;
2,保存了进程页表的位置的pgd_t * pgd;
3,保存进程堆/栈/数据区/代码区地址的一大堆东东
        unsigned long total_vm, locked_vm, shared_vm, exec_vm;
        unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
        unsigned long start_code, end_code, start_data, end_data;
        unsigned long start_brk, brk, start_stack;
        unsigned long arg_start, arg_end, env_start, env_end;

把他们全部打出来看看好了:



bufA和dataA在数据区;bufB是malloc来的,在堆之中;dataB是局部变量,在栈之中。
和理论分析一致!
更重要的是,用户态下输出的地址和内核态下得到的地址范围对的上,那么,这些地址就是虚拟地址。

把页表的第四级打出来,看看。
说明一下,下面两张图是补截的,地址和其他图不能完全对上。




再和内核的页表第四级对比,可以看出什么?


作者: zyr-linux    时间: 2010-08-07 10:45
本帖最后由 zyr-linux 于 2010-08-07 10:54 编辑

现在,我们知道了这些变量的虚拟地址,是不是就能直接在内核态下操作了呢?
实践的结果是————OOPS……
因为,用户态进程的虚拟地址,其转换是通过该进程的页表进行,内核的页表没有这些地址的信息。

不过已经知道了页表的位置,自己转一遍:




得到了物理地址,再加上PAGE_OFFSET获得内核态下能够操作的虚拟地址,把内容打出来验证:



最后,尝试修改:



每改一次,在用户态程序上验证一次,结果符合预期:



以上只是一个简单的尝试,因为在一开始就获得了用户态进程中各个变量的地址;
获得了一个用户态进程的堆、栈、数据段、代码段的地址,并能转换为可在内核态下操作的地址;
那么,理论上,可以对该用户态进程做任何事。
但要想实用,还需要进一步的研究,比如通过反编译,objdump之类的手段获得用户态程序中的地址。
作者: chong232    时间: 2010-08-08 11:49
呵呵,大赞!
作者: jinxinxin163    时间: 2010-08-11 09:27
好东东哦
作者: superlzdcn    时间: 2011-09-09 17:12
楼主关键地方代码也贴出来给我们学习学习阿
作者: 毛xx总理    时间: 2011-09-09 17:48
是的不错的。
作者: rick_cheung    时间: 2012-01-13 23:35
回复 1# zyr-linux


    能否把程序代码发到我的邮箱?rickcheung@foxmail.com
    多谢啦!
作者: ww2000e    时间: 2012-01-14 02:12
:wink: 看得不太懂
作者: zyr-linux    时间: 2012-01-16 10:38
查找页表,转换地址的代码,linux源代码中有现成的,不过我习惯了自己写。
  1. //进行转换是传递信息的结构体
  2. struct PGT_TansInfo
  3. {
  4.         //地址信息
  5.         unsigned long ul_Vaddr;    //虚拟地址
  6.         unsigned long ul_Paddr;    //物理地址

  7.         //页表信息       
  8.         pgd_t * pst_Level4Table;         //页表位置
  9.         unsigned long ul_PageSize;       //页大小
  10.         struct PGT_Level_Info astLevel[PGT_LEVEL_BULL];

  11. };

  12. /******************************************************************************

  13. 函数: int PgtOp_VaddrTrans(struct PGT_TansInfo * vpst_TansInfo)

  14. 功能: 地址转换函数

  15. 输入: 1) struct PGT_TansInfo * vpst_TansInfo: 转换信息结构体指针
  16.              
  17. 输出: 成功返回0,失败返回非0

  18. 备注: 转换虚拟地址为物理地址,并纪录转换中信息
  19.              可用于检查虚拟地址是否正确

  20. ******************************************************************************/
  21. int MemTool_VaddrTrans(struct PGT_TansInfo * vpst_TansInfo)
  22. {
  23.         unsigned long ul_Index;
  24.        
  25.         unsigned long * aul_Talbe = NULL;

  26.         //参数检查并赋值
  27.         if ((NULL == vpst_TansInfo) || (NULL == vpst_TansInfo->pst_Level4Table))
  28.         {
  29.                 printk("vpst_TansInfo or pst_Level4Table is NULL!\n");
  30.                 return -1;
  31.         }

  32.         //X86_64中,虚拟地址前17bit一致
  33.         if ((0 != (vpst_TansInfo->ul_Vaddr & MEMTOOL_ADDR_HEAD_MASK))
  34.                  && (MEMTOOL_ADDR_HEAD_MASK != (vpst_TansInfo->ul_Vaddr & MEMTOOL_ADDR_HEAD_MASK)))
  35.         {
  36.                 printk("vaddr 0x%lx is error!\n", vpst_TansInfo->ul_Vaddr);
  37.                 return -1;
  38.         }

  39.         aul_Talbe = (unsigned long *)vpst_TansInfo->pst_Level4Table;


  40.         //查找第四级页表

  41.         ul_Index = pgd_index(vpst_TansInfo->ul_Vaddr);

  42.         if (0 == vpst_TansInfo->pst_Level4Table[ul_Index].pgd)
  43.         {
  44.                 return -1;
  45.         }
  46.         else
  47.         {
  48.                 vpst_TansInfo->astLevel[PGT_LEVEL_LV4].ul_TableAddr = (unsigned long)aul_Talbe;       
  49.                 vpst_TansInfo->astLevel[PGT_LEVEL_LV4].ul_Index = ul_Index;
  50.                 vpst_TansInfo->astLevel[PGT_LEVEL_LV4].ul_Value = aul_Talbe[ul_Index];
  51.         }

  52.        
  53.         //查找第三级页表
  54.         aul_Talbe = (unsigned long *)(PAGE_OFFSET + ((aul_Talbe[ul_Index] & PAGE_MASK) & MEMTOOL_ADDR_NX_MASK));

  55.         ul_Index = pud_index(vpst_TansInfo->ul_Vaddr);

  56.         if (0 == aul_Talbe[ul_Index])
  57.         {
  58.                 return -1;
  59.         }
  60.         else
  61.         {
  62.                 vpst_TansInfo->astLevel[PGT_LEVEL_PGD].ul_TableAddr = (unsigned long)aul_Talbe;       
  63.                 vpst_TansInfo->astLevel[PGT_LEVEL_PGD].ul_Index = ul_Index;
  64.                 vpst_TansInfo->astLevel[PGT_LEVEL_PGD].ul_Value = aul_Talbe[ul_Index];


  65.                 //判断是不是1GB页
  66.                 if (0 != (_PAGE_PAT & aul_Talbe[ul_Index]))
  67.                 {
  68.                         vpst_TansInfo->ul_PageSize = PGT_PAGESIZE_1G;

  69.                         //取物理地址
  70.                         vpst_TansInfo->ul_Paddr = ((aul_Talbe[ul_Index] & PUD_MASK) & MEMTOOL_ADDR_NX_MASK)
  71.                                                   + (vpst_TansInfo->ul_Vaddr & (PUD_SIZE - 1));

  72.                         return 0;
  73.                 }
  74.         }

  75.         //查找第二级页表
  76.         aul_Talbe = (unsigned long *)(PAGE_OFFSET + ((aul_Talbe[ul_Index] & PAGE_MASK) & MEMTOOL_ADDR_NX_MASK));

  77.         ul_Index = pmd_index(vpst_TansInfo->ul_Vaddr);

  78.         if (0 == aul_Talbe[ul_Index])
  79.         {
  80.                 return -1;
  81.         }
  82.         else
  83.         {
  84.                 vpst_TansInfo->astLevel[PGT_LEVEL_PMD].ul_TableAddr = (unsigned long)aul_Talbe;       
  85.                 vpst_TansInfo->astLevel[PGT_LEVEL_PMD].ul_Index = ul_Index;
  86.                 vpst_TansInfo->astLevel[PGT_LEVEL_PMD].ul_Value = aul_Talbe[ul_Index];
  87.         }

  88.         //判断4K页还是2MB页
  89.         if (0 == (_PAGE_PAT & aul_Talbe[ul_Index]))
  90.         {
  91.                 //4K页,查找第一级页表
  92.                
  93.                 aul_Talbe = (unsigned long *)(PAGE_OFFSET + ((aul_Talbe[ul_Index] & PAGE_MASK) & MEMTOOL_ADDR_NX_MASK));
  94.                
  95.                 ul_Index = pte_index(vpst_TansInfo->ul_Vaddr);

  96.                 if (0 == aul_Talbe[ul_Index])
  97.                 {
  98.                         return -1;
  99.                 }
  100.                 else
  101.                 {
  102.                         vpst_TansInfo->astLevel[PGT_LEVEL_PTE].ul_TableAddr = (unsigned long)aul_Talbe;       
  103.                         vpst_TansInfo->astLevel[PGT_LEVEL_PTE].ul_Index = ul_Index;
  104.                         vpst_TansInfo->astLevel[PGT_LEVEL_PTE].ul_Value = aul_Talbe[ul_Index];

  105.                         vpst_TansInfo->ul_PageSize = PGT_PAGESIZE_4K;

  106.                         //取物理地址
  107.                         vpst_TansInfo->ul_Paddr = ((aul_Talbe[ul_Index] & PAGE_MASK) & MEMTOOL_ADDR_NX_MASK)
  108.                                                   + (vpst_TansInfo->ul_Vaddr & (PAGE_SIZE - 1));

  109.                 }
  110.        
  111.         }
  112.         else
  113.         {
  114.                 vpst_TansInfo->ul_PageSize = PGT_PAGESIZE_2M;
  115.                 //取物理地址
  116.                 vpst_TansInfo->ul_Paddr = ((aul_Talbe[ul_Index] & PMD_MASK)& MEMTOOL_ADDR_NX_MASK)
  117.                                           + (vpst_TansInfo->ul_Vaddr & (PMD_SIZE - 1));
  118.         }

  119.         return 0;

  120. }
复制代码

作者: hhb0331    时间: 2012-11-14 17:59
留爪,学习
谢谢 楼主分享
作者: bensenq    时间: 2012-11-14 23:10
好文章一枚啊。
现在,我们知道了这些变量的虚拟地址,是不是就能直接在内核态下操作了呢?
实践的结果是————OOPS……

如果是进程本身陷入内核态当然是可以直接操作这些虚拟地址的,而其他进程(探测进程)有不同的上下文(MMU相关),当然就不能正确的代换地址了。
作者: yezj2004    时间: 2013-01-17 00:59
支持一下吧
作者: Arthur_    时间: 2014-07-25 13:48
不错 意义很大
作者: yang511yang    时间: 2014-12-28 12:24
本帖最后由 yang511yang 于 2014-12-28 12:25 编辑

回复 1# zyr-linux

请问下图中


为什么执行xbx-memtool就知道要查找vma进程的页表获得虚拟地址对应的物理地址,0xffff88003d29a000又是如何来的?
   
作者: zyr-linux    时间: 2014-12-29 10:10
yang511yang 发表于 2014-12-28 12:24
回复 1# zyr-linux

请问下图中


XBX是自己写的一个小程序进行验证,主要的原理和代码前面已经放出来了。
作者: sky__sea    时间: 2015-07-06 17:09
感觉好高深哦
作者: roman_88888    时间: 2015-08-12 15:27
您好,关于在内核窥视用户态帖子,全部具体执行代码能发一份吗,我现在遇到一个题,移植驱动时,出现内核线程不能访问用户空间分配的内存的情况,想借鉴学习下,望能指导下luoman008@126.com
作者: 夕阳下的孤影    时间: 2015-08-12 23:06
楼主分析的很好啊,mark一下
作者: superwujc    时间: 2016-06-13 16:38
标题: no
本帖最后由 superwujc 于 2016-07-04 20:44 编辑


thks
   




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2