49

In some exploits for getting the root shell, I often see such a pointer:

int i;
unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191); 

Can anyone explain this pointer a little bit? I think 8191 is the size of the kernel stack. p points to the bottom of the kernel stack? Here is how pointer p is used:

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++; 
} 
HuangJie
  • 1,488
  • 1
  • 16
  • 33
  • 3
    The value `8191` in binary is `1111111111111`, and the `long` type is 32 bits. I think to give you a firm answer, we would need to see how the `*p` pointer is being used. The `&` operator is probably a bit mask of some sort. – Tim Biegeleisen Oct 23 '15 at 06:26
  • @TimBiegeleisen Thanks for your reply. I have edited it. – HuangJie Oct 23 '15 at 06:38

2 Answers2

53

The code takes the address of the local variable i to get a pointer into the current stack frame. Then, it aligns the address to 8K page (that is what you do with x & ~8191: 8191 is 2^13 - 1 which means ~8191 is all ones except the low 13 bits, so ANDing it with a number will clear the low 13 bits, i.e. align the number to the nearest lower multiple of 2^13, in other words, align to 8K boundary).

It then takes this address and interprets it as a pointer to a pointer and loads the pointed address from it. See Understanding the getting of task_struct pointer from process kernel stack for further information.

After that, it tries to locate a specific structure stored somewhere after that address: It looks through the following 1024-13 unsigneds, trying to find a place in memory where the current process information (probably) is stored: When it finds a piece of memory holding multiple copies of the current UID and GID, it presumes it has found it. In that case, it modifies it so that the current process gets UID and GID 0, making the process running under root (plus it stores all-ones into the following capability flags).

Cf. struct cred.

Community
  • 1
  • 1
Mormegil
  • 7,955
  • 4
  • 42
  • 77
9

I'm going to post yet another answer because there really is something to add here.

unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191); 

results in p being the pointer to the start of the 8192 byte size block of memory. However, the code is wrong. If p is above INT_MAX (which it can be or it would be cast to unsigned, not unsigned long), the high bits get sheared off by the mask. Correct code is as follows:

unsigned *p = *(unsigned**)(((ptrdiff_t)&i) & ~(ptrdiff_t)8191);

or using uintptr_t:

unsigned *p = *(unsigned**)(((uintptr_t)&i) & ~(uintptr_t)8191U);

It is necessary to cast to integer and back to pointer for the code to work; however to guarantee an int-sized pointer requires use of ptrdiff_t (we recall that signed and unsigned behave exactly the same for bitwise operations). As for why they don't write them with hex constants, who cares. The guys who do these kinds of things know their powers of 2 by heart. It may be faster to read 8191 then 0x1FFF.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 1
    For scrupulous ISO-correctness, `uintptr_t` instead of `ptrdiff_t` in both places. All Linux ABIs, however, guarantee `sizeof(unsigned long) == sizeof(T*)` for all T. So the *minimal* change to make the code correct in the context of a Linux kernel exploit is simply `& ~8191UL` instead of `& ~8191`. – zwol Oct 23 '15 at 20:09
  • When `~8191`, of type `int`, is used in the `&`-expression with an `unsigned long` on the left-hand side, sign extension is applied. Hence, the high bits will not get sheared off the mask. This is the same reason why `uint64_t x = -1;` sets all 64 bits to 1. – mortehu Oct 23 '15 at 20:13
  • 1
    @mortehu: I got burned using that fragment elsewhere. If int has fewer bits than unsigned long the conversions do the wrong thing. – Joshua Oct 23 '15 at 21:05