Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explanation of a pointer in exploit code

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++;  }  
like image 389
HuangJie Avatar asked Oct 23 '15 06:10

HuangJie


People also ask

How do UAF exploits work?

Exploiting UAFsAn attacker can use UAFs to pass arbitrary code — or a reference to it — to a program and navigate to the beginning of the code by using a dangling pointer. In this way, execution of the malicious code can allow the cybercriminal to gain control over a victim's system.

What is pointer subterfuge?

Pointer subterfuge is a general term for exploits that modify a pointer's value. A pointer is a variable that contains the address of a function, array element, or other data structure. Function pointers can be overwritten to transfer control to attacker- supplied shellcode.

What is public exploit code?

An exploit is a code that takes advantage of a software vulnerability or security flaw. It is written either by security researchers as a proof-of-concept threat or by malicious actors for use in their operations.

What is a use after free error?

Use after free errors occur when a program continues to use a pointer after it has been freed. Like double free errors and memory leaks, use after free errors have two common and sometimes overlapping causes: Error conditions and other exceptional circumstances.


2 Answers

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.

like image 84
Mormegil Avatar answered Sep 17 '22 12:09

Mormegil


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.

like image 20
Joshua Avatar answered Sep 19 '22 12:09

Joshua