在获取root shell的一些漏洞中,我经常看到这样一个指针:
int i;
unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191);
有人能解释一下这个指针吗?我认为8191是内核堆栈的大小。 p
指向内核堆栈的底部?
以下是指针p
的使用方法:
int i;
unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191);
for (i = 0; i < 1024-13; i++) {
if (p[0] == uid && p[1] == uid &&
p[2] == uid && p[3] == uid &&
p[4] == gid && p[5] == gid &&
p[6] == gid && p[7] == gid) {
p[0] = p[1] = p[2] = p[3] = 0;
p[4] = p[5] = p[6] = p[7] = 0;
p = (unsigned *) ((char *)(p + 8) + sizeof(void *));
p[0] = p[1] = p[2] = ~0;
break;
}
p++;
}
答案 0 :(得分:54)
代码获取局部变量i
的地址以获取指向当前堆栈帧的指针。然后,它将地址对齐到8K页面(这就是你对x & ~8191
的处理方式:8191是2 ^ 13 - 1,这意味着~8191
除了低13位以外都是1,所以与它进行AND运算number将清除低13位,即将数字对齐到最接近的2 ^ 13的较低倍数,换句话说,对齐到8K边界。)
然后获取此地址并将其解释为指向指针的指针并从中加载指向的地址。有关详细信息,请参阅Understanding the getting of task_struct pointer from process kernel stack。
之后,它会尝试找到存储在该地址之后某处的特定结构:它查看以下1024-13
unsigned
,试图在内存中找到当前进程信息的位置(可能存储:当它找到一块存储器,其中包含当前UID和GID的多个副本时,它假定它已找到它。在这种情况下,它会修改它,以便当前进程获得UID和GID 0,使进程在root下运行(加上它将all-ones存储到以下功能标志中)。
比照。 struct cred
答案 1 :(得分:8)
我要发布另一个答案,因为这里确实有一些东西要添加。
unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191);
导致p成为指向8192字节大小的内存块的开始的指针。但是,代码是错误的。如果p高于INT_MAX(它可以是或者它将被转换为无符号,而不是无符号长),则高位会被掩码剪掉。正确的代码如下:
unsigned *p = *(unsigned**)(((ptrdiff_t)&i) & ~(ptrdiff_t)8191);
或使用uintptr_t:
unsigned *p = *(unsigned**)(((uintptr_t)&i) & ~(uintptr_t)8191U);
有必要转换为整数并返回指针以使代码工作;但是要保证一个int大小的指针需要使用ptrdiff_t(我们记得有符号和无符号对于按位运算的行为完全相同)。至于他们为什么不用十六进制常量来编写它们,谁在乎呢。做这些事情的人都知道他们的力量。读取8191然后读取0x1FFF可能会更快。