在漏洞利用代码中指针的解释

49

在某些获取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++; 
} 

3
二进制下的值为 1111111111111 的十进制数是 8191,而 long 类型是 32 位。要给出一个确切的答案,我们需要看一下 *p 指针是如何被使用的。& 运算符可能是某种位掩码。 - Tim Biegeleisen
@TimBiegeleisen 感谢您的回复。我已经编辑过了。 - HuangJie
2个回答

53
该代码获取本地变量 i 的地址,以获取指向当前堆栈帧的指针。然后,它将该地址对齐到8K页面(使用 x & ~8191 完成对齐:8191 是2^13- 1,这意味着~8191除了低13位之外都是1,因此将其与数字AND运算将清除低13位,即将数字对齐到最近的 2^13 的较低倍数,换句话说,对齐到8K边界)。
然后,它将该地址作为指向指针的指针进行解释,并从中加载所指地址。有关更多信息,请参见Understanding the getting of task_struct pointer from process kernel stack
之后,它尝试定位存储在该地址之后某个特定结构的位置:它查找以下1024-13个无符号整数,试图找到存储当前进程信息(可能)的内存位置:当它找到一个保存当前UID和GID的多个副本的内存片段时,它就认为已经找到了这个位置。在这种情况下,它会修改它,以便当前进程获得UID和GID 0,使进程在root下运行(此外,它将所有的能力标志存储为全1)。
请参阅struct cred

9

我会再次回答这个问题,因为这里确实有一些需要补充的内容。

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(我们回想一下,有符号数和无符号数在位运算中的行为完全相同)。至于为什么他们不使用十六进制常量编写它们,谁会关心呢。这些人知道自己2的幂次方,读取8191可能比读取0x1FFF更快。

1
为了严格遵守ISO标准,应在两个地方使用uintptr_t而不是ptrdiff_t。然而,所有Linux ABIs都保证对于所有T,sizeof(unsigned long) == sizeof(T*)。因此,在Linux内核漏洞的上下文中使代码正确的最小更改只需将& ~8191替换为& ~8191UL - zwol
当使用类型为int~8191与左侧为unsigned long&表达式时,会应用符号扩展。因此,高位不会被掩码削减。这也是为什么uint64_t x = -1;会将所有64位设置为1的原因。 - mortehu
1
@mortehu:我曾在其他地方使用过那个片段,结果被烧伤了。如果int的位数比unsigned long少,转换就会出错。 - Joshua

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接